@cyber-harbour/ui 1.0.69 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-harbour/ui",
3
- "version": "1.0.69",
3
+ "version": "1.0.70",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -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
  // Завантаження та підготовка зображень кнопок
@@ -365,6 +366,44 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
365
366
  [config, theme.graph2D.link]
366
367
  );
367
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
+ };
368
407
  // Функція для рендерингу кнопок навколо вузла
369
408
  const renderNodeButtons = useCallback(
370
409
  (node: NodeObject, ctx: CanvasRenderingContext2D) => {
@@ -410,32 +449,49 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
410
449
 
411
450
  // Вибираємо відповідну іконку залежно від стану наведення
412
451
  const buttonImage = buttonImages[i];
413
- const icon = isHovered ? buttonImage.hoverImg : buttonImage.normalImg;
414
-
415
- // Малюємо іконку
416
- if (icon.complete) {
417
- try {
418
- ctx.drawImage(icon, iconX - iconSize / 2, iconY - iconSize / 2, iconSize, iconSize);
419
- } catch (error) {
420
- console.warn('Error rendering button icon:', error);
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);
421
471
  }
472
+
473
+ ctx.restore();
422
474
  } else {
423
- // Встановлюємо обробник onload, якщо зображення ще не завантажено
424
- icon.onload = () => {
425
- if (ctx2dRef.current) {
426
- try {
427
- ctx.drawImage(icon, iconX - iconSize / 2, iconY - iconSize / 2, iconSize, iconSize);
428
- } catch (error) {
429
- console.warn('Error rendering button icon after load:', error);
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);
430
486
  }
431
- }
432
- };
487
+ };
488
+ }
433
489
  }
434
490
  }
435
491
 
436
492
  ctx.restore();
437
493
  },
438
- [buttonImages, theme.graph2D?.button]
494
+ [buttonImages, theme.graph2D?.button, theme.colors?.text?.lighter]
439
495
  );
440
496
 
441
497
  const renderNodes = useCallback(
@@ -87,6 +87,8 @@ export interface Graph2DProps {
87
87
  export interface NodeButton {
88
88
  img: string;
89
89
  hoverImg: string;
90
+ loading?: boolean;
91
+ count?: (node: NodeObject) => number;
90
92
  onClick: (node: NodeObject) => void;
91
93
  }
92
94