feat: reactive tool flow, with debounce
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user