mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
refactor: group props in view models
This commit is contained in:
@@ -67,13 +67,15 @@ handleFiles, эффекты, workspace-разметка). Декомпозици
|
|||||||
Переработка самого паттерна `$effect(() => init())` — часть H1, отдельно не
|
Переработка самого паттерна `$effect(() => init())` — часть H1, отдельно не
|
||||||
делалась.
|
делалась.
|
||||||
|
|
||||||
### M3. `SchemaPreview.svelte` — интерфейс из ~40 пропсов
|
### M3. `SchemaPreview.svelte` — интерфейс из ~40 пропсов — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
`web/src/lib/components/schema/SchemaPreview.svelte:22-103`: весь стейт
|
Пропсы сгруппированы в 4 view-model объекта по внутреннему потребителю: `head`
|
||||||
оркестратора проброшен вниз по одному (`stepResults`, `stepMaskable`,
|
(SchemaActions), `source` (SchemaSourceTile), `steps` (промежуточные тайлы),
|
||||||
`stepMaskOn`, `stepMasks`, `format`, `quality`, `limitKb`, `alphaLoss` + 11
|
`result` (SchemaResultTile). Интерфейсы — в `schema-preview-model.ts`
|
||||||
колбэков `on*`). Нужен группирующий view-model-объект или разбиение на
|
(`PreviewHeadModel` и др.); имена полей зеркалят пропсы потребителей, поэтому
|
||||||
подкомпоненты.
|
внутри работает спред (`<SchemaActions {...head}>`). Плоскими остались 7
|
||||||
|
идентифицирующих пропсов (inputMode, resultKind, toolId, running, error,
|
||||||
|
aligned, ontogglealign).
|
||||||
|
|
||||||
### M4. `runMask` обходил санитизацию параметров — СДЕЛАНО (2026-10)
|
### M4. `runMask` обходил санитизацию параметров — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { hasPreviewResult } from "./schema-preview-model";
|
import {
|
||||||
|
hasPreviewResult,
|
||||||
|
type PreviewHeadModel,
|
||||||
|
type PreviewResultModel,
|
||||||
|
type PreviewSourceModel,
|
||||||
|
type PreviewStepsModel,
|
||||||
|
} from "./schema-preview-model";
|
||||||
import SchemaActions from "./SchemaActions.svelte";
|
import SchemaActions from "./SchemaActions.svelte";
|
||||||
import PartsGrid from "./PartsGrid.svelte";
|
import PartsGrid from "./PartsGrid.svelte";
|
||||||
import SchemaResultTile from "./SchemaResultTile.svelte";
|
import SchemaResultTile from "./SchemaResultTile.svelte";
|
||||||
@@ -9,101 +15,50 @@
|
|||||||
import ChipButton from "$lib/components/ui/ChipButton.svelte";
|
import ChipButton from "$lib/components/ui/ChipButton.svelte";
|
||||||
import { toDataUrl } from "$lib/core/io";
|
import { toDataUrl } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import type { OutputMime } from "$lib/core/io";
|
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import type {
|
import type { InputMode, ResultKind } from "$lib/registry";
|
||||||
FileResult,
|
|
||||||
InputMode,
|
|
||||||
ResultKind,
|
|
||||||
ResultNote,
|
|
||||||
ToolImageFile,
|
|
||||||
} from "$lib/registry";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
inputMode: InputMode;
|
inputMode: InputMode;
|
||||||
resultKind?: ResultKind;
|
resultKind?: ResultKind;
|
||||||
toolId: string;
|
toolId: string;
|
||||||
source: PixelImage | null;
|
|
||||||
sources?: ToolImageFile[];
|
|
||||||
fileCount?: number;
|
|
||||||
result: PixelImage | null;
|
|
||||||
resultNote?: ResultNote | null;
|
|
||||||
fileResult?: FileResult | null;
|
|
||||||
textSource?: string;
|
|
||||||
textResult?: string | null;
|
|
||||||
textVars?: Record<string, string | number>;
|
|
||||||
mask?: PixelImage | null;
|
|
||||||
maskOn?: boolean;
|
|
||||||
ontogglemask?: () => void;
|
|
||||||
running?: boolean;
|
running?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
stepResults?: (PixelImage | null)[];
|
|
||||||
stepFileSets?: ToolImageFile[][];
|
|
||||||
stepMaskable?: boolean[];
|
|
||||||
stepMaskOn?: boolean[];
|
|
||||||
stepMasks?: (PixelImage | null)[];
|
|
||||||
ontogglestepmask?: (i: number) => void;
|
|
||||||
aligned?: boolean;
|
aligned?: boolean;
|
||||||
ontogglealign?: () => void;
|
ontogglealign?: () => void;
|
||||||
format?: OutputMime;
|
head: PreviewHeadModel;
|
||||||
quality?: number;
|
source: PreviewSourceModel;
|
||||||
limitKb?: number;
|
steps?: PreviewStepsModel;
|
||||||
alphaLoss?: boolean;
|
result: PreviewResultModel;
|
||||||
onupload: (file: File) => void;
|
|
||||||
onuploadmany?: (files: File[]) => void;
|
|
||||||
ontextsource?: (text: string) => void;
|
|
||||||
onrendertext?: () => void;
|
|
||||||
oncopytext?: () => void;
|
|
||||||
ondownloadtxt?: () => void;
|
|
||||||
ondownload: () => void;
|
|
||||||
onformat?: (mime: OutputMime) => void;
|
|
||||||
onquality?: (value: number) => void;
|
|
||||||
onlimit?: (kb: number | undefined) => void;
|
|
||||||
}
|
}
|
||||||
let {
|
let {
|
||||||
inputMode,
|
inputMode,
|
||||||
resultKind = "image",
|
resultKind = "image",
|
||||||
toolId,
|
toolId,
|
||||||
source,
|
|
||||||
sources = [],
|
|
||||||
fileCount = 0,
|
|
||||||
result,
|
|
||||||
resultNote = null,
|
|
||||||
fileResult = null,
|
|
||||||
textSource = "",
|
|
||||||
textResult = null,
|
|
||||||
textVars = undefined,
|
|
||||||
mask = null,
|
|
||||||
maskOn = false,
|
|
||||||
ontogglemask,
|
|
||||||
running = false,
|
running = false,
|
||||||
error = "",
|
error = "",
|
||||||
stepResults = [],
|
|
||||||
stepFileSets = [],
|
|
||||||
stepMaskable = [],
|
|
||||||
stepMaskOn = [],
|
|
||||||
stepMasks = [],
|
|
||||||
ontogglestepmask,
|
|
||||||
aligned = false,
|
aligned = false,
|
||||||
ontogglealign,
|
ontogglealign,
|
||||||
format = "image/png",
|
head,
|
||||||
quality = undefined,
|
source,
|
||||||
limitKb = undefined,
|
steps = {},
|
||||||
alphaLoss = false,
|
result,
|
||||||
onupload,
|
|
||||||
onuploadmany,
|
|
||||||
ontextsource,
|
|
||||||
onrendertext,
|
|
||||||
oncopytext,
|
|
||||||
ondownloadtxt,
|
|
||||||
ondownload,
|
|
||||||
onformat,
|
|
||||||
onquality,
|
|
||||||
onlimit,
|
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const stepResults = $derived(steps.results ?? []);
|
||||||
|
const stepFileSets = $derived(steps.fileSets ?? []);
|
||||||
|
const stepMaskable = $derived(steps.maskable ?? []);
|
||||||
|
const stepMaskOn = $derived(steps.maskOn ?? []);
|
||||||
|
const stepMasks = $derived(steps.masks ?? []);
|
||||||
|
|
||||||
const hasResult = $derived(
|
const hasResult = $derived(
|
||||||
hasPreviewResult({ inputMode, resultKind, result, fileResult, textResult }),
|
hasPreviewResult({
|
||||||
|
inputMode,
|
||||||
|
resultKind,
|
||||||
|
result: result.result,
|
||||||
|
fileResult: result.fileResult ?? null,
|
||||||
|
textResult: result.textResult ?? null,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -124,15 +79,7 @@
|
|||||||
{resultKind}
|
{resultKind}
|
||||||
canDownload={hasResult}
|
canDownload={hasResult}
|
||||||
{running}
|
{running}
|
||||||
{format}
|
{...head}
|
||||||
{quality}
|
|
||||||
{limitKb}
|
|
||||||
{alphaLoss}
|
|
||||||
{onupload}
|
|
||||||
{ondownload}
|
|
||||||
{onformat}
|
|
||||||
{onquality}
|
|
||||||
{onlimit}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -143,15 +90,9 @@
|
|||||||
<div class="pair">
|
<div class="pair">
|
||||||
<SchemaSourceTile
|
<SchemaSourceTile
|
||||||
mode={inputMode}
|
mode={inputMode}
|
||||||
{source}
|
|
||||||
{sources}
|
|
||||||
{textSource}
|
|
||||||
{running}
|
{running}
|
||||||
{fileCount}
|
onupload={head.onupload}
|
||||||
ontextinput={ontextsource}
|
{...source}
|
||||||
{onrendertext}
|
|
||||||
{onupload}
|
|
||||||
{onuploadmany}
|
|
||||||
/>
|
/>
|
||||||
{#each stepResults.slice(0, -1) as img, i (i)}
|
{#each stepResults.slice(0, -1) as img, i (i)}
|
||||||
{@const set = stepFileSets[i] ?? []}
|
{@const set = stepFileSets[i] ?? []}
|
||||||
@@ -167,7 +108,7 @@
|
|||||||
<ChipButton
|
<ChipButton
|
||||||
label={t("resultCard.maskToggle")}
|
label={t("resultCard.maskToggle")}
|
||||||
active={stepMaskOn[i] ?? false}
|
active={stepMaskOn[i] ?? false}
|
||||||
onclick={() => ontogglestepmask?.(i)}
|
onclick={() => steps.ontogglestepmask?.(i)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -184,7 +125,7 @@
|
|||||||
<ChipButton
|
<ChipButton
|
||||||
label={t("resultCard.maskToggle")}
|
label={t("resultCard.maskToggle")}
|
||||||
active={stepMaskOn[i] ?? false}
|
active={stepMaskOn[i] ?? false}
|
||||||
onclick={() => ontogglestepmask?.(i)}
|
onclick={() => steps.ontogglestepmask?.(i)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -197,21 +138,7 @@
|
|||||||
</PreviewTile>
|
</PreviewTile>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<SchemaResultTile
|
<SchemaResultTile {resultKind} {toolId} {running} {...result} />
|
||||||
{resultKind}
|
|
||||||
{result}
|
|
||||||
{resultNote}
|
|
||||||
{fileResult}
|
|
||||||
{textResult}
|
|
||||||
{textVars}
|
|
||||||
{toolId}
|
|
||||||
{running}
|
|
||||||
{mask}
|
|
||||||
{maskOn}
|
|
||||||
{ontogglemask}
|
|
||||||
oncopy={oncopytext}
|
|
||||||
{ondownloadtxt}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -500,48 +500,54 @@
|
|||||||
<section class="panel">
|
<section class="panel">
|
||||||
<SchemaPreview
|
<SchemaPreview
|
||||||
toolId={lastTool.id}
|
toolId={lastTool.id}
|
||||||
{source}
|
|
||||||
sources={sourceFiles}
|
|
||||||
fileCount={sourceFiles.length}
|
|
||||||
{result}
|
|
||||||
{resultNote}
|
|
||||||
{fileResult}
|
|
||||||
{textSource}
|
|
||||||
{textResult}
|
|
||||||
textVars={verdictVars}
|
|
||||||
{inputMode}
|
{inputMode}
|
||||||
resultKind={displayResultKind}
|
resultKind={displayResultKind}
|
||||||
{running}
|
{running}
|
||||||
error={errorText}
|
error={errorText}
|
||||||
{format}
|
|
||||||
quality={currentQuality}
|
|
||||||
{limitKb}
|
|
||||||
{alphaLoss}
|
|
||||||
{stepResults}
|
|
||||||
{stepFileSets}
|
|
||||||
{aligned}
|
{aligned}
|
||||||
{stepMaskable}
|
|
||||||
{stepMaskOn}
|
|
||||||
{stepMasks}
|
|
||||||
ontogglestepmask={toggleStepMaskByIndex}
|
|
||||||
mask={stepMasks.at(-1) ?? null}
|
|
||||||
maskOn={stepMaskOn.at(-1) ?? false}
|
|
||||||
ontogglemask={stepMaskable.at(-1)
|
|
||||||
? () => toggleStepMask(steps[steps.length - 1].key)
|
|
||||||
: undefined}
|
|
||||||
ontogglealign={() => (aligned = !aligned)}
|
ontogglealign={() => (aligned = !aligned)}
|
||||||
onformat={(v) => (format = v)}
|
head={{
|
||||||
onquality={setFormatQuality}
|
format,
|
||||||
onlimit={(v) => (limitKb = v)}
|
quality: currentQuality,
|
||||||
onupload={handleFile}
|
limitKb,
|
||||||
onuploadmany={handleFiles}
|
alphaLoss,
|
||||||
ontextsource={(textValue) => {
|
onupload: handleFile,
|
||||||
textSource = textValue;
|
ondownload: download,
|
||||||
|
onformat: (v) => (format = v),
|
||||||
|
onquality: setFormatQuality,
|
||||||
|
onlimit: (v) => (limitKb = v),
|
||||||
|
}}
|
||||||
|
source={{
|
||||||
|
source,
|
||||||
|
sources: sourceFiles,
|
||||||
|
fileCount: sourceFiles.length,
|
||||||
|
textSource,
|
||||||
|
ontextinput: (v) => (textSource = v),
|
||||||
|
onrendertext: run,
|
||||||
|
onuploadmany: handleFiles,
|
||||||
|
}}
|
||||||
|
steps={{
|
||||||
|
results: stepResults,
|
||||||
|
fileSets: stepFileSets,
|
||||||
|
maskable: stepMaskable,
|
||||||
|
maskOn: stepMaskOn,
|
||||||
|
masks: stepMasks,
|
||||||
|
ontogglestepmask: toggleStepMaskByIndex,
|
||||||
|
}}
|
||||||
|
result={{
|
||||||
|
result,
|
||||||
|
resultNote,
|
||||||
|
fileResult,
|
||||||
|
textResult,
|
||||||
|
textVars: verdictVars,
|
||||||
|
mask: stepMasks.at(-1) ?? null,
|
||||||
|
maskOn: stepMaskOn.at(-1) ?? false,
|
||||||
|
ontogglemask: stepMaskable.at(-1)
|
||||||
|
? () => toggleStepMask(steps[steps.length - 1].key)
|
||||||
|
: undefined,
|
||||||
|
oncopy: copyText,
|
||||||
|
ondownloadtxt: downloadText,
|
||||||
}}
|
}}
|
||||||
onrendertext={run}
|
|
||||||
oncopytext={copyText}
|
|
||||||
ondownloadtxt={downloadText}
|
|
||||||
ondownload={download}
|
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
|
import type { OutputMime } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import type { FileResult, InputMode, ResultKind } from "$lib/registry";
|
import type {
|
||||||
|
FileResult,
|
||||||
|
InputMode,
|
||||||
|
ResultKind,
|
||||||
|
ResultNote,
|
||||||
|
ToolImageFile,
|
||||||
|
} from "$lib/registry";
|
||||||
|
|
||||||
export type SchemaPreviewModelInput = {
|
export type SchemaPreviewModelInput = {
|
||||||
inputMode: InputMode;
|
inputMode: InputMode;
|
||||||
@@ -18,3 +25,54 @@ export function hasPreviewResult(input: SchemaPreviewModelInput): boolean {
|
|||||||
}
|
}
|
||||||
return Boolean(textResult);
|
return Boolean(textResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grouped view-models of SchemaPreview: one object per inner consumer
|
||||||
|
// (field names mirror the consumer's own props, so they spread directly).
|
||||||
|
|
||||||
|
/** SchemaActions: download policy and format settings. */
|
||||||
|
export interface PreviewHeadModel {
|
||||||
|
format: OutputMime;
|
||||||
|
quality?: number;
|
||||||
|
limitKb?: number;
|
||||||
|
alphaLoss?: boolean;
|
||||||
|
onupload: (file: File) => void;
|
||||||
|
ondownload: () => void;
|
||||||
|
onformat?: (mime: OutputMime) => void;
|
||||||
|
onquality?: (value: number) => void;
|
||||||
|
onlimit?: (kb: number | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** SchemaSourceTile minus mode/running/onupload (top-level/head props). */
|
||||||
|
export interface PreviewSourceModel {
|
||||||
|
source: PixelImage | null;
|
||||||
|
sources?: ToolImageFile[];
|
||||||
|
fileCount?: number;
|
||||||
|
textSource?: string;
|
||||||
|
ontextinput?: (text: string) => void;
|
||||||
|
onrendertext?: () => void;
|
||||||
|
onuploadmany?: (files: File[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Intermediate step tiles (chain mode). */
|
||||||
|
export interface PreviewStepsModel {
|
||||||
|
results?: (PixelImage | null)[];
|
||||||
|
fileSets?: ToolImageFile[][];
|
||||||
|
maskable?: boolean[];
|
||||||
|
maskOn?: boolean[];
|
||||||
|
masks?: (PixelImage | null)[];
|
||||||
|
ontogglestepmask?: (i: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** SchemaResultTile minus resultKind/toolId/running (top-level props). */
|
||||||
|
export interface PreviewResultModel {
|
||||||
|
result: PixelImage | null;
|
||||||
|
resultNote?: ResultNote | null;
|
||||||
|
fileResult?: FileResult | null;
|
||||||
|
textResult?: string | null;
|
||||||
|
textVars?: Record<string, string | number>;
|
||||||
|
mask?: PixelImage | null;
|
||||||
|
maskOn?: boolean;
|
||||||
|
ontogglemask?: () => void;
|
||||||
|
oncopy?: () => void;
|
||||||
|
ondownloadtxt?: () => void;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user