import { useEffect, useState, useMemo } from "react"; import { ChevronRight, ChevronDown, FunctionSquare, GitBranch, ListOrdered, Tag, Puzzle, Search } from "lucide-react"; import { useDbViewerStore } from "../../stores/dbViewerStore"; import * as cmd from "../../lib/commands"; import type { FunctionInfo, TriggerInfo, SequenceInfo, EnumInfo, ExtensionInfo } from "../../lib/types"; type ObjectType = "functions" | "triggers" | "sequences" | "enums" | "extensions"; interface ObjectTreeProps { type: ObjectType; connectionId: string; } const TYPE_LABELS: Record = { functions: "functions", triggers: "triggers", sequences: "sequences", enums: "enums", extensions: "extensions", }; const ICONS: Record = { functions: , triggers: , sequences: , enums: , extensions: , }; function SourceCode({ source }: { source: string }) { const [expanded, setExpanded] = useState(false); const maxLen = 500; const truncated = source.length > maxLen && !expanded; const display = truncated ? source.slice(0, maxLen) : source; return (
        {display}
        {truncated && ...}
      
{source.length > maxLen && ( )}
); } export function ObjectTree({ type, connectionId }: ObjectTreeProps) { const currentSchema = useDbViewerStore((s) => s.currentSchema); const functions = useDbViewerStore((s) => s.functions); const triggers = useDbViewerStore((s) => s.triggers); const sequences = useDbViewerStore((s) => s.sequences); const enums = useDbViewerStore((s) => s.enums); const extensions = useDbViewerStore((s) => s.extensions); const setFunctions = useDbViewerStore((s) => s.setFunctions); const setTriggers = useDbViewerStore((s) => s.setTriggers); const setSequences = useDbViewerStore((s) => s.setSequences); const setEnums = useDbViewerStore((s) => s.setEnums); const setExtensions = useDbViewerStore((s) => s.setExtensions); const [loading, setLoading] = useState(false); const [expandedKeys, setExpandedKeys] = useState>(new Set()); const [search, setSearch] = useState(""); // Determine which store accessors to use const data = useMemo(() => { switch (type) { case "functions": return functions; case "triggers": return triggers; case "sequences": return sequences; case "enums": return enums; case "extensions": return extensions; } }, [type, functions, triggers, sequences, enums, extensions]); const setter = useMemo(() => { switch (type) { case "functions": return setFunctions; case "triggers": return setTriggers; case "sequences": return setSequences; case "enums": return setEnums; case "extensions": return setExtensions; } }, [type, setFunctions, setTriggers, setSequences, setEnums, setExtensions]); // Fetch on mount if not in store useEffect(() => { if (data !== null) return; let cancelled = false; setLoading(true); const fetchData = async () => { try { if (type === "extensions") { const result = await cmd.getExtensions(connectionId); if (!cancelled) (setExtensions as (v: ExtensionInfo[]) => void)(result); } else if (type === "functions") { const result = await cmd.getFunctions(connectionId, currentSchema ?? undefined); if (!cancelled) (setFunctions as (v: FunctionInfo[]) => void)(result); } else if (type === "triggers") { const result = await cmd.getTriggers(connectionId, currentSchema ?? undefined); if (!cancelled) (setTriggers as (v: TriggerInfo[]) => void)(result); } else if (type === "sequences") { const result = await cmd.getSequences(connectionId, currentSchema ?? undefined); if (!cancelled) (setSequences as (v: SequenceInfo[]) => void)(result); } else if (type === "enums") { const result = await cmd.getEnums(connectionId, currentSchema ?? undefined); if (!cancelled) (setEnums as (v: EnumInfo[]) => void)(result); } } catch { // Silently fail — store remains null, we show the empty state } finally { if (!cancelled) setLoading(false); } }; fetchData(); return () => { cancelled = true; }; }, [type, connectionId, currentSchema, data, setter, setFunctions, setTriggers, setSequences, setEnums, setExtensions]); const q = search.toLowerCase().trim(); const list = useMemo(() => { if (!data) return []; if (!q) return data; return data.filter((item) => { const name = "name" in item ? (item as { name: string }).name : ""; return name.toLowerCase().includes(q); }); }, [data, q]); const toggle = (key: string) => { setExpandedKeys((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); }; const icon = ICONS[type]; const pluralLabel = TYPE_LABELS[type]; return (
{/* Search bar */}
setSearch(e.target.value)} className="w-full pl-7 pr-2 py-1 text-xs bg-surface-raised border border-border rounded text-text placeholder:text-text-subtle focus:outline-none focus:border-accent/50" />
{/* Content */}
{loading && (
Loading {pluralLabel}...
)} {!loading && list.length === 0 && (
{data === null ? `No ${pluralLabel} found` : `No ${pluralLabel} found`}
)} {!loading && list.map((item) => { const name = "name" in item ? (item as { name: string }).name : ""; const key = name; const isExpanded = expandedKeys.has(key); return (
toggle(key)} > {icon} {name}
{isExpanded && (
{type === "functions" && (() => { const f = item as FunctionInfo; return ( <>
Returns: {f.return_type}
Language: {f.language}
{f.argument_names.length > 0 && (
Args:{" "} {f.argument_names.map((a, i) => ( {f.argument_modes?.[i] && f.argument_modes[i] !== "IN" && ( {f.argument_modes[i]} )} {a} ({f.argument_types?.[i] || "unknown"}) {i < f.argument_names.length - 1 && ", "} ))}
)} {f.source && } ); })()} {type === "triggers" && (() => { const t = item as TriggerInfo; return ( <>
Table: {t.table_schema}.{t.table_name}
Event: {t.event_manipulation}
Timing: {t.action_timing}
Orientation: {t.action_orientation}
Enabled: {t.enabled}
{t.action_statement && } ); })()} {type === "sequences" && (() => { const s = item as SequenceInfo; return ( <>
Current: {s.current_value}
Increment: {s.increment}
Min: {s.min_value}
Max: {s.max_value}
Start: {s.start_value}
Cycle: {s.cycle ? "Yes" : "No"}
); })()} {type === "enums" && (() => { const e = item as EnumInfo; return (
{e.labels.map((label) => ( {label} ))}
); })()} {type === "extensions" && (() => { const e = item as ExtensionInfo; return ( <>
Version: {e.version}
Schema: {e.schema}
{e.comment && (
Comment: {e.comment}
)} ); })()}
)}
); })}
); }