/** * SettingsManager — Persistent settings via localStorage (#2) + input validation (#18) */ const SettingsManager = { STORAGE_KEY: 'screenshotStudioSettings', // Default settings for each tool DEFAULTS: { text: { zoom: 2.1, overlap: 20, viewportWidth: 1920, viewportHeight: 1080, maxScreenshots: 50 }, html: { zoom: 2.1, overlap: 20, viewportWidth: 1920, viewportHeight: 1080, maxScreenshots: 50 }, image: { zoom: 2.1, overlap: 20, viewportWidth: 1920, viewportHeight: 1080, maxScreenshots: 50 }, activeTool: 'text-to-image' }, // Validation ranges (#18) RANGES: { zoom: { min: 0.5, max: 5, warn: 4 }, overlap: { min: 0, max: 200 }, viewportWidth: { min: 800, max: 3840, warn: 3000 }, viewportHeight: { min: 600, max: 2160 }, maxScreenshots: { min: 1, max: 100 } }, /** * Load all saved settings from localStorage. */ load() { try { const raw = localStorage.getItem(this.STORAGE_KEY); if (raw) { return { ...this.DEFAULTS, ...JSON.parse(raw) }; } } catch (e) { console.warn('Failed to load settings:', e); } return { ...this.DEFAULTS }; }, /** * Save all settings to localStorage. */ save(settings) { try { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(settings)); } catch (e) { console.warn('Failed to save settings:', e); } }, /** * Save the active tool tab. */ saveActiveTool(toolId) { const settings = this.load(); settings.activeTool = toolId; this.save(settings); }, /** * Read settings from DOM inputs for a specific tool. */ readFromDOM(tool) { const prefix = { text: 'text', html: 'html', image: 'image-' }[tool] || tool; const isImage = tool === 'image'; const getId = (field) => { if (isImage) return `image-${field.replace(/([A-Z])/g, '-$1').toLowerCase()}`; return `${prefix}${field.charAt(0).toUpperCase() + field.slice(1)}`; }; const idMap = { text: { zoom: 'textZoom', overlap: 'textOverlap', viewportWidth: 'textViewportWidth', viewportHeight: 'textViewportHeight', maxScreenshots: 'textMaxScreenshots' }, html: { zoom: 'htmlZoom', overlap: 'htmlOverlap', viewportWidth: 'htmlViewportWidth', viewportHeight: 'htmlViewportHeight', maxScreenshots: 'htmlMaxScreenshots' }, image: { zoom: 'image-zoom', overlap: 'image-overlap', viewportWidth: 'image-viewport-width', viewportHeight: 'image-viewport-height', maxScreenshots: 'image-max-screenshots' } }; const ids = idMap[tool]; if (!ids) return null; const result = {}; for (const [key, id] of Object.entries(ids)) { const el = document.getElementById(id); if (el) result[key] = parseFloat(el.value); } return result; }, /** * Write settings to DOM inputs for a specific tool. */ writeToDOM(tool, values) { const idMap = { text: { zoom: 'textZoom', overlap: 'textOverlap', viewportWidth: 'textViewportWidth', viewportHeight: 'textViewportHeight', maxScreenshots: 'textMaxScreenshots' }, html: { zoom: 'htmlZoom', overlap: 'htmlOverlap', viewportWidth: 'htmlViewportWidth', viewportHeight: 'htmlViewportHeight', maxScreenshots: 'htmlMaxScreenshots' }, image: { zoom: 'image-zoom', overlap: 'image-overlap', viewportWidth: 'image-viewport-width', viewportHeight: 'image-viewport-height', maxScreenshots: 'image-max-screenshots' } }; const ids = idMap[tool]; if (!ids || !values) return; for (const [key, id] of Object.entries(ids)) { const el = document.getElementById(id); if (el && values[key] !== undefined) { el.value = values[key]; } } }, /** * Save current tool settings from DOM. */ saveToolSettings(tool) { const settings = this.load(); const values = this.readFromDOM(tool); if (values) { settings[tool] = values; this.save(settings); } }, /** * Restore saved settings to DOM. */ restoreToolSettings(tool) { const settings = this.load(); if (settings[tool]) { this.writeToDOM(tool, settings[tool]); } }, /** * Validate a single value and clamp to range (#18). * Returns { value, warning } or null if ok. */ validate(field, value) { const range = this.RANGES[field]; if (!range) return { value, warning: null }; const num = parseFloat(value); if (isNaN(num)) return { value: range.min, warning: `Invalid value for ${field}` }; let warning = null; let clamped = Math.max(range.min, Math.min(range.max, num)); if (clamped !== num) { warning = `${field} clamped to ${clamped} (range: ${range.min}–${range.max})`; } else if (range.warn && num > range.warn) { warning = `High ${field} value (${num}) may slow down rendering`; } return { value: clamped, warning }; }, /** * Validate all settings for a tool, clamp values, and show warnings (#18). * Returns validated settings object. */ validateAll(tool) { const values = this.readFromDOM(tool); if (!values) return null; const warnings = []; const validated = {}; for (const [key, val] of Object.entries(values)) { const result = this.validate(key, val); validated[key] = result.value; if (result.warning) warnings.push(result.warning); } // Write clamped values back to DOM this.writeToDOM(tool, validated); // Show warnings if (warnings.length > 0 && typeof notificationManager !== 'undefined') { notificationManager.warning('Settings Adjusted', warnings.join('. ')); } return validated; }, /** * Initialize: restore all tool settings and set active tool. */ init() { const settings = this.load(); // Restore settings for all tools this.restoreToolSettings('text'); this.restoreToolSettings('html'); this.restoreToolSettings('image'); // Restore active tool if (settings.activeTool && settings.activeTool !== 'text-to-image') { const navItem = document.querySelector(`.nav-item[onclick*="${settings.activeTool}"]`); if (navItem) { navItem.click(); } } // Auto-save settings before generate this._attachSaveListeners(); }, /** * Listen for changes on settings inputs and auto-save. */ _attachSaveListeners() { // Text settings ['textZoom', 'textOverlap', 'textViewportWidth', 'textViewportHeight', 'textMaxScreenshots'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('change', () => this.saveToolSettings('text')); }); // HTML settings ['htmlZoom', 'htmlOverlap', 'htmlViewportWidth', 'htmlViewportHeight', 'htmlMaxScreenshots'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('change', () => this.saveToolSettings('html')); }); // Image settings ['image-zoom', 'image-overlap', 'image-viewport-width', 'image-viewport-height', 'image-max-screenshots'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('change', () => this.saveToolSettings('image')); }); } }; // Initialize on DOM ready document.addEventListener('DOMContentLoaded', () => { SettingsManager.init(); });