Compare commits
2 Commits
5c035ac5ce
...
d0db0ffde7
| Author | SHA1 | Date |
|---|---|---|
|
|
d0db0ffde7 | |
|
|
123d61109e |
|
|
@ -195,12 +195,71 @@ class FolkGraphViewer extends HTMLElement {
|
|||
|
||||
private _delegationsHandler: ((e: Event) => void) | null = null;
|
||||
|
||||
private _onTabChange = (e: Event) => {
|
||||
const tab = (e as CustomEvent).detail?.tab;
|
||||
if (!tab) return;
|
||||
this.applyTab(tab);
|
||||
};
|
||||
|
||||
private applyTab(tab: string) {
|
||||
const wasTrust = this.trustMode;
|
||||
const wasLayers = this.layersMode;
|
||||
|
||||
switch (tab) {
|
||||
case "members":
|
||||
this.filter = "all";
|
||||
this.trustMode = false;
|
||||
if (wasLayers) this.exitLayersMode();
|
||||
break;
|
||||
case "people":
|
||||
this.filter = "person";
|
||||
this.trustMode = false;
|
||||
if (wasLayers) this.exitLayersMode();
|
||||
break;
|
||||
case "companies":
|
||||
this.filter = "company";
|
||||
this.trustMode = false;
|
||||
if (wasLayers) this.exitLayersMode();
|
||||
break;
|
||||
case "trust":
|
||||
this.filter = "all";
|
||||
this.trustMode = true;
|
||||
if (wasLayers) this.exitLayersMode();
|
||||
if (this.layoutMode !== "rings") {
|
||||
this.layoutMode = "rings";
|
||||
const ringsBtn = this.shadow.getElementById("rings-toggle");
|
||||
if (ringsBtn) ringsBtn.classList.add("active");
|
||||
}
|
||||
break;
|
||||
case "layers":
|
||||
this.filter = "all";
|
||||
this.trustMode = false;
|
||||
if (!wasLayers) this.enterLayersMode();
|
||||
break;
|
||||
}
|
||||
this.updateAuthorityBar();
|
||||
// Trust mode change needs full data reload
|
||||
if (this.trustMode !== wasTrust) {
|
||||
this.loadData();
|
||||
} else {
|
||||
this.updateGraphData();
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.space = this.getAttribute("space") || "demo";
|
||||
|
||||
// Read initial tab from attribute or URL
|
||||
const attrTab = this.getAttribute("active-tab");
|
||||
if (attrTab) this.applyTab(attrTab);
|
||||
|
||||
this.renderDOM();
|
||||
this.loadData();
|
||||
this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rnetwork', context: 'Network Graph' }));
|
||||
|
||||
// Listen for shell tab bar changes
|
||||
document.addEventListener("rapp-tab-change", this._onTabChange);
|
||||
|
||||
// Listen for cross-component delegation updates
|
||||
this._delegationsHandler = () => {
|
||||
this._textSpriteCache.clear();
|
||||
|
|
@ -219,6 +278,7 @@ class FolkGraphViewer extends HTMLElement {
|
|||
|
||||
disconnectedCallback() {
|
||||
this._stopPresence?.();
|
||||
document.removeEventListener("rapp-tab-change", this._onTabChange);
|
||||
if (this._keyHandler) {
|
||||
document.removeEventListener("keydown", this._keyHandler);
|
||||
this._keyHandler = null;
|
||||
|
|
@ -869,15 +929,8 @@ class FolkGraphViewer extends HTMLElement {
|
|||
|
||||
<div class="toolbar">
|
||||
<input class="search-input" type="text" placeholder="Search nodes..." id="search-input" value="">
|
||||
<button class="filter-btn active" data-filter="all">All</button>
|
||||
<button class="filter-btn" data-filter="person">People</button>
|
||||
<button class="filter-btn" data-filter="company">Organizations</button>
|
||||
<button class="filter-btn" data-filter="opportunity">Opportunities</button>
|
||||
<button class="filter-btn" data-filter="rspace_user">Members</button>
|
||||
<button class="filter-btn" id="trust-toggle" title="Toggle trust-weighted view">Trust</button>
|
||||
<button class="filter-btn" id="rings-toggle" title="Toggle concentric ring layout">Rings</button>
|
||||
<button class="filter-btn" id="list-toggle" title="Toggle member list sidebar">List</button>
|
||||
<button class="filter-btn" id="layers-toggle" title="Toggle multi-layer rApp visualization">Layers</button>
|
||||
</div>
|
||||
|
||||
<div class="authority-bar" id="authority-bar">
|
||||
|
|
@ -932,17 +985,6 @@ class FolkGraphViewer extends HTMLElement {
|
|||
}
|
||||
|
||||
private attachListeners() {
|
||||
// Filter buttons
|
||||
this.shadow.querySelectorAll("[data-filter]").forEach(el => {
|
||||
el.addEventListener("click", () => {
|
||||
this.filter = (el as HTMLElement).dataset.filter as any;
|
||||
// Update active state
|
||||
this.shadow.querySelectorAll("[data-filter]").forEach(b => b.classList.remove("active"));
|
||||
el.classList.add("active");
|
||||
this.updateGraphData();
|
||||
});
|
||||
});
|
||||
|
||||
// Search
|
||||
let searchTimeout: any;
|
||||
this.shadow.getElementById("search-input")?.addEventListener("input", (e) => {
|
||||
|
|
@ -951,21 +993,6 @@ class FolkGraphViewer extends HTMLElement {
|
|||
searchTimeout = setTimeout(() => this.updateGraphData(), 200);
|
||||
});
|
||||
|
||||
// Trust toggle
|
||||
this.shadow.getElementById("trust-toggle")?.addEventListener("click", () => {
|
||||
this.trustMode = !this.trustMode;
|
||||
const btn = this.shadow.getElementById("trust-toggle");
|
||||
if (btn) btn.classList.toggle("active", this.trustMode);
|
||||
// Auto-enable rings when trust mode is turned on
|
||||
if (this.trustMode && this.layoutMode !== "rings") {
|
||||
this.layoutMode = "rings";
|
||||
const ringsBtn = this.shadow.getElementById("rings-toggle");
|
||||
if (ringsBtn) ringsBtn.classList.add("active");
|
||||
}
|
||||
this.updateAuthorityBar();
|
||||
this.loadData();
|
||||
});
|
||||
|
||||
// Rings toggle
|
||||
this.shadow.getElementById("rings-toggle")?.addEventListener("click", () => {
|
||||
this.layoutMode = this.layoutMode === "rings" ? "force" : "rings";
|
||||
|
|
@ -1028,17 +1055,6 @@ class FolkGraphViewer extends HTMLElement {
|
|||
});
|
||||
});
|
||||
|
||||
// Layers toggle
|
||||
this.shadow.getElementById("layers-toggle")?.addEventListener("click", () => {
|
||||
if (this.layersMode) {
|
||||
this.exitLayersMode();
|
||||
} else {
|
||||
this.enterLayersMode();
|
||||
}
|
||||
const btn = this.shadow.getElementById("layers-toggle");
|
||||
if (btn) btn.classList.toggle("active", this.layersMode);
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
if (this._keyHandler) document.removeEventListener("keydown", this._keyHandler);
|
||||
this._keyHandler = (e: KeyboardEvent) => {
|
||||
|
|
@ -1067,10 +1083,6 @@ class FolkGraphViewer extends HTMLElement {
|
|||
case "F":
|
||||
if (this.graph) this.graph.zoomToFit(300, 20);
|
||||
break;
|
||||
case "t":
|
||||
case "T":
|
||||
this.shadow.getElementById("trust-toggle")?.click();
|
||||
break;
|
||||
case "l":
|
||||
case "L":
|
||||
this.shadow.getElementById("list-toggle")?.click();
|
||||
|
|
|
|||
|
|
@ -692,6 +692,17 @@ routes.get("/api/opportunities", async (c) => {
|
|||
return c.json({ opportunities });
|
||||
});
|
||||
|
||||
// ── Graph tabs (main view) ──
|
||||
const GRAPH_TABS = [
|
||||
{ id: "members", label: "Members" },
|
||||
{ id: "people", label: "People" },
|
||||
{ id: "companies", label: "Companies" },
|
||||
{ id: "trust", label: "Trust" },
|
||||
{ id: "layers", label: "Layers" },
|
||||
] as const;
|
||||
|
||||
const GRAPH_TAB_IDS = new Set(GRAPH_TABS.map(t => t.id));
|
||||
|
||||
// ── CRM sub-route — API-driven CRM view ──
|
||||
const CRM_TABS = [
|
||||
{ id: "pipeline", label: "Pipeline" },
|
||||
|
|
@ -735,6 +746,31 @@ routes.get("/crm/:tabId", (c) => {
|
|||
return c.html(renderCrm(space, tabId, c.get("isSubdomain")));
|
||||
});
|
||||
|
||||
// ── Graph sub-tab routes ──
|
||||
function renderGraph(space: string, activeTab: string, isSubdomain: boolean) {
|
||||
return renderShell({
|
||||
title: `${space} — Network | rSpace`,
|
||||
moduleId: "rnetwork",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
head: GRAPH3D_HEAD,
|
||||
body: `<folk-graph-viewer space="${space}" active-tab="${activeTab}"></folk-graph-viewer>`,
|
||||
scripts: `<script type="module" src="/modules/rnetwork/folk-graph-viewer.js?v=3"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rnetwork/network.css">`,
|
||||
tabs: [...GRAPH_TABS],
|
||||
activeTab,
|
||||
tabBasePath: isSubdomain ? `/rnetwork` : `/${space}/rnetwork`,
|
||||
});
|
||||
}
|
||||
|
||||
routes.get("/:tabId", (c, next) => {
|
||||
const tabId = c.req.param("tabId");
|
||||
// Only handle graph tab IDs here; let other routes (crm, api, etc.) pass through
|
||||
if (!GRAPH_TAB_IDS.has(tabId as any)) return next();
|
||||
const space = c.req.param("space") || "demo";
|
||||
return c.html(renderGraph(space, tabId, c.get("isSubdomain")));
|
||||
});
|
||||
|
||||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
|
|
@ -744,16 +780,7 @@ routes.get("/", (c) => {
|
|||
return c.redirect(c.get("isSubdomain") ? `/rnetwork/crm` : `/${space}/rnetwork/crm`, 301);
|
||||
}
|
||||
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Network | rSpace`,
|
||||
moduleId: "rnetwork",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
head: GRAPH3D_HEAD,
|
||||
body: `<folk-graph-viewer space="${space}"></folk-graph-viewer>`,
|
||||
scripts: `<script type="module" src="/modules/rnetwork/folk-graph-viewer.js?v=2"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rnetwork/network.css">`,
|
||||
}));
|
||||
return c.html(renderGraph(space, "members", c.get("isSubdomain")));
|
||||
});
|
||||
|
||||
// ── MI Data Export ──
|
||||
|
|
|
|||
Loading…
Reference in New Issue