dsh-memory-engine 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/lib/client.js +232 -42
- package/lib/client.src.js +111 -22
- package/lib/config-write.js +2 -0
- package/lib/config.js +7 -2
- package/lib/index.js +8 -4
- package/lib/index.js.bak-before-titlefix +235 -0
- package/lib/map.js +1 -0
- package/lib/render/canvas.js +90 -20
- package/lib/render/layout.mjs +33 -0
- package/lib/render/render.mjs +15 -2
- package/package.json +1 -1
- package/scripts/build-client.mjs +4 -1
package/lib/render/canvas.js
CHANGED
|
@@ -4,8 +4,43 @@ export function createRenderer(canvas, data, options = {}) {
|
|
|
4
4
|
const performance = window.performance;
|
|
5
5
|
const ctx = canvas.getContext('2d', { alpha: false });
|
|
6
6
|
if (!ctx) throw new Error('Canvas 2D 不可用');
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const THEMES = {
|
|
8
|
+
classic: {
|
|
9
|
+
colors: { '人格':[255,215,106], '印象':[143,216,255], '知识':[126,231,178], '日记':[190,160,255], '对话':[255,160,140], '情绪':[255,90,90] },
|
|
10
|
+
fallback:[145,205,255], background:'#03060c', grid:'rgba(40,90,140,0.055)',
|
|
11
|
+
link:'rgba(110,190,255,0.30)', linkHighlight:'rgba(160,220,255,0.22)',
|
|
12
|
+
pulse:[205,245,255], center:[255,255,255], fading:[255,110,110], glow:[0.7,0.19],
|
|
13
|
+
vignette:['rgba(0,0,0,0)','rgba(0,0,0,.78)'],
|
|
14
|
+
},
|
|
15
|
+
neon: {
|
|
16
|
+
colors: { '人格':[255,255,255], '印象':[63,242,224], '知识':[255,95,210], '事件':[165,107,255], '日记':[192,125,255], '对话':[255,147,233], '情绪':[255,66,130] },
|
|
17
|
+
fallback:[145,143,255], background:'#0a0410', grid:'rgba(156,70,210,0.07)',
|
|
18
|
+
link:['rgba(255,95,210,0.36)','rgba(165,107,255,0.36)'], linkHighlight:'rgba(224,175,255,0.24)',
|
|
19
|
+
pulse:[245,210,255], center:[255,255,255], fading:[255,90,165], glow:[0.7,0.19],
|
|
20
|
+
vignette:['rgba(10,4,16,0)','rgba(10,4,16,.78)'],
|
|
21
|
+
},
|
|
22
|
+
paper: {
|
|
23
|
+
colors: { '人格':[40,40,40], '印象':[107,107,107], '知识':[64,64,64], '事件':[85,85,85], '日记':[133,133,133], '对话':[116,116,116], '情绪':[76,76,76] },
|
|
24
|
+
fallback:[96,96,96], background:'#f7f5f0', grid:'rgba(80,78,73,0.022)',
|
|
25
|
+
link:'rgba(65,65,65,0.34)', linkHighlight:'rgba(100,100,100,0.055)',
|
|
26
|
+
pulse:[48,48,48], center:[36,36,36], fading:[126,126,126], glow:[0.025,0.006],
|
|
27
|
+
vignette:['rgba(135,132,125,0)','rgba(135,132,125,.08)'],
|
|
28
|
+
},
|
|
29
|
+
mono: {
|
|
30
|
+
colors: { '人格':[232,232,232], '印象':[200,200,200], '知识':[255,255,255], '事件':[138,138,138], '日记':[174,174,174], '对话':[218,218,218], '情绪':[160,160,160] },
|
|
31
|
+
fallback:[185,185,185], background:'#0d0d0d', grid:'rgba(130,130,130,0.045)',
|
|
32
|
+
link:'rgba(178,178,178,0.30)', linkHighlight:'rgba(210,210,210,0.22)',
|
|
33
|
+
pulse:[235,235,235], center:[255,255,255], fading:[150,150,150], glow:[0.7,0.19],
|
|
34
|
+
vignette:['rgba(0,0,0,0)','rgba(0,0,0,.78)'],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
let themeName = options.graphTheme ?? data.graphTheme ?? 'classic';
|
|
38
|
+
if (!Object.hasOwn(THEMES, themeName)) throw new Error('未知图谱配色');
|
|
39
|
+
let theme = THEMES[themeName];
|
|
40
|
+
let layoutName = options.graphLayout ?? data.graphLayout ?? 'sphere';
|
|
41
|
+
if (!['sphere', 'flat', 'spiral'].includes(layoutName)) throw new Error('未知图谱布局');
|
|
42
|
+
let sphereNodes = [];
|
|
43
|
+
const layouts = new Map();
|
|
9
44
|
let nodes = [], links = [], hubs = [], linkTwinkle = [], visibleLinks = [];
|
|
10
45
|
const byId = new Map(), adjacency = new Map(), sprites = new Map();
|
|
11
46
|
const pulses = [], firing = new Map(), selections = new Set();
|
|
@@ -24,13 +59,44 @@ function glowSprite(rgb) {
|
|
|
24
59
|
off.width = off.height = 96;
|
|
25
60
|
const brush = off.getContext('2d');
|
|
26
61
|
const gradient = brush.createRadialGradient(48,48,1,48,48,48);
|
|
27
|
-
gradient.addColorStop(0,'rgba('+rgb.join(',')+',0
|
|
28
|
-
gradient.addColorStop(0.4,'rgba('+rgb.join(',')+',
|
|
62
|
+
gradient.addColorStop(0,'rgba('+rgb.join(',')+','+theme.glow[0]+')');
|
|
63
|
+
gradient.addColorStop(0.4,'rgba('+rgb.join(',')+','+theme.glow[1]+')');
|
|
29
64
|
gradient.addColorStop(1,'rgba('+rgb.join(',')+',0)');
|
|
30
65
|
brush.fillStyle = gradient; brush.fillRect(0,0,96,96);
|
|
31
66
|
return off;
|
|
32
67
|
}
|
|
33
68
|
|
|
69
|
+
function recolor() {
|
|
70
|
+
sprites.clear();
|
|
71
|
+
for (const star of nodes) {
|
|
72
|
+
star._rgb = theme.colors[star.kind] || theme.fallback;
|
|
73
|
+
const key = star._rgb.join(',');
|
|
74
|
+
if (!sprites.has(key)) sprites.set(key, glowSprite(star._rgb));
|
|
75
|
+
star._glow = sprites.get(key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setTheme(nextTheme) {
|
|
80
|
+
if (destroyed || nextTheme === themeName) return;
|
|
81
|
+
if (!Object.hasOwn(THEMES, nextTheme)) throw new Error('未知图谱配色');
|
|
82
|
+
themeName = nextTheme;
|
|
83
|
+
theme = THEMES[nextTheme];
|
|
84
|
+
recolor();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setLayout(nextLayout) {
|
|
88
|
+
if (destroyed) return;
|
|
89
|
+
if (!['sphere', 'flat', 'spiral'].includes(nextLayout)) throw new Error('未知图谱布局');
|
|
90
|
+
if (layoutName === nextLayout && layouts.has(nextLayout)) return;
|
|
91
|
+
if (!layouts.has(nextLayout)) {
|
|
92
|
+
if (typeof options.layoutNodes !== 'function') throw new Error('缺少图谱布局函数');
|
|
93
|
+
layouts.set(nextLayout, options.layoutNodes(sphereNodes, nextLayout).map(node => ({ ...node.pos })));
|
|
94
|
+
}
|
|
95
|
+
const positions = layouts.get(nextLayout);
|
|
96
|
+
nodes.forEach((node, index) => { node.pos = { ...positions[index] }; });
|
|
97
|
+
layoutName = nextLayout;
|
|
98
|
+
}
|
|
99
|
+
|
|
34
100
|
// 字符串 → 32 位无符号哈希。给连线的闪烁参数做种子:
|
|
35
101
|
// 用 id 而不是 Math.random,保证同一条线在每次打开时节奏一致(不会每次刷新都跳),
|
|
36
102
|
// 但不同连线之间互不同步。
|
|
@@ -47,6 +113,10 @@ function glowSprite(rgb) {
|
|
|
47
113
|
if (destroyed) return;
|
|
48
114
|
spinSpeed=nextData.spinSpeed??0.35;
|
|
49
115
|
nodes = nextData.nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
116
|
+
sphereNodes = nextData.nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
117
|
+
layouts.clear();
|
|
118
|
+
layouts.set('sphere', sphereNodes.map(node => ({ ...node.pos })));
|
|
119
|
+
if (layoutName !== 'sphere') setLayout(layoutName);
|
|
50
120
|
links = nextData.links.map(link => [...link]);
|
|
51
121
|
byId.clear(); adjacency.clear(); sprites.clear(); pulses.length = 0; firing.clear();
|
|
52
122
|
selectedId=typeof options.selectedId==='string'?options.selectedId:null;
|
|
@@ -94,7 +164,6 @@ function glowSprite(rgb) {
|
|
|
94
164
|
.slice(0, LINK_DRAW_CAP);
|
|
95
165
|
}
|
|
96
166
|
for (const star of nodes) {
|
|
97
|
-
star._rgb = colors[star.kind] || fallback;
|
|
98
167
|
const count = Math.min(9, 4 + Math.round(Math.sqrt(star.degree) * 1.2));
|
|
99
168
|
star._dendrites = Array.from({length:count},(_,index) => ({
|
|
100
169
|
angle:index/count*Math.PI*2+random()*0.7,
|
|
@@ -102,11 +171,7 @@ for (const star of nodes) {
|
|
|
102
171
|
fork:random()<0.55 ? (random()-0.5)*1.5 : null
|
|
103
172
|
}));
|
|
104
173
|
}
|
|
105
|
-
|
|
106
|
-
const key = star._rgb.join(',');
|
|
107
|
-
if (!sprites.has(key)) sprites.set(key,glowSprite(star._rgb));
|
|
108
|
-
star._glow = sprites.get(key);
|
|
109
|
-
}
|
|
174
|
+
recolor();
|
|
110
175
|
|
|
111
176
|
}
|
|
112
177
|
|
|
@@ -210,8 +275,8 @@ function frame(now) {
|
|
|
210
275
|
}
|
|
211
276
|
nextFire=now+1400+Math.random()*3200;
|
|
212
277
|
}
|
|
213
|
-
ctx.globalCompositeOperation='source-over'; ctx.fillStyle=
|
|
214
|
-
ctx.strokeStyle=
|
|
278
|
+
ctx.globalCompositeOperation='source-over'; ctx.fillStyle=theme.background; ctx.fillRect(0,0,width,height);
|
|
279
|
+
ctx.strokeStyle=theme.grid;ctx.lineWidth=1;
|
|
215
280
|
for (let index=0;index<20;index++) {
|
|
216
281
|
const y=index/20*height;
|
|
217
282
|
ctx.beginPath();ctx.moveTo(0,y);ctx.bezierCurveTo(width*.3,y+Math.sin(now*.00007+index)*30,width*.7,y-Math.cos(now*.00006+index)*30,width,y);ctx.stroke();
|
|
@@ -246,13 +311,18 @@ function frame(now) {
|
|
|
246
311
|
}
|
|
247
312
|
}
|
|
248
313
|
if (linkPaths.length) {
|
|
249
|
-
|
|
314
|
+
if (Array.isArray(theme.link)) {
|
|
315
|
+
const gradient = ctx.createLinearGradient(0,0,width,height);
|
|
316
|
+
gradient.addColorStop(0,theme.link[0]);
|
|
317
|
+
gradient.addColorStop(1,theme.link[1]);
|
|
318
|
+
ctx.strokeStyle=gradient;
|
|
319
|
+
} else ctx.strokeStyle=theme.link;
|
|
250
320
|
ctx.lineWidth=1;
|
|
251
321
|
ctx.beginPath();
|
|
252
322
|
for (const p of linkPaths) { ctx.moveTo(p[0],p[1]); ctx.quadraticCurveTo(p[2],p[3],p[4],p[5]); }
|
|
253
323
|
ctx.stroke();
|
|
254
324
|
// 更亮的一层,让关联线看得清
|
|
255
|
-
ctx.strokeStyle=
|
|
325
|
+
ctx.strokeStyle=theme.linkHighlight;
|
|
256
326
|
ctx.lineWidth=2;
|
|
257
327
|
ctx.stroke();
|
|
258
328
|
}
|
|
@@ -265,7 +335,7 @@ function frame(now) {
|
|
|
265
335
|
const inverse=1-fraction;
|
|
266
336
|
const x=inverse*inverse*first.p.x+2*inverse*fraction*mx+fraction*fraction*second.p.x;
|
|
267
337
|
const y=inverse*inverse*first.p.y+2*inverse*fraction*my+fraction*fraction*second.p.y;
|
|
268
|
-
ctx.fillStyle='rgba(
|
|
338
|
+
ctx.fillStyle='rgba('+theme.pulse.join(',')+','+(.9*inverse)+')';ctx.beginPath();ctx.arc(x,y,2.5+2*inverse,0,Math.PI*2);ctx.fill();
|
|
269
339
|
}
|
|
270
340
|
for (const {star,p} of points) {
|
|
271
341
|
const act=firing.get(star.id)||0,ret=Math.max(.05,Math.min(1.2,star.retention));
|
|
@@ -305,9 +375,9 @@ function frame(now) {
|
|
|
305
375
|
ctx.globalAlpha=Math.min(1,.4+intensity*.7);ctx.drawImage(star._glow,p.x-glowRadius,p.y-glowRadius,glowRadius*2,glowRadius*2);ctx.globalAlpha=1;
|
|
306
376
|
ctx.fillStyle='rgba('+rgb+','+Math.min(1,.42*intensity)+')';ctx.beginPath();ctx.arc(p.x,p.y,radius,0,Math.PI*2);ctx.fill();
|
|
307
377
|
ctx.strokeStyle='rgba('+rgb+','+Math.min(1,.75*intensity)+')';ctx.lineWidth=1;ctx.stroke();
|
|
308
|
-
ctx.fillStyle='rgba(
|
|
378
|
+
ctx.fillStyle='rgba('+theme.center.join(',')+','+Math.min(1,.6*intensity)+')';ctx.beginPath();ctx.arc(p.x,p.y,radius*.42,0,Math.PI*2);ctx.fill();
|
|
309
379
|
if (star.retention<.25) {
|
|
310
|
-
ctx.strokeStyle='rgba(
|
|
380
|
+
ctx.strokeStyle='rgba('+theme.fading.join(',')+','+Math.max(0,.45-star.retention)+')';ctx.setLineDash([3,4]);
|
|
311
381
|
ctx.beginPath();ctx.arc(p.x,p.y,radius*3.2,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);
|
|
312
382
|
}
|
|
313
383
|
star._screen={x:p.x,y:p.y,r:Math.max(8,radius*2.6)};
|
|
@@ -318,7 +388,7 @@ function frame(now) {
|
|
|
318
388
|
}
|
|
319
389
|
ctx.globalCompositeOperation='source-over';
|
|
320
390
|
const vignette=ctx.createRadialGradient(width/2,height/2,Math.min(width,height)*.34,width/2,height/2,Math.max(width,height)*.8);
|
|
321
|
-
vignette.addColorStop(0,
|
|
391
|
+
vignette.addColorStop(0,theme.vignette[0]);vignette.addColorStop(1,theme.vignette[1]);
|
|
322
392
|
ctx.fillStyle=vignette;ctx.fillRect(0,0,width,height);
|
|
323
393
|
}
|
|
324
394
|
function start() {
|
|
@@ -371,12 +441,12 @@ function frame(now) {
|
|
|
371
441
|
if (destroyed) return;
|
|
372
442
|
stop(); destroyed=true;
|
|
373
443
|
selections.clear(); byId.clear(); adjacency.clear(); sprites.clear();
|
|
374
|
-
nodes=[]; links=[]; hubs=[];
|
|
444
|
+
nodes=[]; links=[]; hubs=[]; sphereNodes=[]; layouts.clear();
|
|
375
445
|
}
|
|
376
446
|
|
|
377
447
|
setData(data);
|
|
378
448
|
return {
|
|
379
|
-
start, stop, resize, setData, destroy,
|
|
449
|
+
start, stop, resize, setData, setTheme, setLayout, destroy,
|
|
380
450
|
fireNeuron(id) { if (!destroyed && byId.has(id)) fire(id,performance.now()); },
|
|
381
451
|
onSelect(callback) {
|
|
382
452
|
if (!destroyed) selections.add(callback);
|
package/lib/render/layout.mjs
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
const SEED = 20261030;
|
|
2
2
|
|
|
3
|
+
export function layoutPositions(nodes, layout = 'sphere') {
|
|
4
|
+
if (!['sphere', 'flat', 'spiral'].includes(layout)) throw new Error('未知图谱布局');
|
|
5
|
+
if (layout === 'sphere') return nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
6
|
+
const memories = nodes.filter(node => !node.isCore).sort((left, right) =>
|
|
7
|
+
(right.degree || 0) - (left.degree || 0) || (String(left.id) < String(right.id) ? -1 : String(left.id) > String(right.id) ? 1 : 0));
|
|
8
|
+
const positions = new Map();
|
|
9
|
+
function jitter(id, axis) {
|
|
10
|
+
let hash = 20261030;
|
|
11
|
+
for (const character of String(id) + ':' + axis) hash = Math.imul(hash ^ character.codePointAt(0), 16777619);
|
|
12
|
+
hash = Math.imul(hash ^ (hash >>> 16), 0x45d9f3b);
|
|
13
|
+
return ((hash ^ (hash >>> 16)) >>> 0) / 4294967295 * 2 - 1;
|
|
14
|
+
}
|
|
15
|
+
if (layout === 'flat') {
|
|
16
|
+
for (let ring = 0; ring < 3; ring++) {
|
|
17
|
+
const members = memories.slice(Math.ceil(memories.length * ring / 3), Math.ceil(memories.length * (ring + 1) / 3));
|
|
18
|
+
const radius = [30, 55, 80][ring];
|
|
19
|
+
members.forEach((node, index) => {
|
|
20
|
+
const angle = index / members.length * Math.PI * 2;
|
|
21
|
+
positions.set(node.id, { x: Math.cos(angle) * radius + jitter(node.id, 'x') * 3,
|
|
22
|
+
y: Math.sin(angle) * radius + jitter(node.id, 'y') * 3, z: 0 });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
memories.forEach((node, index) => {
|
|
27
|
+
const fraction = index / Math.max(1, memories.length - 1);
|
|
28
|
+
const angle = fraction * 3.2 * Math.PI + index % 3 * Math.PI * 2 / 3 + jitter(node.id, 'angle') * 0.35;
|
|
29
|
+
const radius = 18 + fraction * 92;
|
|
30
|
+
positions.set(node.id, { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: jitter(node.id, 'z') * 14 });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return nodes.map(node => ({ ...node, pos: node.isCore ? { x: 0, y: 0, z: 0 } : positions.get(node.id) }));
|
|
34
|
+
}
|
|
35
|
+
|
|
3
36
|
export function layoutMemories(nodes) {
|
|
4
37
|
if (!nodes.some(node => node.isCore)) return layoutBrain(nodes);
|
|
5
38
|
const golden = Math.PI * (3 - Math.sqrt(5));
|
package/lib/render/render.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { stripRendererExport } from '../../scripts/lib/strip-export.mjs';
|
|
3
|
+
import { layoutPositions } from './layout.mjs';
|
|
3
4
|
|
|
4
5
|
const rendererSource = stripRendererExport(fs.readFileSync(new URL('./canvas.js', import.meta.url), 'utf8'));
|
|
5
6
|
|
|
@@ -31,10 +32,21 @@ export function renderHtml(data, title = '记忆神经网络') {
|
|
|
31
32
|
.review h2{font-size:14px;color:#ffdb82;margin:0 0 8px}.row{display:flex;gap:7px;align-items:center;margin:5px 0;color:#b1c6d9}
|
|
32
33
|
.row .name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bar{width:52px;height:5px;border-radius:5px;background:#335066;overflow:hidden}.bar i{display:block;height:100%;background:#6bc9fa}
|
|
33
34
|
.tip{position:fixed;right:18px;bottom:18px;color:#7b99ad;pointer-events:none}
|
|
35
|
+
body[data-graph-theme=neon]{--text:#f4dfff;--muted:#ba9aca;--accent:#ff9de1;--border:#704580;--panel:#170d21ee;--button:#271333}
|
|
36
|
+
body[data-graph-theme=paper]{color-scheme:light;--text:#303030;--muted:#72716d;--accent:#404040;--border:#b5b1a9;--panel:#f7f5f0f2;--button:#eeece6}
|
|
37
|
+
body[data-graph-theme=mono]{--text:#eee;--muted:#aaa;--accent:#eee;--border:#666;--panel:#171717ee;--button:#252525}
|
|
38
|
+
body:not([data-graph-theme=classic]){color:var(--text)}
|
|
39
|
+
body:not([data-graph-theme=classic]) .hud{color:var(--text);text-shadow:none}
|
|
40
|
+
body:not([data-graph-theme=classic]) .hud strong,body:not([data-graph-theme=classic]) h2{color:var(--accent)}
|
|
41
|
+
body:not([data-graph-theme=classic]) .sub,body:not([data-graph-theme=classic]) .tip,body:not([data-graph-theme=classic]) .meta{color:var(--muted)}
|
|
42
|
+
body:not([data-graph-theme=classic]) .panel,body:not([data-graph-theme=classic]) .review{background:var(--panel);border-color:var(--border);box-shadow:none}
|
|
43
|
+
body:not([data-graph-theme=classic]) .row,body:not([data-graph-theme=classic]) .close{color:var(--text)}
|
|
44
|
+
body:not([data-graph-theme=classic]) .copy{color:var(--text);background:var(--button);border-color:var(--border)}
|
|
45
|
+
body:not([data-graph-theme=classic]) .bar{background:var(--border)}body:not([data-graph-theme=classic]) .bar i{background:var(--accent)}
|
|
34
46
|
@media(max-width:700px){.hud{top:8px;left:10px}.hud .sub,.tip{display:none}.review{left:10px;bottom:10px;max-height:24vh}.panel{top:8px;right:8px;max-height:55vh}}
|
|
35
47
|
</style>
|
|
36
48
|
</head>
|
|
37
|
-
<body>
|
|
49
|
+
<body data-graph-theme="${escapeHtml(data.graphTheme || 'classic')}">
|
|
38
50
|
<canvas id="map" aria-label="记忆神经网络"></canvas>
|
|
39
51
|
<div class="hud"><strong>${safeTitle}</strong><div><span id="total"></span> 个神经元 · <span id="linkCount"></span> 条突触</div><div class="sub">大小=重要度和连接数 · 亮度=留存</div></div>
|
|
40
52
|
<section class="panel" id="panel"><button class="close" id="close" aria-label="关闭">×</button><div id="kind"></div><h2 id="title"></h2><div class="meta" id="meta"></div><div class="text" id="body"></div><button class="copy" id="copy" hidden>复制全文</button></section>
|
|
@@ -44,7 +56,8 @@ export function renderHtml(data, title = '记忆神经网络') {
|
|
|
44
56
|
const DATA = ${embedded};
|
|
45
57
|
const canvas = document.getElementById('map');
|
|
46
58
|
${rendererSource}
|
|
47
|
-
|
|
59
|
+
${layoutPositions.toString()}
|
|
60
|
+
const renderer = createRenderer(canvas, { ...DATA, nodes: DATA.stars }, { layoutNodes: layoutPositions });
|
|
48
61
|
document.getElementById('close').onclick=() => { document.getElementById('panel').style.display='none'; };
|
|
49
62
|
document.getElementById('total').textContent=DATA.stats.total;
|
|
50
63
|
document.getElementById('linkCount').textContent=DATA.links.length;
|
package/package.json
CHANGED
package/scripts/build-client.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { stripRendererExport } from './lib/strip-export.mjs';
|
|
4
|
+
import { layoutPositions } from '../lib/render/layout.mjs';
|
|
4
5
|
|
|
5
6
|
const template = fs.readFileSync(new URL('../lib/client.src.js', import.meta.url), 'utf8');
|
|
6
7
|
const renderer = fs.readFileSync(new URL('../lib/render/canvas.js', import.meta.url), 'utf8');
|
|
7
8
|
const marker = '/* __CANVAS_RENDERER__ */';
|
|
8
9
|
if (template.split(marker).length !== 2) throw new Error('Expected exactly one renderer marker');
|
|
10
|
+
const layoutMarker = '/* __GRAPH_LAYOUT__ */';
|
|
11
|
+
if (template.split(layoutMarker).length !== 2) throw new Error('Expected exactly one layout marker');
|
|
9
12
|
const bundled = '// 由 scripts/build-client.mjs 生成,别直接改这个文件\n'
|
|
10
|
-
+ template.replace(marker, () => stripRendererExport(renderer));
|
|
13
|
+
+ template.replace(marker, () => stripRendererExport(renderer)).replace(layoutMarker, () => layoutPositions.toString());
|
|
11
14
|
const target = new URL('../lib/client.js', import.meta.url);
|
|
12
15
|
if (process.argv.includes('--check')) {
|
|
13
16
|
if (fs.readFileSync(target, 'utf8') !== bundled) throw new Error('lib/client.js 已过期,请跑 pnpm build');
|