import { defineStore } from 'pinia' import { ref } from 'vue' import { useWindowsStore } from './windows' import { getScriptLog, clearScriptLog, getWindowDefinitions, clearWindowDefinitions, type WindowDefinitionEntry } from '../services/tools/handlers/canvasHandlers' const API_URL = '' // ============================================ // TYPES // ============================================ export interface SnapshotWindow { id: string title: string x: number y: number width: number height: number zIndex: number source: 'db' | 'inline' componentId?: string definition?: { id: string name: string template: string setup?: string style?: string imports?: string[] props?: string[] } componentProps: Record } export interface SnapshotCSSBlock { id: string css: string } export interface CanvasSnapshot { id: string name: string created: number baseHTML: string | null baseScripts: string[] cssBlocks: SnapshotCSSBlock[] windows: SnapshotWindow[] } export interface SnapshotSummary { id: string name: string thumbnail: string | null created_at: number } // ============================================ // STORE // ============================================ export const useSnapshotsStore = defineStore('snapshots', () => { const snapshots = ref([]) // ---- Capture current canvas state ---- function captureState(name: string): CanvasSnapshot { const windowsStore = useWindowsStore() const winDefs = getWindowDefinitions() // 1. Capture base HTML (excluding floating window containers) const container = document.getElementById('canvas-content') let baseHTML: string | null = null if (container) { const clone = container.cloneNode(true) as HTMLElement // Remove dynamic component wrappers (floating windows) clone.querySelectorAll('.dynamic-component-wrapper').forEach(el => el.remove()) const html = clone.innerHTML.trim() baseHTML = html || null } // 2. Capture scripts const baseScripts = getScriptLog() // 3. Capture CSS blocks const cssBlocks: SnapshotCSSBlock[] = [] document.querySelectorAll('style[id^="canvas-css-"]').forEach(style => { const id = style.id.replace('canvas-css-', '') cssBlocks.push({ id, css: style.textContent || '' }) }) // 4. Capture windows with definitions const windows: SnapshotWindow[] = [] for (const win of windowsStore.windowsList) { const entry: WindowDefinitionEntry | undefined = winDefs.get(win.id) const snapWin: SnapshotWindow = { id: win.id, title: win.title, x: win.x, y: win.y, width: win.width, height: win.height, zIndex: win.zIndex, source: entry?.source || 'inline', componentProps: entry?.componentProps || {} } if (entry?.source === 'db' && entry.componentId) { snapWin.componentId = entry.componentId } if (entry?.definition) { snapWin.definition = { id: entry.definition.id, name: entry.definition.name, template: entry.definition.template, setup: entry.definition.setup, style: entry.definition.style, imports: entry.definition.imports, props: entry.definition.props } } windows.push(snapWin) } return { id: `snap-${Date.now()}`, name, created: Date.now(), baseHTML, baseScripts, cssBlocks, windows } } // ---- Save snapshot to backend ---- async function save(name: string): Promise { const snapshot = captureState(name) const res = await fetch(`${API_URL}/api/snapshots`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: snapshot.id, name: snapshot.name, data: snapshot, created_at: snapshot.created }) }) const result = await res.json() await list() // refresh list return result.id } // ---- List snapshots ---- async function list(): Promise { const res = await fetch(`${API_URL}/api/snapshots`) const data = await res.json() snapshots.value = data return data } // ---- Load full snapshot ---- async function load(id: string): Promise { const res = await fetch(`${API_URL}/api/snapshots/${id}`) const row = await res.json() return row.data } // ---- Remove snapshot ---- async function remove(id: string): Promise { await fetch(`${API_URL}/api/snapshots/${id}`, { method: 'DELETE' }) await list() } // ---- Restore a snapshot ---- async function restore(id: string): Promise { const snapshot = await load(id) const windowsStore = useWindowsStore() // 1. Close all current windows for (const win of windowsStore.windowsList) { const container = document.getElementById(`inline-${win.id}`) if (container) container.remove() document.getElementById(`style-${win.id}`)?.remove() } windowsStore.clear() clearWindowDefinitions() clearScriptLog() // 2. Remove existing canvas CSS document.querySelectorAll('style[id^="canvas-css-"]').forEach(el => el.remove()) // 3. Restore base HTML const canvasEl = document.getElementById('canvas-content') if (canvasEl) { if (snapshot.baseHTML) { canvasEl.innerHTML = snapshot.baseHTML // Execute inline scripts canvasEl.querySelectorAll('script').forEach(oldScript => { const newScript = document.createElement('script') Array.from(oldScript.attributes).forEach(attr => { newScript.setAttribute(attr.name, attr.value) }) newScript.textContent = oldScript.textContent oldScript.parentNode?.replaceChild(newScript, oldScript) }) } else { canvasEl.innerHTML = '' } } // 4. Restore CSS blocks for (const block of snapshot.cssBlocks) { const styleEl = document.createElement('style') styleEl.id = `canvas-css-${block.id}` document.head.appendChild(styleEl) styleEl.textContent = block.css } // 5. Re-execute scripts if (canvasEl && snapshot.baseScripts.length > 0) { for (const code of snapshot.baseScripts) { try { const context = { canvas: canvasEl, windows: windowsStore.windowsList, getWindow: (wid: string) => document.querySelector(`[data-window-id="${wid}"]`), $: (selector: string) => canvasEl.querySelector(selector), $$: (selector: string) => canvasEl.querySelectorAll(selector) } const fn = new Function('ctx', `with(ctx) { ${code} }`) fn(context) } catch (e) { console.warn('[Snapshot] Script replay error:', e) } } } // 6. Restore windows — dynamically import to avoid circular deps const { renderInlineComponent } = await import('../services/dynamicComponents') if (canvasEl) { for (const win of snapshot.windows) { if (!win.definition) continue const definition = { id: win.id, name: win.definition.name, template: win.definition.template, setup: win.definition.setup, style: win.definition.style, props: win.definition.props, imports: win.definition.imports || ['ref', 'reactive', 'computed'] } const layout = { x: win.x, y: win.y, width: win.width, height: win.height } renderInlineComponent(definition, canvasEl, win.componentProps || {}, true, layout) // Re-track definition getWindowDefinitions().set(win.id, { source: win.source, componentId: win.componentId, definition, componentProps: win.componentProps || {} }) } } } return { snapshots, captureState, save, list, load, remove, restore } })