@cyber-harbour/ui 1.0.69 → 1.0.71
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 +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +143 -143
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -134
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Graph2D/Graph2D.tsx +105 -17
- package/src/Graph2D/types.ts +9 -0
- package/src/Theme/themes/dark.ts +2 -0
- package/src/Theme/themes/light.ts +2 -0
- package/src/Theme/types.ts +2 -0
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
|
// Завантаження та підготовка зображень кнопок
|
|
@@ -94,6 +95,11 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
94
95
|
isDragging: false,
|
|
95
96
|
width: width * RATIO,
|
|
96
97
|
height: height * RATIO,
|
|
98
|
+
animation: {
|
|
99
|
+
id: null,
|
|
100
|
+
buttonIndex: null,
|
|
101
|
+
},
|
|
102
|
+
spinnerAngle: 0,
|
|
97
103
|
});
|
|
98
104
|
|
|
99
105
|
const { nodes, links } = useMemo(() => cloneDeep(graphData), [graphData]);
|
|
@@ -365,6 +371,44 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
365
371
|
[config, theme.graph2D.link]
|
|
366
372
|
);
|
|
367
373
|
|
|
374
|
+
const drawIconWithOptionalCounter = (
|
|
375
|
+
ctx: CanvasRenderingContext2D,
|
|
376
|
+
icon: HTMLImageElement,
|
|
377
|
+
iconX: number,
|
|
378
|
+
iconY: number,
|
|
379
|
+
iconSize: number,
|
|
380
|
+
counter: number | null | undefined,
|
|
381
|
+
textColor?: string
|
|
382
|
+
) => {
|
|
383
|
+
try {
|
|
384
|
+
if (counter !== null && counter !== undefined) {
|
|
385
|
+
const fontSize = iconSize * 0.8;
|
|
386
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
387
|
+
ctx.textBaseline = 'middle';
|
|
388
|
+
ctx.textAlign = 'left';
|
|
389
|
+
|
|
390
|
+
const counterText = new Intl.NumberFormat('en', {
|
|
391
|
+
notation: 'compact',
|
|
392
|
+
maximumFractionDigits: 1,
|
|
393
|
+
minimumFractionDigits: 0,
|
|
394
|
+
compactDisplay: 'short',
|
|
395
|
+
}).format(counter);
|
|
396
|
+
|
|
397
|
+
const textWidth = ctx.measureText(counterText).width;
|
|
398
|
+
const spacing = iconSize * 0.3;
|
|
399
|
+
const totalWidth = iconSize + spacing + textWidth;
|
|
400
|
+
const offsetX = totalWidth / 2;
|
|
401
|
+
|
|
402
|
+
ctx.drawImage(icon, iconX - offsetX, iconY - iconSize / 2, iconSize, iconSize);
|
|
403
|
+
ctx.fillStyle = textColor || '#99989C';
|
|
404
|
+
ctx.fillText(counterText, iconX - offsetX + iconSize + spacing, iconY);
|
|
405
|
+
} else {
|
|
406
|
+
ctx.drawImage(icon, iconX - iconSize / 2, iconY - iconSize / 2, iconSize, iconSize);
|
|
407
|
+
}
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.warn('Error rendering icon:', error);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
368
412
|
// Функція для рендерингу кнопок навколо вузла
|
|
369
413
|
const renderNodeButtons = useCallback(
|
|
370
414
|
(node: NodeObject, ctx: CanvasRenderingContext2D) => {
|
|
@@ -410,26 +454,70 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
|
|
|
410
454
|
|
|
411
455
|
// Вибираємо відповідну іконку залежно від стану наведення
|
|
412
456
|
const buttonImage = buttonImages[i];
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
457
|
+
// Якщо кнопка в лоадінгу — малюємо спінер
|
|
458
|
+
if (buttonImage.loading) {
|
|
459
|
+
const spinnerRadius = iconSize / 2;
|
|
460
|
+
const dotRadius = iconSize / 12;
|
|
461
|
+
|
|
462
|
+
ctx.save();
|
|
463
|
+
ctx.translate(iconX, iconY);
|
|
464
|
+
ctx.rotate(stateRef.current.spinnerAngle);
|
|
465
|
+
|
|
466
|
+
// Малюємо кілька променів (ліній) по колу
|
|
467
|
+
for (let j = 0; j < 12; j++) {
|
|
468
|
+
const angle = (Math.PI * 2 * j) / 12;
|
|
469
|
+
const alpha = j / 12;
|
|
470
|
+
|
|
471
|
+
const dotX = Math.cos(angle) * spinnerRadius;
|
|
472
|
+
const dotY = Math.sin(angle) * spinnerRadius;
|
|
473
|
+
|
|
474
|
+
ctx.beginPath();
|
|
475
|
+
ctx.fillStyle = hexToRgba(theme.graph2D?.button?.spinnerColor, alpha);
|
|
476
|
+
ctx.arc(dotX, dotY, dotRadius, 0, Math.PI * 2);
|
|
477
|
+
ctx.fill();
|
|
478
|
+
}
|
|
479
|
+
ctx.restore();
|
|
480
|
+
|
|
481
|
+
stateRef.current.spinnerAngle += 0.1;
|
|
482
|
+
if (stateRef.current.spinnerAngle > Math.PI * 2) {
|
|
483
|
+
stateRef.current.spinnerAngle -= Math.PI * 2;
|
|
484
|
+
}
|
|
485
|
+
if (stateRef.current.animation.id !== null) {
|
|
486
|
+
cancelAnimationFrame(stateRef.current.animation.id);
|
|
487
|
+
stateRef.current.animation.id = null;
|
|
488
|
+
stateRef.current.animation.buttonIndex = null;
|
|
421
489
|
}
|
|
490
|
+
|
|
491
|
+
stateRef.current.animation.id = requestAnimationFrame(renderCanvas2D);
|
|
492
|
+
stateRef.current.animation.buttonIndex = i;
|
|
422
493
|
} else {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
494
|
+
if (stateRef.current.animation.id && stateRef.current.animation.buttonIndex === i) {
|
|
495
|
+
cancelAnimationFrame(stateRef.current.animation.id);
|
|
496
|
+
stateRef.current.animation.id = null;
|
|
497
|
+
stateRef.current.animation.buttonIndex = null;
|
|
498
|
+
}
|
|
499
|
+
const icon = isHovered ? buttonImage.hoverImg : buttonImage.normalImg;
|
|
500
|
+
//каунтер може не існувати
|
|
501
|
+
const counter = buttonImage.getCount ? buttonImage.getCount(node) : null;
|
|
502
|
+
// Малюємо іконку
|
|
503
|
+
if (icon.complete) {
|
|
504
|
+
drawIconWithOptionalCounter(ctx, icon, iconX, iconY, iconSize, counter, theme.graph2D?.button?.textColor);
|
|
505
|
+
} else {
|
|
506
|
+
// Встановлюємо обробник onload, якщо зображення ще не завантажено
|
|
507
|
+
icon.onload = () => {
|
|
508
|
+
if (ctx2dRef.current) {
|
|
509
|
+
drawIconWithOptionalCounter(
|
|
510
|
+
ctx,
|
|
511
|
+
icon,
|
|
512
|
+
iconX,
|
|
513
|
+
iconY,
|
|
514
|
+
iconSize,
|
|
515
|
+
counter,
|
|
516
|
+
theme.graph2D?.button?.textColor
|
|
517
|
+
);
|
|
430
518
|
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
433
521
|
}
|
|
434
522
|
}
|
|
435
523
|
|
package/src/Graph2D/types.ts
CHANGED
|
@@ -63,6 +63,13 @@ export interface GraphState {
|
|
|
63
63
|
isDragging: boolean;
|
|
64
64
|
width: number;
|
|
65
65
|
height: number;
|
|
66
|
+
/** Поточна анімація */
|
|
67
|
+
animation: {
|
|
68
|
+
id: number | null;
|
|
69
|
+
buttonIndex: number | null;
|
|
70
|
+
};
|
|
71
|
+
/** Кут оберту спінера */
|
|
72
|
+
spinnerAngle: number;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
export interface Graph2DProps {
|
|
@@ -87,6 +94,8 @@ export interface Graph2DProps {
|
|
|
87
94
|
export interface NodeButton {
|
|
88
95
|
img: string;
|
|
89
96
|
hoverImg: string;
|
|
97
|
+
loading?: boolean;
|
|
98
|
+
getCount?: (node: NodeObject) => number;
|
|
90
99
|
onClick: (node: NodeObject) => void;
|
|
91
100
|
}
|
|
92
101
|
|
package/src/Theme/themes/dark.ts
CHANGED
|
@@ -857,6 +857,8 @@ export const darkThemePx: Theme = {
|
|
|
857
857
|
stroke: '#1A1A1A',
|
|
858
858
|
normalFill: 'rgba(0, 0, 0, 0.8)',
|
|
859
859
|
hoverFill: 'rgba(25, 25, 25, 0.9)',
|
|
860
|
+
textColor: '#99989C',
|
|
861
|
+
spinnerColor: '#80A0F5',
|
|
860
862
|
},
|
|
861
863
|
grid: {
|
|
862
864
|
dotColor: 'rgba(255, 255, 255, 0.5)',
|
|
@@ -856,6 +856,8 @@ export const lightThemePx: Theme = {
|
|
|
856
856
|
stroke: '#e5e5e5',
|
|
857
857
|
normalFill: 'rgba(255, 255, 255, 0.8)',
|
|
858
858
|
hoverFill: 'rgba(230, 230, 230, 0.9)',
|
|
859
|
+
textColor: '#99989C',
|
|
860
|
+
spinnerColor: '#80A0F5',
|
|
859
861
|
},
|
|
860
862
|
grid: {
|
|
861
863
|
dotColor: 'rgba(0, 0, 0, 0.5)',
|