fix: prevent infinite loop in reactive sliders by tracking base color
Refactor manipulation panel to track a base color and apply all manipulations sequentially from that base, rather than continuously stacking manipulations. Sliders now reset to 0 when color changes externally (e.g., from color picker). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -19,11 +19,13 @@ interface ManipulationPanelProps {
|
||||
}
|
||||
|
||||
export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) {
|
||||
const [lightenAmount, setLightenAmount] = useState(0.2);
|
||||
const [darkenAmount, setDarkenAmount] = useState(0.2);
|
||||
const [saturateAmount, setSaturateAmount] = useState(0.2);
|
||||
const [desaturateAmount, setDesaturateAmount] = useState(0.2);
|
||||
const [rotateAmount, setRotateAmount] = useState(30);
|
||||
// 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);
|
||||
const [desaturateAmount, setDesaturateAmount] = useState(0);
|
||||
const [rotateAmount, setRotateAmount] = useState(0);
|
||||
|
||||
const lightenMutation = useLighten();
|
||||
const darkenMutation = useDarken();
|
||||
@@ -32,112 +34,100 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
|
||||
const rotateMutation = useRotate();
|
||||
const complementMutation = useComplement();
|
||||
|
||||
// Debounce timers
|
||||
const lightenTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const darkenTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const saturateTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const desaturateTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const rotateTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
// Debounce timer
|
||||
const debounceTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
|
||||
// Reactive lighten
|
||||
// Reset sliders when color changes from outside (e.g., color picker)
|
||||
useEffect(() => {
|
||||
if (lightenTimer.current) clearTimeout(lightenTimer.current);
|
||||
lightenTimer.current = setTimeout(async () => {
|
||||
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) {
|
||||
try {
|
||||
const result = await lightenMutation.mutateAsync({
|
||||
colors: [color],
|
||||
amount: lightenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
onColorChange(result.colors[0].output);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent error - user is still adjusting
|
||||
const result = await lightenMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: lightenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}, [lightenAmount]);
|
||||
|
||||
// Reactive darken
|
||||
useEffect(() => {
|
||||
if (darkenTimer.current) clearTimeout(darkenTimer.current);
|
||||
darkenTimer.current = setTimeout(async () => {
|
||||
// Apply darken
|
||||
if (darkenAmount > 0) {
|
||||
try {
|
||||
const result = await darkenMutation.mutateAsync({
|
||||
colors: [color],
|
||||
amount: darkenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
onColorChange(result.colors[0].output);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent error - user is still adjusting
|
||||
const result = await darkenMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: darkenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}, [darkenAmount]);
|
||||
|
||||
// Reactive saturate
|
||||
useEffect(() => {
|
||||
if (saturateTimer.current) clearTimeout(saturateTimer.current);
|
||||
saturateTimer.current = setTimeout(async () => {
|
||||
// Apply saturate
|
||||
if (saturateAmount > 0) {
|
||||
try {
|
||||
const result = await saturateMutation.mutateAsync({
|
||||
colors: [color],
|
||||
amount: saturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
onColorChange(result.colors[0].output);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent error - user is still adjusting
|
||||
const result = await saturateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: saturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}, [saturateAmount]);
|
||||
|
||||
// Reactive desaturate
|
||||
useEffect(() => {
|
||||
if (desaturateTimer.current) clearTimeout(desaturateTimer.current);
|
||||
desaturateTimer.current = setTimeout(async () => {
|
||||
// Apply desaturate
|
||||
if (desaturateAmount > 0) {
|
||||
try {
|
||||
const result = await desaturateMutation.mutateAsync({
|
||||
colors: [color],
|
||||
amount: desaturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
onColorChange(result.colors[0].output);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent error - user is still adjusting
|
||||
const result = await desaturateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: desaturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}, [desaturateAmount]);
|
||||
|
||||
// Reactive rotate
|
||||
useEffect(() => {
|
||||
if (rotateTimer.current) clearTimeout(rotateTimer.current);
|
||||
rotateTimer.current = setTimeout(async () => {
|
||||
// Apply rotate
|
||||
if (rotateAmount !== 0) {
|
||||
try {
|
||||
const result = await rotateMutation.mutateAsync({
|
||||
colors: [color],
|
||||
amount: rotateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
onColorChange(result.colors[0].output);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent error - user is still adjusting
|
||||
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
|
||||
}
|
||||
}, [baseColor, lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount, lightenMutation, darkenMutation, saturateMutation, desaturateMutation, rotateMutation, onColorChange]);
|
||||
|
||||
// Debounced effect to apply manipulations
|
||||
useEffect(() => {
|
||||
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
||||
|
||||
debounceTimer.current = setTimeout(() => {
|
||||
applyManipulations();
|
||||
}, 300);
|
||||
}, [rotateAmount]);
|
||||
|
||||
return () => {
|
||||
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
||||
};
|
||||
}, [applyManipulations]);
|
||||
|
||||
const handleComplement = async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user