import { createModuleLogger } from "./LoggerUtils.js"; import { createCanvas } from "./CommonUtils.js"; import { withErrorHandling, createValidationError } from "../ErrorHandler.js"; const log = createModuleLogger('IconLoader'); // Define tool constants for LayerForge export const LAYERFORGE_TOOLS = { VISIBILITY: 'visibility', MOVE: 'move', ROTATE: 'rotate', SCALE: 'scale', DELETE: 'delete', DUPLICATE: 'duplicate', BLEND_MODE: 'blend_mode', OPACITY: 'opacity', MASK: 'mask', BRUSH: 'brush', ERASER: 'eraser', SHAPE: 'shape', SETTINGS: 'settings', SYSTEM_CLIPBOARD: 'system_clipboard', CLIPSPACE: 'clipspace', }; // SVG Icons for LayerForge tools const SYSTEM_CLIPBOARD_ICON_SVG = ``; const CLIPSPACE_ICON_SVG = ` `; const LAYERFORGE_TOOL_ICONS = { [LAYERFORGE_TOOLS.SYSTEM_CLIPBOARD]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(SYSTEM_CLIPBOARD_ICON_SVG)}`, [LAYERFORGE_TOOLS.CLIPSPACE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(CLIPSPACE_ICON_SVG)}`, [LAYERFORGE_TOOLS.VISIBILITY]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.MOVE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.ROTATE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.SCALE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.DELETE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.DUPLICATE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.BLEND_MODE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.OPACITY]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.MASK]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.BRUSH]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.ERASER]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.SHAPE]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`, [LAYERFORGE_TOOLS.SETTINGS]: `data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}` }; // Tool colors for LayerForge const LAYERFORGE_TOOL_COLORS = { [LAYERFORGE_TOOLS.VISIBILITY]: '#4285F4', [LAYERFORGE_TOOLS.MOVE]: '#34A853', [LAYERFORGE_TOOLS.ROTATE]: '#FBBC05', [LAYERFORGE_TOOLS.SCALE]: '#EA4335', [LAYERFORGE_TOOLS.DELETE]: '#FF6D01', [LAYERFORGE_TOOLS.DUPLICATE]: '#46BDC6', [LAYERFORGE_TOOLS.BLEND_MODE]: '#9C27B0', [LAYERFORGE_TOOLS.OPACITY]: '#8BC34A', [LAYERFORGE_TOOLS.MASK]: '#607D8B', [LAYERFORGE_TOOLS.BRUSH]: '#4285F4', [LAYERFORGE_TOOLS.ERASER]: '#FBBC05', [LAYERFORGE_TOOLS.SHAPE]: '#FF6D01', [LAYERFORGE_TOOLS.SETTINGS]: '#F06292' }; export class IconLoader { constructor() { this._iconCache = {}; this._loadingPromises = new Map(); /** * Preload all LayerForge tool icons */ this.preloadToolIcons = withErrorHandling(async () => { log.info('Starting to preload LayerForge tool icons'); const loadPromises = Object.keys(LAYERFORGE_TOOL_ICONS).map(tool => { return this.loadIcon(tool); }); await Promise.all(loadPromises); log.info(`Successfully preloaded ${loadPromises.length} tool icons`); }, 'IconLoader.preloadToolIcons'); /** * Load a specific icon by tool name */ this.loadIcon = withErrorHandling(async (tool) => { if (!tool) { throw createValidationError("Tool name is required", { tool }); } // Check if already cached if (this._iconCache[tool] && this._iconCache[tool] instanceof HTMLImageElement) { return this._iconCache[tool]; } // Check if already loading if (this._loadingPromises.has(tool)) { return this._loadingPromises.get(tool); } // Create fallback canvas first const fallbackCanvas = this.createFallbackIcon(tool); this._iconCache[tool] = fallbackCanvas; // Start loading the SVG icon const loadPromise = new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { this._iconCache[tool] = img; this._loadingPromises.delete(tool); log.debug(`Successfully loaded icon for tool: ${tool}`); resolve(img); }; img.onerror = (error) => { log.warn(`Failed to load SVG icon for tool: ${tool}, using fallback`); this._loadingPromises.delete(tool); // Keep the fallback canvas in cache reject(error); }; const iconData = LAYERFORGE_TOOL_ICONS[tool]; if (iconData) { img.src = iconData; } else { log.warn(`No icon data found for tool: ${tool}`); reject(createValidationError(`No icon data for tool: ${tool}`, { tool, availableTools: Object.keys(LAYERFORGE_TOOL_ICONS) })); } }); this._loadingPromises.set(tool, loadPromise); return loadPromise; }, 'IconLoader.loadIcon'); log.info('IconLoader initialized'); } /** * Create a fallback canvas icon with colored background and text */ createFallbackIcon(tool) { const { canvas, ctx } = createCanvas(24, 24); if (!ctx) { log.error('Failed to get canvas context for fallback icon'); return canvas; } // Fill background with tool color const color = LAYERFORGE_TOOL_COLORS[tool] || '#888888'; ctx.fillStyle = color; ctx.fillRect(0, 0, 24, 24); // Add border ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1; ctx.strokeRect(0.5, 0.5, 23, 23); // Add text ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const firstChar = tool.charAt(0).toUpperCase(); ctx.fillText(firstChar, 12, 12); return canvas; } /** * Get cached icon (canvas or image) */ getIcon(tool) { return this._iconCache[tool] || null; } /** * Check if icon is loaded (as image, not fallback canvas) */ isIconLoaded(tool) { return this._iconCache[tool] instanceof HTMLImageElement; } /** * Clear all cached icons */ clearCache() { this._iconCache = {}; this._loadingPromises.clear(); log.info('Icon cache cleared'); } /** * Get all available tool names */ getAvailableTools() { return Object.values(LAYERFORGE_TOOLS); } /** * Get tool color */ getToolColor(tool) { return LAYERFORGE_TOOL_COLORS[tool] || '#888888'; } } // Export singleton instance export const iconLoader = new IconLoader(); // Export for external use export { LAYERFORGE_TOOL_ICONS, LAYERFORGE_TOOL_COLORS };