@kage-core/kage-graph-mcp 1.1.34 → 1.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/package.json +1 -1
- package/viewer/app.js +527 -30
- package/viewer/index.html +218 -103
- package/viewer/styles.css +519 -55
package/viewer/app.js
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
visibleEntityIds: new Set(),
|
|
14
14
|
visibleEdgeIds: new Set(),
|
|
15
15
|
selected: null,
|
|
16
|
+
viewerSection: "overview",
|
|
17
|
+
viewerAction: null,
|
|
18
|
+
workspaceTab: "controls",
|
|
19
|
+
workspaceOpen: false,
|
|
20
|
+
pathHighlight: {
|
|
21
|
+
nodes: new Set(),
|
|
22
|
+
edges: new Set(),
|
|
23
|
+
direction: "",
|
|
24
|
+
steps: []
|
|
25
|
+
},
|
|
16
26
|
metrics: null,
|
|
17
27
|
inbox: null,
|
|
18
28
|
reports: {
|
|
@@ -119,6 +129,13 @@
|
|
|
119
129
|
graphSubhead: document.getElementById("graphSubhead"),
|
|
120
130
|
selectionStatus: document.getElementById("selectionStatus"),
|
|
121
131
|
searchInput: document.getElementById("searchInput"),
|
|
132
|
+
pathFromInput: document.getElementById("pathFromInput"),
|
|
133
|
+
pathToInput: document.getElementById("pathToInput"),
|
|
134
|
+
pathNodeOptions: document.getElementById("pathNodeOptions"),
|
|
135
|
+
findPath: document.getElementById("findPath"),
|
|
136
|
+
clearPath: document.getElementById("clearPath"),
|
|
137
|
+
pathStatus: document.getElementById("pathStatus"),
|
|
138
|
+
pathResult: document.getElementById("pathResult"),
|
|
122
139
|
viewMode: document.getElementById("viewMode"),
|
|
123
140
|
renderMode: document.getElementById("renderMode"),
|
|
124
141
|
typeFilter: document.getElementById("typeFilter"),
|
|
@@ -146,11 +163,21 @@
|
|
|
146
163
|
entityCount: document.getElementById("entityCount"),
|
|
147
164
|
edgeCount: document.getElementById("edgeCount"),
|
|
148
165
|
reviewCount: document.getElementById("reviewCount"),
|
|
166
|
+
dashboardStats: document.getElementById("dashboardStats"),
|
|
149
167
|
reviewList: document.getElementById("reviewList"),
|
|
150
168
|
proofStatus: document.getElementById("proofStatus"),
|
|
151
169
|
proofList: document.getElementById("proofList"),
|
|
152
170
|
intelligenceStatus: document.getElementById("intelligenceStatus"),
|
|
153
|
-
intelligenceList: document.getElementById("intelligenceList")
|
|
171
|
+
intelligenceList: document.getElementById("intelligenceList"),
|
|
172
|
+
viewerSectionButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-viewer-section]")) : [],
|
|
173
|
+
dashboardActionButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-dashboard-action]")) : [],
|
|
174
|
+
workspaceTabs: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-workspace-tab]")) : [],
|
|
175
|
+
quickViewButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-quick-view]")) : [],
|
|
176
|
+
quickRenderButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-quick-render]")) : [],
|
|
177
|
+
quickSearch: document.getElementById("quickSearch"),
|
|
178
|
+
quickPath: document.getElementById("quickPath"),
|
|
179
|
+
quickInspector: document.getElementById("quickInspector"),
|
|
180
|
+
closeWorkspace: document.getElementById("closeWorkspace")
|
|
154
181
|
};
|
|
155
182
|
|
|
156
183
|
var MEMORY_CODE_RELATIONS = new Set(["explains_symbol", "informs_symbol", "fixes_symbol", "applies_to_route", "verified_by_test", "affects_code_path"]);
|
|
@@ -161,8 +188,69 @@
|
|
|
161
188
|
var VISIBLE_EDGE_MIN = 160;
|
|
162
189
|
var VISIBLE_EDGE_MAX = 560;
|
|
163
190
|
|
|
191
|
+
setViewerSection(state.viewerSection);
|
|
192
|
+
setWorkspaceTab(state.workspaceTab, false);
|
|
193
|
+
els.viewerSectionButtons.forEach(function (button) {
|
|
194
|
+
button.addEventListener("click", function () {
|
|
195
|
+
setViewerSection(button.getAttribute("data-viewer-section"));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
els.dashboardActionButtons.forEach(function (button) {
|
|
199
|
+
button.addEventListener("click", function () {
|
|
200
|
+
openDashboardAction(button.getAttribute("data-dashboard-action"));
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
els.workspaceTabs.forEach(function (button) {
|
|
204
|
+
button.addEventListener("click", function () {
|
|
205
|
+
setWorkspaceTab(button.getAttribute("data-workspace-tab"), true);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
els.quickViewButtons.forEach(function (button) {
|
|
209
|
+
button.addEventListener("click", function () {
|
|
210
|
+
setViewerSection("graph");
|
|
211
|
+
els.viewMode.value = button.getAttribute("data-quick-view") || "combined";
|
|
212
|
+
state.lastVisibleSignature = "";
|
|
213
|
+
syncQuickControls();
|
|
214
|
+
render();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
els.quickRenderButtons.forEach(function (button) {
|
|
218
|
+
button.addEventListener("click", function () {
|
|
219
|
+
setViewerSection("graph");
|
|
220
|
+
els.renderMode.value = button.getAttribute("data-quick-render") || "2d";
|
|
221
|
+
state.lastVisibleSignature = "";
|
|
222
|
+
syncQuickControls();
|
|
223
|
+
render();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
if (els.quickSearch) {
|
|
227
|
+
els.quickSearch.addEventListener("click", function () {
|
|
228
|
+
setViewerSection("graph");
|
|
229
|
+
setWorkspaceTab("controls", true);
|
|
230
|
+
els.searchInput.focus();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (els.quickPath) {
|
|
234
|
+
els.quickPath.addEventListener("click", function () {
|
|
235
|
+
setViewerSection("graph");
|
|
236
|
+
setWorkspaceTab("controls", true);
|
|
237
|
+
els.pathFromInput.focus();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (els.quickInspector) {
|
|
241
|
+
els.quickInspector.addEventListener("click", function () {
|
|
242
|
+
setViewerSection("graph");
|
|
243
|
+
setWorkspaceTab("inspector", true);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (els.closeWorkspace) els.closeWorkspace.addEventListener("click", closeWorkspace);
|
|
247
|
+
syncQuickControls();
|
|
164
248
|
els.graphFile.addEventListener("change", handleFile);
|
|
165
249
|
els.searchInput.addEventListener("input", scheduleRender);
|
|
250
|
+
els.findPath.addEventListener("click", findDependencyPath);
|
|
251
|
+
els.clearPath.addEventListener("click", clearDependencyPath);
|
|
252
|
+
els.pathFromInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
253
|
+
els.pathToInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
166
254
|
els.viewMode.addEventListener("change", render);
|
|
167
255
|
els.renderMode.addEventListener("change", function () {
|
|
168
256
|
state.lastVisibleSignature = "";
|
|
@@ -206,11 +294,110 @@
|
|
|
206
294
|
els.maxNodes.value = "90";
|
|
207
295
|
els.showDependencies.checked = false;
|
|
208
296
|
state.selected = null;
|
|
297
|
+
setWorkspaceTab("controls", true);
|
|
298
|
+
clearDependencyPath(false);
|
|
209
299
|
state.lastVisibleSignature = "";
|
|
210
300
|
render();
|
|
211
301
|
});
|
|
212
302
|
loadFromUrlParams();
|
|
213
303
|
|
|
304
|
+
function setWorkspaceTab(tab, open) {
|
|
305
|
+
var allowed = new Set(["controls", "inspector", "intelligence", "review", "tables"]);
|
|
306
|
+
state.workspaceTab = allowed.has(tab) ? tab : "controls";
|
|
307
|
+
if (open !== false) state.workspaceOpen = true;
|
|
308
|
+
if (document.body && document.body.classList) {
|
|
309
|
+
document.body.classList.remove(
|
|
310
|
+
"viewer-tab-controls",
|
|
311
|
+
"viewer-tab-inspector",
|
|
312
|
+
"viewer-tab-intelligence",
|
|
313
|
+
"viewer-tab-review",
|
|
314
|
+
"viewer-tab-tables"
|
|
315
|
+
);
|
|
316
|
+
document.body.classList.add("viewer-tab-" + state.workspaceTab);
|
|
317
|
+
document.body.classList.toggle("viewer-workspace-open", state.workspaceOpen);
|
|
318
|
+
}
|
|
319
|
+
els.workspaceTabs.forEach(function (button) {
|
|
320
|
+
var active = button.getAttribute("data-workspace-tab") === state.workspaceTab;
|
|
321
|
+
button.classList.toggle("active", active);
|
|
322
|
+
button.setAttribute("aria-selected", active ? "true" : "false");
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function setViewerSection(section, action) {
|
|
327
|
+
state.viewerSection = section === "graph" ? "graph" : "overview";
|
|
328
|
+
state.viewerAction = action || null;
|
|
329
|
+
if (state.viewerSection === "overview") closeWorkspace();
|
|
330
|
+
if (document.body && document.body.classList) {
|
|
331
|
+
document.body.classList.remove("viewer-section-overview", "viewer-section-graph");
|
|
332
|
+
document.body.classList.add("viewer-section-" + state.viewerSection);
|
|
333
|
+
}
|
|
334
|
+
syncSectionControls();
|
|
335
|
+
if (state.viewerSection === "graph") resizeActiveGraph();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function openDashboardAction(action) {
|
|
339
|
+
var normalized = String(action || "").toLowerCase();
|
|
340
|
+
var tabByAction = {
|
|
341
|
+
memory: "review",
|
|
342
|
+
intelligence: "intelligence",
|
|
343
|
+
review: "review",
|
|
344
|
+
data: "tables"
|
|
345
|
+
};
|
|
346
|
+
setViewerSection("graph", normalized);
|
|
347
|
+
setWorkspaceTab(tabByAction[normalized] || "controls", true);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function syncSectionControls() {
|
|
351
|
+
els.viewerSectionButtons.forEach(function (button) {
|
|
352
|
+
var section = button.getAttribute("data-viewer-section");
|
|
353
|
+
var active = state.viewerSection === section && !state.viewerAction;
|
|
354
|
+
if (button.classList && button.classList.contains("viewer-section")) {
|
|
355
|
+
button.classList.toggle("active", active);
|
|
356
|
+
button.setAttribute("aria-selected", active ? "true" : "false");
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
els.dashboardActionButtons.forEach(function (button) {
|
|
360
|
+
var action = button.getAttribute("data-dashboard-action");
|
|
361
|
+
var active = state.viewerSection === "graph" && state.viewerAction === action;
|
|
362
|
+
if (button.classList && button.classList.contains("viewer-section")) {
|
|
363
|
+
button.classList.toggle("active", active);
|
|
364
|
+
button.setAttribute("aria-selected", active ? "true" : "false");
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function closeWorkspace() {
|
|
370
|
+
state.workspaceOpen = false;
|
|
371
|
+
if (document.body && document.body.classList) {
|
|
372
|
+
document.body.classList.remove("viewer-workspace-open");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function selectEntity(id, openInspector) {
|
|
377
|
+
state.selected = { kind: "entity", id: id };
|
|
378
|
+
setViewerSection("graph");
|
|
379
|
+
if (openInspector) setWorkspaceTab("inspector", true);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function selectEdge(id, openInspector) {
|
|
383
|
+
state.selected = { kind: "edge", id: id };
|
|
384
|
+
setViewerSection("graph");
|
|
385
|
+
if (openInspector) setWorkspaceTab("inspector", true);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function syncQuickControls() {
|
|
389
|
+
els.quickViewButtons.forEach(function (button) {
|
|
390
|
+
var active = button.getAttribute("data-quick-view") === els.viewMode.value;
|
|
391
|
+
button.classList.toggle("active", active);
|
|
392
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
393
|
+
});
|
|
394
|
+
els.quickRenderButtons.forEach(function (button) {
|
|
395
|
+
var active = button.getAttribute("data-quick-render") === els.renderMode.value;
|
|
396
|
+
button.classList.toggle("active", active);
|
|
397
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
214
401
|
function handleFile(event) {
|
|
215
402
|
var files = Array.from(event.target.files || []);
|
|
216
403
|
if (!files.length) return;
|
|
@@ -258,9 +445,12 @@
|
|
|
258
445
|
return [episode.id, episode];
|
|
259
446
|
}));
|
|
260
447
|
state.selected = null;
|
|
448
|
+
closeWorkspace();
|
|
449
|
+
clearDependencyPath(false);
|
|
261
450
|
state.lastVisibleSignature = "";
|
|
262
451
|
|
|
263
452
|
populateFilters();
|
|
453
|
+
populatePathOptions();
|
|
264
454
|
els.emptyState.classList.add("hidden");
|
|
265
455
|
els.graphSummary.textContent = fileName + " loaded: " + entities.length + " nodes, " + edges.length + " relations.";
|
|
266
456
|
render();
|
|
@@ -277,6 +467,8 @@
|
|
|
277
467
|
|
|
278
468
|
function loadFromUrlParams() {
|
|
279
469
|
var params = new URLSearchParams(window.location.search);
|
|
470
|
+
var requestedSection = String(params.get("section") || "").toLowerCase();
|
|
471
|
+
if (requestedSection === "graph" || requestedSection === "overview") setViewerSection(requestedSection);
|
|
280
472
|
applyRequestedView(params.get("view") || params.get("mode"));
|
|
281
473
|
applyRequestedRenderMode(params.get("render") || params.get("graphMode"));
|
|
282
474
|
var memoryGraphPaths = splitParamValues(params.getAll("graph"));
|
|
@@ -836,6 +1028,188 @@
|
|
|
836
1028
|
});
|
|
837
1029
|
}
|
|
838
1030
|
|
|
1031
|
+
function populatePathOptions() {
|
|
1032
|
+
if (!els.pathNodeOptions) return;
|
|
1033
|
+
var seen = new Set();
|
|
1034
|
+
els.pathNodeOptions.textContent = "";
|
|
1035
|
+
pathCandidateEntities().slice(0, 500).forEach(function (entity) {
|
|
1036
|
+
[entity.path, displayName(entity), entity.id].filter(Boolean).forEach(function (value) {
|
|
1037
|
+
var text = String(value);
|
|
1038
|
+
if (seen.has(text)) return;
|
|
1039
|
+
seen.add(text);
|
|
1040
|
+
var option = document.createElement("option");
|
|
1041
|
+
option.value = text;
|
|
1042
|
+
els.pathNodeOptions.appendChild(option);
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function pathCandidateEntities() {
|
|
1048
|
+
return state.entities
|
|
1049
|
+
.filter(function (entity) {
|
|
1050
|
+
return entity.graph_kind === "code" && ["file", "symbol", "route", "test", "script"].indexOf(entity.type) !== -1;
|
|
1051
|
+
})
|
|
1052
|
+
.sort(function (a, b) {
|
|
1053
|
+
return entityImportance(b) - entityImportance(a) || displayName(a).localeCompare(displayName(b));
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function resolvePathEntity(value) {
|
|
1058
|
+
var query = String(value || "").trim();
|
|
1059
|
+
if (!query) return null;
|
|
1060
|
+
var lower = query.toLowerCase();
|
|
1061
|
+
var candidates = pathCandidateEntities();
|
|
1062
|
+
var exact = candidates.filter(function (entity) {
|
|
1063
|
+
return String(entity.id || "").toLowerCase() === lower ||
|
|
1064
|
+
String(entity.path || "").toLowerCase() === lower ||
|
|
1065
|
+
displayName(entity).toLowerCase() === lower;
|
|
1066
|
+
});
|
|
1067
|
+
if (exact.length) return exact[0];
|
|
1068
|
+
var partial = candidates.filter(function (entity) {
|
|
1069
|
+
return String(entity.path || "").toLowerCase().indexOf(lower) !== -1 ||
|
|
1070
|
+
displayName(entity).toLowerCase().indexOf(lower) !== -1 ||
|
|
1071
|
+
String(entity.id || "").toLowerCase().indexOf(lower) !== -1;
|
|
1072
|
+
});
|
|
1073
|
+
return partial[0] || null;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function codePathEdges() {
|
|
1077
|
+
var relations = new Set(["imports", "imports_external", "defines_symbol", "calls", "covers", "defines_route", "handled_by"]);
|
|
1078
|
+
return state.edges.filter(function (edge) {
|
|
1079
|
+
if (!relations.has(edge.relation)) return false;
|
|
1080
|
+
var from = state.entityById.get(edge.from);
|
|
1081
|
+
var to = state.entityById.get(edge.to);
|
|
1082
|
+
return from && to && from.graph_kind === "code" && to.graph_kind === "code";
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function shortestCodePath(fromId, toId, undirected) {
|
|
1087
|
+
var adjacency = new Map();
|
|
1088
|
+
codePathEdges().forEach(function (edge) {
|
|
1089
|
+
if (!adjacency.has(edge.from)) adjacency.set(edge.from, []);
|
|
1090
|
+
adjacency.get(edge.from).push({ to: edge.to, edge: edge });
|
|
1091
|
+
if (undirected) {
|
|
1092
|
+
if (!adjacency.has(edge.to)) adjacency.set(edge.to, []);
|
|
1093
|
+
adjacency.get(edge.to).push({ to: edge.from, edge: edge });
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
var queue = [fromId];
|
|
1097
|
+
var seen = new Set([fromId]);
|
|
1098
|
+
var previous = new Map();
|
|
1099
|
+
while (queue.length) {
|
|
1100
|
+
var current = queue.shift();
|
|
1101
|
+
if (current === toId) break;
|
|
1102
|
+
(adjacency.get(current) || []).forEach(function (step) {
|
|
1103
|
+
if (seen.has(step.to)) return;
|
|
1104
|
+
seen.add(step.to);
|
|
1105
|
+
previous.set(step.to, { from: current, edge: step.edge });
|
|
1106
|
+
queue.push(step.to);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (!seen.has(toId)) return null;
|
|
1110
|
+
var nodes = [toId];
|
|
1111
|
+
var edges = [];
|
|
1112
|
+
var cursor = toId;
|
|
1113
|
+
while (cursor !== fromId) {
|
|
1114
|
+
var prev = previous.get(cursor);
|
|
1115
|
+
if (!prev) return null;
|
|
1116
|
+
edges.unshift(prev.edge.id);
|
|
1117
|
+
cursor = prev.from;
|
|
1118
|
+
nodes.unshift(cursor);
|
|
1119
|
+
}
|
|
1120
|
+
return { nodes: nodes, edges: edges };
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function findDependencyPath() {
|
|
1124
|
+
var from = resolvePathEntity(els.pathFromInput.value);
|
|
1125
|
+
var to = resolvePathEntity(els.pathToInput.value);
|
|
1126
|
+
if (!from || !to) {
|
|
1127
|
+
setPathStatus("Could not resolve both endpoints. Try a file path or exact symbol name.", "warn");
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (from.id === to.id) {
|
|
1131
|
+
setPathStatus("Pick two different code nodes.", "warn");
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
var direction = "forward";
|
|
1135
|
+
var path = shortestCodePath(from.id, to.id, false);
|
|
1136
|
+
if (!path) {
|
|
1137
|
+
var reverse = shortestCodePath(to.id, from.id, false);
|
|
1138
|
+
if (reverse) {
|
|
1139
|
+
direction = "reverse";
|
|
1140
|
+
path = {
|
|
1141
|
+
nodes: reverse.nodes.slice().reverse(),
|
|
1142
|
+
edges: reverse.edges.slice().reverse()
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (!path) {
|
|
1147
|
+
direction = "undirected";
|
|
1148
|
+
path = shortestCodePath(from.id, to.id, true);
|
|
1149
|
+
}
|
|
1150
|
+
if (!path) {
|
|
1151
|
+
state.pathHighlight = { nodes: new Set(), edges: new Set(), direction: "", steps: [] };
|
|
1152
|
+
els.pathResult.textContent = "";
|
|
1153
|
+
els.pathResult.className = "path-result";
|
|
1154
|
+
setPathStatus("No code dependency path found between those nodes.", "warn");
|
|
1155
|
+
render();
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
state.pathHighlight = {
|
|
1159
|
+
nodes: new Set(path.nodes),
|
|
1160
|
+
edges: new Set(path.edges),
|
|
1161
|
+
direction: direction,
|
|
1162
|
+
steps: path.nodes
|
|
1163
|
+
};
|
|
1164
|
+
setPathStatus(path.nodes.length + " nodes, " + path.edges.length + " edge(s), " + direction + " path.", "ok");
|
|
1165
|
+
renderPathResult(path.nodes, path.edges);
|
|
1166
|
+
state.lastVisibleSignature = "";
|
|
1167
|
+
render();
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function setPathStatus(text, status) {
|
|
1171
|
+
els.pathStatus.textContent = text;
|
|
1172
|
+
els.pathStatus.className = "path-status" + (status ? " " + status : "");
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function clearDependencyPath(renderNow) {
|
|
1176
|
+
state.pathHighlight = { nodes: new Set(), edges: new Set(), direction: "", steps: [] };
|
|
1177
|
+
if (els.pathFromInput) els.pathFromInput.value = "";
|
|
1178
|
+
if (els.pathToInput) els.pathToInput.value = "";
|
|
1179
|
+
if (els.pathResult) {
|
|
1180
|
+
els.pathResult.textContent = "";
|
|
1181
|
+
els.pathResult.className = "path-result";
|
|
1182
|
+
}
|
|
1183
|
+
if (els.pathStatus) setPathStatus("Pick two code nodes to trace a dependency path.", "");
|
|
1184
|
+
if (renderNow !== false) {
|
|
1185
|
+
state.lastVisibleSignature = "";
|
|
1186
|
+
render();
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function renderPathResult(nodes, edges) {
|
|
1191
|
+
els.pathResult.textContent = "";
|
|
1192
|
+
nodes.forEach(function (id, index) {
|
|
1193
|
+
var entity = state.entityById.get(id);
|
|
1194
|
+
var edge = index > 0 ? state.edgeById.get(edges[index - 1]) : null;
|
|
1195
|
+
var button = document.createElement("button");
|
|
1196
|
+
button.type = "button";
|
|
1197
|
+
button.className = "path-step";
|
|
1198
|
+
button.innerHTML = "<strong></strong><span></span>";
|
|
1199
|
+
button.querySelector("strong").textContent = entity ? displayName(entity) : id;
|
|
1200
|
+
button.querySelector("span").textContent = [
|
|
1201
|
+
index === 0 ? "start" : edge ? edge.relation : "path",
|
|
1202
|
+
entity && entity.path ? entity.path : id
|
|
1203
|
+
].filter(Boolean).join(" | ");
|
|
1204
|
+
button.addEventListener("click", function () {
|
|
1205
|
+
selectEntity(id, true);
|
|
1206
|
+
render();
|
|
1207
|
+
});
|
|
1208
|
+
els.pathResult.appendChild(button);
|
|
1209
|
+
});
|
|
1210
|
+
els.pathResult.className = "path-result visible";
|
|
1211
|
+
}
|
|
1212
|
+
|
|
839
1213
|
function layoutGraph(visibleIds) {
|
|
840
1214
|
state.positions = new Map();
|
|
841
1215
|
var candidates = state.entities.filter(function (entity) {
|
|
@@ -872,6 +1246,7 @@
|
|
|
872
1246
|
|
|
873
1247
|
function render() {
|
|
874
1248
|
if (!state.graph) return;
|
|
1249
|
+
syncQuickControls();
|
|
875
1250
|
|
|
876
1251
|
var query = parseSearchQuery(els.searchInput.value);
|
|
877
1252
|
state.renderQuery = query;
|
|
@@ -981,7 +1356,16 @@
|
|
|
981
1356
|
}
|
|
982
1357
|
}
|
|
983
1358
|
|
|
984
|
-
|
|
1359
|
+
if (state.pathHighlight && state.pathHighlight.nodes.size) {
|
|
1360
|
+
state.pathHighlight.nodes.forEach(function (id) { entities.add(id); });
|
|
1361
|
+
state.pathHighlight.edges.forEach(function (id) { edges.add(id); });
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
var cappedEdges = capVisibleEdges(edgesWithVisibleEndpoints(edges, entities), entities, options);
|
|
1365
|
+
if (state.pathHighlight && state.pathHighlight.edges.size) {
|
|
1366
|
+
state.pathHighlight.edges.forEach(function (id) { if (edges.has(id)) cappedEdges.add(id); });
|
|
1367
|
+
}
|
|
1368
|
+
return { entities: entities, edges: cappedEdges };
|
|
985
1369
|
}
|
|
986
1370
|
|
|
987
1371
|
function capVisibleEdges(edgeIds, entityIds, options) {
|
|
@@ -1484,9 +1868,10 @@
|
|
|
1484
1868
|
var to = nodeMap.get(edge.to);
|
|
1485
1869
|
if (!from || !to) return;
|
|
1486
1870
|
var connected = focusId && (edge.from === focusId || edge.to === focusId);
|
|
1871
|
+
var pathEdge = state.pathHighlight && state.pathHighlight.edges.has(edge.id);
|
|
1487
1872
|
var matches = matchesSearchQuery(edge, query) || matchesSearchQuery(from.entity, query) || matchesSearchQuery(to.entity, query);
|
|
1488
|
-
var alpha = !matches ? 0.035 : focusId ? (connected ? 0.62 : 0.055) : (dense ? 0.13 : 0.22);
|
|
1489
|
-
var color = hexToRgb(edgeThemeColor(edge, from.entity, to.entity));
|
|
1873
|
+
var alpha = pathEdge ? 0.92 : !matches ? 0.035 : focusId ? (connected ? 0.62 : 0.055) : (dense ? 0.13 : 0.22);
|
|
1874
|
+
var color = hexToRgb(pathEdge ? graphPalette.bridge : edgeThemeColor(edge, from.entity, to.entity));
|
|
1490
1875
|
var dx = to.x - from.x;
|
|
1491
1876
|
var dy = to.y - from.y;
|
|
1492
1877
|
var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
|
|
@@ -1497,10 +1882,10 @@
|
|
|
1497
1882
|
ctx.moveTo(from.x, from.y);
|
|
1498
1883
|
ctx.quadraticCurveTo(cx, cy, to.x, to.y);
|
|
1499
1884
|
ctx.strokeStyle = "rgba(" + color.r + "," + color.g + "," + color.b + "," + alpha + ")";
|
|
1500
|
-
ctx.lineWidth = connected ? 2.2 : 1;
|
|
1885
|
+
ctx.lineWidth = pathEdge ? 3 : connected ? 2.2 : 1;
|
|
1501
1886
|
ctx.stroke();
|
|
1502
|
-
if (connected || (!dense && state.sim.zoom > 1.25)) drawArrow(ctx, from, to, cx, cy, color, alpha);
|
|
1503
|
-
if (connected && state.sim.zoom > 0.62) drawEdgeLabel(ctx, edge, cx, cy);
|
|
1887
|
+
if (pathEdge || connected || (!dense && state.sim.zoom > 1.25)) drawArrow(ctx, from, to, cx, cy, color, alpha);
|
|
1888
|
+
if ((pathEdge || connected) && state.sim.zoom > 0.62) drawEdgeLabel(ctx, edge, cx, cy);
|
|
1504
1889
|
});
|
|
1505
1890
|
}
|
|
1506
1891
|
|
|
@@ -1514,14 +1899,15 @@
|
|
|
1514
1899
|
var selected = state.selected && state.selected.kind === "entity" && state.selected.id === node.id;
|
|
1515
1900
|
var hovered = state.sim.hoverNode && state.sim.hoverNode.id === node.id;
|
|
1516
1901
|
var connected = focusId && (node.id === focusId || (focusNeighbors && focusNeighbors.has(node.id)));
|
|
1902
|
+
var pathNode = state.pathHighlight && state.pathHighlight.nodes.has(node.id);
|
|
1517
1903
|
var matches = matchesSearchQuery(entity, query);
|
|
1518
|
-
var alpha = !matches ? 0.12 : focusId && !connected ? 0.20 : 1;
|
|
1904
|
+
var alpha = pathNode ? 1 : !matches ? 0.12 : focusId && !connected ? 0.20 : 1;
|
|
1519
1905
|
var color = nodeThemeColor(entity);
|
|
1520
1906
|
ctx.save();
|
|
1521
1907
|
ctx.globalAlpha = alpha;
|
|
1522
|
-
if (selected || hovered) {
|
|
1523
|
-
ctx.shadowColor = color;
|
|
1524
|
-
ctx.shadowBlur = selected ? 14 : 10;
|
|
1908
|
+
if (selected || hovered || pathNode) {
|
|
1909
|
+
ctx.shadowColor = pathNode ? graphPalette.bridge : color;
|
|
1910
|
+
ctx.shadowBlur = selected ? 14 : pathNode ? 12 : 10;
|
|
1525
1911
|
}
|
|
1526
1912
|
drawNodeShape(ctx, node.x, node.y, node.r, entity);
|
|
1527
1913
|
ctx.fillStyle = nodeFillColor(entity);
|
|
@@ -1538,18 +1924,18 @@
|
|
|
1538
1924
|
}
|
|
1539
1925
|
ctx.restore();
|
|
1540
1926
|
|
|
1541
|
-
if (selected || hovered) {
|
|
1927
|
+
if (selected || hovered || pathNode) {
|
|
1542
1928
|
ctx.save();
|
|
1543
1929
|
drawNodeShape(ctx, node.x, node.y, node.r + 4, entity);
|
|
1544
|
-
ctx.strokeStyle = color;
|
|
1545
|
-
ctx.lineWidth = selected ? 2.6 : 1.8;
|
|
1546
|
-
ctx.shadowColor = color;
|
|
1930
|
+
ctx.strokeStyle = pathNode ? graphPalette.bridge : color;
|
|
1931
|
+
ctx.lineWidth = selected ? 2.6 : pathNode ? 2.2 : 1.8;
|
|
1932
|
+
ctx.shadowColor = pathNode ? graphPalette.bridge : color;
|
|
1547
1933
|
ctx.shadowBlur = 8;
|
|
1548
1934
|
ctx.stroke();
|
|
1549
1935
|
ctx.restore();
|
|
1550
1936
|
}
|
|
1551
1937
|
|
|
1552
|
-
var shouldLabel = matches && (selected || hovered || (query.active && matches) || (!dense && state.sim.zoom > 0.75) || (dense && state.sim.zoom > 1.55 && node.r > 13));
|
|
1938
|
+
var shouldLabel = pathNode || (matches && (selected || hovered || (query.active && matches) || (!dense && state.sim.zoom > 0.75) || (dense && state.sim.zoom > 1.55 && node.r > 13)));
|
|
1553
1939
|
if (shouldLabel) drawNodeLabel(ctx, node, selected || hovered);
|
|
1554
1940
|
});
|
|
1555
1941
|
}
|
|
@@ -1681,7 +2067,7 @@
|
|
|
1681
2067
|
class: "edge-hit"
|
|
1682
2068
|
});
|
|
1683
2069
|
hit.addEventListener("click", function () {
|
|
1684
|
-
|
|
2070
|
+
selectEdge(edge.id, true);
|
|
1685
2071
|
render();
|
|
1686
2072
|
});
|
|
1687
2073
|
hit.addEventListener("mousedown", function (event) {
|
|
@@ -1733,7 +2119,7 @@
|
|
|
1733
2119
|
var title = svgEl("title");
|
|
1734
2120
|
title.textContent = displayName(entity) + "\n" + (entity.summary || "");
|
|
1735
2121
|
group.addEventListener("click", function () {
|
|
1736
|
-
|
|
2122
|
+
selectEntity(entity.id, true);
|
|
1737
2123
|
render();
|
|
1738
2124
|
});
|
|
1739
2125
|
group.addEventListener("mousedown", function (event) {
|
|
@@ -1786,7 +2172,7 @@
|
|
|
1786
2172
|
button.querySelector(".item-title").textContent = displayName(entity);
|
|
1787
2173
|
button.querySelector(".item-meta").textContent = (entity.type || "unknown") + " | " + entity.id;
|
|
1788
2174
|
button.addEventListener("click", function () {
|
|
1789
|
-
|
|
2175
|
+
selectEntity(entity.id, true);
|
|
1790
2176
|
render();
|
|
1791
2177
|
});
|
|
1792
2178
|
els.entityList.appendChild(button);
|
|
@@ -1800,7 +2186,7 @@
|
|
|
1800
2186
|
button.querySelector(".item-title").textContent = edge.relation || "related";
|
|
1801
2187
|
button.querySelector(".item-meta").textContent = displayName(state.entityById.get(edge.from)) + " -> " + displayName(state.entityById.get(edge.to)) + " | " + reviewStatus(edge);
|
|
1802
2188
|
button.addEventListener("click", function () {
|
|
1803
|
-
|
|
2189
|
+
selectEdge(edge.id, true);
|
|
1804
2190
|
render();
|
|
1805
2191
|
});
|
|
1806
2192
|
els.edgeList.appendChild(button);
|
|
@@ -2029,7 +2415,8 @@
|
|
|
2029
2415
|
button.querySelector(".detail-link-meta").textContent = item.meta;
|
|
2030
2416
|
button.querySelector(".detail-link-body").textContent = item.body;
|
|
2031
2417
|
button.addEventListener("click", function () {
|
|
2032
|
-
|
|
2418
|
+
if (item.entity) selectEntity(item.entity.id, true);
|
|
2419
|
+
else selectEdge(item.edge.id, true);
|
|
2033
2420
|
render();
|
|
2034
2421
|
});
|
|
2035
2422
|
list.appendChild(button);
|
|
@@ -2116,6 +2503,112 @@
|
|
|
2116
2503
|
els.workspaceMode.textContent = (els.viewMode.value || "combined").replace(/^./, function (letter) { return letter.toUpperCase(); });
|
|
2117
2504
|
els.graphSubhead.textContent = visibleEntities.length + " visible nodes and " + visibleEdges.length + " visible relations" +
|
|
2118
2505
|
(hiddenDependencies && !els.showDependencies.checked ? " (" + hiddenDependencies + " dependency/noise nodes hidden)." : ".");
|
|
2506
|
+
renderDashboard();
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
function renderDashboard() {
|
|
2510
|
+
if (!els.dashboardStats) return;
|
|
2511
|
+
var metrics = state.metrics || {};
|
|
2512
|
+
var memoryGraph = metrics.memory_graph || {};
|
|
2513
|
+
var codeGraph = metrics.code_graph || {};
|
|
2514
|
+
var structural = metrics.structural_index || {};
|
|
2515
|
+
var savings = metrics.savings || {};
|
|
2516
|
+
var pain = metrics.pain || {};
|
|
2517
|
+
var memoryNodes = state.entities.filter(function (entity) { return entity.graph_kind === "memory"; }).length;
|
|
2518
|
+
var codeNodes = state.entities.filter(function (entity) { return entity.graph_kind === "code"; }).length;
|
|
2519
|
+
var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
|
|
2520
|
+
var reports = state.reports || {};
|
|
2521
|
+
var reportCount = Object.keys(reports).filter(function (key) { return reports[key]; }).length;
|
|
2522
|
+
var statRows = [
|
|
2523
|
+
["Memory packets", firstNumber(memoryGraph.approved_packets, memoryNodes)],
|
|
2524
|
+
["Code nodes", firstNumber(codeGraph.symbols, structural.symbols, codeNodes)],
|
|
2525
|
+
["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
|
|
2526
|
+
["Memory-code links", memoryCodeEdges.length],
|
|
2527
|
+
["Parser coverage", codeGraph.indexer_coverage_percent != null ? codeGraph.indexer_coverage_percent + "%" : "n/a"],
|
|
2528
|
+
["Tokens saved", firstNumber(savings.estimated_tokens_saved_per_recall, pain.estimated_tokens_saved, "n/a")]
|
|
2529
|
+
];
|
|
2530
|
+
els.dashboardStats.textContent = "";
|
|
2531
|
+
statRows.forEach(function (row) {
|
|
2532
|
+
var item = document.createElement("div");
|
|
2533
|
+
item.className = "dashboard-stat";
|
|
2534
|
+
item.innerHTML = "<strong></strong><span></span>";
|
|
2535
|
+
item.querySelector("strong").textContent = formatDashboardValue(row[1]);
|
|
2536
|
+
item.querySelector("span").textContent = row[0];
|
|
2537
|
+
els.dashboardStats.appendChild(item);
|
|
2538
|
+
});
|
|
2539
|
+
|
|
2540
|
+
setDashboardRows("dashboardMemory", [
|
|
2541
|
+
["Approved packets", firstNumber(memoryGraph.approved_packets, memoryNodes)],
|
|
2542
|
+
["Pending review", firstNumber(memoryGraph.pending_packets, (state.pendingPackets || []).length)],
|
|
2543
|
+
["Evidence coverage", memoryGraph.evidence_coverage_percent != null ? memoryGraph.evidence_coverage_percent + "%" : "n/a"],
|
|
2544
|
+
["Code-linked memory", memoryCodeEdges.length]
|
|
2545
|
+
]);
|
|
2546
|
+
setDashboardRows("dashboardGraph", [
|
|
2547
|
+
["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
|
|
2548
|
+
["Symbols", firstNumber(codeGraph.symbols, structural.symbols, countEntitiesByType("symbol"))],
|
|
2549
|
+
["Relations", state.edges.length],
|
|
2550
|
+
["Dependency/noise hidden", state.entities.filter(function (entity) { return isDependencyEntity(entity); }).length]
|
|
2551
|
+
]);
|
|
2552
|
+
setDashboardRows("dashboardIntel", [
|
|
2553
|
+
["Reports loaded", reportCount],
|
|
2554
|
+
["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "n/a"],
|
|
2555
|
+
["Modules scored", reports.moduleHealth && Array.isArray(reports.moduleHealth.modules) ? reports.moduleHealth.modules.length : "n/a"],
|
|
2556
|
+
["Communities", reports.graphInsights && Array.isArray(reports.graphInsights.communities) ? reports.graphInsights.communities.length : "n/a"]
|
|
2557
|
+
]);
|
|
2558
|
+
var risk = reports.risk || {};
|
|
2559
|
+
var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
|
|
2560
|
+
setDashboardRows("dashboardRisk", [
|
|
2561
|
+
["Risk targets", riskTargets.length || "n/a"],
|
|
2562
|
+
["Hotspots", Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : "n/a"],
|
|
2563
|
+
["Ownership silos", Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : "n/a"],
|
|
2564
|
+
["Cycles", reports.graphInsights && Array.isArray(reports.graphInsights.dependency_cycles) ? reports.graphInsights.dependency_cycles.length : "n/a"]
|
|
2565
|
+
]);
|
|
2566
|
+
var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
|
|
2567
|
+
setDashboardRows("dashboardReview", [
|
|
2568
|
+
["Readiness", metrics.harness && metrics.harness.readiness_score != null ? metrics.harness.readiness_score + "/100" : "n/a"],
|
|
2569
|
+
["Inbox pending", firstNumber(inboxCounts.pending, (state.pendingPackets || []).length)],
|
|
2570
|
+
["Stale flags", firstNumber(inboxCounts.stale, 0)],
|
|
2571
|
+
["Duplicate flags", firstNumber(inboxCounts.duplicates, 0)]
|
|
2572
|
+
]);
|
|
2573
|
+
var workspace = reports.workspace || {};
|
|
2574
|
+
setDashboardRows("dashboardWorkspace", [
|
|
2575
|
+
["Repos", Array.isArray(workspace.repos) ? workspace.repos.length : "n/a"],
|
|
2576
|
+
["Package deps", Array.isArray(workspace.package_dependencies) ? workspace.package_dependencies.length : "n/a"],
|
|
2577
|
+
["Route contracts", Array.isArray(workspace.route_contracts) ? workspace.route_contracts.length : "n/a"],
|
|
2578
|
+
["Co-changes", Array.isArray(workspace.co_changes) ? workspace.co_changes.length : "n/a"]
|
|
2579
|
+
]);
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
function setDashboardRows(cardId, rows) {
|
|
2583
|
+
var card = document.getElementById(cardId);
|
|
2584
|
+
if (!card) return;
|
|
2585
|
+
var list = card.querySelector("ul");
|
|
2586
|
+
if (!list) return;
|
|
2587
|
+
list.textContent = "";
|
|
2588
|
+
rows.forEach(function (row) {
|
|
2589
|
+
var item = document.createElement("li");
|
|
2590
|
+
item.innerHTML = "<strong></strong><span></span>";
|
|
2591
|
+
item.querySelector("strong").textContent = row[0];
|
|
2592
|
+
item.querySelector("span").textContent = formatDashboardValue(row[1]);
|
|
2593
|
+
list.appendChild(item);
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function firstNumber() {
|
|
2598
|
+
for (var index = 0; index < arguments.length; index += 1) {
|
|
2599
|
+
var value = arguments[index];
|
|
2600
|
+
if (value !== null && value !== undefined && value !== "") return value;
|
|
2601
|
+
}
|
|
2602
|
+
return "n/a";
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function countEntitiesByType(type) {
|
|
2606
|
+
return state.entities.filter(function (entity) { return entity.type === type; }).length;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
function formatDashboardValue(value) {
|
|
2610
|
+
if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
|
|
2611
|
+
return String(value == null ? "n/a" : value);
|
|
2119
2612
|
}
|
|
2120
2613
|
|
|
2121
2614
|
function renderReviewQueue() {
|
|
@@ -2553,7 +3046,7 @@
|
|
|
2553
3046
|
scheduleRender();
|
|
2554
3047
|
return;
|
|
2555
3048
|
}
|
|
2556
|
-
|
|
3049
|
+
selectEntity(found.id, true);
|
|
2557
3050
|
els.searchInput.value = normalized;
|
|
2558
3051
|
scheduleRender();
|
|
2559
3052
|
}
|
|
@@ -2847,11 +3340,12 @@
|
|
|
2847
3340
|
|
|
2848
3341
|
function endCanvasPointer() {
|
|
2849
3342
|
if (state.sim.dragNode) {
|
|
2850
|
-
state.
|
|
3343
|
+
selectEntity(state.sim.dragNode.id, true);
|
|
2851
3344
|
state.sim.dragNode = null;
|
|
2852
3345
|
render();
|
|
2853
3346
|
} else if (state.sim.panning && !state.sim.panning.moved) {
|
|
2854
3347
|
state.selected = null;
|
|
3348
|
+
closeWorkspace();
|
|
2855
3349
|
render();
|
|
2856
3350
|
}
|
|
2857
3351
|
state.sim.panning = null;
|
|
@@ -2888,7 +3382,7 @@
|
|
|
2888
3382
|
var world = canvasToWorld(event);
|
|
2889
3383
|
var node = findCanvasNode(world.x, world.y);
|
|
2890
3384
|
if (!node) return;
|
|
2891
|
-
|
|
3385
|
+
selectEntity(node.id, true);
|
|
2892
3386
|
render();
|
|
2893
3387
|
}
|
|
2894
3388
|
|
|
@@ -3062,8 +3556,9 @@
|
|
|
3062
3556
|
state.sim.nodes.forEach(function (node, index) {
|
|
3063
3557
|
var entity = node.entity;
|
|
3064
3558
|
var selected = state.selected && state.selected.kind === "entity" && state.selected.id === node.id;
|
|
3559
|
+
var pathNode = state.pathHighlight && state.pathHighlight.nodes.has(node.id);
|
|
3065
3560
|
var material = new THREE.SpriteMaterial({
|
|
3066
|
-
map: threeNodeTexture(entity, selected),
|
|
3561
|
+
map: threeNodeTexture(entity, selected || pathNode),
|
|
3067
3562
|
transparent: true,
|
|
3068
3563
|
opacity: isDependencyEntity(entity) ? 0.70 : 1,
|
|
3069
3564
|
depthWrite: false
|
|
@@ -3071,7 +3566,7 @@
|
|
|
3071
3566
|
var mesh = new THREE.Sprite(material);
|
|
3072
3567
|
var position = threePosition(node, index);
|
|
3073
3568
|
mesh.position.set(position.x, position.y, position.z);
|
|
3074
|
-
var size = threeNodeSize(node, selected);
|
|
3569
|
+
var size = threeNodeSize(node, selected || pathNode);
|
|
3075
3570
|
mesh.scale.set(size, size, 1);
|
|
3076
3571
|
mesh.userData.node = node;
|
|
3077
3572
|
state.three.nodeGroup.add(mesh);
|
|
@@ -3085,11 +3580,12 @@
|
|
|
3085
3580
|
var fromEntity = from.node.entity;
|
|
3086
3581
|
var toEntity = to.node.entity;
|
|
3087
3582
|
var connected = state.selected && state.selected.kind === "entity" && (state.selected.id === edge.from || state.selected.id === edge.to);
|
|
3583
|
+
var pathEdge = state.pathHighlight && state.pathHighlight.edges.has(edge.id);
|
|
3088
3584
|
var geometry = new THREE.BufferGeometry().setFromPoints([from.mesh.position, to.mesh.position]);
|
|
3089
3585
|
var material = new THREE.LineBasicMaterial({
|
|
3090
|
-
color: new THREE.Color(edgeThemeColor(edge, fromEntity, toEntity)),
|
|
3586
|
+
color: new THREE.Color(pathEdge ? graphPalette.bridge : edgeThemeColor(edge, fromEntity, toEntity)),
|
|
3091
3587
|
transparent: true,
|
|
3092
|
-
opacity: threeEdgeOpacity(edge, fromEntity, toEntity, connected),
|
|
3588
|
+
opacity: pathEdge ? 0.82 : threeEdgeOpacity(edge, fromEntity, toEntity, connected),
|
|
3093
3589
|
depthWrite: false,
|
|
3094
3590
|
depthTest: false
|
|
3095
3591
|
});
|
|
@@ -3426,7 +3922,7 @@
|
|
|
3426
3922
|
: null;
|
|
3427
3923
|
state.three.drag = null;
|
|
3428
3924
|
if (picked) {
|
|
3429
|
-
|
|
3925
|
+
selectEntity(picked.id, true);
|
|
3430
3926
|
render();
|
|
3431
3927
|
}
|
|
3432
3928
|
}
|
|
@@ -3447,7 +3943,7 @@
|
|
|
3447
3943
|
function handleThreeDoubleClick(event) {
|
|
3448
3944
|
var picked = pickThreeNode(event);
|
|
3449
3945
|
if (!picked) return;
|
|
3450
|
-
|
|
3946
|
+
selectEntity(picked.id, true);
|
|
3451
3947
|
render();
|
|
3452
3948
|
}
|
|
3453
3949
|
|
|
@@ -3632,6 +4128,7 @@
|
|
|
3632
4128
|
if (state.pan && state.pan.moved) return;
|
|
3633
4129
|
if (event.target !== els.svg) return;
|
|
3634
4130
|
state.selected = null;
|
|
4131
|
+
closeWorkspace();
|
|
3635
4132
|
render();
|
|
3636
4133
|
}
|
|
3637
4134
|
|