import { Download } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { ProgressCard } from "@/components/common/progress-card"; import { useTranslation } from "@/contexts/i18n-context"; import { useToolProcessor } from "@/hooks/use-tool-processor"; import { useFileStore } from "@/stores/file-store"; export function VignetteSettings({ onImageStyle, onImageOverlay, }: { onImageStyle?: (style: React.CSSProperties | null) => void; onImageOverlay?: (node: React.ReactNode) => void; }) { const { t } = useTranslation(); const { files } = useFileStore(); const { processFiles, processAllFiles, processing, error, downloadUrl, progress } = useToolProcessor("vignette"); const [strength, setStrength] = useState(0.5); const [color, setColor] = useState("#000000"); const [radius, setRadius] = useState(70); const [softness, setSoftness] = useState(50); const [roundness, setRoundness] = useState(100); const [centerX, setCenterX] = useState(50); const [centerY, setCenterY] = useState(50); // Stable refs for the preview callbacks to avoid re-render loops const onOverlayRef = useRef(onImageOverlay); useEffect(() => { onOverlayRef.current = onImageOverlay; }); const onStyleRef = useRef(onImageStyle); useEffect(() => { onStyleRef.current = onImageStyle; }); // Live preview overlay via onImageOverlay useEffect(() => { const outerR = radius / 100; const innerStop = Math.max(0, Math.min(1, outerR * (1 - softness / 100))); const innerPct = (innerStop * 100).toFixed(1); const shape = roundness === 100 ? "circle" : "ellipse"; const gradientColor = `${color}`; const bg = `radial-gradient(${shape} at ${centerX}% ${centerY}%, transparent ${innerPct}%, ${gradientColor} ${(outerR * 100).toFixed(1)}%)`; const overlay = (
); // onImageStyle activates the wrapper branch in image-viewer so the overlay // child actually mounts; without it the overlay node is never rendered. onStyleRef.current?.({}); onOverlayRef.current?.(overlay); return () => { onStyleRef.current?.(null); onOverlayRef.current?.(null); }; }, [strength, color, radius, softness, roundness, centerX, centerY]); const handleProcess = () => { const settings = { strength, color, radius, softness, roundness, centerX, centerY }; if (files.length > 1) { processAllFiles(files, settings); } else { processFiles(files, settings); } }; const hasFile = files.length > 0; const canProcess = hasFile && !processing; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (canProcess) handleProcess(); }; return (
{/* Strength */}
{strength.toFixed(2)}
setStrength(Number(e.target.value))} className="w-full mt-1" />
{/* Radius */}
{radius}%
setRadius(Number(e.target.value))} className="w-full mt-1" />
{/* Softness */}
{softness}%
setSoftness(Number(e.target.value))} className="w-full mt-1" />
{/* Roundness */}
{roundness}%
setRoundness(Number(e.target.value))} className="w-full mt-1" />
{/* Center X */}
{centerX}%
setCenterX(Number(e.target.value))} className="w-full mt-1" />
{/* Center Y */}
{centerY}%
setCenterY(Number(e.target.value))} className="w-full mt-1" />
{/* Vignette Color */}
setColor(e.target.value)} className="w-8 h-8 rounded border border-border shrink-0" /> setColor(e.target.value)} className="flex-1 px-2 py-1 rounded border border-border bg-background text-xs text-foreground font-mono" />
{error &&

{error}

} {processing ? ( ) : ( )} {downloadUrl && ( {t.common.download} )} ); }