({ pattern, setPattern, threshold, setThreshold, invert, setInvert, paletteId, asciiRamp, setAsciiRamp, groupByCategory })
| 17 | } |
| 18 | |
| 19 | export const AlgorithmControls: React.FC<AlgorithmControlsProps> = ({ pattern, setPattern, threshold, setThreshold, invert, setInvert, paletteId, asciiRamp, setAsciiRamp, groupByCategory }) => { |
| 20 | const { t } = useTranslation(); |
| 21 | const algo = algorithms.find((a) => a.id === pattern); |
| 22 | const supportsThreshold = !!algo?.supportsThreshold; |
| 23 | const isAscii = pattern === 25 || (algo?.name.toLowerCase().includes('ascii')); |
| 24 | |
| 25 | const renderOptions = () => { |
| 26 | if (!groupByCategory) { |
| 27 | return algorithms.map((a) => ( |
| 28 | <option key={a.id} value={a.id}> |
| 29 | {a.name} |
| 30 | </option> |
| 31 | )); |
| 32 | } |
| 33 | const groups = getAlgorithmsByCategory(); |
| 34 | const order: ["Error Diffusion", "Ordered", "Other"] = ["Error Diffusion", "Ordered", "Other"]; |
| 35 | return order.map((cat) => { |
| 36 | const list = groups[cat]; |
| 37 | if (!list) return null; |
| 38 | const labelKey = cat === 'Error Diffusion' ? 'tool.algorithmPanel.errorDiffusion' : cat === 'Ordered' ? 'tool.algorithmPanel.ordered' : 'tool.algorithmPanel.other'; |
| 39 | return ( |
| 40 | <optgroup key={cat} label={t(labelKey)}> |
| 41 | {list.map((a) => ( |
| 42 | <option key={a.id} value={a.id}> |
| 43 | {a.name} |
| 44 | </option> |
| 45 | ))} |
| 46 | </optgroup> |
| 47 | ); |
| 48 | }); |
| 49 | }; |
| 50 | |
| 51 | return ( |
| 52 | <div className="space-y-3"> |
| 53 | <div> |
| 54 | <label htmlFor="algo-select" className="sr-only"> |
| 55 | {t('tool.algorithmPanel.title')} |
| 56 | </label> |
| 57 | <select id="algo-select" className="clean-input" value={pattern} onChange={(e) => setPattern(Number(e.target.value))}> |
| 58 | {renderOptions()} |
| 59 | </select> |
| 60 | </div> |
| 61 | {supportsThreshold && ( |
| 62 | <div className="space-y-1"> |
| 63 | <div className="flex items-center justify-between"> |
| 64 | <span className="font-mono text-[10px] tracking-wide text-gray-400">{t('tool.algorithmPanel.luminanceThreshold')}</span> |
| 65 | <span className="text-[10px] text-gray-500">{threshold}</span> |
| 66 | </div> |
| 67 | <input type="range" min={0} max={255} value={threshold} className="clean-range" onChange={(e) => setThreshold(Number(e.target.value))} aria-label={t('tool.algorithmPanel.threshold')} /> |
| 68 | </div> |
| 69 | )} |
| 70 | <div className="grid grid-cols-1 gap-2"> |
| 71 | <button type="button" onClick={() => !paletteId && setInvert((v: boolean) => !v)} disabled={!!paletteId} className={`clean-btn justify-center text-[10px] ${invert ? "border-blue-600 text-blue-400" : ""} ${paletteId ? "cursor-not-allowed opacity-40" : ""}`}> |
| 72 | {t('tool.algorithmPanel.invert')} |
| 73 | </button> |
| 74 | </div> |
| 75 | {isAscii && setAsciiRamp && ( |
| 76 | <div className="space-y-1"> |
nothing calls this directly
no test coverage detected