1
0
mirror of https://github.com/MultiMote/niimblue synced 2026-01-19 19:37:11 +03:00
Files
niimblue/src/lib/LabelPropsEditor.svelte

291 lines
8.9 KiB
Svelte

<script lang="ts">
import { LabelPresetSchema, type LabelPreset, type LabelProps, type LabelUnit } from "../types";
import LabelPresetsBrowser from "./LabelPresetsBrowser.svelte";
import { printerMeta } from "../stores";
import { tr } from "../utils/i18n";
import { DEFAULT_LABEL_PRESETS } from "../defaults";
import { onMount, tick } from "svelte";
import { LocalStoragePersistence } from "../utils/persistence";
import type { PrintDirection } from "@mmote/niimbluelib";
import MdIcon from "./MdIcon.svelte";
import { Toasts } from "../utils/toasts";
import { FileUtils } from "../utils/file_utils";
import { z } from "zod";
export let labelProps: LabelProps;
export let onChange: (newProps: LabelProps) => void;
let labelPresets: LabelPreset[] = DEFAULT_LABEL_PRESETS;
let title: string | undefined = "";
let prevUnit: LabelUnit = "mm";
let unit: LabelUnit = "mm";
let dpmm = 8;
let width = 0;
let height = 0;
let printDirection: PrintDirection = "left";
let error: string = "";
const onApply = () => {
let newWidth = width;
let newHeight = height;
// mm to px
if (unit === "mm") {
newWidth *= dpmm;
newHeight *= dpmm;
}
// limit min siz
newWidth = newWidth < dpmm ? dpmm : newWidth;
newHeight = newHeight < dpmm ? dpmm : newHeight;
// width must me multiple of dpmm
if (printDirection === "left") {
newHeight -= newHeight % dpmm;
} else {
newWidth -= newWidth % dpmm;
}
onChange({
printDirection: printDirection,
size: {
width: Math.floor(newWidth),
height: Math.floor(newHeight),
},
});
};
const onLabelPresetSelected = (index: number) => {
const preset = labelPresets[index];
dpmm = preset.dpmm;
prevUnit = preset.unit;
unit = preset.unit;
printDirection = preset.printDirection;
width = preset.width;
height = preset.height;
title = preset.title ?? "";
onApply();
};
const onLabelPresetDelete = (idx: number) => {
const result = [...labelPresets];
result.splice(idx, 1);
labelPresets = result;
LocalStoragePersistence.saveLabelPresets(labelPresets);
};
const onLabelPresetAdd = () => {
const newPreset: LabelPreset = {
dpmm,
printDirection,
unit,
width,
height,
title,
};
const newPresets = [...labelPresets, newPreset];
try {
LocalStoragePersistence.saveLabelPresets(newPresets);
labelPresets = newPresets;
} catch (e) {
Toasts.zodErrors(e, "Presets save error:");
}
};
const onFlip = () => {
let widthTmp = width;
width = height;
height = widthTmp;
printDirection = printDirection === "top" ? "left" : "top";
};
const onUnitChange = () => {
if (prevUnit === "mm" && unit === "px") {
width = Math.floor(width * dpmm);
height = Math.floor(height * dpmm);
} else if (prevUnit === "px" && unit === "mm") {
width = Math.floor(width / dpmm);
height = Math.floor(height / dpmm);
}
prevUnit = unit;
};
const checkError = (props: LabelProps) => {
error = "";
if ($printerMeta !== undefined) {
const headSize = props.printDirection == "left" ? props.size.height : props.size.width;
if (headSize > $printerMeta.printheadPixels) {
error += $tr("params.label.warning.width") + " ";
error += `(${headSize} > ${$printerMeta.printheadPixels})`;
error += "\n";
}
if ($printerMeta.printDirection !== props.printDirection) {
error += $tr("params.label.warning.direction") + " ";
if ($printerMeta.printDirection == "left") {
error += $tr("params.label.direction.left");
} else {
error += $tr("params.label.direction.top");
}
}
}
};
const fillWithCurrentParams = () => {
prevUnit = "px";
width = labelProps.size.width;
height = labelProps.size.height;
printDirection = labelProps.printDirection;
onUnitChange();
};
const onImportClicked = async () => {
const contents = await FileUtils.pickAndReadTextFile("json");
const rawData = JSON.parse(contents);
if (!confirm($tr("params.label.warning.import"))) {
return;
}
try {
const presets = z.array(LabelPresetSchema).parse(rawData);
LocalStoragePersistence.saveLabelPresets(presets);
labelPresets = presets;
} catch (e) {
Toasts.zodErrors(e, "Presets load error:");
}
};
const onExportClicked = () => {
try {
FileUtils.saveLabelPresetsAsJson(labelPresets);
} catch (e) {
Toasts.zodErrors(e, "Presets save error:");
}
};
onMount(() => {
const defaultPreset: LabelPreset = DEFAULT_LABEL_PRESETS[0];
width = defaultPreset.width;
height = defaultPreset.height;
prevUnit = defaultPreset.unit;
unit = defaultPreset.unit;
printDirection = defaultPreset.printDirection;
try {
const savedPresets: LabelPreset[] | null = LocalStoragePersistence.loadLabelPresets();
if (savedPresets !== null) {
labelPresets = savedPresets;
}
} catch (e) {
Toasts.zodErrors(e, "Presets load error:");
}
tick().then(() => fillWithCurrentParams());
});
$: checkError(labelProps);
</script>
<div class="dropdown">
<button class="btn btn-sm btn-secondary" data-bs-toggle="dropdown" data-bs-auto-close="outside">
<MdIcon icon="settings" />
</button>
<div class="dropdown-menu">
<h6 class="dropdown-header">{$tr("params.label.menu_title")}</h6>
<div class="px-3">
<div class="p-1">
<button class="btn btn-sm btn-outline-secondary" on:click={onImportClicked}>
<MdIcon icon="data_object" />
{$tr("params.label.import")}
</button>
<button class="btn btn-sm btn-outline-secondary" on:click={onExportClicked}>
<MdIcon icon="data_object" />
{$tr("params.label.export")}
</button>
</div>
<div class="mb-3 {error ? 'cursor-help text-warning' : 'text-secondary'}" title={error}>
{$tr("params.label.current")}
{labelProps.size.width}x{labelProps.size.height}
{$tr("params.label.px")}
{#if labelProps.printDirection === "top"}
({$tr("params.label.direction")} {$tr("params.label.direction.top")})
{:else if labelProps.printDirection === "left"}
({$tr("params.label.direction")} {$tr("params.label.direction.left")})
{/if}
<button class="btn btn-sm" on:click={fillWithCurrentParams}><MdIcon icon="arrow_downward" /></button>
</div>
<LabelPresetsBrowser
class="mb-1"
presets={labelPresets}
onItemSelected={onLabelPresetSelected}
onItemDelete={onLabelPresetDelete} />
<div class="input-group flex-nowrap input-group-sm mb-3">
<span class="input-group-text">{$tr("params.label.size")}</span>
<input class="form-control" type="number" min="1" step={dpmm} bind:value={width} />
<button class="btn btn-sm btn-secondary" on:click={onFlip}><MdIcon icon="swap_horiz" /></button>
<input class="form-control" type="number" min="1" step={dpmm} bind:value={height} />
<select class="form-select" bind:value={unit} on:change={onUnitChange}>
<option value="mm"> {$tr("params.label.mm")}</option>
<option value="px"> {$tr("params.label.px")}</option>
</select>
</div>
<div class="input-group flex-nowrap input-group-sm mb-3">
<span class="input-group-text">{$tr("params.label.head_density")}</span>
<input class="form-control" type="number" min="1" bind:value={dpmm} />
<span class="input-group-text cursor-help" title={$tr("params.label.head_density.help")}>
{$tr("params.label.dpmm")}
</span>
</div>
<div class="input-group flex-nowrap input-group-sm mb-3">
<span class="input-group-text">{$tr("params.label.direction")}</span>
<select class="form-select" bind:value={printDirection}>
<option value="left">
{#if $printerMeta?.printDirection === "left"}{/if}
{$tr("params.label.direction.left")}
</option>
<option value="top">
{#if $printerMeta?.printDirection === "top"}{/if}
{$tr("params.label.direction.top")}
</option>
</select>
</div>
<div class="input-group flex-nowrap input-group-sm mb-3">
<span class="input-group-text">{$tr("params.label.label_title")}</span>
<input class="form-control" type="text" bind:value={title} />
</div>
<div class="text-end">
<button class="btn btn-sm btn-secondary" on:click={onLabelPresetAdd}>
{$tr("params.label.save_template")}
</button>
<button class="btn btn-sm btn-primary" on:click={onApply}>{$tr("params.label.apply")}</button>
</div>
</div>
</div>
</div>
<style>
.dropdown-menu {
width: 100vw;
max-width: 450px;
}
.cursor-help {
cursor: help;
}
</style>