mirror of
https://github.com/MultiMote/niimblue
synced 2026-01-19 19:37:11 +03:00
Add image fit modes (closes #68)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 }));
|
||||
};
|
||||
</script>
|
||||
|
||||
<button class="btn btn-sm btn-secondary" on:click={putToCenterV} title={$tr("params.generic.center.vertical")}>
|
||||
@@ -34,7 +59,18 @@
|
||||
<MdIcon icon="horizontal_distribute" />
|
||||
</button>
|
||||
{#if selectedObject instanceof fabric.FabricImage}
|
||||
<button class="btn btn-sm btn-secondary" on:click={fit} title={$tr("params.generic.fit")}>
|
||||
<MdIcon icon="fit_screen" />
|
||||
</button>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-secondary" on:click={fit} title={$tr("params.generic.fit")}>
|
||||
<MdIcon icon="fit_screen" />
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split px-1" data-bs-toggle="dropdown"
|
||||
></button>
|
||||
<div class="dropdown-menu p-1">
|
||||
<select class="form-select form-select-sm" value={$appConfig.fitMode ?? "stretch"} on:change={fitModeChanged}>
|
||||
<option value="stretch">{$tr("params.generic.fit.mode.stretch")}</option>
|
||||
<option value="ratio_min">{$tr("params.generic.fit.mode.ratio_min")}</option>
|
||||
<option value="ratio_max">{$tr("params.generic.fit.mode.ratio_max")}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Слева",
|
||||
|
||||
@@ -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<string[]>([OBJECT_DEFAULTS_TEXT.fontFamily]);
|
||||
export const appConfig = writablePersisted<AppConfig>("config", AppConfigSchema, APP_CONFIG_DEFAULTS);
|
||||
|
||||
export const connectionState = writable<ConnectionState>("disconnected");
|
||||
export const connectedPrinterName = writable<string>("");
|
||||
|
||||
@@ -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<typeof LabelPropsSchema>;
|
||||
export type LabelPreset = z.infer<typeof LabelPresetSchema>;
|
||||
export type FabricJson = z.infer<typeof FabricJsonSchema>;
|
||||
@@ -93,3 +98,4 @@ export type ExportedLabelTemplate = z.infer<typeof ExportedLabelTemplateSchema>;
|
||||
export type PreviewPropsOffset = z.infer<typeof PreviewPropsOffsetSchema>;
|
||||
export type PreviewProps = z.infer<typeof PreviewPropsSchema>;
|
||||
export type AutomationProps = z.infer<typeof AutomationPropsSchema>;
|
||||
export type AppConfig = z.infer<typeof AppConfigSchema>;
|
||||
|
||||
@@ -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<T>(key: string, schema: z.ZodType<T>, initialValue: T): Writable<T> {
|
||||
const wr = writable<T>(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<T>) => {
|
||||
const newValue: T = updater(get(wr));
|
||||
LocalStoragePersistence.validateAndSaveObject(key, newValue, schema);
|
||||
wr.set(newValue);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class LocalStoragePersistence {
|
||||
/** Result in kilobytes */
|
||||
|
||||
Reference in New Issue
Block a user