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:
2025-11-21 09:03:14 +01:00
parent 50bfd2940f
commit cd59f0606b
17 changed files with 570 additions and 264 deletions

View File

@@ -41,12 +41,15 @@ export function drawCheckerboard(
ctx: CanvasRenderingContext2D,
squareSize = 10,
color1 = '#ffffff',
color2 = '#cccccc'
color2 = '#cccccc',
width?: number,
height?: number
): void {
const { width, height } = ctx.canvas;
const w = width ?? ctx.canvas.width;
const h = height ?? ctx.canvas.height;
for (let y = 0; y < height; y += squareSize) {
for (let x = 0; x < width; x += squareSize) {
for (let y = 0; y < h; y += squareSize) {
for (let x = 0; x < w; x += squareSize) {
const isEven = (Math.floor(x / squareSize) + Math.floor(y / squareSize)) % 2 === 0;
ctx.fillStyle = isEven ? color1 : color2;
ctx.fillRect(x, y, squareSize, squareSize);
@@ -160,26 +163,29 @@ export async function loadImageFromFile(file: File): Promise<HTMLImageElement> {
export function drawGrid(
ctx: CanvasRenderingContext2D,
gridSize: number,
color = 'rgba(0, 0, 0, 0.1)'
color = 'rgba(0, 0, 0, 0.1)',
width?: number,
height?: number
): void {
const { width, height } = ctx.canvas;
const w = width ?? ctx.canvas.width;
const h = height ?? ctx.canvas.height;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
// Vertical lines
for (let x = 0; x <= width; x += gridSize) {
for (let x = 0; x <= w; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x + 0.5, 0);
ctx.lineTo(x + 0.5, height);
ctx.lineTo(x + 0.5, h);
ctx.stroke();
}
// Horizontal lines
for (let y = 0; y <= height; y += gridSize) {
for (let y = 0; y <= h; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y + 0.5);
ctx.lineTo(width, y + 0.5);
ctx.lineTo(w, y + 0.5);
ctx.stroke();
}
}