// Save/Load system — export and import circuits and progress import { state } from './state.js'; import { progress } from './levels.js'; const STORAGE_KEY = 'logiclab_state'; /** * Save complete application state to JSON */ export function saveState() { return { timestamp: new Date().toISOString(), circuit: { gates: state.gates, connections: state.connections, nextId: state.nextId }, camera: { camX: state.camX, camY: state.camY, zoom: state.zoom }, components: state.customComponents || {}, progress: { unlockedLevels: progress.unlockedLevels, completedLevels: progress.completedLevels, currentLevel: progress.currentLevel, customComponents: progress.customComponents } }; } /** * Load application state from JSON */ export function loadState(data) { if (!data || !data.circuit) { return { success: false, error: 'Invalid save data' }; } try { // Load circuit state.gates = JSON.parse(JSON.stringify(data.circuit.gates)); state.connections = JSON.parse(JSON.stringify(data.circuit.connections)); state.nextId = data.circuit.nextId; // Load camera if (data.camera) { state.camX = data.camera.camX; state.camY = data.camera.camY; state.zoom = data.camera.zoom; } // Load components if (data.components) { state.customComponents = JSON.parse(JSON.stringify(data.components)); } // Re-link gate.component references to customComponents (authoritative source) for (const gate of state.gates) { if (gate.type.startsWith('COMPONENT:')) { const compId = gate.type.substring(10); if (state.customComponents[compId]) { gate.component = state.customComponents[compId]; } } } // Load progress if (data.progress) { progress.unlockedLevels = data.progress.unlockedLevels || ['buffer']; progress.completedLevels = data.progress.completedLevels || []; progress.currentLevel = data.progress.currentLevel || null; progress.customComponents = data.progress.customComponents || {}; } return { success: true }; } catch (e) { return { success: false, error: e.message }; } } /** * Export as JSON file (for download) */ export function exportAsFile(filename = 'logic-circuit.json') { const data = saveState(); const json = JSON.stringify(data, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } /** * Import from file (returns Promise) */ export function importFromFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { try { const data = JSON.parse(e.target.result); const result = loadState(data); resolve(result); } catch (err) { reject(new Error('Failed to parse JSON')); } }; reader.onerror = () => reject(new Error('Failed to read file')); reader.readAsText(file); }); } /** * Export circuit as a simple netlist (text format) */ export function exportAsNetlist() { let netlist = '# Logic Circuit Netlist\n\n'; netlist += '## Gates\n'; state.gates.forEach(gate => { netlist += `${gate.type} ${gate.id} at (${gate.x.toFixed(0)}, ${gate.y.toFixed(0)})\n`; }); netlist += '\n## Connections\n'; state.connections.forEach(conn => { netlist += `${conn.from}:${conn.fromPort} -> ${conn.to}:${conn.toPort}\n`; }); return netlist; } /** * Copy state to clipboard as JSON (useful for sharing) */ export function copyToClipboard() { const data = saveState(); const json = JSON.stringify(data); navigator.clipboard.writeText(json).then(() => { alert('State copied to clipboard'); }).catch(() => { alert('Failed to copy to clipboard'); }); } /** * Paste state from clipboard */ export async function pasteFromClipboard() { try { const text = await navigator.clipboard.readText(); const data = JSON.parse(text); return loadState(data); } catch (e) { return { success: false, error: e.message }; } } /** * Save state to localStorage */ export function saveToStorage() { try { const data = saveState(); localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (e) { console.warn('[storage] failed to save:', e.message); } } /** * Load state from localStorage (returns true if state was restored) */ export function loadFromStorage() { try { const json = localStorage.getItem(STORAGE_KEY); if (!json) return false; const data = JSON.parse(json); const result = loadState(data); if (result.success) { console.log('[storage] restored state from localStorage'); } return result.success; } catch (e) { console.warn('[storage] failed to load:', e.message); return false; } } /** * Start auto-saving to localStorage on an interval */ let autoSaveInterval = null; export function startAutoSave(intervalMs = 3000) { if (autoSaveInterval) clearInterval(autoSaveInterval); autoSaveInterval = setInterval(saveToStorage, intervalMs); // Also save on page unload window.addEventListener('beforeunload', saveToStorage); console.log(`[storage] auto-save enabled (every ${intervalMs}ms)`); }