Replaces TSX/Babel compilation pipeline with a JSON element model:
- New _do_parse_v2(): 1 LLM call/layout (vs 2) classifies OXML geometry
elements into placeholder types → JSON stored in layout_code
- SlideRenderer.tsx: renders JSON element model as %-positioned divs,
no Babel compilation or runtime errors
- parseLayoutSchema.ts: isJsonLayoutCode() / parseLayoutSchema() /
mergeElementsWithContent() — full JSON schema parsing layer
- useCustomTemplates.ts: transparent dual-format support (JSON + TSX)
via parsedLayoutToCompiled() adapter
Template management improvements:
- PresentationLayoutCodeModel: +is_enabled (bool) +thumbnail_path (str)
- Migration 005: adds both columns to presentation_layout_codes
- DELETE /master-decks/{id}: hard delete (files + TemplateModel +
PresentationLayoutCodeModel rows + MasterDeckModel)
- PATCH /template-management/layouts/{db_id}/toggle-enabled: new endpoint
- LayoutData response: +db_id, +is_enabled, +thumbnail_path
- _register_as_template(): stores thumbnail_path + is_enabled per layout
Admin UI:
- /admin/templates/ — list all custom templates with delete
- /admin/templates/[id]/ — layout grid with screenshots + enable/disable
- AdminSidebar: Templates nav item
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
2.6 KiB
TypeScript
106 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* parseLayoutSchema.ts
|
|
* Parses the new JSON-based element model for slide layouts.
|
|
* Replaces the Babel/TSX compile approach for custom templates.
|
|
*/
|
|
|
|
export interface SlideElement {
|
|
id: string;
|
|
type: "text" | "image" | "chart" | "shape";
|
|
placeholder: string;
|
|
x: number;
|
|
y: number;
|
|
w: number;
|
|
h: number;
|
|
style?: Record<string, string | number>;
|
|
defaultContent?: string;
|
|
content?: string;
|
|
imageUrl?: string;
|
|
}
|
|
|
|
export interface LayoutSchema {
|
|
layoutId: string;
|
|
layoutName: string;
|
|
slideWidth: number;
|
|
slideHeight: number;
|
|
background: string;
|
|
elements: SlideElement[];
|
|
}
|
|
|
|
export interface ParsedLayout {
|
|
layoutId: string;
|
|
layoutName: string;
|
|
layoutDescription: string;
|
|
schema: LayoutSchema;
|
|
sampleData: Record<string, string>;
|
|
isJsonLayout: true;
|
|
}
|
|
|
|
/**
|
|
* Detect if a layout_code string is the new JSON element model format.
|
|
*/
|
|
export function isJsonLayoutCode(layoutCode: string): boolean {
|
|
if (!layoutCode || !layoutCode.trim().startsWith("{")) return false;
|
|
try {
|
|
const parsed = JSON.parse(layoutCode);
|
|
return Array.isArray(parsed.elements) && typeof parsed.layoutId === "string";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a JSON layout schema string into a ParsedLayout.
|
|
* Returns null if the string is not valid JSON or not the new format.
|
|
*/
|
|
export function parseLayoutSchema(layoutCode: string): ParsedLayout | null {
|
|
try {
|
|
const schema: LayoutSchema = JSON.parse(layoutCode);
|
|
if (!schema.elements || !Array.isArray(schema.elements)) {
|
|
return null;
|
|
}
|
|
|
|
// Build sample data from element defaults
|
|
const sampleData: Record<string, string> = {};
|
|
schema.elements.forEach((elem) => {
|
|
if (elem.placeholder && elem.defaultContent) {
|
|
sampleData[elem.placeholder] = elem.defaultContent;
|
|
}
|
|
});
|
|
|
|
return {
|
|
layoutId: schema.layoutId || "layout-0",
|
|
layoutName: schema.layoutName || "Layout",
|
|
layoutDescription: `${schema.elements.length} elements`,
|
|
schema,
|
|
sampleData,
|
|
isJsonLayout: true,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge layout schema elements with slide content data.
|
|
* Slide content keys map to element placeholder names.
|
|
*/
|
|
export function mergeElementsWithContent(
|
|
schema: LayoutSchema,
|
|
content?: Record<string, unknown>
|
|
): SlideElement[] {
|
|
return schema.elements.map((elem) => ({
|
|
...elem,
|
|
content:
|
|
(content?.[elem.placeholder] as string) ??
|
|
(content?.[elem.id] as string) ??
|
|
elem.defaultContent ??
|
|
"",
|
|
imageUrl:
|
|
(content?.[`${elem.placeholder}_url`] as string) ??
|
|
(content?.[`image_url`] as string) ??
|
|
undefined,
|
|
}));
|
|
}
|