toolnet-memory 0.3.16 → 0.3.18
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/.env.example +74 -0
- package/.release-target +1 -1
- package/README.md +157 -7
- package/bin/toolnet-memory +12 -0
- package/bundle/agy-hook.js +59 -59
- package/bundle/agy.js +58 -58
- package/bundle/api.js +15 -15
- package/bundle/audit.js +2 -0
- package/bundle/background-refresh.js +7 -7
- package/bundle/code-capabilities.js +2 -0
- package/bundle/codex.js +63 -63
- package/bundle/context-runtime.js +41 -41
- package/bundle/context.js +15 -15
- package/bundle/copilot-hook.js +73 -73
- package/bundle/cursor-hook.js +73 -73
- package/bundle/docker-healthcheck.js +3 -0
- package/bundle/doctor.js +4 -4
- package/bundle/full-index.js +20 -19
- package/bundle/gc.js +5 -3
- package/bundle/graph-cli.js +1 -1
- package/bundle/graph-index.js +5 -4
- package/bundle/graph-ui.js +4 -3
- package/bundle/grok-hook.js +52 -52
- package/bundle/handoff-refresh.js +2 -2
- package/bundle/help-cli.js +4 -4
- package/bundle/impact.js +5 -4
- package/bundle/incremental.js +5 -5
- package/bundle/init.js +35 -35
- package/bundle/kiro-hook.js +74 -74
- package/bundle/learner.js +8 -6
- package/bundle/mcp.js +19 -17
- package/bundle/memory-agent.js +4 -4
- package/bundle/memory-query.js +3 -3
- package/bundle/opencode.js +73 -73
- package/bundle/project-manual.js +7 -7
- package/bundle/public/index.html +1089 -2306
- package/bundle/runtime.js +16 -13
- package/bundle/semantic.js +6 -5
- package/bundle/service.js +11 -6
- package/bundle/snapshot.js +6 -4
- package/bundle/status-cli.js +4 -4
- package/bundle/toolnet-cli.js +49 -49
- package/bundle/work-continuity.js +7 -7
- package/package.json +26 -2
- package/release-manifest.json +57 -1
package/bundle/public/index.html
CHANGED
|
@@ -1,1500 +1,952 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<meta
|
|
7
|
-
name="viewport"
|
|
8
|
-
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
|
9
|
-
/>
|
|
10
|
-
|
|
11
|
-
<title>
|
|
12
|
-
ToolNet Memory · Graph
|
|
13
|
-
</title>
|
|
14
|
-
|
|
15
|
-
<style>
|
|
16
|
-
* {
|
|
17
|
-
box-sizing: border-box;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
html,
|
|
21
|
-
body {
|
|
22
|
-
width: 100%;
|
|
23
|
-
height: 100%;
|
|
24
|
-
margin: 0;
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
|
|
27
|
-
background:
|
|
28
|
-
#050608;
|
|
29
|
-
|
|
30
|
-
color:
|
|
31
|
-
#f6f7f9;
|
|
32
|
-
|
|
33
|
-
font-family:
|
|
34
|
-
Inter,
|
|
35
|
-
ui-sans-serif,
|
|
36
|
-
system-ui,
|
|
37
|
-
-apple-system,
|
|
38
|
-
BlinkMacSystemFont,
|
|
39
|
-
"Segoe UI",
|
|
40
|
-
sans-serif;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#graph {
|
|
44
|
-
position: fixed;
|
|
45
|
-
inset: 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.panel {
|
|
49
|
-
position: fixed;
|
|
50
|
-
z-index: 20;
|
|
51
|
-
|
|
52
|
-
border:
|
|
53
|
-
1px solid
|
|
54
|
-
rgba(255,255,255,.12);
|
|
55
|
-
|
|
56
|
-
background:
|
|
57
|
-
rgba(10,12,17,.88);
|
|
58
|
-
|
|
59
|
-
backdrop-filter:
|
|
60
|
-
blur(20px);
|
|
61
|
-
|
|
62
|
-
-webkit-backdrop-filter:
|
|
63
|
-
blur(20px);
|
|
64
|
-
|
|
65
|
-
box-shadow:
|
|
66
|
-
0 22px 60px
|
|
67
|
-
rgba(0,0,0,.42);
|
|
68
|
-
|
|
69
|
-
border-radius:
|
|
70
|
-
18px;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
#toolbar {
|
|
74
|
-
top:
|
|
75
|
-
max(
|
|
76
|
-
14px,
|
|
77
|
-
env(safe-area-inset-top)
|
|
78
|
-
);
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
79
5
|
|
|
80
|
-
|
|
81
|
-
14px;
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
82
7
|
|
|
83
|
-
|
|
84
|
-
14px;
|
|
8
|
+
<title>ToolNet Memory · Graph</title>
|
|
85
9
|
|
|
86
|
-
|
|
87
|
-
|
|
10
|
+
<style>
|
|
11
|
+
* {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
88
14
|
|
|
89
|
-
|
|
90
|
-
|
|
15
|
+
html,
|
|
16
|
+
body {
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 100%;
|
|
19
|
+
margin: 0;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
|
|
22
|
+
background: #050608;
|
|
23
|
+
|
|
24
|
+
color: #f6f7f9;
|
|
25
|
+
|
|
26
|
+
font-family:
|
|
27
|
+
Inter,
|
|
28
|
+
ui-sans-serif,
|
|
29
|
+
system-ui,
|
|
30
|
+
-apple-system,
|
|
31
|
+
BlinkMacSystemFont,
|
|
32
|
+
'Segoe UI',
|
|
33
|
+
sans-serif;
|
|
34
|
+
}
|
|
91
35
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
minmax(150px, .7fr)
|
|
97
|
-
auto;
|
|
36
|
+
#graph {
|
|
37
|
+
position: fixed;
|
|
38
|
+
inset: 0;
|
|
39
|
+
}
|
|
98
40
|
|
|
99
|
-
|
|
100
|
-
|
|
41
|
+
.panel {
|
|
42
|
+
position: fixed;
|
|
43
|
+
z-index: 20;
|
|
101
44
|
|
|
102
|
-
|
|
103
|
-
center;
|
|
104
|
-
}
|
|
45
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
105
46
|
|
|
106
|
-
|
|
107
|
-
padding:
|
|
108
|
-
0 8px;
|
|
47
|
+
background: rgba(10, 12, 17, 0.88);
|
|
109
48
|
|
|
110
|
-
|
|
111
|
-
nowrap;
|
|
49
|
+
backdrop-filter: blur(20px);
|
|
112
50
|
|
|
113
|
-
|
|
114
|
-
750;
|
|
51
|
+
-webkit-backdrop-filter: blur(20px);
|
|
115
52
|
|
|
116
|
-
|
|
117
|
-
18px;
|
|
118
|
-
}
|
|
53
|
+
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.42);
|
|
119
54
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
55
|
+
border-radius: 18px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#toolbar {
|
|
59
|
+
top: max(14px, env(safe-area-inset-top));
|
|
123
60
|
|
|
124
|
-
|
|
125
|
-
500;
|
|
126
|
-
}
|
|
61
|
+
left: 14px;
|
|
127
62
|
|
|
128
|
-
|
|
129
|
-
select,
|
|
130
|
-
button {
|
|
131
|
-
height:
|
|
132
|
-
42px;
|
|
63
|
+
right: 14px;
|
|
133
64
|
|
|
134
|
-
|
|
135
|
-
0;
|
|
65
|
+
padding: 14px;
|
|
136
66
|
|
|
137
|
-
|
|
138
|
-
1px solid
|
|
139
|
-
rgba(255,255,255,.12);
|
|
67
|
+
display: grid;
|
|
140
68
|
|
|
141
|
-
|
|
142
|
-
|
|
69
|
+
grid-template-columns:
|
|
70
|
+
auto minmax(170px, 1fr)
|
|
71
|
+
minmax(170px, 1fr)
|
|
72
|
+
minmax(150px, 0.7fr)
|
|
73
|
+
minmax(150px, 0.7fr)
|
|
74
|
+
auto;
|
|
143
75
|
|
|
144
|
-
|
|
145
|
-
rgba(255,255,255,.06);
|
|
76
|
+
gap: 10px;
|
|
146
77
|
|
|
147
|
-
|
|
148
|
-
|
|
78
|
+
align-items: center;
|
|
79
|
+
}
|
|
149
80
|
|
|
150
|
-
|
|
151
|
-
0
|
|
81
|
+
.brand {
|
|
82
|
+
padding: 0 8px;
|
|
152
83
|
|
|
153
|
-
|
|
154
|
-
none;
|
|
84
|
+
white-space: nowrap;
|
|
155
85
|
|
|
156
|
-
|
|
157
|
-
inherit;
|
|
158
|
-
}
|
|
86
|
+
font-weight: 750;
|
|
159
87
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
pointer;
|
|
163
|
-
}
|
|
88
|
+
font-size: 18px;
|
|
89
|
+
}
|
|
164
90
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
650;
|
|
168
|
-
}
|
|
91
|
+
.brand span {
|
|
92
|
+
opacity: 0.45;
|
|
169
93
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
14px;
|
|
94
|
+
font-weight: 500;
|
|
95
|
+
}
|
|
173
96
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
97
|
+
input,
|
|
98
|
+
select,
|
|
99
|
+
button {
|
|
100
|
+
height: 42px;
|
|
179
101
|
|
|
180
|
-
|
|
181
|
-
220px;
|
|
102
|
+
min-width: 0;
|
|
182
103
|
|
|
183
|
-
|
|
184
|
-
14px 16px;
|
|
104
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
185
105
|
|
|
186
|
-
|
|
187
|
-
13px;
|
|
106
|
+
border-radius: 11px;
|
|
188
107
|
|
|
189
|
-
|
|
190
|
-
1.7;
|
|
191
|
-
}
|
|
108
|
+
background: rgba(255, 255, 255, 0.06);
|
|
192
109
|
|
|
193
|
-
|
|
194
|
-
margin-top:
|
|
195
|
-
5px;
|
|
110
|
+
color: white;
|
|
196
111
|
|
|
197
|
-
|
|
198
|
-
.55;
|
|
112
|
+
padding: 0 12px;
|
|
199
113
|
|
|
200
|
-
|
|
201
|
-
11px;
|
|
202
|
-
}
|
|
114
|
+
outline: none;
|
|
203
115
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
96px;
|
|
116
|
+
font: inherit;
|
|
117
|
+
}
|
|
207
118
|
|
|
208
|
-
|
|
209
|
-
|
|
119
|
+
button {
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
}
|
|
210
122
|
|
|
211
|
-
|
|
212
|
-
|
|
123
|
+
#project {
|
|
124
|
+
font-weight: 650;
|
|
125
|
+
}
|
|
213
126
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
410px,
|
|
217
|
-
calc(100vw - 28px)
|
|
218
|
-
);
|
|
127
|
+
#stats {
|
|
128
|
+
left: 14px;
|
|
219
129
|
|
|
220
|
-
|
|
221
|
-
68px 20px 20px;
|
|
130
|
+
bottom: max(14px, env(safe-area-inset-bottom));
|
|
222
131
|
|
|
223
|
-
|
|
224
|
-
auto;
|
|
132
|
+
min-width: 220px;
|
|
225
133
|
|
|
226
|
-
|
|
227
|
-
translateX(
|
|
228
|
-
calc(100% + 30px)
|
|
229
|
-
);
|
|
134
|
+
padding: 14px 16px;
|
|
230
135
|
|
|
231
|
-
|
|
232
|
-
transform .2s ease;
|
|
233
|
-
}
|
|
136
|
+
font-size: 13px;
|
|
234
137
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
translateX(0);
|
|
238
|
-
}
|
|
138
|
+
line-height: 1.7;
|
|
139
|
+
}
|
|
239
140
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
absolute;
|
|
141
|
+
.status {
|
|
142
|
+
margin-top: 5px;
|
|
243
143
|
|
|
244
|
-
|
|
245
|
-
14px;
|
|
144
|
+
opacity: 0.55;
|
|
246
145
|
|
|
247
|
-
|
|
248
|
-
|
|
146
|
+
font-size: 11px;
|
|
147
|
+
}
|
|
249
148
|
|
|
250
|
-
|
|
251
|
-
|
|
149
|
+
#details {
|
|
150
|
+
top: 96px;
|
|
252
151
|
|
|
253
|
-
|
|
254
|
-
44px;
|
|
152
|
+
right: 14px;
|
|
255
153
|
|
|
256
|
-
|
|
257
|
-
grid;
|
|
154
|
+
bottom: 14px;
|
|
258
155
|
|
|
259
|
-
|
|
260
|
-
center;
|
|
156
|
+
width: min(410px, calc(100vw - 28px));
|
|
261
157
|
|
|
262
|
-
|
|
263
|
-
0;
|
|
158
|
+
padding: 68px 20px 20px;
|
|
264
159
|
|
|
265
|
-
|
|
266
|
-
50%;
|
|
160
|
+
overflow-y: auto;
|
|
267
161
|
|
|
268
|
-
|
|
269
|
-
28px;
|
|
162
|
+
transform: translateX(calc(100% + 30px));
|
|
270
163
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
164
|
+
transition: transform 0.2s ease;
|
|
165
|
+
}
|
|
274
166
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
167
|
+
#details.open {
|
|
168
|
+
transform: translateX(0);
|
|
169
|
+
}
|
|
278
170
|
|
|
279
|
-
|
|
280
|
-
|
|
171
|
+
.details-close {
|
|
172
|
+
position: absolute;
|
|
281
173
|
|
|
282
|
-
|
|
283
|
-
1.25;
|
|
174
|
+
top: 14px;
|
|
284
175
|
|
|
285
|
-
|
|
286
|
-
anywhere;
|
|
287
|
-
}
|
|
176
|
+
right: 14px;
|
|
288
177
|
|
|
289
|
-
|
|
290
|
-
margin-top:
|
|
291
|
-
8px;
|
|
178
|
+
width: 44px;
|
|
292
179
|
|
|
293
|
-
|
|
294
|
-
.55;
|
|
180
|
+
height: 44px;
|
|
295
181
|
|
|
296
|
-
|
|
297
|
-
ui-monospace,
|
|
298
|
-
SFMono-Regular,
|
|
299
|
-
Menlo,
|
|
300
|
-
monospace;
|
|
182
|
+
display: grid;
|
|
301
183
|
|
|
302
|
-
|
|
303
|
-
12px;
|
|
184
|
+
place-items: center;
|
|
304
185
|
|
|
305
|
-
|
|
306
|
-
anywhere;
|
|
307
|
-
}
|
|
186
|
+
padding: 0;
|
|
308
187
|
|
|
309
|
-
|
|
310
|
-
display:
|
|
311
|
-
inline-flex;
|
|
188
|
+
border-radius: 50%;
|
|
312
189
|
|
|
313
|
-
|
|
314
|
-
10px 5px 0 0;
|
|
190
|
+
font-size: 28px;
|
|
315
191
|
|
|
316
|
-
|
|
317
|
-
|
|
192
|
+
line-height: 1;
|
|
193
|
+
}
|
|
318
194
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
rgba(255,255,255,.12);
|
|
195
|
+
.detail-title {
|
|
196
|
+
font-size: 21px;
|
|
322
197
|
|
|
323
|
-
|
|
324
|
-
999px;
|
|
198
|
+
font-weight: 750;
|
|
325
199
|
|
|
326
|
-
|
|
327
|
-
rgba(255,255,255,.06);
|
|
200
|
+
line-height: 1.25;
|
|
328
201
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
202
|
+
overflow-wrap: anywhere;
|
|
203
|
+
}
|
|
332
204
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
24px;
|
|
336
|
-
}
|
|
205
|
+
.path {
|
|
206
|
+
margin-top: 8px;
|
|
337
207
|
|
|
338
|
-
|
|
339
|
-
margin-bottom:
|
|
340
|
-
8px;
|
|
208
|
+
opacity: 0.55;
|
|
341
209
|
|
|
342
|
-
|
|
343
|
-
.5;
|
|
210
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
344
211
|
|
|
345
|
-
|
|
346
|
-
11px;
|
|
212
|
+
font-size: 12px;
|
|
347
213
|
|
|
348
|
-
|
|
349
|
-
|
|
214
|
+
overflow-wrap: anywhere;
|
|
215
|
+
}
|
|
350
216
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
217
|
+
.tag {
|
|
218
|
+
display: inline-flex;
|
|
354
219
|
|
|
355
|
-
|
|
356
|
-
display:
|
|
357
|
-
flex;
|
|
220
|
+
margin: 10px 5px 0 0;
|
|
358
221
|
|
|
359
|
-
|
|
360
|
-
space-between;
|
|
222
|
+
padding: 5px 9px;
|
|
361
223
|
|
|
362
|
-
|
|
363
|
-
12px;
|
|
224
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
364
225
|
|
|
365
|
-
|
|
366
|
-
9px 0;
|
|
226
|
+
border-radius: 999px;
|
|
367
227
|
|
|
368
|
-
|
|
369
|
-
1px solid
|
|
370
|
-
rgba(255,255,255,.06);
|
|
228
|
+
background: rgba(255, 255, 255, 0.06);
|
|
371
229
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
}
|
|
375
232
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
233
|
+
.section {
|
|
234
|
+
margin-top: 24px;
|
|
235
|
+
}
|
|
379
236
|
|
|
380
|
-
|
|
381
|
-
|
|
237
|
+
.section-title {
|
|
238
|
+
margin-bottom: 8px;
|
|
382
239
|
|
|
383
|
-
|
|
384
|
-
100;
|
|
240
|
+
opacity: 0.5;
|
|
385
241
|
|
|
386
|
-
|
|
387
|
-
grid;
|
|
242
|
+
font-size: 11px;
|
|
388
243
|
|
|
389
|
-
|
|
390
|
-
center;
|
|
244
|
+
letter-spacing: 0.1em;
|
|
391
245
|
|
|
392
|
-
|
|
393
|
-
|
|
246
|
+
text-transform: uppercase;
|
|
247
|
+
}
|
|
394
248
|
|
|
395
|
-
|
|
396
|
-
|
|
249
|
+
.row {
|
|
250
|
+
display: flex;
|
|
397
251
|
|
|
398
|
-
|
|
399
|
-
center;
|
|
400
|
-
}
|
|
252
|
+
justify-content: space-between;
|
|
401
253
|
|
|
402
|
-
|
|
403
|
-
max-width: 1000px
|
|
404
|
-
) {
|
|
405
|
-
#toolbar {
|
|
406
|
-
grid-template-columns:
|
|
407
|
-
1fr 1fr;
|
|
254
|
+
gap: 12px;
|
|
408
255
|
|
|
409
|
-
|
|
410
|
-
max(
|
|
411
|
-
10px,
|
|
412
|
-
env(safe-area-inset-top)
|
|
413
|
-
);
|
|
256
|
+
padding: 9px 0;
|
|
414
257
|
|
|
415
|
-
|
|
416
|
-
10px;
|
|
258
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
417
259
|
|
|
418
|
-
|
|
419
|
-
10px;
|
|
260
|
+
font-size: 13px;
|
|
420
261
|
}
|
|
421
262
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
1 / -1;
|
|
263
|
+
#loading {
|
|
264
|
+
position: fixed;
|
|
425
265
|
|
|
426
|
-
|
|
427
|
-
17px;
|
|
428
|
-
}
|
|
266
|
+
inset: 0;
|
|
429
267
|
|
|
430
|
-
|
|
431
|
-
grid-column:
|
|
432
|
-
1 / -1;
|
|
433
|
-
}
|
|
268
|
+
z-index: 100;
|
|
434
269
|
|
|
435
|
-
|
|
436
|
-
top:
|
|
437
|
-
10px;
|
|
270
|
+
display: grid;
|
|
438
271
|
|
|
439
|
-
|
|
440
|
-
10px;
|
|
272
|
+
place-items: center;
|
|
441
273
|
|
|
442
|
-
|
|
443
|
-
10px;
|
|
444
|
-
}
|
|
274
|
+
background: #050608;
|
|
445
275
|
|
|
446
|
-
|
|
447
|
-
left:
|
|
448
|
-
10px;
|
|
276
|
+
padding: 24px;
|
|
449
277
|
|
|
450
|
-
|
|
451
|
-
max(
|
|
452
|
-
10px,
|
|
453
|
-
env(safe-area-inset-bottom)
|
|
454
|
-
);
|
|
278
|
+
text-align: center;
|
|
455
279
|
}
|
|
456
|
-
}
|
|
457
280
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
281
|
+
@media (max-width: 1000px) {
|
|
282
|
+
#toolbar {
|
|
283
|
+
grid-template-columns: 1fr 1fr;
|
|
284
|
+
|
|
285
|
+
top: max(10px, env(safe-area-inset-top));
|
|
286
|
+
|
|
287
|
+
left: 10px;
|
|
288
|
+
|
|
289
|
+
right: 10px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.brand {
|
|
293
|
+
grid-column: 1 / -1;
|
|
294
|
+
|
|
295
|
+
font-size: 17px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#search {
|
|
299
|
+
grid-column: 1 / -1;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#details {
|
|
303
|
+
top: 10px;
|
|
304
|
+
|
|
305
|
+
right: 10px;
|
|
306
|
+
|
|
307
|
+
bottom: 10px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#stats {
|
|
311
|
+
left: 10px;
|
|
312
|
+
|
|
313
|
+
bottom: max(10px, env(safe-area-inset-bottom));
|
|
314
|
+
}
|
|
464
315
|
}
|
|
465
316
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
317
|
+
@media (max-width: 1000px) {
|
|
318
|
+
#toolbar {
|
|
319
|
+
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.brand {
|
|
323
|
+
grid-column: 1 / -1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#project {
|
|
327
|
+
grid-column: 1 / 10;
|
|
328
|
+
|
|
329
|
+
width: 100%;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#reset {
|
|
333
|
+
grid-column: 10 / 13;
|
|
334
|
+
|
|
335
|
+
width: 100%;
|
|
336
|
+
|
|
337
|
+
padding-left: 8px;
|
|
338
|
+
|
|
339
|
+
padding-right: 8px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#search {
|
|
343
|
+
grid-column: 1 / -1;
|
|
344
|
+
|
|
345
|
+
width: 100%;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#cluster {
|
|
349
|
+
grid-column: 1 / 7;
|
|
350
|
+
|
|
351
|
+
width: 100%;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#edge {
|
|
355
|
+
grid-column: 7 / 13;
|
|
356
|
+
|
|
357
|
+
width: 100%;
|
|
358
|
+
}
|
|
469
359
|
}
|
|
470
360
|
|
|
471
|
-
|
|
472
|
-
grid
|
|
473
|
-
1 / 10;
|
|
361
|
+
.intel-summary {
|
|
362
|
+
display: grid;
|
|
474
363
|
|
|
475
|
-
|
|
476
|
-
|
|
364
|
+
grid-template-columns: 1fr 1fr;
|
|
365
|
+
|
|
366
|
+
gap: 8px;
|
|
367
|
+
|
|
368
|
+
margin-top: 18px;
|
|
477
369
|
}
|
|
478
370
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
10 / 13;
|
|
371
|
+
.intel-card {
|
|
372
|
+
padding: 12px;
|
|
482
373
|
|
|
483
|
-
|
|
484
|
-
100%;
|
|
374
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
485
375
|
|
|
486
|
-
|
|
487
|
-
8px;
|
|
376
|
+
border-radius: 12px;
|
|
488
377
|
|
|
489
|
-
|
|
490
|
-
8px;
|
|
378
|
+
background: rgba(255, 255, 255, 0.035);
|
|
491
379
|
}
|
|
492
380
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
381
|
+
.intel-card strong {
|
|
382
|
+
display: block;
|
|
383
|
+
|
|
384
|
+
font-size: 18px;
|
|
496
385
|
|
|
497
|
-
|
|
498
|
-
100%;
|
|
386
|
+
margin-top: 3px;
|
|
499
387
|
}
|
|
500
388
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
1 / 7;
|
|
389
|
+
.intel-card span {
|
|
390
|
+
opacity: 0.55;
|
|
504
391
|
|
|
505
|
-
|
|
506
|
-
100%;
|
|
392
|
+
font-size: 11px;
|
|
507
393
|
}
|
|
508
394
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
7 / 13;
|
|
395
|
+
.relation-list {
|
|
396
|
+
display: flex;
|
|
512
397
|
|
|
513
|
-
|
|
514
|
-
|
|
398
|
+
flex-direction: column;
|
|
399
|
+
|
|
400
|
+
gap: 6px;
|
|
515
401
|
}
|
|
516
|
-
}
|
|
517
402
|
|
|
403
|
+
.relation-item {
|
|
404
|
+
width: 100%;
|
|
518
405
|
|
|
519
|
-
|
|
520
|
-
display:
|
|
521
|
-
grid;
|
|
406
|
+
height: auto;
|
|
522
407
|
|
|
523
|
-
|
|
524
|
-
1fr 1fr;
|
|
408
|
+
min-height: 44px;
|
|
525
409
|
|
|
526
|
-
|
|
527
|
-
8px;
|
|
410
|
+
padding: 9px 11px;
|
|
528
411
|
|
|
529
|
-
|
|
530
|
-
18px;
|
|
531
|
-
}
|
|
412
|
+
display: block;
|
|
532
413
|
|
|
533
|
-
|
|
534
|
-
padding:
|
|
535
|
-
12px;
|
|
414
|
+
text-align: left;
|
|
536
415
|
|
|
537
|
-
|
|
538
|
-
1px solid
|
|
539
|
-
rgba(255,255,255,.08);
|
|
416
|
+
border-radius: 10px;
|
|
540
417
|
|
|
541
|
-
|
|
542
|
-
|
|
418
|
+
cursor: pointer;
|
|
419
|
+
}
|
|
543
420
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
421
|
+
.relation-name {
|
|
422
|
+
display: block;
|
|
547
423
|
|
|
548
|
-
|
|
549
|
-
display:
|
|
550
|
-
block;
|
|
424
|
+
font-size: 12px;
|
|
551
425
|
|
|
552
|
-
|
|
553
|
-
18px;
|
|
426
|
+
font-weight: 650;
|
|
554
427
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
428
|
+
overflow-wrap: anywhere;
|
|
429
|
+
}
|
|
558
430
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
.55;
|
|
431
|
+
.relation-meta {
|
|
432
|
+
display: block;
|
|
562
433
|
|
|
563
|
-
|
|
564
|
-
11px;
|
|
565
|
-
}
|
|
434
|
+
margin-top: 4px;
|
|
566
435
|
|
|
567
|
-
|
|
568
|
-
display:
|
|
569
|
-
flex;
|
|
436
|
+
opacity: 0.45;
|
|
570
437
|
|
|
571
|
-
|
|
572
|
-
column;
|
|
438
|
+
font-size: 10px;
|
|
573
439
|
|
|
574
|
-
|
|
575
|
-
6px;
|
|
576
|
-
}
|
|
440
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
577
441
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
100%;
|
|
442
|
+
overflow-wrap: anywhere;
|
|
443
|
+
}
|
|
581
444
|
|
|
582
|
-
|
|
583
|
-
|
|
445
|
+
.relation-more {
|
|
446
|
+
margin-top: 7px;
|
|
584
447
|
|
|
585
|
-
|
|
586
|
-
44px;
|
|
448
|
+
opacity: 0.45;
|
|
587
449
|
|
|
588
|
-
|
|
589
|
-
|
|
450
|
+
font-size: 11px;
|
|
451
|
+
}
|
|
590
452
|
|
|
591
|
-
|
|
592
|
-
|
|
453
|
+
.risk {
|
|
454
|
+
display: inline-flex;
|
|
593
455
|
|
|
594
|
-
|
|
595
|
-
left;
|
|
456
|
+
padding: 5px 9px;
|
|
596
457
|
|
|
597
|
-
|
|
598
|
-
10px;
|
|
458
|
+
margin-top: 8px;
|
|
599
459
|
|
|
600
|
-
|
|
601
|
-
pointer;
|
|
602
|
-
}
|
|
460
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
603
461
|
|
|
604
|
-
|
|
605
|
-
display:
|
|
606
|
-
block;
|
|
462
|
+
border-radius: 999px;
|
|
607
463
|
|
|
608
|
-
|
|
609
|
-
12px;
|
|
464
|
+
font-size: 11px;
|
|
610
465
|
|
|
611
|
-
|
|
612
|
-
|
|
466
|
+
font-weight: 700;
|
|
467
|
+
}
|
|
613
468
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
469
|
+
.path-actions {
|
|
470
|
+
display: flex;
|
|
617
471
|
|
|
618
|
-
|
|
619
|
-
display:
|
|
620
|
-
block;
|
|
472
|
+
gap: 8px;
|
|
621
473
|
|
|
622
|
-
|
|
623
|
-
|
|
474
|
+
margin-top: 10px;
|
|
475
|
+
}
|
|
624
476
|
|
|
625
|
-
|
|
626
|
-
|
|
477
|
+
.path-actions button {
|
|
478
|
+
flex: 1;
|
|
627
479
|
|
|
628
|
-
|
|
629
|
-
10px;
|
|
480
|
+
height: auto;
|
|
630
481
|
|
|
631
|
-
|
|
632
|
-
ui-monospace,
|
|
633
|
-
SFMono-Regular,
|
|
634
|
-
Menlo,
|
|
635
|
-
monospace;
|
|
482
|
+
min-height: 40px;
|
|
636
483
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
484
|
+
white-space: normal;
|
|
485
|
+
}
|
|
640
486
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
7px;
|
|
487
|
+
.path-chain {
|
|
488
|
+
display: flex;
|
|
644
489
|
|
|
645
|
-
|
|
646
|
-
.45;
|
|
490
|
+
flex-direction: column;
|
|
647
491
|
|
|
648
|
-
|
|
649
|
-
11px;
|
|
650
|
-
}
|
|
492
|
+
gap: 5px;
|
|
651
493
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
inline-flex;
|
|
494
|
+
margin-top: 9px;
|
|
495
|
+
}
|
|
655
496
|
|
|
656
|
-
|
|
657
|
-
|
|
497
|
+
.path-node {
|
|
498
|
+
height: auto;
|
|
658
499
|
|
|
659
|
-
|
|
660
|
-
8px;
|
|
500
|
+
min-height: 36px;
|
|
661
501
|
|
|
662
|
-
|
|
663
|
-
1px solid
|
|
664
|
-
rgba(255,255,255,.12);
|
|
502
|
+
padding: 7px 10px;
|
|
665
503
|
|
|
666
|
-
|
|
667
|
-
999px;
|
|
504
|
+
text-align: left;
|
|
668
505
|
|
|
669
|
-
|
|
670
|
-
|
|
506
|
+
font-size: 11px;
|
|
507
|
+
}
|
|
508
|
+
</style>
|
|
509
|
+
</head>
|
|
671
510
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
511
|
+
<body>
|
|
512
|
+
<div id="loading">Loading ToolNet projects…</div>
|
|
675
513
|
|
|
676
|
-
|
|
677
|
-
display:
|
|
678
|
-
flex;
|
|
514
|
+
<div id="graph"></div>
|
|
679
515
|
|
|
680
|
-
|
|
681
|
-
|
|
516
|
+
<div id="toolbar" class="panel">
|
|
517
|
+
<div class="brand">
|
|
518
|
+
ToolNet Memory
|
|
519
|
+
<span>/ Graph</span>
|
|
520
|
+
</div>
|
|
682
521
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
522
|
+
<select id="project" aria-label="Project">
|
|
523
|
+
<option>Loading…</option>
|
|
524
|
+
</select>
|
|
686
525
|
|
|
687
|
-
|
|
688
|
-
flex:
|
|
689
|
-
1;
|
|
526
|
+
<button id="reset">Reset</button>
|
|
690
527
|
|
|
691
|
-
|
|
692
|
-
auto;
|
|
528
|
+
<input id="search" placeholder="Search symbol or file…" />
|
|
693
529
|
|
|
694
|
-
|
|
695
|
-
|
|
530
|
+
<select id="cluster" aria-label="Subsystem">
|
|
531
|
+
<option value="">All subsystems</option>
|
|
532
|
+
</select>
|
|
696
533
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
534
|
+
<select id="edge" aria-label="Relation">
|
|
535
|
+
<option value="">All relations</option>
|
|
536
|
+
</select>
|
|
537
|
+
</div>
|
|
700
538
|
|
|
701
|
-
|
|
702
|
-
display:
|
|
703
|
-
flex;
|
|
539
|
+
<div id="stats" class="panel"></div>
|
|
704
540
|
|
|
705
|
-
|
|
706
|
-
column;
|
|
541
|
+
<div id="details" class="panel"></div>
|
|
707
542
|
|
|
708
|
-
|
|
709
|
-
5px;
|
|
543
|
+
<script src="/vendor/3d-force-graph.min.js"></script>
|
|
710
544
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
545
|
+
<script>
|
|
546
|
+
const GRAPH_TOKEN_SESSION_KEY = 'toolnet.graph.access-token';
|
|
547
|
+
function readGraphToken() {
|
|
548
|
+
try {
|
|
549
|
+
return sessionStorage.getItem(GRAPH_TOKEN_SESSION_KEY) || '';
|
|
550
|
+
} catch {
|
|
551
|
+
return '';
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function writeGraphToken(value) {
|
|
555
|
+
try {
|
|
556
|
+
if (value) {
|
|
557
|
+
sessionStorage.setItem(GRAPH_TOKEN_SESSION_KEY, value);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
sessionStorage.removeItem(GRAPH_TOKEN_SESSION_KEY);
|
|
561
|
+
} catch {
|
|
562
|
+
// Session storage is optional.
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function graphApiRequest(input, init, token) {
|
|
566
|
+
const headers = new Headers(init?.headers);
|
|
567
|
+
if (token) {
|
|
568
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
569
|
+
}
|
|
570
|
+
return window.fetch(input, {
|
|
571
|
+
...init,
|
|
572
|
+
headers,
|
|
573
|
+
cache: 'no-store',
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
async function graphApiFetch(input, init = {}) {
|
|
577
|
+
let token = readGraphToken();
|
|
578
|
+
let response = await graphApiRequest(input, init, token);
|
|
579
|
+
if (response.status !== 401) {
|
|
580
|
+
return response;
|
|
581
|
+
}
|
|
582
|
+
// Existing token is invalid/stale.
|
|
583
|
+
writeGraphToken('');
|
|
584
|
+
token = window.prompt('ToolNet Graph access token')?.trim() || '';
|
|
585
|
+
if (!token) {
|
|
586
|
+
return response;
|
|
587
|
+
}
|
|
588
|
+
writeGraphToken(token);
|
|
589
|
+
response = await graphApiRequest(input, init, token);
|
|
590
|
+
if (response.status === 401) {
|
|
591
|
+
writeGraphToken('');
|
|
592
|
+
}
|
|
593
|
+
return response;
|
|
594
|
+
}
|
|
595
|
+
const state = {
|
|
596
|
+
raw: null,
|
|
714
597
|
|
|
715
|
-
|
|
716
|
-
height:
|
|
717
|
-
auto;
|
|
598
|
+
graph: null,
|
|
718
599
|
|
|
719
|
-
|
|
720
|
-
36px;
|
|
600
|
+
project: '',
|
|
721
601
|
|
|
722
|
-
|
|
723
|
-
7px 10px;
|
|
602
|
+
search: '',
|
|
724
603
|
|
|
725
|
-
|
|
726
|
-
left;
|
|
604
|
+
cluster: '',
|
|
727
605
|
|
|
728
|
-
|
|
729
|
-
11px;
|
|
730
|
-
}
|
|
606
|
+
edge: '',
|
|
731
607
|
|
|
732
|
-
|
|
733
|
-
</head>
|
|
608
|
+
selected: null,
|
|
734
609
|
|
|
735
|
-
|
|
736
|
-
<div id="loading">
|
|
737
|
-
Loading ToolNet projects…
|
|
738
|
-
</div>
|
|
610
|
+
pathStart: null,
|
|
739
611
|
|
|
740
|
-
|
|
612
|
+
highlightedPath: [],
|
|
741
613
|
|
|
742
|
-
|
|
743
|
-
id="toolbar"
|
|
744
|
-
class="panel"
|
|
745
|
-
>
|
|
746
|
-
<div class="brand">
|
|
747
|
-
ToolNet Memory
|
|
748
|
-
<span>/ Graph</span>
|
|
749
|
-
</div>
|
|
614
|
+
viewMode: 'overview',
|
|
750
615
|
|
|
751
|
-
|
|
752
|
-
id="project"
|
|
753
|
-
aria-label="Project"
|
|
754
|
-
>
|
|
755
|
-
<option>
|
|
756
|
-
Loading…
|
|
757
|
-
</option>
|
|
758
|
-
</select>
|
|
759
|
-
|
|
760
|
-
<button id="reset">
|
|
761
|
-
Reset
|
|
762
|
-
</button>
|
|
763
|
-
|
|
764
|
-
<input
|
|
765
|
-
id="search"
|
|
766
|
-
placeholder="Search symbol or file…"
|
|
767
|
-
/>
|
|
768
|
-
|
|
769
|
-
<select
|
|
770
|
-
id="cluster"
|
|
771
|
-
aria-label="Subsystem"
|
|
772
|
-
>
|
|
773
|
-
<option value="">
|
|
774
|
-
All subsystems
|
|
775
|
-
</option>
|
|
776
|
-
</select>
|
|
777
|
-
|
|
778
|
-
<select
|
|
779
|
-
id="edge"
|
|
780
|
-
aria-label="Relation"
|
|
781
|
-
>
|
|
782
|
-
<option value="">
|
|
783
|
-
All relations
|
|
784
|
-
</option>
|
|
785
|
-
</select>
|
|
786
|
-
|
|
787
|
-
</div>
|
|
788
|
-
|
|
789
|
-
<div
|
|
790
|
-
id="stats"
|
|
791
|
-
class="panel"
|
|
792
|
-
></div>
|
|
793
|
-
|
|
794
|
-
<div
|
|
795
|
-
id="details"
|
|
796
|
-
class="panel"
|
|
797
|
-
></div>
|
|
798
|
-
|
|
799
|
-
<script src="/vendor/3d-force-graph.min.js"></script>
|
|
800
|
-
|
|
801
|
-
<script>
|
|
802
|
-
const state = {
|
|
803
|
-
raw:
|
|
804
|
-
null,
|
|
805
|
-
|
|
806
|
-
graph:
|
|
807
|
-
null,
|
|
808
|
-
|
|
809
|
-
project:
|
|
810
|
-
"",
|
|
811
|
-
|
|
812
|
-
search:
|
|
813
|
-
"",
|
|
814
|
-
|
|
815
|
-
cluster:
|
|
816
|
-
"",
|
|
817
|
-
|
|
818
|
-
edge:
|
|
819
|
-
"",
|
|
820
|
-
|
|
821
|
-
selected:
|
|
822
|
-
null,
|
|
823
|
-
|
|
824
|
-
pathStart:
|
|
825
|
-
null,
|
|
826
|
-
|
|
827
|
-
highlightedPath:
|
|
828
|
-
[],
|
|
829
|
-
|
|
830
|
-
viewMode:
|
|
831
|
-
"overview",
|
|
832
|
-
|
|
833
|
-
viewCluster:
|
|
834
|
-
"",
|
|
835
|
-
|
|
836
|
-
viewFile:
|
|
837
|
-
"",
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
const graphEl =
|
|
841
|
-
document.getElementById(
|
|
842
|
-
"graph"
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
const projectEl =
|
|
846
|
-
document.getElementById(
|
|
847
|
-
"project"
|
|
848
|
-
);
|
|
849
|
-
|
|
850
|
-
const searchEl =
|
|
851
|
-
document.getElementById(
|
|
852
|
-
"search"
|
|
853
|
-
);
|
|
854
|
-
|
|
855
|
-
const clusterEl =
|
|
856
|
-
document.getElementById(
|
|
857
|
-
"cluster"
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
const edgeEl =
|
|
861
|
-
document.getElementById(
|
|
862
|
-
"edge"
|
|
863
|
-
);
|
|
864
|
-
|
|
865
|
-
const statsEl =
|
|
866
|
-
document.getElementById(
|
|
867
|
-
"stats"
|
|
868
|
-
);
|
|
869
|
-
|
|
870
|
-
const detailsEl =
|
|
871
|
-
document.getElementById(
|
|
872
|
-
"details"
|
|
873
|
-
);
|
|
874
|
-
|
|
875
|
-
const colors = [
|
|
876
|
-
"#62a8ff",
|
|
877
|
-
"#ff8f6b",
|
|
878
|
-
"#67d49b",
|
|
879
|
-
"#c493ff",
|
|
880
|
-
"#ffd166",
|
|
881
|
-
"#73d2de",
|
|
882
|
-
"#ff6b9a",
|
|
883
|
-
"#9fd356",
|
|
884
|
-
"#e5989b",
|
|
885
|
-
"#8ecae6",
|
|
886
|
-
"#c9cba3",
|
|
887
|
-
"#f4a261",
|
|
888
|
-
];
|
|
889
|
-
|
|
890
|
-
function escapeHtml(
|
|
891
|
-
value
|
|
892
|
-
) {
|
|
893
|
-
return String(
|
|
894
|
-
value ?? ""
|
|
895
|
-
)
|
|
896
|
-
.replaceAll(
|
|
897
|
-
"&",
|
|
898
|
-
"&"
|
|
899
|
-
)
|
|
900
|
-
.replaceAll(
|
|
901
|
-
"<",
|
|
902
|
-
"<"
|
|
903
|
-
)
|
|
904
|
-
.replaceAll(
|
|
905
|
-
">",
|
|
906
|
-
">"
|
|
907
|
-
)
|
|
908
|
-
.replaceAll(
|
|
909
|
-
'"',
|
|
910
|
-
"""
|
|
911
|
-
)
|
|
912
|
-
.replaceAll(
|
|
913
|
-
"'",
|
|
914
|
-
"'"
|
|
915
|
-
);
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function hashCode(
|
|
919
|
-
value
|
|
920
|
-
) {
|
|
921
|
-
let hash =
|
|
922
|
-
0;
|
|
923
|
-
|
|
924
|
-
for (
|
|
925
|
-
let i = 0;
|
|
926
|
-
i < value.length;
|
|
927
|
-
i++
|
|
928
|
-
) {
|
|
929
|
-
hash =
|
|
930
|
-
(
|
|
931
|
-
(
|
|
932
|
-
hash << 5
|
|
933
|
-
) -
|
|
934
|
-
hash
|
|
935
|
-
) +
|
|
936
|
-
value.charCodeAt(
|
|
937
|
-
i
|
|
938
|
-
);
|
|
616
|
+
viewCluster: '',
|
|
939
617
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
618
|
+
viewFile: '',
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const graphEl = document.getElementById('graph');
|
|
622
|
+
|
|
623
|
+
const projectEl = document.getElementById('project');
|
|
624
|
+
|
|
625
|
+
const searchEl = document.getElementById('search');
|
|
626
|
+
|
|
627
|
+
const clusterEl = document.getElementById('cluster');
|
|
628
|
+
|
|
629
|
+
const edgeEl = document.getElementById('edge');
|
|
630
|
+
|
|
631
|
+
const statsEl = document.getElementById('stats');
|
|
632
|
+
|
|
633
|
+
const detailsEl = document.getElementById('details');
|
|
634
|
+
|
|
635
|
+
const colors = [
|
|
636
|
+
'#62a8ff',
|
|
637
|
+
'#ff8f6b',
|
|
638
|
+
'#67d49b',
|
|
639
|
+
'#c493ff',
|
|
640
|
+
'#ffd166',
|
|
641
|
+
'#73d2de',
|
|
642
|
+
'#ff6b9a',
|
|
643
|
+
'#9fd356',
|
|
644
|
+
'#e5989b',
|
|
645
|
+
'#8ecae6',
|
|
646
|
+
'#c9cba3',
|
|
647
|
+
'#f4a261',
|
|
964
648
|
];
|
|
965
|
-
}
|
|
966
649
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
"
|
|
650
|
+
function escapeHtml(value) {
|
|
651
|
+
return String(value ?? '')
|
|
652
|
+
.replaceAll('&', '&')
|
|
653
|
+
.replaceAll('<', '<')
|
|
654
|
+
.replaceAll('>', '>')
|
|
655
|
+
.replaceAll('"', '"')
|
|
656
|
+
.replaceAll("'", ''');
|
|
657
|
+
}
|
|
973
658
|
|
|
974
|
-
|
|
975
|
-
|
|
659
|
+
function hashCode(value) {
|
|
660
|
+
let hash = 0;
|
|
976
661
|
|
|
977
|
-
|
|
978
|
-
|
|
662
|
+
for (let i = 0; i < value.length; i++) {
|
|
663
|
+
hash = (hash << 5) - hash + value.charCodeAt(i);
|
|
979
664
|
|
|
980
|
-
|
|
981
|
-
|
|
665
|
+
hash |= 0;
|
|
666
|
+
}
|
|
982
667
|
|
|
983
|
-
|
|
984
|
-
|
|
668
|
+
return Math.abs(hash);
|
|
669
|
+
}
|
|
985
670
|
|
|
986
|
-
|
|
987
|
-
|
|
671
|
+
function nodeColor(node) {
|
|
672
|
+
const value = node.clusterLabel || node.cluster || node.layer || node.type || 'other';
|
|
988
673
|
|
|
989
|
-
|
|
990
|
-
|
|
674
|
+
return colors[hashCode(value) % colors.length];
|
|
675
|
+
}
|
|
991
676
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
677
|
+
function linkColor(link) {
|
|
678
|
+
const map = {
|
|
679
|
+
CALL_REFERENCE: 'rgba(255,190,90,.72)',
|
|
995
680
|
|
|
996
|
-
|
|
997
|
-
map[link.type] ||
|
|
998
|
-
"rgba(255,255,255,.18)"
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
681
|
+
CALLS: 'rgba(255,255,255,.30)',
|
|
1001
682
|
|
|
1002
|
-
|
|
1003
|
-
state.selected =
|
|
1004
|
-
null;
|
|
683
|
+
IMPORTS: 'rgba(100,170,255,.46)',
|
|
1005
684
|
|
|
1006
|
-
|
|
1007
|
-
.classList
|
|
1008
|
-
.remove(
|
|
1009
|
-
"open"
|
|
1010
|
-
);
|
|
685
|
+
USES_TYPE: 'rgba(190,130,255,.44)',
|
|
1011
686
|
|
|
1012
|
-
|
|
1013
|
-
"";
|
|
1014
|
-
}
|
|
687
|
+
WRITES: 'rgba(255,100,100,.60)',
|
|
1015
688
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
689
|
+
TESTS: 'rgba(100,220,150,.42)',
|
|
690
|
+
|
|
691
|
+
IMPLEMENTS: 'rgba(100,220,220,.48)',
|
|
692
|
+
|
|
693
|
+
INHERITS: 'rgba(235,140,255,.50)',
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
return map[link.type] || 'rgba(255,255,255,.18)';
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function closeDetails() {
|
|
700
|
+
state.selected = null;
|
|
1021
701
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
702
|
+
detailsEl.classList.remove('open');
|
|
703
|
+
|
|
704
|
+
detailsEl.innerHTML = '';
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const CALL_RELATIONS = new Set(['CALLS', 'CALL_REFERENCE']);
|
|
708
|
+
|
|
709
|
+
const DEPENDENCY_RELATIONS = new Set([
|
|
710
|
+
'CALLS',
|
|
711
|
+
'CALL_REFERENCE',
|
|
712
|
+
'IMPORTS',
|
|
713
|
+
'USES_TYPE',
|
|
714
|
+
'WRITES',
|
|
715
|
+
'INHERITS',
|
|
716
|
+
'IMPLEMENTS',
|
|
717
|
+
'ROUTE',
|
|
1032
718
|
]);
|
|
1033
719
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const value =
|
|
1052
|
-
link[side];
|
|
1053
|
-
|
|
1054
|
-
return typeof value ===
|
|
1055
|
-
"object"
|
|
1056
|
-
? value.id
|
|
1057
|
-
: value;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function rawNode(
|
|
1061
|
-
id
|
|
1062
|
-
) {
|
|
1063
|
-
return state.raw
|
|
1064
|
-
?.nodes
|
|
1065
|
-
?.find(
|
|
1066
|
-
node =>
|
|
1067
|
-
node.id === id
|
|
1068
|
-
);
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
function uniqueNodes(
|
|
1072
|
-
values
|
|
1073
|
-
) {
|
|
1074
|
-
const map =
|
|
1075
|
-
new Map();
|
|
1076
|
-
|
|
1077
|
-
for (
|
|
1078
|
-
const node
|
|
1079
|
-
of values
|
|
1080
|
-
) {
|
|
1081
|
-
if (
|
|
1082
|
-
node &&
|
|
1083
|
-
!map.has(
|
|
1084
|
-
node.id
|
|
1085
|
-
)
|
|
1086
|
-
) {
|
|
1087
|
-
map.set(
|
|
1088
|
-
node.id,
|
|
1089
|
-
node
|
|
1090
|
-
);
|
|
1091
|
-
}
|
|
720
|
+
const IMPACT_WEIGHTS = {
|
|
721
|
+
CALLS: 4,
|
|
722
|
+
CALL_REFERENCE: 6,
|
|
723
|
+
USES_TYPE: 4,
|
|
724
|
+
TESTS: 2,
|
|
725
|
+
ROUTE: 4,
|
|
726
|
+
WRITES: 3,
|
|
727
|
+
IMPORTS: 3,
|
|
728
|
+
INHERITS: 5,
|
|
729
|
+
IMPLEMENTS: 5,
|
|
730
|
+
DEFINES: 1,
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
function endpointId(link, side) {
|
|
734
|
+
const value = link[side];
|
|
735
|
+
|
|
736
|
+
return typeof value === 'object' ? value.id : value;
|
|
1092
737
|
}
|
|
1093
738
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
[];
|
|
1106
|
-
|
|
1107
|
-
for (
|
|
1108
|
-
const link
|
|
1109
|
-
of state.raw.links
|
|
1110
|
-
) {
|
|
1111
|
-
if (
|
|
1112
|
-
allowed &&
|
|
1113
|
-
!allowed.has(
|
|
1114
|
-
link.type
|
|
1115
|
-
)
|
|
1116
|
-
) {
|
|
1117
|
-
continue;
|
|
739
|
+
function rawNode(id) {
|
|
740
|
+
return state.raw?.nodes?.find((node) => node.id === id);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function uniqueNodes(values) {
|
|
744
|
+
const map = new Map();
|
|
745
|
+
|
|
746
|
+
for (const node of values) {
|
|
747
|
+
if (node && !map.has(node.id)) {
|
|
748
|
+
map.set(node.id, node);
|
|
749
|
+
}
|
|
1118
750
|
}
|
|
1119
751
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
link,
|
|
1123
|
-
"source"
|
|
1124
|
-
);
|
|
752
|
+
return [...map.values()];
|
|
753
|
+
}
|
|
1125
754
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
link,
|
|
1129
|
-
"target"
|
|
1130
|
-
);
|
|
755
|
+
function relatedNodes(nodeId, direction, allowed) {
|
|
756
|
+
const result = [];
|
|
1131
757
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
target ===
|
|
1136
|
-
nodeId
|
|
1137
|
-
) {
|
|
1138
|
-
const node =
|
|
1139
|
-
rawNode(
|
|
1140
|
-
source
|
|
1141
|
-
);
|
|
1142
|
-
|
|
1143
|
-
if (node) {
|
|
1144
|
-
result.push(
|
|
1145
|
-
node
|
|
1146
|
-
);
|
|
758
|
+
for (const link of state.raw.links) {
|
|
759
|
+
if (allowed && !allowed.has(link.type)) {
|
|
760
|
+
continue;
|
|
1147
761
|
}
|
|
1148
|
-
}
|
|
1149
762
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
763
|
+
const source = endpointId(link, 'source');
|
|
764
|
+
|
|
765
|
+
const target = endpointId(link, 'target');
|
|
766
|
+
|
|
767
|
+
if (direction === 'incoming' && target === nodeId) {
|
|
768
|
+
const node = rawNode(source);
|
|
769
|
+
|
|
770
|
+
if (node) {
|
|
771
|
+
result.push(node);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (direction === 'outgoing' && source === nodeId) {
|
|
776
|
+
const node = rawNode(target);
|
|
777
|
+
|
|
778
|
+
if (node) {
|
|
779
|
+
result.push(node);
|
|
780
|
+
}
|
|
1165
781
|
}
|
|
1166
782
|
}
|
|
1167
|
-
}
|
|
1168
783
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
784
|
+
return uniqueNodes(result);
|
|
785
|
+
}
|
|
1173
786
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
maxDepth = 3
|
|
1177
|
-
) {
|
|
1178
|
-
const visited =
|
|
1179
|
-
new Map();
|
|
787
|
+
function blastRadius(startId, maxDepth = 3) {
|
|
788
|
+
const visited = new Map();
|
|
1180
789
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
startId,
|
|
790
|
+
const queue = [
|
|
791
|
+
{
|
|
792
|
+
id: startId,
|
|
1185
793
|
|
|
1186
|
-
|
|
1187
|
-
0,
|
|
794
|
+
depth: 0,
|
|
1188
795
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
];
|
|
796
|
+
score: 0,
|
|
797
|
+
},
|
|
798
|
+
];
|
|
1193
799
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
{
|
|
1197
|
-
depth:
|
|
1198
|
-
0,
|
|
800
|
+
visited.set(startId, {
|
|
801
|
+
depth: 0,
|
|
1199
802
|
|
|
1200
|
-
score:
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1203
|
-
);
|
|
1204
|
-
|
|
1205
|
-
while (
|
|
1206
|
-
queue.length
|
|
1207
|
-
) {
|
|
1208
|
-
const current =
|
|
1209
|
-
queue.shift();
|
|
1210
|
-
|
|
1211
|
-
if (
|
|
1212
|
-
current.depth >=
|
|
1213
|
-
maxDepth
|
|
1214
|
-
) {
|
|
1215
|
-
continue;
|
|
1216
|
-
}
|
|
803
|
+
score: 0,
|
|
804
|
+
});
|
|
1217
805
|
|
|
1218
|
-
|
|
1219
|
-
const
|
|
1220
|
-
of state.raw.links
|
|
1221
|
-
) {
|
|
1222
|
-
const weight =
|
|
1223
|
-
IMPACT_WEIGHTS[
|
|
1224
|
-
link.type
|
|
1225
|
-
];
|
|
806
|
+
while (queue.length) {
|
|
807
|
+
const current = queue.shift();
|
|
1226
808
|
|
|
1227
|
-
if (
|
|
809
|
+
if (current.depth >= maxDepth) {
|
|
1228
810
|
continue;
|
|
1229
811
|
}
|
|
1230
812
|
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
link,
|
|
1234
|
-
"source"
|
|
1235
|
-
);
|
|
1236
|
-
|
|
1237
|
-
const target =
|
|
1238
|
-
endpointId(
|
|
1239
|
-
link,
|
|
1240
|
-
"target"
|
|
1241
|
-
);
|
|
1242
|
-
|
|
1243
|
-
/*
|
|
1244
|
-
* Reverse traversal:
|
|
1245
|
-
* if source depends on target,
|
|
1246
|
-
* changing target may affect source.
|
|
1247
|
-
*/
|
|
1248
|
-
if (
|
|
1249
|
-
target !==
|
|
1250
|
-
current.id
|
|
1251
|
-
) {
|
|
1252
|
-
continue;
|
|
1253
|
-
}
|
|
813
|
+
for (const link of state.raw.links) {
|
|
814
|
+
const weight = IMPACT_WEIGHTS[link.type];
|
|
1254
815
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
const nextScore =
|
|
1260
|
-
current.score +
|
|
1261
|
-
weight *
|
|
1262
|
-
(
|
|
1263
|
-
maxDepth -
|
|
1264
|
-
current.depth
|
|
1265
|
-
);
|
|
1266
|
-
|
|
1267
|
-
const previous =
|
|
1268
|
-
visited.get(
|
|
1269
|
-
source
|
|
1270
|
-
);
|
|
1271
|
-
|
|
1272
|
-
if (
|
|
1273
|
-
!previous ||
|
|
1274
|
-
nextDepth <
|
|
1275
|
-
previous.depth ||
|
|
1276
|
-
nextScore >
|
|
1277
|
-
previous.score
|
|
1278
|
-
) {
|
|
1279
|
-
visited.set(
|
|
1280
|
-
source,
|
|
1281
|
-
{
|
|
1282
|
-
depth:
|
|
1283
|
-
nextDepth,
|
|
1284
|
-
|
|
1285
|
-
score:
|
|
1286
|
-
nextScore,
|
|
1287
|
-
}
|
|
1288
|
-
);
|
|
816
|
+
if (!weight) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
1289
819
|
|
|
1290
|
-
|
|
1291
|
-
id:
|
|
1292
|
-
source,
|
|
820
|
+
const source = endpointId(link, 'source');
|
|
1293
821
|
|
|
1294
|
-
|
|
1295
|
-
nextDepth,
|
|
822
|
+
const target = endpointId(link, 'target');
|
|
1296
823
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
824
|
+
/*
|
|
825
|
+
* Reverse traversal:
|
|
826
|
+
* if source depends on target,
|
|
827
|
+
* changing target may affect source.
|
|
828
|
+
*/
|
|
829
|
+
if (target !== current.id) {
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const nextDepth = current.depth + 1;
|
|
834
|
+
|
|
835
|
+
const nextScore = current.score + weight * (maxDepth - current.depth);
|
|
836
|
+
|
|
837
|
+
const previous = visited.get(source);
|
|
838
|
+
|
|
839
|
+
if (!previous || nextDepth < previous.depth || nextScore > previous.score) {
|
|
840
|
+
visited.set(source, {
|
|
841
|
+
depth: nextDepth,
|
|
842
|
+
|
|
843
|
+
score: nextScore,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
queue.push({
|
|
847
|
+
id: source,
|
|
848
|
+
|
|
849
|
+
depth: nextDepth,
|
|
850
|
+
|
|
851
|
+
score: nextScore,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
1300
854
|
}
|
|
1301
855
|
}
|
|
1302
|
-
}
|
|
1303
856
|
|
|
1304
|
-
|
|
1305
|
-
startId
|
|
1306
|
-
);
|
|
857
|
+
visited.delete(startId);
|
|
1307
858
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
]
|
|
1312
|
-
.map(
|
|
1313
|
-
([id, info]) => ({
|
|
1314
|
-
node:
|
|
1315
|
-
rawNode(
|
|
1316
|
-
id
|
|
1317
|
-
),
|
|
859
|
+
const impacted = [...visited.entries()]
|
|
860
|
+
.map(([id, info]) => ({
|
|
861
|
+
node: rawNode(id),
|
|
1318
862
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
)
|
|
1322
|
-
.
|
|
1323
|
-
item =>
|
|
1324
|
-
item.node
|
|
1325
|
-
)
|
|
1326
|
-
.sort(
|
|
1327
|
-
(a, b) =>
|
|
1328
|
-
b.score -
|
|
1329
|
-
a.score
|
|
1330
|
-
);
|
|
1331
|
-
|
|
1332
|
-
const score =
|
|
1333
|
-
impacted.reduce(
|
|
1334
|
-
(
|
|
1335
|
-
total,
|
|
1336
|
-
item
|
|
1337
|
-
) =>
|
|
1338
|
-
total +
|
|
1339
|
-
item.score,
|
|
1340
|
-
0
|
|
1341
|
-
);
|
|
863
|
+
...info,
|
|
864
|
+
}))
|
|
865
|
+
.filter((item) => item.node)
|
|
866
|
+
.sort((a, b) => b.score - a.score);
|
|
1342
867
|
|
|
1343
|
-
|
|
1344
|
-
"LOW";
|
|
1345
|
-
|
|
1346
|
-
if (
|
|
1347
|
-
score >= 250 ||
|
|
1348
|
-
impacted.length >= 40
|
|
1349
|
-
) {
|
|
1350
|
-
risk =
|
|
1351
|
-
"CRITICAL";
|
|
1352
|
-
} else if (
|
|
1353
|
-
score >= 120 ||
|
|
1354
|
-
impacted.length >= 20
|
|
1355
|
-
) {
|
|
1356
|
-
risk =
|
|
1357
|
-
"HIGH";
|
|
1358
|
-
} else if (
|
|
1359
|
-
score >= 40 ||
|
|
1360
|
-
impacted.length >= 8
|
|
1361
|
-
) {
|
|
1362
|
-
risk =
|
|
1363
|
-
"MEDIUM";
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
return {
|
|
1367
|
-
impacted,
|
|
1368
|
-
score,
|
|
1369
|
-
risk,
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
function shortestPath(
|
|
1374
|
-
fromId,
|
|
1375
|
-
toId,
|
|
1376
|
-
maxDepth = 10
|
|
1377
|
-
) {
|
|
1378
|
-
if (
|
|
1379
|
-
fromId ===
|
|
1380
|
-
toId
|
|
1381
|
-
) {
|
|
1382
|
-
return [
|
|
1383
|
-
fromId
|
|
1384
|
-
];
|
|
1385
|
-
}
|
|
868
|
+
const score = impacted.reduce((total, item) => total + item.score, 0);
|
|
1386
869
|
|
|
1387
|
-
|
|
1388
|
-
{
|
|
1389
|
-
id:
|
|
1390
|
-
fromId,
|
|
870
|
+
let risk = 'LOW';
|
|
1391
871
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
872
|
+
if (score >= 250 || impacted.length >= 40) {
|
|
873
|
+
risk = 'CRITICAL';
|
|
874
|
+
} else if (score >= 120 || impacted.length >= 20) {
|
|
875
|
+
risk = 'HIGH';
|
|
876
|
+
} else if (score >= 40 || impacted.length >= 8) {
|
|
877
|
+
risk = 'MEDIUM';
|
|
1395
878
|
}
|
|
1396
|
-
];
|
|
1397
879
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
if (
|
|
1410
|
-
current.path.length >
|
|
1411
|
-
maxDepth
|
|
1412
|
-
) {
|
|
1413
|
-
continue;
|
|
880
|
+
return {
|
|
881
|
+
impacted,
|
|
882
|
+
score,
|
|
883
|
+
risk,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function shortestPath(fromId, toId, maxDepth = 10) {
|
|
888
|
+
if (fromId === toId) {
|
|
889
|
+
return [fromId];
|
|
1414
890
|
}
|
|
1415
891
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
) {
|
|
1420
|
-
if (
|
|
1421
|
-
!DEPENDENCY_RELATIONS
|
|
1422
|
-
.has(
|
|
1423
|
-
link.type
|
|
1424
|
-
)
|
|
1425
|
-
) {
|
|
1426
|
-
continue;
|
|
1427
|
-
}
|
|
892
|
+
const queue = [
|
|
893
|
+
{
|
|
894
|
+
id: fromId,
|
|
1428
895
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
"source"
|
|
1433
|
-
) !==
|
|
1434
|
-
current.id
|
|
1435
|
-
) {
|
|
1436
|
-
continue;
|
|
1437
|
-
}
|
|
896
|
+
path: [fromId],
|
|
897
|
+
},
|
|
898
|
+
];
|
|
1438
899
|
|
|
1439
|
-
|
|
1440
|
-
endpointId(
|
|
1441
|
-
link,
|
|
1442
|
-
"target"
|
|
1443
|
-
);
|
|
900
|
+
const visited = new Set([fromId]);
|
|
1444
901
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
) {
|
|
902
|
+
while (queue.length) {
|
|
903
|
+
const current = queue.shift();
|
|
904
|
+
|
|
905
|
+
if (current.path.length > maxDepth) {
|
|
1450
906
|
continue;
|
|
1451
907
|
}
|
|
1452
908
|
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
909
|
+
for (const link of state.raw.links) {
|
|
910
|
+
if (!DEPENDENCY_RELATIONS.has(link.type)) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
1457
913
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
) {
|
|
1462
|
-
return path;
|
|
1463
|
-
}
|
|
914
|
+
if (endpointId(link, 'source') !== current.id) {
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
1464
917
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
918
|
+
const next = endpointId(link, 'target');
|
|
919
|
+
|
|
920
|
+
if (visited.has(next)) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
1468
923
|
|
|
1469
|
-
|
|
1470
|
-
id:
|
|
1471
|
-
next,
|
|
924
|
+
const path = [...current.path, next];
|
|
1472
925
|
|
|
1473
|
-
|
|
1474
|
-
|
|
926
|
+
if (next === toId) {
|
|
927
|
+
return path;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
visited.add(next);
|
|
931
|
+
|
|
932
|
+
queue.push({
|
|
933
|
+
id: next,
|
|
934
|
+
|
|
935
|
+
path,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
1475
938
|
}
|
|
939
|
+
|
|
940
|
+
return null;
|
|
1476
941
|
}
|
|
1477
942
|
|
|
1478
|
-
|
|
1479
|
-
|
|
943
|
+
function relationHtml(title, nodes, limit = 8) {
|
|
944
|
+
const visible = nodes.slice(0, limit);
|
|
1480
945
|
|
|
1481
|
-
|
|
1482
|
-
title,
|
|
1483
|
-
nodes,
|
|
1484
|
-
limit = 8
|
|
1485
|
-
) {
|
|
1486
|
-
const visible =
|
|
1487
|
-
nodes.slice(
|
|
1488
|
-
0,
|
|
1489
|
-
limit
|
|
1490
|
-
);
|
|
1491
|
-
|
|
1492
|
-
return `
|
|
946
|
+
return `
|
|
1493
947
|
<div class="section">
|
|
1494
948
|
<div class="section-title">
|
|
1495
|
-
${escapeHtml(
|
|
1496
|
-
title
|
|
1497
|
-
)}
|
|
949
|
+
${escapeHtml(title)}
|
|
1498
950
|
· ${nodes.length}
|
|
1499
951
|
</div>
|
|
1500
952
|
|
|
@@ -1502,33 +954,24 @@
|
|
|
1502
954
|
visible.length
|
|
1503
955
|
? `
|
|
1504
956
|
<div class="relation-list">
|
|
1505
|
-
${
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
node => `
|
|
957
|
+
${visible
|
|
958
|
+
.map(
|
|
959
|
+
(node) => `
|
|
1509
960
|
<button
|
|
1510
961
|
class="relation-item"
|
|
1511
|
-
data-jump-node="${escapeHtml(
|
|
1512
|
-
node.id
|
|
1513
|
-
)}"
|
|
962
|
+
data-jump-node="${escapeHtml(node.id)}"
|
|
1514
963
|
>
|
|
1515
964
|
<span class="relation-name">
|
|
1516
|
-
${escapeHtml(
|
|
1517
|
-
node.qualifiedName ||
|
|
1518
|
-
node.name
|
|
1519
|
-
)}
|
|
965
|
+
${escapeHtml(node.qualifiedName || node.name)}
|
|
1520
966
|
</span>
|
|
1521
967
|
|
|
1522
968
|
<span class="relation-meta">
|
|
1523
|
-
${escapeHtml(
|
|
1524
|
-
node.filePath
|
|
1525
|
-
)}
|
|
969
|
+
${escapeHtml(node.filePath)}
|
|
1526
970
|
</span>
|
|
1527
971
|
</button>
|
|
1528
972
|
`
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
}
|
|
973
|
+
)
|
|
974
|
+
.join('')}
|
|
1532
975
|
</div>
|
|
1533
976
|
`
|
|
1534
977
|
: `
|
|
@@ -1539,331 +982,167 @@
|
|
|
1539
982
|
}
|
|
1540
983
|
|
|
1541
984
|
${
|
|
1542
|
-
nodes.length >
|
|
1543
|
-
limit
|
|
985
|
+
nodes.length > limit
|
|
1544
986
|
? `
|
|
1545
987
|
<div class="relation-more">
|
|
1546
|
-
+${
|
|
1547
|
-
nodes.length -
|
|
1548
|
-
limit
|
|
1549
|
-
} more
|
|
988
|
+
+${nodes.length - limit} more
|
|
1550
989
|
</div>
|
|
1551
990
|
`
|
|
1552
|
-
:
|
|
991
|
+
: ''
|
|
1553
992
|
}
|
|
1554
993
|
</div>
|
|
1555
994
|
`;
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
function resetPathHighlight() {
|
|
1559
|
-
state.highlightedPath =
|
|
1560
|
-
[];
|
|
1561
|
-
|
|
1562
|
-
if (
|
|
1563
|
-
!state.graph
|
|
1564
|
-
) {
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
state.graph
|
|
1569
|
-
.linkWidth(
|
|
1570
|
-
link =>
|
|
1571
|
-
link.type ===
|
|
1572
|
-
"CALL_REFERENCE"
|
|
1573
|
-
? 1.2
|
|
1574
|
-
: .45
|
|
1575
|
-
)
|
|
1576
|
-
.linkColor(
|
|
1577
|
-
link =>
|
|
1578
|
-
linkColor(
|
|
1579
|
-
link
|
|
1580
|
-
)
|
|
1581
|
-
);
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
function isPathLink(
|
|
1585
|
-
link,
|
|
1586
|
-
path
|
|
1587
|
-
) {
|
|
1588
|
-
const source =
|
|
1589
|
-
endpointId(
|
|
1590
|
-
link,
|
|
1591
|
-
"source"
|
|
1592
|
-
);
|
|
995
|
+
}
|
|
1593
996
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
link,
|
|
1597
|
-
"target"
|
|
1598
|
-
);
|
|
997
|
+
function resetPathHighlight() {
|
|
998
|
+
state.highlightedPath = [];
|
|
1599
999
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
i <
|
|
1603
|
-
path.length - 1;
|
|
1604
|
-
i++
|
|
1605
|
-
) {
|
|
1606
|
-
if (
|
|
1607
|
-
path[i] ===
|
|
1608
|
-
source &&
|
|
1609
|
-
path[i + 1] ===
|
|
1610
|
-
target
|
|
1611
|
-
) {
|
|
1612
|
-
return true;
|
|
1000
|
+
if (!state.graph) {
|
|
1001
|
+
return;
|
|
1613
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
state.graph
|
|
1005
|
+
.linkWidth((link) => (link.type === 'CALL_REFERENCE' ? 1.2 : 0.45))
|
|
1006
|
+
.linkColor((link) => linkColor(link));
|
|
1614
1007
|
}
|
|
1615
1008
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1009
|
+
function isPathLink(link, path) {
|
|
1010
|
+
const source = endpointId(link, 'source');
|
|
1011
|
+
|
|
1012
|
+
const target = endpointId(link, 'target');
|
|
1618
1013
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1014
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1015
|
+
if (path[i] === source && path[i + 1] === target) {
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1624
1019
|
|
|
1625
|
-
|
|
1626
|
-
!state.graph
|
|
1627
|
-
) {
|
|
1628
|
-
return;
|
|
1020
|
+
return false;
|
|
1629
1021
|
}
|
|
1630
1022
|
|
|
1631
|
-
|
|
1632
|
-
.
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1023
|
+
function highlightPath(path) {
|
|
1024
|
+
state.highlightedPath = path || [];
|
|
1025
|
+
|
|
1026
|
+
if (!state.graph) {
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
state.graph
|
|
1031
|
+
.linkWidth((link) =>
|
|
1032
|
+
isPathLink(link, state.highlightedPath)
|
|
1638
1033
|
? 3.5
|
|
1639
|
-
:
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
state.highlightedPath
|
|
1651
|
-
)
|
|
1652
|
-
? "rgba(255,225,80,.98)"
|
|
1653
|
-
: linkColor(
|
|
1654
|
-
link
|
|
1655
|
-
)
|
|
1656
|
-
);
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
function flyToNode(
|
|
1660
|
-
node
|
|
1661
|
-
) {
|
|
1662
|
-
const distance =
|
|
1663
|
-
70;
|
|
1664
|
-
|
|
1665
|
-
const length =
|
|
1666
|
-
Math.hypot(
|
|
1667
|
-
node.x || 1,
|
|
1668
|
-
node.y || 1,
|
|
1669
|
-
node.z || 1
|
|
1670
|
-
);
|
|
1034
|
+
: link.type === 'CALL_REFERENCE'
|
|
1035
|
+
? 1.2
|
|
1036
|
+
: 0.35
|
|
1037
|
+
)
|
|
1038
|
+
.linkColor((link) =>
|
|
1039
|
+
isPathLink(link, state.highlightedPath) ? 'rgba(255,225,80,.98)' : linkColor(link)
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function flyToNode(node) {
|
|
1044
|
+
const distance = 70;
|
|
1671
1045
|
|
|
1672
|
-
|
|
1673
|
-
1 +
|
|
1674
|
-
distance /
|
|
1675
|
-
length;
|
|
1046
|
+
const length = Math.hypot(node.x || 1, node.y || 1, node.z || 1);
|
|
1676
1047
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1048
|
+
const ratio = 1 + distance / length;
|
|
1049
|
+
|
|
1050
|
+
state.graph.cameraPosition(
|
|
1679
1051
|
{
|
|
1680
|
-
x:
|
|
1681
|
-
node.x *
|
|
1682
|
-
ratio,
|
|
1052
|
+
x: node.x * ratio,
|
|
1683
1053
|
|
|
1684
|
-
y:
|
|
1685
|
-
node.y *
|
|
1686
|
-
ratio,
|
|
1054
|
+
y: node.y * ratio,
|
|
1687
1055
|
|
|
1688
|
-
z:
|
|
1689
|
-
node.z *
|
|
1690
|
-
ratio,
|
|
1056
|
+
z: node.z * ratio,
|
|
1691
1057
|
},
|
|
1692
1058
|
|
|
1693
1059
|
node,
|
|
1694
1060
|
|
|
1695
1061
|
800
|
|
1696
1062
|
);
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
state.edge =
|
|
1721
|
-
"";
|
|
1722
|
-
|
|
1723
|
-
applyFilters();
|
|
1724
|
-
|
|
1725
|
-
setTimeout(
|
|
1726
|
-
() => {
|
|
1727
|
-
const node =
|
|
1728
|
-
state.graph
|
|
1729
|
-
.graphData()
|
|
1730
|
-
.nodes
|
|
1731
|
-
.find(
|
|
1732
|
-
item =>
|
|
1733
|
-
item.id ===
|
|
1734
|
-
id
|
|
1735
|
-
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function jumpToNode(id) {
|
|
1066
|
+
/*
|
|
1067
|
+
* Clear filters if destination is hidden.
|
|
1068
|
+
*/
|
|
1069
|
+
searchEl.value = '';
|
|
1070
|
+
|
|
1071
|
+
clusterEl.value = '';
|
|
1072
|
+
|
|
1073
|
+
edgeEl.value = '';
|
|
1074
|
+
|
|
1075
|
+
state.search = '';
|
|
1076
|
+
|
|
1077
|
+
state.cluster = '';
|
|
1078
|
+
|
|
1079
|
+
state.edge = '';
|
|
1080
|
+
|
|
1081
|
+
applyFilters();
|
|
1082
|
+
|
|
1083
|
+
setTimeout(() => {
|
|
1084
|
+
const node = state.graph.graphData().nodes.find((item) => item.id === id);
|
|
1736
1085
|
|
|
1737
1086
|
if (!node) {
|
|
1738
1087
|
return;
|
|
1739
1088
|
}
|
|
1740
1089
|
|
|
1741
|
-
showDetails(
|
|
1742
|
-
node
|
|
1743
|
-
);
|
|
1090
|
+
showDetails(node);
|
|
1744
1091
|
|
|
1745
|
-
flyToNode(
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
},
|
|
1749
|
-
100
|
|
1750
|
-
);
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
function showDetails(
|
|
1754
|
-
node
|
|
1755
|
-
) {
|
|
1756
|
-
if (
|
|
1757
|
-
node.viewKind ===
|
|
1758
|
-
"cluster"
|
|
1759
|
-
) {
|
|
1760
|
-
void loadProject(
|
|
1761
|
-
state.project,
|
|
1762
|
-
{
|
|
1763
|
-
mode:
|
|
1764
|
-
"files",
|
|
1092
|
+
flyToNode(node);
|
|
1093
|
+
}, 100);
|
|
1094
|
+
}
|
|
1765
1095
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
);
|
|
1096
|
+
function showDetails(node) {
|
|
1097
|
+
if (node.viewKind === 'cluster') {
|
|
1098
|
+
void loadProject(state.project, {
|
|
1099
|
+
mode: 'files',
|
|
1771
1100
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1101
|
+
cluster: node.clusterLabel || node.name,
|
|
1102
|
+
});
|
|
1774
1103
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
"file"
|
|
1778
|
-
) {
|
|
1779
|
-
void loadProject(
|
|
1780
|
-
state.project,
|
|
1781
|
-
{
|
|
1782
|
-
mode:
|
|
1783
|
-
"symbols",
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1784
1106
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
);
|
|
1107
|
+
if (node.viewKind === 'file') {
|
|
1108
|
+
void loadProject(state.project, {
|
|
1109
|
+
mode: 'symbols',
|
|
1789
1110
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1111
|
+
file: node.filePath,
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1792
1116
|
|
|
1793
|
-
|
|
1794
|
-
node;
|
|
1117
|
+
state.selected = node;
|
|
1795
1118
|
|
|
1796
|
-
|
|
1797
|
-
.classList
|
|
1798
|
-
.add(
|
|
1799
|
-
"open"
|
|
1800
|
-
);
|
|
1119
|
+
detailsEl.classList.add('open');
|
|
1801
1120
|
|
|
1802
|
-
|
|
1803
|
-
relatedNodes(
|
|
1804
|
-
node.id,
|
|
1805
|
-
"incoming",
|
|
1806
|
-
CALL_RELATIONS
|
|
1807
|
-
);
|
|
1121
|
+
const callers = relatedNodes(node.id, 'incoming', CALL_RELATIONS);
|
|
1808
1122
|
|
|
1809
|
-
|
|
1810
|
-
relatedNodes(
|
|
1811
|
-
node.id,
|
|
1812
|
-
"outgoing",
|
|
1813
|
-
CALL_RELATIONS
|
|
1814
|
-
);
|
|
1123
|
+
const callees = relatedNodes(node.id, 'outgoing', CALL_RELATIONS);
|
|
1815
1124
|
|
|
1816
|
-
|
|
1817
|
-
relatedNodes(
|
|
1818
|
-
node.id,
|
|
1819
|
-
"outgoing",
|
|
1820
|
-
DEPENDENCY_RELATIONS
|
|
1821
|
-
);
|
|
1125
|
+
const dependencies = relatedNodes(node.id, 'outgoing', DEPENDENCY_RELATIONS);
|
|
1822
1126
|
|
|
1823
|
-
|
|
1824
|
-
relatedNodes(
|
|
1825
|
-
node.id,
|
|
1826
|
-
"incoming",
|
|
1827
|
-
DEPENDENCY_RELATIONS
|
|
1828
|
-
);
|
|
1127
|
+
const dependents = relatedNodes(node.id, 'incoming', DEPENDENCY_RELATIONS);
|
|
1829
1128
|
|
|
1830
|
-
|
|
1831
|
-
blastRadius(
|
|
1832
|
-
node.id,
|
|
1833
|
-
3
|
|
1834
|
-
);
|
|
1129
|
+
const blast = blastRadius(node.id, 3);
|
|
1835
1130
|
|
|
1836
|
-
|
|
1837
|
-
null;
|
|
1838
|
-
|
|
1839
|
-
if (
|
|
1840
|
-
state.pathStart &&
|
|
1841
|
-
state.pathStart !==
|
|
1842
|
-
node.id
|
|
1843
|
-
) {
|
|
1844
|
-
path =
|
|
1845
|
-
shortestPath(
|
|
1846
|
-
state.pathStart,
|
|
1847
|
-
node.id
|
|
1848
|
-
);
|
|
1131
|
+
let path = null;
|
|
1849
1132
|
|
|
1850
|
-
if (
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
)
|
|
1854
|
-
|
|
1855
|
-
|
|
1133
|
+
if (state.pathStart && state.pathStart !== node.id) {
|
|
1134
|
+
path = shortestPath(state.pathStart, node.id);
|
|
1135
|
+
|
|
1136
|
+
if (path) {
|
|
1137
|
+
highlightPath(path);
|
|
1138
|
+
} else {
|
|
1139
|
+
resetPathHighlight();
|
|
1140
|
+
}
|
|
1856
1141
|
}
|
|
1857
|
-
}
|
|
1858
1142
|
|
|
1859
|
-
|
|
1860
|
-
state.pathStart
|
|
1861
|
-
? rawNode(
|
|
1862
|
-
state.pathStart
|
|
1863
|
-
)
|
|
1864
|
-
: null;
|
|
1143
|
+
const pathStartNode = state.pathStart ? rawNode(state.pathStart) : null;
|
|
1865
1144
|
|
|
1866
|
-
|
|
1145
|
+
detailsEl.innerHTML = `
|
|
1867
1146
|
<button
|
|
1868
1147
|
class="details-close"
|
|
1869
1148
|
id="details-close"
|
|
@@ -1874,47 +1153,36 @@
|
|
|
1874
1153
|
</button>
|
|
1875
1154
|
|
|
1876
1155
|
<div class="detail-title">
|
|
1877
|
-
${escapeHtml(
|
|
1878
|
-
node.qualifiedName ||
|
|
1879
|
-
node.name
|
|
1880
|
-
)}
|
|
1156
|
+
${escapeHtml(node.qualifiedName || node.name)}
|
|
1881
1157
|
</div>
|
|
1882
1158
|
|
|
1883
1159
|
<div class="path">
|
|
1884
|
-
${escapeHtml(
|
|
1885
|
-
node.filePath
|
|
1886
|
-
)}
|
|
1160
|
+
${escapeHtml(node.filePath)}
|
|
1887
1161
|
</div>
|
|
1888
1162
|
|
|
1889
1163
|
<div>
|
|
1890
1164
|
<span class="tag">
|
|
1891
|
-
${escapeHtml(
|
|
1892
|
-
node.type
|
|
1893
|
-
)}
|
|
1165
|
+
${escapeHtml(node.type)}
|
|
1894
1166
|
</span>
|
|
1895
1167
|
|
|
1896
1168
|
${
|
|
1897
1169
|
node.clusterLabel
|
|
1898
1170
|
? `
|
|
1899
1171
|
<span class="tag">
|
|
1900
|
-
${escapeHtml(
|
|
1901
|
-
node.clusterLabel
|
|
1902
|
-
)}
|
|
1172
|
+
${escapeHtml(node.clusterLabel)}
|
|
1903
1173
|
</span>
|
|
1904
1174
|
`
|
|
1905
|
-
:
|
|
1175
|
+
: ''
|
|
1906
1176
|
}
|
|
1907
1177
|
|
|
1908
1178
|
${
|
|
1909
1179
|
node.layer
|
|
1910
1180
|
? `
|
|
1911
1181
|
<span class="tag">
|
|
1912
|
-
${escapeHtml(
|
|
1913
|
-
node.layer
|
|
1914
|
-
)}
|
|
1182
|
+
${escapeHtml(node.layer)}
|
|
1915
1183
|
</span>
|
|
1916
1184
|
`
|
|
1917
|
-
:
|
|
1185
|
+
: ''
|
|
1918
1186
|
}
|
|
1919
1187
|
|
|
1920
1188
|
${
|
|
@@ -1922,13 +1190,10 @@
|
|
|
1922
1190
|
? `
|
|
1923
1191
|
<span class="tag">
|
|
1924
1192
|
dead-code:
|
|
1925
|
-
${escapeHtml(
|
|
1926
|
-
node.deadCode
|
|
1927
|
-
.confidence
|
|
1928
|
-
)}
|
|
1193
|
+
${escapeHtml(node.deadCode.confidence)}
|
|
1929
1194
|
</span>
|
|
1930
1195
|
`
|
|
1931
|
-
:
|
|
1196
|
+
: ''
|
|
1932
1197
|
}
|
|
1933
1198
|
</div>
|
|
1934
1199
|
|
|
@@ -1996,39 +1261,19 @@
|
|
|
1996
1261
|
${
|
|
1997
1262
|
blast.impacted.length
|
|
1998
1263
|
? relationHtml(
|
|
1999
|
-
|
|
2000
|
-
blast.impacted
|
|
2001
|
-
.slice(
|
|
2002
|
-
0,
|
|
2003
|
-
8
|
|
2004
|
-
)
|
|
2005
|
-
.map(
|
|
2006
|
-
item =>
|
|
2007
|
-
item.node
|
|
2008
|
-
)
|
|
1264
|
+
'Top Impacted',
|
|
1265
|
+
blast.impacted.slice(0, 8).map((item) => item.node)
|
|
2009
1266
|
)
|
|
2010
|
-
:
|
|
1267
|
+
: ''
|
|
2011
1268
|
}
|
|
2012
1269
|
|
|
2013
|
-
${relationHtml(
|
|
2014
|
-
"Callers",
|
|
2015
|
-
callers
|
|
2016
|
-
)}
|
|
1270
|
+
${relationHtml('Callers', callers)}
|
|
2017
1271
|
|
|
2018
|
-
${relationHtml(
|
|
2019
|
-
"Callees",
|
|
2020
|
-
callees
|
|
2021
|
-
)}
|
|
1272
|
+
${relationHtml('Callees', callees)}
|
|
2022
1273
|
|
|
2023
|
-
${relationHtml(
|
|
2024
|
-
"Dependencies",
|
|
2025
|
-
dependencies
|
|
2026
|
-
)}
|
|
1274
|
+
${relationHtml('Dependencies', dependencies)}
|
|
2027
1275
|
|
|
2028
|
-
${relationHtml(
|
|
2029
|
-
"Dependents",
|
|
2030
|
-
dependents
|
|
2031
|
-
)}
|
|
1276
|
+
${relationHtml('Dependents', dependents)}
|
|
2032
1277
|
|
|
2033
1278
|
<div class="section">
|
|
2034
1279
|
<div class="section-title">
|
|
@@ -2047,11 +1292,7 @@
|
|
|
2047
1292
|
<div class="path">
|
|
2048
1293
|
From:
|
|
2049
1294
|
${escapeHtml(
|
|
2050
|
-
pathStartNode
|
|
2051
|
-
?.qualifiedName ||
|
|
2052
|
-
pathStartNode
|
|
2053
|
-
?.name ||
|
|
2054
|
-
state.pathStart
|
|
1295
|
+
pathStartNode?.qualifiedName || pathStartNode?.name || state.pathStart
|
|
2055
1296
|
)}
|
|
2056
1297
|
</div>
|
|
2057
1298
|
`
|
|
@@ -2062,12 +1303,7 @@
|
|
|
2062
1303
|
id="path-start"
|
|
2063
1304
|
type="button"
|
|
2064
1305
|
>
|
|
2065
|
-
${
|
|
2066
|
-
state.pathStart ===
|
|
2067
|
-
node.id
|
|
2068
|
-
? "Start selected"
|
|
2069
|
-
: "Set path start"
|
|
2070
|
-
}
|
|
1306
|
+
${state.pathStart === node.id ? 'Start selected' : 'Set path start'}
|
|
2071
1307
|
</button>
|
|
2072
1308
|
|
|
2073
1309
|
${
|
|
@@ -2080,333 +1316,159 @@
|
|
|
2080
1316
|
Clear path
|
|
2081
1317
|
</button>
|
|
2082
1318
|
`
|
|
2083
|
-
:
|
|
1319
|
+
: ''
|
|
2084
1320
|
}
|
|
2085
1321
|
</div>
|
|
2086
1322
|
|
|
2087
1323
|
${
|
|
2088
|
-
state.pathStart &&
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
? (
|
|
2092
|
-
path
|
|
2093
|
-
? `
|
|
1324
|
+
state.pathStart && state.pathStart !== node.id
|
|
1325
|
+
? path
|
|
1326
|
+
? `
|
|
2094
1327
|
<div class="path-chain">
|
|
2095
|
-
${
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
id
|
|
2101
|
-
);
|
|
2102
|
-
|
|
2103
|
-
return `
|
|
1328
|
+
${path
|
|
1329
|
+
.map((id) => {
|
|
1330
|
+
const item = rawNode(id);
|
|
1331
|
+
|
|
1332
|
+
return `
|
|
2104
1333
|
<button
|
|
2105
1334
|
class="path-node"
|
|
2106
|
-
data-jump-node="${escapeHtml(
|
|
2107
|
-
id
|
|
2108
|
-
)}"
|
|
1335
|
+
data-jump-node="${escapeHtml(id)}"
|
|
2109
1336
|
>
|
|
2110
|
-
${escapeHtml(
|
|
2111
|
-
item?.qualifiedName ||
|
|
2112
|
-
item?.name ||
|
|
2113
|
-
id
|
|
2114
|
-
)}
|
|
1337
|
+
${escapeHtml(item?.qualifiedName || item?.name || id)}
|
|
2115
1338
|
</button>
|
|
2116
1339
|
`;
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
}
|
|
1340
|
+
})
|
|
1341
|
+
.join('')}
|
|
2120
1342
|
</div>
|
|
2121
1343
|
`
|
|
2122
|
-
|
|
1344
|
+
: `
|
|
2123
1345
|
<div class="relation-more">
|
|
2124
1346
|
Không tìm thấy directed path
|
|
2125
1347
|
trong 10 bước.
|
|
2126
1348
|
</div>
|
|
2127
1349
|
`
|
|
2128
|
-
|
|
2129
|
-
: ""
|
|
1350
|
+
: ''
|
|
2130
1351
|
}
|
|
2131
1352
|
</div>
|
|
2132
1353
|
`;
|
|
2133
1354
|
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
)
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
event => {
|
|
1355
|
+
document.getElementById('details-close')?.addEventListener('click', (event) => {
|
|
1356
|
+
event.stopPropagation();
|
|
1357
|
+
closeDetails();
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
detailsEl.querySelectorAll('[data-jump-node]').forEach((element) => {
|
|
1361
|
+
element.addEventListener('click', (event) => {
|
|
2141
1362
|
event.stopPropagation();
|
|
2142
|
-
closeDetails();
|
|
2143
|
-
}
|
|
2144
|
-
);
|
|
2145
1363
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
)
|
|
2150
|
-
.forEach(
|
|
2151
|
-
element => {
|
|
2152
|
-
element
|
|
2153
|
-
.addEventListener(
|
|
2154
|
-
"click",
|
|
2155
|
-
event => {
|
|
2156
|
-
event.stopPropagation();
|
|
2157
|
-
|
|
2158
|
-
jumpToNode(
|
|
2159
|
-
element.dataset
|
|
2160
|
-
.jumpNode
|
|
2161
|
-
);
|
|
2162
|
-
}
|
|
2163
|
-
);
|
|
2164
|
-
}
|
|
2165
|
-
);
|
|
1364
|
+
jumpToNode(element.dataset.jumpNode);
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
2166
1367
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
"path-start"
|
|
2170
|
-
)
|
|
2171
|
-
?.addEventListener(
|
|
2172
|
-
"click",
|
|
2173
|
-
event => {
|
|
2174
|
-
event.stopPropagation();
|
|
1368
|
+
document.getElementById('path-start')?.addEventListener('click', (event) => {
|
|
1369
|
+
event.stopPropagation();
|
|
2175
1370
|
|
|
2176
|
-
|
|
2177
|
-
node.id;
|
|
1371
|
+
state.pathStart = node.id;
|
|
2178
1372
|
|
|
2179
|
-
|
|
1373
|
+
resetPathHighlight();
|
|
2180
1374
|
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
);
|
|
2184
|
-
}
|
|
2185
|
-
);
|
|
1375
|
+
showDetails(node);
|
|
1376
|
+
});
|
|
2186
1377
|
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
"path-clear"
|
|
2190
|
-
)
|
|
2191
|
-
?.addEventListener(
|
|
2192
|
-
"click",
|
|
2193
|
-
event => {
|
|
2194
|
-
event.stopPropagation();
|
|
1378
|
+
document.getElementById('path-clear')?.addEventListener('click', (event) => {
|
|
1379
|
+
event.stopPropagation();
|
|
2195
1380
|
|
|
2196
|
-
|
|
2197
|
-
null;
|
|
1381
|
+
state.pathStart = null;
|
|
2198
1382
|
|
|
2199
|
-
|
|
1383
|
+
resetPathHighlight();
|
|
2200
1384
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2205
|
-
);
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
function populateFilters() {
|
|
2209
|
-
clusterEl.innerHTML =
|
|
2210
|
-
'<option value="">All subsystems</option>';
|
|
2211
|
-
|
|
2212
|
-
edgeEl.innerHTML =
|
|
2213
|
-
'<option value="">All relations</option>';
|
|
2214
|
-
|
|
2215
|
-
const clusters =
|
|
2216
|
-
[
|
|
2217
|
-
...new Set(
|
|
2218
|
-
state.raw.nodes
|
|
2219
|
-
.map(
|
|
2220
|
-
node =>
|
|
2221
|
-
node.clusterLabel
|
|
2222
|
-
)
|
|
2223
|
-
.filter(Boolean)
|
|
2224
|
-
)
|
|
2225
|
-
]
|
|
2226
|
-
.sort();
|
|
2227
|
-
|
|
2228
|
-
for (
|
|
2229
|
-
const value
|
|
2230
|
-
of clusters
|
|
2231
|
-
) {
|
|
2232
|
-
const option =
|
|
2233
|
-
document.createElement(
|
|
2234
|
-
"option"
|
|
2235
|
-
);
|
|
1385
|
+
showDetails(node);
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
2236
1388
|
|
|
2237
|
-
|
|
2238
|
-
|
|
1389
|
+
function populateFilters() {
|
|
1390
|
+
clusterEl.innerHTML = '<option value="">All subsystems</option>';
|
|
2239
1391
|
|
|
2240
|
-
|
|
2241
|
-
value;
|
|
1392
|
+
edgeEl.innerHTML = '<option value="">All relations</option>';
|
|
2242
1393
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
);
|
|
2246
|
-
}
|
|
1394
|
+
const clusters = [
|
|
1395
|
+
...new Set(state.raw.nodes.map((node) => node.clusterLabel).filter(Boolean)),
|
|
1396
|
+
].sort();
|
|
2247
1397
|
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
...new Set(
|
|
2251
|
-
state.raw.links
|
|
2252
|
-
.map(
|
|
2253
|
-
link =>
|
|
2254
|
-
link.type
|
|
2255
|
-
)
|
|
2256
|
-
)
|
|
2257
|
-
]
|
|
2258
|
-
.sort();
|
|
2259
|
-
|
|
2260
|
-
for (
|
|
2261
|
-
const value
|
|
2262
|
-
of edges
|
|
2263
|
-
) {
|
|
2264
|
-
const option =
|
|
2265
|
-
document.createElement(
|
|
2266
|
-
"option"
|
|
2267
|
-
);
|
|
1398
|
+
for (const value of clusters) {
|
|
1399
|
+
const option = document.createElement('option');
|
|
2268
1400
|
|
|
2269
|
-
|
|
2270
|
-
value;
|
|
1401
|
+
option.value = value;
|
|
2271
1402
|
|
|
2272
|
-
|
|
2273
|
-
value;
|
|
1403
|
+
option.textContent = value;
|
|
2274
1404
|
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
);
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
function filteredData() {
|
|
2282
|
-
const search =
|
|
2283
|
-
state.search
|
|
2284
|
-
.trim()
|
|
2285
|
-
.toLowerCase();
|
|
2286
|
-
|
|
2287
|
-
let nodes =
|
|
2288
|
-
state.raw.nodes;
|
|
2289
|
-
|
|
2290
|
-
if (
|
|
2291
|
-
state.cluster
|
|
2292
|
-
) {
|
|
2293
|
-
nodes =
|
|
2294
|
-
nodes.filter(
|
|
2295
|
-
node =>
|
|
2296
|
-
node.clusterLabel ===
|
|
2297
|
-
state.cluster
|
|
2298
|
-
);
|
|
2299
|
-
}
|
|
1405
|
+
clusterEl.appendChild(option);
|
|
1406
|
+
}
|
|
2300
1407
|
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
node.qualifiedName
|
|
2314
|
-
?.toLowerCase()
|
|
2315
|
-
.includes(
|
|
2316
|
-
search
|
|
2317
|
-
) ||
|
|
2318
|
-
|
|
2319
|
-
node.filePath
|
|
2320
|
-
?.toLowerCase()
|
|
2321
|
-
.includes(
|
|
2322
|
-
search
|
|
2323
|
-
)
|
|
2324
|
-
);
|
|
1408
|
+
const edges = [...new Set(state.raw.links.map((link) => link.type))].sort();
|
|
1409
|
+
|
|
1410
|
+
for (const value of edges) {
|
|
1411
|
+
const option = document.createElement('option');
|
|
1412
|
+
|
|
1413
|
+
option.value = value;
|
|
1414
|
+
|
|
1415
|
+
option.textContent = value;
|
|
1416
|
+
|
|
1417
|
+
edgeEl.appendChild(option);
|
|
1418
|
+
}
|
|
2325
1419
|
}
|
|
2326
1420
|
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
nodes.map(
|
|
2330
|
-
node =>
|
|
2331
|
-
node.id
|
|
2332
|
-
)
|
|
2333
|
-
);
|
|
1421
|
+
function filteredData() {
|
|
1422
|
+
const search = state.search.trim().toLowerCase();
|
|
2334
1423
|
|
|
2335
|
-
|
|
2336
|
-
state.raw.links
|
|
2337
|
-
.filter(
|
|
2338
|
-
link => {
|
|
2339
|
-
const source =
|
|
2340
|
-
typeof link.source ===
|
|
2341
|
-
"object"
|
|
2342
|
-
? link.source.id
|
|
2343
|
-
: link.source;
|
|
2344
|
-
|
|
2345
|
-
const target =
|
|
2346
|
-
typeof link.target ===
|
|
2347
|
-
"object"
|
|
2348
|
-
? link.target.id
|
|
2349
|
-
: link.target;
|
|
2350
|
-
|
|
2351
|
-
return (
|
|
2352
|
-
ids.has(
|
|
2353
|
-
source
|
|
2354
|
-
) &&
|
|
2355
|
-
ids.has(
|
|
2356
|
-
target
|
|
2357
|
-
)
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
);
|
|
1424
|
+
let nodes = state.raw.nodes;
|
|
2361
1425
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
1426
|
+
if (state.cluster) {
|
|
1427
|
+
nodes = nodes.filter((node) => node.clusterLabel === state.cluster);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (search) {
|
|
1431
|
+
nodes = nodes.filter(
|
|
1432
|
+
(node) =>
|
|
1433
|
+
node.name?.toLowerCase().includes(search) ||
|
|
1434
|
+
node.qualifiedName?.toLowerCase().includes(search) ||
|
|
1435
|
+
node.filePath?.toLowerCase().includes(search)
|
|
2370
1436
|
);
|
|
2371
|
-
|
|
1437
|
+
}
|
|
2372
1438
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
target:
|
|
2393
|
-
typeof link.target ===
|
|
2394
|
-
"object"
|
|
2395
|
-
? link.target.id
|
|
2396
|
-
: link.target,
|
|
2397
|
-
})
|
|
2398
|
-
),
|
|
2399
|
-
};
|
|
2400
|
-
}
|
|
1439
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
1440
|
+
|
|
1441
|
+
let links = state.raw.links.filter((link) => {
|
|
1442
|
+
const source = typeof link.source === 'object' ? link.source.id : link.source;
|
|
1443
|
+
|
|
1444
|
+
const target = typeof link.target === 'object' ? link.target.id : link.target;
|
|
1445
|
+
|
|
1446
|
+
return ids.has(source) && ids.has(target);
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
if (state.edge) {
|
|
1450
|
+
links = links.filter((link) => link.type === state.edge);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return {
|
|
1454
|
+
nodes: nodes.map((node) => ({
|
|
1455
|
+
...node,
|
|
1456
|
+
})),
|
|
2401
1457
|
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
) {
|
|
2405
|
-
const dashboard =
|
|
2406
|
-
state.raw.dashboard ||
|
|
2407
|
-
{};
|
|
1458
|
+
links: links.map((link) => ({
|
|
1459
|
+
...link,
|
|
2408
1460
|
|
|
2409
|
-
|
|
1461
|
+
source: typeof link.source === 'object' ? link.source.id : link.source,
|
|
1462
|
+
|
|
1463
|
+
target: typeof link.target === 'object' ? link.target.id : link.target,
|
|
1464
|
+
})),
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function renderStats(data) {
|
|
1469
|
+
const dashboard = state.raw.dashboard || {};
|
|
1470
|
+
|
|
1471
|
+
statsEl.innerHTML = `
|
|
2410
1472
|
<div>
|
|
2411
1473
|
<strong>
|
|
2412
1474
|
${data.nodes.length}
|
|
@@ -2436,552 +1498,273 @@
|
|
|
2436
1498
|
</div>
|
|
2437
1499
|
|
|
2438
1500
|
<div class="status">
|
|
2439
|
-
${escapeHtml(
|
|
2440
|
-
dashboard.name ||
|
|
2441
|
-
state.project
|
|
2442
|
-
)}
|
|
1501
|
+
${escapeHtml(dashboard.name || state.project)}
|
|
2443
1502
|
|
|
2444
|
-
${
|
|
2445
|
-
dashboard.hasArchitecture
|
|
2446
|
-
? " · architecture"
|
|
2447
|
-
: ""
|
|
2448
|
-
}
|
|
1503
|
+
${dashboard.hasArchitecture ? ' · architecture' : ''}
|
|
2449
1504
|
|
|
2450
|
-
${
|
|
2451
|
-
dashboard.hasAnalysis
|
|
2452
|
-
? " · analysis"
|
|
2453
|
-
: ""
|
|
2454
|
-
}
|
|
1505
|
+
${dashboard.hasAnalysis ? ' · analysis' : ''}
|
|
2455
1506
|
</div>
|
|
2456
1507
|
`;
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
function applyFilters() {
|
|
2460
|
-
const data =
|
|
2461
|
-
filteredData();
|
|
2462
|
-
|
|
2463
|
-
state.graph.graphData(
|
|
2464
|
-
data
|
|
2465
|
-
);
|
|
2466
|
-
|
|
2467
|
-
renderStats(
|
|
2468
|
-
data
|
|
2469
|
-
);
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
|
-
async function loadProject(
|
|
2473
|
-
project,
|
|
2474
|
-
options = {}
|
|
2475
|
-
) {
|
|
2476
|
-
closeDetails();
|
|
2477
|
-
|
|
2478
|
-
const mode =
|
|
2479
|
-
options.mode ||
|
|
2480
|
-
"overview";
|
|
2481
|
-
|
|
2482
|
-
let loading =
|
|
2483
|
-
document.getElementById(
|
|
2484
|
-
"loading"
|
|
2485
|
-
);
|
|
1508
|
+
}
|
|
2486
1509
|
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
document.createElement(
|
|
2490
|
-
"div"
|
|
2491
|
-
);
|
|
1510
|
+
function applyFilters() {
|
|
1511
|
+
const data = filteredData();
|
|
2492
1512
|
|
|
2493
|
-
|
|
2494
|
-
"loading";
|
|
1513
|
+
state.graph.graphData(data);
|
|
2495
1514
|
|
|
2496
|
-
|
|
2497
|
-
.appendChild(
|
|
2498
|
-
loading
|
|
2499
|
-
);
|
|
1515
|
+
renderStats(data);
|
|
2500
1516
|
}
|
|
2501
1517
|
|
|
2502
|
-
|
|
2503
|
-
|
|
1518
|
+
async function loadProject(project, options = {}) {
|
|
1519
|
+
closeDetails();
|
|
2504
1520
|
|
|
2505
|
-
|
|
2506
|
-
new URLSearchParams();
|
|
1521
|
+
const mode = options.mode || 'overview';
|
|
2507
1522
|
|
|
2508
|
-
|
|
2509
|
-
"project",
|
|
2510
|
-
project
|
|
2511
|
-
);
|
|
1523
|
+
let loading = document.getElementById('loading');
|
|
2512
1524
|
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
mode
|
|
2516
|
-
);
|
|
1525
|
+
if (!loading) {
|
|
1526
|
+
loading = document.createElement('div');
|
|
2517
1527
|
|
|
2518
|
-
|
|
2519
|
-
options.cluster
|
|
2520
|
-
) {
|
|
2521
|
-
params.set(
|
|
2522
|
-
"cluster",
|
|
2523
|
-
options.cluster
|
|
2524
|
-
);
|
|
2525
|
-
}
|
|
1528
|
+
loading.id = 'loading';
|
|
2526
1529
|
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
) {
|
|
2530
|
-
params.set(
|
|
2531
|
-
"file",
|
|
2532
|
-
options.file
|
|
2533
|
-
);
|
|
2534
|
-
}
|
|
1530
|
+
document.body.appendChild(loading);
|
|
1531
|
+
}
|
|
2535
1532
|
|
|
2536
|
-
|
|
2537
|
-
await fetch(
|
|
2538
|
-
`/api/graph?${params.toString()}`
|
|
2539
|
-
);
|
|
1533
|
+
loading.textContent = `Loading ${project} · ${mode}…`;
|
|
2540
1534
|
|
|
2541
|
-
|
|
2542
|
-
!response.ok
|
|
2543
|
-
) {
|
|
2544
|
-
const error =
|
|
2545
|
-
await response
|
|
2546
|
-
.json()
|
|
2547
|
-
.catch(
|
|
2548
|
-
() => null
|
|
2549
|
-
);
|
|
2550
|
-
|
|
2551
|
-
throw new Error(
|
|
2552
|
-
error?.error ||
|
|
2553
|
-
"Unable to load graph"
|
|
2554
|
-
);
|
|
2555
|
-
}
|
|
1535
|
+
const params = new URLSearchParams();
|
|
2556
1536
|
|
|
2557
|
-
|
|
2558
|
-
project;
|
|
1537
|
+
params.set('project', project);
|
|
2559
1538
|
|
|
2560
|
-
|
|
2561
|
-
mode;
|
|
1539
|
+
params.set('mode', mode);
|
|
2562
1540
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
1541
|
+
if (options.cluster) {
|
|
1542
|
+
params.set('cluster', options.cluster);
|
|
1543
|
+
}
|
|
2566
1544
|
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
1545
|
+
if (options.file) {
|
|
1546
|
+
params.set('file', options.file);
|
|
1547
|
+
}
|
|
2570
1548
|
|
|
2571
|
-
|
|
2572
|
-
await response.json();
|
|
1549
|
+
const response = await graphApiFetch(`/api/graph?${params.toString()}`);
|
|
2573
1550
|
|
|
2574
|
-
|
|
2575
|
-
|
|
1551
|
+
if (!response.ok) {
|
|
1552
|
+
const error = await response.json().catch(() => null);
|
|
2576
1553
|
|
|
2577
|
-
|
|
2578
|
-
|
|
1554
|
+
throw new Error(error?.error || 'Unable to load graph');
|
|
1555
|
+
}
|
|
2579
1556
|
|
|
2580
|
-
|
|
2581
|
-
"";
|
|
1557
|
+
state.project = project;
|
|
2582
1558
|
|
|
2583
|
-
|
|
2584
|
-
null;
|
|
1559
|
+
state.viewMode = mode;
|
|
2585
1560
|
|
|
2586
|
-
|
|
2587
|
-
"";
|
|
1561
|
+
state.viewCluster = options.cluster || '';
|
|
2588
1562
|
|
|
2589
|
-
|
|
2590
|
-
"";
|
|
1563
|
+
state.viewFile = options.file || '';
|
|
2591
1564
|
|
|
2592
|
-
|
|
2593
|
-
"";
|
|
1565
|
+
state.raw = await response.json();
|
|
2594
1566
|
|
|
2595
|
-
|
|
1567
|
+
state.search = '';
|
|
2596
1568
|
|
|
2597
|
-
|
|
2598
|
-
filteredData();
|
|
1569
|
+
state.cluster = '';
|
|
2599
1570
|
|
|
2600
|
-
|
|
2601
|
-
!state.graph
|
|
2602
|
-
) {
|
|
2603
|
-
state.graph =
|
|
2604
|
-
ForceGraph3D()(
|
|
2605
|
-
graphEl
|
|
2606
|
-
)
|
|
2607
|
-
.backgroundColor(
|
|
2608
|
-
"#050608"
|
|
2609
|
-
)
|
|
1571
|
+
state.edge = '';
|
|
2610
1572
|
|
|
2611
|
-
|
|
2612
|
-
node =>
|
|
2613
|
-
`${
|
|
2614
|
-
node.qualifiedName ||
|
|
2615
|
-
node.name
|
|
2616
|
-
}\n${
|
|
2617
|
-
node.filePath ||
|
|
2618
|
-
""
|
|
2619
|
-
}`
|
|
2620
|
-
)
|
|
1573
|
+
state.pathStart = null;
|
|
2621
1574
|
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
1575
|
+
searchEl.value = '';
|
|
1576
|
+
|
|
1577
|
+
clusterEl.value = '';
|
|
1578
|
+
|
|
1579
|
+
edgeEl.value = '';
|
|
1580
|
+
|
|
1581
|
+
populateFilters();
|
|
1582
|
+
|
|
1583
|
+
const data = filteredData();
|
|
1584
|
+
|
|
1585
|
+
if (!state.graph) {
|
|
1586
|
+
state.graph = ForceGraph3D()(graphEl)
|
|
1587
|
+
.backgroundColor('#050608')
|
|
1588
|
+
|
|
1589
|
+
.nodeLabel((node) => `${node.qualifiedName || node.name}\n${node.filePath || ''}`)
|
|
1590
|
+
|
|
1591
|
+
.nodeColor((node) => nodeColor(node))
|
|
2628
1592
|
|
|
2629
1593
|
.nodeVal(
|
|
2630
|
-
node =>
|
|
2631
|
-
2 +
|
|
2632
|
-
Math.min(
|
|
2633
|
-
10,
|
|
2634
|
-
Math.log2(
|
|
2635
|
-
1 +
|
|
2636
|
-
(
|
|
2637
|
-
node.incoming ||
|
|
2638
|
-
0
|
|
2639
|
-
) +
|
|
2640
|
-
(
|
|
2641
|
-
node.outgoing ||
|
|
2642
|
-
0
|
|
2643
|
-
)
|
|
2644
|
-
)
|
|
2645
|
-
)
|
|
1594
|
+
(node) => 2 + Math.min(10, Math.log2(1 + (node.incoming || 0) + (node.outgoing || 0)))
|
|
2646
1595
|
)
|
|
2647
1596
|
|
|
2648
|
-
.linkColor(
|
|
2649
|
-
link =>
|
|
2650
|
-
linkColor(
|
|
2651
|
-
link
|
|
2652
|
-
)
|
|
2653
|
-
)
|
|
1597
|
+
.linkColor((link) => linkColor(link))
|
|
2654
1598
|
|
|
2655
|
-
.linkOpacity(
|
|
2656
|
-
.38
|
|
2657
|
-
)
|
|
1599
|
+
.linkOpacity(0.38)
|
|
2658
1600
|
|
|
2659
|
-
.linkWidth(
|
|
2660
|
-
link =>
|
|
2661
|
-
link.type ===
|
|
2662
|
-
"CALL_REFERENCE"
|
|
2663
|
-
? 1
|
|
2664
|
-
: .4
|
|
2665
|
-
)
|
|
1601
|
+
.linkWidth((link) => (link.type === 'CALL_REFERENCE' ? 1 : 0.4))
|
|
2666
1602
|
|
|
2667
1603
|
/*
|
|
2668
1604
|
* Particles across 200k relationships are extremely
|
|
2669
1605
|
* expensive on mobile GPUs. Keep them disabled.
|
|
2670
1606
|
*/
|
|
2671
|
-
.linkDirectionalParticles(
|
|
2672
|
-
0
|
|
2673
|
-
)
|
|
1607
|
+
.linkDirectionalParticles(0)
|
|
2674
1608
|
|
|
2675
|
-
.onBackgroundClick(
|
|
2676
|
-
()
|
|
2677
|
-
|
|
2678
|
-
}
|
|
2679
|
-
)
|
|
1609
|
+
.onBackgroundClick(() => {
|
|
1610
|
+
closeDetails();
|
|
1611
|
+
})
|
|
2680
1612
|
|
|
2681
|
-
.onNodeClick(
|
|
2682
|
-
node
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
node.viewKind ===
|
|
2687
|
-
"file"
|
|
2688
|
-
) {
|
|
2689
|
-
showDetails(
|
|
2690
|
-
node
|
|
2691
|
-
);
|
|
2692
|
-
|
|
2693
|
-
return;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
showDetails(
|
|
2697
|
-
node
|
|
2698
|
-
);
|
|
2699
|
-
|
|
2700
|
-
flyToNode(
|
|
2701
|
-
node
|
|
2702
|
-
);
|
|
1613
|
+
.onNodeClick((node) => {
|
|
1614
|
+
if (node.viewKind === 'cluster' || node.viewKind === 'file') {
|
|
1615
|
+
showDetails(node);
|
|
1616
|
+
|
|
1617
|
+
return;
|
|
2703
1618
|
}
|
|
2704
|
-
);
|
|
2705
|
-
}
|
|
2706
1619
|
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
: 80
|
|
2713
|
-
);
|
|
1620
|
+
showDetails(node);
|
|
1621
|
+
|
|
1622
|
+
flyToNode(node);
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
2714
1625
|
|
|
2715
|
-
|
|
2716
|
-
data
|
|
2717
|
-
);
|
|
1626
|
+
state.graph.cooldownTicks(mode === 'overview' ? 50 : 80);
|
|
2718
1627
|
|
|
2719
|
-
|
|
2720
|
-
data
|
|
2721
|
-
);
|
|
1628
|
+
state.graph.graphData(data);
|
|
2722
1629
|
|
|
2723
|
-
|
|
2724
|
-
`${
|
|
2725
|
-
state.raw.dashboard
|
|
2726
|
-
?.name ||
|
|
2727
|
-
project
|
|
2728
|
-
} · ToolNet Graph`;
|
|
1630
|
+
renderStats(data);
|
|
2729
1631
|
|
|
2730
|
-
|
|
2731
|
-
document.getElementById(
|
|
2732
|
-
"loading"
|
|
2733
|
-
);
|
|
1632
|
+
document.title = `${state.raw.dashboard?.name || project} · ToolNet Graph`;
|
|
2734
1633
|
|
|
2735
|
-
|
|
2736
|
-
loading.remove();
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
setTimeout(
|
|
2740
|
-
() => {
|
|
2741
|
-
state.graph
|
|
2742
|
-
.zoomToFit(
|
|
2743
|
-
500,
|
|
2744
|
-
45
|
|
2745
|
-
);
|
|
2746
|
-
},
|
|
2747
|
-
150
|
|
2748
|
-
);
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
async function main() {
|
|
2752
|
-
const response =
|
|
2753
|
-
await fetch(
|
|
2754
|
-
"/api/projects"
|
|
2755
|
-
);
|
|
1634
|
+
loading = document.getElementById('loading');
|
|
2756
1635
|
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
1636
|
+
if (loading) {
|
|
1637
|
+
loading.remove();
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
setTimeout(() => {
|
|
1641
|
+
state.graph.zoomToFit(500, 45);
|
|
1642
|
+
}, 150);
|
|
2763
1643
|
}
|
|
2764
1644
|
|
|
2765
|
-
|
|
2766
|
-
await
|
|
1645
|
+
async function main() {
|
|
1646
|
+
const response = await graphApiFetch('/api/projects');
|
|
2767
1647
|
|
|
2768
|
-
|
|
2769
|
-
|
|
1648
|
+
if (!response.ok) {
|
|
1649
|
+
throw new Error('Unable to load ToolNet projects');
|
|
1650
|
+
}
|
|
2770
1651
|
|
|
2771
|
-
|
|
2772
|
-
const project
|
|
2773
|
-
of catalog.projects
|
|
2774
|
-
) {
|
|
2775
|
-
const option =
|
|
2776
|
-
document.createElement(
|
|
2777
|
-
"option"
|
|
2778
|
-
);
|
|
1652
|
+
const catalog = await response.json();
|
|
2779
1653
|
|
|
2780
|
-
|
|
2781
|
-
project.remote;
|
|
1654
|
+
projectEl.innerHTML = '';
|
|
2782
1655
|
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
? project.name
|
|
2786
|
-
: `${project.name} · not indexed`;
|
|
1656
|
+
for (const project of catalog.projects) {
|
|
1657
|
+
const option = document.createElement('option');
|
|
2787
1658
|
|
|
2788
|
-
|
|
2789
|
-
!project.hasGraph;
|
|
1659
|
+
option.value = project.remote;
|
|
2790
1660
|
|
|
2791
|
-
|
|
2792
|
-
option
|
|
2793
|
-
);
|
|
2794
|
-
}
|
|
1661
|
+
option.textContent = project.hasGraph ? project.name : `${project.name} · not indexed`;
|
|
2795
1662
|
|
|
2796
|
-
|
|
2797
|
-
new URLSearchParams(
|
|
2798
|
-
window.location.search
|
|
2799
|
-
).get(
|
|
2800
|
-
"project"
|
|
2801
|
-
);
|
|
1663
|
+
option.disabled = !project.hasGraph;
|
|
2802
1664
|
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
.some(
|
|
2806
|
-
project =>
|
|
2807
|
-
project.remote ===
|
|
2808
|
-
requested &&
|
|
2809
|
-
project.hasGraph
|
|
2810
|
-
);
|
|
1665
|
+
projectEl.appendChild(option);
|
|
1666
|
+
}
|
|
2811
1667
|
|
|
2812
|
-
|
|
2813
|
-
requestedValid
|
|
2814
|
-
? requested
|
|
2815
|
-
: catalog.defaultProject;
|
|
1668
|
+
const requested = new URLSearchParams(window.location.search).get('project');
|
|
2816
1669
|
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
"No indexed projects found"
|
|
1670
|
+
const requestedValid = catalog.projects.some(
|
|
1671
|
+
(project) => project.remote === requested && project.hasGraph
|
|
2820
1672
|
);
|
|
2821
|
-
}
|
|
2822
1673
|
|
|
2823
|
-
|
|
2824
|
-
selected;
|
|
1674
|
+
const selected = requestedValid ? requested : catalog.defaultProject;
|
|
2825
1675
|
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
1676
|
+
if (!selected) {
|
|
1677
|
+
throw new Error('No indexed projects found');
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
projectEl.value = selected;
|
|
1681
|
+
|
|
1682
|
+
await loadProject(selected);
|
|
1683
|
+
}
|
|
2830
1684
|
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
async () => {
|
|
2834
|
-
const project =
|
|
2835
|
-
projectEl.value;
|
|
1685
|
+
projectEl.addEventListener('change', async () => {
|
|
1686
|
+
const project = projectEl.value;
|
|
2836
1687
|
|
|
2837
1688
|
if (!project) {
|
|
2838
1689
|
return;
|
|
2839
1690
|
}
|
|
2840
1691
|
|
|
2841
|
-
const url =
|
|
2842
|
-
new URL(
|
|
2843
|
-
window.location.href
|
|
2844
|
-
);
|
|
1692
|
+
const url = new URL(window.location.href);
|
|
2845
1693
|
|
|
2846
|
-
url.searchParams.set(
|
|
2847
|
-
"project",
|
|
2848
|
-
project
|
|
2849
|
-
);
|
|
1694
|
+
url.searchParams.set('project', project);
|
|
2850
1695
|
|
|
2851
|
-
history.replaceState(
|
|
2852
|
-
{},
|
|
2853
|
-
"",
|
|
2854
|
-
url
|
|
2855
|
-
);
|
|
1696
|
+
history.replaceState({}, '', url);
|
|
2856
1697
|
|
|
2857
1698
|
try {
|
|
2858
|
-
await loadProject(
|
|
2859
|
-
|
|
2860
|
-
);
|
|
2861
|
-
} catch (
|
|
2862
|
-
error
|
|
2863
|
-
) {
|
|
2864
|
-
alert(
|
|
2865
|
-
error.message
|
|
2866
|
-
);
|
|
1699
|
+
await loadProject(project);
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
alert(error.message);
|
|
2867
1702
|
}
|
|
2868
|
-
}
|
|
2869
|
-
);
|
|
1703
|
+
});
|
|
2870
1704
|
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
() => {
|
|
2874
|
-
state.search =
|
|
2875
|
-
searchEl.value;
|
|
1705
|
+
searchEl.addEventListener('input', () => {
|
|
1706
|
+
state.search = searchEl.value;
|
|
2876
1707
|
|
|
2877
1708
|
applyFilters();
|
|
2878
|
-
}
|
|
2879
|
-
);
|
|
1709
|
+
});
|
|
2880
1710
|
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
() => {
|
|
2884
|
-
state.cluster =
|
|
2885
|
-
clusterEl.value;
|
|
1711
|
+
clusterEl.addEventListener('change', () => {
|
|
1712
|
+
state.cluster = clusterEl.value;
|
|
2886
1713
|
|
|
2887
1714
|
applyFilters();
|
|
2888
|
-
}
|
|
2889
|
-
);
|
|
1715
|
+
});
|
|
2890
1716
|
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
() => {
|
|
2894
|
-
state.edge =
|
|
2895
|
-
edgeEl.value;
|
|
1717
|
+
edgeEl.addEventListener('change', () => {
|
|
1718
|
+
state.edge = edgeEl.value;
|
|
2896
1719
|
|
|
2897
1720
|
applyFilters();
|
|
2898
|
-
}
|
|
2899
|
-
);
|
|
2900
|
-
|
|
2901
|
-
document
|
|
2902
|
-
.getElementById(
|
|
2903
|
-
"reset"
|
|
2904
|
-
)
|
|
2905
|
-
.addEventListener(
|
|
2906
|
-
"click",
|
|
2907
|
-
() => {
|
|
2908
|
-
if (
|
|
2909
|
-
state.viewMode !==
|
|
2910
|
-
"overview"
|
|
2911
|
-
) {
|
|
2912
|
-
void loadProject(
|
|
2913
|
-
state.project
|
|
2914
|
-
);
|
|
1721
|
+
});
|
|
2915
1722
|
|
|
2916
|
-
|
|
2917
|
-
|
|
1723
|
+
document.getElementById('reset').addEventListener('click', () => {
|
|
1724
|
+
if (state.viewMode !== 'overview') {
|
|
1725
|
+
void loadProject(state.project);
|
|
2918
1726
|
|
|
2919
|
-
|
|
2920
|
-
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
2921
1729
|
|
|
2922
|
-
|
|
2923
|
-
"";
|
|
1730
|
+
searchEl.value = '';
|
|
2924
1731
|
|
|
2925
|
-
|
|
2926
|
-
"";
|
|
1732
|
+
clusterEl.value = '';
|
|
2927
1733
|
|
|
2928
|
-
|
|
2929
|
-
"";
|
|
1734
|
+
edgeEl.value = '';
|
|
2930
1735
|
|
|
2931
|
-
|
|
2932
|
-
"";
|
|
1736
|
+
state.search = '';
|
|
2933
1737
|
|
|
2934
|
-
|
|
2935
|
-
"";
|
|
1738
|
+
state.cluster = '';
|
|
2936
1739
|
|
|
2937
|
-
|
|
2938
|
-
null;
|
|
1740
|
+
state.edge = '';
|
|
2939
1741
|
|
|
2940
|
-
|
|
1742
|
+
state.pathStart = null;
|
|
2941
1743
|
|
|
2942
|
-
|
|
1744
|
+
resetPathHighlight();
|
|
2943
1745
|
|
|
2944
|
-
|
|
1746
|
+
closeDetails();
|
|
2945
1747
|
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
document.addEventListener(
|
|
2955
|
-
"keydown",
|
|
2956
|
-
event => {
|
|
2957
|
-
if (
|
|
2958
|
-
event.key ===
|
|
2959
|
-
"Escape"
|
|
2960
|
-
) {
|
|
1748
|
+
applyFilters();
|
|
1749
|
+
|
|
1750
|
+
state.graph.zoomToFit(700, 60);
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
document.addEventListener('keydown', (event) => {
|
|
1754
|
+
if (event.key === 'Escape') {
|
|
2961
1755
|
closeDetails();
|
|
2962
1756
|
}
|
|
2963
|
-
}
|
|
2964
|
-
);
|
|
1757
|
+
});
|
|
2965
1758
|
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
const loading =
|
|
2969
|
-
document.getElementById(
|
|
2970
|
-
"loading"
|
|
2971
|
-
);
|
|
1759
|
+
main().catch((error) => {
|
|
1760
|
+
const loading = document.getElementById('loading');
|
|
2972
1761
|
|
|
2973
|
-
if (
|
|
2974
|
-
loading
|
|
2975
|
-
) {
|
|
2976
|
-
loading.textContent =
|
|
2977
|
-
error.message;
|
|
1762
|
+
if (loading) {
|
|
1763
|
+
loading.textContent = error.message;
|
|
2978
1764
|
}
|
|
2979
1765
|
|
|
2980
|
-
console.error(
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
);
|
|
2985
|
-
</script>
|
|
2986
|
-
</body>
|
|
1766
|
+
console.error(error);
|
|
1767
|
+
});
|
|
1768
|
+
</script>
|
|
1769
|
+
</body>
|
|
2987
1770
|
</html>
|