From e662d1df248469f4e554b39a4e42d25a3296192d Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sat, 5 Sep 2026 17:47:41 +0500 Subject: [PATCH] feat: add layout groupt to tools --- docs/plan-composite-params.md | 26 +++- web/src/lib/registry-new/alpha.ts | 98 +++++++++--- web/src/lib/registry-new/filters.ts | 47 ++++-- web/src/lib/registry-new/generate.ts | 218 +++++++++++++++++++-------- web/src/lib/registry-new/geometry.ts | 102 +++++++++---- web/src/lib/registry-new/text.ts | 45 +++--- 6 files changed, 381 insertions(+), 155 deletions(-) diff --git a/docs/plan-composite-params.md b/docs/plan-composite-params.md index a06ad9d..cf2f8a5 100644 --- a/docs/plan-composite-params.md +++ b/docs/plan-composite-params.md @@ -24,8 +24,14 @@ > раскладка `schema.layout` реализована** (шаг 32): тип `ToolSchemaLayout` > {groups: [{title?, cols?, fields}]}, рендер групп в `SchemaFields.svelte` > (заголовок группы + сетка колонок, неупомянутые поля — в общей группе), -> пилот — `add-text` (группы Text/Placement/Plate). -> Следующее: шаг 33 — UI-макет каждого переведённого инструмента (ревью по одному). +> пилот — `add-text` (группы Text/Placement/Plate). **UI-макеты расставлены +> по переведённым инструментам** (шаг 33): geometry (fit-on-background, +> change-canvas-size, resize, crop), маски alpha (Shape/Position), generate +> (create-empty, linear-gradient, color-spectrum, random-colors, draw-grid, +> step-colors, placeholder, text-to-png), text (add-text, date-stamp), +> filters (randomize-pixels, add-noise) — везде, где канвас отделён от +> параметров эффекта либо фигура от позиции. +> Следующее: шаг 34 — поглощение `tool-views.ts` (Фаза 5, зачистка). > > Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts` > (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate) @@ -422,7 +428,21 @@ Typed field builders + `Field` + `toolSchema

()` + `ToolSchema

