mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
refactor: separate schema preview logic
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import MetaList from "$lib/components/MetaList.svelte";
|
||||
import { buildSchemaPreviewModel } from "$lib/components/schema-preview-model";
|
||||
import SchemaActions from "$lib/components/SchemaActions.svelte";
|
||||
import SchemaResultTile from "$lib/components/SchemaResultTile.svelte";
|
||||
import SchemaSourceTile from "$lib/components/SchemaSourceTile.svelte";
|
||||
@@ -46,36 +47,11 @@
|
||||
ondownload,
|
||||
}: Props = $props();
|
||||
|
||||
const sourceValue = $derived.by(() => {
|
||||
switch (inputMode) {
|
||||
case "none":
|
||||
return "—";
|
||||
case "text":
|
||||
return t("textInput.heading");
|
||||
case "image":
|
||||
return source ? `${source.width} × ${source.height} px` : "—";
|
||||
}
|
||||
});
|
||||
const resultValue = $derived(
|
||||
resultKind === "image"
|
||||
? result
|
||||
? `${result.width} × ${result.height} px`
|
||||
: "—"
|
||||
: resultKind === "files"
|
||||
? fileResult && fileResult.files.length > 0
|
||||
? `${fileResult.files.length} ${t("resultCard.parts")}`
|
||||
: "—"
|
||||
: textResult
|
||||
? t("textInput.heading")
|
||||
: "—",
|
||||
);
|
||||
const formatValue = $derived(resultKind === "files" ? "ZIP (PNG)" : "PNG");
|
||||
const hasResult = $derived(
|
||||
resultKind === "image"
|
||||
? Boolean(result)
|
||||
: resultKind === "files"
|
||||
? Boolean(fileResult && fileResult.files.length > 0)
|
||||
: Boolean(textResult),
|
||||
const model = $derived(
|
||||
buildSchemaPreviewModel(
|
||||
{ inputMode, resultKind, source, result, fileResult, textResult },
|
||||
t,
|
||||
),
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -83,7 +59,7 @@
|
||||
<span class="label">{t("resultCard.previewPanel")}</span>
|
||||
<SchemaActions
|
||||
{inputMode}
|
||||
canDownload={hasResult}
|
||||
canDownload={model.hasResult}
|
||||
{running}
|
||||
{onupload}
|
||||
{ondownload}
|
||||
@@ -119,9 +95,9 @@
|
||||
<div class="meta">
|
||||
<MetaList
|
||||
items={[
|
||||
{ caption: t("sourceCard.source"), value: sourceValue },
|
||||
{ caption: t("resultCard.result"), value: resultValue },
|
||||
{ caption: t("resultCard.format"), value: formatValue },
|
||||
{ caption: t("sourceCard.source"), value: model.sourceValue },
|
||||
{ caption: t("resultCard.result"), value: model.resultValue },
|
||||
{ caption: t("resultCard.format"), value: model.formatValue },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { makeImage } from "../core/test-helpers";
|
||||
import {
|
||||
buildSchemaPreviewModel,
|
||||
type PreviewTranslate,
|
||||
} from "./schema-preview-model";
|
||||
|
||||
const image = makeImage(2, 3, [
|
||||
[1, 2, 3, 255],
|
||||
[4, 5, 6, 255],
|
||||
[7, 8, 9, 255],
|
||||
[10, 11, 12, 255],
|
||||
[13, 14, 15, 255],
|
||||
[16, 17, 18, 255],
|
||||
]);
|
||||
|
||||
const translate: PreviewTranslate = (key) => {
|
||||
if (key === "resultCard.parts") return "parts";
|
||||
if (key === "textInput.heading") return "Text";
|
||||
return key;
|
||||
};
|
||||
|
||||
function input(
|
||||
overrides: Partial<Parameters<typeof buildSchemaPreviewModel>[0]> = {},
|
||||
) {
|
||||
return {
|
||||
inputMode: "image" as const,
|
||||
resultKind: "image" as const,
|
||||
source: null,
|
||||
result: null,
|
||||
fileResult: null,
|
||||
textResult: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildSchemaPreviewModel", () => {
|
||||
it("formats source values for every input mode", () => {
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ inputMode: "none" }), translate),
|
||||
).toMatchObject({ sourceValue: "—" });
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ inputMode: "text" }), translate),
|
||||
).toMatchObject({ sourceValue: "Text" });
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ inputMode: "image", source: image }),
|
||||
translate,
|
||||
),
|
||||
).toMatchObject({ sourceValue: "2 × 3 px" });
|
||||
});
|
||||
|
||||
it("formats image results and enables download only with a result", () => {
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ result: image }), translate),
|
||||
).toEqual({
|
||||
sourceValue: "—",
|
||||
resultValue: "2 × 3 px",
|
||||
formatValue: "PNG",
|
||||
hasResult: true,
|
||||
});
|
||||
expect(buildSchemaPreviewModel(input(), translate)).toMatchObject({
|
||||
resultValue: "—",
|
||||
hasResult: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("formats file results as ZIP and rejects empty file results", () => {
|
||||
const fileResult = { files: [{ name: "part.png", image }] };
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ resultKind: "files", fileResult }),
|
||||
translate,
|
||||
),
|
||||
).toEqual({
|
||||
sourceValue: "—",
|
||||
resultValue: "1 parts",
|
||||
formatValue: "ZIP (PNG)",
|
||||
hasResult: true,
|
||||
});
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ resultKind: "files", fileResult: { files: [] } }),
|
||||
translate,
|
||||
),
|
||||
).toMatchObject({ resultValue: "—", hasResult: false });
|
||||
});
|
||||
|
||||
it.each(["text", "verdict"] as const)(
|
||||
"uses text result presence for %s output",
|
||||
(resultKind) => {
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ resultKind, textResult: "done" }),
|
||||
translate,
|
||||
),
|
||||
).toMatchObject({
|
||||
resultValue: "Text",
|
||||
formatValue: "PNG",
|
||||
hasResult: true,
|
||||
});
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ resultKind }), translate),
|
||||
).toMatchObject({
|
||||
resultValue: "—",
|
||||
hasResult: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { PixelImage } from "../core/types";
|
||||
import type { FileResult, InputMode, ResultKind } from "../registry";
|
||||
|
||||
export type PreviewTranslate = (key: string) => string;
|
||||
|
||||
export type SchemaPreviewModelInput = {
|
||||
inputMode: InputMode;
|
||||
resultKind: ResultKind;
|
||||
source: PixelImage | null;
|
||||
result: PixelImage | null;
|
||||
fileResult: FileResult | null;
|
||||
textResult: string | null;
|
||||
};
|
||||
|
||||
export type SchemaPreviewModel = {
|
||||
sourceValue: string;
|
||||
resultValue: string;
|
||||
formatValue: string;
|
||||
hasResult: boolean;
|
||||
};
|
||||
|
||||
export function buildSchemaPreviewModel(
|
||||
input: SchemaPreviewModelInput,
|
||||
translate: PreviewTranslate,
|
||||
): SchemaPreviewModel {
|
||||
const { inputMode, resultKind, source, result, fileResult, textResult } =
|
||||
input;
|
||||
const sourceValue =
|
||||
inputMode === "none"
|
||||
? "—"
|
||||
: inputMode === "text"
|
||||
? translate("textInput.heading")
|
||||
: source
|
||||
? `${source.width} × ${source.height} px`
|
||||
: "—";
|
||||
const hasFiles = Boolean(fileResult && fileResult.files.length > 0);
|
||||
const resultValue =
|
||||
resultKind === "image"
|
||||
? result
|
||||
? `${result.width} × ${result.height} px`
|
||||
: "—"
|
||||
: resultKind === "files"
|
||||
? hasFiles
|
||||
? `${fileResult?.files.length} ${translate("resultCard.parts")}`
|
||||
: "—"
|
||||
: textResult
|
||||
? translate("textInput.heading")
|
||||
: "—";
|
||||
return {
|
||||
sourceValue,
|
||||
resultValue,
|
||||
formatValue: resultKind === "files" ? "ZIP (PNG)" : "PNG",
|
||||
hasResult:
|
||||
resultKind === "image"
|
||||
? Boolean(result)
|
||||
: resultKind === "files"
|
||||
? hasFiles
|
||||
: Boolean(textResult),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user