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';
import { useState, useEffect, useRef, useCallback } from 'react';
import { useState } from 'react';
import { Slider } from '@/components/ui/slider';
import { Button } from '@/components/ui/button';
import {
@@ -19,11 +19,11 @@ interface ManipulationPanelProps {
}
export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) {
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 [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);
const lightenMutation = useLighten();
const darkenMutation = useDarken();
@@ -32,101 +32,80 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
const rotateMutation = useRotate();
const complementMutation = useComplement();
// Track if we're applying our own changes to prevent feedback loop
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);
const handleLighten = async () => {
try {
const result = await lightenMutation.mutateAsync({
colors: [color],
amount: lightenAmount,
});
if (result.colors[0]) {
onColorChange(result.colors[0].output);
toast.success(`Lightened by ${(lightenAmount * 100).toFixed(0)}%`);
}
} catch (error) {
toast.error('Failed to lighten color');
}
}, [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;
const handleDarken = async () => {
try {
const result = await darkenMutation.mutateAsync({
colors: [color],
amount: darkenAmount,
});
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;
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;
const handleSaturate = async () => {
try {
const result = await saturateMutation.mutateAsync({
colors: [color],
amount: saturateAmount,
});
if (result.colors[0]) {
onColorChange(result.colors[0].output);
toast.success(`Saturated by ${(saturateAmount * 100).toFixed(0)}%`);
}
}, 300);
} catch (error) {
toast.error('Failed to saturate color');
}
};
return () => clearTimeout(timer);
}, [lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount]);
const handleDesaturate = async () => {
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 () => {
try {
@@ -151,7 +130,7 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
return (
<div className="space-y-6">
{/* Lighten */}
<div>
<div className="space-y-3">
<Slider
label="Lighten"
min={0}
@@ -162,10 +141,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%"
showValue
/>
<Button onClick={handleLighten} disabled={isLoading} className="w-full">
Apply Lighten
</Button>
</div>
{/* Darken */}
<div>
<div className="space-y-3">
<Slider
label="Darken"
min={0}
@@ -176,10 +158,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%"
showValue
/>
<Button onClick={handleDarken} disabled={isLoading} className="w-full">
Apply Darken
</Button>
</div>
{/* Saturate */}
<div>
<div className="space-y-3">
<Slider
label="Saturate"
min={0}
@@ -190,10 +175,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%"
showValue
/>
<Button onClick={handleSaturate} disabled={isLoading} className="w-full">
Apply Saturate
</Button>
</div>
{/* Desaturate */}
<div>
<div className="space-y-3">
<Slider
label="Desaturate"
min={0}
@@ -204,10 +192,13 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="%"
showValue
/>
<Button onClick={handleDesaturate} disabled={isLoading} className="w-full">
Apply Desaturate
</Button>
</div>
{/* Rotate Hue */}
<div>
<div className="space-y-3">
<Slider
label="Rotate Hue"
min={-180}
@@ -218,6 +209,9 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
suffix="°"
showValue
/>
<Button onClick={handleRotate} disabled={isLoading} className="w-full">
Apply Rotation
</Button>
</div>
{/* Quick Actions */}