diff --git a/src/defaults.ts b/src/defaults.ts
index 5953170..35370ed 100644
--- a/src/defaults.ts
+++ b/src/defaults.ts
@@ -1,5 +1,5 @@
import * as fabric from "fabric";
-import type { LabelPreset, LabelProps } from "./types";
+import type { AppConfig, LabelPreset, LabelProps } from "./types";
/** Default presets for LabelPropsEditor */
export const DEFAULT_LABEL_PRESETS: LabelPreset[] = [
@@ -57,3 +57,7 @@ export const THUMBNAIL_HEIGHT = 48;
/** Generate thumbnail in jpeg format with this quality */
export const THUMBNAIL_QUALITY = 0.7;
+
+export const APP_CONFIG_DEFAULTS: AppConfig = {
+ fitMode: "stretch"
+}
diff --git a/src/lib/GenericObjectParamsControls.svelte b/src/lib/GenericObjectParamsControls.svelte
index bd521c9..2306a93 100644
--- a/src/lib/GenericObjectParamsControls.svelte
+++ b/src/lib/GenericObjectParamsControls.svelte
@@ -2,6 +2,7 @@
import * as fabric from "fabric";
import { tr } from "../utils/i18n";
import MdIcon from "./basic/MdIcon.svelte";
+ import { appConfig } from "../stores";
export let selectedObject: fabric.FabricObject;
export let valueUpdated: () => void;
@@ -17,14 +18,38 @@
};
const fit = () => {
- selectedObject.set({
- left: 0,
- top: 0,
- scaleX: selectedObject.canvas!.width / selectedObject.width,
- scaleY: selectedObject.canvas!.height / selectedObject.height,
- });
+ const imageRatio = selectedObject.width / selectedObject.height;
+ const canvasRatio = selectedObject.canvas!.width / selectedObject.canvas!.height;
+
+ if ($appConfig.fitMode === "ratio_min") {
+ if (imageRatio > canvasRatio) {
+ selectedObject.scaleToWidth(selectedObject.canvas!.width);
+ } else {
+ selectedObject.scaleToHeight(selectedObject.canvas!.height);
+ }
+ selectedObject.canvas!.centerObject(selectedObject);
+ } else if ($appConfig.fitMode === "ratio_max") {
+ if (imageRatio > canvasRatio) {
+ selectedObject.scaleToHeight(selectedObject.canvas!.height);
+ } else {
+ selectedObject.scaleToWidth(selectedObject.canvas!.width);
+ }
+ selectedObject.canvas!.centerObject(selectedObject);
+ } else {
+ selectedObject.set({
+ left: 0,
+ top: 0,
+ scaleX: selectedObject.canvas!.width / selectedObject.width,
+ scaleY: selectedObject.canvas!.height / selectedObject.height,
+ });
+ }
valueUpdated();
};
+
+ const fitModeChanged = (e: Event & { currentTarget: HTMLSelectElement }) => {
+ const fitMode = e.currentTarget.value as "stretch" | "ratio_min" | "ratio_max";
+ appConfig.update((v) => ({ ...v, fitMode: fitMode }));
+ };
{#if selectedObject instanceof fabric.FabricImage}
-
+
+
+
+
+
{/if}
diff --git a/src/locale/dicts/en.json b/src/locale/dicts/en.json
index 1d7c3ef..63f331c 100644
--- a/src/locale/dicts/en.json
+++ b/src/locale/dicts/en.json
@@ -41,6 +41,9 @@
"params.generic.center.horizontal": "Center horizontally",
"params.generic.center.vertical": "Center vertically",
"params.generic.fit": "Fit to page",
+ "params.generic.fit.mode.stretch": "Stretch",
+ "params.generic.fit.mode.ratio_min": "Keep ratio #1",
+ "params.generic.fit.mode.ratio_max": "Keep ratio #2",
"params.label.apply": "Apply",
"params.label.current": "Current parameters:",
"params.label.direction.left": "Left",
diff --git a/src/locale/dicts/ru.json b/src/locale/dicts/ru.json
index 3c36ec5..e45ff66 100644
--- a/src/locale/dicts/ru.json
+++ b/src/locale/dicts/ru.json
@@ -41,6 +41,9 @@
"params.generic.center.horizontal": "Выровнять горизонтально",
"params.generic.center.vertical": "Выровнять вертикально",
"params.generic.fit": "Растянуть под размер страницы",
+ "params.generic.fit.mode.stretch": "Растянуть",
+ "params.generic.fit.mode.ratio_min": "Сохранить соотношение сторон #1",
+ "params.generic.fit.mode.ratio_max": "Сохранить соотношение сторон #2",
"params.label.apply": "Применить",
"params.label.current": "Текущие параметры:",
"params.label.direction.left": "Слева",
diff --git a/src/stores.ts b/src/stores.ts
index 2a141aa..8e77524 100644
--- a/src/stores.ts
+++ b/src/stores.ts
@@ -1,5 +1,5 @@
import { get, readable, writable } from "svelte/store";
-import type { AutomationProps, ConnectionState, ConnectionType } from "./types";
+import { AppConfigSchema, type AppConfig, type AutomationProps, type ConnectionState, type ConnectionType } from "./types";
import {
NiimbotBluetoothClient,
NiimbotCapacitorBleClient,
@@ -16,10 +16,11 @@ import {
} from "@mmote/niimbluelib";
import { Toasts } from "./utils/toasts";
import { tr } from "./utils/i18n";
-import { LocalStoragePersistence } from "./utils/persistence";
-import { OBJECT_DEFAULTS_TEXT } from "./defaults";
+import { LocalStoragePersistence, writablePersisted } from "./utils/persistence";
+import { APP_CONFIG_DEFAULTS, OBJECT_DEFAULTS_TEXT } from "./defaults";
export const fontCache = writable([OBJECT_DEFAULTS_TEXT.fontFamily]);
+export const appConfig = writablePersisted("config", AppConfigSchema, APP_CONFIG_DEFAULTS);
export const connectionState = writable("disconnected");
export const connectedPrinterName = writable("");
diff --git a/src/types.ts b/src/types.ts
index a27cf7f..db5e695 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -86,6 +86,11 @@ export const AutomationPropsSchema = z.object({
startPrint: z.enum(["after_connect", "immediately"]).optional(),
});
+export const AppConfigSchema = z.object({
+ /** Keep image aspect ration when using "fit" button */
+ fitMode: z.enum(["stretch", "ratio_min", "ratio_max"]).optional(),
+});
+
export type LabelProps = z.infer;
export type LabelPreset = z.infer;
export type FabricJson = z.infer;
@@ -93,3 +98,4 @@ export type ExportedLabelTemplate = z.infer;
export type PreviewPropsOffset = z.infer;
export type PreviewProps = z.infer;
export type AutomationProps = z.infer;
+export type AppConfig = z.infer;
diff --git a/src/utils/persistence.ts b/src/utils/persistence.ts
index 65923bd..3279e08 100644
--- a/src/utils/persistence.ts
+++ b/src/utils/persistence.ts
@@ -14,6 +14,40 @@ import {
} from "../types";
import { z } from "zod";
import { FileUtils } from "./file_utils";
+import { get, writable, type Updater, type Writable } from "svelte/store";
+
+/** Writable store, value is persisted to localStorage */
+export function writablePersisted(key: string, schema: z.ZodType, initialValue: T): Writable {
+ const wr = writable(initialValue);
+
+ console.log("read");
+
+ try {
+ const val = LocalStoragePersistence.loadAndValidateObject(key, schema);
+ if (val != null) {
+ wr.set(val);
+ } else {
+ wr.set(initialValue);
+ }
+ } catch (_e) {
+ wr.set(initialValue);
+ }
+
+ return {
+ subscribe: wr.subscribe,
+
+ set: (value: T) => {
+ LocalStoragePersistence.validateAndSaveObject(key, value, schema);
+ wr.set(value);
+ },
+
+ update: (updater: Updater) => {
+ const newValue: T = updater(get(wr));
+ LocalStoragePersistence.validateAndSaveObject(key, newValue, schema);
+ wr.set(newValue);
+ },
+ };
+}
export class LocalStoragePersistence {
/** Result in kilobytes */