diff --git a/web/src/app.css b/web/src/app.css index 7ef31a3..2a3b3a8 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -162,6 +162,7 @@ --size-step-grip: 20px; --size-card-min: 240px; --size-card-min-narrow: 200px; + --size-parts-grid-min: 120px; --size-workspace-min: 260px; --size-content-max: 880px; --size-content-max-wide: 1680px; diff --git a/web/src/lib/components/SchemaPreview.svelte b/web/src/lib/components/SchemaPreview.svelte index e088cc4..1c5f4ee 100644 --- a/web/src/lib/components/SchemaPreview.svelte +++ b/web/src/lib/components/SchemaPreview.svelte @@ -4,13 +4,14 @@ import SchemaResultTile from "$lib/components/SchemaResultTile.svelte"; import SchemaSourceTile from "$lib/components/SchemaSourceTile.svelte"; import type { PixelImage } from "$lib/core/types"; - import type { InputMode, ResultKind } from "$lib/registry"; + import type { FileResult, InputMode, ResultKind } from "$lib/registry"; interface Props { inputMode: InputMode; resultKind?: ResultKind; source: PixelImage | null; result: PixelImage | null; + fileResult?: FileResult | null; textSource?: string; textResult?: string | null; running?: boolean; @@ -28,6 +29,7 @@ resultKind = "image", source, result, + fileResult = null, textSource = "", textResult = null, running = false, @@ -56,9 +58,21 @@ ? result ? `${result.width} × ${result.height} px` : "—" - : textResult - ? "text" - : "—", + : resultKind === "files" + ? fileResult + ? `${fileResult.files.length} parts` + : "—" + : textResult + ? "text" + : "—", + ); + const formatValue = $derived(resultKind === "files" ? "ZIP (PNG)" : "PNG"); + const hasResult = $derived( + resultKind === "image" + ? Boolean(result) + : resultKind === "files" + ? Boolean(fileResult) + : Boolean(textResult), ); @@ -68,7 +82,7 @@ > {})} @@ -92,8 +106,8 @@ diff --git a/web/src/lib/components/SchemaResultTile.svelte b/web/src/lib/components/SchemaResultTile.svelte index 37bf7b5..47ffe84 100644 --- a/web/src/lib/components/SchemaResultTile.svelte +++ b/web/src/lib/components/SchemaResultTile.svelte @@ -1,14 +1,15 @@ -
+
RESULT + {#if resultKind === "files" && fileResult} + {fileResult.files.length} parts + {/if} {#if running} {/if} @@ -54,6 +66,15 @@ ondownload={ondownloadtxt ?? (() => {})} /> + {:else if resultKind === "files" && partUrls.length > 0} +
+ {#each partUrls as part (part.name)} +
+ {part.name} +
{part.name}
+
+ {/each} +
{:else if resultKind === "image" && resultUrl} result {:else if running} @@ -83,7 +104,7 @@ min-height: clamp(var(--space-brand), 30vh, 60vh); border: var(--size-border) solid var(--color-border); border-radius: var(--radius-s); - overflow: hidden; + overflow: auto; background: var(--color-background-muted); color: var(--color-text-muted); } @@ -103,6 +124,47 @@ .empty { font: var(--font-size-s) var(--font-mono); } + .count { + margin-left: var(--space-m); + color: var(--color-main); + } + .parts-grid { + display: grid; + grid-template-columns: repeat( + auto-fill, + minmax(var(--size-parts-grid-min), 1fr) + ); + gap: var(--space-m); + width: 100%; + height: 100%; + padding: var(--space-l); + box-sizing: border-box; + align-content: start; + } + .part { + margin: 0; + display: flex; + flex-direction: column; + gap: var(--space-s); + } + .part img { + width: 100%; + height: auto; + display: block; + border: var(--size-border) solid var(--color-border); + border-radius: var(--radius-s); + background: repeating-conic-gradient( + var(--color-checker-main) 0 25%, + var(--color-checker-alt) 0 50% + ) + 50% / 16px 16px; + } + .part figcaption { + font: var(--font-size-s) var(--font-mono); + color: var(--color-text-muted); + text-align: center; + overflow-wrap: anywhere; + } .text-result-wrap { width: 100%; padding: var(--space-l); diff --git a/web/src/lib/components/SchemaToolView.svelte b/web/src/lib/components/SchemaToolView.svelte index d342428..0191e2b 100644 --- a/web/src/lib/components/SchemaToolView.svelte +++ b/web/src/lib/components/SchemaToolView.svelte @@ -7,7 +7,8 @@ import type { PixelImage } from "$lib/core/types"; import { execute } from "$lib/executor"; import { t } from "$lib/i18n/t"; - import type { ToolEntry } from "$lib/registry"; + import type { FileResult, ToolEntry } from "$lib/registry"; + import { downloadZip } from "$lib/zip"; import { defaultSchemaParams, sanitizeSchemaParams, @@ -29,6 +30,7 @@ let values = $state>({}); let source = $state(null); let result = $state(null); + let fileResult = $state(null); let textSource = $state(""); let textResult = $state(null); let running = $state(false); @@ -50,6 +52,7 @@ function reset() { values = schema ? defaultSchemaParams(schema) : {}; result = null; + fileResult = null; textResult = null; error = ""; } @@ -76,9 +79,15 @@ if (resultKind === "image") { result = out as PixelImage; textResult = null; + fileResult = null; + } else if (resultKind === "files") { + fileResult = out as FileResult; + result = null; + textResult = null; } else { textResult = out as string; result = null; + fileResult = null; } } catch (e) { error = errorText(e); @@ -88,7 +97,13 @@ } async function download() { - if (!result || !schema) return; + if (!schema) return; + if (resultKind === "files") { + if (!fileResult) return; + await downloadZip(fileResult.files, `${tool.id}.zip`); + return; + } + if (!result) return; const out = tool.output; const quality = out?.qualityParamId ? Number(values[out.qualityParamId]) / 100 @@ -164,6 +179,7 @@