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