import { useEffect, useMemo, useState } from "react"; import { X, Trash2, Pencil, Check, Loader2 } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import type { PeqEqUi } from "../constants"; import { PEQ_PRESET_NAME_MAX_LENGTH } from "../constants"; import { clampPeqPresetName, isPeqPresetNameTooLong } from "../peqPresetName"; import { PeqPresetNameLengthHint } from "./PeqPresetNameLengthHint"; import { PeqOverwriteConfirmDialog } from "./PeqOverwriteConfirmDialog"; import { eqInterp } from "../utils"; /* ── Custom checkbox ── */ function Checkbox({ checked, onChange, className, }: { checked: boolean; onChange: () => void; className?: string; }) { return ( ); } type PeqItem = { name: string; filters?: any[] | string; autoPre?: number; preamp?: number; canDel?: number; brand?: string; model?: string; target?: string; form?: string; }; /** * Preset management dialog. * * Behavior: * - Delete (single / batch): calls onDeletePresets callback with preset names, * which should call info.cgi with { peqRemove: names } then sync. * - Rename: calls onRenamePreset(oldName, newName, item) callback which * should: 1) peqChange with new name + same EQ data 2) peqRemove old name. */ export function PeqPresetManageDialog({ open, items, selectedIdx, onClose, eqUi, onDeletePresets, onRenamePreset, }: { open: boolean; items: PeqItem[]; selectedIdx: number; onClose: () => void; eqUi: PeqEqUi; /** * Called with preset names to delete. Should call api.removePeq(names) then * fetchEqSyncPeq + applyPeqStateToUI. Return true on success, false on failure. */ onDeletePresets: (names: string[]) => Promise; /** * Called to rename a preset. Implementation should: * 1) upgradePeqChange({ peqChange: { name: newName, filters, ...rest } }) * 2) api.removePeq([oldName]) * 3) fetchEqSyncPeq + applyPeqStateToUI * Return true on success, false on failure. */ onRenamePreset: (oldName: string, newName: string, item: PeqItem) => Promise; }) { const [checked, setChecked] = useState>({}); const [editingIdx, setEditingIdx] = useState(null); const [editingName, setEditingName] = useState(""); const [deleting, setDeleting] = useState(false); const [renaming, setRenaming] = useState(false); const [deleteConfirmIdxs, setDeleteConfirmIdxs] = useState( null, ); const checkedIdxs = useMemo( () => Object.entries(checked) .filter(([, v]) => v) .map(([k]) => Number(k)), [checked] ); const allChecked = items.length > 0 && checkedIdxs.length === items.length; useEffect(() => { if (!open) setDeleteConfirmIdxs(null); }, [open]); if (!open) return null; const toggleAll = () => { if (allChecked) { setChecked({}); return; } const next: Record = {}; for (let i = 0; i < items.length; i++) next[i] = true; setChecked(next); }; const toggleOne = (idx: number) => { setChecked(prev => ({ ...prev, [idx]: !prev[idx] })); }; const beginRename = (idx: number) => { const name = items[idx]?.name ?? ""; setEditingIdx(idx); setEditingName(name); }; const commitRename = async () => { if (editingIdx === null) return; const nextName = editingName.trim(); if (!nextName) { toast.error(eqUi.renameEmptyName); return; } if (isPeqPresetNameTooLong(nextName)) { toast.error(eqUi.addPresetNameTooLong); return; } const oldItem = items[editingIdx]; if (!oldItem) return; if (nextName === oldItem.name) { setEditingIdx(null); setEditingName(""); return; } if (items.some((it, i) => i !== editingIdx && it.name === nextName)) { toast.error(eqUi.renameNameExists); return; } setRenaming(true); try { const ok = await onRenamePreset(oldItem.name, nextName, oldItem); if (ok) { setEditingIdx(null); setEditingName(""); } } finally { setRenaming(false); } }; const requestDelete = (idxs: number[]) => { const uniq = Array.from(new Set(idxs)).filter( i => i >= 0 && i < items.length ); if (uniq.length === 0) return; setDeleteConfirmIdxs(uniq); }; const executeDelete = async () => { const idxs = deleteConfirmIdxs; if (!idxs) return; setDeleteConfirmIdxs(null); const names = idxs.map(i => items[i].name); setDeleting(true); try { const ok = await onDeletePresets(names); if (ok) { setChecked({}); if (editingIdx !== null && idxs.includes(editingIdx)) { setEditingIdx(null); setEditingName(""); } } } finally { setDeleting(false); } }; const deleteConfirmDescription = deleteConfirmIdxs === null ? "" : deleteConfirmIdxs.length === 1 ? eqInterp(eqUi.deletePresetConfirm, { name: items[deleteConfirmIdxs[0]]?.name ?? "", }) : eqInterp(eqUi.batchDeleteConfirm, { count: deleteConfirmIdxs.length, }); return ( <>
e.stopPropagation()} >

{eqUi.managePresetTitle}

{items.length === 0 ? (
{eqUi.noPresets}
) : ( items.map((item, idx) => { const active = idx === selectedIdx; const isEditing = idx === editingIdx; return (
toggleOne(idx)} />
{isEditing ? (
setEditingName(clampPeqPresetName(e.target.value))} maxLength={PEQ_PRESET_NAME_MAX_LENGTH} className="h-9 min-w-0 flex-1 rounded-[10px] border border-white/12 bg-[#15171b] px-3 text-[14px] text-white/90 outline-none placeholder:text-white/30 focus:border-[#00FFF6]/40" />
) : (
{item.name}
)}
{!isEditing && (
)}
); }) )}
{ void executeDelete(); }} onCancel={() => setDeleteConfirmIdxs(null)} /> ); }