@cyber-harbour/ui 1.0.68 → 1.0.70
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/dist/index.d.mts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +143 -143
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +132 -132
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Graph2D/Graph2D.tsx +76 -25
- package/src/Graph2D/types.ts +2 -4
package/package.json
CHANGED
package/src/Graph2D/Graph2D.tsx
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from 'd3';
|
|
28
28
|
import { styled, useTheme } from 'styled-components';
|
|
29
29
|
import GraphLoader from './GraphLoader';
|
|
30
|
+
import { hexToRgba } from '../Theme';
|
|
30
31
|
|
|
31
32
|
const RATIO = window.devicePixelRatio || 1;
|
|
32
33
|
// Завантаження та підготовка зображень кнопок
|
|
@@ -90,10 +91,8 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
90
91
|
highlightLinks: new Set(),
|
|
91
92
|
lastMousePos: { x: 0, y: 0 },
|
|
92
93
|
mustBeStoppedPropagation: false,
|
|
93
|
-
lastHoveredNode: null,
|
|
94
94
|
mouseStartPos: null,
|
|
95
95
|
isDragging: false,
|
|
96
|
-
lastHoveredNodeRef: null,
|
|
97
96
|
width: width * RATIO,
|
|
98
97
|
height: height * RATIO,
|
|
99
98
|
});
|
|
@@ -367,6 +366,44 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
367
366
|
[config, theme.graph2D.link]
|
|
368
367
|
);
|
|
369
368
|
|
|
369
|
+
const drawIconWithOptionalCounter = (
|
|
370
|
+
ctx: CanvasRenderingContext2D,
|
|
371
|
+
icon: HTMLImageElement,
|
|
372
|
+
iconX: number,
|
|
373
|
+
iconY: number,
|
|
374
|
+
iconSize: number,
|
|
375
|
+
counter: number | null | undefined,
|
|
376
|
+
textColor?: string
|
|
377
|
+
) => {
|
|
378
|
+
try {
|
|
379
|
+
if (counter !== null && counter !== undefined) {
|
|
380
|
+
const fontSize = iconSize * 0.8;
|
|
381
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
382
|
+
ctx.textBaseline = 'middle';
|
|
383
|
+
ctx.textAlign = 'left';
|
|
384
|
+
|
|
385
|
+
const counterText = new Intl.NumberFormat('en', {
|
|
386
|
+
notation: 'compact',
|
|
387
|
+
maximumFractionDigits: 1,
|
|
388
|
+
minimumFractionDigits: 0,
|
|
389
|
+
compactDisplay: 'short',
|
|
390
|
+
}).format(counter);
|
|
391
|
+
|
|
392
|
+
const textWidth = ctx.measureText(counterText).width;
|
|
393
|
+
const spacing = iconSize * 0.3;
|
|
394
|
+
const totalWidth = iconSize + spacing + textWidth;
|
|
395
|
+
const offsetX = totalWidth / 2;
|
|
396
|
+
|
|
397
|
+
ctx.drawImage(icon, iconX - offsetX, iconY - iconSize / 2, iconSize, iconSize);
|
|
398
|
+
ctx.fillStyle = textColor || '#99989C';
|
|
399
|
+
ctx.fillText(counterText, iconX - offsetX + iconSize + spacing, iconY);
|
|
400
|
+
} else {
|
|
401
|
+
ctx.drawImage(icon, iconX - iconSize / 2, iconY - iconSize / 2, iconSize, iconSize);
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.warn('Error rendering icon:', error);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
370
407
|
// Функція для рендерингу кнопок навколо вузла
|
|
371
408
|
const renderNodeButtons = useCallback(
|
|
372
409
|
(node: NodeObject, ctx: CanvasRenderingContext2D) => {
|
|
@@ -412,32 +449,49 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
412
449
|
|
|
413
450
|
// Вибираємо відповідну іконку залежно від стану наведення
|
|
414
451
|
const buttonImage = buttonImages[i];
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
452
|
+
// Якщо кнопка в лоадінгу — малюємо спінер
|
|
453
|
+
if (buttonImage.loading) {
|
|
454
|
+
const spinnerRadius = iconSize / 3;
|
|
455
|
+
const spinnerLineLength = iconSize / 3;
|
|
456
|
+
const spinnerLineWidth = 1;
|
|
457
|
+
|
|
458
|
+
ctx.save();
|
|
459
|
+
ctx.translate(iconX, iconY);
|
|
460
|
+
|
|
461
|
+
// Малюємо кілька променів (ліній) по колу
|
|
462
|
+
for (let j = 0; j < 12; j++) {
|
|
463
|
+
const alpha = j / 12;
|
|
464
|
+
ctx.beginPath();
|
|
465
|
+
ctx.strokeStyle = hexToRgba(theme.colors.primary.main, alpha);
|
|
466
|
+
ctx.lineWidth = spinnerLineWidth;
|
|
467
|
+
ctx.moveTo(0, -spinnerRadius);
|
|
468
|
+
ctx.lineTo(0, -spinnerRadius - spinnerLineLength);
|
|
469
|
+
ctx.stroke();
|
|
470
|
+
ctx.rotate((Math.PI * 2) / 12);
|
|
423
471
|
}
|
|
472
|
+
|
|
473
|
+
ctx.restore();
|
|
424
474
|
} else {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
475
|
+
const icon = isHovered ? buttonImage.hoverImg : buttonImage.normalImg;
|
|
476
|
+
//каунтер може не існувати
|
|
477
|
+
const counter = buttonImage.count ? buttonImage.count(node) : null;
|
|
478
|
+
// Малюємо іконку
|
|
479
|
+
if (icon.complete) {
|
|
480
|
+
drawIconWithOptionalCounter(ctx, icon, iconX, iconY, iconSize, counter, theme.colors?.text?.lighter);
|
|
481
|
+
} else {
|
|
482
|
+
// Встановлюємо обробник onload, якщо зображення ще не завантажено
|
|
483
|
+
icon.onload = () => {
|
|
484
|
+
if (ctx2dRef.current) {
|
|
485
|
+
drawIconWithOptionalCounter(ctx, icon, iconX, iconY, iconSize, counter, theme.colors?.text?.lighter);
|
|
432
486
|
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
435
489
|
}
|
|
436
490
|
}
|
|
437
491
|
|
|
438
492
|
ctx.restore();
|
|
439
493
|
},
|
|
440
|
-
[buttonImages, theme.graph2D?.button]
|
|
494
|
+
[buttonImages, theme.graph2D?.button, theme.colors?.text?.lighter]
|
|
441
495
|
);
|
|
442
496
|
|
|
443
497
|
const renderNodes = useCallback(
|
|
@@ -1062,13 +1116,10 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
1062
1116
|
const handleNodeHover = useCallback(
|
|
1063
1117
|
(node: NodeObject | null) => {
|
|
1064
1118
|
// Перевіряємо, чи вузол той самий, що і останній вузол, на який наводили
|
|
1065
|
-
if (node === stateRef.current.
|
|
1119
|
+
if (node === stateRef.current.hoveredNode) {
|
|
1066
1120
|
return; // Пропускаємо обробку, якщо це той самий вузол
|
|
1067
1121
|
}
|
|
1068
1122
|
|
|
1069
|
-
// Оновлюємо посилання на останній наведений вузол
|
|
1070
|
-
stateRef.current.lastHoveredNodeRef = node;
|
|
1071
|
-
|
|
1072
1123
|
const newHighlightNodes = new Set<NodeObject>();
|
|
1073
1124
|
const newHighlightLinks = new Set<any>();
|
|
1074
1125
|
|
|
@@ -1350,8 +1401,8 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
1350
1401
|
|
|
1351
1402
|
if (shouldRenderLink) {
|
|
1352
1403
|
renderCanvas2D();
|
|
1404
|
+
return;
|
|
1353
1405
|
}
|
|
1354
|
-
return;
|
|
1355
1406
|
}
|
|
1356
1407
|
}
|
|
1357
1408
|
|
package/src/Graph2D/types.ts
CHANGED
|
@@ -57,10 +57,6 @@ export interface GraphState {
|
|
|
57
57
|
lastMousePos: { x: number; y: number };
|
|
58
58
|
/** Флаг необходимости остановки распространения события */
|
|
59
59
|
mustBeStoppedPropagation: boolean;
|
|
60
|
-
/** Последний узел, над которым был курсор */
|
|
61
|
-
lastHoveredNode: NodeObject | null;
|
|
62
|
-
/** Кэшированная ссылка на последний узел, над которым был курсор (для избежания лишних вычислений) */
|
|
63
|
-
lastHoveredNodeRef: NodeObject | null;
|
|
64
60
|
/** Начальная позиция курсора при начале перетаскивания */
|
|
65
61
|
mouseStartPos: { x: number; y: number } | null;
|
|
66
62
|
/** Флаг режима перетаскивания */
|
|
@@ -91,6 +87,8 @@ export interface Graph2DProps {
|
|
|
91
87
|
export interface NodeButton {
|
|
92
88
|
img: string;
|
|
93
89
|
hoverImg: string;
|
|
90
|
+
loading?: boolean;
|
|
91
|
+
count?: (node: NodeObject) => number;
|
|
94
92
|
onClick: (node: NodeObject) => void;
|
|
95
93
|
}
|
|
96
94
|
|