import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' import { devtools } from 'zustand/middleware' interface EditorState { // Current state image: File | null processedImage: string | null mask: string | null background: string | null isProcessing: boolean zoom: number tool: 'select' | 'brush' | 'eraser' | 'wand' | null // History history: Array<{ processedImage: string | null mask: string | null background: string | null }> historyIndex: number // Settings brushSize: number brushHardness: number tolerance: number feather: number edgeRefinement: number // Actions setImage: (image: File | null) => void setProcessedImage: (image: string | null) => void setMask: (mask: string | null) => void setBackground: (background: string | null) => void setIsProcessing: (processing: boolean) => void setZoom: (zoom: number) => void setTool: (tool: EditorState['tool']) => void // History actions saveToHistory: () => void undo: () => void redo: () => void canUndo: boolean canRedo: boolean // Settings actions setBrushSize: (size: number) => void setBrushHardness: (hardness: number) => void setTolerance: (tolerance: number) => void setFeather: (feather: number) => void setEdgeRefinement: (refinement: number) => void // Utils reset: () => void exportImage: (format: 'png' | 'jpeg' | 'webp', quality?: number) => Promise } const initialState = { image: null, processedImage: null, mask: null, background: null, isProcessing: false, zoom: 100, tool: null, history: [], historyIndex: -1, brushSize: 20, brushHardness: 80, tolerance: 20, feather: 2, edgeRefinement: 50, } export const useEditorStore = create()( devtools( immer((set, get) => ({ ...initialState, setImage: (image) => set((state) => { state.image = image if (!image) { state.processedImage = null state.mask = null state.background = null state.history = [] state.historyIndex = -1 } }), setProcessedImage: (image) => set((state) => { state.processedImage = image }), setMask: (mask) => set((state) => { state.mask = mask }), setBackground: (background) => set((state) => { state.background = background get().saveToHistory() }), setIsProcessing: (processing) => set((state) => { state.isProcessing = processing }), setZoom: (zoom) => set((state) => { state.zoom = Math.max(10, Math.min(500, zoom)) }), setTool: (tool) => set((state) => { state.tool = tool }), saveToHistory: () => set((state) => { const { processedImage, mask, background } = state const historyEntry = { processedImage, mask, background } // Remove any history after current index state.history = state.history.slice(0, state.historyIndex + 1) // Add new entry state.history.push(historyEntry) state.historyIndex++ // Limit history to 50 entries if (state.history.length > 50) { state.history.shift() state.historyIndex-- } }), undo: () => set((state) => { if (state.historyIndex > 0) { state.historyIndex-- const entry = state.history[state.historyIndex] state.processedImage = entry.processedImage state.mask = entry.mask state.background = entry.background } }), redo: () => set((state) => { if (state.historyIndex < state.history.length - 1) { state.historyIndex++ const entry = state.history[state.historyIndex] state.processedImage = entry.processedImage state.mask = entry.mask state.background = entry.background } }), get canUndo() { return get().historyIndex > 0 }, get canRedo() { return get().historyIndex < get().history.length - 1 }, setBrushSize: (size) => set((state) => { state.brushSize = size }), setBrushHardness: (hardness) => set((state) => { state.brushHardness = hardness }), setTolerance: (tolerance) => set((state) => { state.tolerance = tolerance }), setFeather: (feather) => set((state) => { state.feather = feather }), setEdgeRefinement: (refinement) => set((state) => { state.edgeRefinement = refinement }), reset: () => set(() => initialState), exportImage: async (format, quality = 0.95) => { const { processedImage, background } = get() if (!processedImage) throw new Error('No image to export') // Create canvas and composite image with background const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) throw new Error('Failed to create canvas context') return new Promise((resolve, reject) => { const img = new Image() img.onload = () => { canvas.width = img.width canvas.height = img.height // Draw background if present if (background) { if (background.startsWith('#')) { ctx.fillStyle = background ctx.fillRect(0, 0, canvas.width, canvas.height) } else if (background.startsWith('linear-gradient')) { // Handle image backgrounds const bgImg = new Image() bgImg.onload = () => { ctx.drawImage(bgImg, 0, 0, canvas.width, canvas.height) ctx.drawImage(img, 0, 0) canvas.toBlob( (blob) => { if (blob) resolve(blob) else reject(new Error('Failed to export image')) }, `image/${format}`, quality ) } bgImg.src = background return } } // Draw the processed image ctx.drawImage(img, 0, 0) canvas.toBlob( (blob) => { if (blob) resolve(blob) else reject(new Error('Failed to export image')) }, `image/${format}`, quality ) } img.onerror = () => reject(new Error('Failed to load image')) img.src = processedImage }) }, })) ) ) gradient backgrounds const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height) gradient.addColorStop(0, '#667eea') gradient.addColorStop(1, '#764ba2') ctx.fillStyle = gradient ctx.fillRect(0, 0, canvas.width, canvas.height) } else { // Handle