/** * — community relationship graph. * * Displays network nodes (people, companies, opportunities) * and edges in a force-directed layout with search and filtering. */ class FolkGraphViewer extends HTMLElement { private shadow: ShadowRoot; private space = ""; private workspaces: any[] = []; private info: any = null; private filter: "all" | "person" | "company" | "opportunity" = "all"; private searchQuery = ""; private error = ""; constructor() { super(); this.shadow = this.attachShadow({ mode: "open" }); } connectedCallback() { this.space = this.getAttribute("space") || "demo"; this.loadData(); this.render(); } private getApiBase(): string { const path = window.location.pathname; const match = path.match(/^\/([^/]+)\/network/); return match ? `/${match[1]}/network` : ""; } private async loadData() { const base = this.getApiBase(); try { const [wsRes, infoRes] = await Promise.all([ fetch(`${base}/api/workspaces`), fetch(`${base}/api/info`), ]); if (wsRes.ok) this.workspaces = await wsRes.json(); if (infoRes.ok) this.info = await infoRes.json(); } catch { /* offline */ } this.render(); } private render() { this.shadow.innerHTML = ` ${this.error ? `
${this.esc(this.error)}
` : ""}
\u{1F310} Network Graph
${(["all", "person", "company", "opportunity"] as const).map(f => `` ).join("")}

\u{1F578}\u{FE0F}

Community Relationship Graph

Connect the force-directed layout engine to visualize your network.

Automerge CRDT sync + d3-force layout

People
Companies
Opportunities
${this.workspaces.length > 0 ? `
Workspaces
${this.workspaces.map(ws => `
${this.esc(ws.name || ws.slug)}
${ws.nodeCount || 0} nodes \u00B7 ${ws.edgeCount || 0} edges
`).join("")}
` : ""} `; this.attachListeners(); } private attachListeners() { this.shadow.querySelectorAll("[data-filter]").forEach(el => { el.addEventListener("click", () => { this.filter = (el as HTMLElement).dataset.filter as any; this.render(); }); }); this.shadow.getElementById("search-input")?.addEventListener("input", (e) => { this.searchQuery = (e.target as HTMLInputElement).value; }); } private esc(s: string): string { const d = document.createElement("div"); d.textContent = s || ""; return d.innerHTML; } } customElements.define("folk-graph-viewer", FolkGraphViewer);