nodalix 0.1.0
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 +726 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +3905 -0
- package/dist/index.mjs +3840 -0
- package/package.json +60 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3840 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/index.js
|
|
4
|
+
import React3, { useEffect as useEffect2, useState as useState2, useMemo as useMemo2 } from "react";
|
|
5
|
+
|
|
6
|
+
// src/Components/Graph.js
|
|
7
|
+
import React2, { useRef, useEffect, useState, useMemo } from "react";
|
|
8
|
+
import { DataSet, Network } from "vis";
|
|
9
|
+
|
|
10
|
+
// src/Components/Options.js
|
|
11
|
+
var getGridTheme = (isDark) => isDark ? {
|
|
12
|
+
background: "#0b1220",
|
|
13
|
+
minor: "rgba(148,163,184,0.14)",
|
|
14
|
+
major: "rgba(148,163,184,0.28)"
|
|
15
|
+
} : {
|
|
16
|
+
background: "#f8fafc",
|
|
17
|
+
minor: "rgba(15,23,42,0.07)",
|
|
18
|
+
major: "rgba(15,23,42,0.14)"
|
|
19
|
+
};
|
|
20
|
+
var getNiceGridStep = (scale) => {
|
|
21
|
+
const targetPx = 32;
|
|
22
|
+
const raw = targetPx / Math.max(scale, 0.05);
|
|
23
|
+
const pow = Math.pow(10, Math.floor(Math.log10(raw)));
|
|
24
|
+
const norm = raw / pow;
|
|
25
|
+
let nice;
|
|
26
|
+
if (norm <= 1) nice = 1;
|
|
27
|
+
else if (norm <= 2) nice = 2;
|
|
28
|
+
else if (norm <= 5) nice = 5;
|
|
29
|
+
else nice = 10;
|
|
30
|
+
return nice * pow;
|
|
31
|
+
};
|
|
32
|
+
var drawNetworkGrid = (ctx, network, isDark) => {
|
|
33
|
+
if (!ctx || !network) return;
|
|
34
|
+
const theme = getGridTheme(isDark);
|
|
35
|
+
const canvas = ctx.canvas;
|
|
36
|
+
const w = canvas.clientWidth;
|
|
37
|
+
const h = canvas.clientHeight;
|
|
38
|
+
if (!w || !h) return;
|
|
39
|
+
const tl = network.DOMtoCanvas({ x: 0, y: 0 });
|
|
40
|
+
const br = network.DOMtoCanvas({ x: w, y: h });
|
|
41
|
+
const minX = Math.min(tl.x, br.x);
|
|
42
|
+
const maxX = Math.max(tl.x, br.x);
|
|
43
|
+
const minY = Math.min(tl.y, br.y);
|
|
44
|
+
const maxY = Math.max(tl.y, br.y);
|
|
45
|
+
const scale = network.getScale();
|
|
46
|
+
const step = getNiceGridStep(scale);
|
|
47
|
+
const majorEvery = 5;
|
|
48
|
+
const lineWidth = 1 / Math.max(scale, 0.05);
|
|
49
|
+
ctx.save();
|
|
50
|
+
ctx.fillStyle = theme.background;
|
|
51
|
+
ctx.fillRect(minX, minY, maxX - minX, maxY - minY);
|
|
52
|
+
const startX = Math.floor(minX / step) * step;
|
|
53
|
+
const startY = Math.floor(minY / step) * step;
|
|
54
|
+
let col = 0;
|
|
55
|
+
for (let x = startX; x <= maxX; x += step, col++) {
|
|
56
|
+
const isMajor = col % majorEvery === 0;
|
|
57
|
+
ctx.strokeStyle = isMajor ? theme.major : theme.minor;
|
|
58
|
+
ctx.lineWidth = isMajor ? lineWidth * 1.35 : lineWidth;
|
|
59
|
+
ctx.beginPath();
|
|
60
|
+
ctx.moveTo(x, minY);
|
|
61
|
+
ctx.lineTo(x, maxY);
|
|
62
|
+
ctx.stroke();
|
|
63
|
+
}
|
|
64
|
+
let row = 0;
|
|
65
|
+
for (let y = startY; y <= maxY; y += step, row++) {
|
|
66
|
+
const isMajor = row % majorEvery === 0;
|
|
67
|
+
ctx.strokeStyle = isMajor ? theme.major : theme.minor;
|
|
68
|
+
ctx.lineWidth = isMajor ? lineWidth * 1.35 : lineWidth;
|
|
69
|
+
ctx.beginPath();
|
|
70
|
+
ctx.moveTo(minX, y);
|
|
71
|
+
ctx.lineTo(maxX, y);
|
|
72
|
+
ctx.stroke();
|
|
73
|
+
}
|
|
74
|
+
ctx.restore();
|
|
75
|
+
};
|
|
76
|
+
var buildInteractionOptions = (dragNodes = true) => ({
|
|
77
|
+
selectable: true,
|
|
78
|
+
multiselect: true,
|
|
79
|
+
dragNodes: !!dragNodes,
|
|
80
|
+
dragView: true,
|
|
81
|
+
zoomView: true,
|
|
82
|
+
hover: true,
|
|
83
|
+
hoverConnectedEdges: false,
|
|
84
|
+
tooltipDelay: 100
|
|
85
|
+
});
|
|
86
|
+
var buildOptions = (isDark, { dragNodes = true } = {}) => {
|
|
87
|
+
const palette = isDark ? {
|
|
88
|
+
text: "#e5e7eb",
|
|
89
|
+
subtext: "#9ca3af",
|
|
90
|
+
edge: "#6ea8ff",
|
|
91
|
+
nodeBorder: "#93c5fd",
|
|
92
|
+
nodeBg: "#0b1220",
|
|
93
|
+
boxBorder: "#374151",
|
|
94
|
+
labelBg: "rgba(17,24,39,.6)",
|
|
95
|
+
mainBorder: "#60a5fa",
|
|
96
|
+
mainBg: "#0f172a"
|
|
97
|
+
} : {
|
|
98
|
+
text: "#111827",
|
|
99
|
+
subtext: "#4b5563",
|
|
100
|
+
edge: "rgb(50, 87, 155)",
|
|
101
|
+
nodeBorder: "#1f3a5f",
|
|
102
|
+
nodeBg: "#ffffff",
|
|
103
|
+
boxBorder: "rgb(200,200,200)",
|
|
104
|
+
labelBg: "#ffffff",
|
|
105
|
+
mainBorder: "#1d4ed8",
|
|
106
|
+
mainBg: "#ffffff"
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
layout: { hierarchical: false },
|
|
110
|
+
// فیزیک خاموش برای چیدمان قطعی
|
|
111
|
+
physics: { enabled: false },
|
|
112
|
+
interaction: buildInteractionOptions(dragNodes),
|
|
113
|
+
edges: {
|
|
114
|
+
width: 2,
|
|
115
|
+
smooth: {
|
|
116
|
+
type: "cubicBezier",
|
|
117
|
+
forceDirection: "horizontal",
|
|
118
|
+
roundness: 0.2
|
|
119
|
+
},
|
|
120
|
+
arrows: { to: { enabled: true } },
|
|
121
|
+
font: {
|
|
122
|
+
size: 14,
|
|
123
|
+
align: "middle",
|
|
124
|
+
color: palette.text,
|
|
125
|
+
background: palette.labelBg,
|
|
126
|
+
face: "Vazir"
|
|
127
|
+
},
|
|
128
|
+
color: {
|
|
129
|
+
color: palette.edge,
|
|
130
|
+
highlight: palette.edge,
|
|
131
|
+
hover: palette.edge,
|
|
132
|
+
inherit: false
|
|
133
|
+
},
|
|
134
|
+
chosen: {
|
|
135
|
+
edge(values, id, selected) {
|
|
136
|
+
if (selected) {
|
|
137
|
+
values.shadow = true;
|
|
138
|
+
values.shadowColor = values.color || "rgba(0,0,0,1)";
|
|
139
|
+
values.shadowSize = 10;
|
|
140
|
+
values.shadowX = 0;
|
|
141
|
+
values.shadowY = 0;
|
|
142
|
+
values.width = 3;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
// پیشفرض نودها
|
|
148
|
+
nodes: {
|
|
149
|
+
shape: "dot",
|
|
150
|
+
size: 14,
|
|
151
|
+
borderWidth: 1,
|
|
152
|
+
borderWidthSelected: 2,
|
|
153
|
+
font: {
|
|
154
|
+
size: 12,
|
|
155
|
+
face: "Vazir",
|
|
156
|
+
color: palette.text
|
|
157
|
+
},
|
|
158
|
+
color: {
|
|
159
|
+
border: palette.nodeBorder,
|
|
160
|
+
background: palette.nodeBg,
|
|
161
|
+
highlight: {
|
|
162
|
+
border: palette.mainBorder,
|
|
163
|
+
background: palette.nodeBg
|
|
164
|
+
},
|
|
165
|
+
hover: {
|
|
166
|
+
border: palette.mainBorder,
|
|
167
|
+
background: palette.nodeBg
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
shadow: false
|
|
171
|
+
},
|
|
172
|
+
groups: {
|
|
173
|
+
/**
|
|
174
|
+
* MAIN nodes (type: "main") — formerly "address"
|
|
175
|
+
*/
|
|
176
|
+
main: {
|
|
177
|
+
shape: "circularImage",
|
|
178
|
+
icon: { face: "FontAwesome", code: "\uF007", size: 50, color: "black" },
|
|
179
|
+
font: { size: 13, face: "Vazir", color: palette.edge, align: "left" },
|
|
180
|
+
borderWidth: 2,
|
|
181
|
+
borderColor: "#FF5733",
|
|
182
|
+
color: { background: "#ffffff" },
|
|
183
|
+
shapeProperties: { useBorderWithImage: true },
|
|
184
|
+
align: "horizontal",
|
|
185
|
+
chosen: {
|
|
186
|
+
node(values) {
|
|
187
|
+
values.borderWidth = 3;
|
|
188
|
+
values.size = 22;
|
|
189
|
+
values.shadow = true;
|
|
190
|
+
values.shadowColor = "rgba(0,0,0,.25)";
|
|
191
|
+
values.shadowSize = 14;
|
|
192
|
+
values.shadowX = 0;
|
|
193
|
+
values.shadowY = 2;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* EDGE INFO GROUP (نودهای اطلاعات یال / Tr)
|
|
199
|
+
*/
|
|
200
|
+
edgeInfo: {
|
|
201
|
+
shape: "dot",
|
|
202
|
+
size: 8,
|
|
203
|
+
borderWidth: 1,
|
|
204
|
+
font: {
|
|
205
|
+
size: 10,
|
|
206
|
+
face: "Vazir",
|
|
207
|
+
color: palette.subtext,
|
|
208
|
+
align: "center"
|
|
209
|
+
},
|
|
210
|
+
color: {
|
|
211
|
+
border: "rgba(107,114,128,0.55)",
|
|
212
|
+
background: "rgba(107,114,128,0.28)",
|
|
213
|
+
highlight: {
|
|
214
|
+
border: "rgba(107,114,128,0.7)",
|
|
215
|
+
background: "rgba(107,114,128,0.35)"
|
|
216
|
+
},
|
|
217
|
+
hover: {
|
|
218
|
+
border: "rgba(107,114,128,0.7)",
|
|
219
|
+
background: "rgba(107,114,128,0.35)"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
chosen: {
|
|
223
|
+
node(values) {
|
|
224
|
+
values.borderWidth = 1;
|
|
225
|
+
values.size = 8;
|
|
226
|
+
values.shadow = false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
sub: {
|
|
231
|
+
shape: "dot",
|
|
232
|
+
size: 6,
|
|
233
|
+
font: { size: 13, family: "Arial", color: palette.edge },
|
|
234
|
+
color: {
|
|
235
|
+
border: palette.edge,
|
|
236
|
+
background: palette.edge,
|
|
237
|
+
highlight: { background: palette.edge, border: palette.edge }
|
|
238
|
+
},
|
|
239
|
+
borderWidth: 0
|
|
240
|
+
},
|
|
241
|
+
hotWallet: {
|
|
242
|
+
shape: "text",
|
|
243
|
+
font: { size: 12, face: "Vazir", color: palette.edge, align: "left" },
|
|
244
|
+
borderWidth: 0,
|
|
245
|
+
size: 0,
|
|
246
|
+
color: {
|
|
247
|
+
background: "transparent",
|
|
248
|
+
border: "transparent",
|
|
249
|
+
highlight: { background: "transparent", border: "transparent" }
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
labelNode: {
|
|
253
|
+
font: { size: 12, face: "Vazir", color: palette.subtext, align: "center" },
|
|
254
|
+
borderWidth: 1,
|
|
255
|
+
borderColor: "#FF5733",
|
|
256
|
+
align: "horizontal",
|
|
257
|
+
shape: "box",
|
|
258
|
+
color: {
|
|
259
|
+
border: palette.boxBorder,
|
|
260
|
+
background: isDark ? "rgba(31,41,55,.6)" : "#fff"
|
|
261
|
+
},
|
|
262
|
+
size: 15
|
|
263
|
+
},
|
|
264
|
+
Arrow: {
|
|
265
|
+
font: { size: 22, face: "Vazir", color: palette.edge, align: "center" },
|
|
266
|
+
borderWidth: 0,
|
|
267
|
+
align: "center",
|
|
268
|
+
shape: "text",
|
|
269
|
+
color: {
|
|
270
|
+
background: "transparent",
|
|
271
|
+
border: "transparent",
|
|
272
|
+
highlight: { background: "transparent", border: "transparent" }
|
|
273
|
+
},
|
|
274
|
+
size: 16
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/Components/miladiCalendar.js
|
|
281
|
+
function MiladiCalendar(time) {
|
|
282
|
+
if (time.toString().length === 10) {
|
|
283
|
+
time = time * 1e3;
|
|
284
|
+
}
|
|
285
|
+
const date = new Date(time);
|
|
286
|
+
const year = date.getFullYear();
|
|
287
|
+
const month = date.getMonth() + 1;
|
|
288
|
+
const day = date.getDate();
|
|
289
|
+
const hour = date.getHours();
|
|
290
|
+
const minute = date.getMinutes();
|
|
291
|
+
const second = date.getSeconds();
|
|
292
|
+
return {
|
|
293
|
+
year,
|
|
294
|
+
month,
|
|
295
|
+
day,
|
|
296
|
+
hour,
|
|
297
|
+
minute,
|
|
298
|
+
second
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/graphConfig.js
|
|
303
|
+
var NODE_TYPE = {
|
|
304
|
+
MAIN: "main",
|
|
305
|
+
SUB: "sub"
|
|
306
|
+
};
|
|
307
|
+
var EDGE_LABEL_FIELDS = {
|
|
308
|
+
VALUE: "value",
|
|
309
|
+
SUB_TEXT: "subText",
|
|
310
|
+
SUB_VALUE: "subValue",
|
|
311
|
+
TIME: "time"
|
|
312
|
+
};
|
|
313
|
+
var DEFAULT_EDGE_LABEL_FIELDS = [
|
|
314
|
+
EDGE_LABEL_FIELDS.VALUE,
|
|
315
|
+
EDGE_LABEL_FIELDS.SUB_TEXT,
|
|
316
|
+
EDGE_LABEL_FIELDS.TIME
|
|
317
|
+
];
|
|
318
|
+
var EDGE_LINE_STYLE = {
|
|
319
|
+
SOLID: "solid",
|
|
320
|
+
DASHED: "dashed",
|
|
321
|
+
DOTTED: "dotted"
|
|
322
|
+
};
|
|
323
|
+
var DEFAULT_EDGE_LINE_STYLE = EDGE_LINE_STYLE.SOLID;
|
|
324
|
+
function normalizeEdgeLineStyle(style) {
|
|
325
|
+
const allowed = new Set(Object.values(EDGE_LINE_STYLE));
|
|
326
|
+
return allowed.has(style) ? style : DEFAULT_EDGE_LINE_STYLE;
|
|
327
|
+
}
|
|
328
|
+
function resolveEdgeVisStyle(options = {}) {
|
|
329
|
+
const {
|
|
330
|
+
isDark = false,
|
|
331
|
+
paintedColor = null,
|
|
332
|
+
paintedLineStyle = null
|
|
333
|
+
} = options;
|
|
334
|
+
const lineStyle = normalizeEdgeLineStyle(paintedLineStyle || DEFAULT_EDGE_LINE_STYLE);
|
|
335
|
+
const defaultColor = isDark ? "#6ea8ff" : "rgb(50, 87, 155)";
|
|
336
|
+
const color = paintedColor || defaultColor;
|
|
337
|
+
let dashes = false;
|
|
338
|
+
if (lineStyle === EDGE_LINE_STYLE.DASHED) dashes = [10, 8];
|
|
339
|
+
if (lineStyle === EDGE_LINE_STYLE.DOTTED) dashes = [2, 7];
|
|
340
|
+
return {
|
|
341
|
+
width: 2,
|
|
342
|
+
dashes,
|
|
343
|
+
smooth: {
|
|
344
|
+
type: "cubicBezier",
|
|
345
|
+
forceDirection: "horizontal",
|
|
346
|
+
roundness: 0.2
|
|
347
|
+
},
|
|
348
|
+
arrows: { to: { enabled: true, scaleFactor: 0.85 } },
|
|
349
|
+
color: {
|
|
350
|
+
color,
|
|
351
|
+
highlight: color,
|
|
352
|
+
hover: color,
|
|
353
|
+
inherit: false
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function mirrorEdgeRef(edge, overrides = {}) {
|
|
358
|
+
return {
|
|
359
|
+
id: overrides.id ?? (edge == null ? void 0 : edge.id),
|
|
360
|
+
value: (edge == null ? void 0 : edge.value) ?? null,
|
|
361
|
+
subText: (edge == null ? void 0 : edge.subText) ?? null,
|
|
362
|
+
subValue: (edge == null ? void 0 : edge.subValue) ?? null,
|
|
363
|
+
time: (edge == null ? void 0 : edge.time) ?? Math.floor(Date.now() / 1e3)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
var DEFAULT_RATE_COLOR_RANGES = [
|
|
367
|
+
{ min: 70, max: Infinity, color: "red" },
|
|
368
|
+
{ min: 50, max: 70, color: "orange" }
|
|
369
|
+
];
|
|
370
|
+
function normalizeEdgeLabelFields(fields) {
|
|
371
|
+
if (!Array.isArray(fields) || !fields.length) return [...DEFAULT_EDGE_LABEL_FIELDS];
|
|
372
|
+
const allowed = new Set(Object.values(EDGE_LABEL_FIELDS));
|
|
373
|
+
const normalized = fields.filter((f) => allowed.has(f));
|
|
374
|
+
return normalized.length ? normalized : [...DEFAULT_EDGE_LABEL_FIELDS];
|
|
375
|
+
}
|
|
376
|
+
function normalizeRateColorRanges(ranges) {
|
|
377
|
+
if (!Array.isArray(ranges) || !ranges.length) return [...DEFAULT_RATE_COLOR_RANGES];
|
|
378
|
+
return ranges.filter((r) => r && r.color).map((r) => ({
|
|
379
|
+
min: r.min ?? -Infinity,
|
|
380
|
+
max: r.max ?? Infinity,
|
|
381
|
+
color: r.color
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
function getRateBorderColor(rate, ranges, defaultColor) {
|
|
385
|
+
if (rate == null || !Number.isFinite(Number(rate))) return defaultColor;
|
|
386
|
+
const value = Number(rate);
|
|
387
|
+
const bands = normalizeRateColorRanges(ranges);
|
|
388
|
+
for (const band of bands) {
|
|
389
|
+
const min = band.min ?? -Infinity;
|
|
390
|
+
const max = band.max ?? Infinity;
|
|
391
|
+
if (max === Infinity) {
|
|
392
|
+
if (value >= min) return band.color;
|
|
393
|
+
} else if (value >= min && value < max) {
|
|
394
|
+
return band.color;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return defaultColor;
|
|
398
|
+
}
|
|
399
|
+
var formatTime = (timestamp) => {
|
|
400
|
+
const m = MiladiCalendar(timestamp);
|
|
401
|
+
return `\u200E ${m.year}/${m.month}/${m.day} - ${m.hour}:${m.minute}`;
|
|
402
|
+
};
|
|
403
|
+
function buildEdgeInfoLabel(edgeData, fields = DEFAULT_EDGE_LABEL_FIELDS) {
|
|
404
|
+
const list = normalizeEdgeLabelFields(fields);
|
|
405
|
+
if (!edgeData) return "";
|
|
406
|
+
const lines = [];
|
|
407
|
+
const hasValue = list.includes(EDGE_LABEL_FIELDS.VALUE);
|
|
408
|
+
const hasSubText = list.includes(EDGE_LABEL_FIELDS.SUB_TEXT);
|
|
409
|
+
const hasSubValue = list.includes(EDGE_LABEL_FIELDS.SUB_VALUE);
|
|
410
|
+
const hasTime = list.includes(EDGE_LABEL_FIELDS.TIME);
|
|
411
|
+
if (hasValue) {
|
|
412
|
+
let line = `\u200E${Number(edgeData.value || 0).toLocaleString()}`;
|
|
413
|
+
if (hasSubText && edgeData.subText) line += ` ${edgeData.subText}`;
|
|
414
|
+
lines.push(line);
|
|
415
|
+
} else if (hasSubText && edgeData.subText) {
|
|
416
|
+
lines.push(String(edgeData.subText));
|
|
417
|
+
}
|
|
418
|
+
if (hasSubValue) {
|
|
419
|
+
const v = edgeData.subValue;
|
|
420
|
+
lines.push(
|
|
421
|
+
typeof v === "number" && !isNaN(v) ? `\u200E${v.toLocaleString()} USD` : "\u0642\u06CC\u0645\u062A \u0646\u0627\u0645\u0634\u062E\u0635"
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (hasTime) {
|
|
425
|
+
const t = edgeData.time;
|
|
426
|
+
lines.push(
|
|
427
|
+
typeof t === "number" && !isNaN(t) ? formatTime(t) : "\u0632\u0645\u0627\u0646 \u0646\u0627\u0645\u0634\u062E\u0635"
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return lines.join(" \n ");
|
|
431
|
+
}
|
|
432
|
+
function getNodeDisplayLabel(item) {
|
|
433
|
+
if (!item) return "";
|
|
434
|
+
if (item.type === NODE_TYPE.SUB) return item.subText ?? "";
|
|
435
|
+
if (item.label != null) return item.label;
|
|
436
|
+
if (item.entity != null) return item.entity.name ?? "";
|
|
437
|
+
const text = item.text ?? "";
|
|
438
|
+
return `...${String(text).substring(0, 7)}`;
|
|
439
|
+
}
|
|
440
|
+
var DEFAULT_NODE_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
441
|
+
<circle cx="32" cy="32" r="31" fill="#f1f5f9" stroke="#cbd5e1" stroke-width="2"/>
|
|
442
|
+
<circle cx="32" cy="24" r="9" fill="#94a3b8"/>
|
|
443
|
+
<path fill="#94a3b8" d="M14 52c3.2-9.6 11.2-14 18-14s14.8 4.4 18 14H14z"/>
|
|
444
|
+
</svg>`;
|
|
445
|
+
var DEFAULT_NODE_IMAGE = `data:image/svg+xml,${encodeURIComponent(
|
|
446
|
+
DEFAULT_NODE_ICON_SVG
|
|
447
|
+
)}`;
|
|
448
|
+
var hasEntityImage = (value) => typeof value === "string" && value.trim().length > 0;
|
|
449
|
+
var GRAPH_LANGUAGE = {
|
|
450
|
+
FA: "fa",
|
|
451
|
+
EN: "en"
|
|
452
|
+
};
|
|
453
|
+
function normalizeGraphLanguage(value) {
|
|
454
|
+
return value === GRAPH_LANGUAGE.EN ? GRAPH_LANGUAGE.EN : GRAPH_LANGUAGE.FA;
|
|
455
|
+
}
|
|
456
|
+
function getNodeEntityImage(item) {
|
|
457
|
+
var _a, _b, _c;
|
|
458
|
+
if (!(item == null ? void 0 : item.entity)) return DEFAULT_NODE_IMAGE;
|
|
459
|
+
const fromMetadata = (_b = (_a = item.entity) == null ? void 0 : _a.metadata) == null ? void 0 : _b.image;
|
|
460
|
+
const fromEntity = (_c = item.entity) == null ? void 0 : _c.image;
|
|
461
|
+
if (hasEntityImage(fromMetadata)) return fromMetadata.trim();
|
|
462
|
+
if (hasEntityImage(fromEntity)) return fromEntity.trim();
|
|
463
|
+
return DEFAULT_NODE_IMAGE;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/Components/SetEdgesData.js
|
|
467
|
+
var SetEdgesData = (GraphData) => {
|
|
468
|
+
const CreatedEdges = [];
|
|
469
|
+
const nodeIdMap = GraphData.reduce((map, node) => {
|
|
470
|
+
map[node.id] = node.id;
|
|
471
|
+
return map;
|
|
472
|
+
}, {});
|
|
473
|
+
GraphData.forEach((node) => {
|
|
474
|
+
if (node.type !== NODE_TYPE.MAIN) return;
|
|
475
|
+
if (Array.isArray(node.inputs)) {
|
|
476
|
+
node.inputs.forEach((edgeData) => {
|
|
477
|
+
const sourceId = nodeIdMap[edgeData.id];
|
|
478
|
+
if (sourceId !== void 0) {
|
|
479
|
+
CreatedEdges.push({ from: sourceId, to: node.id });
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (Array.isArray(node.outputs)) {
|
|
484
|
+
node.outputs.forEach((edgeData) => {
|
|
485
|
+
const targetId = nodeIdMap[edgeData.id];
|
|
486
|
+
if (targetId !== void 0) {
|
|
487
|
+
CreatedEdges.push({ from: node.id, to: targetId });
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
return CreatedEdges;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/Components/Graph.js
|
|
496
|
+
import html2canvas from "html2canvas";
|
|
497
|
+
import * as XLSX from "xlsx";
|
|
498
|
+
|
|
499
|
+
// src/graphGuide.js
|
|
500
|
+
function getGraphGuide(isFa) {
|
|
501
|
+
if (isFa) {
|
|
502
|
+
return {
|
|
503
|
+
title: "\u0631\u0627\u0647\u0646\u0645\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u0627\u0632 \u06AF\u0631\u0627\u0641",
|
|
504
|
+
close: "\u0628\u0633\u062A\u0646",
|
|
505
|
+
sections: [
|
|
506
|
+
{
|
|
507
|
+
title: "\u062D\u0631\u06A9\u062A \u062F\u0631 \u0628\u0648\u0645",
|
|
508
|
+
items: [
|
|
509
|
+
"\u0628\u0631\u0627\u06CC \u062C\u0627\u0628\u0647\u200C\u062C\u0627\u06CC\u06CC \u0646\u0645\u0627\u060C \u0631\u0648\u06CC \u0641\u0636\u0627\u06CC \u062E\u0627\u0644\u06CC \u06A9\u0644\u06CC\u06A9 \u06A9\u0631\u062F\u0647 \u0648 \u0628\u06A9\u0634\u06CC\u062F.",
|
|
510
|
+
"\u0628\u0627 \u0627\u0633\u06A9\u0631\u0648\u0644 \u0645\u0627\u0648\u0633 \u0628\u0632\u0631\u06AF\u200C\u0646\u0645\u0627\u06CC\u06CC \u0648 \u06A9\u0648\u0686\u06A9\u200C\u0646\u0645\u0627\u06CC\u06CC \u06A9\u0646\u06CC\u062F."
|
|
511
|
+
]
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
title: "\u0627\u0646\u062A\u062E\u0627\u0628 \u0648 \u0648\u06CC\u0631\u0627\u06CC\u0634",
|
|
515
|
+
items: [
|
|
516
|
+
"\u0631\u0648\u06CC \u06AF\u0631\u0647 \u06CC\u0627 \u06CC\u0627\u0644 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F \u062A\u0627 \u0627\u0646\u062A\u062E\u0627\u0628 \u0634\u0648\u062F.",
|
|
517
|
+
"\u0628\u0631\u0627\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u0686\u0646\u062F\u062A\u0627\u06CC\u06CC\u060C \u06A9\u0644\u06CC\u062F Ctrl \u0631\u0627 \u0646\u06AF\u0647 \u062F\u0627\u0631\u06CC\u062F \u0648 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F.",
|
|
518
|
+
"\u06AF\u0631\u0647\u200C\u0647\u0627\u06CC \u0627\u0635\u0644\u06CC (main) \u0631\u0627 \u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u0628\u0627 \u06A9\u0634\u06CC\u062F\u0646 \u062C\u0627\u0628\u0647\u200C\u062C\u0627 \u06A9\u0646\u06CC\u062F."
|
|
519
|
+
]
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
title: "\u067E\u0646\u0644 \u0627\u0628\u0632\u0627\u0631\u0647\u0627 (\u06AF\u0648\u0634\u0647 \u0628\u0627\u0644\u0627)",
|
|
523
|
+
items: [
|
|
524
|
+
"\u062C\u0633\u062A\u062C\u0648: \u0647\u0627\u06CC\u0644\u0627\u06CC\u062A \u06AF\u0631\u0647 \u0628\u0631 \u0627\u0633\u0627\u0633 \u0634\u0646\u0627\u0633\u0647 \u06CC\u0627 \u0628\u0631\u0686\u0633\u0628.",
|
|
525
|
+
"\u0646\u0645\u0627\u06CC\u0634 \u0627\u0637\u0644\u0627\u0639\u0627\u062A \u06CC\u0627\u0644: \u0646\u0645\u0627\u06CC\u0634 \u06CC\u0627 \u0645\u062E\u0641\u06CC \u06A9\u0631\u062F\u0646 \u06AF\u0631\u0647\u200C\u0647\u0627\u06CC \u0645\u06CC\u0627\u0646\u06CC \u0631\u0648\u06CC \u06CC\u0627\u0644\u200C\u0647\u0627.",
|
|
526
|
+
"\u06AF\u0631\u06CC\u062F: \u0646\u0645\u0627\u06CC\u0634 \u0634\u0628\u06A9\u0647\u0654 \u067E\u0633\u200C\u0632\u0645\u06CC\u0646\u0647.",
|
|
527
|
+
"\u0631\u0646\u06AF \u06CC\u0627\u0644: \u0631\u0646\u06AF\u200C\u0622\u0645\u06CC\u0632\u06CC \u06CC\u0627\u0644\u200C\u0647\u0627\u06CC \u0627\u0646\u062A\u062E\u0627\u0628\u200C\u0634\u062F\u0647.",
|
|
528
|
+
"\u0645\u0631\u062A\u0628\u200C\u0633\u0627\u0632\u06CC: \u0686\u06CC\u062F\u0645\u0627\u0646 \u062E\u0648\u062F\u06A9\u0627\u0631 \u06AF\u0631\u0647\u200C\u0647\u0627.",
|
|
529
|
+
"\u0627\u0633\u06A9\u0631\u06CC\u0646\u200C\u0634\u0627\u062A \u0648 \u062E\u0631\u0648\u062C\u06CC Excel.",
|
|
530
|
+
"\u0647\u0627\u06CC\u0644\u0627\u06CC\u062A \u0645\u0633\u06CC\u0631\u0647\u0627: \u067E\u06CC\u062F\u0627 \u06A9\u0631\u062F\u0646 \u0645\u0633\u06CC\u0631 \u0628\u06CC\u0646 \u062F\u0648 \u0634\u0646\u0627\u0633\u0647 \u06AF\u0631\u0647."
|
|
531
|
+
]
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
title: "\u0627\u0637\u0644\u0627\u0639\u0627\u062A \u0647\u0646\u06AF\u0627\u0645 \u0647\u0627\u0648\u0631",
|
|
535
|
+
items: [
|
|
536
|
+
"\u0628\u0627 \u0642\u0631\u0627\u0631 \u062F\u0627\u062F\u0646 \u0645\u0648\u0633 \u0631\u0648\u06CC \u06AF\u0631\u0647\u060C \u062C\u0632\u0626\u06CC\u0627\u062A \u0622\u0646 \u0646\u0645\u0627\u06CC\u0634 \u062F\u0627\u062F\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F.",
|
|
537
|
+
"\u0627\u06AF\u0631 \u062A\u0648\u0633\u0639\u0647\u200C\u062F\u0647\u0646\u062F\u0647 \u0641\u0639\u0627\u0644 \u06A9\u0631\u062F\u0647 \u0628\u0627\u0634\u062F\u060C \u0647\u0627\u0648\u0631 \u0631\u0648\u06CC \u06CC\u0627\u0644 \u0647\u0645 \u0627\u0637\u0644\u0627\u0639\u0627\u062A \u0627\u062A\u0635\u0627\u0644 \u0631\u0627 \u0646\u0634\u0627\u0646 \u0645\u06CC\u200C\u062F\u0647\u062F."
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
title: "\u0632\u0628\u0627\u0646",
|
|
542
|
+
items: [
|
|
543
|
+
"\u0627\u0632 \u0628\u062E\u0634 \u0632\u0628\u0627\u0646 \u062F\u0631 \u067E\u0646\u0644 \u0627\u0628\u0632\u0627\u0631\u0647\u0627 \u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u0628\u06CC\u0646 \u0641\u0627\u0631\u0633\u06CC \u0648 \u0627\u0646\u06AF\u0644\u06CC\u0633\u06CC \u062C\u0627\u0628\u0647\u200C\u062C\u0627 \u0634\u0648\u06CC\u062F."
|
|
544
|
+
]
|
|
545
|
+
}
|
|
546
|
+
]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
title: "Graph Usage Guide",
|
|
551
|
+
close: "Close",
|
|
552
|
+
sections: [
|
|
553
|
+
{
|
|
554
|
+
title: "Canvas navigation",
|
|
555
|
+
items: [
|
|
556
|
+
"Click and drag empty space to pan the view.",
|
|
557
|
+
"Use the mouse wheel to zoom in and out."
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
title: "Selection and editing",
|
|
562
|
+
items: [
|
|
563
|
+
"Click a node or edge to select it.",
|
|
564
|
+
"Hold Ctrl and click for multi-selection.",
|
|
565
|
+
"Main nodes can be repositioned by dragging."
|
|
566
|
+
]
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
title: "Tools panel (top corner)",
|
|
570
|
+
items: [
|
|
571
|
+
"Search: highlight nodes by ID or label.",
|
|
572
|
+
"Edge info nodes: show or hide mid-edge label nodes.",
|
|
573
|
+
"Grid: toggle background grid.",
|
|
574
|
+
"Edge color: paint selected edges.",
|
|
575
|
+
"Arrange: auto-layout nodes.",
|
|
576
|
+
"Screenshot and Excel export.",
|
|
577
|
+
"Path highlight: find routes between two node IDs."
|
|
578
|
+
]
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
title: "Hover information",
|
|
582
|
+
items: [
|
|
583
|
+
"Hover a node to see its details.",
|
|
584
|
+
"If enabled by the developer, hovering an edge shows connection data."
|
|
585
|
+
]
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
title: "Language",
|
|
589
|
+
items: [
|
|
590
|
+
"Switch between Persian and English from the language section in the tools panel."
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
]
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/branding.js
|
|
598
|
+
var FOOTER_TEXT = "powered by nodalix";
|
|
599
|
+
|
|
600
|
+
// src/edition.js
|
|
601
|
+
var NODALIX_EDITION = "full";
|
|
602
|
+
var NODALIX_MAX_NODES = Infinity;
|
|
603
|
+
var isBasicEdition = () => NODALIX_EDITION === "basic";
|
|
604
|
+
var isFullEdition = () => NODALIX_EDITION === "full";
|
|
605
|
+
var hasNodeLimit = () => Number.isFinite(NODALIX_MAX_NODES);
|
|
606
|
+
|
|
607
|
+
// src/graphNodeLimits.js
|
|
608
|
+
var S = (v) => String(v);
|
|
609
|
+
function isHelperNodeId(id) {
|
|
610
|
+
const s = S(id);
|
|
611
|
+
return s.endsWith("_Arrow") || s.endsWith("hotWallet") || s.includes("Tr");
|
|
612
|
+
}
|
|
613
|
+
function isUserGraphNode(node) {
|
|
614
|
+
return Boolean(node == null ? void 0 : node.id) && !isHelperNodeId(node.id);
|
|
615
|
+
}
|
|
616
|
+
function countGraphNodes(nodes) {
|
|
617
|
+
if (!Array.isArray(nodes)) return 0;
|
|
618
|
+
return nodes.filter(isUserGraphNode).length;
|
|
619
|
+
}
|
|
620
|
+
function enforceNodeLimit(nodes, maxNodes) {
|
|
621
|
+
if (!Array.isArray(nodes)) return [];
|
|
622
|
+
if (!Number.isFinite(maxNodes)) return [...nodes];
|
|
623
|
+
let keptCount = 0;
|
|
624
|
+
const kept = [];
|
|
625
|
+
for (const node of nodes) {
|
|
626
|
+
if (!isUserGraphNode(node)) {
|
|
627
|
+
kept.push(node);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (keptCount >= maxNodes) continue;
|
|
631
|
+
kept.push(node);
|
|
632
|
+
keptCount += 1;
|
|
633
|
+
}
|
|
634
|
+
return kept;
|
|
635
|
+
}
|
|
636
|
+
function canAddGraphNode(nodes, maxNodes) {
|
|
637
|
+
if (!Number.isFinite(maxNodes)) return true;
|
|
638
|
+
return countGraphNodes(nodes) < maxNodes;
|
|
639
|
+
}
|
|
640
|
+
function getNodeLimitError(maxNodes) {
|
|
641
|
+
return `Graph node limit reached (${maxNodes} max in nodalix-basic).`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/Components/graphPanelIcons.js
|
|
645
|
+
import React from "react";
|
|
646
|
+
var stroke = {
|
|
647
|
+
fill: "none",
|
|
648
|
+
stroke: "currentColor",
|
|
649
|
+
strokeWidth: 2,
|
|
650
|
+
strokeLinecap: "round",
|
|
651
|
+
strokeLinejoin: "round"
|
|
652
|
+
};
|
|
653
|
+
function IconCamera({ size = 16 }) {
|
|
654
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
|
|
655
|
+
"path",
|
|
656
|
+
{
|
|
657
|
+
...stroke,
|
|
658
|
+
d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"
|
|
659
|
+
}
|
|
660
|
+
), /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "12", cy: "13", r: "4" }));
|
|
661
|
+
}
|
|
662
|
+
function IconTable({ size = 16 }) {
|
|
663
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("rect", { ...stroke, x: "3", y: "4", width: "18", height: "16", rx: "2" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M3 10h18M9 4v16M15 4v16" }));
|
|
664
|
+
}
|
|
665
|
+
function IconRoute({ size = 16 }) {
|
|
666
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "6", cy: "19", r: "2" }), /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "18", cy: "5", r: "2" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M8 17c4-2 6-5 8-10" }));
|
|
667
|
+
}
|
|
668
|
+
function IconEraser({ size = 16 }) {
|
|
669
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
|
|
670
|
+
"path",
|
|
671
|
+
{
|
|
672
|
+
...stroke,
|
|
673
|
+
d: "M20 20H8l-6-6 9.5-9.5a2.8 2.8 0 0 1 4 0L20 10v10z"
|
|
674
|
+
}
|
|
675
|
+
), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M6.5 13.5L14 6" }));
|
|
676
|
+
}
|
|
677
|
+
function IconSearch({ size = 16 }) {
|
|
678
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "11", cy: "11", r: "7" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M20 20l-3.5-3.5" }));
|
|
679
|
+
}
|
|
680
|
+
function IconGlobe({ size = 14 }) {
|
|
681
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "12", cy: "12", r: "9" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18" }));
|
|
682
|
+
}
|
|
683
|
+
function IconChevron({ size = 14, up = true }) {
|
|
684
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
|
|
685
|
+
"path",
|
|
686
|
+
{
|
|
687
|
+
...stroke,
|
|
688
|
+
d: up ? "M6 15l6-6 6 6" : "M6 9l6 6 6-6"
|
|
689
|
+
}
|
|
690
|
+
));
|
|
691
|
+
}
|
|
692
|
+
function IconLineDash({ size = 28 }) {
|
|
693
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 28 28", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
|
|
694
|
+
"line",
|
|
695
|
+
{
|
|
696
|
+
x1: "4",
|
|
697
|
+
y1: "14",
|
|
698
|
+
x2: "24",
|
|
699
|
+
y2: "14",
|
|
700
|
+
stroke: "currentColor",
|
|
701
|
+
strokeWidth: "2.5",
|
|
702
|
+
strokeDasharray: "5 4",
|
|
703
|
+
strokeLinecap: "round"
|
|
704
|
+
}
|
|
705
|
+
));
|
|
706
|
+
}
|
|
707
|
+
function IconLineDot({ size = 28 }) {
|
|
708
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 28 28", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
|
|
709
|
+
"line",
|
|
710
|
+
{
|
|
711
|
+
x1: "4",
|
|
712
|
+
y1: "14",
|
|
713
|
+
x2: "24",
|
|
714
|
+
y2: "14",
|
|
715
|
+
stroke: "currentColor",
|
|
716
|
+
strokeWidth: "2.5",
|
|
717
|
+
strokeDasharray: "1.5 4",
|
|
718
|
+
strokeLinecap: "round"
|
|
719
|
+
}
|
|
720
|
+
));
|
|
721
|
+
}
|
|
722
|
+
function IconLineSolid({ size = 28 }) {
|
|
723
|
+
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 28 28", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
|
|
724
|
+
"line",
|
|
725
|
+
{
|
|
726
|
+
x1: "4",
|
|
727
|
+
y1: "14",
|
|
728
|
+
x2: "24",
|
|
729
|
+
y2: "14",
|
|
730
|
+
stroke: "currentColor",
|
|
731
|
+
strokeWidth: "2.5",
|
|
732
|
+
strokeLinecap: "round"
|
|
733
|
+
}
|
|
734
|
+
));
|
|
735
|
+
}
|
|
736
|
+
function IconButtonContent({ icon, label }) {
|
|
737
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
738
|
+
"span",
|
|
739
|
+
{
|
|
740
|
+
style: {
|
|
741
|
+
display: "inline-flex",
|
|
742
|
+
alignItems: "center",
|
|
743
|
+
justifyContent: "center",
|
|
744
|
+
flexShrink: 0
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
icon
|
|
748
|
+
), label ? /* @__PURE__ */ React.createElement("span", null, label) : null);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/Components/Graph.js
|
|
752
|
+
var S2 = (v) => String(v);
|
|
753
|
+
var Nodalix_graph_engine = ({
|
|
754
|
+
Data,
|
|
755
|
+
NodesPosition,
|
|
756
|
+
SetNodesPosition,
|
|
757
|
+
Reload,
|
|
758
|
+
SetReload,
|
|
759
|
+
SetData,
|
|
760
|
+
SetScale,
|
|
761
|
+
Scale,
|
|
762
|
+
SetXPosition,
|
|
763
|
+
XPosition,
|
|
764
|
+
SetYPosition,
|
|
765
|
+
YPosition,
|
|
766
|
+
edgeLabelFields,
|
|
767
|
+
rateColorRanges,
|
|
768
|
+
showEdgeHoverInfo = false,
|
|
769
|
+
showHelpButton = true,
|
|
770
|
+
gridUserConfigurable = true,
|
|
771
|
+
showGrid: showGridProp = true,
|
|
772
|
+
nodesDraggableUserConfigurable = true,
|
|
773
|
+
nodesDraggable: nodesDraggableProp = true,
|
|
774
|
+
edgeInfoNodesUserConfigurable = true,
|
|
775
|
+
showEdgeInfoNodes: showEdgeInfoNodesProp = true,
|
|
776
|
+
languageUserConfigurable = true,
|
|
777
|
+
language: languageProp = "fa",
|
|
778
|
+
TakeSceenShot,
|
|
779
|
+
SetSelectedEdges,
|
|
780
|
+
PaintedEdges,
|
|
781
|
+
SetPaintedEdges,
|
|
782
|
+
setSelectedNodes,
|
|
783
|
+
SetNode,
|
|
784
|
+
onNodeClickRef
|
|
785
|
+
}) => {
|
|
786
|
+
var _a;
|
|
787
|
+
const resolvedEdgeLabelFields = useMemo(
|
|
788
|
+
() => normalizeEdgeLabelFields(edgeLabelFields),
|
|
789
|
+
[edgeLabelFields]
|
|
790
|
+
);
|
|
791
|
+
const resolvedRateColorRanges = useMemo(
|
|
792
|
+
() => normalizeRateColorRanges(rateColorRanges),
|
|
793
|
+
[rateColorRanges]
|
|
794
|
+
);
|
|
795
|
+
useEffect(() => {
|
|
796
|
+
console.log(Data);
|
|
797
|
+
}, [Data]);
|
|
798
|
+
const [isDarkMode, setIsDark] = useState(false);
|
|
799
|
+
const [isStart, setIsStart] = useState(false);
|
|
800
|
+
const [screenShot, setScreenShot] = useState(TakeSceenShot);
|
|
801
|
+
const [isCapturing, setIsCapturing] = useState(false);
|
|
802
|
+
const [AddPositionLoading, setAddPositionLoading] = useState(false);
|
|
803
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
804
|
+
const [userShowEdgeInfoNodes, setUserShowEdgeInfoNodes] = useState(
|
|
805
|
+
!!showEdgeInfoNodesProp
|
|
806
|
+
);
|
|
807
|
+
const [userShowGrid, setUserShowGrid] = useState(!!showGridProp);
|
|
808
|
+
const [userNodesDraggable, setUserNodesDraggable] = useState(
|
|
809
|
+
!!nodesDraggableProp
|
|
810
|
+
);
|
|
811
|
+
const [userLang, setUserLang] = useState(
|
|
812
|
+
() => normalizeGraphLanguage(languageProp)
|
|
813
|
+
);
|
|
814
|
+
const [pathFromId, setPathFromId] = useState("");
|
|
815
|
+
const [pathToId, setPathToId] = useState("");
|
|
816
|
+
const [pathMessage, setPathMessage] = useState("");
|
|
817
|
+
const [hoverInfo, setHoverInfo] = useState({
|
|
818
|
+
visible: false,
|
|
819
|
+
x: 0,
|
|
820
|
+
y: 0,
|
|
821
|
+
node: null
|
|
822
|
+
});
|
|
823
|
+
const [edgeHoverInfo, setEdgeHoverInfo] = useState({
|
|
824
|
+
visible: false,
|
|
825
|
+
x: 0,
|
|
826
|
+
y: 0,
|
|
827
|
+
fromId: "",
|
|
828
|
+
toId: "",
|
|
829
|
+
summary: ""
|
|
830
|
+
});
|
|
831
|
+
const [showGuide, setShowGuide] = useState(false);
|
|
832
|
+
const [panelOpen, setPanelOpen] = useState(true);
|
|
833
|
+
const showEdgeHoverInfoRef = useRef(!!showEdgeHoverInfo);
|
|
834
|
+
const edgeLabelFieldsRef = useRef(resolvedEdgeLabelFields);
|
|
835
|
+
const containerRef = useRef(null);
|
|
836
|
+
const networkInstanceRef = useRef(null);
|
|
837
|
+
const showGridRef = useRef(!!showGridProp);
|
|
838
|
+
const nodesDraggableRef = useRef(!!nodesDraggableProp);
|
|
839
|
+
const isDarkModeRef = useRef(
|
|
840
|
+
typeof document !== "undefined" && document.documentElement.classList.contains("dark")
|
|
841
|
+
);
|
|
842
|
+
const nodesRef = useRef(null);
|
|
843
|
+
const edgesRef = useRef(null);
|
|
844
|
+
const initializedRef = useRef(false);
|
|
845
|
+
const viewportInitializedRef = useRef(false);
|
|
846
|
+
const showEdgeInfoNodes = edgeInfoNodesUserConfigurable ? userShowEdgeInfoNodes : !!showEdgeInfoNodesProp;
|
|
847
|
+
const ShowGrid = gridUserConfigurable ? userShowGrid : !!showGridProp;
|
|
848
|
+
const nodesDraggableEnabled = nodesDraggableUserConfigurable ? userNodesDraggable : !!nodesDraggableProp;
|
|
849
|
+
const lang = languageUserConfigurable ? userLang : normalizeGraphLanguage(languageProp);
|
|
850
|
+
const isFa = lang === "fa";
|
|
851
|
+
const guideContent = useMemo(() => getGraphGuide(isFa), [isFa]);
|
|
852
|
+
useEffect(() => {
|
|
853
|
+
showEdgeHoverInfoRef.current = !!showEdgeHoverInfo;
|
|
854
|
+
if (!showEdgeHoverInfo) {
|
|
855
|
+
setEdgeHoverInfo({
|
|
856
|
+
visible: false,
|
|
857
|
+
x: 0,
|
|
858
|
+
y: 0,
|
|
859
|
+
fromId: "",
|
|
860
|
+
toId: "",
|
|
861
|
+
summary: ""
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}, [showEdgeHoverInfo]);
|
|
865
|
+
useEffect(() => {
|
|
866
|
+
edgeLabelFieldsRef.current = resolvedEdgeLabelFields;
|
|
867
|
+
}, [resolvedEdgeLabelFields]);
|
|
868
|
+
useEffect(() => {
|
|
869
|
+
const el = document.documentElement;
|
|
870
|
+
const update = () => setIsDark(el.classList.contains("dark"));
|
|
871
|
+
update();
|
|
872
|
+
const obs = new MutationObserver(update);
|
|
873
|
+
obs.observe(el, { attributes: true, attributeFilter: ["class"] });
|
|
874
|
+
return () => obs.disconnect();
|
|
875
|
+
}, []);
|
|
876
|
+
useEffect(() => {
|
|
877
|
+
if (edgeInfoNodesUserConfigurable) {
|
|
878
|
+
setUserShowEdgeInfoNodes(!!showEdgeInfoNodesProp);
|
|
879
|
+
}
|
|
880
|
+
}, [showEdgeInfoNodesProp, edgeInfoNodesUserConfigurable]);
|
|
881
|
+
useEffect(() => {
|
|
882
|
+
if (gridUserConfigurable) {
|
|
883
|
+
setUserShowGrid(!!showGridProp);
|
|
884
|
+
}
|
|
885
|
+
}, [showGridProp, gridUserConfigurable]);
|
|
886
|
+
useEffect(() => {
|
|
887
|
+
if (nodesDraggableUserConfigurable) {
|
|
888
|
+
setUserNodesDraggable(!!nodesDraggableProp);
|
|
889
|
+
}
|
|
890
|
+
}, [nodesDraggableProp, nodesDraggableUserConfigurable]);
|
|
891
|
+
useEffect(() => {
|
|
892
|
+
if (languageUserConfigurable) {
|
|
893
|
+
setUserLang(normalizeGraphLanguage(languageProp));
|
|
894
|
+
}
|
|
895
|
+
}, [languageProp, languageUserConfigurable]);
|
|
896
|
+
useEffect(() => {
|
|
897
|
+
var _a2;
|
|
898
|
+
showGridRef.current = !!ShowGrid;
|
|
899
|
+
(_a2 = networkInstanceRef.current) == null ? void 0 : _a2.redraw();
|
|
900
|
+
}, [ShowGrid]);
|
|
901
|
+
useEffect(() => {
|
|
902
|
+
nodesDraggableRef.current = !!nodesDraggableEnabled;
|
|
903
|
+
const instance = networkInstanceRef.current;
|
|
904
|
+
if (!instance) return;
|
|
905
|
+
try {
|
|
906
|
+
instance.setOptions({
|
|
907
|
+
interaction: buildInteractionOptions(nodesDraggableEnabled)
|
|
908
|
+
});
|
|
909
|
+
} catch (_) {
|
|
910
|
+
}
|
|
911
|
+
}, [nodesDraggableEnabled]);
|
|
912
|
+
useEffect(() => {
|
|
913
|
+
var _a2;
|
|
914
|
+
isDarkModeRef.current = !!isDarkMode;
|
|
915
|
+
(_a2 = networkInstanceRef.current) == null ? void 0 : _a2.redraw();
|
|
916
|
+
}, [isDarkMode]);
|
|
917
|
+
const graphNodeCount = useMemo(() => countGraphNodes(Data), [Data]);
|
|
918
|
+
const nodeLimitReached = isBasicEdition() && Number.isFinite(NODALIX_MAX_NODES) && graphNodeCount >= NODALIX_MAX_NODES;
|
|
919
|
+
const dataRef = useRef(Data);
|
|
920
|
+
const newPositionsRef = useRef(
|
|
921
|
+
Array.isArray(NodesPosition) ? NodesPosition : []
|
|
922
|
+
);
|
|
923
|
+
const selectedNodeIdsRef = useRef([]);
|
|
924
|
+
const selectedEdgeIdsRef = useRef([]);
|
|
925
|
+
const findEdgePayload = (fromId, toId) => {
|
|
926
|
+
const data = dataRef.current || [];
|
|
927
|
+
const fromNode = data.find((n) => S2(n.id) === fromId);
|
|
928
|
+
if (fromNode == null ? void 0 : fromNode.outputs) {
|
|
929
|
+
const match = fromNode.outputs.find((o) => S2(o == null ? void 0 : o.id) === toId);
|
|
930
|
+
if (match) return match;
|
|
931
|
+
}
|
|
932
|
+
const toNode = data.find((n) => S2(n.id) === toId);
|
|
933
|
+
if (toNode == null ? void 0 : toNode.inputs) {
|
|
934
|
+
const match = toNode.inputs.find((i) => S2(i == null ? void 0 : i.id) === fromId);
|
|
935
|
+
if (match) return match;
|
|
936
|
+
}
|
|
937
|
+
return null;
|
|
938
|
+
};
|
|
939
|
+
const getNodeLabelById = (id) => {
|
|
940
|
+
var _a2;
|
|
941
|
+
const fromDs = (_a2 = nodesRef.current) == null ? void 0 : _a2.get(id);
|
|
942
|
+
if (fromDs == null ? void 0 : fromDs.label) return S2(fromDs.label);
|
|
943
|
+
const fromData = (dataRef.current || []).find((n) => S2(n.id) === id);
|
|
944
|
+
if (fromData) return getNodeDisplayLabel(fromData) || S2(fromData.text || id);
|
|
945
|
+
return S2(id);
|
|
946
|
+
};
|
|
947
|
+
const baseNodeStyleRef = useRef(/* @__PURE__ */ new Map());
|
|
948
|
+
const baseEdgeStyleRef = useRef(/* @__PURE__ */ new Map());
|
|
949
|
+
useEffect(() => {
|
|
950
|
+
dataRef.current = Data;
|
|
951
|
+
}, [Data]);
|
|
952
|
+
useEffect(() => {
|
|
953
|
+
if (Array.isArray(NodesPosition)) {
|
|
954
|
+
newPositionsRef.current = NodesPosition.map((p) => ({
|
|
955
|
+
...p,
|
|
956
|
+
id: S2(p.id)
|
|
957
|
+
}));
|
|
958
|
+
}
|
|
959
|
+
}, [NodesPosition]);
|
|
960
|
+
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
961
|
+
const snapToGridIfNeeded = (x, y, instance) => {
|
|
962
|
+
if (!showGridRef.current || !instance) return { x, y };
|
|
963
|
+
const step = getNiceGridStep(instance.getScale());
|
|
964
|
+
if (!isFinite(step) || step <= 0) return { x, y };
|
|
965
|
+
return {
|
|
966
|
+
x: Math.round(x / step) * step,
|
|
967
|
+
y: Math.round(y / step) * step
|
|
968
|
+
};
|
|
969
|
+
};
|
|
970
|
+
const emitNodeClick = (nodePayload) => {
|
|
971
|
+
if (!nodePayload) return;
|
|
972
|
+
SetNode(nodePayload);
|
|
973
|
+
if (typeof (onNodeClickRef == null ? void 0 : onNodeClickRef.current) === "function") {
|
|
974
|
+
onNodeClickRef.current(nodePayload);
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
const downloadBlob = (blob, filename) => {
|
|
978
|
+
const url = URL.createObjectURL(blob);
|
|
979
|
+
const a = document.createElement("a");
|
|
980
|
+
a.href = url;
|
|
981
|
+
a.download = filename;
|
|
982
|
+
document.body.appendChild(a);
|
|
983
|
+
a.click();
|
|
984
|
+
a.remove();
|
|
985
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
986
|
+
};
|
|
987
|
+
const getPaintedMap = () => {
|
|
988
|
+
const map = /* @__PURE__ */ new Map();
|
|
989
|
+
(PaintedEdges || []).forEach((p) => {
|
|
990
|
+
const id = S2(p == null ? void 0 : p.id);
|
|
991
|
+
if (!id) return;
|
|
992
|
+
const entry = {
|
|
993
|
+
color: (p == null ? void 0 : p.color) || null,
|
|
994
|
+
lineStyle: (p == null ? void 0 : p.lineStyle) || null
|
|
995
|
+
};
|
|
996
|
+
if (!entry.color && !entry.lineStyle) return;
|
|
997
|
+
map.set(id, entry);
|
|
998
|
+
});
|
|
999
|
+
return map;
|
|
1000
|
+
};
|
|
1001
|
+
const compactPaintedEntry = (entry) => {
|
|
1002
|
+
if (!entry) return null;
|
|
1003
|
+
const next = {};
|
|
1004
|
+
if (entry.color) next.color = entry.color;
|
|
1005
|
+
if (entry.lineStyle && entry.lineStyle !== EDGE_LINE_STYLE.SOLID) {
|
|
1006
|
+
next.lineStyle = entry.lineStyle;
|
|
1007
|
+
}
|
|
1008
|
+
return Object.keys(next).length ? next : null;
|
|
1009
|
+
};
|
|
1010
|
+
const syncPaintedEdgesToParent = (map) => {
|
|
1011
|
+
if (typeof SetPaintedEdges !== "function") return;
|
|
1012
|
+
const arr = Array.from(map.entries()).map(([id, entry]) => {
|
|
1013
|
+
const compact = compactPaintedEntry(entry);
|
|
1014
|
+
return compact ? { id, ...compact } : null;
|
|
1015
|
+
}).filter(Boolean);
|
|
1016
|
+
SetPaintedEdges(arr);
|
|
1017
|
+
};
|
|
1018
|
+
const buildPaintedEdgeVisUpdate = (id, entry) => ({
|
|
1019
|
+
id,
|
|
1020
|
+
...resolveEdgeVisStyle({
|
|
1021
|
+
isDark: isDarkMode,
|
|
1022
|
+
paintedColor: (entry == null ? void 0 : entry.color) || null,
|
|
1023
|
+
paintedLineStyle: (entry == null ? void 0 : entry.lineStyle) || null
|
|
1024
|
+
})
|
|
1025
|
+
});
|
|
1026
|
+
const applyColorToSelectedEdges = (colorOrNull) => {
|
|
1027
|
+
const instance = networkInstanceRef.current;
|
|
1028
|
+
const edgesDS = edgesRef.current;
|
|
1029
|
+
if (!instance || !edgesDS) return;
|
|
1030
|
+
const selectedIds = instance.getSelectedEdges().map(S2);
|
|
1031
|
+
if (!selectedIds.length) return;
|
|
1032
|
+
const map = getPaintedMap();
|
|
1033
|
+
const updates = [];
|
|
1034
|
+
selectedIds.forEach((id) => {
|
|
1035
|
+
const edge = edgesDS.get(id);
|
|
1036
|
+
if (!edge) return;
|
|
1037
|
+
const existing = map.get(id) || { color: null, lineStyle: null };
|
|
1038
|
+
const nextEntry = { ...existing, color: colorOrNull || null };
|
|
1039
|
+
const compact = compactPaintedEntry(nextEntry);
|
|
1040
|
+
if (compact) map.set(id, nextEntry);
|
|
1041
|
+
else map.delete(id);
|
|
1042
|
+
updates.push(
|
|
1043
|
+
buildPaintedEdgeVisUpdate(
|
|
1044
|
+
id,
|
|
1045
|
+
compact ? nextEntry : { color: null, lineStyle: null }
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
1048
|
+
});
|
|
1049
|
+
if (updates.length) edgesDS.update(updates);
|
|
1050
|
+
syncPaintedEdgesToParent(map);
|
|
1051
|
+
captureBaseStyles();
|
|
1052
|
+
paintSelectionStyles();
|
|
1053
|
+
};
|
|
1054
|
+
const applyLineStyleToSelectedEdges = (lineStyleOrNull) => {
|
|
1055
|
+
const instance = networkInstanceRef.current;
|
|
1056
|
+
const edgesDS = edgesRef.current;
|
|
1057
|
+
if (!instance || !edgesDS) return;
|
|
1058
|
+
const selectedIds = instance.getSelectedEdges().map(S2);
|
|
1059
|
+
if (!selectedIds.length) return;
|
|
1060
|
+
const map = getPaintedMap();
|
|
1061
|
+
const updates = [];
|
|
1062
|
+
selectedIds.forEach((id) => {
|
|
1063
|
+
const edge = edgesDS.get(id);
|
|
1064
|
+
if (!edge) return;
|
|
1065
|
+
const existing = map.get(id) || { color: null, lineStyle: null };
|
|
1066
|
+
const nextEntry = {
|
|
1067
|
+
...existing,
|
|
1068
|
+
lineStyle: lineStyleOrNull && lineStyleOrNull !== EDGE_LINE_STYLE.SOLID ? normalizeEdgeLineStyle(lineStyleOrNull) : null
|
|
1069
|
+
};
|
|
1070
|
+
const compact = compactPaintedEntry(nextEntry);
|
|
1071
|
+
if (compact) map.set(id, nextEntry);
|
|
1072
|
+
else map.delete(id);
|
|
1073
|
+
updates.push(
|
|
1074
|
+
buildPaintedEdgeVisUpdate(
|
|
1075
|
+
id,
|
|
1076
|
+
compact ? nextEntry : { color: null, lineStyle: null }
|
|
1077
|
+
)
|
|
1078
|
+
);
|
|
1079
|
+
});
|
|
1080
|
+
if (updates.length) edgesDS.update(updates);
|
|
1081
|
+
syncPaintedEdgesToParent(map);
|
|
1082
|
+
captureBaseStyles();
|
|
1083
|
+
paintSelectionStyles();
|
|
1084
|
+
};
|
|
1085
|
+
const runSearch = (term) => {
|
|
1086
|
+
var _a2, _b;
|
|
1087
|
+
const instance = networkInstanceRef.current;
|
|
1088
|
+
const nodesDS = nodesRef.current;
|
|
1089
|
+
if (!instance || !nodesDS) return;
|
|
1090
|
+
const q = S2(term || "").trim().toLowerCase();
|
|
1091
|
+
if (!q) return;
|
|
1092
|
+
const all = nodesDS.get().filter((n) => !isHelperNodeId(n.id));
|
|
1093
|
+
const hits = all.filter((n) => {
|
|
1094
|
+
const id = S2(n.id).toLowerCase();
|
|
1095
|
+
const label = S2(n.label || "").toLowerCase();
|
|
1096
|
+
return id.includes(q) || label.includes(q);
|
|
1097
|
+
});
|
|
1098
|
+
if (!hits.length) return;
|
|
1099
|
+
const hitIds = hits.map((h) => S2(h.id));
|
|
1100
|
+
instance.setSelection(
|
|
1101
|
+
{ nodes: hitIds, edges: [] },
|
|
1102
|
+
{ unselectAll: true, highlightEdges: false }
|
|
1103
|
+
);
|
|
1104
|
+
selectedNodeIdsRef.current = [...hitIds];
|
|
1105
|
+
selectedEdgeIdsRef.current = [];
|
|
1106
|
+
setSelectedNodes(hitIds.map((id) => nodesDS.get(id)).filter(Boolean));
|
|
1107
|
+
SetSelectedEdges([]);
|
|
1108
|
+
paintSelectionStyles();
|
|
1109
|
+
if (hitIds.length === 1) {
|
|
1110
|
+
instance.focus(hitIds[0], {
|
|
1111
|
+
scale: Math.max(instance.getScale(), 1.1),
|
|
1112
|
+
animation: { duration: 450, easingFunction: "easeInOutQuad" }
|
|
1113
|
+
});
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1117
|
+
hitIds.forEach((id) => {
|
|
1118
|
+
const bb = instance.getBoundingBox(id);
|
|
1119
|
+
if (!bb) return;
|
|
1120
|
+
minX = Math.min(minX, bb.left);
|
|
1121
|
+
minY = Math.min(minY, bb.top);
|
|
1122
|
+
maxX = Math.max(maxX, bb.right);
|
|
1123
|
+
maxY = Math.max(maxY, bb.bottom);
|
|
1124
|
+
});
|
|
1125
|
+
if (isFinite(minX) && isFinite(minY) && isFinite(maxX) && isFinite(maxY)) {
|
|
1126
|
+
const pad = 100;
|
|
1127
|
+
const cw = ((_a2 = containerRef.current) == null ? void 0 : _a2.clientWidth) || 1e3;
|
|
1128
|
+
const ch = ((_b = containerRef.current) == null ? void 0 : _b.clientHeight) || 700;
|
|
1129
|
+
const w = Math.max(1, maxX - minX + pad * 2);
|
|
1130
|
+
const h = Math.max(1, maxY - minY + pad * 2);
|
|
1131
|
+
const scale = Math.min(cw / w, ch / h);
|
|
1132
|
+
const center = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
|
|
1133
|
+
instance.moveTo({
|
|
1134
|
+
position: center,
|
|
1135
|
+
scale,
|
|
1136
|
+
animation: { duration: 500, easingFunction: "easeInOutQuad" }
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
const takeGraphExcelExport = () => {
|
|
1141
|
+
const nodesDS = nodesRef.current;
|
|
1142
|
+
const edgesDS = edgesRef.current;
|
|
1143
|
+
if (!nodesDS || !edgesDS) return;
|
|
1144
|
+
const mainNodes = nodesDS.get().filter((n) => !isHelperNodeId(n.id)).map((n) => ({
|
|
1145
|
+
id: S2(n.id),
|
|
1146
|
+
label: S2(n.label ?? ""),
|
|
1147
|
+
type: S2(n.type ?? n.group ?? ""),
|
|
1148
|
+
address: S2(n.address ?? n.text ?? ""),
|
|
1149
|
+
subText: S2(n.subText ?? ""),
|
|
1150
|
+
rate: n.rate ?? "",
|
|
1151
|
+
metadata: S2(n.metadata ?? ""),
|
|
1152
|
+
inputs: JSON.stringify(Array.isArray(n.inputs) ? n.inputs : []),
|
|
1153
|
+
outputs: JSON.stringify(Array.isArray(n.outputs) ? n.outputs : []),
|
|
1154
|
+
inputCount: Array.isArray(n.inputs) ? n.inputs.length : 0,
|
|
1155
|
+
outputCount: Array.isArray(n.outputs) ? n.outputs.length : 0,
|
|
1156
|
+
x: Number(n.x ?? 0),
|
|
1157
|
+
y: Number(n.y ?? 0)
|
|
1158
|
+
}));
|
|
1159
|
+
const edges = edgesDS.get().map((e) => ({
|
|
1160
|
+
id: S2(e.id),
|
|
1161
|
+
from: S2(e.from),
|
|
1162
|
+
to: S2(e.to),
|
|
1163
|
+
label: S2(e.label ?? ""),
|
|
1164
|
+
value: e.value ?? "",
|
|
1165
|
+
subText: S2(e.subText ?? ""),
|
|
1166
|
+
subValue: e.subValue ?? "",
|
|
1167
|
+
time: e.time ?? "",
|
|
1168
|
+
width: e.width ?? ""
|
|
1169
|
+
}));
|
|
1170
|
+
const workbook = XLSX.utils.book_new();
|
|
1171
|
+
const nodeSheet = XLSX.utils.json_to_sheet(mainNodes);
|
|
1172
|
+
const edgeSheet = XLSX.utils.json_to_sheet(edges);
|
|
1173
|
+
XLSX.utils.book_append_sheet(workbook, nodeSheet, "Nodes");
|
|
1174
|
+
XLSX.utils.book_append_sheet(workbook, edgeSheet, "Edges");
|
|
1175
|
+
const array = XLSX.write(workbook, {
|
|
1176
|
+
type: "array",
|
|
1177
|
+
bookType: "xlsx",
|
|
1178
|
+
compression: true
|
|
1179
|
+
});
|
|
1180
|
+
const blob = new Blob([array], {
|
|
1181
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
1182
|
+
});
|
|
1183
|
+
downloadBlob(blob, `graph-export-${Date.now()}.xlsx`);
|
|
1184
|
+
};
|
|
1185
|
+
const findAndHighlightAllPaths = () => {
|
|
1186
|
+
const instance = networkInstanceRef.current;
|
|
1187
|
+
const nodesDS = nodesRef.current;
|
|
1188
|
+
const edgesDS = edgesRef.current;
|
|
1189
|
+
const fromId = S2(pathFromId).trim();
|
|
1190
|
+
const toId = S2(pathToId).trim();
|
|
1191
|
+
if (!instance || !nodesDS || !edgesDS) return;
|
|
1192
|
+
if (!fromId || !toId) {
|
|
1193
|
+
setPathMessage(
|
|
1194
|
+
isFa ? "\u0644\u0637\u0641\u0627\u064B \u0622\u06CC\u062F\u06CC \u0645\u0628\u062F\u0627 \u0648 \u0645\u0642\u0635\u062F \u0631\u0627 \u0648\u0627\u0631\u062F \u06A9\u0646\u06CC\u062F." : "Please provide both source and destination ids."
|
|
1195
|
+
);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (!nodesDS.get(fromId) || !nodesDS.get(toId)) {
|
|
1199
|
+
setPathMessage(
|
|
1200
|
+
isFa ? "\u06CC\u06A9\u06CC \u0627\u0632 \u0622\u06CC\u062F\u06CC\u200C\u0647\u0627 \u062F\u0631 \u06AF\u0631\u0627\u0641 \u0648\u062C\u0648\u062F \u0646\u062F\u0627\u0631\u062F." : "One of the node ids does not exist in graph."
|
|
1201
|
+
);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const edgeRows = edgesDS.get();
|
|
1205
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
1206
|
+
const edgeIdsByPair = /* @__PURE__ */ new Map();
|
|
1207
|
+
edgeRows.forEach((e) => {
|
|
1208
|
+
const from = S2(e.from);
|
|
1209
|
+
const to = S2(e.to);
|
|
1210
|
+
if (!adjacency.has(from)) adjacency.set(from, /* @__PURE__ */ new Set());
|
|
1211
|
+
adjacency.get(from).add(to);
|
|
1212
|
+
const key = `${from}__${to}`;
|
|
1213
|
+
if (!edgeIdsByPair.has(key)) edgeIdsByPair.set(key, []);
|
|
1214
|
+
edgeIdsByPair.get(key).push(S2(e.id));
|
|
1215
|
+
});
|
|
1216
|
+
const MAX_PATHS = 3e3;
|
|
1217
|
+
const MAX_DEPTH = Math.max(nodesDS.getIds().length || 0, 1);
|
|
1218
|
+
const allPaths = [];
|
|
1219
|
+
const nodePath = [fromId];
|
|
1220
|
+
const visited = /* @__PURE__ */ new Set([fromId]);
|
|
1221
|
+
const dfs = (current, depth) => {
|
|
1222
|
+
if (allPaths.length >= MAX_PATHS || depth > MAX_DEPTH) return;
|
|
1223
|
+
if (current === toId) {
|
|
1224
|
+
allPaths.push([...nodePath]);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const nextSet = adjacency.get(current);
|
|
1228
|
+
if (!nextSet || !nextSet.size) return;
|
|
1229
|
+
for (const next of nextSet) {
|
|
1230
|
+
if (visited.has(next)) continue;
|
|
1231
|
+
visited.add(next);
|
|
1232
|
+
nodePath.push(next);
|
|
1233
|
+
dfs(next, depth + 1);
|
|
1234
|
+
nodePath.pop();
|
|
1235
|
+
visited.delete(next);
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
dfs(fromId, 0);
|
|
1239
|
+
if (!allPaths.length) {
|
|
1240
|
+
instance.setSelection(
|
|
1241
|
+
{ nodes: [fromId, toId], edges: [] },
|
|
1242
|
+
{ unselectAll: true, highlightEdges: false }
|
|
1243
|
+
);
|
|
1244
|
+
selectedNodeIdsRef.current = [fromId, toId];
|
|
1245
|
+
selectedEdgeIdsRef.current = [];
|
|
1246
|
+
setSelectedNodes([nodesDS.get(fromId), nodesDS.get(toId)].filter(Boolean));
|
|
1247
|
+
SetSelectedEdges([]);
|
|
1248
|
+
paintSelectionStyles();
|
|
1249
|
+
setPathMessage(
|
|
1250
|
+
isFa ? "\u0645\u0633\u06CC\u0631 \u0645\u0633\u062A\u0642\u06CC\u0645\u06CC \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F." : "No directed path was found."
|
|
1251
|
+
);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const selectedNodes = /* @__PURE__ */ new Set();
|
|
1255
|
+
const selectedEdges = /* @__PURE__ */ new Set();
|
|
1256
|
+
allPaths.forEach((path) => {
|
|
1257
|
+
path.forEach((id) => selectedNodes.add(id));
|
|
1258
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1259
|
+
const ids = edgeIdsByPair.get(`${path[i]}__${path[i + 1]}`) || [];
|
|
1260
|
+
ids.forEach((id) => selectedEdges.add(id));
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
const selectedNodeIds = Array.from(selectedNodes);
|
|
1264
|
+
const selectedEdgeIds = Array.from(selectedEdges);
|
|
1265
|
+
instance.setSelection(
|
|
1266
|
+
{ nodes: selectedNodeIds, edges: selectedEdgeIds },
|
|
1267
|
+
{ unselectAll: true, highlightEdges: false }
|
|
1268
|
+
);
|
|
1269
|
+
selectedNodeIdsRef.current = selectedNodeIds;
|
|
1270
|
+
selectedEdgeIdsRef.current = selectedEdgeIds;
|
|
1271
|
+
setSelectedNodes(selectedNodeIds.map((id) => nodesDS.get(id)).filter(Boolean));
|
|
1272
|
+
SetSelectedEdges(selectedEdgeIds.map((id) => edgesDS.get(id)).filter(Boolean));
|
|
1273
|
+
paintSelectionStyles();
|
|
1274
|
+
setPathMessage(
|
|
1275
|
+
isFa ? `${allPaths.length.toLocaleString()} \u0645\u0633\u06CC\u0631 \u067E\u06CC\u062F\u0627 \u0634\u062F.` : `${allPaths.length.toLocaleString()} path(s) found.`
|
|
1276
|
+
);
|
|
1277
|
+
};
|
|
1278
|
+
const handleAutoArrangeGraph = async () => {
|
|
1279
|
+
var _a2;
|
|
1280
|
+
const instance = networkInstanceRef.current;
|
|
1281
|
+
const nodesDS = nodesRef.current;
|
|
1282
|
+
const edgesDS = edgesRef.current;
|
|
1283
|
+
if (!instance || !nodesDS || !edgesDS) return;
|
|
1284
|
+
if (handleAutoArrangeGraph.__running) return;
|
|
1285
|
+
handleAutoArrangeGraph.__running = true;
|
|
1286
|
+
setAddPositionLoading(true);
|
|
1287
|
+
const raf = () => new Promise((r) => requestAnimationFrame(r));
|
|
1288
|
+
const S3 = (v) => String(v ?? "");
|
|
1289
|
+
try {
|
|
1290
|
+
try {
|
|
1291
|
+
instance.setOptions({ physics: { enabled: false } });
|
|
1292
|
+
} catch (_) {
|
|
1293
|
+
}
|
|
1294
|
+
const allNodes = nodesDS.get();
|
|
1295
|
+
const allEdges = edgesDS.get();
|
|
1296
|
+
const byId = new Map(allNodes.map((n) => [S3(n.id), n]));
|
|
1297
|
+
const mainNodes = allNodes.filter((n) => !isHelperNodeId(n.id));
|
|
1298
|
+
if (!mainNodes.length) return;
|
|
1299
|
+
const mainIds = new Set(mainNodes.map((n) => S3(n.id)));
|
|
1300
|
+
const mainEdges = allEdges.filter(
|
|
1301
|
+
(e) => mainIds.has(S3(e.from)) && mainIds.has(S3(e.to))
|
|
1302
|
+
);
|
|
1303
|
+
const BOX_MARGIN = 14;
|
|
1304
|
+
const INFO_BOX_MARGIN = 16;
|
|
1305
|
+
const clamp = (v, min, max) => Math.min(Math.max(v, min), max);
|
|
1306
|
+
const boxesOverlap = (x1, y1, hw1, hh1, x2, y2, hw2, hh2, margin = 0) => Math.abs(x1 - x2) < hw1 + hw2 + margin && Math.abs(y1 - y2) < hh1 + hh2 + margin;
|
|
1307
|
+
const estimateMainHalfSize = (mainId) => {
|
|
1308
|
+
var _a3;
|
|
1309
|
+
const node = byId.get(mainId) || {};
|
|
1310
|
+
const size = Number((node == null ? void 0 : node.size) ?? 18);
|
|
1311
|
+
const labelLen = S3((node == null ? void 0 : node.label) ?? "").length;
|
|
1312
|
+
const fontSize = Number(((_a3 = node == null ? void 0 : node.font) == null ? void 0 : _a3.size) ?? 13);
|
|
1313
|
+
return {
|
|
1314
|
+
x: clamp(26 + size + labelLen * (fontSize * 0.38), 38, 130),
|
|
1315
|
+
y: clamp(24 + size * 1.05, 34, 88)
|
|
1316
|
+
};
|
|
1317
|
+
};
|
|
1318
|
+
const estimateInfoHalfSize = (infoNode) => {
|
|
1319
|
+
const lines = S3((infoNode == null ? void 0 : infoNode.label) ?? "").split("\n");
|
|
1320
|
+
const maxChars = lines.reduce((m, l) => Math.max(m, l.trim().length), 0);
|
|
1321
|
+
const fontSize = 12;
|
|
1322
|
+
const lineHeight = 15;
|
|
1323
|
+
const pad = 18;
|
|
1324
|
+
return {
|
|
1325
|
+
x: clamp(pad + maxChars * (fontSize * 0.58), 64, 300),
|
|
1326
|
+
y: clamp(pad + lines.length * lineHeight, 30, 130)
|
|
1327
|
+
};
|
|
1328
|
+
};
|
|
1329
|
+
const getMainHalf = (mainId) => estimateMainHalfSize(mainId);
|
|
1330
|
+
const getInfoHalf = (infoNode) => estimateInfoHalfSize(infoNode);
|
|
1331
|
+
const overlapsAnyMainBox = (x, y, hw, hh, mainPosMap2, margin = BOX_MARGIN) => {
|
|
1332
|
+
for (const [mid, p] of Object.entries(mainPosMap2)) {
|
|
1333
|
+
if (!p) continue;
|
|
1334
|
+
const mh = getMainHalf(mid);
|
|
1335
|
+
if (boxesOverlap(x, y, hw, hh, p.x, p.y, mh.x, mh.y, margin))
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
return false;
|
|
1339
|
+
};
|
|
1340
|
+
const overlapsAnyInfoBox = (id, x, y, hw, hh, infoPlaced2, infoHalfMap2, margin = INFO_BOX_MARGIN) => {
|
|
1341
|
+
for (const [oid, p] of Object.entries(infoPlaced2)) {
|
|
1342
|
+
if (!p || oid === id) continue;
|
|
1343
|
+
const oh = infoHalfMap2[oid] || { x: 80, y: 40 };
|
|
1344
|
+
if (boxesOverlap(x, y, hw, hh, p.x, p.y, oh.x, oh.y, margin))
|
|
1345
|
+
return true;
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1348
|
+
};
|
|
1349
|
+
const overlapsPlacedHelpers = (id, x, y, hw, hh, placedMap2, halfMap, margin = BOX_MARGIN) => {
|
|
1350
|
+
for (const [oid, p] of Object.entries(placedMap2)) {
|
|
1351
|
+
if (!p || oid === id) continue;
|
|
1352
|
+
const oh = halfMap[oid];
|
|
1353
|
+
if (!oh) continue;
|
|
1354
|
+
if (boxesOverlap(x, y, hw, hh, p.x, p.y, oh.x, oh.y, margin))
|
|
1355
|
+
return true;
|
|
1356
|
+
}
|
|
1357
|
+
return false;
|
|
1358
|
+
};
|
|
1359
|
+
const collidesMapBox = (id, x, y, hw, hh, map, halfMap, margin = BOX_MARGIN) => {
|
|
1360
|
+
for (const [oid, p] of Object.entries(map)) {
|
|
1361
|
+
if (!p || oid === id) continue;
|
|
1362
|
+
const oh = halfMap[oid] || { x: hw, y: hh };
|
|
1363
|
+
if (boxesOverlap(x, y, hw, hh, p.x, p.y, oh.x, oh.y, margin))
|
|
1364
|
+
return true;
|
|
1365
|
+
}
|
|
1366
|
+
return false;
|
|
1367
|
+
};
|
|
1368
|
+
const placeAroundBox = (id, bx, by, halfX, halfY, placedMap2, halfMap, opts = {}) => {
|
|
1369
|
+
const margin = opts.margin ?? BOX_MARGIN;
|
|
1370
|
+
const ringStep = opts.ringStep ?? 20;
|
|
1371
|
+
const maxRings = opts.maxRings ?? 48;
|
|
1372
|
+
const angles = opts.angles ?? 48;
|
|
1373
|
+
if (!collidesMapBox(id, bx, by, halfX, halfY, placedMap2, halfMap, margin))
|
|
1374
|
+
return { x: bx, y: by };
|
|
1375
|
+
for (let r = 1; r <= maxRings; r++) {
|
|
1376
|
+
const rad = r * ringStep;
|
|
1377
|
+
for (let k = 0; k < angles; k++) {
|
|
1378
|
+
const a = 2 * Math.PI * k / angles;
|
|
1379
|
+
const x = bx + Math.cos(a) * rad;
|
|
1380
|
+
const y = by + Math.sin(a) * rad;
|
|
1381
|
+
if (!collidesMapBox(id, x, y, halfX, halfY, placedMap2, halfMap, margin))
|
|
1382
|
+
return { x, y };
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return { x: bx, y: by };
|
|
1386
|
+
};
|
|
1387
|
+
const infoPosIsValid = (id, x, y, infoNode, infoHalf, mainPosMap2, infoPlaced2, infoHalfMap2, placedMap2, placedHalfMap2) => !overlapsAnyMainBox(
|
|
1388
|
+
x,
|
|
1389
|
+
y,
|
|
1390
|
+
infoHalf.x,
|
|
1391
|
+
infoHalf.y,
|
|
1392
|
+
mainPosMap2,
|
|
1393
|
+
INFO_BOX_MARGIN
|
|
1394
|
+
) && !overlapsAnyInfoBox(
|
|
1395
|
+
id,
|
|
1396
|
+
x,
|
|
1397
|
+
y,
|
|
1398
|
+
infoHalf.x,
|
|
1399
|
+
infoHalf.y,
|
|
1400
|
+
infoPlaced2,
|
|
1401
|
+
infoHalfMap2,
|
|
1402
|
+
INFO_BOX_MARGIN
|
|
1403
|
+
) && !overlapsPlacedHelpers(
|
|
1404
|
+
id,
|
|
1405
|
+
x,
|
|
1406
|
+
y,
|
|
1407
|
+
infoHalf.x,
|
|
1408
|
+
infoHalf.y,
|
|
1409
|
+
placedMap2,
|
|
1410
|
+
placedHalfMap2,
|
|
1411
|
+
BOX_MARGIN
|
|
1412
|
+
);
|
|
1413
|
+
const pushOutFromMainForbidden = (id, p, infoNode, infoHalf, mainPosMap2, infoPlaced2, infoHalfMap2, placedMap2, placedHalfMap2) => {
|
|
1414
|
+
let x = p.x, y = p.y;
|
|
1415
|
+
const isBad = (cx, cy) => !infoPosIsValid(
|
|
1416
|
+
id,
|
|
1417
|
+
cx,
|
|
1418
|
+
cy,
|
|
1419
|
+
infoNode,
|
|
1420
|
+
infoHalf,
|
|
1421
|
+
mainPosMap2,
|
|
1422
|
+
infoPlaced2,
|
|
1423
|
+
infoHalfMap2,
|
|
1424
|
+
placedMap2,
|
|
1425
|
+
placedHalfMap2
|
|
1426
|
+
);
|
|
1427
|
+
if (!isBad(x, y)) return { x, y };
|
|
1428
|
+
const step = Math.max(24, Math.min(infoHalf.x, infoHalf.y) * 0.35);
|
|
1429
|
+
for (let k = 1; k <= 180; k++) {
|
|
1430
|
+
const d = k * step;
|
|
1431
|
+
const tries = [
|
|
1432
|
+
{ x, y: y + d },
|
|
1433
|
+
{ x, y: y - d },
|
|
1434
|
+
{ x: x + d, y },
|
|
1435
|
+
{ x: x - d, y },
|
|
1436
|
+
{ x: x + d, y: y + d },
|
|
1437
|
+
{ x: x - d, y: y - d },
|
|
1438
|
+
{ x: x + d, y: y - d },
|
|
1439
|
+
{ x: x - d, y: y + d }
|
|
1440
|
+
];
|
|
1441
|
+
for (const t of tries) {
|
|
1442
|
+
if (!isBad(t.x, t.y)) return t;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
for (let r = step * 2; r <= 5600; r += step * 1.6) {
|
|
1446
|
+
for (let a = 0; a < 40; a++) {
|
|
1447
|
+
const ang = 2 * Math.PI * a / 40;
|
|
1448
|
+
const cx = x + Math.cos(ang) * r;
|
|
1449
|
+
const cy = y + Math.sin(ang) * r;
|
|
1450
|
+
if (!isBad(cx, cy)) return { x: cx, y: cy };
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return { x, y };
|
|
1454
|
+
};
|
|
1455
|
+
const inDeg = /* @__PURE__ */ Object.create(null);
|
|
1456
|
+
const outDeg = /* @__PURE__ */ Object.create(null);
|
|
1457
|
+
const adj = /* @__PURE__ */ Object.create(null);
|
|
1458
|
+
const revAdj = /* @__PURE__ */ Object.create(null);
|
|
1459
|
+
for (const n of mainNodes) {
|
|
1460
|
+
const id = S3(n.id);
|
|
1461
|
+
inDeg[id] = 0;
|
|
1462
|
+
outDeg[id] = 0;
|
|
1463
|
+
adj[id] = [];
|
|
1464
|
+
revAdj[id] = [];
|
|
1465
|
+
}
|
|
1466
|
+
for (const e of mainEdges) {
|
|
1467
|
+
const f = S3(e.from), t = S3(e.to);
|
|
1468
|
+
if (inDeg[f] == null || inDeg[t] == null) continue;
|
|
1469
|
+
adj[f].push(t);
|
|
1470
|
+
revAdj[t].push(f);
|
|
1471
|
+
outDeg[f] += 1;
|
|
1472
|
+
inDeg[t] += 1;
|
|
1473
|
+
}
|
|
1474
|
+
const q = [];
|
|
1475
|
+
const inTmp = { ...inDeg };
|
|
1476
|
+
const layer = /* @__PURE__ */ Object.create(null);
|
|
1477
|
+
for (const id of Object.keys(inTmp)) {
|
|
1478
|
+
if (inTmp[id] === 0) {
|
|
1479
|
+
q.push(id);
|
|
1480
|
+
layer[id] = 0;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (!q.length) {
|
|
1484
|
+
const ids = Object.keys(inTmp).sort();
|
|
1485
|
+
q.push(ids[0]);
|
|
1486
|
+
layer[ids[0]] = 0;
|
|
1487
|
+
}
|
|
1488
|
+
while (q.length) {
|
|
1489
|
+
const u = q.shift();
|
|
1490
|
+
const nextL = (layer[u] ?? 0) + 1;
|
|
1491
|
+
for (const v of adj[u]) {
|
|
1492
|
+
if (layer[v] == null || nextL > layer[v]) layer[v] = nextL;
|
|
1493
|
+
inTmp[v] -= 1;
|
|
1494
|
+
if (inTmp[v] === 0) q.push(v);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
let changed = true;
|
|
1498
|
+
while (changed) {
|
|
1499
|
+
changed = false;
|
|
1500
|
+
for (const id of Object.keys(inDeg)) {
|
|
1501
|
+
if (layer[id] != null) continue;
|
|
1502
|
+
const parents = revAdj[id] || [];
|
|
1503
|
+
if (!parents.length) {
|
|
1504
|
+
layer[id] = 0;
|
|
1505
|
+
changed = true;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
const known = parents.filter((p) => layer[p] != null);
|
|
1509
|
+
if (!known.length) continue;
|
|
1510
|
+
layer[id] = Math.max(...known.map((p) => layer[p])) + 1;
|
|
1511
|
+
changed = true;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
for (const id of Object.keys(inDeg)) {
|
|
1515
|
+
if (layer[id] == null) layer[id] = 0;
|
|
1516
|
+
}
|
|
1517
|
+
const layers = /* @__PURE__ */ Object.create(null);
|
|
1518
|
+
for (const [id, l] of Object.entries(layer)) {
|
|
1519
|
+
if (!layers[l]) layers[l] = [];
|
|
1520
|
+
layers[l].push(id);
|
|
1521
|
+
}
|
|
1522
|
+
const layerKeys = Object.keys(layers).map(Number).sort((a, b) => a - b);
|
|
1523
|
+
const oldPos = /* @__PURE__ */ Object.create(null);
|
|
1524
|
+
for (const n of allNodes) oldPos[S3(n.id)] = { x: n.x || 0, y: n.y || 0 };
|
|
1525
|
+
for (const lk of layerKeys) {
|
|
1526
|
+
layers[lk].sort((a, b) => {
|
|
1527
|
+
var _a3, _b;
|
|
1528
|
+
return (((_a3 = oldPos[a]) == null ? void 0 : _a3.y) ?? 0) - (((_b = oldPos[b]) == null ? void 0 : _b.y) ?? 0);
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
const nCount = mainNodes.length;
|
|
1532
|
+
const medianOf = (arr) => {
|
|
1533
|
+
if (!arr.length) return Number.POSITIVE_INFINITY;
|
|
1534
|
+
const sorted = [...arr].sort((a, b) => a - b);
|
|
1535
|
+
const mid = Math.floor(sorted.length / 2);
|
|
1536
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
1537
|
+
};
|
|
1538
|
+
const barySort = (ids, getNeighbors) => {
|
|
1539
|
+
return [...ids].sort((a, b) => {
|
|
1540
|
+
var _a3, _b;
|
|
1541
|
+
const na = getNeighbors(a);
|
|
1542
|
+
const nb = getNeighbors(b);
|
|
1543
|
+
const ba = medianOf(na);
|
|
1544
|
+
const bb = medianOf(nb);
|
|
1545
|
+
if (ba !== bb) return ba - bb;
|
|
1546
|
+
return (((_a3 = oldPos[a]) == null ? void 0 : _a3.y) ?? 0) - (((_b = oldPos[b]) == null ? void 0 : _b.y) ?? 0);
|
|
1547
|
+
});
|
|
1548
|
+
};
|
|
1549
|
+
const indexInLayer = () => {
|
|
1550
|
+
const idx = /* @__PURE__ */ Object.create(null);
|
|
1551
|
+
for (const lk of layerKeys) {
|
|
1552
|
+
const arr = layers[lk] || [];
|
|
1553
|
+
const m = /* @__PURE__ */ Object.create(null);
|
|
1554
|
+
for (let i = 0; i < arr.length; i++) m[arr[i]] = i;
|
|
1555
|
+
idx[lk] = m;
|
|
1556
|
+
}
|
|
1557
|
+
return idx;
|
|
1558
|
+
};
|
|
1559
|
+
const crossSweeps = Math.min(
|
|
1560
|
+
16,
|
|
1561
|
+
8 + Math.floor(layerKeys.length / 2) + Math.floor(nCount / 80)
|
|
1562
|
+
);
|
|
1563
|
+
for (let sweep = 0; sweep < crossSweeps; sweep++) {
|
|
1564
|
+
let idxMap2 = indexInLayer();
|
|
1565
|
+
for (let i = 1; i < layerKeys.length; i++) {
|
|
1566
|
+
const lk = layerKeys[i];
|
|
1567
|
+
layers[lk] = barySort(
|
|
1568
|
+
layers[lk],
|
|
1569
|
+
(id) => (revAdj[id] || []).filter(
|
|
1570
|
+
(p) => {
|
|
1571
|
+
var _a3;
|
|
1572
|
+
return (layer[p] ?? 999) < lk && ((_a3 = idxMap2[layer[p]]) == null ? void 0 : _a3[p]) != null;
|
|
1573
|
+
}
|
|
1574
|
+
).map((p) => idxMap2[layer[p]][p])
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
idxMap2 = indexInLayer();
|
|
1578
|
+
for (let i = layerKeys.length - 2; i >= 0; i--) {
|
|
1579
|
+
const lk = layerKeys[i];
|
|
1580
|
+
layers[lk] = barySort(
|
|
1581
|
+
layers[lk],
|
|
1582
|
+
(id) => (adj[id] || []).filter(
|
|
1583
|
+
(c) => {
|
|
1584
|
+
var _a3;
|
|
1585
|
+
return (layer[c] ?? -1) > lk && ((_a3 = idxMap2[layer[c]]) == null ? void 0 : _a3[c]) != null;
|
|
1586
|
+
}
|
|
1587
|
+
).map((c) => idxMap2[layer[c]][c])
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const countAllCrossings = () => {
|
|
1592
|
+
var _a3, _b, _c, _d;
|
|
1593
|
+
const idx = indexInLayer();
|
|
1594
|
+
let c = 0;
|
|
1595
|
+
for (let li = 0; li < layerKeys.length; li++) {
|
|
1596
|
+
for (let lj = li + 1; lj < layerKeys.length; lj++) {
|
|
1597
|
+
const L = layerKeys[li];
|
|
1598
|
+
const R = layerKeys[lj];
|
|
1599
|
+
const edgesLR = mainEdges.filter((e) => {
|
|
1600
|
+
const f = S3(e.from);
|
|
1601
|
+
const t = S3(e.to);
|
|
1602
|
+
return layer[f] === L && layer[t] === R;
|
|
1603
|
+
});
|
|
1604
|
+
for (let i = 0; i < edgesLR.length; i++) {
|
|
1605
|
+
for (let j = i + 1; j < edgesLR.length; j++) {
|
|
1606
|
+
const f1 = S3(edgesLR[i].from);
|
|
1607
|
+
const t1 = S3(edgesLR[i].to);
|
|
1608
|
+
const f2 = S3(edgesLR[j].from);
|
|
1609
|
+
const t2 = S3(edgesLR[j].to);
|
|
1610
|
+
const y1f = ((_a3 = idx[L]) == null ? void 0 : _a3[f1]) ?? 0;
|
|
1611
|
+
const y2f = ((_b = idx[L]) == null ? void 0 : _b[f2]) ?? 0;
|
|
1612
|
+
const y1t = ((_c = idx[R]) == null ? void 0 : _c[t1]) ?? 0;
|
|
1613
|
+
const y2t = ((_d = idx[R]) == null ? void 0 : _d[t2]) ?? 0;
|
|
1614
|
+
if ((y1f - y2f) * (y1t - y2t) < 0) c++;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return c;
|
|
1620
|
+
};
|
|
1621
|
+
const optimizeLayerOrders = () => {
|
|
1622
|
+
const maxPasses = Math.min(10, 3 + Math.floor(nCount / 60));
|
|
1623
|
+
for (let pass = 0; pass < maxPasses; pass++) {
|
|
1624
|
+
let improved = false;
|
|
1625
|
+
for (const lk of layerKeys) {
|
|
1626
|
+
const arr = layers[lk];
|
|
1627
|
+
if (!arr || arr.length < 2) continue;
|
|
1628
|
+
for (let i = 0; i < arr.length - 1; i++) {
|
|
1629
|
+
const before = countAllCrossings();
|
|
1630
|
+
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
|
|
1631
|
+
const after = countAllCrossings();
|
|
1632
|
+
if (after < before) improved = true;
|
|
1633
|
+
else [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
if (!improved) break;
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
optimizeLayerOrders();
|
|
1640
|
+
const maxLayerSize = Math.max(
|
|
1641
|
+
1,
|
|
1642
|
+
...layerKeys.map((lk) => (layers[lk] || []).length)
|
|
1643
|
+
);
|
|
1644
|
+
const numLayers = Math.max(1, layerKeys.length);
|
|
1645
|
+
const NODE_GAP_Y = clamp(
|
|
1646
|
+
165 + Math.sqrt(maxLayerSize) * 36 + Math.log2(nCount + 1) * 16,
|
|
1647
|
+
175,
|
|
1648
|
+
380
|
|
1649
|
+
);
|
|
1650
|
+
const BASE_LAYER_GAP_X = clamp(
|
|
1651
|
+
300 + Math.sqrt(nCount) * 8 + Math.sqrt(maxLayerSize) * 6,
|
|
1652
|
+
300,
|
|
1653
|
+
480
|
|
1654
|
+
);
|
|
1655
|
+
const edgeCountBetween = /* @__PURE__ */ Object.create(null);
|
|
1656
|
+
for (const e of mainEdges) {
|
|
1657
|
+
const f = S3(e.from);
|
|
1658
|
+
const t = S3(e.to);
|
|
1659
|
+
const lf = layer[f];
|
|
1660
|
+
const lt = layer[t];
|
|
1661
|
+
if (lf == null || lt == null || lf >= lt) continue;
|
|
1662
|
+
const key = `${lf}|${lt}`;
|
|
1663
|
+
edgeCountBetween[key] = (edgeCountBetween[key] || 0) + 1;
|
|
1664
|
+
}
|
|
1665
|
+
const colX = [];
|
|
1666
|
+
let xAcc = 0;
|
|
1667
|
+
for (let i = 0; i < layerKeys.length; i++) {
|
|
1668
|
+
colX.push(xAcc);
|
|
1669
|
+
if (i >= layerKeys.length - 1) break;
|
|
1670
|
+
const leftL = layerKeys[i];
|
|
1671
|
+
const rightL = layerKeys[i + 1];
|
|
1672
|
+
let denseEdges = 0;
|
|
1673
|
+
for (let l = leftL; l < rightL; l++) {
|
|
1674
|
+
for (let r = l + 1; r <= rightL; r++) {
|
|
1675
|
+
denseEdges = Math.max(denseEdges, edgeCountBetween[`${l}|${r}`] || 0);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
xAcc += BASE_LAYER_GAP_X + Math.sqrt(denseEdges) * 18 + (((_a2 = layers[rightL]) == null ? void 0 : _a2.length) ?? 0) * 3;
|
|
1679
|
+
}
|
|
1680
|
+
const newPosMap = /* @__PURE__ */ Object.create(null);
|
|
1681
|
+
const placedMap = /* @__PURE__ */ Object.create(null);
|
|
1682
|
+
const placedHalfMap = /* @__PURE__ */ Object.create(null);
|
|
1683
|
+
const mainPosMap = /* @__PURE__ */ Object.create(null);
|
|
1684
|
+
const mainHalfMap = /* @__PURE__ */ Object.create(null);
|
|
1685
|
+
for (let cx = 0; cx < layerKeys.length; cx++) {
|
|
1686
|
+
const lk = layerKeys[cx];
|
|
1687
|
+
const ids = layers[lk] || [];
|
|
1688
|
+
const bx = colX[cx];
|
|
1689
|
+
const nodeHalfs = ids.map((id) => estimateMainHalfSize(id));
|
|
1690
|
+
let totalH = 0;
|
|
1691
|
+
for (let i = 0; i < ids.length; i++) {
|
|
1692
|
+
totalH += nodeHalfs[i].y * 2;
|
|
1693
|
+
if (i < ids.length - 1) totalH += NODE_GAP_Y;
|
|
1694
|
+
}
|
|
1695
|
+
let yCursor = -totalH / 2;
|
|
1696
|
+
for (let i = 0; i < ids.length; i++) {
|
|
1697
|
+
const id = ids[i];
|
|
1698
|
+
const half = nodeHalfs[i];
|
|
1699
|
+
const by = yCursor + half.y;
|
|
1700
|
+
yCursor += half.y * 2 + NODE_GAP_Y;
|
|
1701
|
+
const p = placeAroundBox(
|
|
1702
|
+
id,
|
|
1703
|
+
bx,
|
|
1704
|
+
by,
|
|
1705
|
+
half.x,
|
|
1706
|
+
half.y,
|
|
1707
|
+
placedMap,
|
|
1708
|
+
placedHalfMap,
|
|
1709
|
+
{ ringStep: 22, maxRings: 52, angles: 48 }
|
|
1710
|
+
);
|
|
1711
|
+
newPosMap[id] = { id, x: p.x, y: p.y };
|
|
1712
|
+
placedMap[id] = { x: p.x, y: p.y };
|
|
1713
|
+
placedHalfMap[id] = { x: half.x, y: half.y };
|
|
1714
|
+
mainPosMap[id] = { x: p.x, y: p.y };
|
|
1715
|
+
mainHalfMap[id] = { x: half.x, y: half.y };
|
|
1716
|
+
}
|
|
1717
|
+
if ((cx & 7) === 0) await raf();
|
|
1718
|
+
}
|
|
1719
|
+
for (const n of allNodes) {
|
|
1720
|
+
const id = S3(n.id);
|
|
1721
|
+
if (!id.endsWith("_Arrow")) continue;
|
|
1722
|
+
const base = id.replace("_Arrow", "");
|
|
1723
|
+
if (!newPosMap[base]) continue;
|
|
1724
|
+
const p = placeAroundBox(
|
|
1725
|
+
id,
|
|
1726
|
+
newPosMap[base].x,
|
|
1727
|
+
newPosMap[base].y - 46,
|
|
1728
|
+
28,
|
|
1729
|
+
28,
|
|
1730
|
+
placedMap,
|
|
1731
|
+
placedHalfMap,
|
|
1732
|
+
{ ringStep: 14, maxRings: 30, angles: 36 }
|
|
1733
|
+
);
|
|
1734
|
+
newPosMap[id] = { id, x: p.x, y: p.y };
|
|
1735
|
+
placedMap[id] = { x: p.x, y: p.y };
|
|
1736
|
+
placedHalfMap[id] = { x: 28, y: 28 };
|
|
1737
|
+
}
|
|
1738
|
+
for (const n of allNodes) {
|
|
1739
|
+
const id = S3(n.id);
|
|
1740
|
+
if (!id.endsWith("hotWallet")) continue;
|
|
1741
|
+
const base = id.replace("hotWallet", "");
|
|
1742
|
+
if (!newPosMap[base]) continue;
|
|
1743
|
+
const owner = byId.get(base);
|
|
1744
|
+
const by = (owner == null ? void 0 : owner.main) ? newPosMap[base].y - 50 : newPosMap[base].y - 30;
|
|
1745
|
+
const p = placeAroundBox(
|
|
1746
|
+
id,
|
|
1747
|
+
newPosMap[base].x,
|
|
1748
|
+
by,
|
|
1749
|
+
36,
|
|
1750
|
+
20,
|
|
1751
|
+
placedMap,
|
|
1752
|
+
placedHalfMap,
|
|
1753
|
+
{ ringStep: 14, maxRings: 30, angles: 36 }
|
|
1754
|
+
);
|
|
1755
|
+
newPosMap[id] = { id, x: p.x, y: p.y };
|
|
1756
|
+
placedMap[id] = { x: p.x, y: p.y };
|
|
1757
|
+
placedHalfMap[id] = { x: 36, y: 20 };
|
|
1758
|
+
}
|
|
1759
|
+
const infoNodes = allNodes.filter((n) => {
|
|
1760
|
+
const fromId = S3(n.from);
|
|
1761
|
+
const toId = S3(n.to);
|
|
1762
|
+
return fromId && toId && mainIds.has(fromId) && mainIds.has(toId);
|
|
1763
|
+
});
|
|
1764
|
+
const infoNodeById = new Map(infoNodes.map((n) => [S3(n.id), n]));
|
|
1765
|
+
const infoPlaced = /* @__PURE__ */ Object.create(null);
|
|
1766
|
+
const infoHalfMap = /* @__PURE__ */ Object.create(null);
|
|
1767
|
+
const infoByEdge = /* @__PURE__ */ Object.create(null);
|
|
1768
|
+
for (const n of infoNodes) {
|
|
1769
|
+
const key = `${S3(n.from)}|${S3(n.to)}`;
|
|
1770
|
+
if (!infoByEdge[key]) infoByEdge[key] = [];
|
|
1771
|
+
infoByEdge[key].push(n);
|
|
1772
|
+
}
|
|
1773
|
+
const sortedInfoNodes = [...infoNodes].sort((a, b) => {
|
|
1774
|
+
const ha = estimateInfoHalfSize(a);
|
|
1775
|
+
const hb = estimateInfoHalfSize(b);
|
|
1776
|
+
return hb.x * hb.y - ha.x * ha.y;
|
|
1777
|
+
});
|
|
1778
|
+
const edgeSlotIndex = /* @__PURE__ */ Object.create(null);
|
|
1779
|
+
for (let idx = 0; idx < sortedInfoNodes.length; idx++) {
|
|
1780
|
+
const n = sortedInfoNodes[idx];
|
|
1781
|
+
const id = S3(n.id);
|
|
1782
|
+
const fromId = S3(n.from);
|
|
1783
|
+
const toId = S3(n.to);
|
|
1784
|
+
const edgeKey = `${fromId}|${toId}`;
|
|
1785
|
+
const slot = edgeSlotIndex[edgeKey] ?? 0;
|
|
1786
|
+
edgeSlotIndex[edgeKey] = slot + 1;
|
|
1787
|
+
if (!newPosMap[fromId] || !newPosMap[toId]) continue;
|
|
1788
|
+
const infoHalf = estimateInfoHalfSize(n);
|
|
1789
|
+
const fx = newPosMap[fromId].x, fy = newPosMap[fromId].y;
|
|
1790
|
+
const tx = newPosMap[toId].x, ty = newPosMap[toId].y;
|
|
1791
|
+
const mx = (fx + tx) / 2;
|
|
1792
|
+
const my = (fy + ty) / 2;
|
|
1793
|
+
const dx = tx - fx;
|
|
1794
|
+
const dy = ty - fy;
|
|
1795
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1796
|
+
const ux = dx / len;
|
|
1797
|
+
const uy = dy / len;
|
|
1798
|
+
let nx = -uy, ny = ux;
|
|
1799
|
+
if (!isFinite(nx) || !isFinite(ny)) {
|
|
1800
|
+
nx = 0;
|
|
1801
|
+
ny = -1;
|
|
1802
|
+
}
|
|
1803
|
+
const fromHalf = getMainHalf(fromId);
|
|
1804
|
+
const toHalf = getMainHalf(toId);
|
|
1805
|
+
const minClearance = Math.max(fromHalf.x, fromHalf.y, toHalf.x, toHalf.y) + Math.max(infoHalf.x, infoHalf.y) + INFO_BOX_MARGIN + 24;
|
|
1806
|
+
const totalOnEdge = (infoByEdge[edgeKey] || []).length;
|
|
1807
|
+
const slotShift = (slot - (totalOnEdge - 1) / 2) * (infoHalf.y * 2 + 32);
|
|
1808
|
+
const tangShift = slot * (infoHalf.x * 0.55 + 20);
|
|
1809
|
+
let chosen = null;
|
|
1810
|
+
const baseOff = clamp(minClearance + edgeSlotIndex[edgeKey] * 18, minClearance, 320);
|
|
1811
|
+
for (let lvl = 0; lvl <= 120 && !chosen; lvl++) {
|
|
1812
|
+
const off = baseOff + lvl * 24;
|
|
1813
|
+
const tang = tangShift + lvl * 12;
|
|
1814
|
+
const cands = [
|
|
1815
|
+
{ x: mx + nx * off + ux * tang, y: my + ny * off + uy * tang + slotShift },
|
|
1816
|
+
{ x: mx - nx * off - ux * tang, y: my - ny * off - uy * tang - slotShift },
|
|
1817
|
+
{ x: mx + nx * off - ux * tang, y: my + ny * off - uy * tang + slotShift },
|
|
1818
|
+
{ x: mx - nx * off + ux * tang, y: my - ny * off + uy * tang - slotShift },
|
|
1819
|
+
{ x: mx + ux * tang + nx * (off * 0.65), y: my + uy * tang + ny * (off * 0.65) },
|
|
1820
|
+
{ x: mx - ux * tang - nx * (off * 0.65), y: my - uy * tang - ny * (off * 0.65) }
|
|
1821
|
+
];
|
|
1822
|
+
for (const c of cands) {
|
|
1823
|
+
if (infoPosIsValid(
|
|
1824
|
+
id,
|
|
1825
|
+
c.x,
|
|
1826
|
+
c.y,
|
|
1827
|
+
n,
|
|
1828
|
+
infoHalf,
|
|
1829
|
+
mainPosMap,
|
|
1830
|
+
infoPlaced,
|
|
1831
|
+
infoHalfMap,
|
|
1832
|
+
placedMap,
|
|
1833
|
+
placedHalfMap
|
|
1834
|
+
)) {
|
|
1835
|
+
chosen = c;
|
|
1836
|
+
break;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (!chosen) {
|
|
1841
|
+
for (let off = minClearance; off <= 5600 && !chosen; off += 48) {
|
|
1842
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1843
|
+
const c = {
|
|
1844
|
+
x: mx + nx * off * side + ux * tangShift,
|
|
1845
|
+
y: my + ny * off * side + uy * tangShift + slotShift
|
|
1846
|
+
};
|
|
1847
|
+
if (infoPosIsValid(
|
|
1848
|
+
id,
|
|
1849
|
+
c.x,
|
|
1850
|
+
c.y,
|
|
1851
|
+
n,
|
|
1852
|
+
infoHalf,
|
|
1853
|
+
mainPosMap,
|
|
1854
|
+
infoPlaced,
|
|
1855
|
+
infoHalfMap,
|
|
1856
|
+
placedMap,
|
|
1857
|
+
placedHalfMap
|
|
1858
|
+
)) {
|
|
1859
|
+
chosen = c;
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
if (!chosen) {
|
|
1866
|
+
for (let r = minClearance; r <= 5600 && !chosen; r += 64) {
|
|
1867
|
+
for (let a = 0; a < 48; a++) {
|
|
1868
|
+
const ang = 2 * Math.PI * a / 48;
|
|
1869
|
+
const c = {
|
|
1870
|
+
x: mx + Math.cos(ang) * r + slot * 30,
|
|
1871
|
+
y: my + Math.sin(ang) * r + slotShift
|
|
1872
|
+
};
|
|
1873
|
+
if (infoPosIsValid(
|
|
1874
|
+
id,
|
|
1875
|
+
c.x,
|
|
1876
|
+
c.y,
|
|
1877
|
+
n,
|
|
1878
|
+
infoHalf,
|
|
1879
|
+
mainPosMap,
|
|
1880
|
+
infoPlaced,
|
|
1881
|
+
infoHalfMap,
|
|
1882
|
+
placedMap,
|
|
1883
|
+
placedHalfMap
|
|
1884
|
+
)) {
|
|
1885
|
+
chosen = c;
|
|
1886
|
+
break;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (!chosen) chosen = { x: mx + nx * minClearance, y: my + ny * minClearance };
|
|
1892
|
+
chosen = pushOutFromMainForbidden(
|
|
1893
|
+
id,
|
|
1894
|
+
chosen,
|
|
1895
|
+
n,
|
|
1896
|
+
infoHalf,
|
|
1897
|
+
mainPosMap,
|
|
1898
|
+
infoPlaced,
|
|
1899
|
+
infoHalfMap,
|
|
1900
|
+
placedMap,
|
|
1901
|
+
placedHalfMap
|
|
1902
|
+
);
|
|
1903
|
+
newPosMap[id] = { id, x: chosen.x, y: chosen.y };
|
|
1904
|
+
placedMap[id] = { x: chosen.x, y: chosen.y };
|
|
1905
|
+
placedHalfMap[id] = { x: infoHalf.x, y: infoHalf.y };
|
|
1906
|
+
infoPlaced[id] = { x: chosen.x, y: chosen.y };
|
|
1907
|
+
infoHalfMap[id] = { x: infoHalf.x, y: infoHalf.y };
|
|
1908
|
+
if ((idx & 31) === 0) await raf();
|
|
1909
|
+
}
|
|
1910
|
+
if (ShowGrid) {
|
|
1911
|
+
Object.entries(newPosMap).forEach(([id, p]) => {
|
|
1912
|
+
const snapped = snapToGridIfNeeded(p.x, p.y, instance);
|
|
1913
|
+
newPosMap[id] = { id, x: snapped.x, y: snapped.y };
|
|
1914
|
+
placedMap[id] = { x: snapped.x, y: snapped.y };
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
for (const [id, infoNode] of infoNodeById.entries()) {
|
|
1918
|
+
const pos = newPosMap[id];
|
|
1919
|
+
if (!pos) continue;
|
|
1920
|
+
const infoHalf = infoHalfMap[id] || estimateInfoHalfSize(infoNode);
|
|
1921
|
+
let finalPos = { ...pos };
|
|
1922
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
1923
|
+
finalPos = pushOutFromMainForbidden(
|
|
1924
|
+
id,
|
|
1925
|
+
finalPos,
|
|
1926
|
+
infoNode,
|
|
1927
|
+
infoHalf,
|
|
1928
|
+
mainPosMap,
|
|
1929
|
+
infoPlaced,
|
|
1930
|
+
infoHalfMap,
|
|
1931
|
+
placedMap,
|
|
1932
|
+
placedHalfMap
|
|
1933
|
+
);
|
|
1934
|
+
if (ShowGrid) {
|
|
1935
|
+
finalPos = snapToGridIfNeeded(finalPos.x, finalPos.y, instance);
|
|
1936
|
+
}
|
|
1937
|
+
const stillBad = overlapsAnyMainBox(
|
|
1938
|
+
finalPos.x,
|
|
1939
|
+
finalPos.y,
|
|
1940
|
+
infoHalf.x,
|
|
1941
|
+
infoHalf.y,
|
|
1942
|
+
mainPosMap,
|
|
1943
|
+
INFO_BOX_MARGIN
|
|
1944
|
+
);
|
|
1945
|
+
if (!stillBad) break;
|
|
1946
|
+
}
|
|
1947
|
+
newPosMap[id] = { id, x: finalPos.x, y: finalPos.y };
|
|
1948
|
+
placedMap[id] = { x: finalPos.x, y: finalPos.y };
|
|
1949
|
+
infoPlaced[id] = { x: finalPos.x, y: finalPos.y };
|
|
1950
|
+
}
|
|
1951
|
+
const defaultEdgeSmooth = {
|
|
1952
|
+
type: "cubicBezier",
|
|
1953
|
+
forceDirection: "horizontal",
|
|
1954
|
+
roundness: 0.2
|
|
1955
|
+
};
|
|
1956
|
+
const edgeSmoothById = /* @__PURE__ */ Object.create(null);
|
|
1957
|
+
const mainToMainEdges = allEdges.filter(
|
|
1958
|
+
(e) => mainIds.has(S3(e.from)) && mainIds.has(S3(e.to))
|
|
1959
|
+
);
|
|
1960
|
+
const getEdgeMid = (e) => {
|
|
1961
|
+
var _a3, _b, _c, _d;
|
|
1962
|
+
const f = S3(e.from);
|
|
1963
|
+
const t = S3(e.to);
|
|
1964
|
+
const fx = ((_a3 = newPosMap[f]) == null ? void 0 : _a3.x) ?? 0;
|
|
1965
|
+
const fy = ((_b = newPosMap[f]) == null ? void 0 : _b.y) ?? 0;
|
|
1966
|
+
const tx = ((_c = newPosMap[t]) == null ? void 0 : _c.x) ?? 0;
|
|
1967
|
+
const ty = ((_d = newPosMap[t]) == null ? void 0 : _d.y) ?? 0;
|
|
1968
|
+
return { fx, fy, tx, ty, mx: (fx + tx) / 2, my: (fy + ty) / 2 };
|
|
1969
|
+
};
|
|
1970
|
+
const setEdgeSmooth = (e, smooth) => {
|
|
1971
|
+
edgeSmoothById[S3(e.id)] = smooth;
|
|
1972
|
+
};
|
|
1973
|
+
const pairGroups = /* @__PURE__ */ Object.create(null);
|
|
1974
|
+
for (const e of mainToMainEdges) {
|
|
1975
|
+
const key = `${S3(e.from)}|${S3(e.to)}`;
|
|
1976
|
+
if (!pairGroups[key]) pairGroups[key] = [];
|
|
1977
|
+
pairGroups[key].push(e);
|
|
1978
|
+
}
|
|
1979
|
+
for (const group of Object.values(pairGroups)) {
|
|
1980
|
+
if (group.length <= 1) continue;
|
|
1981
|
+
const sorted = [...group].sort((a, b) => S3(a.id).localeCompare(S3(b.id)));
|
|
1982
|
+
sorted.forEach((e, i) => {
|
|
1983
|
+
const tier = Math.floor(i / 2);
|
|
1984
|
+
const roundness = clamp(0.2 + tier * 0.24, 0.18, 0.85);
|
|
1985
|
+
const type = i % 2 === 0 ? "curvedCW" : "curvedCCW";
|
|
1986
|
+
setEdgeSmooth(e, { type, roundness });
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
const channelBucket = Math.max(36, NODE_GAP_Y * 0.28);
|
|
1990
|
+
const channelGroups = /* @__PURE__ */ Object.create(null);
|
|
1991
|
+
for (const e of mainToMainEdges) {
|
|
1992
|
+
if (edgeSmoothById[S3(e.id)]) continue;
|
|
1993
|
+
const f = S3(e.from);
|
|
1994
|
+
const t = S3(e.to);
|
|
1995
|
+
const lf = layer[f];
|
|
1996
|
+
const lt = layer[t];
|
|
1997
|
+
if (lf == null || lt == null) continue;
|
|
1998
|
+
const mid = getEdgeMid(e);
|
|
1999
|
+
let channelKey;
|
|
2000
|
+
if (lf === lt) {
|
|
2001
|
+
channelKey = `col|${lf}|${Math.round(mid.mx / channelBucket)}`;
|
|
2002
|
+
} else {
|
|
2003
|
+
const leftL = Math.min(lf, lt);
|
|
2004
|
+
const rightL = Math.max(lf, lt);
|
|
2005
|
+
channelKey = `lr|${leftL}|${rightL}|${Math.round(mid.my / channelBucket)}`;
|
|
2006
|
+
}
|
|
2007
|
+
if (!channelGroups[channelKey]) channelGroups[channelKey] = [];
|
|
2008
|
+
channelGroups[channelKey].push(e);
|
|
2009
|
+
}
|
|
2010
|
+
for (const group of Object.values(channelGroups)) {
|
|
2011
|
+
if (group.length <= 1) continue;
|
|
2012
|
+
const sorted = [...group].sort((a, b) => getEdgeMid(a).my - getEdgeMid(b).my);
|
|
2013
|
+
const n = sorted.length;
|
|
2014
|
+
sorted.forEach((e, i) => {
|
|
2015
|
+
if (edgeSmoothById[S3(e.id)]) return;
|
|
2016
|
+
const offset = i - (n - 1) / 2;
|
|
2017
|
+
if (Math.abs(offset) < 0.01) {
|
|
2018
|
+
setEdgeSmooth(e, { ...defaultEdgeSmooth, roundness: 0.08 });
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
const type = offset < 0 ? "curvedCCW" : "curvedCW";
|
|
2022
|
+
const roundness = clamp(0.14 + Math.abs(offset) * 0.18, 0.12, 0.75);
|
|
2023
|
+
setEdgeSmooth(e, { type, roundness });
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
const edgeSmoothUpdates = mainToMainEdges.map((e) => ({
|
|
2027
|
+
id: e.id,
|
|
2028
|
+
smooth: edgeSmoothById[S3(e.id)] || defaultEdgeSmooth
|
|
2029
|
+
}));
|
|
2030
|
+
const updates = Object.values(newPosMap).map((p) => {
|
|
2031
|
+
const id = S3(p.id);
|
|
2032
|
+
const isMain = mainIds.has(id);
|
|
2033
|
+
return {
|
|
2034
|
+
id: p.id,
|
|
2035
|
+
x: p.x,
|
|
2036
|
+
y: p.y,
|
|
2037
|
+
physics: false,
|
|
2038
|
+
// main nodes must stay draggable; only helpers stay fixed
|
|
2039
|
+
fixed: isMain ? false : { x: true, y: true }
|
|
2040
|
+
};
|
|
2041
|
+
});
|
|
2042
|
+
const CHUNK = updates.length > 4e3 ? 300 : updates.length > 1500 ? 500 : 800;
|
|
2043
|
+
for (let i = 0; i < updates.length; i += CHUNK) {
|
|
2044
|
+
nodesDS.update(updates.slice(i, i + CHUNK));
|
|
2045
|
+
await raf();
|
|
2046
|
+
}
|
|
2047
|
+
if (edgeSmoothUpdates.length) {
|
|
2048
|
+
for (let i = 0; i < edgeSmoothUpdates.length; i += CHUNK) {
|
|
2049
|
+
edgesDS.update(edgeSmoothUpdates.slice(i, i + CHUNK));
|
|
2050
|
+
await raf();
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
const nextPositionsArr = Object.values(newPosMap);
|
|
2054
|
+
newPositionsRef.current = nextPositionsArr;
|
|
2055
|
+
SetNodesPosition(nextPositionsArr);
|
|
2056
|
+
const dataCopy = Array.isArray(dataRef.current) ? [...dataRef.current] : [];
|
|
2057
|
+
const idxMap = new Map(dataCopy.map((d, i) => [S3(d.id), i]));
|
|
2058
|
+
for (const [id, p] of Object.entries(newPosMap)) {
|
|
2059
|
+
const i = idxMap.get(id);
|
|
2060
|
+
if (i != null)
|
|
2061
|
+
dataCopy[i] = { ...dataCopy[i], x: p.x, y: p.y, physics: false };
|
|
2062
|
+
}
|
|
2063
|
+
SetData(dataCopy);
|
|
2064
|
+
dataRef.current = dataCopy;
|
|
2065
|
+
try {
|
|
2066
|
+
instance.setOptions({
|
|
2067
|
+
physics: { enabled: false },
|
|
2068
|
+
interaction: buildInteractionOptions(nodesDraggableRef.current)
|
|
2069
|
+
});
|
|
2070
|
+
} catch (_) {
|
|
2071
|
+
}
|
|
2072
|
+
if (mainNodes.length <= 1200) {
|
|
2073
|
+
instance.fit({
|
|
2074
|
+
animation: { duration: 350, easingFunction: "easeInOutQuad" }
|
|
2075
|
+
});
|
|
2076
|
+
} else {
|
|
2077
|
+
instance.moveTo({
|
|
2078
|
+
animation: { duration: 180, easingFunction: "easeInOutQuad" }
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
try {
|
|
2082
|
+
instance.storePositions();
|
|
2083
|
+
} catch (_) {
|
|
2084
|
+
}
|
|
2085
|
+
} catch (err) {
|
|
2086
|
+
console.error("Auto arrange failed:", err);
|
|
2087
|
+
} finally {
|
|
2088
|
+
setAddPositionLoading(false);
|
|
2089
|
+
handleAutoArrangeGraph.__running = false;
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
const takeFullGraphScreenshot = async () => {
|
|
2093
|
+
const instance = networkInstanceRef.current;
|
|
2094
|
+
const container = containerRef.current;
|
|
2095
|
+
const nodesDS = nodesRef.current;
|
|
2096
|
+
if (!instance || !container || !nodesDS || isCapturing) return;
|
|
2097
|
+
setIsCapturing(true);
|
|
2098
|
+
const mainNodeIds = nodesDS.get().map((n) => S2(n.id)).filter((id) => !isHelperNodeId(id));
|
|
2099
|
+
if (!mainNodeIds.length) {
|
|
2100
|
+
setIsCapturing(false);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
const prevScale = instance.getScale();
|
|
2104
|
+
const prevPos = instance.getViewPosition();
|
|
2105
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
2106
|
+
mainNodeIds.forEach((id) => {
|
|
2107
|
+
const bb = instance.getBoundingBox(id);
|
|
2108
|
+
if (!bb) return;
|
|
2109
|
+
minX = Math.min(minX, bb.left);
|
|
2110
|
+
minY = Math.min(minY, bb.top);
|
|
2111
|
+
maxX = Math.max(maxX, bb.right);
|
|
2112
|
+
maxY = Math.max(maxY, bb.bottom);
|
|
2113
|
+
});
|
|
2114
|
+
if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) {
|
|
2115
|
+
setIsCapturing(false);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
const padding = 120;
|
|
2119
|
+
minX -= padding;
|
|
2120
|
+
minY -= padding;
|
|
2121
|
+
maxX += padding;
|
|
2122
|
+
maxY += padding;
|
|
2123
|
+
const graphW = Math.max(1, maxX - minX);
|
|
2124
|
+
const graphH = Math.max(1, maxY - minY);
|
|
2125
|
+
const cw = container.clientWidth || 1200;
|
|
2126
|
+
const ch = container.clientHeight || 800;
|
|
2127
|
+
const targetScale = Math.min(cw / graphW, ch / graphH);
|
|
2128
|
+
const center = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
|
|
2129
|
+
try {
|
|
2130
|
+
instance.moveTo({
|
|
2131
|
+
position: center,
|
|
2132
|
+
scale: targetScale,
|
|
2133
|
+
animation: { duration: 320, easingFunction: "easeInOutQuad" }
|
|
2134
|
+
});
|
|
2135
|
+
await wait(500);
|
|
2136
|
+
const canvas = await html2canvas(container, {
|
|
2137
|
+
backgroundColor: "#ffffff",
|
|
2138
|
+
useCORS: true,
|
|
2139
|
+
scale: Math.min((window.devicePixelRatio || 2) + 1, 4),
|
|
2140
|
+
logging: false
|
|
2141
|
+
});
|
|
2142
|
+
canvas.toBlob(
|
|
2143
|
+
(blob) => {
|
|
2144
|
+
if (!blob) return;
|
|
2145
|
+
downloadBlob(blob, `graph-full-${Date.now()}.png`);
|
|
2146
|
+
},
|
|
2147
|
+
"image/png",
|
|
2148
|
+
1
|
|
2149
|
+
);
|
|
2150
|
+
} finally {
|
|
2151
|
+
instance.moveTo({
|
|
2152
|
+
position: prevPos,
|
|
2153
|
+
scale: prevScale,
|
|
2154
|
+
animation: { duration: 220, easingFunction: "easeInOutQuad" }
|
|
2155
|
+
});
|
|
2156
|
+
setIsCapturing(false);
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
const syncAttachedNodesLive = (movedNodeIds) => {
|
|
2160
|
+
const instance = networkInstanceRef.current;
|
|
2161
|
+
const nodesDS = nodesRef.current;
|
|
2162
|
+
if (!instance || !nodesDS || !(movedNodeIds == null ? void 0 : movedNodeIds.length)) return;
|
|
2163
|
+
const ids = movedNodeIds.map(S2);
|
|
2164
|
+
const pos = instance.getPositions(ids);
|
|
2165
|
+
ids.forEach((id) => {
|
|
2166
|
+
const p = pos[id];
|
|
2167
|
+
if (!p) return;
|
|
2168
|
+
const arrowId = `${id}_Arrow`;
|
|
2169
|
+
if (nodesDS.get(arrowId)) instance.moveNode(arrowId, p.x, p.y - 46);
|
|
2170
|
+
const hotId = `${id}hotWallet`;
|
|
2171
|
+
if (nodesDS.get(hotId)) {
|
|
2172
|
+
const owner = nodesDS.get(id);
|
|
2173
|
+
instance.moveNode(hotId, p.x, (owner == null ? void 0 : owner.main) ? p.y - 45 : p.y - 25);
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
};
|
|
2177
|
+
const syncEdgeLabelNodesLive = (movedNodeIds) => {
|
|
2178
|
+
const instance = networkInstanceRef.current;
|
|
2179
|
+
const nodesDS = nodesRef.current;
|
|
2180
|
+
if (!instance || !nodesDS || !(movedNodeIds == null ? void 0 : movedNodeIds.length)) return;
|
|
2181
|
+
const ids = new Set(movedNodeIds.map(S2));
|
|
2182
|
+
const relatedLabels = nodesDS.get().filter(
|
|
2183
|
+
(n) => n.group === "labelNode" && (ids.has(S2(n.from)) || ids.has(S2(n.to)))
|
|
2184
|
+
);
|
|
2185
|
+
relatedLabels.forEach((labelNode) => {
|
|
2186
|
+
const fromId = S2(labelNode.from);
|
|
2187
|
+
const toId = S2(labelNode.to);
|
|
2188
|
+
const fp = instance.getPositions([fromId])[fromId];
|
|
2189
|
+
const tp = instance.getPositions([toId])[toId];
|
|
2190
|
+
if (!fp || !tp) return;
|
|
2191
|
+
instance.moveNode(labelNode.id, (fp.x + tp.x) / 2, (fp.y + tp.y) / 2);
|
|
2192
|
+
});
|
|
2193
|
+
};
|
|
2194
|
+
const captureBaseStyles = () => {
|
|
2195
|
+
const nodesDS = nodesRef.current;
|
|
2196
|
+
const edgesDS = edgesRef.current;
|
|
2197
|
+
if (!nodesDS || !edgesDS) return;
|
|
2198
|
+
baseNodeStyleRef.current.clear();
|
|
2199
|
+
baseEdgeStyleRef.current.clear();
|
|
2200
|
+
nodesDS.get().forEach((n) => {
|
|
2201
|
+
baseNodeStyleRef.current.set(S2(n.id), {
|
|
2202
|
+
borderWidth: n.borderWidth ?? 1.5,
|
|
2203
|
+
color: n.color ?? void 0,
|
|
2204
|
+
shadow: n.shadow ?? false
|
|
2205
|
+
});
|
|
2206
|
+
});
|
|
2207
|
+
edgesDS.get().forEach((e) => {
|
|
2208
|
+
baseEdgeStyleRef.current.set(S2(e.id), {
|
|
2209
|
+
width: e.width ?? 2,
|
|
2210
|
+
color: e.color ?? void 0,
|
|
2211
|
+
shadow: e.shadow ?? false,
|
|
2212
|
+
dashes: e.dashes ?? false
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
2215
|
+
};
|
|
2216
|
+
const paintSelectionStyles = () => {
|
|
2217
|
+
const instance = networkInstanceRef.current;
|
|
2218
|
+
const nodesDS = nodesRef.current;
|
|
2219
|
+
const edgesDS = edgesRef.current;
|
|
2220
|
+
if (!instance || !nodesDS || !edgesDS) return;
|
|
2221
|
+
const selectedNodeIds = new Set(instance.getSelectedNodes().map(S2));
|
|
2222
|
+
const selectedEdgeIds = new Set(instance.getSelectedEdges().map(S2));
|
|
2223
|
+
const nodeUpdates = [];
|
|
2224
|
+
nodesDS.getIds().map(S2).forEach((id) => {
|
|
2225
|
+
const base = baseNodeStyleRef.current.get(id) || {};
|
|
2226
|
+
if (selectedNodeIds.has(id)) {
|
|
2227
|
+
nodeUpdates.push({
|
|
2228
|
+
id,
|
|
2229
|
+
borderWidth: 6,
|
|
2230
|
+
shadow: {
|
|
2231
|
+
enabled: true,
|
|
2232
|
+
color: "rgba(0,168,255,0.55)",
|
|
2233
|
+
size: 20,
|
|
2234
|
+
x: 0,
|
|
2235
|
+
y: 0
|
|
2236
|
+
},
|
|
2237
|
+
color: {
|
|
2238
|
+
...typeof base.color === "object" ? base.color : {},
|
|
2239
|
+
border: "#00A8FF"
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
} else {
|
|
2243
|
+
nodeUpdates.push({
|
|
2244
|
+
id,
|
|
2245
|
+
borderWidth: base.borderWidth ?? 1.5,
|
|
2246
|
+
shadow: base.shadow ?? false,
|
|
2247
|
+
color: base.color
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
const edgeUpdates = [];
|
|
2252
|
+
edgesDS.getIds().map(S2).forEach((id) => {
|
|
2253
|
+
var _a2;
|
|
2254
|
+
const base = baseEdgeStyleRef.current.get(id) || {};
|
|
2255
|
+
if (selectedEdgeIds.has(id)) {
|
|
2256
|
+
const baseColor = typeof base.color === "object" ? (_a2 = base.color) == null ? void 0 : _a2.color : void 0;
|
|
2257
|
+
const c = baseColor || "#00A8FF";
|
|
2258
|
+
edgeUpdates.push({
|
|
2259
|
+
id,
|
|
2260
|
+
width: Math.max(base.width ?? 2, 4),
|
|
2261
|
+
shadow: {
|
|
2262
|
+
enabled: true,
|
|
2263
|
+
color: "rgba(0,168,255,0.65)",
|
|
2264
|
+
size: 14,
|
|
2265
|
+
x: 0,
|
|
2266
|
+
y: 0
|
|
2267
|
+
},
|
|
2268
|
+
color: { color: c, highlight: c, hover: c, inherit: false },
|
|
2269
|
+
dashes: base.dashes ?? false
|
|
2270
|
+
});
|
|
2271
|
+
} else {
|
|
2272
|
+
edgeUpdates.push({
|
|
2273
|
+
id,
|
|
2274
|
+
width: base.width ?? 2,
|
|
2275
|
+
shadow: base.shadow ?? false,
|
|
2276
|
+
color: base.color,
|
|
2277
|
+
dashes: base.dashes ?? false
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
if (nodeUpdates.length) nodesDS.update(nodeUpdates);
|
|
2282
|
+
if (edgeUpdates.length) edgesDS.update(edgeUpdates);
|
|
2283
|
+
};
|
|
2284
|
+
const edgeColorPalette = useMemo(
|
|
2285
|
+
() => [
|
|
2286
|
+
"#ef4444",
|
|
2287
|
+
"#f97316",
|
|
2288
|
+
"#f59e0b",
|
|
2289
|
+
"#eab308",
|
|
2290
|
+
"#22c55e",
|
|
2291
|
+
"#14b8a6",
|
|
2292
|
+
"#06b6d4",
|
|
2293
|
+
"#3b82f6",
|
|
2294
|
+
"#8b5cf6",
|
|
2295
|
+
"#ec4899"
|
|
2296
|
+
],
|
|
2297
|
+
[]
|
|
2298
|
+
);
|
|
2299
|
+
const getPanelShellStyle = () => ({
|
|
2300
|
+
position: "absolute",
|
|
2301
|
+
top: 12,
|
|
2302
|
+
left: 12,
|
|
2303
|
+
zIndex: 99999,
|
|
2304
|
+
width: 300,
|
|
2305
|
+
maxWidth: "calc(100% - 24px)",
|
|
2306
|
+
borderRadius: 16,
|
|
2307
|
+
padding: 10,
|
|
2308
|
+
backdropFilter: "blur(12px)",
|
|
2309
|
+
WebkitBackdropFilter: "blur(12px)",
|
|
2310
|
+
background: isDarkMode ? "linear-gradient(160deg, rgba(15,23,42,0.92), rgba(30,41,59,0.86))" : "linear-gradient(160deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))",
|
|
2311
|
+
border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.24)" : "rgba(15,23,42,0.08)"}`,
|
|
2312
|
+
boxShadow: isDarkMode ? "0 14px 40px rgba(2,6,23,.48)" : "0 14px 36px rgba(15,23,42,.10)",
|
|
2313
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
2314
|
+
overflow: "hidden"
|
|
2315
|
+
});
|
|
2316
|
+
const panelInsetStyle = {
|
|
2317
|
+
padding: "8px 10px",
|
|
2318
|
+
borderRadius: 10,
|
|
2319
|
+
background: isDarkMode ? "rgba(15,23,42,.72)" : "rgba(255,255,255,.82)",
|
|
2320
|
+
border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.2)" : "rgba(15,23,42,0.08)"}`,
|
|
2321
|
+
boxShadow: isDarkMode ? "0 10px 24px rgba(2,6,23,.18)" : "0 10px 24px rgba(15,23,42,.06)"
|
|
2322
|
+
};
|
|
2323
|
+
const panelToggleRowStyle = {
|
|
2324
|
+
display: "flex",
|
|
2325
|
+
alignItems: "center",
|
|
2326
|
+
justifyContent: "space-between",
|
|
2327
|
+
gap: 6,
|
|
2328
|
+
padding: "0 1px 5px"
|
|
2329
|
+
};
|
|
2330
|
+
const panelCollapseBodyStyle = (open) => ({
|
|
2331
|
+
maxHeight: open ? 620 : 0,
|
|
2332
|
+
opacity: open ? 1 : 0,
|
|
2333
|
+
transition: "max-height 260ms ease, opacity 180ms ease",
|
|
2334
|
+
overflow: "hidden",
|
|
2335
|
+
pointerEvents: open ? "auto" : "none"
|
|
2336
|
+
});
|
|
2337
|
+
const compactInputStyle = {
|
|
2338
|
+
width: "100%",
|
|
2339
|
+
borderRadius: 8,
|
|
2340
|
+
border: `1px solid ${isDarkMode ? "#334155" : "#cbd5e1"}`,
|
|
2341
|
+
background: isDarkMode ? "rgba(15,23,42,.92)" : "#fff",
|
|
2342
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
2343
|
+
padding: "8px 10px",
|
|
2344
|
+
outline: "none",
|
|
2345
|
+
fontSize: 12,
|
|
2346
|
+
boxSizing: "border-box"
|
|
2347
|
+
};
|
|
2348
|
+
const compactSectionLabel = {
|
|
2349
|
+
fontSize: 12,
|
|
2350
|
+
fontWeight: 900,
|
|
2351
|
+
marginBottom: 8,
|
|
2352
|
+
opacity: 1,
|
|
2353
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
2354
|
+
background: isDarkMode ? "rgba(14,165,233,.12)" : "rgba(14,165,233,.10)",
|
|
2355
|
+
padding: "4px 8px",
|
|
2356
|
+
borderRadius: 999,
|
|
2357
|
+
border: `1px solid ${isDarkMode ? "rgba(56,189,248,.22)" : "rgba(14,165,233,.18)"}`
|
|
2358
|
+
};
|
|
2359
|
+
useEffect(() => {
|
|
2360
|
+
if (!containerRef.current || initializedRef.current) return;
|
|
2361
|
+
nodesRef.current = new DataSet([]);
|
|
2362
|
+
edgesRef.current = new DataSet([]);
|
|
2363
|
+
const baseOptions = buildOptions(!!isDarkMode, {
|
|
2364
|
+
dragNodes: nodesDraggableRef.current
|
|
2365
|
+
});
|
|
2366
|
+
const instance = new Network(
|
|
2367
|
+
containerRef.current,
|
|
2368
|
+
{ nodes: nodesRef.current, edges: edgesRef.current },
|
|
2369
|
+
{
|
|
2370
|
+
...baseOptions,
|
|
2371
|
+
physics: { enabled: false },
|
|
2372
|
+
interaction: buildInteractionOptions(nodesDraggableRef.current)
|
|
2373
|
+
}
|
|
2374
|
+
);
|
|
2375
|
+
networkInstanceRef.current = instance;
|
|
2376
|
+
initializedRef.current = true;
|
|
2377
|
+
instance.on("beforeDrawing", (ctx) => {
|
|
2378
|
+
if (!showGridRef.current) return;
|
|
2379
|
+
drawNetworkGrid(ctx, instance, isDarkModeRef.current);
|
|
2380
|
+
});
|
|
2381
|
+
const syncSelection = () => {
|
|
2382
|
+
const nodeIds = instance.getSelectedNodes().map(S2);
|
|
2383
|
+
const edgeIds = instance.getSelectedEdges().map(S2);
|
|
2384
|
+
selectedNodeIdsRef.current = [...nodeIds];
|
|
2385
|
+
selectedEdgeIdsRef.current = [...edgeIds];
|
|
2386
|
+
setSelectedNodes(
|
|
2387
|
+
nodeIds.map((id) => nodesRef.current.get(id)).filter(Boolean)
|
|
2388
|
+
);
|
|
2389
|
+
SetSelectedEdges(
|
|
2390
|
+
edgeIds.map((id) => edgesRef.current.get(id)).filter(Boolean)
|
|
2391
|
+
);
|
|
2392
|
+
paintSelectionStyles();
|
|
2393
|
+
};
|
|
2394
|
+
instance.on("zoom", (params) => SetScale(params.scale));
|
|
2395
|
+
instance.on("dragging", (params) => {
|
|
2396
|
+
var _a2;
|
|
2397
|
+
if (!((_a2 = params == null ? void 0 : params.nodes) == null ? void 0 : _a2.length)) return;
|
|
2398
|
+
const movedMain = params.nodes.map(S2).filter((id) => !isHelperNodeId(id));
|
|
2399
|
+
if (!movedMain.length) return;
|
|
2400
|
+
syncAttachedNodesLive(movedMain);
|
|
2401
|
+
syncEdgeLabelNodesLive(movedMain);
|
|
2402
|
+
});
|
|
2403
|
+
instance.on("dragEnd", (params) => {
|
|
2404
|
+
var _a2;
|
|
2405
|
+
const viewPos = instance.getViewPosition();
|
|
2406
|
+
SetXPosition(viewPos.x);
|
|
2407
|
+
SetYPosition(viewPos.y);
|
|
2408
|
+
if ((_a2 = params == null ? void 0 : params.nodes) == null ? void 0 : _a2.length) {
|
|
2409
|
+
const movedIds = params.nodes.map(S2).filter((id) => !isHelperNodeId(id));
|
|
2410
|
+
if (!movedIds.length) {
|
|
2411
|
+
syncSelection();
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
const currentPositions = instance.getPositions(movedIds);
|
|
2415
|
+
const dataCopy = [...dataRef.current];
|
|
2416
|
+
const posMap = new Map(
|
|
2417
|
+
(newPositionsRef.current || []).map((p) => [
|
|
2418
|
+
S2(p.id),
|
|
2419
|
+
{ ...p, id: S2(p.id) }
|
|
2420
|
+
])
|
|
2421
|
+
);
|
|
2422
|
+
movedIds.forEach((id) => {
|
|
2423
|
+
const p = currentPositions[id];
|
|
2424
|
+
if (!p) return;
|
|
2425
|
+
const gridOn = !!showGridRef.current;
|
|
2426
|
+
const finalPos = gridOn ? snapToGridIfNeeded(p.x, p.y, instance) : { x: p.x, y: p.y };
|
|
2427
|
+
if (gridOn) {
|
|
2428
|
+
instance.moveNode(id, finalPos.x, finalPos.y);
|
|
2429
|
+
}
|
|
2430
|
+
const i = dataCopy.findIndex((d) => S2(d.id) === id);
|
|
2431
|
+
if (i >= 0)
|
|
2432
|
+
dataCopy[i] = { ...dataCopy[i], x: finalPos.x, y: finalPos.y };
|
|
2433
|
+
posMap.set(id, { id, x: finalPos.x, y: finalPos.y });
|
|
2434
|
+
const arrowId = `${id}_Arrow`;
|
|
2435
|
+
if (nodesRef.current.get(arrowId)) {
|
|
2436
|
+
const arrowPos = gridOn ? snapToGridIfNeeded(finalPos.x, finalPos.y - 46, instance) : { x: finalPos.x, y: finalPos.y - 46 };
|
|
2437
|
+
instance.moveNode(arrowId, arrowPos.x, arrowPos.y);
|
|
2438
|
+
posMap.set(arrowId, { id: arrowId, x: arrowPos.x, y: arrowPos.y });
|
|
2439
|
+
}
|
|
2440
|
+
const hotId = `${id}hotWallet`;
|
|
2441
|
+
if (nodesRef.current.get(hotId)) {
|
|
2442
|
+
const owner = nodesRef.current.get(id);
|
|
2443
|
+
const hotY = (owner == null ? void 0 : owner.main) ? finalPos.y - 45 : finalPos.y - 25;
|
|
2444
|
+
const hotPos = gridOn ? snapToGridIfNeeded(finalPos.x, hotY, instance) : { x: finalPos.x, y: hotY };
|
|
2445
|
+
instance.moveNode(hotId, hotPos.x, hotPos.y);
|
|
2446
|
+
posMap.set(hotId, { id: hotId, x: hotPos.x, y: hotPos.y });
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
syncEdgeLabelNodesLive(movedIds);
|
|
2450
|
+
const nextPositions = Array.from(posMap.values());
|
|
2451
|
+
newPositionsRef.current = nextPositions;
|
|
2452
|
+
SetData(dataCopy);
|
|
2453
|
+
dataRef.current = dataCopy;
|
|
2454
|
+
SetNodesPosition(nextPositions);
|
|
2455
|
+
try {
|
|
2456
|
+
instance.storePositions();
|
|
2457
|
+
} catch (_) {
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
syncSelection();
|
|
2461
|
+
});
|
|
2462
|
+
instance.on("hoverNode", (params) => {
|
|
2463
|
+
var _a2;
|
|
2464
|
+
const id = S2(params.node);
|
|
2465
|
+
if (isHelperNodeId(id)) {
|
|
2466
|
+
setHoverInfo({ visible: false, x: 0, y: 0, node: null });
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
setEdgeHoverInfo({
|
|
2470
|
+
visible: false,
|
|
2471
|
+
x: 0,
|
|
2472
|
+
y: 0,
|
|
2473
|
+
fromId: "",
|
|
2474
|
+
toId: "",
|
|
2475
|
+
summary: ""
|
|
2476
|
+
});
|
|
2477
|
+
const n = (_a2 = nodesRef.current) == null ? void 0 : _a2.get(id);
|
|
2478
|
+
if (!n) return;
|
|
2479
|
+
const dom = instance.canvasToDOM(instance.getPositions([id])[id]);
|
|
2480
|
+
setHoverInfo({ visible: true, x: dom.x, y: dom.y - 18, node: n });
|
|
2481
|
+
});
|
|
2482
|
+
instance.on(
|
|
2483
|
+
"blurNode",
|
|
2484
|
+
() => setHoverInfo({ visible: false, x: 0, y: 0, node: null })
|
|
2485
|
+
);
|
|
2486
|
+
instance.on("hoverEdge", (params) => {
|
|
2487
|
+
var _a2, _b, _c, _d;
|
|
2488
|
+
if (!showEdgeHoverInfoRef.current) return;
|
|
2489
|
+
const edge = (_a2 = edgesRef.current) == null ? void 0 : _a2.get(S2(params.edge));
|
|
2490
|
+
if (!edge) return;
|
|
2491
|
+
const fromId = S2(edge.from);
|
|
2492
|
+
const toId = S2(edge.to);
|
|
2493
|
+
const payload = findEdgePayload(fromId, toId);
|
|
2494
|
+
const summary = payload ? buildEdgeInfoLabel(payload, edgeLabelFieldsRef.current) : "";
|
|
2495
|
+
const rect = (_b = containerRef.current) == null ? void 0 : _b.getBoundingClientRect();
|
|
2496
|
+
const clientX = ((_c = params == null ? void 0 : params.event) == null ? void 0 : _c.clientX) ?? 0;
|
|
2497
|
+
const clientY = ((_d = params == null ? void 0 : params.event) == null ? void 0 : _d.clientY) ?? 0;
|
|
2498
|
+
const x = rect ? clientX - rect.left : clientX;
|
|
2499
|
+
const y = rect ? clientY - rect.top : clientY;
|
|
2500
|
+
setHoverInfo({ visible: false, x: 0, y: 0, node: null });
|
|
2501
|
+
setEdgeHoverInfo({
|
|
2502
|
+
visible: true,
|
|
2503
|
+
x,
|
|
2504
|
+
y: y - 12,
|
|
2505
|
+
fromId,
|
|
2506
|
+
toId,
|
|
2507
|
+
summary
|
|
2508
|
+
});
|
|
2509
|
+
});
|
|
2510
|
+
instance.on("blurEdge", () => {
|
|
2511
|
+
if (!showEdgeHoverInfoRef.current) return;
|
|
2512
|
+
setEdgeHoverInfo({
|
|
2513
|
+
visible: false,
|
|
2514
|
+
x: 0,
|
|
2515
|
+
y: 0,
|
|
2516
|
+
fromId: "",
|
|
2517
|
+
toId: "",
|
|
2518
|
+
summary: ""
|
|
2519
|
+
});
|
|
2520
|
+
});
|
|
2521
|
+
instance.on("selectNode", syncSelection);
|
|
2522
|
+
instance.on("selectEdge", syncSelection);
|
|
2523
|
+
instance.on("deselectNode", syncSelection);
|
|
2524
|
+
instance.on("deselectEdge", syncSelection);
|
|
2525
|
+
return () => {
|
|
2526
|
+
if (networkInstanceRef.current) {
|
|
2527
|
+
try {
|
|
2528
|
+
networkInstanceRef.current.destroy();
|
|
2529
|
+
} catch (_) {
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
networkInstanceRef.current = null;
|
|
2533
|
+
initializedRef.current = false;
|
|
2534
|
+
viewportInitializedRef.current = false;
|
|
2535
|
+
};
|
|
2536
|
+
}, []);
|
|
2537
|
+
useEffect(() => {
|
|
2538
|
+
if (!initializedRef.current) return;
|
|
2539
|
+
const instance = networkInstanceRef.current;
|
|
2540
|
+
const nodesDS = nodesRef.current;
|
|
2541
|
+
const edgesDS = edgesRef.current;
|
|
2542
|
+
if (!instance || !nodesDS || !edgesDS) return;
|
|
2543
|
+
const posMap = new Map(
|
|
2544
|
+
(newPositionsRef.current || []).map((p) => [S2(p.id), p])
|
|
2545
|
+
);
|
|
2546
|
+
const nodesArr = [];
|
|
2547
|
+
const edgesArr = [];
|
|
2548
|
+
const defaultBorder = isDarkMode ? "#93c5fd" : "rgb(50, 87, 155)";
|
|
2549
|
+
const paintedMap = /* @__PURE__ */ new Map();
|
|
2550
|
+
(PaintedEdges || []).forEach((p) => {
|
|
2551
|
+
const id = S2(p.id);
|
|
2552
|
+
if (!id) return;
|
|
2553
|
+
const entry = {
|
|
2554
|
+
color: p.color || null,
|
|
2555
|
+
lineStyle: p.lineStyle || null
|
|
2556
|
+
};
|
|
2557
|
+
if (entry.color || entry.lineStyle) paintedMap.set(id, entry);
|
|
2558
|
+
});
|
|
2559
|
+
Data.forEach((item) => {
|
|
2560
|
+
const id = S2(item.id);
|
|
2561
|
+
const p = posMap.get(id);
|
|
2562
|
+
const x = Number((p == null ? void 0 : p.x) ?? item.x ?? 0);
|
|
2563
|
+
const y = Number((p == null ? void 0 : p.y) ?? item.y ?? 0);
|
|
2564
|
+
const label = getNodeDisplayLabel(item);
|
|
2565
|
+
const borderColor = getRateBorderColor(
|
|
2566
|
+
item.rate,
|
|
2567
|
+
resolvedRateColorRanges,
|
|
2568
|
+
defaultBorder
|
|
2569
|
+
);
|
|
2570
|
+
nodesArr.push({
|
|
2571
|
+
...item,
|
|
2572
|
+
id,
|
|
2573
|
+
x,
|
|
2574
|
+
y,
|
|
2575
|
+
group: item.type,
|
|
2576
|
+
selectable: true,
|
|
2577
|
+
main: !!item.main,
|
|
2578
|
+
borderWidth: 2,
|
|
2579
|
+
color: { border: borderColor },
|
|
2580
|
+
rate: item.rate ?? null,
|
|
2581
|
+
address: item.text,
|
|
2582
|
+
image: getNodeEntityImage(item),
|
|
2583
|
+
label
|
|
2584
|
+
});
|
|
2585
|
+
if (item.metadata != null) {
|
|
2586
|
+
nodesArr.push({
|
|
2587
|
+
id: `${id}hotWallet`,
|
|
2588
|
+
x,
|
|
2589
|
+
y: item.main ? y - 45 : y - 25,
|
|
2590
|
+
group: "hotWallet",
|
|
2591
|
+
image: "/images/fire.png",
|
|
2592
|
+
label: item.metadata,
|
|
2593
|
+
selectable: false,
|
|
2594
|
+
physics: false,
|
|
2595
|
+
fixed: { x: true, y: true }
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
if (item.main) {
|
|
2599
|
+
nodesArr.push({
|
|
2600
|
+
id: `${id}_Arrow`,
|
|
2601
|
+
x,
|
|
2602
|
+
y: y - 46,
|
|
2603
|
+
group: "Arrow",
|
|
2604
|
+
label: "\u2193",
|
|
2605
|
+
selectable: false,
|
|
2606
|
+
physics: false,
|
|
2607
|
+
fixed: { x: true, y: true }
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
const rawEdges = SetEdgesData(Data).map((e, idx) => {
|
|
2612
|
+
const from = S2(e.from);
|
|
2613
|
+
const to = S2(e.to);
|
|
2614
|
+
const id = e.id != null ? S2(e.id) : `${from}__${to}__${S2(e.label ?? "")}__${idx}`;
|
|
2615
|
+
const painted = paintedMap.get(id) || { color: null, lineStyle: null };
|
|
2616
|
+
const visStyle = resolveEdgeVisStyle({
|
|
2617
|
+
isDark: isDarkMode,
|
|
2618
|
+
paintedColor: painted.color,
|
|
2619
|
+
paintedLineStyle: painted.lineStyle
|
|
2620
|
+
});
|
|
2621
|
+
return {
|
|
2622
|
+
id,
|
|
2623
|
+
from,
|
|
2624
|
+
to,
|
|
2625
|
+
label: e.label != null ? S2(e.label) : "",
|
|
2626
|
+
selectable: true,
|
|
2627
|
+
...visStyle
|
|
2628
|
+
};
|
|
2629
|
+
});
|
|
2630
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2631
|
+
const uniqueEdges = rawEdges.filter((e) => {
|
|
2632
|
+
const key = `${e.from}__${e.to}__${S2(e.label ?? "")}`;
|
|
2633
|
+
if (seen.has(key)) return false;
|
|
2634
|
+
seen.add(key);
|
|
2635
|
+
return true;
|
|
2636
|
+
});
|
|
2637
|
+
edgesArr.push(...uniqueEdges);
|
|
2638
|
+
if (showEdgeInfoNodes && resolvedEdgeLabelFields.length) {
|
|
2639
|
+
Data.forEach((node) => {
|
|
2640
|
+
if (node.type !== NODE_TYPE.MAIN) return;
|
|
2641
|
+
const nodeId = S2(node.id);
|
|
2642
|
+
const selfPos = posMap.get(nodeId) || {
|
|
2643
|
+
x: node.x ?? 0,
|
|
2644
|
+
y: node.y ?? 0
|
|
2645
|
+
};
|
|
2646
|
+
const makeLabel = (edgeData) => buildEdgeInfoLabel(edgeData, resolvedEdgeLabelFields);
|
|
2647
|
+
(node.inputs || []).forEach((inp) => {
|
|
2648
|
+
const other = Data.find((d) => S2(d.id) === S2(inp.id));
|
|
2649
|
+
if (!other) return;
|
|
2650
|
+
const otherId = S2(other.id);
|
|
2651
|
+
const otherPos = posMap.get(otherId) || {
|
|
2652
|
+
x: other.x ?? 0,
|
|
2653
|
+
y: other.y ?? 0
|
|
2654
|
+
};
|
|
2655
|
+
nodesArr.push({
|
|
2656
|
+
id: `${nodeId}Tr${otherId}_in`,
|
|
2657
|
+
x: (selfPos.x + otherPos.x) / 2,
|
|
2658
|
+
y: (selfPos.y + otherPos.y) / 2,
|
|
2659
|
+
from: nodeId,
|
|
2660
|
+
to: otherId,
|
|
2661
|
+
group: "labelNode",
|
|
2662
|
+
label: makeLabel(inp),
|
|
2663
|
+
selectable: false,
|
|
2664
|
+
physics: false,
|
|
2665
|
+
fixed: { x: true, y: true }
|
|
2666
|
+
});
|
|
2667
|
+
});
|
|
2668
|
+
(node.outputs || []).forEach((out) => {
|
|
2669
|
+
const other = Data.find((d) => S2(d.id) === S2(out.id));
|
|
2670
|
+
if (!other) return;
|
|
2671
|
+
const otherId = S2(other.id);
|
|
2672
|
+
const otherPos = posMap.get(otherId) || {
|
|
2673
|
+
x: other.x ?? 0,
|
|
2674
|
+
y: other.y ?? 0
|
|
2675
|
+
};
|
|
2676
|
+
nodesArr.push({
|
|
2677
|
+
id: `${nodeId}Tr${otherId}_out`,
|
|
2678
|
+
x: (selfPos.x + otherPos.x) / 2,
|
|
2679
|
+
y: (selfPos.y + otherPos.y) / 2,
|
|
2680
|
+
from: nodeId,
|
|
2681
|
+
to: otherId,
|
|
2682
|
+
group: "labelNode",
|
|
2683
|
+
label: makeLabel(out),
|
|
2684
|
+
selectable: false,
|
|
2685
|
+
physics: false,
|
|
2686
|
+
fixed: { x: true, y: true }
|
|
2687
|
+
});
|
|
2688
|
+
});
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
nodesDS.clear();
|
|
2692
|
+
edgesDS.clear();
|
|
2693
|
+
nodesDS.add(nodesArr);
|
|
2694
|
+
edgesDS.add(edgesArr);
|
|
2695
|
+
const validNodeIds = selectedNodeIdsRef.current.map(S2).filter((id) => !!nodesDS.get(id));
|
|
2696
|
+
const validEdgeIds = selectedEdgeIdsRef.current.map(S2).filter((id) => !!edgesDS.get(id));
|
|
2697
|
+
instance.setSelection(
|
|
2698
|
+
{ nodes: validNodeIds, edges: validEdgeIds },
|
|
2699
|
+
{ unselectAll: true, highlightEdges: false }
|
|
2700
|
+
);
|
|
2701
|
+
captureBaseStyles();
|
|
2702
|
+
paintSelectionStyles();
|
|
2703
|
+
if (!viewportInitializedRef.current) {
|
|
2704
|
+
instance.moveTo({
|
|
2705
|
+
scale: Scale,
|
|
2706
|
+
position: { x: XPosition, y: YPosition }
|
|
2707
|
+
});
|
|
2708
|
+
viewportInitializedRef.current = true;
|
|
2709
|
+
}
|
|
2710
|
+
}, [
|
|
2711
|
+
Data,
|
|
2712
|
+
Reload,
|
|
2713
|
+
resolvedEdgeLabelFields,
|
|
2714
|
+
resolvedRateColorRanges,
|
|
2715
|
+
PaintedEdges,
|
|
2716
|
+
isDarkMode,
|
|
2717
|
+
Scale,
|
|
2718
|
+
XPosition,
|
|
2719
|
+
YPosition,
|
|
2720
|
+
showEdgeInfoNodes
|
|
2721
|
+
]);
|
|
2722
|
+
useEffect(() => {
|
|
2723
|
+
if (!networkInstanceRef.current) return;
|
|
2724
|
+
const baseOptions = buildOptions(!!isDarkMode, {
|
|
2725
|
+
dragNodes: nodesDraggableRef.current
|
|
2726
|
+
});
|
|
2727
|
+
networkInstanceRef.current.setOptions({
|
|
2728
|
+
...baseOptions,
|
|
2729
|
+
physics: { enabled: false },
|
|
2730
|
+
interaction: buildInteractionOptions(nodesDraggableRef.current)
|
|
2731
|
+
});
|
|
2732
|
+
paintSelectionStyles();
|
|
2733
|
+
}, [isDarkMode]);
|
|
2734
|
+
useEffect(() => {
|
|
2735
|
+
if (!isStart) {
|
|
2736
|
+
SetReload(!Reload);
|
|
2737
|
+
setIsStart(true);
|
|
2738
|
+
}
|
|
2739
|
+
}, []);
|
|
2740
|
+
useEffect(() => {
|
|
2741
|
+
if (screenShot !== TakeSceenShot) {
|
|
2742
|
+
takeFullGraphScreenshot();
|
|
2743
|
+
setScreenShot(TakeSceenShot);
|
|
2744
|
+
}
|
|
2745
|
+
}, [TakeSceenShot]);
|
|
2746
|
+
useEffect(() => {
|
|
2747
|
+
const t = setTimeout(() => {
|
|
2748
|
+
if (searchTerm.trim()) runSearch(searchTerm);
|
|
2749
|
+
}, 180);
|
|
2750
|
+
return () => clearTimeout(t);
|
|
2751
|
+
}, [searchTerm]);
|
|
2752
|
+
useEffect(() => {
|
|
2753
|
+
const network = networkInstanceRef.current;
|
|
2754
|
+
const nodesDS = nodesRef.current;
|
|
2755
|
+
if (!network || !nodesDS) return;
|
|
2756
|
+
const S3 = (v) => String(v ?? "");
|
|
2757
|
+
const isIgnoredNode = (id) => {
|
|
2758
|
+
const s = S3(id);
|
|
2759
|
+
return s.endsWith("hotWallet") || // helper
|
|
2760
|
+
s.endsWith("_Arrow") || // helper
|
|
2761
|
+
s.includes("Tr") || // edgeInfo-like
|
|
2762
|
+
s.includes("edgeInfo");
|
|
2763
|
+
};
|
|
2764
|
+
const handleNodeClick = (params) => {
|
|
2765
|
+
if (!(params == null ? void 0 : params.nodes) || params.nodes.length === 0) {
|
|
2766
|
+
SetNode(null);
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
const clickedId = params.nodes[0];
|
|
2770
|
+
if (!clickedId) {
|
|
2771
|
+
SetNode(null);
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
if (isIgnoredNode(clickedId)) {
|
|
2775
|
+
SetNode(null);
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
const nodeData = nodesDS.get(clickedId);
|
|
2779
|
+
if (!nodeData) {
|
|
2780
|
+
SetNode(null);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
const rawNode = (dataRef.current || []).find(
|
|
2784
|
+
(d) => S3(d.id) === S3(clickedId)
|
|
2785
|
+
);
|
|
2786
|
+
emitNodeClick(rawNode ? { ...rawNode } : { ...nodeData });
|
|
2787
|
+
};
|
|
2788
|
+
network.on("click", handleNodeClick);
|
|
2789
|
+
return () => {
|
|
2790
|
+
network.off("click", handleNodeClick);
|
|
2791
|
+
};
|
|
2792
|
+
}, [SetNode]);
|
|
2793
|
+
const actionButtonBase = {
|
|
2794
|
+
border: "none",
|
|
2795
|
+
borderRadius: 10,
|
|
2796
|
+
padding: "8px 12px",
|
|
2797
|
+
cursor: "pointer",
|
|
2798
|
+
fontWeight: 700,
|
|
2799
|
+
fontSize: 11,
|
|
2800
|
+
color: "#fff",
|
|
2801
|
+
display: "inline-flex",
|
|
2802
|
+
alignItems: "center",
|
|
2803
|
+
justifyContent: "center",
|
|
2804
|
+
gap: 7,
|
|
2805
|
+
boxShadow: "0 4px 14px rgba(2,6,23,.2)",
|
|
2806
|
+
transition: "transform 120ms ease, filter 160ms ease, box-shadow 160ms ease"
|
|
2807
|
+
};
|
|
2808
|
+
const getGlassShellStyle = () => ({
|
|
2809
|
+
backdropFilter: "blur(12px)",
|
|
2810
|
+
WebkitBackdropFilter: "blur(12px)",
|
|
2811
|
+
background: isDarkMode ? "linear-gradient(160deg, rgba(15,23,42,0.92), rgba(30,41,59,0.86))" : "linear-gradient(160deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))",
|
|
2812
|
+
border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.24)" : "rgba(15,23,42,0.08)"}`,
|
|
2813
|
+
boxShadow: isDarkMode ? "0 14px 40px rgba(2,6,23,.48)" : "0 14px 36px rgba(15,23,42,.10)",
|
|
2814
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a"
|
|
2815
|
+
});
|
|
2816
|
+
const toolbarButtonStyle = (bg, extra = {}) => ({
|
|
2817
|
+
...actionButtonBase,
|
|
2818
|
+
background: bg,
|
|
2819
|
+
...extra
|
|
2820
|
+
});
|
|
2821
|
+
const ghostIconButtonStyle = (extra = {}) => ({
|
|
2822
|
+
display: "inline-flex",
|
|
2823
|
+
alignItems: "center",
|
|
2824
|
+
justifyContent: "center",
|
|
2825
|
+
gap: 6,
|
|
2826
|
+
border: `1px solid ${isDarkMode ? "#475569" : "#cbd5e1"}`,
|
|
2827
|
+
borderRadius: 12,
|
|
2828
|
+
padding: "7px 10px",
|
|
2829
|
+
cursor: "pointer",
|
|
2830
|
+
fontWeight: 700,
|
|
2831
|
+
fontSize: 12,
|
|
2832
|
+
background: isDarkMode ? "rgba(15,23,42,.65)" : "rgba(255,255,255,.9)",
|
|
2833
|
+
color: isDarkMode ? "#f8fafc" : "#0f172a",
|
|
2834
|
+
transition: "background 140ms ease, border-color 140ms ease",
|
|
2835
|
+
...extra
|
|
2836
|
+
});
|
|
2837
|
+
return /* @__PURE__ */ React2.createElement(
|
|
2838
|
+
"div",
|
|
2839
|
+
{
|
|
2840
|
+
style: {
|
|
2841
|
+
position: "relative",
|
|
2842
|
+
width: "100%",
|
|
2843
|
+
height: "100%",
|
|
2844
|
+
minHeight: 400
|
|
2845
|
+
}
|
|
2846
|
+
},
|
|
2847
|
+
/* @__PURE__ */ React2.createElement(
|
|
2848
|
+
"div",
|
|
2849
|
+
{
|
|
2850
|
+
id: "myGraphDiv",
|
|
2851
|
+
ref: containerRef,
|
|
2852
|
+
className: "outline-none",
|
|
2853
|
+
style: { width: "100%", height: "100%", outline: "none" }
|
|
2854
|
+
}
|
|
2855
|
+
),
|
|
2856
|
+
/* @__PURE__ */ React2.createElement(
|
|
2857
|
+
"div",
|
|
2858
|
+
{
|
|
2859
|
+
dir: isFa ? "rtl" : "ltr",
|
|
2860
|
+
style: {
|
|
2861
|
+
position: "absolute",
|
|
2862
|
+
top: 12,
|
|
2863
|
+
right: 12,
|
|
2864
|
+
zIndex: 1e5,
|
|
2865
|
+
display: "flex",
|
|
2866
|
+
flexWrap: "wrap",
|
|
2867
|
+
gap: 8,
|
|
2868
|
+
padding: 7,
|
|
2869
|
+
borderRadius: 14,
|
|
2870
|
+
...getGlassShellStyle()
|
|
2871
|
+
}
|
|
2872
|
+
},
|
|
2873
|
+
/* @__PURE__ */ React2.createElement(
|
|
2874
|
+
"button",
|
|
2875
|
+
{
|
|
2876
|
+
type: "button",
|
|
2877
|
+
onClick: takeFullGraphScreenshot,
|
|
2878
|
+
disabled: isCapturing,
|
|
2879
|
+
title: isFa ? "\u0627\u0633\u06A9\u0631\u06CC\u0646\u200C\u0634\u0627\u062A" : "Screenshot",
|
|
2880
|
+
style: toolbarButtonStyle(
|
|
2881
|
+
isCapturing ? "#64748b" : isDarkMode ? "linear-gradient(135deg,#0ea5e9,#0284c7)" : "linear-gradient(135deg,#0284c7,#0369a1)",
|
|
2882
|
+
{
|
|
2883
|
+
cursor: isCapturing ? "not-allowed" : "pointer",
|
|
2884
|
+
opacity: isCapturing ? 0.9 : 1
|
|
2885
|
+
}
|
|
2886
|
+
)
|
|
2887
|
+
},
|
|
2888
|
+
/* @__PURE__ */ React2.createElement(
|
|
2889
|
+
IconButtonContent,
|
|
2890
|
+
{
|
|
2891
|
+
icon: /* @__PURE__ */ React2.createElement(IconCamera, { size: 15 }),
|
|
2892
|
+
label: isCapturing ? isFa ? "..." : "..." : isFa ? "\u0627\u0633\u06A9\u0631\u06CC\u0646\u200C\u0634\u0627\u062A" : "Screenshot"
|
|
2893
|
+
}
|
|
2894
|
+
)
|
|
2895
|
+
),
|
|
2896
|
+
/* @__PURE__ */ React2.createElement(
|
|
2897
|
+
"button",
|
|
2898
|
+
{
|
|
2899
|
+
type: "button",
|
|
2900
|
+
onClick: takeGraphExcelExport,
|
|
2901
|
+
title: isFa ? "\u062E\u0631\u0648\u062C\u06CC \u0627\u06A9\u0633\u0644" : "Export Excel",
|
|
2902
|
+
style: toolbarButtonStyle(
|
|
2903
|
+
isDarkMode ? "linear-gradient(135deg,#0f766e,#0d9488)" : "linear-gradient(135deg,#0f766e,#0f766e)"
|
|
2904
|
+
)
|
|
2905
|
+
},
|
|
2906
|
+
/* @__PURE__ */ React2.createElement(
|
|
2907
|
+
IconButtonContent,
|
|
2908
|
+
{
|
|
2909
|
+
icon: /* @__PURE__ */ React2.createElement(IconTable, { size: 15 }),
|
|
2910
|
+
label: isFa ? "\u0627\u06A9\u0633\u0644" : "Excel"
|
|
2911
|
+
}
|
|
2912
|
+
)
|
|
2913
|
+
)
|
|
2914
|
+
),
|
|
2915
|
+
/* @__PURE__ */ React2.createElement("div", { dir: isFa ? "rtl" : "ltr", style: getPanelShellStyle() }, /* @__PURE__ */ React2.createElement("div", { style: panelToggleRowStyle }, /* @__PURE__ */ React2.createElement(
|
|
2916
|
+
"div",
|
|
2917
|
+
{
|
|
2918
|
+
style: {
|
|
2919
|
+
display: "flex",
|
|
2920
|
+
alignItems: "center",
|
|
2921
|
+
gap: 6,
|
|
2922
|
+
minWidth: 0
|
|
2923
|
+
}
|
|
2924
|
+
},
|
|
2925
|
+
/* @__PURE__ */ React2.createElement("div", { style: { fontSize: 13, fontWeight: 800, letterSpacing: 0.1 } }, isFa ? "\u067E\u0646\u0644 \u06AF\u0631\u0627\u0641" : "Graph Panel"),
|
|
2926
|
+
/* @__PURE__ */ React2.createElement(
|
|
2927
|
+
"span",
|
|
2928
|
+
{
|
|
2929
|
+
style: {
|
|
2930
|
+
fontSize: 11,
|
|
2931
|
+
fontWeight: 800,
|
|
2932
|
+
padding: "3px 8px",
|
|
2933
|
+
borderRadius: 999,
|
|
2934
|
+
background: isDarkMode ? "rgba(15,23,42,.7)" : "rgba(226,232,240,.95)",
|
|
2935
|
+
whiteSpace: "nowrap"
|
|
2936
|
+
}
|
|
2937
|
+
},
|
|
2938
|
+
isBasicEdition() && Number.isFinite(NODALIX_MAX_NODES) ? `${graphNodeCount}/${NODALIX_MAX_NODES}` : graphNodeCount
|
|
2939
|
+
)
|
|
2940
|
+
), /* @__PURE__ */ React2.createElement(
|
|
2941
|
+
"button",
|
|
2942
|
+
{
|
|
2943
|
+
type: "button",
|
|
2944
|
+
onClick: () => setPanelOpen((p) => !p),
|
|
2945
|
+
title: panelOpen ? isFa ? "\u0628\u0633\u062A\u0646" : "Collapse" : isFa ? "\u0628\u0627\u0632 \u06A9\u0631\u062F\u0646" : "Expand",
|
|
2946
|
+
style: {
|
|
2947
|
+
width: 30,
|
|
2948
|
+
height: 30,
|
|
2949
|
+
minWidth: 30,
|
|
2950
|
+
borderRadius: 8,
|
|
2951
|
+
border: `1px solid ${isDarkMode ? "#475569" : "#cbd5e1"}`,
|
|
2952
|
+
background: isDarkMode ? "#0f172a" : "#ffffff",
|
|
2953
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
2954
|
+
cursor: "pointer",
|
|
2955
|
+
display: "inline-flex",
|
|
2956
|
+
alignItems: "center",
|
|
2957
|
+
justifyContent: "center"
|
|
2958
|
+
}
|
|
2959
|
+
},
|
|
2960
|
+
/* @__PURE__ */ React2.createElement(IconChevron, { size: 15, up: panelOpen })
|
|
2961
|
+
)), /* @__PURE__ */ React2.createElement("div", { style: panelCollapseBodyStyle(panelOpen) }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 7 } }, nodeLimitReached ? /* @__PURE__ */ React2.createElement(
|
|
2962
|
+
"div",
|
|
2963
|
+
{
|
|
2964
|
+
style: {
|
|
2965
|
+
...panelInsetStyle,
|
|
2966
|
+
background: isDarkMode ? "rgba(127,29,29,.35)" : "rgba(254,226,226,.95)",
|
|
2967
|
+
border: `1px solid ${isDarkMode ? "#b91c1c" : "#fca5a5"}`,
|
|
2968
|
+
color: isDarkMode ? "#fecaca" : "#991b1b",
|
|
2969
|
+
fontSize: 11,
|
|
2970
|
+
fontWeight: 700,
|
|
2971
|
+
lineHeight: 1.4
|
|
2972
|
+
}
|
|
2973
|
+
},
|
|
2974
|
+
isFa ? `Basic: \u062D\u062F\u0627\u06A9\u062B\u0631 ${NODALIX_MAX_NODES} \u06AF\u0631\u0647` : `Basic: max ${NODALIX_MAX_NODES} nodes`
|
|
2975
|
+
) : null, languageUserConfigurable ? /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { dir: "ltr", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 } }, /* @__PURE__ */ React2.createElement(
|
|
2976
|
+
"button",
|
|
2977
|
+
{
|
|
2978
|
+
type: "button",
|
|
2979
|
+
onClick: () => setUserLang("fa"),
|
|
2980
|
+
style: {
|
|
2981
|
+
...ghostIconButtonStyle({
|
|
2982
|
+
border: "none",
|
|
2983
|
+
background: lang === "fa" ? "#0ea5e9" : "transparent",
|
|
2984
|
+
color: lang === "fa" ? "#fff" : isDarkMode ? "#e2e8f0" : "#0f172a"
|
|
2985
|
+
})
|
|
2986
|
+
}
|
|
2987
|
+
},
|
|
2988
|
+
/* @__PURE__ */ React2.createElement(IconButtonContent, { icon: /* @__PURE__ */ React2.createElement(IconGlobe, { size: 14 }), label: "FA" })
|
|
2989
|
+
), /* @__PURE__ */ React2.createElement(
|
|
2990
|
+
"button",
|
|
2991
|
+
{
|
|
2992
|
+
type: "button",
|
|
2993
|
+
onClick: () => setUserLang("en"),
|
|
2994
|
+
style: {
|
|
2995
|
+
...ghostIconButtonStyle({
|
|
2996
|
+
border: "none",
|
|
2997
|
+
background: lang === "en" ? "#0ea5e9" : "transparent",
|
|
2998
|
+
color: lang === "en" ? "#fff" : isDarkMode ? "#e2e8f0" : "#0f172a"
|
|
2999
|
+
})
|
|
3000
|
+
}
|
|
3001
|
+
},
|
|
3002
|
+
/* @__PURE__ */ React2.createElement(IconButtonContent, { icon: /* @__PURE__ */ React2.createElement(IconGlobe, { size: 14 }), label: "EN" })
|
|
3003
|
+
))) : null, /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement(
|
|
3004
|
+
"div",
|
|
3005
|
+
{
|
|
3006
|
+
style: {
|
|
3007
|
+
display: "flex",
|
|
3008
|
+
alignItems: "center",
|
|
3009
|
+
gap: 8,
|
|
3010
|
+
color: isDarkMode ? "#94a3b8" : "#64748b"
|
|
3011
|
+
}
|
|
3012
|
+
},
|
|
3013
|
+
/* @__PURE__ */ React2.createElement("span", { style: { display: "inline-flex", flexShrink: 0 } }, /* @__PURE__ */ React2.createElement(IconSearch, { size: 15 })),
|
|
3014
|
+
/* @__PURE__ */ React2.createElement(
|
|
3015
|
+
"input",
|
|
3016
|
+
{
|
|
3017
|
+
dir: isFa ? "rtl" : "ltr",
|
|
3018
|
+
value: searchTerm,
|
|
3019
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
3020
|
+
placeholder: isFa ? "\u062C\u0633\u062A\u062C\u0648: ID \u06CC\u0627 Label" : "Search: ID or Label",
|
|
3021
|
+
style: {
|
|
3022
|
+
...compactInputStyle,
|
|
3023
|
+
border: "none",
|
|
3024
|
+
background: "transparent",
|
|
3025
|
+
padding: 0,
|
|
3026
|
+
flex: 1
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
)
|
|
3030
|
+
)), edgeInfoNodesUserConfigurable || gridUserConfigurable || nodesDraggableUserConfigurable ? /* @__PURE__ */ React2.createElement(
|
|
3031
|
+
"div",
|
|
3032
|
+
{
|
|
3033
|
+
style: {
|
|
3034
|
+
...panelInsetStyle,
|
|
3035
|
+
display: "flex",
|
|
3036
|
+
flexWrap: "wrap",
|
|
3037
|
+
gap: "6px 12px",
|
|
3038
|
+
alignItems: "center"
|
|
3039
|
+
}
|
|
3040
|
+
},
|
|
3041
|
+
edgeInfoNodesUserConfigurable ? /* @__PURE__ */ React2.createElement(
|
|
3042
|
+
"label",
|
|
3043
|
+
{
|
|
3044
|
+
style: {
|
|
3045
|
+
display: "flex",
|
|
3046
|
+
alignItems: "center",
|
|
3047
|
+
gap: 4,
|
|
3048
|
+
cursor: "pointer",
|
|
3049
|
+
fontSize: 12,
|
|
3050
|
+
fontWeight: 700
|
|
3051
|
+
}
|
|
3052
|
+
},
|
|
3053
|
+
/* @__PURE__ */ React2.createElement(
|
|
3054
|
+
"input",
|
|
3055
|
+
{
|
|
3056
|
+
type: "checkbox",
|
|
3057
|
+
checked: showEdgeInfoNodes,
|
|
3058
|
+
onChange: (e) => setUserShowEdgeInfoNodes(e.target.checked),
|
|
3059
|
+
style: { width: 14, height: 14 }
|
|
3060
|
+
}
|
|
3061
|
+
),
|
|
3062
|
+
isFa ? "\u06CC\u0627\u0644" : "Edge"
|
|
3063
|
+
) : null,
|
|
3064
|
+
gridUserConfigurable ? /* @__PURE__ */ React2.createElement(
|
|
3065
|
+
"label",
|
|
3066
|
+
{
|
|
3067
|
+
style: {
|
|
3068
|
+
display: "flex",
|
|
3069
|
+
alignItems: "center",
|
|
3070
|
+
gap: 4,
|
|
3071
|
+
cursor: "pointer",
|
|
3072
|
+
fontSize: 12,
|
|
3073
|
+
fontWeight: 700
|
|
3074
|
+
}
|
|
3075
|
+
},
|
|
3076
|
+
/* @__PURE__ */ React2.createElement(
|
|
3077
|
+
"input",
|
|
3078
|
+
{
|
|
3079
|
+
type: "checkbox",
|
|
3080
|
+
checked: ShowGrid,
|
|
3081
|
+
onChange: (e) => setUserShowGrid(e.target.checked),
|
|
3082
|
+
style: { width: 14, height: 14 }
|
|
3083
|
+
}
|
|
3084
|
+
),
|
|
3085
|
+
isFa ? "\u06AF\u0631\u06CC\u062F" : "Grid"
|
|
3086
|
+
) : null,
|
|
3087
|
+
nodesDraggableUserConfigurable ? /* @__PURE__ */ React2.createElement(
|
|
3088
|
+
"label",
|
|
3089
|
+
{
|
|
3090
|
+
style: {
|
|
3091
|
+
display: "flex",
|
|
3092
|
+
alignItems: "center",
|
|
3093
|
+
gap: 4,
|
|
3094
|
+
cursor: "pointer",
|
|
3095
|
+
fontSize: 12,
|
|
3096
|
+
fontWeight: 700
|
|
3097
|
+
}
|
|
3098
|
+
},
|
|
3099
|
+
/* @__PURE__ */ React2.createElement(
|
|
3100
|
+
"input",
|
|
3101
|
+
{
|
|
3102
|
+
type: "checkbox",
|
|
3103
|
+
checked: nodesDraggableEnabled,
|
|
3104
|
+
onChange: (e) => setUserNodesDraggable(e.target.checked),
|
|
3105
|
+
style: { width: 14, height: 14 }
|
|
3106
|
+
}
|
|
3107
|
+
),
|
|
3108
|
+
isFa ? "\u062C\u0627\u0628\u0647\u200C\u062C\u0627\u06CC\u06CC" : "Drag"
|
|
3109
|
+
) : null
|
|
3110
|
+
) : null, /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { style: compactSectionLabel }, isFa ? "\u0631\u0646\u06AF \u06CC\u0627\u0644" : "Edge color"), /* @__PURE__ */ React2.createElement(
|
|
3111
|
+
"div",
|
|
3112
|
+
{
|
|
3113
|
+
style: {
|
|
3114
|
+
display: "grid",
|
|
3115
|
+
gridTemplateColumns: "repeat(5, 1fr)",
|
|
3116
|
+
gap: 6,
|
|
3117
|
+
justifyItems: "center",
|
|
3118
|
+
marginBottom: 6
|
|
3119
|
+
}
|
|
3120
|
+
},
|
|
3121
|
+
edgeColorPalette.map((c) => /* @__PURE__ */ React2.createElement(
|
|
3122
|
+
"button",
|
|
3123
|
+
{
|
|
3124
|
+
key: c,
|
|
3125
|
+
type: "button",
|
|
3126
|
+
onClick: () => applyColorToSelectedEdges(c),
|
|
3127
|
+
title: c,
|
|
3128
|
+
style: {
|
|
3129
|
+
width: 28,
|
|
3130
|
+
height: 28,
|
|
3131
|
+
borderRadius: 7,
|
|
3132
|
+
border: "1.5px solid rgba(255,255,255,0.9)",
|
|
3133
|
+
background: c,
|
|
3134
|
+
cursor: "pointer",
|
|
3135
|
+
padding: 0
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
))
|
|
3139
|
+
), /* @__PURE__ */ React2.createElement(
|
|
3140
|
+
"button",
|
|
3141
|
+
{
|
|
3142
|
+
type: "button",
|
|
3143
|
+
onClick: () => applyColorToSelectedEdges(null),
|
|
3144
|
+
style: { ...ghostIconButtonStyle({ width: "100%" }) }
|
|
3145
|
+
},
|
|
3146
|
+
/* @__PURE__ */ React2.createElement(
|
|
3147
|
+
IconButtonContent,
|
|
3148
|
+
{
|
|
3149
|
+
icon: /* @__PURE__ */ React2.createElement(IconEraser, { size: 14 }),
|
|
3150
|
+
label: isFa ? "\u062D\u0630\u0641 \u0631\u0646\u06AF" : "Clear color"
|
|
3151
|
+
}
|
|
3152
|
+
)
|
|
3153
|
+
)), /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { style: compactSectionLabel }, isFa ? "\u0627\u0633\u062A\u0627\u06CC\u0644 \u062E\u0637" : "Line style"), /* @__PURE__ */ React2.createElement(
|
|
3154
|
+
"div",
|
|
3155
|
+
{
|
|
3156
|
+
style: {
|
|
3157
|
+
display: "grid",
|
|
3158
|
+
gridTemplateColumns: "1fr 1fr 1fr",
|
|
3159
|
+
gap: 6
|
|
3160
|
+
}
|
|
3161
|
+
},
|
|
3162
|
+
[
|
|
3163
|
+
{
|
|
3164
|
+
style: EDGE_LINE_STYLE.DASHED,
|
|
3165
|
+
label: isFa ? "\u0686\u06CC\u0646" : "Dash",
|
|
3166
|
+
icon: /* @__PURE__ */ React2.createElement(IconLineDash, { size: 24 })
|
|
3167
|
+
},
|
|
3168
|
+
{
|
|
3169
|
+
style: EDGE_LINE_STYLE.DOTTED,
|
|
3170
|
+
label: isFa ? "\u0646\u0642\u0637\u0647" : "Dot",
|
|
3171
|
+
icon: /* @__PURE__ */ React2.createElement(IconLineDot, { size: 24 })
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
style: null,
|
|
3175
|
+
label: isFa ? "\u0633\u0627\u062F\u0647" : "Solid",
|
|
3176
|
+
icon: /* @__PURE__ */ React2.createElement(IconLineSolid, { size: 24 })
|
|
3177
|
+
}
|
|
3178
|
+
].map((item) => /* @__PURE__ */ React2.createElement(
|
|
3179
|
+
"button",
|
|
3180
|
+
{
|
|
3181
|
+
key: item.label,
|
|
3182
|
+
type: "button",
|
|
3183
|
+
onClick: () => applyLineStyleToSelectedEdges(item.style),
|
|
3184
|
+
title: item.label,
|
|
3185
|
+
style: {
|
|
3186
|
+
...ghostIconButtonStyle({
|
|
3187
|
+
flexDirection: "column",
|
|
3188
|
+
gap: 2,
|
|
3189
|
+
padding: "6px 4px",
|
|
3190
|
+
background: isDarkMode ? "#0f172a" : "#fff"
|
|
3191
|
+
})
|
|
3192
|
+
}
|
|
3193
|
+
},
|
|
3194
|
+
item.icon,
|
|
3195
|
+
/* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, fontWeight: 800 } }, item.label)
|
|
3196
|
+
))
|
|
3197
|
+
)), /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { style: compactSectionLabel }, isFa ? "\u0645\u0633\u06CC\u0631" : "Path"), /* @__PURE__ */ React2.createElement(
|
|
3198
|
+
"div",
|
|
3199
|
+
{
|
|
3200
|
+
style: {
|
|
3201
|
+
display: "grid",
|
|
3202
|
+
gridTemplateColumns: "1fr 1fr",
|
|
3203
|
+
gap: 6,
|
|
3204
|
+
marginBottom: 6
|
|
3205
|
+
}
|
|
3206
|
+
},
|
|
3207
|
+
/* @__PURE__ */ React2.createElement(
|
|
3208
|
+
"input",
|
|
3209
|
+
{
|
|
3210
|
+
dir: "ltr",
|
|
3211
|
+
value: pathFromId,
|
|
3212
|
+
onChange: (e) => setPathFromId(e.target.value),
|
|
3213
|
+
placeholder: isFa ? "\u0627\u0632" : "From",
|
|
3214
|
+
style: compactInputStyle
|
|
3215
|
+
}
|
|
3216
|
+
),
|
|
3217
|
+
/* @__PURE__ */ React2.createElement(
|
|
3218
|
+
"input",
|
|
3219
|
+
{
|
|
3220
|
+
dir: "ltr",
|
|
3221
|
+
value: pathToId,
|
|
3222
|
+
onChange: (e) => setPathToId(e.target.value),
|
|
3223
|
+
placeholder: isFa ? "\u0628\u0647" : "To",
|
|
3224
|
+
style: compactInputStyle
|
|
3225
|
+
}
|
|
3226
|
+
)
|
|
3227
|
+
), /* @__PURE__ */ React2.createElement(
|
|
3228
|
+
"div",
|
|
3229
|
+
{
|
|
3230
|
+
style: {
|
|
3231
|
+
display: "grid",
|
|
3232
|
+
gridTemplateColumns: "1fr 1fr",
|
|
3233
|
+
gap: 6
|
|
3234
|
+
}
|
|
3235
|
+
},
|
|
3236
|
+
/* @__PURE__ */ React2.createElement(
|
|
3237
|
+
"button",
|
|
3238
|
+
{
|
|
3239
|
+
type: "button",
|
|
3240
|
+
onClick: findAndHighlightAllPaths,
|
|
3241
|
+
style: toolbarButtonStyle(
|
|
3242
|
+
isDarkMode ? "linear-gradient(135deg,#7c3aed,#6d28d9)" : "linear-gradient(135deg,#6d28d9,#5b21b6)",
|
|
3243
|
+
{ width: "100%" }
|
|
3244
|
+
)
|
|
3245
|
+
},
|
|
3246
|
+
/* @__PURE__ */ React2.createElement(
|
|
3247
|
+
IconButtonContent,
|
|
3248
|
+
{
|
|
3249
|
+
icon: /* @__PURE__ */ React2.createElement(IconRoute, { size: 14 }),
|
|
3250
|
+
label: isFa ? "\u0645\u0633\u06CC\u0631" : "Find"
|
|
3251
|
+
}
|
|
3252
|
+
)
|
|
3253
|
+
),
|
|
3254
|
+
/* @__PURE__ */ React2.createElement(
|
|
3255
|
+
"button",
|
|
3256
|
+
{
|
|
3257
|
+
type: "button",
|
|
3258
|
+
onClick: () => {
|
|
3259
|
+
const instance = networkInstanceRef.current;
|
|
3260
|
+
if (!instance) return;
|
|
3261
|
+
instance.unselectAll();
|
|
3262
|
+
selectedNodeIdsRef.current = [];
|
|
3263
|
+
selectedEdgeIdsRef.current = [];
|
|
3264
|
+
setSelectedNodes([]);
|
|
3265
|
+
SetSelectedEdges([]);
|
|
3266
|
+
paintSelectionStyles();
|
|
3267
|
+
setPathMessage("");
|
|
3268
|
+
},
|
|
3269
|
+
style: { ...ghostIconButtonStyle({ width: "100%" }) }
|
|
3270
|
+
},
|
|
3271
|
+
/* @__PURE__ */ React2.createElement(
|
|
3272
|
+
IconButtonContent,
|
|
3273
|
+
{
|
|
3274
|
+
icon: /* @__PURE__ */ React2.createElement(IconEraser, { size: 14 }),
|
|
3275
|
+
label: isFa ? "\u067E\u0627\u06A9" : "Clear"
|
|
3276
|
+
}
|
|
3277
|
+
)
|
|
3278
|
+
)
|
|
3279
|
+
), pathMessage ? /* @__PURE__ */ React2.createElement(
|
|
3280
|
+
"div",
|
|
3281
|
+
{
|
|
3282
|
+
style: {
|
|
3283
|
+
marginTop: 4,
|
|
3284
|
+
fontSize: 11,
|
|
3285
|
+
fontWeight: 700,
|
|
3286
|
+
color: isDarkMode ? "#93c5fd" : "#1d4ed8",
|
|
3287
|
+
lineHeight: 1.35
|
|
3288
|
+
}
|
|
3289
|
+
},
|
|
3290
|
+
pathMessage
|
|
3291
|
+
) : null)))),
|
|
3292
|
+
showHelpButton ? /* @__PURE__ */ React2.createElement(
|
|
3293
|
+
"button",
|
|
3294
|
+
{
|
|
3295
|
+
type: "button",
|
|
3296
|
+
onClick: () => setShowGuide(true),
|
|
3297
|
+
title: isFa ? "\u0631\u0627\u0647\u0646\u0645\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647" : "Usage guide",
|
|
3298
|
+
"aria-label": isFa ? "\u0631\u0627\u0647\u0646\u0645\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647" : "Usage guide",
|
|
3299
|
+
style: {
|
|
3300
|
+
position: "absolute",
|
|
3301
|
+
bottom: 12,
|
|
3302
|
+
right: 12,
|
|
3303
|
+
zIndex: 99995,
|
|
3304
|
+
width: 40,
|
|
3305
|
+
height: 40,
|
|
3306
|
+
borderRadius: 999,
|
|
3307
|
+
border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.35)" : "rgba(15,23,42,0.12)"}`,
|
|
3308
|
+
background: isDarkMode ? "linear-gradient(135deg, rgba(15,23,42,0.95), rgba(30,41,59,0.9))" : "linear-gradient(135deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))",
|
|
3309
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
3310
|
+
fontSize: 18,
|
|
3311
|
+
fontWeight: 900,
|
|
3312
|
+
cursor: "pointer",
|
|
3313
|
+
boxShadow: isDarkMode ? "0 8px 24px rgba(2,6,23,.45)" : "0 8px 20px rgba(15,23,42,.14)"
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
3316
|
+
"?"
|
|
3317
|
+
) : null,
|
|
3318
|
+
showGuide ? /* @__PURE__ */ React2.createElement(
|
|
3319
|
+
"div",
|
|
3320
|
+
{
|
|
3321
|
+
role: "dialog",
|
|
3322
|
+
"aria-modal": "true",
|
|
3323
|
+
"aria-label": guideContent.title,
|
|
3324
|
+
onClick: () => setShowGuide(false),
|
|
3325
|
+
style: {
|
|
3326
|
+
position: "absolute",
|
|
3327
|
+
inset: 0,
|
|
3328
|
+
zIndex: 100001,
|
|
3329
|
+
background: "rgba(2,6,23,0.45)",
|
|
3330
|
+
display: "grid",
|
|
3331
|
+
placeItems: "center",
|
|
3332
|
+
padding: 16
|
|
3333
|
+
}
|
|
3334
|
+
},
|
|
3335
|
+
/* @__PURE__ */ React2.createElement(
|
|
3336
|
+
"div",
|
|
3337
|
+
{
|
|
3338
|
+
dir: isFa ? "rtl" : "ltr",
|
|
3339
|
+
onClick: (e) => e.stopPropagation(),
|
|
3340
|
+
style: {
|
|
3341
|
+
width: "min(520px, 100%)",
|
|
3342
|
+
maxHeight: "min(78vh, 720px)",
|
|
3343
|
+
overflow: "auto",
|
|
3344
|
+
borderRadius: 16,
|
|
3345
|
+
padding: 16,
|
|
3346
|
+
background: isDarkMode ? "linear-gradient(135deg, rgba(15,23,42,0.98), rgba(30,41,59,0.96))" : "linear-gradient(135deg, rgba(255,255,255,0.98), rgba(248,250,252,0.96))",
|
|
3347
|
+
border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.25)" : "rgba(15,23,42,0.10)"}`,
|
|
3348
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
3349
|
+
boxShadow: "0 18px 48px rgba(2,6,23,.35)"
|
|
3350
|
+
}
|
|
3351
|
+
},
|
|
3352
|
+
/* @__PURE__ */ React2.createElement(
|
|
3353
|
+
"div",
|
|
3354
|
+
{
|
|
3355
|
+
style: {
|
|
3356
|
+
display: "flex",
|
|
3357
|
+
alignItems: "center",
|
|
3358
|
+
justifyContent: "space-between",
|
|
3359
|
+
gap: 12,
|
|
3360
|
+
marginBottom: 12
|
|
3361
|
+
}
|
|
3362
|
+
},
|
|
3363
|
+
/* @__PURE__ */ React2.createElement("h3", { style: { margin: 0, fontSize: 16, fontWeight: 900 } }, guideContent.title),
|
|
3364
|
+
/* @__PURE__ */ React2.createElement(
|
|
3365
|
+
"button",
|
|
3366
|
+
{
|
|
3367
|
+
type: "button",
|
|
3368
|
+
onClick: () => setShowGuide(false),
|
|
3369
|
+
style: {
|
|
3370
|
+
border: `1px solid ${isDarkMode ? "#475569" : "#cbd5e1"}`,
|
|
3371
|
+
borderRadius: 10,
|
|
3372
|
+
padding: "6px 10px",
|
|
3373
|
+
cursor: "pointer",
|
|
3374
|
+
fontWeight: 700,
|
|
3375
|
+
fontSize: 12,
|
|
3376
|
+
background: isDarkMode ? "#0f172a" : "#fff",
|
|
3377
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a"
|
|
3378
|
+
}
|
|
3379
|
+
},
|
|
3380
|
+
guideContent.close
|
|
3381
|
+
)
|
|
3382
|
+
),
|
|
3383
|
+
guideContent.sections.map((section) => /* @__PURE__ */ React2.createElement("div", { key: section.title, style: { marginBottom: 14 } }, /* @__PURE__ */ React2.createElement(
|
|
3384
|
+
"div",
|
|
3385
|
+
{
|
|
3386
|
+
style: {
|
|
3387
|
+
fontSize: 13,
|
|
3388
|
+
fontWeight: 800,
|
|
3389
|
+
marginBottom: 6,
|
|
3390
|
+
color: isDarkMode ? "#93c5fd" : "#1d4ed8"
|
|
3391
|
+
}
|
|
3392
|
+
},
|
|
3393
|
+
section.title
|
|
3394
|
+
), /* @__PURE__ */ React2.createElement(
|
|
3395
|
+
"ul",
|
|
3396
|
+
{
|
|
3397
|
+
style: {
|
|
3398
|
+
margin: 0,
|
|
3399
|
+
paddingInlineStart: 18,
|
|
3400
|
+
fontSize: 12,
|
|
3401
|
+
lineHeight: 1.65
|
|
3402
|
+
}
|
|
3403
|
+
},
|
|
3404
|
+
section.items.map((item) => /* @__PURE__ */ React2.createElement("li", { key: item, style: { marginBottom: 4 } }, item))
|
|
3405
|
+
)))
|
|
3406
|
+
)
|
|
3407
|
+
) : null,
|
|
3408
|
+
/* @__PURE__ */ React2.createElement(
|
|
3409
|
+
"div",
|
|
3410
|
+
{
|
|
3411
|
+
style: {
|
|
3412
|
+
position: "absolute",
|
|
3413
|
+
left: "50%",
|
|
3414
|
+
bottom: 8,
|
|
3415
|
+
transform: "translateX(-50%)",
|
|
3416
|
+
zIndex: 99990,
|
|
3417
|
+
pointerEvents: "none",
|
|
3418
|
+
fontSize: 11,
|
|
3419
|
+
fontWeight: 700,
|
|
3420
|
+
letterSpacing: 0.4,
|
|
3421
|
+
color: isDarkMode ? "rgba(148,163,184,.95)" : "rgba(15,23,42,.75)",
|
|
3422
|
+
background: isDarkMode ? "rgba(2,6,23,.45)" : "rgba(255,255,255,.72)",
|
|
3423
|
+
border: `1px solid ${isDarkMode ? "rgba(71,85,105,.45)" : "rgba(148,163,184,.45)"}`,
|
|
3424
|
+
borderRadius: 999,
|
|
3425
|
+
padding: "4px 10px",
|
|
3426
|
+
backdropFilter: "blur(4px)"
|
|
3427
|
+
}
|
|
3428
|
+
},
|
|
3429
|
+
FOOTER_TEXT
|
|
3430
|
+
),
|
|
3431
|
+
hoverInfo.visible && hoverInfo.node && /* @__PURE__ */ React2.createElement(
|
|
3432
|
+
"div",
|
|
3433
|
+
{
|
|
3434
|
+
dir: isFa ? "rtl" : "ltr",
|
|
3435
|
+
style: {
|
|
3436
|
+
position: "absolute",
|
|
3437
|
+
left: hoverInfo.x,
|
|
3438
|
+
top: hoverInfo.y,
|
|
3439
|
+
transform: "translate(-50%, -100%)",
|
|
3440
|
+
zIndex: 1e5,
|
|
3441
|
+
pointerEvents: "none",
|
|
3442
|
+
maxWidth: 360,
|
|
3443
|
+
background: isDarkMode ? "rgba(2,6,23,0.96)" : "rgba(255,255,255,0.98)",
|
|
3444
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
3445
|
+
border: `1px solid ${isDarkMode ? "#334155" : "#cbd5e1"}`,
|
|
3446
|
+
borderRadius: 10,
|
|
3447
|
+
padding: 10,
|
|
3448
|
+
boxShadow: "0 8px 24px rgba(0,0,0,.22)",
|
|
3449
|
+
fontSize: 12,
|
|
3450
|
+
lineHeight: 1.5,
|
|
3451
|
+
whiteSpace: "pre-wrap",
|
|
3452
|
+
wordBreak: "break-word"
|
|
3453
|
+
}
|
|
3454
|
+
},
|
|
3455
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0634\u0646\u0627\u0633\u0647" : "ID", ":"), " ", S2(hoverInfo.node.id)),
|
|
3456
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0628\u0631\u0686\u0633\u0628" : "Label", ":"), " ", S2(hoverInfo.node.label || "-")),
|
|
3457
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0646\u0648\u0639" : "Type", ":"), " ", S2(hoverInfo.node.type || hoverInfo.node.group || "-")),
|
|
3458
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0622\u062F\u0631\u0633" : "Address", ":"), " ", S2(hoverInfo.node.address || hoverInfo.node.text || "-")),
|
|
3459
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0645\u062A\u0627\u062F\u06CC\u062A\u0627" : "Metadata", ":"), " ", S2(hoverInfo.node.metadata || "-")),
|
|
3460
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0631\u06CC\u062A" : "Rate", ":"), " ", S2(hoverInfo.node.rate ?? "-")),
|
|
3461
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0632\u06CC\u0631\u0645\u062A\u0646" : "Sub Text", ":"), " ", S2(hoverInfo.node.subText || "-")),
|
|
3462
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0646\u0647\u0627\u062F" : "Entity", ":"), " ", S2(((_a = hoverInfo.node.entity) == null ? void 0 : _a.name) || "-")),
|
|
3463
|
+
/* @__PURE__ */ React2.createElement("details", null, /* @__PURE__ */ React2.createElement("summary", { style: { cursor: "default" } }, isFa ? "\u062F\u0627\u062F\u0647 \u062E\u0627\u0645" : "raw"), /* @__PURE__ */ React2.createElement("pre", { style: { margin: 0, fontSize: 11 } }, JSON.stringify(hoverInfo.node, null, 2)))
|
|
3464
|
+
),
|
|
3465
|
+
showEdgeHoverInfo && edgeHoverInfo.visible ? /* @__PURE__ */ React2.createElement(
|
|
3466
|
+
"div",
|
|
3467
|
+
{
|
|
3468
|
+
dir: isFa ? "rtl" : "ltr",
|
|
3469
|
+
style: {
|
|
3470
|
+
position: "absolute",
|
|
3471
|
+
left: edgeHoverInfo.x,
|
|
3472
|
+
top: edgeHoverInfo.y,
|
|
3473
|
+
transform: "translate(-50%, -100%)",
|
|
3474
|
+
zIndex: 1e5,
|
|
3475
|
+
pointerEvents: "none",
|
|
3476
|
+
maxWidth: 360,
|
|
3477
|
+
background: isDarkMode ? "rgba(2,6,23,0.96)" : "rgba(255,255,255,0.98)",
|
|
3478
|
+
color: isDarkMode ? "#e2e8f0" : "#0f172a",
|
|
3479
|
+
border: `1px solid ${isDarkMode ? "#334155" : "#cbd5e1"}`,
|
|
3480
|
+
borderRadius: 10,
|
|
3481
|
+
padding: 10,
|
|
3482
|
+
boxShadow: "0 8px 24px rgba(0,0,0,.22)",
|
|
3483
|
+
fontSize: 12,
|
|
3484
|
+
lineHeight: 1.5,
|
|
3485
|
+
whiteSpace: "pre-wrap",
|
|
3486
|
+
wordBreak: "break-word"
|
|
3487
|
+
}
|
|
3488
|
+
},
|
|
3489
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0627\u0632" : "From", ":"), " ", getNodeLabelById(edgeHoverInfo.fromId)),
|
|
3490
|
+
/* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0628\u0647" : "To", ":"), " ", getNodeLabelById(edgeHoverInfo.toId)),
|
|
3491
|
+
edgeHoverInfo.summary ? /* @__PURE__ */ React2.createElement("div", { style: { marginTop: 6 } }, edgeHoverInfo.summary) : /* @__PURE__ */ React2.createElement("div", { style: { marginTop: 6, opacity: 0.75 } }, isFa ? "\u0627\u0637\u0644\u0627\u0639\u0627\u062A \u06CC\u0627\u0644 \u0645\u0648\u062C\u0648\u062F \u0646\u06CC\u0633\u062A" : "No edge data available")
|
|
3492
|
+
) : null
|
|
3493
|
+
);
|
|
3494
|
+
};
|
|
3495
|
+
var Graph_default = Nodalix_graph_engine;
|
|
3496
|
+
|
|
3497
|
+
// src/index.js
|
|
3498
|
+
var __NODALIX_STORE__ = {
|
|
3499
|
+
data: [],
|
|
3500
|
+
setData: null,
|
|
3501
|
+
// توسط Nodalix هنگام mount مقداردهی میشود
|
|
3502
|
+
node: null,
|
|
3503
|
+
// نود انتخاب شده
|
|
3504
|
+
setNode: null,
|
|
3505
|
+
// setter نود انتخاب شده
|
|
3506
|
+
onNodeClick: null
|
|
3507
|
+
// callback کلیک روی نود
|
|
3508
|
+
};
|
|
3509
|
+
var nodeClickHandlerRef = { current: null };
|
|
3510
|
+
var scheduleNodalixUpdate = (fn) => {
|
|
3511
|
+
if (typeof queueMicrotask === "function") {
|
|
3512
|
+
queueMicrotask(fn);
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
Promise.resolve().then(fn);
|
|
3516
|
+
};
|
|
3517
|
+
function GetSelectedNode() {
|
|
3518
|
+
return __NODALIX_STORE__.node ?? null;
|
|
3519
|
+
}
|
|
3520
|
+
function SetSelectedNode(node) {
|
|
3521
|
+
if (typeof __NODALIX_STORE__.setNode !== "function") {
|
|
3522
|
+
return { success: false, error: "Nodalix instance is not mounted yet." };
|
|
3523
|
+
}
|
|
3524
|
+
const next = node ?? null;
|
|
3525
|
+
__NODALIX_STORE__.node = next;
|
|
3526
|
+
__NODALIX_STORE__.setNode(next);
|
|
3527
|
+
return { success: true, node: next };
|
|
3528
|
+
}
|
|
3529
|
+
function OnNodeClick(handler) {
|
|
3530
|
+
if (handler != null && typeof handler !== "function") {
|
|
3531
|
+
return { success: false, error: "handler must be a function or null." };
|
|
3532
|
+
}
|
|
3533
|
+
nodeClickHandlerRef.current = handler ?? null;
|
|
3534
|
+
__NODALIX_STORE__.onNodeClick = nodeClickHandlerRef.current;
|
|
3535
|
+
return { success: true };
|
|
3536
|
+
}
|
|
3537
|
+
function getSafeNodePosition(params = {}) {
|
|
3538
|
+
const {
|
|
3539
|
+
node = {},
|
|
3540
|
+
nodes = [],
|
|
3541
|
+
inputAnchorIds = [],
|
|
3542
|
+
outputAnchorIds = [],
|
|
3543
|
+
minGapY = 100,
|
|
3544
|
+
yStep = 100
|
|
3545
|
+
} = params;
|
|
3546
|
+
const minGapX = params.minGapX ?? params.minGap ?? 300;
|
|
3547
|
+
const positionedNodes = nodes.filter(
|
|
3548
|
+
(n) => typeof (n == null ? void 0 : n.x) === "number" && typeof (n == null ? void 0 : n.y) === "number"
|
|
3549
|
+
);
|
|
3550
|
+
const baseWidth = typeof node.width === "number" ? node.width : 220;
|
|
3551
|
+
const inputCount = Array.isArray(node.inputs) ? node.inputs.length : 0;
|
|
3552
|
+
const outputCount = Array.isArray(node.outputs) ? node.outputs.length : 0;
|
|
3553
|
+
const ioBoost = Math.min(inputCount + outputCount, 6) * 28;
|
|
3554
|
+
const computedWidth = baseWidth + ioBoost;
|
|
3555
|
+
const avg = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
3556
|
+
const isPlacementSafe = (x2, y2) => positionedNodes.every(
|
|
3557
|
+
(n) => Math.abs(n.x - x2) >= minGapX || Math.abs(n.y - y2) >= minGapY
|
|
3558
|
+
);
|
|
3559
|
+
if (!positionedNodes.length) {
|
|
3560
|
+
return {
|
|
3561
|
+
x: typeof node.x === "number" ? node.x : 0,
|
|
3562
|
+
y: typeof node.y === "number" ? node.y : 0,
|
|
3563
|
+
width: computedWidth
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
const resolveAnchors = (ids) => ids.map((id) => positionedNodes.find((n) => n.id === id)).filter(Boolean);
|
|
3567
|
+
const inputAnchors = resolveAnchors(inputAnchorIds);
|
|
3568
|
+
const outputAnchors = resolveAnchors(outputAnchorIds);
|
|
3569
|
+
const connectedById = /* @__PURE__ */ new Map();
|
|
3570
|
+
for (const anchor of [...inputAnchors, ...outputAnchors]) {
|
|
3571
|
+
connectedById.set(anchor.id, anchor);
|
|
3572
|
+
}
|
|
3573
|
+
const connected = Array.from(connectedById.values());
|
|
3574
|
+
const hasInput = inputAnchors.length > 0;
|
|
3575
|
+
const hasOutput = outputAnchors.length > 0;
|
|
3576
|
+
let x;
|
|
3577
|
+
let baseY;
|
|
3578
|
+
if (hasInput && hasOutput) {
|
|
3579
|
+
x = avg(connected.map((n) => n.x));
|
|
3580
|
+
baseY = avg(connected.map((n) => n.y));
|
|
3581
|
+
} else if (hasInput) {
|
|
3582
|
+
x = avg(inputAnchors.map((n) => n.x)) + minGapX;
|
|
3583
|
+
baseY = avg(inputAnchors.map((n) => n.y));
|
|
3584
|
+
} else if (hasOutput) {
|
|
3585
|
+
x = avg(outputAnchors.map((n) => n.x)) - minGapX;
|
|
3586
|
+
baseY = avg(outputAnchors.map((n) => n.y));
|
|
3587
|
+
} else {
|
|
3588
|
+
x = typeof node.x === "number" ? node.x : avg(positionedNodes.map((n) => n.x));
|
|
3589
|
+
baseY = typeof node.y === "number" ? node.y : avg(positionedNodes.map((n) => n.y));
|
|
3590
|
+
}
|
|
3591
|
+
let y = baseY;
|
|
3592
|
+
const maxSteps = 200;
|
|
3593
|
+
for (let step = 0; step <= maxSteps && !isPlacementSafe(x, y); step++) {
|
|
3594
|
+
y = baseY - step * yStep;
|
|
3595
|
+
}
|
|
3596
|
+
return { x, y, width: computedWidth };
|
|
3597
|
+
}
|
|
3598
|
+
var applyNodeLimit = (nodes) => enforceNodeLimit(nodes, NODALIX_MAX_NODES);
|
|
3599
|
+
function AddNewNode(rawNode, options = {}) {
|
|
3600
|
+
const minGapX = options.minGapX ?? options.minGap ?? 300;
|
|
3601
|
+
const minGapY = options.minGapY ?? 100;
|
|
3602
|
+
const yStep = options.yStep ?? 100;
|
|
3603
|
+
if (typeof __NODALIX_STORE__.setData !== "function") {
|
|
3604
|
+
return { success: false, error: "Nodalix instance is not mounted yet." };
|
|
3605
|
+
}
|
|
3606
|
+
if (!rawNode || typeof rawNode !== "object") {
|
|
3607
|
+
return { success: false, error: "Invalid node object." };
|
|
3608
|
+
}
|
|
3609
|
+
if (!rawNode.id || typeof rawNode.id !== "string" || !rawNode.id.trim()) {
|
|
3610
|
+
return {
|
|
3611
|
+
success: false,
|
|
3612
|
+
error: "Node id is required and must be a non-empty string."
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
const current = Array.isArray(__NODALIX_STORE__.data) ? __NODALIX_STORE__.data : [];
|
|
3616
|
+
if (!canAddGraphNode(current, NODALIX_MAX_NODES)) {
|
|
3617
|
+
return { success: false, error: getNodeLimitError(NODALIX_MAX_NODES) };
|
|
3618
|
+
}
|
|
3619
|
+
if (current.some((n) => n.id === rawNode.id)) {
|
|
3620
|
+
return { success: false, error: "Duplicate node id." };
|
|
3621
|
+
}
|
|
3622
|
+
const inEdges = Array.isArray(rawNode.inputs) ? rawNode.inputs.filter(Boolean) : [];
|
|
3623
|
+
const outEdges = Array.isArray(rawNode.outputs) ? rawNode.outputs.filter(Boolean) : [];
|
|
3624
|
+
const inputAnchorIds = inEdges.map((e) => e == null ? void 0 : e.id).filter((id) => id && current.some((n) => n.id === id));
|
|
3625
|
+
const outputAnchorIds = outEdges.map((e) => e == null ? void 0 : e.id).filter((id) => id && current.some((n) => n.id === id));
|
|
3626
|
+
const { x, y } = getSafeNodePosition({
|
|
3627
|
+
node: rawNode,
|
|
3628
|
+
nodes: current,
|
|
3629
|
+
inputAnchorIds,
|
|
3630
|
+
outputAnchorIds,
|
|
3631
|
+
minGapX,
|
|
3632
|
+
minGapY,
|
|
3633
|
+
yStep
|
|
3634
|
+
});
|
|
3635
|
+
const newNode = {
|
|
3636
|
+
...rawNode,
|
|
3637
|
+
x,
|
|
3638
|
+
y,
|
|
3639
|
+
inputs: inEdges,
|
|
3640
|
+
outputs: outEdges
|
|
3641
|
+
};
|
|
3642
|
+
let updated = current.map((n) => ({ ...n }));
|
|
3643
|
+
const indexById = new Map(updated.map((n, i) => [n.id, i]));
|
|
3644
|
+
for (const e of newNode.inputs) {
|
|
3645
|
+
const srcId = e == null ? void 0 : e.id;
|
|
3646
|
+
if (!srcId || !indexById.has(srcId)) continue;
|
|
3647
|
+
const srcNode = updated[indexById.get(srcId)];
|
|
3648
|
+
const exists = Array.isArray(srcNode.outputs) ? srcNode.outputs.some((o) => (o == null ? void 0 : o.id) === newNode.id) : false;
|
|
3649
|
+
if (!exists) {
|
|
3650
|
+
const mirrored = mirrorEdgeRef(e, {
|
|
3651
|
+
id: newNode.id,
|
|
3652
|
+
time: (e == null ? void 0 : e.time) ?? Math.floor(Date.now() / 1e3)
|
|
3653
|
+
});
|
|
3654
|
+
srcNode.outputs = Array.isArray(srcNode.outputs) ? [...srcNode.outputs, mirrored] : [mirrored];
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
for (const e of newNode.outputs) {
|
|
3658
|
+
const tgtId = e == null ? void 0 : e.id;
|
|
3659
|
+
if (!tgtId || !indexById.has(tgtId)) continue;
|
|
3660
|
+
const tgtNode = updated[indexById.get(tgtId)];
|
|
3661
|
+
const exists = Array.isArray(tgtNode.inputs) ? tgtNode.inputs.some((i) => (i == null ? void 0 : i.id) === newNode.id) : false;
|
|
3662
|
+
if (!exists) {
|
|
3663
|
+
const mirrored = mirrorEdgeRef(e, {
|
|
3664
|
+
id: newNode.id,
|
|
3665
|
+
time: (e == null ? void 0 : e.time) ?? Math.floor(Date.now() / 1e3)
|
|
3666
|
+
});
|
|
3667
|
+
tgtNode.inputs = Array.isArray(tgtNode.inputs) ? [...tgtNode.inputs, mirrored] : [mirrored];
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
updated = [...updated, newNode];
|
|
3671
|
+
updated = applyNodeLimit(updated);
|
|
3672
|
+
const violates = updated.some(
|
|
3673
|
+
(n) => n.id !== newNode.id && Math.abs(n.x - newNode.x) < minGapX && Math.abs(n.y - newNode.y) < minGapY
|
|
3674
|
+
);
|
|
3675
|
+
if (violates) {
|
|
3676
|
+
return { success: false, error: "Failed to find a safe position for the new node." };
|
|
3677
|
+
}
|
|
3678
|
+
__NODALIX_STORE__.data = updated;
|
|
3679
|
+
__NODALIX_STORE__.setData(updated);
|
|
3680
|
+
return { success: true, node: newNode, data: updated, length: updated.length };
|
|
3681
|
+
}
|
|
3682
|
+
function Graph_Engine({
|
|
3683
|
+
NewData,
|
|
3684
|
+
onNodeClick,
|
|
3685
|
+
edgeLabelFields = DEFAULT_EDGE_LABEL_FIELDS,
|
|
3686
|
+
rateColorRanges = DEFAULT_RATE_COLOR_RANGES,
|
|
3687
|
+
showEdgeHoverInfo = false,
|
|
3688
|
+
showHelpButton = true,
|
|
3689
|
+
gridUserConfigurable = true,
|
|
3690
|
+
showGrid = true,
|
|
3691
|
+
nodesDraggableUserConfigurable = true,
|
|
3692
|
+
nodesDraggable = true,
|
|
3693
|
+
edgeInfoNodesUserConfigurable = true,
|
|
3694
|
+
showEdgeInfoNodes = true,
|
|
3695
|
+
languageUserConfigurable = true,
|
|
3696
|
+
language = "fa"
|
|
3697
|
+
}) {
|
|
3698
|
+
const resolvedEdgeLabelFields = useMemo2(
|
|
3699
|
+
() => normalizeEdgeLabelFields(edgeLabelFields),
|
|
3700
|
+
[edgeLabelFields]
|
|
3701
|
+
);
|
|
3702
|
+
const resolvedRateColorRanges = useMemo2(
|
|
3703
|
+
() => normalizeRateColorRanges(rateColorRanges),
|
|
3704
|
+
[rateColorRanges]
|
|
3705
|
+
);
|
|
3706
|
+
const [data, setData] = useState2([]);
|
|
3707
|
+
const [reload, setReload] = useState2(false);
|
|
3708
|
+
const [nodesPosition, setNodesPosition] = useState2([]);
|
|
3709
|
+
const [scale, setScale] = useState2(1);
|
|
3710
|
+
const [xPosition, setXPosition] = useState2(0);
|
|
3711
|
+
const [yPosition, setYPosition] = useState2(0);
|
|
3712
|
+
const [selectedEdges, setSelectedEdges] = useState2([]);
|
|
3713
|
+
const [selectedNodes, setSelectedNodes] = useState2([]);
|
|
3714
|
+
const [Node, SetNode] = useState2(null);
|
|
3715
|
+
const [PaintedEdges, SetPaintedEdges] = useState2([]);
|
|
3716
|
+
useEffect2(() => {
|
|
3717
|
+
setData(applyNodeLimit(Array.isArray(NewData) ? NewData : []));
|
|
3718
|
+
}, [NewData]);
|
|
3719
|
+
useEffect2(() => {
|
|
3720
|
+
__NODALIX_STORE__.setData = (updater) => {
|
|
3721
|
+
scheduleNodalixUpdate(() => {
|
|
3722
|
+
if (typeof updater === "function") {
|
|
3723
|
+
setData((prev) => {
|
|
3724
|
+
const next = applyNodeLimit(updater(prev));
|
|
3725
|
+
__NODALIX_STORE__.data = next;
|
|
3726
|
+
return next;
|
|
3727
|
+
});
|
|
3728
|
+
} else {
|
|
3729
|
+
const next = applyNodeLimit(updater);
|
|
3730
|
+
setData(next);
|
|
3731
|
+
__NODALIX_STORE__.data = next;
|
|
3732
|
+
}
|
|
3733
|
+
});
|
|
3734
|
+
};
|
|
3735
|
+
return () => {
|
|
3736
|
+
if (__NODALIX_STORE__.setData) __NODALIX_STORE__.setData = null;
|
|
3737
|
+
};
|
|
3738
|
+
}, []);
|
|
3739
|
+
useEffect2(() => {
|
|
3740
|
+
__NODALIX_STORE__.data = data;
|
|
3741
|
+
}, [data]);
|
|
3742
|
+
useEffect2(() => {
|
|
3743
|
+
__NODALIX_STORE__.setNode = (updater) => {
|
|
3744
|
+
scheduleNodalixUpdate(() => {
|
|
3745
|
+
if (typeof updater === "function") {
|
|
3746
|
+
SetNode((prev) => {
|
|
3747
|
+
const next = updater(prev);
|
|
3748
|
+
__NODALIX_STORE__.node = next ?? null;
|
|
3749
|
+
return next ?? null;
|
|
3750
|
+
});
|
|
3751
|
+
} else {
|
|
3752
|
+
SetNode(updater ?? null);
|
|
3753
|
+
__NODALIX_STORE__.node = updater ?? null;
|
|
3754
|
+
}
|
|
3755
|
+
});
|
|
3756
|
+
};
|
|
3757
|
+
return () => {
|
|
3758
|
+
if (__NODALIX_STORE__.setNode) __NODALIX_STORE__.setNode = null;
|
|
3759
|
+
};
|
|
3760
|
+
}, []);
|
|
3761
|
+
useEffect2(() => {
|
|
3762
|
+
__NODALIX_STORE__.node = Node ?? null;
|
|
3763
|
+
}, [Node]);
|
|
3764
|
+
useEffect2(() => {
|
|
3765
|
+
if (typeof onNodeClick === "function") {
|
|
3766
|
+
OnNodeClick(onNodeClick);
|
|
3767
|
+
}
|
|
3768
|
+
}, [onNodeClick]);
|
|
3769
|
+
return /* @__PURE__ */ React3.createElement("div", { style: { direction: "ltr", width: "100%", height: "100%" } }, /* @__PURE__ */ React3.createElement(
|
|
3770
|
+
Graph_default,
|
|
3771
|
+
{
|
|
3772
|
+
Data: data,
|
|
3773
|
+
SetData: setData,
|
|
3774
|
+
Reload: reload,
|
|
3775
|
+
SetReload: setReload,
|
|
3776
|
+
NodesPosition: nodesPosition,
|
|
3777
|
+
SetNodesPosition: setNodesPosition,
|
|
3778
|
+
Scale: scale,
|
|
3779
|
+
SetScale: setScale,
|
|
3780
|
+
XPosition: xPosition,
|
|
3781
|
+
SetXPosition: setXPosition,
|
|
3782
|
+
YPosition: yPosition,
|
|
3783
|
+
SetYPosition: setYPosition,
|
|
3784
|
+
edgeLabelFields: resolvedEdgeLabelFields,
|
|
3785
|
+
rateColorRanges: resolvedRateColorRanges,
|
|
3786
|
+
showEdgeHoverInfo: !!showEdgeHoverInfo,
|
|
3787
|
+
showHelpButton: !!showHelpButton,
|
|
3788
|
+
gridUserConfigurable: !!gridUserConfigurable,
|
|
3789
|
+
showGrid: !!showGrid,
|
|
3790
|
+
nodesDraggableUserConfigurable: !!nodesDraggableUserConfigurable,
|
|
3791
|
+
nodesDraggable: !!nodesDraggable,
|
|
3792
|
+
edgeInfoNodesUserConfigurable: !!edgeInfoNodesUserConfigurable,
|
|
3793
|
+
showEdgeInfoNodes: !!showEdgeInfoNodes,
|
|
3794
|
+
languageUserConfigurable: !!languageUserConfigurable,
|
|
3795
|
+
language,
|
|
3796
|
+
TakeSceenShot: true,
|
|
3797
|
+
SetSelectedEdges: setSelectedEdges,
|
|
3798
|
+
setSelectedNodes,
|
|
3799
|
+
PaintedEdges,
|
|
3800
|
+
SetPaintedEdges,
|
|
3801
|
+
SetNode,
|
|
3802
|
+
onNodeClickRef: nodeClickHandlerRef
|
|
3803
|
+
}
|
|
3804
|
+
));
|
|
3805
|
+
}
|
|
3806
|
+
export {
|
|
3807
|
+
AddNewNode,
|
|
3808
|
+
DEFAULT_EDGE_LABEL_FIELDS,
|
|
3809
|
+
DEFAULT_NODE_IMAGE,
|
|
3810
|
+
DEFAULT_RATE_COLOR_RANGES,
|
|
3811
|
+
EDGE_LABEL_FIELDS,
|
|
3812
|
+
EDGE_LINE_STYLE,
|
|
3813
|
+
GRAPH_LANGUAGE,
|
|
3814
|
+
GetSelectedNode,
|
|
3815
|
+
Graph_Engine,
|
|
3816
|
+
NODALIX_EDITION,
|
|
3817
|
+
NODALIX_MAX_NODES,
|
|
3818
|
+
NODE_TYPE,
|
|
3819
|
+
OnNodeClick,
|
|
3820
|
+
SetSelectedNode,
|
|
3821
|
+
buildEdgeInfoLabel,
|
|
3822
|
+
canAddGraphNode,
|
|
3823
|
+
countGraphNodes,
|
|
3824
|
+
enforceNodeLimit,
|
|
3825
|
+
getGraphGuide,
|
|
3826
|
+
getNodeDisplayLabel,
|
|
3827
|
+
getNodeEntityImage,
|
|
3828
|
+
getRateBorderColor,
|
|
3829
|
+
getSafeNodePosition,
|
|
3830
|
+
hasNodeLimit,
|
|
3831
|
+
isBasicEdition,
|
|
3832
|
+
isFullEdition,
|
|
3833
|
+
isHelperNodeId,
|
|
3834
|
+
mirrorEdgeRef,
|
|
3835
|
+
normalizeEdgeLabelFields,
|
|
3836
|
+
normalizeEdgeLineStyle,
|
|
3837
|
+
normalizeGraphLanguage,
|
|
3838
|
+
normalizeRateColorRanges,
|
|
3839
|
+
resolveEdgeVisStyle
|
|
3840
|
+
};
|