/** * Left sidebar controls for the graph view (R3.5.2). * * Contains: space filter, edge-type checkboxes, depth slider, * include-orphans toggle, page search autocomplete, reset button, * and graph stats display. */ import { Stack, Text, Slider, Checkbox, Switch, Button, TextInput, Divider, Badge, Group, Alert, Select, Paper, } from "@mantine/core"; import { IconSearch, IconRefresh, IconAlertTriangle } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { useMemo } from "react"; import { GraphNode, GraphMeta } from "../services/graph-client"; import { GraphFilters, EdgeType, useGraphFilters, } from "../hooks/use-graph-controls"; const EDGE_TYPE_OPTIONS: Array<{ value: EdgeType; labelKey: string; color: string }> = [ { value: "wikilink", labelKey: "graph.edge_type_wikilink", color: "blue" }, { value: "mention", labelKey: "graph.edge_type_mention", color: "green" }, { value: "database_embed", labelKey: "graph.edge_type_database_embed", color: "orange", }, ]; interface GraphControlsProps { nodes: GraphNode[]; meta: GraphMeta | null; onSearchChange: (term: string) => void; searchTerm: string; } export function GraphControls({ nodes, meta, onSearchChange, searchTerm, }: GraphControlsProps) { const { t } = useTranslation(); const [filters, setFilters] = useGraphFilters(); const spaceOptions = useMemo(() => { const seen = new Map(); for (const n of nodes) { if (!seen.has(n.spaceId)) { seen.set(n.spaceId, n.spaceName ?? n.spaceId); } } return [ { value: "", label: t("graph.all_spaces") }, ...Array.from(seen.entries()).map(([id, name]) => ({ value: id, label: name, })), ]; }, [nodes, t]); function update(partial: Partial) { setFilters((prev) => ({ ...prev, ...partial })); } function resetFilters() { setFilters({ depth: 2, edgeTypes: ["wikilink", "mention", "database_embed"], includeOrphans: false, spaceId: null, searchTerm: "", }); onSearchChange(""); } function toggleEdgeType(type: EdgeType, checked: boolean) { if (checked) { update({ edgeTypes: [...filters.edgeTypes, type] }); } else { update({ edgeTypes: filters.edgeTypes.filter((t) => t !== type) }); } } return ( } placeholder={t("graph.search_placeholder")} value={searchTerm} onChange={(e) => onSearchChange(e.currentTarget.value)} size="sm" aria-label={t("graph.search_placeholder")} />