ppt-tool/frontend/app/hooks/parseLayoutSchema.ts
Vadym Samoilenko ae41562103 Phase 8: Data-driven slide architecture + template management overhaul
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>
2026-03-01 20:05:25 +00:00

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,
}));
}