mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
feat: add tool schema layout
This commit is contained in:
@@ -56,16 +56,61 @@
|
||||
.replace(/[_-]+/g, " ")
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#each Object.entries(schema.fields) as [id, field] (id)}
|
||||
{@const Control = FIELDS[field.spec.kind]}
|
||||
<Control
|
||||
label={labelOf(id)}
|
||||
value={values[id]}
|
||||
spec={field.spec}
|
||||
onchange={(v) => onchange(id, v)}
|
||||
/>
|
||||
{#each layoutGroups as group (group.key)}
|
||||
{#if group.title}
|
||||
<span class="group-title">{group.title}</span>
|
||||
{/if}
|
||||
<div
|
||||
class="group-fields"
|
||||
style:grid-template-columns={group.cols > 1
|
||||
? `repeat(${group.cols}, minmax(0, 1fr))`
|
||||
: undefined}
|
||||
>
|
||||
{#each group.fields as id (id)}
|
||||
{@const Control = FIELDS[schema.fields[id].spec.kind]}
|
||||
<Control
|
||||
label={labelOf(id)}
|
||||
value={values[id]}
|
||||
spec={schema.fields[id].spec}
|
||||
onchange={(v) => onchange(id, v)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="panel-foot">
|
||||
@@ -76,6 +121,23 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.group-title {
|
||||
display: block;
|
||||
margin-top: var(--space-l);
|
||||
margin-bottom: var(--space-s);
|
||||
color: var(--color-text-muted);
|
||||
font: var(--font-size-s) var(--font-mono);
|
||||
letter-spacing: var(--space-text-l);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.group-title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.group-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
column-gap: var(--space-l);
|
||||
}
|
||||
.panel-foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -13,20 +13,31 @@ interface AddTextParams {
|
||||
plate: Plate;
|
||||
}
|
||||
|
||||
const addTextSchema = toolSchema<AddTextParams>({
|
||||
text: field.text({ default: "Hello!", placeholder: "Your text" }),
|
||||
style: field.fontStyle({
|
||||
min: 8,
|
||||
max: 200,
|
||||
size: 48,
|
||||
font: "sans",
|
||||
bold: true,
|
||||
color: "#ffffff",
|
||||
}),
|
||||
position: field.position9({ default: "bottom-right" }),
|
||||
margin: field.slider({ min: 0, max: 200, step: 1, default: 24 }),
|
||||
plate: field.plate({ enabled: false, color: "#000000", opacity: 60 }),
|
||||
});
|
||||
const addTextSchema = toolSchema<AddTextParams>(
|
||||
{
|
||||
text: field.text({ default: "Hello!", placeholder: "Your text" }),
|
||||
style: field.fontStyle({
|
||||
min: 8,
|
||||
max: 200,
|
||||
size: 48,
|
||||
font: "sans",
|
||||
bold: true,
|
||||
color: "#ffffff",
|
||||
}),
|
||||
position: field.position9({ default: "bottom-right" }),
|
||||
margin: field.slider({ min: 0, max: 200, step: 1, default: 24 }),
|
||||
plate: field.plate({ enabled: false, color: "#000000", opacity: 60 }),
|
||||
},
|
||||
{
|
||||
layout: {
|
||||
groups: [
|
||||
{ title: "Text", fields: ["text", "style"] },
|
||||
{ title: "Placement", fields: ["position", "margin"] },
|
||||
{ title: "Plate", fields: ["plate"] },
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const addText: ToolEntry<AddTextParams> = {
|
||||
id: "add-text-png",
|
||||
|
||||
@@ -26,7 +26,10 @@ const frameSchema = toolSchema<FrameParams>(
|
||||
],
|
||||
}),
|
||||
},
|
||||
{ layout: { group: "frame", cols: 2 }, label: "Frame" },
|
||||
{
|
||||
layout: { groups: [{ title: "Frame", fields: ["thickness", "color"] }] },
|
||||
label: "Frame",
|
||||
},
|
||||
);
|
||||
|
||||
describe("registry-schema: дефолты из схемы", () => {
|
||||
@@ -40,6 +43,30 @@ describe("registry-schema: дефолты из схемы", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("registry-schema: раскладка (schema.layout)", () => {
|
||||
it("toolSchema сохраняет группы полей", () => {
|
||||
expect(frameSchema.layout).toEqual({
|
||||
groups: [{ title: "Frame", fields: ["thickness", "color"] }],
|
||||
});
|
||||
});
|
||||
|
||||
it("обе схемы без layout не добавляют layout", () => {
|
||||
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(plain.layout).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("registry-schema: санитайз", () => {
|
||||
it("пропускает валидные значения", () => {
|
||||
const out = sanitizeSchemaParams(frameSchema, {
|
||||
|
||||
@@ -205,16 +205,30 @@ export interface Field<T> {
|
||||
__fieldT?: T;
|
||||
}
|
||||
|
||||
export interface ToolLayoutGroup {
|
||||
/** Подпись группы (пустая группа не рендерится). */
|
||||
title?: string;
|
||||
/** Количество колонок сетки внутри группы (по умолчанию 1). */
|
||||
cols?: number;
|
||||
/** Имена полей схемы, попадающих в группу. */
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
export interface ToolSchemaLayout {
|
||||
/** Группы полей виджета. Неупомянутые поля — в общей группе в конце. */
|
||||
groups: ToolLayoutGroup[];
|
||||
}
|
||||
|
||||
export interface ToolSchemaMeta {
|
||||
/** Пер-инструмент раскладка полей (группировка/колонки). */
|
||||
layout?: { group?: string; cols?: number };
|
||||
layout?: ToolSchemaLayout;
|
||||
/** Короткая подпись инструмента для нового UI (необязательно). */
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface ToolSchema<P> {
|
||||
fields: { [K in keyof P]: Field<P[K]> };
|
||||
layout?: { group?: string; cols?: number };
|
||||
layout?: ToolSchemaLayout;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user