mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
153 lines
3.7 KiB
TypeScript
153 lines
3.7 KiB
TypeScript
import { formatStamp } from "./core/datefmt";
|
|
import type { OutputMime } from "./core/io";
|
|
import type { PixelImage } from "./core/types";
|
|
import { t } from "./i18n/t";
|
|
import { safeSetItem, storage } from "./storage";
|
|
import {
|
|
CHAIN_VERSION,
|
|
parseChain,
|
|
serializeSteps,
|
|
type ChainStep,
|
|
} from "./pipeline.svelte";
|
|
import type { ToolImageFile } from "./registry";
|
|
|
|
export interface NamedChain {
|
|
id: string;
|
|
name: string;
|
|
updatedAt: number;
|
|
steps: ChainStep[];
|
|
}
|
|
|
|
const STORE_KEY = "chains:named";
|
|
|
|
export function autoChainName(date = new Date()): string {
|
|
return t("savedChains.autoName", {
|
|
stamp: formatStamp(date, "YYYY-MM-DD hh:mm"),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Lenient per entry: one broken chain must not kill the rest of the list.
|
|
* Steps reuse the same serialized shape and validation as parseChain.
|
|
*/
|
|
export function parseNamedChains(data: unknown): NamedChain[] {
|
|
if (
|
|
!data ||
|
|
typeof data !== "object" ||
|
|
(data as { version?: unknown }).version !== CHAIN_VERSION ||
|
|
!Array.isArray((data as { chains?: unknown }).chains)
|
|
) {
|
|
return [];
|
|
}
|
|
const out: NamedChain[] = [];
|
|
for (const raw of (data as { chains: unknown[] }).chains) {
|
|
if (!raw || typeof raw !== "object") continue;
|
|
const { id, name, updatedAt, steps } = raw as {
|
|
id?: unknown;
|
|
name?: unknown;
|
|
updatedAt?: unknown;
|
|
steps?: unknown;
|
|
};
|
|
if (typeof id !== "string" || typeof name !== "string") continue;
|
|
const parsed = parseChain({ version: CHAIN_VERSION, steps });
|
|
if (!parsed) continue;
|
|
out.push({
|
|
id,
|
|
name,
|
|
updatedAt:
|
|
typeof updatedAt === "number" && Number.isFinite(updatedAt)
|
|
? updatedAt
|
|
: 0,
|
|
steps: parsed,
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function serializeNamedChains(
|
|
chains: NamedChain[],
|
|
): Record<string, unknown> {
|
|
return {
|
|
version: CHAIN_VERSION,
|
|
chains: chains.map((c) => ({
|
|
id: c.id,
|
|
name: c.name,
|
|
updatedAt: c.updatedAt,
|
|
steps: serializeSteps(c.steps),
|
|
})),
|
|
};
|
|
}
|
|
|
|
function readAll(): NamedChain[] {
|
|
const raw = storage()?.getItem(STORE_KEY);
|
|
if (!raw) return [];
|
|
try {
|
|
return parseNamedChains(JSON.parse(raw));
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function writeAll(chains: NamedChain[]): void {
|
|
safeSetItem(STORE_KEY, JSON.stringify(serializeNamedChains(chains)));
|
|
}
|
|
|
|
/** Newest first. */
|
|
export function listChains(): NamedChain[] {
|
|
return readAll().sort((a, b) => b.updatedAt - a.updatedAt);
|
|
}
|
|
|
|
export function getChain(id: string): NamedChain | undefined {
|
|
return readAll().find((c) => c.id === id);
|
|
}
|
|
|
|
export function createNamedChain(name: string, steps: ChainStep[]): NamedChain {
|
|
const chain: NamedChain = {
|
|
id: crypto.randomUUID(),
|
|
name: name.trim() || autoChainName(),
|
|
updatedAt: Date.now(),
|
|
steps,
|
|
};
|
|
writeAll([...readAll(), chain]);
|
|
return chain;
|
|
}
|
|
|
|
export function updateChainSteps(id: string, steps: ChainStep[]): void {
|
|
writeAll(
|
|
readAll().map((c) =>
|
|
c.id === id ? { ...c, steps, updatedAt: Date.now() } : c,
|
|
),
|
|
);
|
|
}
|
|
|
|
export function renameChain(id: string, name: string): void {
|
|
const trimmed = name.trim();
|
|
if (!trimmed) return;
|
|
writeAll(readAll().map((c) => (c.id === id ? { ...c, name: trimmed } : c)));
|
|
}
|
|
|
|
export function deleteChain(id: string): void {
|
|
writeAll(readAll().filter((c) => c.id !== id));
|
|
}
|
|
|
|
/** State that cannot persist across the tool -> pipeline route swap. */
|
|
export interface ChainHandoff {
|
|
source: PixelImage | null;
|
|
sourceFiles?: ToolImageFile[];
|
|
textSource: string;
|
|
format: OutputMime;
|
|
limitKb: number | undefined;
|
|
}
|
|
|
|
const handoffs = new Map<string, ChainHandoff>();
|
|
|
|
export function stashHandoff(id: string, handoff: ChainHandoff): void {
|
|
handoffs.set(id, handoff);
|
|
}
|
|
|
|
export function takeHandoff(id: string): ChainHandoff | undefined {
|
|
const handoff = handoffs.get(id);
|
|
handoffs.delete(id);
|
|
return handoff;
|
|
}
|