revert: restore button-based manipulation controls

Revert to the original button-based approach for color manipulation.
The reactive slider implementation was causing infinite loops and page
reloads. Buttons provide stable, predictable behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-17 22:23:17 +01:00
parent d09ecd17d5
commit 94b1aff193

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState } from 'react';
import { Slider } from '@/components/ui/slider'; import { Slider } from '@/components/ui/slider';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
@@ -19,11 +19,11 @@ interface ManipulationPanelProps {
} }
export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) { export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) {
const [lightenAmount, setLightenAmount] = useState(0); const [lightenAmount, setLightenAmount] = useState(0.2);
const [darkenAmount, setDarkenAmount] = useState(0); const [darkenAmount, setDarkenAmount] = useState(0.2);
const [saturateAmount, setSaturateAmount] = useState(0); const [saturateAmount, setSaturateAmount] = useState(0.2);
const [desaturateAmount, setDesaturateAmount] = useState(0); const [desaturateAmount, setDesaturateAmount] = useState(0.2);
const [rotateAmount, setRotateAmount] = useState(0); const [rotateAmount, setRotateAmount] = useState(30);
const lightenMutation = useLighten(); const lightenMutation = useLighten();
const darkenMutation = useDarken(); const darkenMutation = useDarken();
@@ -32,101 +32,80 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
const rotateMutation = useRotate(); const rotateMutation = useRotate();
const complementMutation = useComplement(); const complementMutation = useComplement();
// Track if we're applying our own changes to prevent feedback loop const handleLighten = async () => {
const isApplyingRef = useRef(false); try {
const baseColorRef = useRef(color); const result = await lightenMutation.mutateAsync({
colors: [color],
// Update base color only when not applying our own changes amount: lightenAmount,
useEffect(() => { });
if (!isApplyingRef.current) { if (result.colors[0]) {
baseColorRef.current = color; onColorChange(result.colors[0].output);
// Reset sliders when color changes externally toast.success(`Lightened by ${(lightenAmount * 100).toFixed(0)}%`);
setLightenAmount(0); }
setDarkenAmount(0); } catch (error) {
setSaturateAmount(0); toast.error('Failed to lighten color');
setDesaturateAmount(0);
setRotateAmount(0);
} }
}, [color]); };
// Debounced effect to apply manipulations const handleDarken = async () => {
useEffect(() => { try {
const timer = setTimeout(async () => { const result = await darkenMutation.mutateAsync({
// Skip if all sliders are at neutral position colors: [color],
if (lightenAmount === 0 && darkenAmount === 0 && saturateAmount === 0 && amount: darkenAmount,
desaturateAmount === 0 && rotateAmount === 0) { });
return; if (result.colors[0]) {
onColorChange(result.colors[0].output);
toast.success(`Darkened by ${(darkenAmount * 100).toFixed(0)}%`);
} }
} catch (error) {
toast.error('Failed to darken color');
}
};
isApplyingRef.current = true; const handleSaturate = async () => {
let currentColor = baseColorRef.current; try {
const result = await saturateMutation.mutateAsync({
try { colors: [color],
// Apply lighten amount: saturateAmount,
if (lightenAmount > 0) { });
const result = await lightenMutation.mutateAsync({ if (result.colors[0]) {
colors: [currentColor], onColorChange(result.colors[0].output);
amount: lightenAmount, toast.success(`Saturated by ${(saturateAmount * 100).toFixed(0)}%`);
});
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); } catch (error) {
toast.error('Failed to saturate color');
}
};
return () => clearTimeout(timer); const handleDesaturate = async () => {
}, [lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount]); try {
const result = await desaturateMutation.mutateAsync({
colors: [color],
amount: desaturateAmount,
});
if (result.colors[0]) {
onColorChange(result.colors[0].output);
toast.success(`Desaturated by ${(desaturateAmount * 100).toFixed(0)}%`);
}
} catch (error) {
toast.error('Failed to desaturate color');
}
};
const handleRotate = async () => {
try {
const result = await rotateMutation.mutateAsync({
colors: [color],
amount: rotateAmount,
});
if (result.colors[0]) {
onColorChange(result.colors[0].output);
toast.success(`Rotated hue by ${rotateAmount}°`);
}
} catch (error) {
toast.error('Failed to rotate hue');
}
};
const handleComplement = async () => { const handleComplement = async () => {
try { try {
@@ -151,7 +130,7 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Lighten */} {/* Lighten */}
<div> <div className="space-y-3">
<Slider <Slider
label="Lighten" label="Lighten"
min={0} min={0}
@@ -162,10 +141,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%" suffix="%"
showValue showValue
/> />
<Button onClick={handleLighten} disabled={isLoading} className="w-full">
Apply Lighten
</Button>
</div> </div>
{/* Darken */} {/* Darken */}
<div> <div className="space-y-3">
<Slider <Slider
label="Darken" label="Darken"
min={0} min={0}
@@ -176,10 +158,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%" suffix="%"
showValue showValue
/> />
<Button onClick={handleDarken} disabled={isLoading} className="w-full">
Apply Darken
</Button>
</div> </div>
{/* Saturate */} {/* Saturate */}
<div> <div className="space-y-3">
<Slider <Slider
label="Saturate" label="Saturate"
min={0} min={0}
@@ -190,10 +175,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%" suffix="%"
showValue showValue
/> />
<Button onClick={handleSaturate} disabled={isLoading} className="w-full">
Apply Saturate
</Button>
</div> </div>
{/* Desaturate */} {/* Desaturate */}
<div> <div className="space-y-3">
<Slider <Slider
label="Desaturate" label="Desaturate"
min={0} min={0}
@@ -204,10 +192,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%" suffix="%"
showValue showValue
/> />
<Button onClick={handleDesaturate} disabled={isLoading} className="w-full">
Apply Desaturate
</Button>
</div> </div>
{/* Rotate Hue */} {/* Rotate Hue */}
<div> <div className="space-y-3">
<Slider <Slider
label="Rotate Hue" label="Rotate Hue"
min={-180} min={-180}
@@ -218,6 +209,9 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="°" suffix="°"
showValue showValue
/> />
<Button onClick={handleRotate} disabled={isLoading} className="w-full">
Apply Rotation
</Button>
</div> </div>
{/* Quick Actions */} {/* Quick Actions */}