diff --git a/src/lib/studio-scene-data.ts b/src/lib/studio-scene-data.ts index 0b1913b..0c70553 100644 --- a/src/lib/studio-scene-data.ts +++ b/src/lib/studio-scene-data.ts @@ -43,14 +43,75 @@ function parseLayer(value: unknown): Layer | null { }; } +/** + * Bridge a V2 template/saved-project scene (content-element model, no canvas + * geometry) into editable studio layers so every input shows in the editor. + * Text/TextArea → text layer; Media/Image/Video/Audio → image layer. Laid out in a + * column since AE templates carry no canvas coords. One-time on first load; after the + * user edits + saves, the scene persists in the studio's own layer format. + */ +function layersFromContents(contents: unknown[]): Layer[] { + const str = (v: unknown) => (typeof v === "string" ? v : ""); + const num = (v: unknown) => (typeof v === "number" ? v : undefined); + const items = contents.filter(isRecord); + items.sort( + (a, b) => + (num(a.sort) ?? num(a.positionInContainer) ?? 0) - + (num(b.sort) ?? num(b.positionInContainer) ?? 0) + ); + return items.map((c, i) => { + const type = str(c.type).toLowerCase(); + const isMedia = ["media", "image", "video", "audio", "voiceover"].includes(type); + const key = str(c.key) || str(c.id) || `el${i}`; + const value = str(c.value) || str(c.defaultValue) || str(c.default_value); + return { + id: `c-${key}`, + type: isMedia ? "image" : "text", + name: str(c.title) || key, + x: 160, + y: 160 + i * 150, + width: 1600, + height: isMedia ? 360 : 110, + rotation: 0, + opacity: 1, + zIndex: i, + props: isMedia + ? { src: value } + : { + text: value || (str(c.title) || key), + fontSize: 48, + fill: "#111827", + fontFamily: "Inter, sans-serif", + align: "center", + }, + } as Layer; + }); +} + function parseScene(value: unknown): Scene | null { if (!isRecord(value)) return null; - if (typeof value.id !== "string" || typeof value.name !== "string") return null; - if (!Array.isArray(value.layers)) return null; + if (typeof value.id !== "string") return null; + const name = + typeof value.name === "string" + ? value.name + : typeof value.title === "string" + ? value.title + : typeof value.key === "string" + ? value.key + : "Scene"; - const layers = value.layers - .map(parseLayer) - .filter((layer): layer is Layer => layer !== null); + // Studio's own format carries `layers`; a freshly-copied V2 template carries + // `contents` (content elements / inputs) instead — bridge those to layers. + let layers: Layer[]; + if (Array.isArray(value.layers)) { + layers = value.layers + .map(parseLayer) + .filter((layer): layer is Layer => layer !== null); + } else if (Array.isArray(value.contents)) { + layers = layersFromContents(value.contents); + } else { + return null; + } const transitionType = typeof value.transitionType === "string" && @@ -60,9 +121,15 @@ function parseScene(value: unknown): Scene | null { return { id: value.id, - name: value.name, + name, duration: - typeof value.duration === "number" ? value.duration : DEFAULT_SCENE_DURATION, + typeof value.duration === "number" + ? value.duration + : typeof value.sceneLengthSec === "number" + ? value.sceneLengthSec + : typeof value.defaultDurationSec === "number" + ? value.defaultDurationSec + : DEFAULT_SCENE_DURATION, layers, transitionType, };