feat: add two colors field control
This commit is contained in:
@@ -5,10 +5,14 @@
|
|||||||
> все одиночные инструменты (18 шт). Фаза 3 — **составной тип `dimension`
|
> все одиночные инструменты (18 шт). Фаза 3 — **составной тип `dimension`
|
||||||
> полностью переведён** (12 инструментов: create-empty, single-color,
|
> полностью переведён** (12 инструментов: create-empty, single-color,
|
||||||
> random-noise, linear-gradient, color-spectrum, random-colors, draw-grid,
|
> random-noise, linear-gradient, color-spectrum, random-colors, draw-grid,
|
||||||
> placeholder, fit-on-background, change-canvas-size, resize, crop); preview
|
> placeholder, fit-on-background, change-canvas-size, resize, crop); **составной
|
||||||
|
> тип `color-pair` полностью переведён** (4 инструмента: blend-two,
|
||||||
|
> step-colors, linear-gradient, two-colors); preview
|
||||||
> научен применять **генераторы** (`executeGenerate`, кнопка Generate) и
|
> научен применять **генераторы** (`executeGenerate`, кнопка Generate) и
|
||||||
> рендерить все kinds схемы (slider/number/color/select/checkbox/dimension).
|
> рендерить все kinds схемы (slider/number/color/select/checkbox/dimension/
|
||||||
> Следующее: `color-pair` (пилот blend-two-png → two-colors → step-colors).
|
> color-pair).
|
||||||
|
> Следующее: `offset` (пилот circle-mask-png → square-mask → star-mask →
|
||||||
|
> wavy-mask).
|
||||||
>
|
>
|
||||||
> Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts`
|
> Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts`
|
||||||
> (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate)
|
> (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate)
|
||||||
@@ -345,8 +349,12 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
Preview: генераторы применяются через `executeGenerate` (кнопка Generate),
|
Preview: генераторы применяются через `executeGenerate` (кнопка Generate),
|
||||||
`SchemaFields` рендерит все kinds схемы (slider/number/color/select/
|
`SchemaFields` рендерит все kinds схемы (slider/number/color/select/
|
||||||
checkbox/dimension). Тесты: 592 passed.
|
checkbox/dimension). Тесты: 592 passed.
|
||||||
25. `color-pair` — пилот (blend-two-png), затем two-colors → step-colors →
|
25. `color-pair` — **все 4 инструмента переведены** ✔ (`blend-two-png`,
|
||||||
linear-gradient (уже переведён в dimension, здесь добавляется к нему)
|
`step-colors-png`, `linear-gradient-png` — генераторы в
|
||||||
|
`registry-new/generate.ts`, `two-colors-png` — run в `registry-new/color.ts`).
|
||||||
|
Составной тип во всех видах: вложенный объект `pair: { from, to }` +
|
||||||
|
`field.colorPair`, виджет `kit/fields/schema/ColorPairControl.svelte`,
|
||||||
|
kind `color-pair` в схеме (default/sanitize). Тесты: +4 (596 passed).
|
||||||
26. `offset` — пилот (circle-mask-png), затем square-mask → star-mask →
|
26. `offset` — пилот (circle-mask-png), затем square-mask → star-mask →
|
||||||
wavy-mask
|
wavy-mask
|
||||||
27. `position9` — перевести add-text → date-stamp → watermark-image
|
27. `position9` — перевести add-text → date-stamp → watermark-image
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { ToolSchema } from "$lib/registry-schema";
|
import type { ToolSchema } from "$lib/registry-schema";
|
||||||
import CheckboxControl from "./fields/schema/CheckboxControl.svelte";
|
import CheckboxControl from "./fields/schema/CheckboxControl.svelte";
|
||||||
import ColorControl from "./fields/schema/ColorControl.svelte";
|
import ColorControl from "./fields/schema/ColorControl.svelte";
|
||||||
|
import ColorPairControl from "./fields/schema/ColorPairControl.svelte";
|
||||||
import RangeControl from "./fields/schema/RangeControl.svelte";
|
import RangeControl from "./fields/schema/RangeControl.svelte";
|
||||||
import SelectControl from "./fields/schema/SelectControl.svelte";
|
import SelectControl from "./fields/schema/SelectControl.svelte";
|
||||||
import TextControl from "./fields/schema/TextControl.svelte";
|
import TextControl from "./fields/schema/TextControl.svelte";
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
number: RangeControl,
|
number: RangeControl,
|
||||||
slider: RangeControl,
|
slider: RangeControl,
|
||||||
color: ColorControl,
|
color: ColorControl,
|
||||||
|
"color-pair": ColorPairControl,
|
||||||
select: SelectControl,
|
select: SelectControl,
|
||||||
checkbox: CheckboxControl,
|
checkbox: CheckboxControl,
|
||||||
text: TextControl,
|
text: TextControl,
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {
|
||||||
|
ColorPair,
|
||||||
|
ColorPairSpec,
|
||||||
|
FieldSpec,
|
||||||
|
} from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
spec: FieldSpec;
|
||||||
|
onchange?: (value: ColorPair) => void;
|
||||||
|
}
|
||||||
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as ColorPairSpec);
|
||||||
|
const current = $derived.by(() => {
|
||||||
|
const v = value as Partial<ColorPair> | undefined;
|
||||||
|
return {
|
||||||
|
from: typeof v?.from === "string" ? v.from : sp.from,
|
||||||
|
to: typeof v?.to === "string" ? v.to : sp.to,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function setColor(axis: "from" | "to", hex: string) {
|
||||||
|
onchange?.({ ...current, [axis]: hex });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="control color-pair-control">
|
||||||
|
<span class="pair-label">{label}</span>
|
||||||
|
<div class="pair-field">
|
||||||
|
<label class="pair-axis">
|
||||||
|
<span class="swatch" style="background:{current.from}"></span>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={current.from}
|
||||||
|
oninput={(e) => setColor("from", (e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
<span class="axis-label">From</span>
|
||||||
|
</label>
|
||||||
|
<label class="pair-axis">
|
||||||
|
<span class="swatch" style="background:{current.to}"></span>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={current.to}
|
||||||
|
oninput={(e) => setColor("to", (e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
<span class="axis-label">To</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.control {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-m);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-m);
|
||||||
|
}
|
||||||
|
.pair-label {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
letter-spacing: var(--space-text-l);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.pair-field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--space-m);
|
||||||
|
}
|
||||||
|
.pair-axis {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--space-xxl) 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
}
|
||||||
|
.swatch {
|
||||||
|
width: var(--space-xxl);
|
||||||
|
height: var(--space-xxl);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
}
|
||||||
|
.pair-axis input[type="color"] {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--space-xxl);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
background: var(--color-background);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.axis-label {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,29 @@
|
|||||||
import { gammaCorrection, temperature, tint } from "../core/color";
|
import { gammaCorrection, temperature, twoColors, tint } from "../core/color";
|
||||||
import { parseHexList } from "../core/palette";
|
import { parseHexList } from "../core/palette";
|
||||||
import { ditherImage, mapToNearest, quantizeImage } from "../core/quantize";
|
import { ditherImage, mapToNearest, quantizeImage } from "../core/quantize";
|
||||||
import { field, toolSchema } from "../registry-schema";
|
import { field, toolSchema, type ColorPair } from "../registry-schema";
|
||||||
import type { ToolEntry } from "./types";
|
import type { ToolEntry } from "./types";
|
||||||
|
|
||||||
|
interface TwoColorsParams {
|
||||||
|
pair: ColorPair;
|
||||||
|
threshold: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const twoColorsSchema = toolSchema<TwoColorsParams>({
|
||||||
|
pair: field.colorPair({ from: "#ffffff", to: "#000000" }),
|
||||||
|
threshold: field.slider({ min: 0, max: 100, step: 1, default: 50 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const twoColorsTool: ToolEntry<TwoColorsParams> = {
|
||||||
|
id: "two-colors-png",
|
||||||
|
title: "Two colors PNG",
|
||||||
|
description:
|
||||||
|
"Recolors the image into two chosen colors by luminance threshold.",
|
||||||
|
category: "color",
|
||||||
|
schema: twoColorsSchema,
|
||||||
|
run: (img, p) => twoColors(img, p.pair.from, p.pair.to, p.threshold),
|
||||||
|
};
|
||||||
|
|
||||||
interface GammaParams {
|
interface GammaParams {
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
@@ -123,6 +143,7 @@ const ditheringTool: ToolEntry<DitheringParams> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const colorEntries = [
|
export const colorEntries = [
|
||||||
|
twoColorsTool,
|
||||||
gammaTool,
|
gammaTool,
|
||||||
temperatureTool,
|
temperatureTool,
|
||||||
tintTool,
|
tintTool,
|
||||||
|
|||||||
@@ -2,8 +2,18 @@ import { renderTextToImage } from "../core/domText";
|
|||||||
import { colorSpectrum, drawGrid, randomColorBlocks } from "../core/gen-tools";
|
import { colorSpectrum, drawGrid, randomColorBlocks } from "../core/gen-tools";
|
||||||
import { gradientImage, noiseImage, solidImage } from "../core/generate";
|
import { gradientImage, noiseImage, solidImage } from "../core/generate";
|
||||||
import { changeCanvasSize } from "../core/geometry";
|
import { changeCanvasSize } from "../core/geometry";
|
||||||
import { hexToRgb } from "../core/palette";
|
import {
|
||||||
import { field, toolSchema, type Dimension } from "../registry-schema";
|
hexToRgb,
|
||||||
|
renderBlend,
|
||||||
|
renderSwatches,
|
||||||
|
stepColors,
|
||||||
|
} from "../core/palette";
|
||||||
|
import {
|
||||||
|
field,
|
||||||
|
toolSchema,
|
||||||
|
type ColorPair,
|
||||||
|
type Dimension,
|
||||||
|
} from "../registry-schema";
|
||||||
import type { ToolEntry } from "./types";
|
import type { ToolEntry } from "./types";
|
||||||
|
|
||||||
function rgba(hex: string): [number, number, number, number] {
|
function rgba(hex: string): [number, number, number, number] {
|
||||||
@@ -89,15 +99,13 @@ const randomNoise: ToolEntry<RandomNoiseParams> = {
|
|||||||
|
|
||||||
interface LinearGradientParams {
|
interface LinearGradientParams {
|
||||||
size: Dimension;
|
size: Dimension;
|
||||||
fromColor: string;
|
pair: ColorPair;
|
||||||
toColor: string;
|
|
||||||
direction: "horizontal" | "vertical";
|
direction: "horizontal" | "vertical";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const linearGradientSchema = toolSchema<LinearGradientParams>({
|
export const linearGradientSchema = toolSchema<LinearGradientParams>({
|
||||||
size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }),
|
size: field.dimension({ min: 1, max: 20000, width: 800, height: 600 }),
|
||||||
fromColor: field.color({ default: "#000000" }),
|
pair: field.colorPair({ from: "#000000", to: "#ffffff" }),
|
||||||
toColor: field.color({ default: "#ffffff" }),
|
|
||||||
direction: field.select({
|
direction: field.select({
|
||||||
default: "horizontal",
|
default: "horizontal",
|
||||||
options: [
|
options: [
|
||||||
@@ -117,7 +125,7 @@ const linearGradient: ToolEntry<LinearGradientParams> = {
|
|||||||
generate: (p) => {
|
generate: (p) => {
|
||||||
const w = Math.trunc(p.size.width);
|
const w = Math.trunc(p.size.width);
|
||||||
const h = Math.trunc(p.size.height);
|
const h = Math.trunc(p.size.height);
|
||||||
return gradientImage(w, h, rgba(p.fromColor), rgba(p.toColor), p.direction);
|
return gradientImage(w, h, rgba(p.pair.from), rgba(p.pair.to), p.direction);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,6 +271,59 @@ const placeholder: ToolEntry<PlaceholderParams> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface BlendTwoParams {
|
||||||
|
pair: ColorPair;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blendTwoSchema = toolSchema<BlendTwoParams>({
|
||||||
|
pair: field.colorPair({ from: "#000000", to: "#ffffff" }),
|
||||||
|
width: field.slider({ min: 128, max: 1024, step: 16, default: 512 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const blendTwo: ToolEntry<BlendTwoParams> = {
|
||||||
|
id: "blend-two-png",
|
||||||
|
title: "Blend Two Colors PNG",
|
||||||
|
description: "A continuous horizontal gradient between two colors.",
|
||||||
|
category: "generate",
|
||||||
|
schema: blendTwoSchema,
|
||||||
|
generate: (p) => renderBlend(p.pair.from, p.pair.to, p.width),
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StepColorsParams {
|
||||||
|
pair: ColorPair;
|
||||||
|
steps: number;
|
||||||
|
width: number;
|
||||||
|
layout: "strip" | "grid";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stepColorsSchema = toolSchema<StepColorsParams>({
|
||||||
|
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" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const stepColorsTool: ToolEntry<StepColorsParams> = {
|
||||||
|
id: "step-colors-png",
|
||||||
|
title: "Color Steps PNG",
|
||||||
|
description: "A discrete set of evenly spaced steps between two colors.",
|
||||||
|
category: "generate",
|
||||||
|
schema: stepColorsSchema,
|
||||||
|
generate: (p) =>
|
||||||
|
renderSwatches(
|
||||||
|
stepColors(p.pair.from, p.pair.to, p.steps),
|
||||||
|
p.width,
|
||||||
|
p.layout,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
export const generateEntries = [
|
export const generateEntries = [
|
||||||
createEmpty,
|
createEmpty,
|
||||||
singleColor,
|
singleColor,
|
||||||
@@ -272,4 +333,6 @@ export const generateEntries = [
|
|||||||
randomColors,
|
randomColors,
|
||||||
drawGridTool,
|
drawGridTool,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
blendTwo,
|
||||||
|
stepColorsTool,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ describe("registry-new (переведённые инструменты)", () =>
|
|||||||
"linear-gradient-png",
|
"linear-gradient-png",
|
||||||
{
|
{
|
||||||
size: { width: 40, height: 30 },
|
size: { width: 40, height: 30 },
|
||||||
fromColor: "#000000",
|
pair: { from: "#000000", to: "#ffffff" },
|
||||||
toColor: "#ffffff",
|
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -182,6 +181,69 @@ describe("registry-new (переведённые инструменты)", () =>
|
|||||||
expect(topLeft).toBe(255);
|
expect(topLeft).toBe(255);
|
||||||
expect(center).toBe(0);
|
expect(center).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("blend-two: дефолты пары и работа генератора", async () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "blend-two-png")!;
|
||||||
|
const d = defaultSchemaParams(tool.schema);
|
||||||
|
expect(d.pair).toEqual({ from: "#000000", to: "#ffffff" });
|
||||||
|
const img = await tool.generate!(
|
||||||
|
sanitizeSchemaParams(tool.schema, {
|
||||||
|
pair: { from: "#ff0000", to: "#0000ff" },
|
||||||
|
width: 128,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(img.width).toBe(128);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("step-colors: рендерит steps полос по паре", async () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "step-colors-png")!;
|
||||||
|
const img = await tool.generate!(
|
||||||
|
sanitizeSchemaParams(tool.schema, {
|
||||||
|
pair: { from: "#000000", to: "#ffffff" },
|
||||||
|
steps: 4,
|
||||||
|
width: 128,
|
||||||
|
layout: "strip",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(img.width).toBe(128);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("two-colors: перекрашивает светлые/тёмные пиксели по паре", async () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "two-colors-png")!;
|
||||||
|
// 50×1, левая половина тёмная, правая светлая
|
||||||
|
const data = new Uint8ClampedArray(50 * 4).fill(255);
|
||||||
|
const img = { width: 50, height: 1, data };
|
||||||
|
for (let x = 0; x < 25; x++) {
|
||||||
|
const i = x * 4;
|
||||||
|
data[i] = 0;
|
||||||
|
data[i + 1] = 0;
|
||||||
|
data[i + 2] = 0;
|
||||||
|
}
|
||||||
|
const out = await tool.run!(
|
||||||
|
img,
|
||||||
|
sanitizeSchemaParams(tool.schema, {
|
||||||
|
pair: { from: "#ffffff", to: "#ff0000" },
|
||||||
|
threshold: 50,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Светлый пиксель → from (#ffffff), тёмный → to (#ff0000)
|
||||||
|
expect(out.data[0]).toBe(255);
|
||||||
|
expect(out.data[1]).toBe(0);
|
||||||
|
expect(out.data[2]).toBe(0);
|
||||||
|
expect(out.data[100]).toBe(255);
|
||||||
|
expect(out.data[101]).toBe(255);
|
||||||
|
expect(out.data[102]).toBe(255);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sanitize чинит мусор в паре и клампит threshold", () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "two-colors-png")!;
|
||||||
|
const s = sanitizeSchemaParams(tool.schema, {
|
||||||
|
pair: { from: "not-a-color", to: "#00ff00", extra: 1 },
|
||||||
|
threshold: 999,
|
||||||
|
});
|
||||||
|
expect(s.pair).toEqual({ from: "#ffffff", to: "#00ff00" });
|
||||||
|
expect(s.threshold).toBe(100);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function solid(width: number, height: number) {
|
function solid(width: number, height: number) {
|
||||||
|
|||||||
@@ -64,6 +64,18 @@ export interface DimensionSpec {
|
|||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Пара цветов «от → к» (градиенты, сведение к двум цветам и т.п.). */
|
||||||
|
export interface ColorPair {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColorPairSpec {
|
||||||
|
kind: "color-pair";
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type FieldSpec =
|
export type FieldSpec =
|
||||||
| NumberSpec
|
| NumberSpec
|
||||||
| SliderSpec
|
| SliderSpec
|
||||||
@@ -71,7 +83,8 @@ export type FieldSpec =
|
|||||||
| SelectSpec
|
| SelectSpec
|
||||||
| TextSpec
|
| TextSpec
|
||||||
| CheckboxSpec
|
| CheckboxSpec
|
||||||
| DimensionSpec;
|
| DimensionSpec
|
||||||
|
| ColorPairSpec;
|
||||||
|
|
||||||
/** `Field<T>`: runtime-спека поля + phantom-тип ожидаемого значения (number|string|boolean). */
|
/** `Field<T>`: runtime-спека поля + phantom-тип ожидаемого значения (number|string|boolean). */
|
||||||
export interface Field<T> {
|
export interface Field<T> {
|
||||||
@@ -120,6 +133,9 @@ export const field = {
|
|||||||
}): Field<Dimension> => ({
|
}): Field<Dimension> => ({
|
||||||
spec: { kind: "dimension", ...s },
|
spec: { kind: "dimension", ...s },
|
||||||
}),
|
}),
|
||||||
|
colorPair: (s: { from: string; to: string }): Field<ColorPair> => ({
|
||||||
|
spec: { kind: "color-pair", ...s },
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,6 +164,8 @@ export function defaultSchemaParams<P>(
|
|||||||
const spec = schema.fields[key].spec;
|
const spec = schema.fields[key].spec;
|
||||||
if (spec.kind === "dimension") {
|
if (spec.kind === "dimension") {
|
||||||
out[key] = { width: spec.width, height: spec.height };
|
out[key] = { width: spec.width, height: spec.height };
|
||||||
|
} else if (spec.kind === "color-pair") {
|
||||||
|
out[key] = { from: spec.from, to: spec.to };
|
||||||
} else {
|
} else {
|
||||||
out[key] = spec.default;
|
out[key] = spec.default;
|
||||||
}
|
}
|
||||||
@@ -212,6 +230,25 @@ export function sanitizeSchemaParams<P>(
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "color-pair": {
|
||||||
|
const r =
|
||||||
|
typeof raw === "object" &&
|
||||||
|
raw !== null &&
|
||||||
|
"from" in raw &&
|
||||||
|
"to" in raw
|
||||||
|
? (raw as Record<string, unknown>)
|
||||||
|
: undefined;
|
||||||
|
const from =
|
||||||
|
typeof r?.from === "string" && /^#[0-9a-f]{6}$/i.test(r.from)
|
||||||
|
? r.from
|
||||||
|
: spec.from;
|
||||||
|
const to =
|
||||||
|
typeof r?.to === "string" && /^#[0-9a-f]{6}$/i.test(r.to)
|
||||||
|
? r.to
|
||||||
|
: spec.to;
|
||||||
|
out[key] = { from, to };
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
Reference in New Issue
Block a user