SPB Git

spb/akos

Public
1.9 KB · 28 lines tsx
1import { useState } from "react";2import { useSearchParams } from "react-router";3import type { Graph } from "../api";4import { useFetch } from "../hooks";5import { ErrorBox, Loading } from "../components/ui";6import { LayeredGraph } from "../components/Graph";78export default function GraphPage() {9  const [sp, setSp] = useSearchParams();10  const focus = sp.get("focus") ?? ""; const depth = Number(sp.get("depth") ?? 2);11  const [f, setF] = useState(focus);12  const { data, error, loading } = useFetch<Graph>(`graph?focus=${encodeURIComponent(focus)}&depth=${depth}`);13  const types = data ? Array.from(new Set(data.nodes.map((n) => n.type))) : [];14  return (15    <div>16      <h1>Knowledge Graph</h1><p className="hint">Relational entities and lineage edges. Columns are entity types; click a node to open it. Focus on one node (type:id) to trim the neighbourhood.</p>17      <form className="filters" onSubmit={(e) => { e.preventDefault(); setSp({ focus: f, depth: String(depth) }); }}>18        <input aria-label="Focus node" placeholder="focus e.g. api:<uuid> or dataset:<uuid>" value={f} onChange={(e) => setF(e.target.value)} list="nodes" />19        <datalist id="nodes">{data?.nodes.slice(0, 500).map((n) => <option key={n.id} value={n.id}>{n.label}</option>)}</datalist>20        <select aria-label="Depth" value={depth} onChange={(e) => setSp({ focus, depth: e.target.value })}>{[1, 2, 3, 4].map((d) => <option key={d} value={d}>depth {d}</option>)}</select>21        <button className="btn">Apply</button>{focus && <button type="button" className="btn" onClick={() => { setF(""); setSp({}); }}>Clear focus</button>}22      </form>23      {loading && <Loading />}<ErrorBox error={error} />24      {data && <><div className="hint" style={{ marginBottom: 8 }}>{data.nodes.length} nodes · {data.edges.length} edges · types: {types.join(", ") || "none"}</div><LayeredGraph graph={data} /></>}25    </div>26  );27}28
Files · main
Menu

Theme