@andespindola/brainlink 0.1.0-beta.162 → 0.1.0-beta.164
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.
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export const createClientCss = () => `:root {
|
|
2
2
|
color-scheme: dark;
|
|
3
|
-
--bg: #
|
|
4
|
-
--panel: #
|
|
5
|
-
--panel-strong: #
|
|
6
|
-
--line:
|
|
7
|
-
--text: #
|
|
8
|
-
--muted: #
|
|
9
|
-
--accent: #
|
|
10
|
-
--accent-weak: rgba(
|
|
3
|
+
--bg: #071019;
|
|
4
|
+
--panel: #0d1823;
|
|
5
|
+
--panel-strong: #112130;
|
|
6
|
+
--line: rgba(143, 172, 204, 0.18);
|
|
7
|
+
--text: #edf4ff;
|
|
8
|
+
--muted: #97a9bd;
|
|
9
|
+
--accent: #5aa8ff;
|
|
10
|
+
--accent-weak: rgba(90, 168, 255, 0.18);
|
|
11
11
|
--danger: #ff6b6b;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -63,7 +63,8 @@ select {
|
|
|
63
63
|
min-height: 72px;
|
|
64
64
|
padding: 10px 16px;
|
|
65
65
|
border-bottom: 1px solid var(--line);
|
|
66
|
-
background:
|
|
66
|
+
background: rgba(9, 18, 28, 0.92);
|
|
67
|
+
box-shadow: 0 1px 14px rgba(1, 6, 13, 0.42);
|
|
67
68
|
backdrop-filter: blur(8px);
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -81,9 +82,15 @@ select {
|
|
|
81
82
|
position: relative;
|
|
82
83
|
width: 100%;
|
|
83
84
|
height: 100%;
|
|
85
|
+
touch-action: none;
|
|
86
|
+
user-select: none;
|
|
87
|
+
-webkit-user-select: none;
|
|
84
88
|
background:
|
|
85
|
-
|
|
86
|
-
linear-gradient(
|
|
89
|
+
linear-gradient(rgba(164, 197, 230, 0.05) 1px, transparent 1px),
|
|
90
|
+
linear-gradient(90deg, rgba(164, 197, 230, 0.05) 1px, transparent 1px),
|
|
91
|
+
radial-gradient(circle at top, rgba(43, 93, 143, 0.22), transparent 44%),
|
|
92
|
+
#08131d;
|
|
93
|
+
background-size: 28px 28px, 28px 28px, auto;
|
|
87
94
|
overflow: hidden;
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -93,6 +100,7 @@ select {
|
|
|
93
100
|
inset: 0;
|
|
94
101
|
width: 100%;
|
|
95
102
|
height: 100%;
|
|
103
|
+
touch-action: none;
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
#graph {
|
|
@@ -126,23 +134,23 @@ select {
|
|
|
126
134
|
.graph-label {
|
|
127
135
|
position: absolute;
|
|
128
136
|
max-width: 220px;
|
|
129
|
-
transform: translate(-50%, calc(-100% -
|
|
130
|
-
padding: 4px
|
|
131
|
-
border: 1px solid rgba(
|
|
137
|
+
transform: translate(-50%, calc(-100% - 12px));
|
|
138
|
+
padding: 4px 8px;
|
|
139
|
+
border: 1px solid rgba(143, 172, 204, 0.18);
|
|
132
140
|
border-radius: 6px;
|
|
133
|
-
background: rgba(
|
|
141
|
+
background: rgba(7, 16, 25, 0.92);
|
|
134
142
|
color: var(--text);
|
|
135
143
|
font-size: 11px;
|
|
136
144
|
line-height: 1.25;
|
|
137
145
|
white-space: nowrap;
|
|
138
146
|
overflow: hidden;
|
|
139
147
|
text-overflow: ellipsis;
|
|
140
|
-
box-shadow: 0
|
|
148
|
+
box-shadow: 0 12px 28px rgba(1, 6, 13, 0.36);
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
.graph-label.is-focused {
|
|
144
|
-
border-color: rgba(
|
|
145
|
-
color: #
|
|
152
|
+
border-color: rgba(90, 168, 255, 0.56);
|
|
153
|
+
color: #d7e9ff;
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
.graph-tooltip {
|
|
@@ -152,12 +160,12 @@ select {
|
|
|
152
160
|
padding: 8px 10px;
|
|
153
161
|
border: 1px solid var(--line);
|
|
154
162
|
border-radius: 6px;
|
|
155
|
-
background: rgba(
|
|
163
|
+
background: rgba(9, 18, 28, 0.96);
|
|
156
164
|
color: var(--text);
|
|
157
165
|
font-size: 12px;
|
|
158
166
|
line-height: 1.35;
|
|
159
167
|
pointer-events: none;
|
|
160
|
-
box-shadow: 0
|
|
168
|
+
box-shadow: 0 18px 44px rgba(1, 6, 13, 0.42);
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
.graph-tooltip strong,
|
|
@@ -179,10 +187,10 @@ select {
|
|
|
179
187
|
z-index: 3;
|
|
180
188
|
width: 180px;
|
|
181
189
|
height: 120px;
|
|
182
|
-
border: 1px solid rgba(
|
|
190
|
+
border: 1px solid rgba(143, 172, 204, 0.18);
|
|
183
191
|
border-radius: 8px;
|
|
184
|
-
background: rgba(
|
|
185
|
-
box-shadow: 0
|
|
192
|
+
background: rgba(8, 19, 29, 0.84);
|
|
193
|
+
box-shadow: 0 18px 40px rgba(1, 6, 13, 0.36);
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
.eyebrow {
|
|
@@ -215,7 +223,7 @@ select {
|
|
|
215
223
|
border: 1px solid var(--line);
|
|
216
224
|
border-radius: 8px;
|
|
217
225
|
outline: none;
|
|
218
|
-
background: rgba(
|
|
226
|
+
background: rgba(12, 24, 36, 0.94);
|
|
219
227
|
color: var(--text);
|
|
220
228
|
padding: 0 14px;
|
|
221
229
|
}
|
|
@@ -236,7 +244,7 @@ select {
|
|
|
236
244
|
height: 38px;
|
|
237
245
|
border: 1px solid var(--line);
|
|
238
246
|
border-radius: 8px;
|
|
239
|
-
background: rgba(
|
|
247
|
+
background: rgba(12, 24, 36, 0.94);
|
|
240
248
|
color: var(--text);
|
|
241
249
|
cursor: pointer;
|
|
242
250
|
}
|
|
@@ -257,7 +265,7 @@ select {
|
|
|
257
265
|
padding: 10px 12px;
|
|
258
266
|
border: 1px solid var(--line);
|
|
259
267
|
border-radius: 10px;
|
|
260
|
-
background: rgba(
|
|
268
|
+
background: rgba(12, 24, 36, 0.94);
|
|
261
269
|
display: grid;
|
|
262
270
|
gap: 3px;
|
|
263
271
|
}
|
|
@@ -332,7 +340,7 @@ li small {
|
|
|
332
340
|
padding: 12px;
|
|
333
341
|
border: 1px solid var(--line);
|
|
334
342
|
border-radius: 8px;
|
|
335
|
-
background: #
|
|
343
|
+
background: #091521;
|
|
336
344
|
color: var(--text);
|
|
337
345
|
white-space: pre-wrap;
|
|
338
346
|
overflow: auto;
|
|
@@ -362,7 +370,8 @@ li small {
|
|
|
362
370
|
border-radius: 8px;
|
|
363
371
|
background: var(--panel);
|
|
364
372
|
color: var(--text);
|
|
365
|
-
box-shadow: 0 24px 80px rgba(
|
|
373
|
+
box-shadow: 0 24px 80px rgba(23, 32, 51, 0.22);
|
|
374
|
+
backdrop-filter: blur(10px);
|
|
366
375
|
overflow: hidden;
|
|
367
376
|
}
|
|
368
377
|
|
|
@@ -466,7 +475,7 @@ li small {
|
|
|
466
475
|
padding: 10px;
|
|
467
476
|
border: 1px solid var(--line);
|
|
468
477
|
border-radius: 8px;
|
|
469
|
-
background:
|
|
478
|
+
background: #091521;
|
|
470
479
|
display: grid;
|
|
471
480
|
grid-template-rows: auto minmax(0, 1fr);
|
|
472
481
|
gap: 8px;
|
|
@@ -506,7 +515,7 @@ li small {
|
|
|
506
515
|
display: flex;
|
|
507
516
|
align-items: center;
|
|
508
517
|
justify-content: center;
|
|
509
|
-
background:
|
|
518
|
+
background: linear-gradient(180deg, rgba(7, 16, 25, 0), rgba(7, 16, 25, 0.84));
|
|
510
519
|
}
|
|
511
520
|
|
|
512
521
|
.app-footer small {
|
|
@@ -369,14 +369,41 @@ const parseColor = (hex) => {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
const graphTheme = {
|
|
372
|
-
node: parseColor('#
|
|
373
|
-
nodeCluster: parseColor('#
|
|
374
|
-
nodeHighlight: parseColor('#
|
|
375
|
-
nodeSelected: parseColor('#
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
372
|
+
node: parseColor('#5aa8ff'),
|
|
373
|
+
nodeCluster: parseColor('#3f7fbd'),
|
|
374
|
+
nodeHighlight: parseColor('#ffcb67'),
|
|
375
|
+
nodeSelected: parseColor('#edf4ff'),
|
|
376
|
+
nodePalette: [
|
|
377
|
+
parseColor('#5aa8ff'),
|
|
378
|
+
parseColor('#5ecf92'),
|
|
379
|
+
parseColor('#ffb65c'),
|
|
380
|
+
parseColor('#ff7dac'),
|
|
381
|
+
parseColor('#a88fff'),
|
|
382
|
+
parseColor('#59d0dd'),
|
|
383
|
+
parseColor('#ff8f6a'),
|
|
384
|
+
parseColor('#a4b3c3'),
|
|
385
|
+
parseColor('#c9945f'),
|
|
386
|
+
parseColor('#7cb6ff')
|
|
387
|
+
],
|
|
388
|
+
edge: [0.59, 0.71, 0.83, 0.14],
|
|
389
|
+
edgeHeavy: [0.59, 0.71, 0.83, 0.3],
|
|
390
|
+
clear: parseColor('#08131d')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const segmentPalette = ['#5aa8ff', '#5ecf92', '#ffb65c', '#ff7dac', '#a88fff', '#59d0dd', '#ff8f6a', '#a4b3c3', '#c9945f', '#7cb6ff']
|
|
394
|
+
|
|
395
|
+
const segmentColorIndex = (segment) => {
|
|
396
|
+
const value = String(segment || '')
|
|
397
|
+
let hash = 0
|
|
398
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
399
|
+
hash = ((hash << 5) - hash + value.charCodeAt(index)) | 0
|
|
400
|
+
}
|
|
401
|
+
return Math.abs(hash) % segmentPalette.length
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const segmentColor = (segment) => segmentPalette[segmentColorIndex(segment)] || segmentPalette[0]
|
|
405
|
+
const nodeKind = (node) => node?.[6] === 'cluster' ? 'cluster' : 'node'
|
|
406
|
+
const isRealGraphNode = (node) => nodeKind(node) === 'node'
|
|
380
407
|
|
|
381
408
|
const clampScale = (scale) => Math.max(zoomRange.min, Math.min(zoomRange.max, scale))
|
|
382
409
|
|
|
@@ -518,7 +545,7 @@ const drawFallback = () => {
|
|
|
518
545
|
canvas.width = Math.floor(width * ratio)
|
|
519
546
|
canvas.height = Math.floor(height * ratio)
|
|
520
547
|
ctx2dFallback.setTransform(ratio, 0, 0, ratio, 0, 0)
|
|
521
|
-
ctx2dFallback.fillStyle = '#
|
|
548
|
+
ctx2dFallback.fillStyle = '#08131d'
|
|
522
549
|
ctx2dFallback.fillRect(0, 0, width, height)
|
|
523
550
|
|
|
524
551
|
const nodes = Array.isArray(state.chunk.nodes) ? state.chunk.nodes : []
|
|
@@ -528,7 +555,7 @@ const drawFallback = () => {
|
|
|
528
555
|
nodeById.set(nodes[i][0], nodes[i])
|
|
529
556
|
}
|
|
530
557
|
|
|
531
|
-
ctx2dFallback.strokeStyle = 'rgba(
|
|
558
|
+
ctx2dFallback.strokeStyle = 'rgba(151,181,212,0.18)'
|
|
532
559
|
ctx2dFallback.lineWidth = 1
|
|
533
560
|
for (let i = 0; i < edges.length; i += 1) {
|
|
534
561
|
const edge = edges[i]
|
|
@@ -547,16 +574,16 @@ const drawFallback = () => {
|
|
|
547
574
|
const node = nodes[i]
|
|
548
575
|
const p = worldToScreen(node[2], node[3])
|
|
549
576
|
const selected = state.selectedNodeId === node[0]
|
|
550
|
-
const color = node[
|
|
577
|
+
const color = segmentColor(node[5] || node[4] || node[1])
|
|
551
578
|
const radius = Math.max(2.4, Math.min(14, 4 + node[7] * 0.55))
|
|
552
579
|
|
|
553
580
|
ctx2dFallback.beginPath()
|
|
554
|
-
ctx2dFallback.fillStyle = selected ? '#
|
|
581
|
+
ctx2dFallback.fillStyle = selected ? '#edf4ff' : color
|
|
555
582
|
ctx2dFallback.arc(p.x, p.y, radius, 0, Math.PI * 2)
|
|
556
583
|
ctx2dFallback.fill()
|
|
557
584
|
}
|
|
558
585
|
|
|
559
|
-
ctx2dFallback.fillStyle = '#
|
|
586
|
+
ctx2dFallback.fillStyle = '#97a9bd'
|
|
560
587
|
ctx2dFallback.font = '12px Inter, system-ui, sans-serif'
|
|
561
588
|
ctx2dFallback.textAlign = 'center'
|
|
562
589
|
ctx2dFallback.fillText('Fallback canvas mode', Math.max(width, 320) / 2, 24)
|
|
@@ -645,6 +672,28 @@ const updateNodePositionInChunk = (nodeId, x, y) => {
|
|
|
645
672
|
updateGraphOverlays()
|
|
646
673
|
}
|
|
647
674
|
|
|
675
|
+
const focusNodeInViewport = (nodeId, nextScale = null) => {
|
|
676
|
+
const node = nodeByIdFromChunk().get(nodeId)
|
|
677
|
+
if (!node) {
|
|
678
|
+
return false
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const x = Number(node[2])
|
|
682
|
+
const y = Number(node[3])
|
|
683
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (Number.isFinite(nextScale)) {
|
|
688
|
+
state.camera.scale = clampScale(Number(nextScale))
|
|
689
|
+
}
|
|
690
|
+
state.camera.x = state.viewport.width / 2 - x * state.camera.scale
|
|
691
|
+
state.camera.y = state.viewport.height / 2 - y * state.camera.scale
|
|
692
|
+
updateWorkerCamera()
|
|
693
|
+
scheduleChunkFetch()
|
|
694
|
+
return true
|
|
695
|
+
}
|
|
696
|
+
|
|
648
697
|
const showTooltip = (node, pointer) => {
|
|
649
698
|
if (!elements.tooltip || !node) {
|
|
650
699
|
return
|
|
@@ -718,7 +767,7 @@ const drawMiniMap = () => {
|
|
|
718
767
|
miniMap.height = Math.floor(height * ratio)
|
|
719
768
|
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
|
|
720
769
|
ctx.clearRect(0, 0, width, height)
|
|
721
|
-
ctx.fillStyle = 'rgba(
|
|
770
|
+
ctx.fillStyle = 'rgba(8, 19, 29, 0.88)'
|
|
722
771
|
ctx.fillRect(0, 0, width, height)
|
|
723
772
|
|
|
724
773
|
const xs = nodes.map((node) => Number(node[2])).filter(Number.isFinite)
|
|
@@ -738,7 +787,7 @@ const drawMiniMap = () => {
|
|
|
738
787
|
})
|
|
739
788
|
state.miniMapView = { minX, minY, scale, offsetX, offsetY, width, height }
|
|
740
789
|
|
|
741
|
-
ctx.fillStyle = 'rgba(
|
|
790
|
+
ctx.fillStyle = 'rgba(90, 168, 255, 0.62)'
|
|
742
791
|
nodes.forEach((node) => {
|
|
743
792
|
const point = toMini(Number(node[2]), Number(node[3]))
|
|
744
793
|
ctx.fillRect(point.x - 1, point.y - 1, 2, 2)
|
|
@@ -748,7 +797,7 @@ const drawMiniMap = () => {
|
|
|
748
797
|
const worldBottomRight = screenToWorld(state.viewport.width, state.viewport.height)
|
|
749
798
|
const topLeft = toMini(Math.min(worldTopLeft.x, worldBottomRight.x), Math.min(worldTopLeft.y, worldBottomRight.y))
|
|
750
799
|
const bottomRight = toMini(Math.max(worldTopLeft.x, worldBottomRight.x), Math.max(worldTopLeft.y, worldBottomRight.y))
|
|
751
|
-
ctx.strokeStyle = 'rgba(
|
|
800
|
+
ctx.strokeStyle = 'rgba(90, 168, 255, 0.86)'
|
|
752
801
|
ctx.lineWidth = 1
|
|
753
802
|
ctx.strokeRect(topLeft.x, topLeft.y, Math.max(3, bottomRight.x - topLeft.x), Math.max(3, bottomRight.y - topLeft.y))
|
|
754
803
|
}
|
|
@@ -1103,11 +1152,28 @@ const pickFallbackNodeId = (screenX, screenY) => {
|
|
|
1103
1152
|
return typeof node?.[0] === 'string' ? node[0] : ''
|
|
1104
1153
|
}
|
|
1105
1154
|
|
|
1155
|
+
const handlePickedNode = (node) => {
|
|
1156
|
+
const nodeId = typeof node?.id === 'string' ? node.id : typeof node?.[0] === 'string' ? node[0] : ''
|
|
1157
|
+
if (!nodeId) {
|
|
1158
|
+
return
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const kind = typeof node?.kind === 'string' ? node.kind : nodeKind(node)
|
|
1162
|
+
if (kind === 'cluster') {
|
|
1163
|
+
const currentScale = state.camera.scale
|
|
1164
|
+
const targetScale = currentScale < 0.22 ? 0.28 : Math.min(1.1, currentScale * 1.6)
|
|
1165
|
+
focusNodeInViewport(nodeId, targetScale)
|
|
1166
|
+
return
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
loadNodeDetails(nodeId).catch((error) => console.error(error))
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1106
1172
|
const pickAt = (screenX, screenY) => {
|
|
1107
1173
|
if (state.rendererMode === 'fallback') {
|
|
1108
|
-
const
|
|
1109
|
-
if (
|
|
1110
|
-
|
|
1174
|
+
const node = pickFallbackNode(screenX, screenY)
|
|
1175
|
+
if (node) {
|
|
1176
|
+
handlePickedNode(node)
|
|
1111
1177
|
}
|
|
1112
1178
|
return
|
|
1113
1179
|
}
|
|
@@ -1145,6 +1211,19 @@ const resolvePointer = (event) => {
|
|
|
1145
1211
|
|
|
1146
1212
|
const setupInput = () => {
|
|
1147
1213
|
const dragActivationDistance = 6
|
|
1214
|
+
const resetPointerState = (pointerId = null) => {
|
|
1215
|
+
state.pointer.down = false
|
|
1216
|
+
state.pointer.dragging = false
|
|
1217
|
+
state.pointer.dragNodeId = ''
|
|
1218
|
+
canvas.classList.remove('is-node-dragging')
|
|
1219
|
+
if (pointerId !== null) {
|
|
1220
|
+
try {
|
|
1221
|
+
if (canvas.hasPointerCapture(pointerId)) {
|
|
1222
|
+
canvas.releasePointerCapture(pointerId)
|
|
1223
|
+
}
|
|
1224
|
+
} catch {}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1148
1227
|
|
|
1149
1228
|
canvas.addEventListener('wheel', (event) => {
|
|
1150
1229
|
event.preventDefault()
|
|
@@ -1155,9 +1234,10 @@ const setupInput = () => {
|
|
|
1155
1234
|
}, { passive: false })
|
|
1156
1235
|
|
|
1157
1236
|
canvas.addEventListener('pointerdown', (event) => {
|
|
1237
|
+
event.preventDefault()
|
|
1158
1238
|
const pointer = resolvePointer(event)
|
|
1159
1239
|
const candidateNode = pickFallbackNode(pointer.x, pointer.y)
|
|
1160
|
-
const candidateNodeId = candidateNode
|
|
1240
|
+
const candidateNodeId = isRealGraphNode(candidateNode) && typeof candidateNode?.[0] === 'string' ? candidateNode[0] : ''
|
|
1161
1241
|
const candidateX = Number(candidateNode?.[2])
|
|
1162
1242
|
const candidateY = Number(candidateNode?.[3])
|
|
1163
1243
|
const world = screenToWorld(pointer.x, pointer.y)
|
|
@@ -1175,10 +1255,15 @@ const setupInput = () => {
|
|
|
1175
1255
|
state.pointer.nodeStartY = candidateNodeId && Number.isFinite(candidateY) ? candidateY : 0
|
|
1176
1256
|
state.pointer.worldAnchorX = world.x
|
|
1177
1257
|
state.pointer.worldAnchorY = world.y
|
|
1178
|
-
|
|
1258
|
+
try {
|
|
1259
|
+
canvas.setPointerCapture(event.pointerId)
|
|
1260
|
+
} catch {}
|
|
1179
1261
|
})
|
|
1180
1262
|
|
|
1181
1263
|
canvas.addEventListener('pointermove', (event) => {
|
|
1264
|
+
if (state.pointer.down) {
|
|
1265
|
+
event.preventDefault()
|
|
1266
|
+
}
|
|
1182
1267
|
const pointer = resolvePointer(event)
|
|
1183
1268
|
|
|
1184
1269
|
if (state.pointer.down) {
|
|
@@ -1216,7 +1301,7 @@ const setupInput = () => {
|
|
|
1216
1301
|
}
|
|
1217
1302
|
|
|
1218
1303
|
const hovered = pickFallbackNode(pointer.x, pointer.y)
|
|
1219
|
-
const hoveredId = hovered
|
|
1304
|
+
const hoveredId = isRealGraphNode(hovered) && typeof hovered?.[0] === 'string' ? hovered[0] : ''
|
|
1220
1305
|
if (state.hoveredNodeId !== hoveredId) {
|
|
1221
1306
|
state.hoveredNodeId = hoveredId
|
|
1222
1307
|
canvas.classList.toggle('is-node-hover', Boolean(hoveredId))
|
|
@@ -1235,11 +1320,7 @@ const setupInput = () => {
|
|
|
1235
1320
|
const shouldPick = !state.pointer.dragging && distanceFromStart < dragActivationDistance
|
|
1236
1321
|
const shouldRefreshAfterDrag = state.pointer.dragging
|
|
1237
1322
|
const shouldPersistNodePosition = state.pointer.dragging && Boolean(state.pointer.dragNodeId)
|
|
1238
|
-
|
|
1239
|
-
state.pointer.dragging = false
|
|
1240
|
-
canvas.classList.remove('is-node-dragging')
|
|
1241
|
-
state.pointer.dragNodeId = ''
|
|
1242
|
-
canvas.releasePointerCapture(event.pointerId)
|
|
1323
|
+
resetPointerState(event.pointerId)
|
|
1243
1324
|
|
|
1244
1325
|
if (shouldPick) {
|
|
1245
1326
|
pickAt(pointer.x, pointer.y)
|
|
@@ -1262,6 +1343,16 @@ const setupInput = () => {
|
|
|
1262
1343
|
updateGraphOverlays()
|
|
1263
1344
|
})
|
|
1264
1345
|
|
|
1346
|
+
canvas.addEventListener('pointercancel', (event) => {
|
|
1347
|
+
resetPointerState(event.pointerId)
|
|
1348
|
+
hideTooltip()
|
|
1349
|
+
updateGraphOverlays()
|
|
1350
|
+
})
|
|
1351
|
+
|
|
1352
|
+
canvas.addEventListener('lostpointercapture', () => {
|
|
1353
|
+
resetPointerState()
|
|
1354
|
+
})
|
|
1355
|
+
|
|
1265
1356
|
elements.miniMap.addEventListener('click', (event) => {
|
|
1266
1357
|
if (!state.miniMapView) {
|
|
1267
1358
|
return
|
|
@@ -1482,7 +1573,7 @@ const setupRenderWorker = () => {
|
|
|
1482
1573
|
|
|
1483
1574
|
if (payload.type === 'pick-result') {
|
|
1484
1575
|
if (payload.node && typeof payload.node.id === 'string' && payload.node.id.length > 0) {
|
|
1485
|
-
|
|
1576
|
+
handlePickedNode(payload.node)
|
|
1486
1577
|
}
|
|
1487
1578
|
return
|
|
1488
1579
|
}
|
|
@@ -14,6 +14,7 @@ const state = {
|
|
|
14
14
|
y: new Float32Array(0),
|
|
15
15
|
relevance: new Float32Array(0),
|
|
16
16
|
radius: new Float32Array(0),
|
|
17
|
+
colorIndex: new Uint8Array(0),
|
|
17
18
|
visible: new Uint8Array(0),
|
|
18
19
|
highlighted: new Uint8Array(0),
|
|
19
20
|
focused: new Uint8Array(0),
|
|
@@ -39,13 +40,25 @@ let pointPositionsBuffer = new Float32Array(0)
|
|
|
39
40
|
let pointSizesBuffer = new Float32Array(0)
|
|
40
41
|
|
|
41
42
|
const defaultTheme = {
|
|
42
|
-
node: [0.
|
|
43
|
-
nodeCluster: [0.
|
|
44
|
-
nodeHighlight: [0.95, 0.
|
|
45
|
-
nodeSelected: [0.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
node: [0.30, 0.56, 0.85, 1],
|
|
44
|
+
nodeCluster: [0.18, 0.44, 0.71, 1],
|
|
45
|
+
nodeHighlight: [0.95, 0.70, 0.25, 1],
|
|
46
|
+
nodeSelected: [0.09, 0.13, 0.20, 1],
|
|
47
|
+
nodePalette: [
|
|
48
|
+
[0.30, 0.56, 0.85, 1],
|
|
49
|
+
[0.40, 0.73, 0.43, 1],
|
|
50
|
+
[0.94, 0.64, 0.23, 1],
|
|
51
|
+
[0.85, 0.37, 0.55, 1],
|
|
52
|
+
[0.55, 0.45, 0.85, 1],
|
|
53
|
+
[0.33, 0.75, 0.77, 1],
|
|
54
|
+
[0.93, 0.42, 0.34, 1],
|
|
55
|
+
[0.60, 0.65, 0.70, 1],
|
|
56
|
+
[0.72, 0.51, 0.33, 1],
|
|
57
|
+
[0.44, 0.62, 0.85, 1]
|
|
58
|
+
],
|
|
59
|
+
edge: [0.23, 0.31, 0.42, 0.18],
|
|
60
|
+
edgeHeavy: [0.23, 0.31, 0.42, 0.34],
|
|
61
|
+
clear: [0.96, 0.97, 0.98, 1]
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
const theme = { ...defaultTheme }
|
|
@@ -181,6 +194,7 @@ const ensureNodeCapacity = (count) => {
|
|
|
181
194
|
state.y = new Float32Array(nextCapacity)
|
|
182
195
|
state.relevance = new Float32Array(nextCapacity)
|
|
183
196
|
state.radius = new Float32Array(nextCapacity)
|
|
197
|
+
state.colorIndex = new Uint8Array(nextCapacity)
|
|
184
198
|
state.visible = new Uint8Array(nextCapacity)
|
|
185
199
|
state.highlighted = new Uint8Array(nextCapacity)
|
|
186
200
|
state.focused = new Uint8Array(nextCapacity)
|
|
@@ -199,11 +213,21 @@ const ensureEdgeCapacity = (count) => {
|
|
|
199
213
|
}
|
|
200
214
|
|
|
201
215
|
const nodeRadius = (relevance, kind) => {
|
|
202
|
-
const base = kind === 'cluster' ?
|
|
203
|
-
const modifier = Math.min(
|
|
216
|
+
const base = kind === 'cluster' ? 8.8 : 5.4
|
|
217
|
+
const modifier = Math.min(5.6, Math.max(0, relevance * 0.62))
|
|
204
218
|
return base + modifier
|
|
205
219
|
}
|
|
206
220
|
|
|
221
|
+
const segmentColorIndex = (segment) => {
|
|
222
|
+
const value = String(segment || '')
|
|
223
|
+
let hash = 0
|
|
224
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
225
|
+
hash = ((hash << 5) - hash + value.charCodeAt(index)) | 0
|
|
226
|
+
}
|
|
227
|
+
const palette = Array.isArray(theme.nodePalette) && theme.nodePalette.length > 0 ? theme.nodePalette : [theme.node]
|
|
228
|
+
return Math.abs(hash) % palette.length
|
|
229
|
+
}
|
|
230
|
+
|
|
207
231
|
const loadChunk = (chunk) => {
|
|
208
232
|
const nodes = Array.isArray(chunk?.nodes) ? chunk.nodes : []
|
|
209
233
|
const edges = Array.isArray(chunk?.edges) ? chunk.edges : []
|
|
@@ -223,6 +247,7 @@ const loadChunk = (chunk) => {
|
|
|
223
247
|
const title = typeof row?.[1] === 'string' ? row[1] : id
|
|
224
248
|
const x = Number.isFinite(row?.[2]) ? Number(row[2]) : 0
|
|
225
249
|
const y = Number.isFinite(row?.[3]) ? Number(row[3]) : 0
|
|
250
|
+
const segment = typeof row?.[5] === 'string' ? row[5] : ''
|
|
226
251
|
const kind = row?.[6] === 'cluster' ? 'cluster' : 'node'
|
|
227
252
|
const relevance = Number.isFinite(row?.[7]) ? Number(row[7]) : 0
|
|
228
253
|
|
|
@@ -233,6 +258,7 @@ const loadChunk = (chunk) => {
|
|
|
233
258
|
state.y[index] = y
|
|
234
259
|
state.relevance[index] = relevance
|
|
235
260
|
state.radius[index] = nodeRadius(relevance, kind)
|
|
261
|
+
state.colorIndex[index] = segmentColorIndex(segment || title)
|
|
236
262
|
state.visible[index] = 0
|
|
237
263
|
state.highlighted[index] = highlightedIds.has(id) ? 1 : 0
|
|
238
264
|
state.focused[index] = focusedIds.has(id) ? 1 : 0
|
|
@@ -349,6 +375,13 @@ const drawNodeLayer = (predicate, color, radiusBoost = 1) => {
|
|
|
349
375
|
gl.drawArrays(gl.POINTS, 0, positionCursor / 2)
|
|
350
376
|
}
|
|
351
377
|
|
|
378
|
+
const drawColoredNodeLayer = (predicate, radiusBoost = 1) => {
|
|
379
|
+
const palette = Array.isArray(theme.nodePalette) && theme.nodePalette.length > 0 ? theme.nodePalette : [theme.node]
|
|
380
|
+
for (let colorIndex = 0; colorIndex < palette.length; colorIndex += 1) {
|
|
381
|
+
drawNodeLayer((index) => predicate(index) && state.colorIndex[index] === colorIndex, palette[colorIndex], radiusBoost)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
352
385
|
const clear = () => {
|
|
353
386
|
if (!gl || !canvas) return
|
|
354
387
|
gl.viewport(0, 0, canvas.width, canvas.height)
|
|
@@ -398,15 +431,13 @@ const renderFrame = (now) => {
|
|
|
398
431
|
scheduleSettledRender(now)
|
|
399
432
|
}
|
|
400
433
|
|
|
401
|
-
|
|
402
|
-
(index) => state.visible[index] === 1 && state.selected[index] === 0 && state.highlighted[index] === 0,
|
|
403
|
-
theme.node,
|
|
434
|
+
drawColoredNodeLayer(
|
|
435
|
+
(index) => state.visible[index] === 1 && state.kinds[index] !== 'cluster' && state.selected[index] === 0 && state.highlighted[index] === 0 && state.focused[index] === 0,
|
|
404
436
|
1
|
|
405
437
|
)
|
|
406
438
|
|
|
407
|
-
|
|
439
|
+
drawColoredNodeLayer(
|
|
408
440
|
(index) => state.visible[index] === 1 && state.kinds[index] === 'cluster' && state.selected[index] === 0,
|
|
409
|
-
theme.nodeCluster,
|
|
410
441
|
1.15
|
|
411
442
|
)
|
|
412
443
|
|
package/package.json
CHANGED