feat: add dimension tools for new flow
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { RotateCcw } from "@lucide/svelte";
|
||||
import type { ToolSchema } from "$lib/registry-schema";
|
||||
import type { ToolSchema, Dimension } from "$lib/registry-schema";
|
||||
import DimensionField from "./fields/DimensionField.svelte";
|
||||
|
||||
interface Props {
|
||||
schema: ToolSchema<Record<string, unknown>>;
|
||||
@@ -50,6 +51,15 @@
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
{:else if field.spec.kind === "dimension"}
|
||||
<div class="control">
|
||||
<DimensionField
|
||||
label={labelOf(id)}
|
||||
value={(values[id] as Dimension) ?? { width: field.spec.width, height: field.spec.height }}
|
||||
spec={field.spec}
|
||||
oninput={(v) => onchange(id, v)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Check, Upload } from "@lucide/svelte";
|
||||
import { Check, Sparkles, Upload } from "@lucide/svelte";
|
||||
import { toDataUrl } from "$lib/core/io";
|
||||
import type { PixelImage } from "$lib/core/types";
|
||||
import MetaList from "$lib/components/kit/MetaList.svelte";
|
||||
@@ -10,10 +10,12 @@
|
||||
result: PixelImage | null;
|
||||
running: boolean;
|
||||
error: string;
|
||||
isGenerator?: boolean;
|
||||
onupload: (file: File) => void;
|
||||
ongenerate?: () => void;
|
||||
ondownload: () => void;
|
||||
}
|
||||
let { source, result, running, error, onupload, ondownload }: Props =
|
||||
let { source, result, running, error, isGenerator = false, onupload, ongenerate, ondownload }: Props =
|
||||
$props();
|
||||
|
||||
let sourceUrl = $derived(source ? toDataUrl(source) : null);
|
||||
@@ -21,19 +23,29 @@
|
||||
</script>
|
||||
|
||||
<div class="panel-head">
|
||||
<span class="label">SOURCE / RESULT</span>
|
||||
<span class="label">{isGenerator ? "GENERATOR / RESULT" : "SOURCE / RESULT"}</span>
|
||||
<div class="head-actions">
|
||||
<label class="upload">
|
||||
<Upload size={14} /> Open image
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onchange={(e) => {
|
||||
const f = (e.target as HTMLInputElement).files?.[0];
|
||||
if (f) onupload(f);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
{#if isGenerator}
|
||||
<button
|
||||
class="upload generate-btn"
|
||||
onclick={ongenerate}
|
||||
disabled={running}
|
||||
>
|
||||
<Sparkles size={14} /> {running ? "Generating…" : "Generate"}
|
||||
</button>
|
||||
{:else}
|
||||
<label class="upload">
|
||||
<Upload size={14} /> Open image
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onchange={(e) => {
|
||||
const f = (e.target as HTMLInputElement).files?.[0];
|
||||
if (f) onupload(f);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
<DownloadButton label="Download result" onclick={ondownload} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,17 +55,19 @@
|
||||
{/if}
|
||||
|
||||
<div class="pair">
|
||||
<figure class="tile">
|
||||
<figcaption><span>SOURCE</span></figcaption>
|
||||
<div class="canvas">
|
||||
{#if sourceUrl}
|
||||
<img src={sourceUrl} alt="source" />
|
||||
{:else}
|
||||
<span class="empty">choose an image</span>
|
||||
{/if}
|
||||
</div>
|
||||
</figure>
|
||||
<figure class="tile">
|
||||
{#if !isGenerator}
|
||||
<figure class="tile">
|
||||
<figcaption><span>SOURCE</span></figcaption>
|
||||
<div class="canvas">
|
||||
{#if sourceUrl}
|
||||
<img src={sourceUrl} alt="source" />
|
||||
{:else}
|
||||
<span class="empty">choose an image</span>
|
||||
{/if}
|
||||
</div>
|
||||
</figure>
|
||||
{/if}
|
||||
<figure class="tile" class:full={isGenerator}>
|
||||
<figcaption><span>RESULT {running ? "…" : ""}</span></figcaption>
|
||||
<div class="canvas checker">
|
||||
{#if resultUrl}
|
||||
@@ -61,7 +75,7 @@
|
||||
{:else if running}
|
||||
<Check size={22} />
|
||||
{:else}
|
||||
<span class="empty">no result yet</span>
|
||||
<span class="empty">{isGenerator ? "click Generate" : "no result yet"}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</figure>
|
||||
@@ -115,6 +129,15 @@
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
.generate-btn {
|
||||
background: var(--color-main);
|
||||
border-color: var(--color-main);
|
||||
color: white;
|
||||
}
|
||||
.generate-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.upload input {
|
||||
display: none;
|
||||
}
|
||||
@@ -128,6 +151,9 @@
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-l);
|
||||
}
|
||||
.pair > .full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.tile figcaption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { debounce } from "$lib/core/debounce";
|
||||
import { decodeFile, encode } from "$lib/core/io";
|
||||
import type { PixelImage } from "$lib/core/types";
|
||||
import { executeStep } from "$lib/preview/executor";
|
||||
import { executeGenerate, executeStep } from "$lib/preview/executor";
|
||||
import type { ToolEntry } from "$lib/registry-new";
|
||||
import {
|
||||
defaultSchemaParams,
|
||||
@@ -21,6 +21,8 @@
|
||||
tool.schema as ToolSchema<Record<string, unknown>> | undefined,
|
||||
);
|
||||
|
||||
const isGenerator = $derived(Boolean(tool.generate && !tool.run));
|
||||
|
||||
let values = $state<Record<string, unknown>>({});
|
||||
let source = $state<PixelImage | null>(null);
|
||||
let result = $state<PixelImage | null>(null);
|
||||
@@ -56,12 +58,32 @@
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (!schema || !tool.run || !source) return;
|
||||
if (!schema) return;
|
||||
if (isGenerator) {
|
||||
if (!tool.generate) return;
|
||||
await runGenerate();
|
||||
return;
|
||||
}
|
||||
if (!source) return;
|
||||
error = "";
|
||||
running = true;
|
||||
const params = sanitizeSchemaParams(schema, values);
|
||||
try {
|
||||
result = await executeStep(tool, source, params);
|
||||
} catch (e) {
|
||||
error = errorText(e);
|
||||
} finally {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function runGenerate() {
|
||||
if (!schema || !tool.generate) return;
|
||||
running = true;
|
||||
error = "";
|
||||
const params = sanitizeSchemaParams(schema, values);
|
||||
try {
|
||||
result = await executeStep(tool, source, params);
|
||||
result = await executeGenerate(tool, params);
|
||||
} catch (e) {
|
||||
error = errorText(e);
|
||||
} finally {
|
||||
@@ -85,7 +107,7 @@
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!source || !started) return;
|
||||
if (isGenerator || !source || !started) return;
|
||||
void values;
|
||||
debouncedRun();
|
||||
return () => debouncedRun.cancel();
|
||||
@@ -122,7 +144,9 @@
|
||||
{result}
|
||||
{running}
|
||||
{error}
|
||||
{isGenerator}
|
||||
onupload={handleFile}
|
||||
ongenerate={runGenerate}
|
||||
ondownload={download}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import type { Dimension, DimensionSpec } from "$lib/registry-schema";
|
||||
import NumberField from "./NumberField.svelte";
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
value: Dimension;
|
||||
spec: DimensionSpec;
|
||||
oninput?: (value: Dimension) => void;
|
||||
}
|
||||
let { label, value = $bindable(), spec, oninput }: Props = $props();
|
||||
|
||||
function setAxis(axis: "width" | "height", n: number) {
|
||||
value = { ...value, [axis]: n };
|
||||
oninput?.(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="dimension-field">
|
||||
<span class="dimension-label">{label}</span>
|
||||
<NumberField
|
||||
label="Width"
|
||||
value={value.width}
|
||||
min={spec.min}
|
||||
max={spec.max}
|
||||
oninput={(n) => setAxis("width", n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Height"
|
||||
value={value.height}
|
||||
min={spec.min}
|
||||
max={spec.max}
|
||||
oninput={(n) => setAxis("height", n)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dimension-field {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-m);
|
||||
}
|
||||
.dimension-label {
|
||||
grid-column: 1 / -1;
|
||||
color: var(--foreground);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user