import { useMemo, useRef, useState } from "react"; import { driverById } from "./catalog"; import { DriverIcon } from "./DriverIcon"; import { DEFAULT_CONN_GROUP, useWorkspaceStore, type SavedConnection, } from "./store"; function cx(...xs: Array) { return xs.filter(Boolean).join(" "); } type DropHint = { groupId: string; index: number } | null; export function ConnectionSidebar({ onSelect, onDelete, }: { onSelect: (id: string) => void; onDelete: (id: string) => void; }) { const connections = useWorkspaceStore((s) => s.connections); const groups = useWorkspaceStore((s) => s.connGroups); const expanded = useWorkspaceStore((s) => s.expandedConnGroups); const activeId = useWorkspaceStore((s) => s.activeConnectionId); const addConnGroup = useWorkspaceStore((s) => s.addConnGroup); const renameConnGroup = useWorkspaceStore((s) => s.renameConnGroup); const removeConnGroup = useWorkspaceStore((s) => s.removeConnGroup); const moveConnection = useWorkspaceStore((s) => s.moveConnection); const toggleConnGroup = useWorkspaceStore((s) => s.toggleConnGroup); const notify = useWorkspaceStore((s) => s.notify); const editConnection = useWorkspaceStore((s) => s.editConnection); const [dragId, setDragId] = useState(null); const [dropHint, setDropHint] = useState(null); const dragIdRef = useRef(null); const byGroup = useMemo(() => { const map = new Map(); for (const g of groups) map.set(g.id, []); for (const c of connections) { const gid = c.groupId || DEFAULT_CONN_GROUP; if (!map.has(gid)) map.set(gid, []); map.get(gid)!.push(c); } for (const list of map.values()) { list.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)); } return map; }, [connections, groups]); const onAddGroup = () => { const name = window.prompt("分组名称", "新分组"); if (name == null) return; const id = addConnGroup(name); notify({ kind: "info", title: "分组", message: `已创建「${name.trim() || "新分组"}」`, mode: "toast" }); void id; }; const onRenameGroup = (id: string, cur: string) => { if (id === DEFAULT_CONN_GROUP) return; const name = window.prompt("重命名分组", cur); if (name == null || !name.trim()) return; renameConnGroup(id, name); }; const onRemoveGroup = (id: string, name: string) => { if (id === DEFAULT_CONN_GROUP) return; if (!window.confirm(`删除分组「${name}」?连接将移回默认。`)) return; removeConnGroup(id); }; const setDrop = (groupId: string, index: number) => { const id = dragIdRef.current; if (!id) return; setDropHint({ groupId, index }); }; const commitDrop = () => { const id = dragIdRef.current; const hint = dropHint; dragIdRef.current = null; setDragId(null); setDropHint(null); if (!id || !hint) return; moveConnection(id, hint.groupId, hint.index); }; return (
连接
{connections.length === 0 && (
暂无连接
)} {groups.map((g) => { const list = byGroup.get(g.id) ?? []; const open = expanded.includes(g.id); return (
{ if (!dragIdRef.current) return; e.preventDefault(); setDrop(g.id, list.length); }} onDrop={(e) => { e.preventDefault(); commitDrop(); }} >
toggleConnGroup(g.id)}> {open ? "▾" : "▸"} {g.name} {list.length} {g.id !== DEFAULT_CONN_GROUP && ( e.stopPropagation()}> )}
{open && (
{list.length === 0 && (
{ e.preventDefault(); setDrop(g.id, 0); }} > 拖到此处
)} {list.map((c, i) => (
{dropHint?.groupId === g.id && dropHint.index === i &&
}
{ dragIdRef.current = c.id; setDragId(c.id); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", c.id); }} onDragEnd={() => { dragIdRef.current = null; setDragId(null); setDropHint(null); }} onDragOver={(e) => { if (!dragIdRef.current || dragIdRef.current === c.id) return; e.preventDefault(); e.stopPropagation(); const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const before = e.clientY < rect.top + rect.height / 2; setDrop(g.id, before ? i : i + 1); }} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); commitDrop(); }} >
{dropHint?.groupId === g.id && dropHint.index === list.length && i === list.length - 1 && (
)}
))}
)}
); })}
); }