feat: implement UI state persistence and theme toggle
Major improvements to UI state management and user preferences: - Add theme toggle with dark/light mode support - Implement Zustand persist middleware for UI state - Add ui-store for panel layout preferences (dock width, heights, tabs) - Persist tool settings (active tool, size, opacity, hardness, etc.) - Persist canvas view preferences (grid, rulers, snap-to-grid) - Persist shape tool settings - Persist collapsible section states - Fix canvas coordinate transformation for centered rendering - Constrain checkerboard and grid to canvas bounds - Add icons to all tab buttons and collapsible sections - Restructure panel-dock to use persisted state Storage impact: ~3.5KB total across all preferences Storage keys: tool-storage, canvas-view-storage, shape-storage, ui-storage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { CanvasState, CanvasSelection, Point } from '@/types';
|
||||
|
||||
interface CanvasStore extends CanvasState {
|
||||
@@ -38,7 +39,7 @@ interface CanvasStore extends CanvasState {
|
||||
/** Clear selection */
|
||||
clearSelection: () => void;
|
||||
/** Convert screen coordinates to canvas coordinates */
|
||||
screenToCanvas: (screenX: number, screenY: number) => Point;
|
||||
screenToCanvas: (screenX: number, screenY: number, containerWidth?: number, containerHeight?: number) => Point;
|
||||
/** Convert canvas coordinates to screen coordinates */
|
||||
canvasToScreen: (canvasX: number, canvasY: number) => Point;
|
||||
}
|
||||
@@ -49,24 +50,26 @@ const MIN_ZOOM = 0.1;
|
||||
const MAX_ZOOM = 10;
|
||||
const ZOOM_STEP = 1.2;
|
||||
|
||||
export const useCanvasStore = create<CanvasStore>((set, get) => ({
|
||||
width: DEFAULT_CANVAS_WIDTH,
|
||||
height: DEFAULT_CANVAS_HEIGHT,
|
||||
zoom: 1,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
backgroundColor: '#ffffff',
|
||||
showGrid: false,
|
||||
gridSize: 20,
|
||||
showRulers: true,
|
||||
snapToGrid: false,
|
||||
selection: {
|
||||
active: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
export const useCanvasStore = create<CanvasStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
width: DEFAULT_CANVAS_WIDTH,
|
||||
height: DEFAULT_CANVAS_HEIGHT,
|
||||
zoom: 1,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
backgroundColor: '#ffffff',
|
||||
showGrid: false,
|
||||
gridSize: 20,
|
||||
showRulers: true,
|
||||
snapToGrid: false,
|
||||
selection: {
|
||||
active: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
|
||||
setDimensions: (width, height) => {
|
||||
set({ width, height });
|
||||
@@ -153,11 +156,20 @@ export const useCanvasStore = create<CanvasStore>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
screenToCanvas: (screenX, screenY) => {
|
||||
const { zoom, offsetX, offsetY } = get();
|
||||
screenToCanvas: (screenX, screenY, containerWidth = 0, containerHeight = 0) => {
|
||||
const { zoom, offsetX, offsetY, width, height } = get();
|
||||
// The canvas is rendered with this transformation:
|
||||
// 1. translate(offsetX + containerWidth/2, offsetY + containerHeight/2) - center in viewport with offset
|
||||
// 2. scale(zoom) - apply zoom
|
||||
// 3. translate(-width/2, -height/2) - position canvas so (0,0) is at top-left
|
||||
//
|
||||
// To reverse:
|
||||
// 1. Subtract container center and offset
|
||||
// 2. Divide by zoom
|
||||
// 3. Add canvas center
|
||||
return {
|
||||
x: (screenX - offsetX) / zoom,
|
||||
y: (screenY - offsetY) / zoom,
|
||||
x: (screenX - containerWidth / 2 - offsetX) / zoom + width / 2,
|
||||
y: (screenY - containerHeight / 2 - offsetY) / zoom + height / 2,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -168,4 +180,17 @@ export const useCanvasStore = create<CanvasStore>((set, get) => ({
|
||||
y: canvasY * zoom + offsetY,
|
||||
};
|
||||
},
|
||||
}));
|
||||
}),
|
||||
{
|
||||
name: 'canvas-view-storage',
|
||||
partialize: (state) => ({
|
||||
backgroundColor: state.backgroundColor,
|
||||
showGrid: state.showGrid,
|
||||
gridSize: state.gridSize,
|
||||
showRulers: state.showRulers,
|
||||
snapToGrid: state.snapToGrid,
|
||||
// Exclude: width, height, zoom, offsetX, offsetY, selection
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user