` — неупомянутые поля — в общей группе в конце, без заголовка). Пилот — `add-text-png` (Text: text+style; Placement: position+margin; Plate). Тесты: schema-layout в registry-schema.test.ts; полные запуски чисты. -33. Для каждого переведённого инструмента — UI-макет, принимается отдельно. +33. Для каждого переведённого инструмента — UI-макет **расставлен** ✔. + Layout-группы заданы там, где группировка содержательна (канвас отдельно + от параметров эффекта, фигура отдельно от позиции; составные виджеты — + внутри групп целиком): + - geometry: fit-on-background (Canvas/Background), change-canvas-size + (Canvas/Anchor), resize (Canvas/Scaling), crop (Offset/Crop area); + - alpha-маски: circle/square/star/wavy (Shape — с 2 колонками где плотно, + Position — offset); + - generate: create-empty (Canvas/Fill), linear-gradient (Canvas/Colors), + color-spectrum (Canvas/Spectrum), random-colors (Canvas/Random), + draw-grid (Canvas/Grid), step-colors (Colors/Output), placeholder + (Canvas/Colors/Text), text-to-png (Text/Background/Padding); + - text: add-text, date-stamp (Text/Placement/Plate); + - filters: randomize-pixels (Blocks), add-noise (Noise/Seed). + Инструменты с 1–3 простыми полями остались без layout (одна общая группа). ### Фаза 5 — зачистка diff --git a/web/src/lib/registry-new/alpha.ts b/web/src/lib/registry-new/alpha.ts index 650c312..ac38512 100644 --- a/web/src/lib/registry-new/alpha.ts +++ b/web/src/lib/registry-new/alpha.ts @@ -77,10 +77,20 @@ interface CircleMaskParams { offset: Offset; } -const circleMaskSchema = toolSchema({ - size: field.slider({ min: 20, max: 100, step: 1, default: 100 }), - offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), -}); +const circleMaskSchema = toolSchema( + { + size: field.slider({ min: 20, max: 100, step: 1, default: 100 }), + offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), + }, + { + layout: { + groups: [ + { title: "Shape", fields: ["size"] }, + { title: "Position", fields: ["offset"] }, + ], + }, + }, +); const circleMask: ToolEntry = { id: "circle-mask-png", @@ -104,11 +114,25 @@ interface SquareMaskParams { offset: Offset; } -const squareMaskSchema = toolSchema({ - widthPct: field.slider({ min: 10, max: 100, step: 1, default: 100 }), - heightPct: field.slider({ min: 10, max: 100, step: 1, default: 100 }), - offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), -}); +const squareMaskSchema = toolSchema( + { + widthPct: field.slider({ min: 10, max: 100, step: 1, default: 100 }), + heightPct: field.slider({ min: 10, max: 100, step: 1, default: 100 }), + offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), + }, + { + layout: { + groups: [ + { + title: "Shape", + cols: 2, + fields: ["widthPct", "heightPct"], + }, + { title: "Position", fields: ["offset"] }, + ], + }, + }, +); const squareMask: ToolEntry = { id: "square-mask-png", @@ -134,13 +158,27 @@ interface StarMaskParams { offset: Offset; } -const starMaskSchema = toolSchema({ - points: field.slider({ min: 3, max: 12, step: 1, default: 5 }), - innerRadius: field.slider({ min: 10, max: 90, step: 1, default: 45 }), - size: field.slider({ min: 20, max: 100, step: 1, default: 100 }), - rotation: field.slider({ min: -180, max: 180, step: 1, default: 0 }), - offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), -}); +const starMaskSchema = toolSchema( + { + points: field.slider({ min: 3, max: 12, step: 1, default: 5 }), + innerRadius: field.slider({ min: 10, max: 90, step: 1, default: 45 }), + size: field.slider({ min: 20, max: 100, step: 1, default: 100 }), + rotation: field.slider({ min: -180, max: 180, step: 1, default: 0 }), + offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), + }, + { + layout: { + groups: [ + { + title: "Shape", + cols: 2, + fields: ["points", "innerRadius", "size", "rotation"], + }, + { title: "Position", fields: ["offset"] }, + ], + }, + }, +); const starMask: ToolEntry = { id: "star-mask-png", @@ -166,13 +204,27 @@ interface WavyMaskParams { offset: Offset; } -const wavyMaskSchema = toolSchema({ - size: field.slider({ min: 20, max: 100, step: 1, default: 90 }), - amplitude: field.slider({ min: 2, max: 30, step: 1, default: 8 }), - waves: field.slider({ min: 3, max: 24, step: 1, default: 8 }), - phase: field.slider({ min: 0, max: 360, step: 1, default: 0 }), - offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), -}); +const wavyMaskSchema = toolSchema( + { + size: field.slider({ min: 20, max: 100, step: 1, default: 90 }), + amplitude: field.slider({ min: 2, max: 30, step: 1, default: 8 }), + waves: field.slider({ min: 3, max: 24, step: 1, default: 8 }), + phase: field.slider({ min: 0, max: 360, step: 1, default: 0 }), + offset: field.offset({ min: -50, max: 50, x: 0, y: 0 }), + }, + { + layout: { + groups: [ + { + title: "Shape", + cols: 2, + fields: ["size", "amplitude", "waves", "phase"], + }, + { title: "Position", fields: ["offset"] }, + ], + }, + }, +); const wavyMask: ToolEntry = { id: "wavy-mask-png", diff --git a/web/src/lib/registry-new/filters.ts b/web/src/lib/registry-new/filters.ts index 30b529c..96fe2e1 100644 --- a/web/src/lib/registry-new/filters.ts +++ b/web/src/lib/registry-new/filters.ts @@ -45,10 +45,17 @@ interface RandomizePixelsParams { seed: number; } -export const randomizePixelsSchema = toolSchema({ - blockSize: field.slider({ min: 1, max: 64, step: 1, default: 8 }), - seed: field.number({ min: 0, max: 999999, step: 1, default: 42 }), -}); +export const randomizePixelsSchema = toolSchema( + { + blockSize: field.slider({ min: 1, max: 64, step: 1, default: 8 }), + seed: field.number({ min: 0, max: 999999, step: 1, default: 42 }), + }, + { + layout: { + groups: [{ title: "Blocks", cols: 2, fields: ["blockSize", "seed"] }], + }, + }, +); const randomizePixels: ToolEntry = { id: "randomize-pixels-png", @@ -66,17 +73,27 @@ interface AddNoiseParams { seed: number; } -export const addNoiseSchema = toolSchema({ - amount: field.slider({ min: 0, max: 100, step: 1, default: 25 }), - mode: field.select({ - default: "mono", - options: [ - { value: "mono", label: "Monochrome grain" }, - { value: "color", label: "Color noise" }, - ], - }), - seed: field.number({ min: 0, max: 999999, step: 1, default: 1234 }), -}); +export const addNoiseSchema = toolSchema( + { + amount: field.slider({ min: 0, max: 100, step: 1, default: 25 }), + mode: field.select({ + default: "mono", + options: [ + { value: "mono", label: "Monochrome grain" }, + { value: "color", label: "Color noise" }, + ], + }), + seed: field.number({ min: 0, max: 999999, step: 1, default: 1234 }), + }, + { + layout: { + groups: [ + { title: "Noise", cols: 2, fields: ["amount", "mode"] }, + { title: "Seed", fields: ["seed"] }, + ], + }, + }, +); const addNoiseTool: ToolEntry = { id: "add-noise-png", diff --git a/web/src/lib/registry-new/generate.ts b/web/src/lib/registry-new/generate.ts index a2de6d2..77b14f1 100644 --- a/web/src/lib/registry-new/generate.ts +++ b/web/src/lib/registry-new/generate.ts @@ -69,11 +69,21 @@ interface CreateEmptyParams { color: string; } -export const createEmptySchema = toolSchema({ - size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), - transparent: field.checkbox({ default: true }), - color: field.color({ default: "#ffffff" }), -}); +export const createEmptySchema = toolSchema( + { + size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), + transparent: field.checkbox({ default: true }), + color: field.color({ default: "#ffffff" }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Fill", fields: ["transparent", "color"] }, + ], + }, + }, +); const createEmpty: ToolEntry = { id: "create-empty-png", @@ -144,10 +154,20 @@ interface LinearGradientParams { gradient: Gradient; } -export const linearGradientSchema = toolSchema({ - size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), - gradient: field.gradient({ from: "#000000", to: "#ffffff", angle: 0 }), -}); +export const linearGradientSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), + gradient: field.gradient({ from: "#000000", to: "#ffffff", angle: 0 }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Colors", fields: ["gradient"] }, + ], + }, + }, +); const linearGradient: ToolEntry = { id: "linear-gradient-png", @@ -176,18 +196,28 @@ interface ColorSpectrumParams { lightness: number; } -export const colorSpectrumSchema = toolSchema({ - size: field.dimension({ min: 1, max: 5000, width: 1024, height: 128 }), - direction: field.select({ - default: "horizontal", - options: [ - { value: "horizontal", label: "Horizontal" }, - { value: "vertical", label: "Vertical" }, - ], - }), - saturation: field.slider({ min: 0, max: 100, step: 1, default: 100 }), - lightness: field.slider({ min: 0, max: 100, step: 1, default: 50 }), -}); +export const colorSpectrumSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 5000, width: 1024, height: 128 }), + direction: field.select({ + default: "horizontal", + options: [ + { value: "horizontal", label: "Horizontal" }, + { value: "vertical", label: "Vertical" }, + ], + }), + saturation: field.slider({ min: 0, max: 100, step: 1, default: 100 }), + lightness: field.slider({ min: 0, max: 100, step: 1, default: 50 }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Spectrum", fields: ["direction", "saturation", "lightness"] }, + ], + }, + }, +); const colorSpectrumTool: ToolEntry = { id: "color-spectrum-png", @@ -209,11 +239,21 @@ interface RandomColorsParams { seed: number; } -export const randomColorsSchema = toolSchema({ - size: field.dimension({ min: 1, max: 5000, width: 512, height: 512 }), - blockSize: field.slider({ min: 4, max: 256, step: 2, default: 64 }), - seed: field.number({ min: 0, max: 999999999, step: 1, default: 7 }), -}); +export const randomColorsSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 5000, width: 512, height: 512 }), + blockSize: field.slider({ min: 4, max: 256, step: 2, default: 64 }), + seed: field.number({ min: 0, max: 999999999, step: 1, default: 7 }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Random", cols: 2, fields: ["blockSize", "seed"] }, + ], + }, + }, +); const randomColors: ToolEntry = { id: "random-colors-png", @@ -238,14 +278,28 @@ interface DrawGridParams { transparentBg: boolean; } -export const drawGridSchema = toolSchema({ - size: field.dimension({ min: 1, max: 5000, width: 512, height: 512 }), - cols: field.slider({ min: 1, max: 64, step: 1, default: 8 }), - rows: field.slider({ min: 1, max: 64, step: 1, default: 8 }), - lineWidth: field.slider({ min: 1, max: 40, step: 1, default: 2 }), - color: field.color({ default: "#111318" }), - transparentBg: field.checkbox({ default: true }), -}); +export const drawGridSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 5000, width: 512, height: 512 }), + cols: field.slider({ min: 1, max: 64, step: 1, default: 8 }), + rows: field.slider({ min: 1, max: 64, step: 1, default: 8 }), + lineWidth: field.slider({ min: 1, max: 40, step: 1, default: 2 }), + color: field.color({ default: "#111318" }), + transparentBg: field.checkbox({ default: true }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { + title: "Grid", + cols: 2, + fields: ["cols", "rows", "lineWidth", "color", "transparentBg"], + }, + ], + }, + }, +); const drawGridTool: ToolEntry = { id: "draw-grid-png", @@ -276,12 +330,23 @@ interface PlaceholderParams { showText: boolean; } -export const placeholderSchema = toolSchema({ - size: field.dimension({ min: 1, max: 5000, width: 800, height: 400 }), - backgroundColor: field.color({ default: "#dfe2e8" }), - color: field.color({ default: "#5c6470" }), - showText: field.checkbox({ default: true }), -}); +export const placeholderSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 5000, width: 800, height: 400 }), + backgroundColor: field.color({ default: "#dfe2e8" }), + color: field.color({ default: "#5c6470" }), + showText: field.checkbox({ default: true }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Colors", cols: 2, fields: ["backgroundColor", "color"] }, + { title: "Text", fields: ["showText"] }, + ], + }, + }, +); const placeholder: ToolEntry = { id: "placeholder-png", @@ -337,18 +402,28 @@ interface StepColorsParams { layout: "strip" | "grid"; } -export const stepColorsSchema = toolSchema({ - pair: field.colorPair({ from: "#000000", to: "#ffffff" }), - steps: field.slider({ min: 2, max: 12, step: 1, default: 6 }), - width: field.slider({ min: 128, max: 1024, step: 16, default: 512 }), - layout: field.select({ - default: "grid", - options: [ - { value: "grid", label: "Grid" }, - { value: "strip", label: "Strip" }, - ], - }), -}); +export const stepColorsSchema = toolSchema( + { + pair: field.colorPair({ from: "#000000", to: "#ffffff" }), + steps: field.slider({ min: 2, max: 12, step: 1, default: 6 }), + width: field.slider({ min: 128, max: 1024, step: 16, default: 512 }), + layout: field.select({ + default: "grid", + options: [ + { value: "grid", label: "Grid" }, + { value: "strip", label: "Strip" }, + ], + }), + }, + { + layout: { + groups: [ + { title: "Colors", fields: ["pair"] }, + { title: "Output", cols: 2, fields: ["steps", "width", "layout"] }, + ], + }, + }, +); const stepColorsTool: ToolEntry = { id: "step-colors-png", @@ -372,20 +447,31 @@ interface TextToPngParams { padding: number; } -export const textToPngSchema = toolSchema({ - text: field.text({ default: "Hello!", placeholder: "Your text" }), - style: field.fontStyle({ - min: 8, - max: 300, - size: 96, - font: "sans", - bold: true, - color: "#111318", - }), - transparentBg: field.checkbox({ default: false }), - backgroundColor: field.color({ default: "#ffffff" }), - padding: field.slider({ min: 0, max: 200, step: 2, default: 24 }), -}); +export const textToPngSchema = toolSchema( + { + text: field.text({ default: "Hello!", placeholder: "Your text" }), + style: field.fontStyle({ + min: 8, + max: 300, + size: 96, + font: "sans", + bold: true, + color: "#111318", + }), + transparentBg: field.checkbox({ default: false }), + backgroundColor: field.color({ default: "#ffffff" }), + padding: field.slider({ min: 0, max: 200, step: 2, default: 24 }), + }, + { + layout: { + groups: [ + { title: "Text", fields: ["text", "style"] }, + { title: "Background", fields: ["transparentBg", "backgroundColor"] }, + { title: "Padding", fields: ["padding"] }, + ], + }, + }, +); const textToPng: ToolEntry = { id: "text-to-png", diff --git a/web/src/lib/registry-new/geometry.ts b/web/src/lib/registry-new/geometry.ts index 513f240..1ba79ed 100644 --- a/web/src/lib/registry-new/geometry.ts +++ b/web/src/lib/registry-new/geometry.ts @@ -43,11 +43,21 @@ interface FitOnBackgroundParams { color: string; } -export const fitOnBackgroundSchema = toolSchema({ - size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), - transparent: field.checkbox({ default: false }), - color: field.color({ default: "#ffffff" }), -}); +export const fitOnBackgroundSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), + transparent: field.checkbox({ default: false }), + color: field.color({ default: "#ffffff" }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Background", fields: ["transparent", "color"] }, + ], + }, + }, +); const fitOnBackground: ToolEntry = { id: "fit-on-background-png", @@ -80,23 +90,33 @@ interface ChangeCanvasSizeParams { anchor: Anchor9; } -export const changeCanvasSizeSchema = toolSchema({ - size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), - anchor: field.select({ - default: "center", - options: [ - { value: "top-left", label: "Top left" }, - { value: "top-center", label: "Top center" }, - { value: "top-right", label: "Top right" }, - { value: "middle-left", label: "Middle left" }, - { value: "center", label: "Center" }, - { value: "middle-right", label: "Middle right" }, - { value: "bottom-left", label: "Bottom left" }, - { value: "bottom-center", label: "Bottom center" }, - { value: "bottom-right", label: "Bottom right" }, - ], - }), -}); +export const changeCanvasSizeSchema = toolSchema( + { + size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }), + anchor: field.select({ + default: "center", + options: [ + { value: "top-left", label: "Top left" }, + { value: "top-center", label: "Top center" }, + { value: "top-right", label: "Top right" }, + { value: "middle-left", label: "Middle left" }, + { value: "center", label: "Center" }, + { value: "middle-right", label: "Middle right" }, + { value: "bottom-left", label: "Bottom left" }, + { value: "bottom-center", label: "Bottom center" }, + { value: "bottom-right", label: "Bottom right" }, + ], + }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Anchor", fields: ["anchor"] }, + ], + }, + }, +); const changeCanvasSizeTool: ToolEntry = { id: "change-canvas-size-png", @@ -119,10 +139,20 @@ interface ResizeParams { keepAspect: boolean; } -export const resizeSchema = toolSchema({ - size: field.dimension({ min: 0, max: 20000, width: 0, height: 0 }), - keepAspect: field.checkbox({ default: true }), -}); +export const resizeSchema = toolSchema( + { + size: field.dimension({ min: 0, max: 20000, width: 0, height: 0 }), + keepAspect: field.checkbox({ default: true }), + }, + { + layout: { + groups: [ + { title: "Canvas", fields: ["size"] }, + { title: "Scaling", fields: ["keepAspect"] }, + ], + }, + }, +); const resizeTool: ToolEntry = { id: "resize-png", @@ -159,11 +189,21 @@ interface CropParams { size: Dimension; } -export const cropSchema = toolSchema({ - x: field.number({ min: -100000, max: 100000, step: 1, default: 0 }), - y: field.number({ min: -100000, max: 100000, step: 1, default: 0 }), - size: field.dimension({ min: 0, max: 100000, width: 0, height: 0 }), -}); +export const cropSchema = toolSchema( + { + x: field.number({ min: -100000, max: 100000, step: 1, default: 0 }), + y: field.number({ min: -100000, max: 100000, step: 1, default: 0 }), + size: field.dimension({ min: 0, max: 100000, width: 0, height: 0 }), + }, + { + layout: { + groups: [ + { title: "Offset", fields: ["x", "y"] }, + { title: "Crop area", fields: ["size"] }, + ], + }, + }, +); const cropTool: ToolEntry = { id: "crop-png", diff --git a/web/src/lib/registry-new/text.ts b/web/src/lib/registry-new/text.ts index 296e71d..c0c8cc5 100644 --- a/web/src/lib/registry-new/text.ts +++ b/web/src/lib/registry-new/text.ts @@ -70,23 +70,34 @@ interface DateStampParams { plate: Plate; } -const dateStampSchema = toolSchema({ - format: field.text({ - default: "YYYY-MM-DD", - placeholder: "YYYY-MM-DD hh:mm", - }), - style: field.fontStyle({ - min: 8, - max: 200, - size: 32, - font: "mono", - bold: false, - color: "#ffffff", - }), - position: field.position9({ default: "bottom-right" }), - margin: field.slider({ min: 0, max: 200, step: 1, default: 20 }), - plate: field.plate({ enabled: true, color: "#000000", opacity: 55 }), -}); +const dateStampSchema = toolSchema( + { + format: field.text({ + default: "YYYY-MM-DD", + placeholder: "YYYY-MM-DD hh:mm", + }), + style: field.fontStyle({ + min: 8, + max: 200, + size: 32, + font: "mono", + bold: false, + color: "#ffffff", + }), + position: field.position9({ default: "bottom-right" }), + margin: field.slider({ min: 0, max: 200, step: 1, default: 20 }), + plate: field.plate({ enabled: true, color: "#000000", opacity: 55 }), + }, + { + layout: { + groups: [ + { title: "Text", fields: ["format", "style"] }, + { title: "Placement", fields: ["position", "margin"] }, + { title: "Plate", fields: ["plate"] }, + ], + }, + }, +); const dateStamp: ToolEntry = { id: "date-stamp-png",