'use client'; import { useState } from 'react'; import { useFilterStore } from '@/store/filter-store'; import { useLayerStore } from '@/store/layer-store'; import { useHistoryStore } from '@/store/history-store'; import { useLoadingStore } from '@/store/loading-store'; import { useFilterPreview } from '@/hooks/use-filter-preview'; import { FilterCommand } from '@/core/commands/filter-command'; import type { FilterType } from '@/types/filter'; import { toast } from '@/lib/toast-utils'; import { Wand2, Sun, SunMoon, Palette, Droplet, Sparkles, Slash, Paintbrush, Circle, Grid3x3, Eye, Check, X, } from 'lucide-react'; import { cn } from '@/lib/utils'; const FILTERS: Array<{ type: FilterType; label: string; icon: React.ComponentType<{ className?: string }>; hasParams: boolean; }> = [ { type: 'brightness', label: 'Brightness', icon: Sun, hasParams: true }, { type: 'contrast', label: 'Contrast', icon: SunMoon, hasParams: true }, { type: 'hue-saturation', label: 'Hue/Saturation', icon: Palette, hasParams: true, }, { type: 'blur', label: 'Blur', icon: Droplet, hasParams: true }, { type: 'sharpen', label: 'Sharpen', icon: Sparkles, hasParams: true }, { type: 'invert', label: 'Invert', icon: Slash, hasParams: false }, { type: 'grayscale', label: 'Grayscale', icon: Paintbrush, hasParams: false }, { type: 'sepia', label: 'Sepia', icon: Circle, hasParams: false }, { type: 'threshold', label: 'Threshold', icon: Grid3x3, hasParams: true }, { type: 'posterize', label: 'Posterize', icon: Grid3x3, hasParams: true }, ]; export function FilterPanel() { const { activeFilter, params, setActiveFilter, updateParams, resetParams, isPreviewMode, setPreviewMode, } = useFilterStore(); const { activeLayerId, layers } = useLayerStore(); const { executeCommand } = useHistoryStore(); const { setLoading } = useLoadingStore(); const [selectedFilter, setSelectedFilter] = useState(null); useFilterPreview(); const activeLayer = layers.find((l) => l.id === activeLayerId); const hasActiveLayer = !!activeLayer && !activeLayer.locked; const handleFilterSelect = async (filterType: FilterType) => { const filter = FILTERS.find((f) => f.type === filterType); if (!filter) return; if (filter.hasParams) { setSelectedFilter(filterType); setActiveFilter(filterType); resetParams(); } else { // Apply filter immediately for filters without parameters if (activeLayer) { setLoading(true, `Applying ${filter.label.toLowerCase()} filter...`); try { const command = await FilterCommand.applyToLayerAsync(activeLayer, filterType, {}); executeCommand(command); toast.success(`Applied ${filter.label.toLowerCase()} filter`); } catch (error) { console.error('Failed to apply filter:', error); toast.error('Failed to apply filter'); } finally { setLoading(false); } } } }; const handleApply = async () => { if (activeFilter && activeLayer) { const filter = FILTERS.find((f) => f.type === activeFilter); setLoading(true, `Applying ${filter?.label.toLowerCase() || 'filter'}...`); try { setPreviewMode(false); const command = await FilterCommand.applyToLayerAsync( activeLayer, activeFilter, params ); executeCommand(command); setActiveFilter(null); setSelectedFilter(null); toast.success(`Applied ${filter?.label.toLowerCase() || 'filter'}`); } catch (error) { console.error('Failed to apply filter:', error); toast.error('Failed to apply filter'); } finally { setLoading(false); } } }; const handleCancel = () => { setPreviewMode(false); setActiveFilter(null); setSelectedFilter(null); resetParams(); }; const handlePreviewToggle = () => { setPreviewMode(!isPreviewMode); }; return (
{/* Filter list */}
{FILTERS.map((filter) => ( ))}
{/* Filter parameters */} {selectedFilter && activeFilter && (

Parameters

{activeFilter === 'brightness' && (
updateParams({ brightness: Number(e.target.value) }) } className="w-full" />
{params.brightness ?? 0}
)} {activeFilter === 'contrast' && (
updateParams({ contrast: Number(e.target.value) }) } className="w-full" />
{params.contrast ?? 0}
)} {activeFilter === 'hue-saturation' && ( <>
updateParams({ hue: Number(e.target.value) }) } className="w-full" />
{params.hue ?? 0}°
updateParams({ saturation: Number(e.target.value) }) } className="w-full" />
{params.saturation ?? 0}%
updateParams({ lightness: Number(e.target.value) }) } className="w-full" />
{params.lightness ?? 0}%
)} {activeFilter === 'blur' && (
updateParams({ radius: Number(e.target.value) }) } className="w-full" />
{params.radius ?? 5}px
)} {activeFilter === 'sharpen' && (
updateParams({ amount: Number(e.target.value) }) } className="w-full" />
{params.amount ?? 50}%
)} {activeFilter === 'threshold' && (
updateParams({ threshold: Number(e.target.value) }) } className="w-full" />
{params.threshold ?? 128}
)} {activeFilter === 'posterize' && (
updateParams({ levels: Number(e.target.value) }) } className="w-full" />
{params.levels ?? 8}
)} {/* Preview toggle */} {/* Action buttons */}
)}
{!hasActiveLayer && (

Select an unlocked layer to apply filters

)}
); }