mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
refactor: separate schema fields logic
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
FieldSpecKind,
|
||||
ToolSchema,
|
||||
} from "$lib/registry-schema";
|
||||
import { resolveLayoutGroups } from "$lib/registry-schema";
|
||||
import { fieldLabel, groupLabel } from "$lib/i18n/schema-tool-strings";
|
||||
import { t } from "$lib/i18n/t";
|
||||
import { RotateCcw } from "@lucide/svelte";
|
||||
@@ -57,38 +58,7 @@
|
||||
}
|
||||
let { schema, values, toolId, onchange, onreset }: Props = $props();
|
||||
|
||||
interface LayoutGroup {
|
||||
key: string;
|
||||
title?: string;
|
||||
cols: number;
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
const layoutGroups = $derived.by((): LayoutGroup[] => {
|
||||
const all = Object.keys(schema.fields);
|
||||
const groups = schema.layout?.groups ?? [];
|
||||
const used: Record<string, true> = {};
|
||||
const named = groups
|
||||
.map((g, gi) => {
|
||||
const fields = g.fields.filter((f) => {
|
||||
if (used[f] || !(f in schema.fields)) return false;
|
||||
used[f] = true;
|
||||
return true;
|
||||
});
|
||||
return {
|
||||
key: `${g.title ?? "group"}-${gi}`,
|
||||
title: g.title,
|
||||
cols: Math.max(1, Math.trunc(g.cols ?? 1)),
|
||||
fields,
|
||||
} satisfies LayoutGroup;
|
||||
})
|
||||
.filter((g) => g.fields.length > 0);
|
||||
const rest = all.filter((f) => !used[f]);
|
||||
if (rest.length > 0) {
|
||||
named.push({ key: "__default", title: undefined, cols: 1, fields: rest });
|
||||
}
|
||||
return named;
|
||||
});
|
||||
const layoutGroups = $derived(resolveLayoutGroups(schema));
|
||||
</script>
|
||||
|
||||
{#each layoutGroups as group (group.key)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
applySourceDefaults,
|
||||
defaultSchemaParams,
|
||||
field,
|
||||
resolveLayoutGroups,
|
||||
sanitizeSchemaParams,
|
||||
toolSchema,
|
||||
type Dimension,
|
||||
@@ -130,6 +131,82 @@ describe("registry-schema: раскладка (schema.layout)", () => {
|
||||
});
|
||||
expect(plain.layout).toBeUndefined();
|
||||
});
|
||||
|
||||
it("разрешает named-группы и хвост без layout", () => {
|
||||
expect(resolveLayoutGroups(frameSchema)).toEqual([
|
||||
{
|
||||
key: "Frame-0",
|
||||
title: "Frame",
|
||||
cols: 1,
|
||||
fields: ["thickness", "color"],
|
||||
},
|
||||
{ key: "__default", cols: 1, fields: ["enabled", "count"] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("убирает неизвестные и повторные поля, нормализует cols", () => {
|
||||
const schema = toolSchema<FrameParams>(
|
||||
{
|
||||
thickness: field.slider({ min: 1, max: 500, default: 5 }),
|
||||
color: field.color({ default: "#000000" }),
|
||||
enabled: field.checkbox({ default: true }),
|
||||
count: field.select({
|
||||
default: "two",
|
||||
options: [
|
||||
{ value: "one", label: "One" },
|
||||
{ value: "two", label: "Two" },
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
layout: {
|
||||
groups: [
|
||||
{
|
||||
title: "Duplicate",
|
||||
cols: 0,
|
||||
fields: ["thickness", "thickness", "missing"],
|
||||
},
|
||||
{ title: "Empty", fields: ["missing"] },
|
||||
{ cols: 3, fields: ["color"] },
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(resolveLayoutGroups(schema)).toEqual([
|
||||
{
|
||||
key: "Duplicate-0",
|
||||
title: "Duplicate",
|
||||
cols: 1,
|
||||
fields: ["thickness"],
|
||||
},
|
||||
{ key: "group-2", title: undefined, cols: 3, fields: ["color"] },
|
||||
{ key: "__default", cols: 1, fields: ["enabled", "count"] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("складывает все поля без layout в default-группу", () => {
|
||||
const plain = toolSchema<FrameParams>({
|
||||
thickness: field.slider({ min: 1, max: 500, default: 5 }),
|
||||
color: field.color({ default: "#000000" }),
|
||||
enabled: field.checkbox({ default: true }),
|
||||
count: field.select({
|
||||
default: "two",
|
||||
options: [
|
||||
{ value: "one", label: "One" },
|
||||
{ value: "two", label: "Two" },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(resolveLayoutGroups(plain)).toEqual([
|
||||
{
|
||||
key: "__default",
|
||||
cols: 1,
|
||||
fields: ["thickness", "color", "enabled", "count"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("registry-schema: санитайз", () => {
|
||||
|
||||
@@ -341,6 +341,41 @@ export function toolSchema<P>(
|
||||
return { fields, ...(meta ?? {}) };
|
||||
}
|
||||
|
||||
export interface ResolvedLayoutGroup {
|
||||
key: string;
|
||||
title?: string;
|
||||
cols: number;
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
export function resolveLayoutGroups<P>(
|
||||
schema: ToolSchema<P>,
|
||||
): ResolvedLayoutGroup[] {
|
||||
const all = Object.keys(schema.fields);
|
||||
const groups = schema.layout?.groups ?? [];
|
||||
const used: Record<string, true> = {};
|
||||
const named: ResolvedLayoutGroup[] = groups
|
||||
.map((group, index) => {
|
||||
const fields = group.fields.filter((fieldId) => {
|
||||
if (used[fieldId] || !(fieldId in schema.fields)) return false;
|
||||
used[fieldId] = true;
|
||||
return true;
|
||||
});
|
||||
return {
|
||||
key: `${group.title ?? "group"}-${index}`,
|
||||
title: group.title,
|
||||
cols: Math.max(1, Math.trunc(group.cols ?? 1)),
|
||||
fields,
|
||||
} satisfies ResolvedLayoutGroup;
|
||||
})
|
||||
.filter((group) => group.fields.length > 0);
|
||||
const rest = all.filter((fieldId) => !used[fieldId]);
|
||||
if (rest.length > 0) {
|
||||
named.push({ key: "__default", cols: 1, fields: rest });
|
||||
}
|
||||
return named;
|
||||
}
|
||||
|
||||
function clamp(value: number, min?: number, max?: number): number {
|
||||
if (min !== undefined && value < min) return min;
|
||||
if (max !== undefined && value > max) return max;
|
||||
|
||||
Reference in New Issue
Block a user