From 52cb9c8a1148f50825f9761f5bfbf8ba0776fca2 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sat, 10 Oct 2026 04:10:13 +0500 Subject: [PATCH] refactor: separate SchemaAlignedLayout component --- docs/architecture-audit-2026-10.md | 25 +- .../schema/SchemaAlignedLayout.svelte | 355 ++++++++++++++++++ .../components/schema/SchemaToolView.svelte | 289 +++----------- 3 files changed, 411 insertions(+), 258 deletions(-) create mode 100644 web/src/lib/components/schema/SchemaAlignedLayout.svelte diff --git a/docs/architecture-audit-2026-10.md b/docs/architecture-audit-2026-10.md index deccb3a..ca38b0b 100644 --- a/docs/architecture-audit-2026-10.md +++ b/docs/architecture-audit-2026-10.md @@ -21,19 +21,22 @@ ## High -### H1. `SchemaToolView.svelte` — god-component, 1064 строки +### H1. `SchemaToolView.svelte` — god-component — В РАБОТЕ -`web/src/lib/components/schema/SchemaToolView.svelte` — ~10 ответственностей в -одном компоненте: состояние цепочки шагов (`init` :218, `setStepValue` :256, -`resetStep` :304, `replaceStepTool` :337), загрузка файлов (`handleFiles` :355), -оркестрация запуска (`run` :430), политика скачивания с encode/fitWithinBytes -(`download` :467), clipboard (`copyText` :491), paste-listener (`onMount` :548), -вычисление mask-превью (:579), drag&drop шагов (:349), aspect-lock (:272-299), -две большие раскладки в разметке (workspace :739-803, aligned :804-943). +План декомпозиции (подшаги, каждый отдельным ревью-коммитом): -Кандидаты на вынос: `useChainRunner` (run/download/clipboard), `useStepMasks`, -`schema-tool-state.svelte.ts` (стейт шагов), `AlignedLayout.svelte` / -`WorkspaceLayout.svelte`. +- **H1a — СДЕЛАНО (2026-10):** aligned-раскладка вынесена в + `SchemaAlignedLayout.svelte` (view-only: разметка + scoped-стили, сниппет + `stepCard` остался в родителе и передаётся пропсом). Родитель уменьшился + примерно на 200 строк. +- **H1b (далее):** runner (run/download/clipboard, `runGen`/`running`, + `assignResult`, `toDisplayError`) → `schema-tool-runner.svelte.ts`. +- **H1c (далее):** стейт шагов (мутации `steps`, `touchedByStep`, `lastAxis`) → + `schema-tool-state.svelte.ts`. + +Исходная формулировка: ~10 ответственностей в одном компоненте — состояние +цепочки шагов, загрузка файлов, оркестрация запуска, политика скачивания, +clipboard, paste-listener, mask-превью, drag&drop, aspect-lock, две раскладки. ### H2. Race condition превью — СДЕЛАНО (2026-10) diff --git a/web/src/lib/components/schema/SchemaAlignedLayout.svelte b/web/src/lib/components/schema/SchemaAlignedLayout.svelte new file mode 100644 index 0000000..e054863 --- /dev/null +++ b/web/src/lib/components/schema/SchemaAlignedLayout.svelte @@ -0,0 +1,355 @@ + + +{#snippet maskChip(i: number)} + {#if stepMaskable[i]} + ontogglestepmask(i)} + /> + {/if} +{/snippet} + +
+
+
+
+ {t("resultCard.previewPanel")} + + +
+
+ {#if errorText} + + {/if} + {#each warnings as w (w)} +

{w}

+ {/each} + {#if canExtend} +
+ onaddstep(0, id)} /> +
+
+ {/if} + {#each steps as step, i (step.key)} + {@const input = i === 0 ? null : stepResults[i - 1]} + {@const out = stepResults[i]} + {#if i > 0} +
+
+
+ {/if} +
+ {@render stepCard(step, i)} +
+ {#if i === 0} + + {:else} + {@const inSet = stepFileSets[i - 1] ?? []} + 1 ? inSet.length : undefined} + > + {#if inSet.length > 1} + + {:else if input} + + {:else} + {t("resultCard.noResult")} + {/if} + + {/if} + {#if i === steps.length - 1} + ontogglestepmask(i) + : undefined} + {oncopy} + {ondownloadtxt} + /> + {:else} + {@const outSet = stepFileSets[i] ?? []} + 1 ? outSet.length : undefined} + > + {#snippet actions()} + {@render maskChip(i)} + {/snippet} + {#if outSet.length > 1 && !stepMaskOn[i]} + + {:else if out} + + {:else} + {t("resultCard.noResult")} + {/if} + + {/if} +
+
+ {#if canExtend} +
+ onaddstep(i + 1, id)} /> +
+
+ {/if} + {/each} +
+ + diff --git a/web/src/lib/components/schema/SchemaToolView.svelte b/web/src/lib/components/schema/SchemaToolView.svelte index 7160097..6075183 100644 --- a/web/src/lib/components/schema/SchemaToolView.svelte +++ b/web/src/lib/components/schema/SchemaToolView.svelte @@ -3,9 +3,7 @@ import { resolve } from "$app/paths"; import StepCard from "$lib/components/display/StepCard.svelte"; import Button from "$lib/components/ui/Button.svelte"; - import ChipButton from "$lib/components/ui/ChipButton.svelte"; import { ButtonVariantDefine } from "$lib/components/ui/define"; - import Toggle from "$lib/components/ui/Toggle.svelte"; import { isFav, toggleFav } from "$lib/favorites.svelte"; import { Star } from "@lucide/svelte"; import { hasTransparency } from "$lib/core/analyze"; @@ -17,7 +15,6 @@ encode, fitWithinBytes, outputFormatByMime, - toDataUrl, type OutputMime, } from "$lib/core/io"; import type { PixelImage } from "$lib/core/types"; @@ -73,13 +70,9 @@ import { onMount } from "svelte"; import { SvelteMap, SvelteSet } from "svelte/reactivity"; import AddStepButton from "./AddStepButton.svelte"; - import PreviewTile from "./PreviewTile.svelte"; - import PartsGrid from "./PartsGrid.svelte"; - import SchemaActions from "./SchemaActions.svelte"; + import SchemaAlignedLayout from "./SchemaAlignedLayout.svelte"; import SchemaFields from "./SchemaFields.svelte"; import SchemaPreview from "./SchemaPreview.svelte"; - import SchemaResultTile from "./SchemaResultTile.svelte"; - import SchemaSourceTile from "./SchemaSourceTile.svelte"; import ToolPickerButton from "./ToolPickerButton.svelte"; interface Props { @@ -732,16 +725,6 @@ {/snippet} - {#snippet maskChip(i: number)} - {#if stepMaskable[i]} - toggleStepMask(steps[i].key)} - /> - {/if} - {/snippet} - {#if !alignedMode}
@@ -808,144 +791,47 @@

{warningText(w)}

{/each} {:else} -
-
-
-
- {t("resultCard.previewPanel")} - - (format = v)} - onquality={setFormatQuality} - onlimit={(v) => (limitKb = v)} - /> -
-
- {#if errorText} - - {/if} - {#each allWarnings as w (warningText(w))} -

{warningText(w)}

- {/each} - {#if canExtend} -
- addStepAt(0, id)} /> -
-
- {/if} - {#each steps as step, i (step.key)} - {@const input = i === 0 ? null : stepResults[i - 1]} - {@const out = stepResults[i]} - {#if i > 0} -
-
-
- {/if} -
- {@render stepCard(step, i)} -
- {#if i === 0} - (textSource = v)} - onrendertext={run} - onupload={handleFile} - onuploadmany={handleFiles} - /> - {:else} - {@const inSet = stepFileSets[i - 1] ?? []} - 1 ? inSet.length : undefined} - > - {#if inSet.length > 1} - - {:else if input} - - {:else} - {t("resultCard.noResult")} - {/if} - - {/if} - {#if i === steps.length - 1} - toggleStepMask(steps[i].key) - : undefined} - oncopy={copyText} - ondownloadtxt={downloadText} - /> - {:else} - {@const outSet = stepFileSets[i] ?? []} - 1 ? outSet.length : undefined} - > - {#snippet actions()} - {@render maskChip(i)} - {/snippet} - {#if outSet.length > 1 && !stepMaskOn[i]} - - {:else if out} - - {:else} - {t("resultCard.noResult")} - {/if} - - {/if} -
-
- {#if canExtend} -
- addStepAt(i + 1, id)} /> -
-
- {/if} - {/each} -
+ (textSource = v)} + onrendertext={run} + onupload={handleFile} + onuploadmany={handleFiles} + ondownload={download} + oncopy={copyText} + ondownloadtxt={downloadText} + onformat={(v) => (format = v)} + onquality={setFormatQuality} + onlimit={(v) => (limitKb = v)} + /> {/if}
{:else} @@ -1011,105 +897,14 @@ .no-schema { font: var(--font-size-s) var(--font-mono); } - .aligned { - display: grid; - } - .aligned-row { - display: grid; - grid-template-columns: minmax(var(--size-workspace-min), 1fr) 2fr; - gap: var(--space-xxl); - align-items: start; - } - /* Сегменты справа образуют «одну панель»: общий фон, боковые границы, - верх у шапки и низ у последней строки. */ - .panel-segment { - background: var(--color-panel); - border-left: var(--size-border) solid var(--color-border); - border-right: var(--size-border) solid var(--color-border); - padding: var(--space-xl); - } - .head-segment { - display: flex; - align-items: center; - gap: var(--space-xl); - flex-wrap: wrap; - border-top: var(--size-border) solid var(--color-border); - border-bottom: var(--size-border) solid var(--color-border); - border-radius: var(--radius-m) var(--radius-m) 0 0; - } - .head-segment .label { - font: var(--font-size-s) var(--font-mono); - letter-spacing: var(--space-text-l); - text-transform: uppercase; - color: var(--color-main); - } - .pair-segment { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--space-l); - } - /* Коннектор в строках «Add step»: продолжает боковые границы панели. */ - .connect-segment { - align-self: stretch; - padding: 0; - } - .connect-segment.add-last { - border-bottom: var(--size-border) solid var(--color-border); - border-radius: 0 0 var(--radius-m) var(--radius-m); - } - /* Полоса-разделитель между группами: ячейки с фоном и боковыми границами - (как у карточки слева и панели справа), сверху — линия через всю ширину. */ - .group-divider { - position: relative; - display: grid; - grid-template-columns: minmax(var(--size-workspace-min), 1fr) 2fr; - gap: var(--space-xxl); - } - .gd-cell { - grid-column: 2; - background: var(--color-panel); - border-left: var(--size-border) solid var(--color-border); - border-right: var(--size-border) solid var(--color-border); - padding: var(--space-s) 0; - } - .group-divider::after { - content: ""; - position: absolute; - inset: 0 0 auto; - border-top: var(--size-border) solid var(--color-border); - } - .last-segment { - border-bottom: var(--size-border) solid var(--color-border); - border-radius: 0 0 var(--radius-m) var(--radius-m); - } - .align-toggle { - display: inline-flex; - align-items: center; - gap: var(--space-m); - margin-right: auto; - color: var(--color-text-muted); - font: var(--font-size-s) var(--font-mono); - cursor: pointer; - } - .error { - margin: 0; - color: var(--color-danger); - font: var(--font-size-s) var(--font-mono); - } .warn { margin: 0; color: var(--color-warning); font: var(--font-size-s) var(--font-mono); } - .empty { - font: var(--font-size-s) var(--font-mono); - } @media (--bp-tablet) { .workspace { grid-template-columns: 1fr; } - .aligned-row { - grid-template-columns: 1fr; - } }