Files
paint-ui/store/text-store.ts
Sebastian Krüger fea87d3a1e feat: implement comprehensive text tool (Phase 11)
Add complete text rendering system with the following features:

**Text Tool Core:**
- TextTool class with click-to-place text functionality
- Text cursor and pointer event handling
- Integration with DrawCommand for undo/redo support

**Text Rendering:**
- Multi-line text support with line height control
- Custom letter spacing
- Text alignment (left/center/right)
- Font families: 13 web-safe fonts + 14 popular Google Fonts
- Dynamic Google Font loading via Web Font Loader API
- Font styles (normal/italic) and weights (100-900)

**Text Dialog UI:**
- Full-featured text editor dialog
- Live preview of text with all formatting
- Font family selection (web-safe + Google Fonts)
- Font size (8-500px), style, and weight controls
- Color picker with hex input
- Text alignment options
- Line height slider (0.5-3x)
- Letter spacing slider (-10 to 50px)
- Multi-line text input with textarea

**State Management:**
- text-store with Zustand + persist middleware
- All text settings preserved across sessions
- Dialog state management (open/close)
- Click position tracking

**Integration:**
- Added text tool to tool palette with Type icon
- Registered TextTool in canvas tool system
- Added TextDialog to editor layout
- Full type safety with TypeScript interfaces

**Undoable:**
- Text rendering fully integrated with command pattern
- Each text insertion creates single undo point
- Proper before/after state capture

This completes Phase 11 of the implementation plan, marking
the transition from MVP to a fully-featured image editor.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 09:45:05 +01:00

98 lines
2.4 KiB
TypeScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { TextSettings, TextStore as ITextStore } from '@/types/text';
const DEFAULT_SETTINGS: TextSettings = {
text: '',
fontFamily: 'Arial',
fontSize: 48,
fontStyle: 'normal',
fontWeight: 'normal',
color: '#000000',
align: 'left',
baseline: 'alphabetic',
lineHeight: 1.2,
letterSpacing: 0,
};
export const useTextStore = create<ITextStore>()(
persist(
(set) => ({
settings: { ...DEFAULT_SETTINGS },
isDialogOpen: false,
clickPosition: null,
setText: (text) =>
set((state) => ({
settings: { ...state.settings, text },
})),
setFontFamily: (fontFamily) =>
set((state) => ({
settings: { ...state.settings, fontFamily },
})),
setFontSize: (fontSize) =>
set((state) => ({
settings: { ...state.settings, fontSize: Math.max(8, Math.min(500, fontSize)) },
})),
setFontStyle: (fontStyle) =>
set((state) => ({
settings: { ...state.settings, fontStyle },
})),
setFontWeight: (fontWeight) =>
set((state) => ({
settings: { ...state.settings, fontWeight },
})),
setColor: (color) =>
set((state) => ({
settings: { ...state.settings, color },
})),
setAlign: (align) =>
set((state) => ({
settings: { ...state.settings, align },
})),
setBaseline: (baseline) =>
set((state) => ({
settings: { ...state.settings, baseline },
})),
setLineHeight: (lineHeight) =>
set((state) => ({
settings: { ...state.settings, lineHeight: Math.max(0.5, Math.min(3, lineHeight)) },
})),
setLetterSpacing: (letterSpacing) =>
set((state) => ({
settings: { ...state.settings, letterSpacing: Math.max(-10, Math.min(50, letterSpacing)) },
})),
updateSettings: (settings) =>
set((state) => ({
settings: { ...state.settings, ...settings },
})),
openDialog: (x, y) =>
set({
isDialogOpen: true,
clickPosition: { x, y },
}),
closeDialog: () =>
set({
isDialogOpen: false,
clickPosition: null,
}),
}),
{
name: 'text-storage',
partialize: (state) => ({ settings: state.settings }), // Only persist settings, not dialog state
}
)
);