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);
const baseColorRef = useRef(color);
// Update base color only when not applying our own changes
useEffect(() => {
if (!isApplyingRef.current) {
baseColorRef.current = color;
// Reset sliders when color changes externally
setLightenAmount(0);
setDarkenAmount(0);
setSaturateAmount(0);
setDesaturateAmount(0);
setRotateAmount(0);
}
}, [color]);
// Debounced effect to apply manipulations
useEffect(() => {
const timer = setTimeout(async () => {
// Skip if all sliders are at neutral position
if (lightenAmount === 0 && darkenAmount === 0 && saturateAmount === 0 &&
desaturateAmount === 0 && rotateAmount === 0) {
return;
}
isApplyingRef.current = true;
let currentColor = baseColorRef.current;
try { try {
// Apply lighten
if (lightenAmount > 0) {
const result = await lightenMutation.mutateAsync({ const result = await lightenMutation.mutateAsync({
colors: [currentColor], colors: [color],
amount: lightenAmount, amount: lightenAmount,
}); });
if (result.colors[0]) { if (result.colors[0]) {
currentColor = result.colors[0].output; onColorChange(result.colors[0].output);
toast.success(`Lightened by ${(lightenAmount * 100).toFixed(0)}%`);
} }
} catch (error) {
toast.error('Failed to lighten color');
} }
};
// Apply darken const handleDarken = async () => {
if (darkenAmount > 0) { try {
const result = await darkenMutation.mutateAsync({ const result = await darkenMutation.mutateAsync({
colors: [currentColor], colors: [color],
amount: darkenAmount, amount: darkenAmount,
}); });
if (result.colors[0]) { if (result.colors[0]) {
currentColor = result.colors[0].output; onColorChange(result.colors[0].output);
toast.success(`Darkened by ${(darkenAmount * 100).toFixed(0)}%`);
} }
} catch (error) {
toast.error('Failed to darken color');
} }
};
// Apply saturate const handleSaturate = async () => {
if (saturateAmount > 0) { try {
const result = await saturateMutation.mutateAsync({ const result = await saturateMutation.mutateAsync({
colors: [currentColor], colors: [color],
amount: saturateAmount, amount: saturateAmount,
}); });
if (result.colors[0]) { if (result.colors[0]) {
currentColor = result.colors[0].output; onColorChange(result.colors[0].output);
toast.success(`Saturated by ${(saturateAmount * 100).toFixed(0)}%`);
} }
} catch (error) {
toast.error('Failed to saturate color');
} }
};
// Apply desaturate const handleDesaturate = async () => {
if (desaturateAmount > 0) { try {
const result = await desaturateMutation.mutateAsync({ const result = await desaturateMutation.mutateAsync({
colors: [currentColor], colors: [color],
amount: desaturateAmount, amount: desaturateAmount,
}); });
if (result.colors[0]) { if (result.colors[0]) {
currentColor = result.colors[0].output; onColorChange(result.colors[0].output);
toast.success(`Desaturated by ${(desaturateAmount * 100).toFixed(0)}%`);
} }
} catch (error) {
toast.error('Failed to desaturate color');
} }
};
// Apply rotate const handleRotate = async () => {
if (rotateAmount !== 0) { try {
const result = await rotateMutation.mutateAsync({ const result = await rotateMutation.mutateAsync({
colors: [currentColor], colors: [color],
amount: rotateAmount, amount: rotateAmount,
}); });
if (result.colors[0]) { if (result.colors[0]) {
currentColor = result.colors[0].output; onColorChange(result.colors[0].output);
toast.success(`Rotated hue by ${rotateAmount}°`);
} }
}
onColorChange(currentColor);
} catch (error) { } catch (error) {
// Silent error during manipulation toast.error('Failed to rotate hue');
} finally {
isApplyingRef.current = false;
} }
}, 300); };
return () => clearTimeout(timer);
}, [lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount]);
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 */}