feat: reactive tool flow, with debounce

This commit is contained in:
2026-08-22 21:06:50 +05:00
parent 5560435b55
commit d0ee4ec77f
3 changed files with 36 additions and 14 deletions
+19 -8
View File
@@ -21,6 +21,10 @@
const isInfo = $derived(tool.resultType === 'info'); const isInfo = $derived(tool.resultType === 'info');
const sanitized = $derived(sanitizeParams(tool, values)); const sanitized = $derived(sanitizeParams(tool, values));
let runToken = 0;
let lastRunSource: PixelImage | null = null;
let lastRunValuesJson = '';
async function handleFile(file: File) { async function handleFile(file: File) {
errorText = ''; errorText = '';
status = 'processing'; status = 'processing';
@@ -41,20 +45,27 @@
async function runTool() { async function runTool() {
if (!source || isInfo) return; if (!source || isInfo) return;
const token = ++runToken;
lastRunSource = source;
lastRunValuesJson = JSON.stringify(sanitized);
try { try {
result = await tool.run(source, sanitized); const next = await tool.run(source, sanitized);
if (token !== runToken) return;
result = next;
status = 'loaded'; status = 'loaded';
} catch (e) { } catch (e) {
if (token !== runToken) return;
showError(e); showError(e);
} }
} }
function apply() { $effect(() => {
if (!source || isInfo || status === 'processing') return; const valuesJson = JSON.stringify(sanitized);
errorText = ''; if (source === lastRunSource && valuesJson === lastRunValuesJson) return;
status = 'processing'; if (!source || isInfo) return;
runTool(); const timer = setTimeout(() => void runTool(), 300);
} return () => clearTimeout(timer);
});
function showError(e: unknown) { function showError(e: unknown) {
status = source ? 'loaded' : 'idle'; status = source ? 'loaded' : 'idle';
@@ -123,7 +134,7 @@
</div> </div>
{#if source && !isInfo} {#if source && !isInfo}
<ParamsCard params={tool.params} bind:values busy={status === 'processing'} onApply={apply} /> <ParamsCard params={tool.params} bind:values />
{/if} {/if}
</section> </section>
@@ -1,23 +1,19 @@
<script lang="ts"> <script lang="ts">
import type { ParamDef } from '$lib/registry';
import ParamForm from '../ParamForm.svelte'; import ParamForm from '../ParamForm.svelte';
import Button from '../ui/Button.svelte'; import type { ParamDef } from '$lib/registry';
interface Props { interface Props {
params: ParamDef[]; params: ParamDef[];
values: Record<string, any>; values: Record<string, any>;
busy: boolean;
onApply: () => void;
} }
let { params, values = $bindable(), busy, onApply }: Props = $props(); let { params, values = $bindable() }: Props = $props();
</script> </script>
<div class="panel root"> <div class="panel root">
<h2 class="heading-section">Параметры</h2> <h2 class="heading-section">Параметры</h2>
{#if params.length > 0} {#if params.length > 0}
<ParamForm {params} bind:values /> <ParamForm {params} bind:values />
<Button onclick={onApply} {busy} busyText="Обработка…" fullWidth>Применить</Button>
{:else} {:else}
<p class="hint text-caption text-muted"> <p class="hint text-caption text-muted">
У этого инструмента нет параметров — результат уже готов. У этого инструмента нет параметров — результат уже готов.
@@ -57,6 +57,9 @@
{:else} {:else}
<div class="media"> <div class="media">
<Preview image={result} /> <Preview image={result} />
{#if status === 'processing'}
<span class="recalc" aria-live="polite">Пересчёт…</span>
{/if}
</div> </div>
<DownloadButton <DownloadButton
image={result} image={result}
@@ -88,5 +91,17 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 16rem; min-height: 16rem;
position: relative;
}
.recalc {
position: absolute;
top: var(--space-2);
right: var(--space-2);
padding: 2px var(--space-2);
border-radius: var(--radius-s);
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
color: var(--accent);
font-size: var(--text-xs);
} }
</style> </style>