// Global state management for Lightningbeam // This module centralizes all global state that was previously scattered in main.js import { deepMerge } from "./utils.js"; // Core application context // Contains UI state, selections, tool settings, etc. export let context = { mouseDown: false, mousePos: { x: 0, y: 0 }, swatches: [ "#000000", "#FFFFFF", "#FF0000", "#FFFF00", "#00FF00", "#00FFFF", "#0000FF", "#FF00FF", ], lineWidth: 5, simplifyMode: "smooth", fillShape: false, strokeShape: true, fillGaps: 5, dropperColor: "Fill color", dragging: false, selectionRect: undefined, selection: [], shapeselection: [], oldselection: [], oldshapeselection: [], selectedFrames: [], dragDirection: undefined, zoomLevel: 1, timelineWidget: null, // Reference to TimelineWindowV2 widget for zoom controls config: null, // Reference to config object (set after config is initialized) mode: "select", // Current tool mode // Playback and recording state playing: false, isRecording: false, recordingTrackId: null, recordingClipId: null, playPauseButton: null, // Reference to play/pause button for updating appearance // MIDI activity indicator lastMidiInputTime: 0, // Timestamp (Date.now()) of last MIDI input // Metronome state metronomeEnabled: false, metronomeButton: null, // Reference to metronome button for updating appearance metronomeGroup: null, // Reference to metronome button group for showing/hiding }; // Application configuration // Contains settings, shortcuts, file properties, etc. export let config = { shortcuts: { playAnimation: " ", undo: "z", redo: "Z", new: "n", newWindow: "N", save: "s", saveAs: "S", open: "o", import: "i", export: "e", quit: "q", copy: "c", paste: "v", delete: "Backspace", selectAll: "a", selectNone: "A", group: "g", addLayer: "l", addAudioTrack: "t", addKeyframe: "F6", addBlankKeyframe: "F7", zoomIn: "+", zoomOut: "-", resetZoom: "0", nextLayout: "Tab", previousLayout: "Tab", }, fileWidth: 800, fileHeight: 600, framerate: 24, bpm: 120, timeSignature: { numerator: 4, denominator: 4 }, recentFiles: [], scrollSpeed: 1, debug: false, reopenLastSession: false, lastImportFilterIndex: 0, // Index of last used filter in import dialog (0=Image, 1=Audio, 2=Lightningbeam) audioBufferSize: 256, // Audio buffer size in frames (128, 256, 512, 1024, etc. - requires restart) minClipDuration: 0.1, // Minimum clip duration in seconds when trimming // Layout settings currentLayout: "animation", // Current active layout key defaultLayout: "animation", // Default layout for new files showStartScreen: false, // Show layout picker on startup (disabled for now) restoreLayoutFromFile: true, // Restore layout when opening files customLayouts: [] // User-saved custom layouts }; // Object pointer registry // Maps UUIDs to object instances for quick lookup export let pointerList = {}; // Undo/redo state tracking // Stores initial property values when starting an action export let startProps = {}; // Helper function to get keyboard shortcut in platform format export function getShortcut(shortcut) { if (!(shortcut in config.shortcuts)) return undefined; let shortcutValue = config.shortcuts[shortcut].replace("", "CmdOrCtrl+"); const key = shortcutValue.slice(-1); // If the last character is uppercase, prepend "Shift+" to it return key === key.toUpperCase() && key !== key.toLowerCase() ? shortcutValue.replace(key, `Shift+${key}`) : shortcutValue.replace("++", "+Shift+="); // Hardcode uppercase from = to + } // Configuration file management const CONFIG_FILE_PATH = "config.json"; // Load configuration from localStorage export async function loadConfig() { try { const configData = localStorage.getItem("lightningbeamConfig") || "{}"; const loaded = JSON.parse(configData); // Merge loaded config with defaults Object.assign(config, deepMerge({ ...config }, loaded)); // Ensure recentFiles is always an array (fix legacy string format) let needsResave = false; if (typeof config.recentFiles === 'string') { config.recentFiles = config.recentFiles.split(',').filter(f => f.length > 0); needsResave = true; } else if (!Array.isArray(config.recentFiles)) { config.recentFiles = []; needsResave = true; } // Make config accessible to widgets via context context.config = config; console.log('[loadConfig] Loaded config.recentFiles:', config.recentFiles); // Re-save config if we had to fix the format if (needsResave) { console.log('[loadConfig] Re-saving config to fix array format'); await saveConfig(); } return config; } catch (error) { console.log("Error loading config, using defaults:", error); context.config = config; return config; } } // Save configuration to localStorage export async function saveConfig() { try { localStorage.setItem( "lightningbeamConfig", JSON.stringify(config, null, 2), ); } catch (error) { console.error("Error saving config:", error); } } // Add a file to recent files list export async function addRecentFile(filePath) { config.recentFiles = [ filePath, ...config.recentFiles.filter(file => file !== filePath) ].slice(0, 10); console.log('[addRecentFile] Added file, recentFiles now:', config.recentFiles); await saveConfig(); } // Utility to reset pointer list (useful for testing) export function clearPointerList() { pointerList = {}; } // Utility to reset start props (useful for testing) export function clearStartProps() { startProps = {}; } // Helper to register an object in the pointer list export function registerObject(uuid, object) { pointerList[uuid] = object; } // Helper to unregister an object from the pointer list export function unregisterObject(uuid) { delete pointerList[uuid]; } // Helper to get an object from the pointer list export function getObject(uuid) { return pointerList[uuid]; }