refactor: remove double logic, change names

This commit is contained in:
2026-10-10 10:07:22 +05:00
parent 637f656229
commit 6288b03ac0
9 changed files with 59 additions and 41 deletions
+14 -11
View File
@@ -145,17 +145,19 @@ round-семантика `clampByte` и trunc-семантика `clampInt` со
## Low ## Low
### L1. Флаг `running` общий для `run()` и `download()` ### L1. Флаг `running` общий для `run()` и `download()` — СДЕЛАНО (2026-10)
`SchemaToolView.svelte:124,433,469`: параллельный auto-run и клик по download В `schema-tool-runner.svelte.ts` флаги разделены: `running` (run) и
гоняют один `$state`; индикация и `disabled` ведут себя некорректно. `downloading` (download); `download()` больше не затирает индикацию
параллельного auto-run. Компонент передаёт в превью
`busy = running || downloading`.
### L2. Дублирование dismiss-логики поповеров ### L2. Дублирование dismiss-логики поповеров — СДЕЛАНО (2026-10)
`SchemaDownload.svelte:62-76` и `ToolPickerButton.svelte:25-39` — побуквенно Общий хелпер `onDismiss(root, close)` в `web/src/lib/components/ui/dismiss.ts`
одинаковые ~15 строк: pointerdown-outside + Escape + подписка на `window` в (pointerdown-outside + Escape + отписка); `SchemaDownload.svelte` и
`$effect`. Кандидат на shared action (`clickOutside`/`popoverDismiss`). Сами `ToolPickerButton.svelte` переведены на него — по ~15 одинаковых строк удалено
эффекты корректны. из каждого.
### L3. Несогласованный API `Toggle` ### L3. Несогласованный API `Toggle`
@@ -178,10 +180,11 @@ round-семантика `clampByte` и trunc-семантика `clampInt` со
`["linear-gradient-png","resize-png","quantize-png"]` (kit — витрина, не `["linear-gradient-png","resize-png","quantize-png"]` (kit — витрина, не
production). production).
### L6. Дублирование CSS-значения превью-тайла ### L6. Дублирование CSS-значения превью-тайла — СДЕЛАНО (2026-10)
`clamp(var(--space-brand), 30vh, 60vh)` в `PreviewTile.svelte:96` и Новый токен `--size-tile-canvas-min: clamp(var(--space-brand), 30vh, 60vh)` в
`SchemaResultTile.svelte:129`; магические `30vh/60vh` не в токенах. `app.css`; `PreviewTile.svelte` и `SchemaResultTile.svelte` используют
`var(--size-tile-canvas-min)`.
### L7. `localStorage.setItem` без try/catch вне app.html — СДЕЛАНО (2026-10) ### L7. `localStorage.setItem` без try/catch вне app.html — СДЕЛАНО (2026-10)
+1
View File
@@ -151,6 +151,7 @@
--size-preview-canvas-min-height-s: 180px; --size-preview-canvas-min-height-s: 180px;
--size-preview-canvas-min-height-l: 360px; --size-preview-canvas-min-height-l: 360px;
--size-tile-canvas-min: clamp(var(--space-brand), 30vh, 60vh);
--size-panel-min-height: 220px; --size-panel-min-height: 220px;
--size-tool-icon: 42px; --size-tool-icon: 42px;
@@ -93,7 +93,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: clamp(var(--space-brand), 30vh, 60vh); min-height: var(--size-tile-canvas-min);
border: var(--size-border) solid var(--color-border); border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s); border-radius: var(--radius-s);
overflow: hidden; overflow: hidden;
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import RangeControl from "$lib/components/fields/schema/RangeControl.svelte"; import RangeControl from "$lib/components/fields/schema/RangeControl.svelte";
import Icon from "$lib/components/ui/Icon.svelte"; import Icon from "$lib/components/ui/Icon.svelte";
import { onDismiss } from "$lib/components/ui/dismiss";
import type { OutputMime } from "$lib/core/io"; import type { OutputMime } from "$lib/core/io";
import { OUTPUT_FORMATS, outputFormatByMime } from "$lib/output-formats"; import { OUTPUT_FORMATS, outputFormatByMime } from "$lib/output-formats";
import type { SliderSpec } from "$lib/registry-schema"; import type { SliderSpec } from "$lib/registry-schema";
@@ -58,18 +59,10 @@
$effect(() => { $effect(() => {
if (!open) return; if (!open) return;
function onPointerDown(e: PointerEvent) { return onDismiss(
if (!root?.contains(e.target as Node)) open = false; () => root,
} () => (open = false),
function onKey(e: KeyboardEvent) { );
if (e.key === "Escape") open = false;
}
window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("keydown", onKey);
return () => {
window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("keydown", onKey);
};
}); });
</script> </script>
@@ -126,7 +126,7 @@
ломал parts-grid в одну колонку. */ ломал parts-grid в одну колонку. */
width: 100%; width: 100%;
align-self: stretch; align-self: stretch;
min-height: clamp(var(--space-brand), 30vh, 60vh); min-height: var(--size-tile-canvas-min);
border: var(--size-border) solid var(--color-border); border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s); border-radius: var(--radius-s);
overflow: auto; overflow: auto;
@@ -142,6 +142,8 @@
}); });
const { run, download, copyText, downloadText } = runner; const { run, download, copyText, downloadText } = runner;
const running = $derived(runner.running); const running = $derived(runner.running);
// Display-level busy: either a run or a download is in flight.
const busy = $derived(running || runner.downloading);
const result = $derived(runner.result); const result = $derived(runner.result);
const fileResult = $derived(runner.fileResult); const fileResult = $derived(runner.fileResult);
const textResult = $derived(runner.textResult); const textResult = $derived(runner.textResult);
@@ -499,7 +501,7 @@
toolId={lastTool.id} toolId={lastTool.id}
{inputMode} {inputMode}
resultKind={displayResultKind} resultKind={displayResultKind}
{running} running={busy}
error={errorText} error={errorText}
{aligned} {aligned}
ontogglealign={() => (aligned = !aligned)} ontogglealign={() => (aligned = !aligned)}
@@ -561,7 +563,7 @@
{source} {source}
{sourceFiles} {sourceFiles}
{textSource} {textSource}
{running} running={busy}
resultKind={displayResultKind} resultKind={displayResultKind}
{result} {result}
{resultNote} {resultNote}
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import Icon from "$lib/components/ui/Icon.svelte"; import Icon from "$lib/components/ui/Icon.svelte";
import { onDismiss } from "$lib/components/ui/dismiss";
import { pageTitle } from "$lib/i18n/schema-tool-strings"; import { pageTitle } from "$lib/i18n/schema-tool-strings";
import { chainablePages } from "$lib/pipeline.svelte"; import { chainablePages } from "$lib/pipeline.svelte";
import type { Page } from "$lib/registry"; import type { Page } from "$lib/registry";
@@ -24,18 +25,10 @@
$effect(() => { $effect(() => {
if (!open) return; if (!open) return;
function onPointerDown(e: PointerEvent) { return onDismiss(
if (!root?.contains(e.target as Node)) open = false; () => root,
} () => (open = false),
function onKey(e: KeyboardEvent) { );
if (e.key === "Escape") open = false;
}
window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("keydown", onKey);
return () => {
window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("keydown", onKey);
};
}); });
</script> </script>
@@ -40,7 +40,10 @@ export interface SchemaToolRunnerCtx {
} }
interface RunnerState { interface RunnerState {
// run() and download() have separate flags: a parallel auto-run and a
// download click must not clobber each other's indicator/disabled state.
running: boolean; running: boolean;
downloading: boolean;
// Generation of the latest run(): a stale in-flight chain (params changed // Generation of the latest run(): a stale in-flight chain (params changed
// while the worker was busy) must not overwrite newer results. // while the worker was busy) must not overwrite newer results.
runGen: number; runGen: number;
@@ -138,7 +141,7 @@ async function run(s: RunnerState, ctx: SchemaToolRunnerCtx) {
async function download(s: RunnerState, ctx: SchemaToolRunnerCtx) { async function download(s: RunnerState, ctx: SchemaToolRunnerCtx) {
if (!ctx.schema()) return; if (!ctx.schema()) return;
s.running = true; s.downloading = true;
s.displayError = null; s.displayError = null;
try { try {
if (s.fileResult) { if (s.fileResult) {
@@ -157,7 +160,7 @@ async function download(s: RunnerState, ctx: SchemaToolRunnerCtx) {
} catch (e) { } catch (e) {
s.displayError = toDisplayError(e); s.displayError = toDisplayError(e);
} finally { } finally {
s.running = false; s.downloading = false;
} }
} }
@@ -198,6 +201,7 @@ function clearResults(s: RunnerState) {
export function createSchemaToolRunner(ctx: SchemaToolRunnerCtx) { export function createSchemaToolRunner(ctx: SchemaToolRunnerCtx) {
const s = $state<RunnerState>({ const s = $state<RunnerState>({
running: false, running: false,
downloading: false,
runGen: 0, runGen: 0,
result: null, result: null,
fileResult: null, fileResult: null,
@@ -213,6 +217,9 @@ export function createSchemaToolRunner(ctx: SchemaToolRunnerCtx) {
get running() { get running() {
return s.running; return s.running;
}, },
get downloading() {
return s.downloading;
},
get result() { get result() {
return s.result; return s.result;
}, },
+19
View File
@@ -0,0 +1,19 @@
// Popover dismiss: close on pointerdown outside the root and on Escape.
// Subscribe inside `$effect` while the popover is open; returns unsubscribe.
export function onDismiss(
root: () => HTMLElement | null,
close: () => void,
): () => void {
function onPointerDown(e: PointerEvent) {
if (!root()?.contains(e.target as Node)) close();
}
function onKey(e: KeyboardEvent) {
if (e.key === "Escape") close();
}
window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("keydown", onKey);
return () => {
window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("keydown", onKey);
};
}