mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
132 lines
3.4 KiB
TypeScript
132 lines
3.4 KiB
TypeScript
import { getTool, PAGES, type Page } from "./registry";
|
|
import { defaultSchemaParams, sanitizeSchemaParams } from "./registry-schema";
|
|
|
|
export interface ChainStep {
|
|
key: string;
|
|
id: string;
|
|
params: Record<string, unknown>;
|
|
collapsed?: boolean;
|
|
}
|
|
|
|
export const CHAIN_VERSION = 1;
|
|
|
|
function uid(): string {
|
|
return crypto.randomUUID();
|
|
}
|
|
|
|
/** Tool can be inserted mid-chain: image in, one or many images out. */
|
|
export function isChainable(toolId: string): boolean {
|
|
const tool = getTool(toolId);
|
|
if (!tool || tool.input !== "image") return false;
|
|
const kind = tool.result ?? "image";
|
|
return kind === "image" || kind === "files";
|
|
}
|
|
|
|
/** Pages whose tools can be added as steps (for the picker). */
|
|
export function chainablePages(): Page[] {
|
|
return PAGES.filter((p) => isChainable(p.steps[0].id));
|
|
}
|
|
|
|
export function createStep(id: string): ChainStep | null {
|
|
const tool = getTool(id);
|
|
if (!tool) return null;
|
|
return { key: uid(), id, params: defaultSchemaParams(tool.schema) };
|
|
}
|
|
|
|
export function createChain(page: Page): ChainStep[] {
|
|
const steps: ChainStep[] = [];
|
|
for (const s of page.steps) {
|
|
const tool = getTool(s.id);
|
|
if (!tool) continue;
|
|
steps.push({
|
|
key: uid(),
|
|
id: s.id,
|
|
params: sanitizeSchemaParams(tool.schema, s.params ?? {}),
|
|
});
|
|
}
|
|
return steps;
|
|
}
|
|
|
|
/** Validates a saved chain; invalid data -> null (page default). */
|
|
export function parseChain(data: unknown): ChainStep[] | null {
|
|
if (
|
|
!data ||
|
|
typeof data !== "object" ||
|
|
(data as { version?: unknown }).version !== CHAIN_VERSION ||
|
|
!Array.isArray((data as { steps?: unknown }).steps)
|
|
) {
|
|
return null;
|
|
}
|
|
const out: ChainStep[] = [];
|
|
for (const raw of (data as { steps: unknown[] }).steps) {
|
|
if (!raw || typeof raw !== "object") return null;
|
|
const { id, params } = raw as { id?: unknown; params?: unknown };
|
|
if (typeof id !== "string") return null;
|
|
const tool = getTool(id);
|
|
if (!tool) return null;
|
|
out.push({
|
|
key: uid(),
|
|
id,
|
|
collapsed:
|
|
(raw as { collapsed?: unknown }).collapsed === true ? true : undefined,
|
|
params: sanitizeSchemaParams(
|
|
tool.schema,
|
|
(params ?? {}) as Record<string, unknown>,
|
|
),
|
|
});
|
|
}
|
|
return out.length > 0 ? out : null;
|
|
}
|
|
|
|
/** Serialized step shape shared by per-slug chains and named chains. */
|
|
export function serializeSteps(steps: ChainStep[]): Record<string, unknown>[] {
|
|
return steps.map((s) => ({
|
|
id: s.id,
|
|
params: s.params,
|
|
...(s.collapsed ? { collapsed: true } : {}),
|
|
}));
|
|
}
|
|
|
|
/** True while steps match the page definition identity-wise (params ignored). */
|
|
export function isStructuralDefault(page: Page, steps: ChainStep[]): boolean {
|
|
return (
|
|
steps.length === page.steps.length &&
|
|
steps.every((s, i) => s.id === page.steps[i].id)
|
|
);
|
|
}
|
|
|
|
export function insertStep(
|
|
steps: ChainStep[],
|
|
index: number,
|
|
id: string,
|
|
): ChainStep[] {
|
|
const step = createStep(id);
|
|
if (!step) return steps;
|
|
return [...steps.slice(0, index), step, ...steps.slice(index)];
|
|
}
|
|
|
|
export function removeStep(steps: ChainStep[], key: string): ChainStep[] {
|
|
if (steps.length <= 1) return steps;
|
|
return steps.filter((s) => s.key !== key);
|
|
}
|
|
|
|
export function moveStep(
|
|
steps: ChainStep[],
|
|
from: number,
|
|
to: number,
|
|
): ChainStep[] {
|
|
if (
|
|
from === to ||
|
|
from < 0 ||
|
|
to < 0 ||
|
|
from >= steps.length ||
|
|
to >= steps.length
|
|
) {
|
|
return steps;
|
|
}
|
|
const next = [...steps];
|
|
const [moved] = next.splice(from, 1);
|
|
next.splice(to, 0, moved);
|
|
return next;
|
|
}
|