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()( persist( (set, get) => ({ settings: { ...DEFAULT_SETTINGS }, textObjects: [], isOnCanvasEditorActive: false, editorPosition: null, editorText: '', editingTextId: 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 }, })), // Text object management addTextObject: (textObject) => set((state) => ({ textObjects: [ ...state.textObjects, { ...textObject, id: `text-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, }, ], })), updateTextObject: (id, updates) => set((state) => ({ textObjects: state.textObjects.map((obj) => obj.id === id ? { ...obj, ...updates } : obj ), })), deleteTextObject: (id) => set((state) => ({ textObjects: state.textObjects.filter((obj) => obj.id !== id), })), getTextObjectAt: (x, y, layerId) => { const state = get(); // Create temporary canvas for text measurement const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return null; // Find text objects at position (reverse order to get topmost) for (let i = state.textObjects.length - 1; i >= 0; i--) { const obj = state.textObjects[i]; if (obj.layerId !== layerId) continue; // Measure text properly ctx.font = `${obj.fontStyle} ${obj.fontWeight} ${obj.fontSize}px ${obj.fontFamily}`; const lines = obj.text.split('\n'); let maxWidth = 0; lines.forEach((line) => { const metrics = ctx.measureText(line || ' '); if (metrics.width > maxWidth) maxWidth = metrics.width; }); // Account for the padding we add in the editor (40px horizontal, 10px vertical) const width = maxWidth + 40; const height = lines.length * obj.fontSize * obj.lineHeight + 10; // The text is stored at obj.x, obj.y (which already includes the +10 offset from editor) // But we need to check against the editor bounds which start 10px before const boundX = obj.x - 10; const boundY = obj.y; if ( x >= boundX && x <= boundX + width && y >= boundY && y <= boundY + height ) { return obj; } } return null; }, activateOnCanvasEditor: (canvasX, canvasY, textId) => { const state = get(); if (textId) { // Editing existing text const textObj = state.textObjects.find((obj) => obj.id === textId); if (textObj) { set({ isOnCanvasEditorActive: true, // Subtract the 10px padding offset to position editor correctly editorPosition: { x: textObj.x - 10, y: textObj.y }, editorText: textObj.text, editingTextId: textId, settings: { ...state.settings, fontFamily: textObj.fontFamily, fontSize: textObj.fontSize, fontStyle: textObj.fontStyle, fontWeight: textObj.fontWeight, color: textObj.color, align: textObj.align, baseline: textObj.baseline, lineHeight: textObj.lineHeight, letterSpacing: textObj.letterSpacing, }, }); } } else { // Creating new text set({ isOnCanvasEditorActive: true, editorPosition: { x: canvasX, y: canvasY }, editorText: '', editingTextId: null, }); } }, deactivateOnCanvasEditor: () => set({ isOnCanvasEditorActive: false, editorPosition: null, editorText: '', editingTextId: null, }), updateEditorPosition: (canvasX, canvasY) => set({ editorPosition: { x: canvasX, y: canvasY }, }), updateEditorText: (text) => set({ editorText: text, }), }), { name: 'text-storage', partialize: (state) => ({ settings: state.settings, }), // Only persist settings, not text objects (they're saved with project) } ) );