import type { Scene } from '@/types/scene' const FILE_VERSION = 1 const MAGIC = 'animatr-project' export interface ProjectFile { magic: typeof MAGIC fileVersion: number savedAt: string scene: Scene } export function serializeProject(scene: Scene): string { const payload: ProjectFile = { magic: MAGIC, fileVersion: FILE_VERSION, savedAt: new Date().toISOString(), scene, } return JSON.stringify(payload, null, 2) } export class ProjectFileError extends Error { constructor(message: string) { super(message) this.name = 'ProjectFileError' } } export function parseProject(text: string): Scene { let data: unknown try { data = JSON.parse(text) } catch { throw new ProjectFileError('File is not valid JSON.') } if (!data || typeof data !== 'object') { throw new ProjectFileError('File is empty or not an object.') } const obj = data as Record if (obj.magic !== MAGIC) { throw new ProjectFileError('Not an Animatr project file (magic missing).') } const scene = obj.scene if (!scene || typeof scene !== 'object') { throw new ProjectFileError('Project file has no scene.') } const s = scene as Record if (!s.canvas || typeof s.canvas !== 'object') { throw new ProjectFileError('Scene data incomplete: canvas missing.') } if (!Array.isArray(s.layers)) { throw new ProjectFileError('Scene data incomplete: layers missing.') } if (!Array.isArray(s.assets)) { throw new ProjectFileError('Scene data incomplete: assets missing.') } return scene as Scene } export function downloadProject(scene: Scene, filename = 'projekt.animatr.json') { const source = serializeProject(scene) const blob = new Blob([source], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename document.body.appendChild(a) a.click() document.body.removeChild(a) setTimeout(() => URL.revokeObjectURL(url), 1000) } export function pickProjectFile(): Promise { return new Promise((resolve) => { const input = document.createElement('input') input.type = 'file' input.accept = '.json,application/json' input.onchange = () => { const f = input.files?.[0] resolve(f ?? null) } input.oncancel = () => resolve(null) input.click() }) } export async function readProjectFile(file: File): Promise { const text = await file.text() return parseProject(text) }