Add comprehensive UX and performance improvements: **Loading States & Feedback:** - Add loading overlay with spinner and custom messages - Integrate loading states into all file operations (open, save, export) - Create loading-store.ts for centralized loading state management **Keyboard Navigation:** - Expand keyboard shortcuts to include tool selection (1-7) - Add layer navigation with Arrow Up/Down - Add layer operations (Ctrl+D duplicate, Delete/Backspace remove) - Display keyboard shortcuts in tool tooltips - Enhanced keyboard shortcut system with proper key conflict handling **Performance - Code Splitting:** - Implement dynamic tool loader with lazy loading - Tools load on-demand when first selected - Preload common tools (pencil, brush, eraser) for instant access - Add tool caching to prevent redundant loads - Reduces initial bundle size and improves startup time **Integration:** - Add LoadingOverlay to app layout - Update canvas-with-tools to use lazy-loaded tool instances - Add keyboard shortcut hints to tool palette UI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
import type { BaseTool } from '@/tools';
|
|
import type { ToolType } from '@/types';
|
|
|
|
/**
|
|
* Tool loader cache
|
|
*/
|
|
const toolCache = new Map<string, BaseTool>();
|
|
const toolLoadingPromises = new Map<string, Promise<BaseTool>>();
|
|
|
|
/**
|
|
* Dynamically import and instantiate a tool
|
|
*/
|
|
async function loadTool(toolType: ToolType): Promise<BaseTool> {
|
|
// Check cache first
|
|
if (toolCache.has(toolType)) {
|
|
return toolCache.get(toolType)!;
|
|
}
|
|
|
|
// Check if already loading
|
|
if (toolLoadingPromises.has(toolType)) {
|
|
return toolLoadingPromises.get(toolType)!;
|
|
}
|
|
|
|
// Start loading
|
|
const loadPromise = (async () => {
|
|
let tool: BaseTool;
|
|
|
|
switch (toolType) {
|
|
case 'pencil': {
|
|
const { PencilTool } = await import('@/tools/pencil-tool');
|
|
tool = new PencilTool();
|
|
break;
|
|
}
|
|
case 'brush': {
|
|
const { BrushTool } = await import('@/tools/brush-tool');
|
|
tool = new BrushTool();
|
|
break;
|
|
}
|
|
case 'eraser': {
|
|
const { EraserTool } = await import('@/tools/eraser-tool');
|
|
tool = new EraserTool();
|
|
break;
|
|
}
|
|
case 'fill': {
|
|
const { FillTool } = await import('@/tools/fill-tool');
|
|
tool = new FillTool();
|
|
break;
|
|
}
|
|
case 'eyedropper': {
|
|
const { EyedropperTool } = await import('@/tools/eyedropper-tool');
|
|
tool = new EyedropperTool();
|
|
break;
|
|
}
|
|
case 'select':
|
|
case 'rectangular-select': {
|
|
const { RectangularSelectionTool } = await import('@/tools/rectangular-selection-tool');
|
|
tool = new RectangularSelectionTool();
|
|
break;
|
|
}
|
|
case 'elliptical-select': {
|
|
const { EllipticalSelectionTool } = await import('@/tools/elliptical-selection-tool');
|
|
tool = new EllipticalSelectionTool();
|
|
break;
|
|
}
|
|
case 'lasso-select': {
|
|
const { LassoSelectionTool } = await import('@/tools/lasso-selection-tool');
|
|
tool = new LassoSelectionTool();
|
|
break;
|
|
}
|
|
case 'magic-wand': {
|
|
const { MagicWandTool } = await import('@/tools/magic-wand-tool');
|
|
tool = new MagicWandTool();
|
|
break;
|
|
}
|
|
case 'move': {
|
|
const { MoveTool } = await import('@/tools/move-tool');
|
|
tool = new MoveTool();
|
|
break;
|
|
}
|
|
case 'transform': {
|
|
const { FreeTransformTool } = await import('@/tools/free-transform-tool');
|
|
tool = new FreeTransformTool();
|
|
break;
|
|
}
|
|
case 'shape': {
|
|
const { ShapeTool } = await import('@/tools/shape-tool');
|
|
tool = new ShapeTool();
|
|
break;
|
|
}
|
|
case 'text': {
|
|
const { TextTool } = await import('@/tools/text-tool');
|
|
tool = new TextTool();
|
|
break;
|
|
}
|
|
default: {
|
|
// Fallback to pencil tool
|
|
const { PencilTool } = await import('@/tools/pencil-tool');
|
|
tool = new PencilTool();
|
|
}
|
|
}
|
|
|
|
// Cache the tool
|
|
toolCache.set(toolType, tool);
|
|
toolLoadingPromises.delete(toolType);
|
|
|
|
return tool;
|
|
})();
|
|
|
|
toolLoadingPromises.set(toolType, loadPromise);
|
|
return loadPromise;
|
|
}
|
|
|
|
/**
|
|
* Get a tool instance (loads it if not cached)
|
|
*/
|
|
export async function getTool(toolType: ToolType): Promise<BaseTool> {
|
|
return loadTool(toolType);
|
|
}
|
|
|
|
/**
|
|
* Preload a tool (for performance optimization)
|
|
*/
|
|
export function preloadTool(toolType: ToolType): void {
|
|
loadTool(toolType).catch((error) => {
|
|
console.error(`Failed to preload tool ${toolType}:`, error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Preload commonly used tools
|
|
*/
|
|
export function preloadCommonTools(): void {
|
|
// Preload the most commonly used tools
|
|
preloadTool('pencil');
|
|
preloadTool('brush');
|
|
preloadTool('eraser');
|
|
}
|
|
|
|
/**
|
|
* Check if a tool is loaded
|
|
*/
|
|
export function isToolLoaded(toolType: ToolType): boolean {
|
|
return toolCache.has(toolType);
|
|
}
|
|
|
|
/**
|
|
* Clear tool cache (for testing)
|
|
*/
|
|
export function clearToolCache(): void {
|
|
toolCache.clear();
|
|
toolLoadingPromises.clear();
|
|
}
|