diff --git a/components/tools/ManipulationPanel.tsx b/components/tools/ManipulationPanel.tsx index a3fee22..8cee83e 100644 --- a/components/tools/ManipulationPanel.tsx +++ b/components/tools/ManipulationPanel.tsx @@ -19,8 +19,6 @@ interface ManipulationPanelProps { } export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) { - // Track base color and reset sliders when color changes externally - const [baseColor, setBaseColor] = useState(color); const [lightenAmount, setLightenAmount] = useState(0); const [darkenAmount, setDarkenAmount] = useState(0); const [saturateAmount, setSaturateAmount] = useState(0); @@ -34,100 +32,101 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro const rotateMutation = useRotate(); const complementMutation = useComplement(); - // Debounce timer - const debounceTimer = useRef(undefined); + // Track if we're applying our own changes to prevent feedback loop + const isApplyingRef = useRef(false); + const baseColorRef = useRef(color); - // Reset sliders when color changes from outside (e.g., color picker) + // Update base color only when not applying our own changes useEffect(() => { - setBaseColor(color); - setLightenAmount(0); - setDarkenAmount(0); - setSaturateAmount(0); - setDesaturateAmount(0); - setRotateAmount(0); - }, [color]); - - // Apply all manipulations to base color - const applyManipulations = useCallback(async () => { - let currentColor = baseColor; - - try { - // Apply lighten - if (lightenAmount > 0) { - const result = await lightenMutation.mutateAsync({ - colors: [currentColor], - amount: lightenAmount, - }); - if (result.colors[0]) { - currentColor = result.colors[0].output; - } - } - - // Apply darken - if (darkenAmount > 0) { - const result = await darkenMutation.mutateAsync({ - colors: [currentColor], - amount: darkenAmount, - }); - if (result.colors[0]) { - currentColor = result.colors[0].output; - } - } - - // Apply saturate - if (saturateAmount > 0) { - const result = await saturateMutation.mutateAsync({ - colors: [currentColor], - amount: saturateAmount, - }); - if (result.colors[0]) { - currentColor = result.colors[0].output; - } - } - - // Apply desaturate - if (desaturateAmount > 0) { - const result = await desaturateMutation.mutateAsync({ - colors: [currentColor], - amount: desaturateAmount, - }); - if (result.colors[0]) { - currentColor = result.colors[0].output; - } - } - - // Apply rotate - if (rotateAmount !== 0) { - const result = await rotateMutation.mutateAsync({ - colors: [currentColor], - amount: rotateAmount, - }); - if (result.colors[0]) { - currentColor = result.colors[0].output; - } - } - - // Only update if color changed - if (currentColor !== baseColor) { - onColorChange(currentColor); - } - } catch (error) { - // Silent error during manipulation + if (!isApplyingRef.current) { + baseColorRef.current = color; + // Reset sliders when color changes externally + setLightenAmount(0); + setDarkenAmount(0); + setSaturateAmount(0); + setDesaturateAmount(0); + setRotateAmount(0); } - }, [baseColor, lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount, lightenMutation, darkenMutation, saturateMutation, desaturateMutation, rotateMutation, onColorChange]); + }, [color]); // Debounced effect to apply manipulations useEffect(() => { - if (debounceTimer.current) clearTimeout(debounceTimer.current); + const timer = setTimeout(async () => { + // Skip if all sliders are at neutral position + if (lightenAmount === 0 && darkenAmount === 0 && saturateAmount === 0 && + desaturateAmount === 0 && rotateAmount === 0) { + return; + } - debounceTimer.current = setTimeout(() => { - applyManipulations(); + isApplyingRef.current = true; + let currentColor = baseColorRef.current; + + try { + // Apply lighten + if (lightenAmount > 0) { + const result = await lightenMutation.mutateAsync({ + colors: [currentColor], + amount: lightenAmount, + }); + if (result.colors[0]) { + currentColor = result.colors[0].output; + } + } + + // Apply darken + if (darkenAmount > 0) { + const result = await darkenMutation.mutateAsync({ + colors: [currentColor], + amount: darkenAmount, + }); + if (result.colors[0]) { + currentColor = result.colors[0].output; + } + } + + // Apply saturate + if (saturateAmount > 0) { + const result = await saturateMutation.mutateAsync({ + colors: [currentColor], + amount: saturateAmount, + }); + if (result.colors[0]) { + currentColor = result.colors[0].output; + } + } + + // Apply desaturate + if (desaturateAmount > 0) { + const result = await desaturateMutation.mutateAsync({ + colors: [currentColor], + amount: desaturateAmount, + }); + if (result.colors[0]) { + currentColor = result.colors[0].output; + } + } + + // Apply rotate + if (rotateAmount !== 0) { + const result = await rotateMutation.mutateAsync({ + colors: [currentColor], + amount: rotateAmount, + }); + if (result.colors[0]) { + currentColor = result.colors[0].output; + } + } + + onColorChange(currentColor); + } catch (error) { + // Silent error during manipulation + } finally { + isApplyingRef.current = false; + } }, 300); - return () => { - if (debounceTimer.current) clearTimeout(debounceTimer.current); - }; - }, [applyManipulations]); + return () => clearTimeout(timer); + }, [lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount]); const handleComplement = async () => { try {