@cyber-harbour/ui 1.0.34 → 1.0.36

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.
Files changed (36) hide show
  1. package/dist/eye_light-3WS4REO5.png +0 -0
  2. package/dist/eye_light_hover-PVS4UAB4.png +0 -0
  3. package/dist/group_light-RVCSCGRJ.png +0 -0
  4. package/dist/group_light_hover-LVI5PRZM.png +0 -0
  5. package/dist/index.d.mts +43 -5
  6. package/dist/index.d.ts +43 -5
  7. package/dist/index.js +184 -170
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +268 -254
  10. package/dist/index.mjs.map +1 -1
  11. package/package.json +2 -1
  12. package/src/Core/Header/Header.tsx +1 -1
  13. package/src/Core/Sidebar/Sidebar.tsx +3 -3
  14. package/src/Core/Sidebar/SidebarDelimeter.tsx +1 -1
  15. package/src/FullscreenCard/FullscreenCard.tsx +39 -8
  16. package/src/Graph2D/Graph2D.tsx +71 -70
  17. package/src/Graph2D/GraphLoader.tsx +84 -0
  18. package/src/Graph2D/eye_light.png +0 -0
  19. package/src/Graph2D/eye_light_hover.png +0 -0
  20. package/src/Graph2D/group_light.png +0 -0
  21. package/src/Graph2D/group_light_hover.png +0 -0
  22. package/src/Graph2D/types.ts +1 -0
  23. package/src/Layouts/PageLayout/PageLayout.tsx +6 -3
  24. package/src/Theme/ThemeProvider.tsx +9 -3
  25. package/src/Theme/index.ts +1 -1
  26. package/src/Theme/themes/config.ts +41 -0
  27. package/src/Theme/themes/dark.ts +703 -0
  28. package/src/Theme/themes/index.ts +2 -0
  29. package/src/Theme/{theme.ts → themes/light.ts} +30 -41
  30. package/src/Theme/types.ts +24 -0
  31. package/dist/cross_light-JTZWFLSV.png +0 -0
  32. package/dist/cross_light_hover-UQZ7E3CW.png +0 -0
  33. package/dist/eye_light-EQXRQBFN.png +0 -0
  34. package/dist/eye_light_hover-5XFRPJS4.png +0 -0
  35. package/src/Graph2D/cross_light.png +0 -0
  36. package/src/Graph2D/cross_light_hover.png +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-harbour/ui",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -29,6 +29,7 @@
29
29
  "dependencies": {
30
30
  "@types/d3-force": "^3.0.10",
31
31
  "d3-force": "^3.0.0",
32
+ "react-content-loader": "^7.0.2",
32
33
  "react-force-graph-2d": "^1.27.1",
33
34
  "react-tiny-popover": "^8.1.6",
34
35
  "styled-components": "^6.1.18"
@@ -20,7 +20,7 @@ const StyledContainer = styled.header(
20
20
  padding-right: 20px;
21
21
  height: 56px;
22
22
  background-color: ${theme.colors.background};
23
- border-bottom: 1px solid ${theme.colors.stroke.main};
23
+ border-bottom: 1px solid ${theme.colors.stroke.light};
24
24
 
25
25
  &:before {
26
26
  content: '';
@@ -40,8 +40,8 @@ const StyledContainer = styled.aside<StyledProps>(
40
40
  width: ${theme.sidebar.width};
41
41
  padding: 12px;
42
42
  height: 100%;
43
- border-right: 1px solid ${theme.colors.stroke.light};
44
- background: ${theme.colors.background};
43
+ border-right: 1px solid ${theme.sidebar.border};
44
+ background: ${theme.sidebar.background};
45
45
  ${
46
46
  $collapsed
47
47
  ? `
@@ -60,7 +60,7 @@ const StyledContainer = styled.aside<StyledProps>(
60
60
  height: 25dvh;
61
61
  transform: translateY(-100%);
62
62
  background: ${theme.colors.background};
63
- border-right: 1px solid ${theme.colors.stroke.light};
63
+ border-right: 1px solid ${theme.sidebar.border};
64
64
 
65
65
  width: ${theme.sidebar.width};
66
66
  ${
@@ -16,7 +16,7 @@ const StyledDelimeter = styled.div<StyledProps>(
16
16
  min-width: 32px;
17
17
  width: 0;
18
18
 
19
- border-bottom: 1px dashed ${theme.colors.stroke.main};
19
+ border-bottom: 1px dashed ${theme.sidebar.delimeter.color};
20
20
  margin-block: 8px;
21
21
  margin-left: 4px;
22
22
 
@@ -1,26 +1,57 @@
1
1
  import styled from 'styled-components';
2
+ import { pxToRem } from '../Theme';
2
3
 
3
4
  interface FullscreenCardProps {
4
5
  children: any;
6
+ className?: string;
5
7
  position: 'absolute' | 'fixed';
6
8
  isActive: boolean;
9
+ top?: number;
10
+ left?: number;
11
+ right?: number;
12
+ bottom?: number;
7
13
  }
8
14
 
9
- export const FullscreenCard = ({ isActive, position, ...props }: FullscreenCardProps) => {
10
- return <StyledContainer $isActive={isActive} $position={position} {...props} />;
15
+ export const FullscreenCard = ({
16
+ isActive,
17
+ position,
18
+ top = 0,
19
+ left = 0,
20
+ right,
21
+ bottom,
22
+ ...props
23
+ }: FullscreenCardProps) => {
24
+ return (
25
+ <StyledContainer
26
+ $isActive={isActive}
27
+ $position={position}
28
+ $top={top}
29
+ $left={left}
30
+ $right={right}
31
+ $bottom={bottom}
32
+ {...props}
33
+ />
34
+ );
11
35
  };
12
36
 
13
- const StyledContainer = styled.div<{ $isActive: boolean; $position: 'absolute' | 'fixed' }>(
14
- ({ $isActive, $position }) => `
37
+ const StyledContainer = styled.div<{
38
+ $isActive: boolean;
39
+ $top?: number;
40
+ $left?: number;
41
+ $right?: number;
42
+ $bottom?: number;
43
+ $position: 'absolute' | 'fixed';
44
+ }>(
45
+ ({ $isActive, $top, $left, $right, $bottom, $position, theme }) => `
15
46
  ${
16
47
  $isActive
17
48
  ? `
18
49
  position: ${$position};
19
- top: 0;
20
- left: 0;
21
- height: 100%;
22
- width: 100%;
23
50
  z-index: 1000;
51
+ ${$top ? `top: ${pxToRem($top, theme.baseSize)};` : ''}
52
+ ${$left ? `left: ${pxToRem($left, theme.baseSize)};` : ''}
53
+ ${$right ? `right: ${pxToRem($right, theme.baseSize)};` : ''}
54
+ ${$bottom ? `bottom: ${pxToRem($bottom, theme.baseSize)};` : ''}
24
55
  `
25
56
  : ''
26
57
  }
@@ -2,13 +2,12 @@ import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from 'react-f
2
2
  import { Graph2DProps } from './types';
3
3
  import { useEffect, useRef, useState, useCallback, useLayoutEffect } from 'react';
4
4
  import { forceCollide } from 'd3-force';
5
- import { styled } from 'styled-components';
5
+ import { styled, useTheme } from 'styled-components';
6
6
  import eyeLightIcon from './eye_light.png';
7
7
  import eyeLightHoverIcon from './eye_light_hover.png';
8
- import crossLightIcon from './cross_light.png';
9
- import crossLightHoverIcon from './cross_light_hover.png';
10
-
11
- const ALPHA_MIN = 0.5;
8
+ import groupLightIcon from './group_light.png';
9
+ import groupLightHoverIcon from './group_light_hover.png';
10
+ import GraphLoader from './GraphLoader';
12
11
 
13
12
  // Створюємо та налаштовуємо об'єкти зображень
14
13
  const imgEyeLightIcon = new Image();
@@ -17,11 +16,11 @@ imgEyeLightIcon.src = eyeLightIcon;
17
16
  const imgEyeLightHoverIcon = new Image();
18
17
  imgEyeLightHoverIcon.src = eyeLightHoverIcon;
19
18
 
20
- const imgCrossLightIcon = new Image();
21
- imgCrossLightIcon.src = crossLightIcon;
19
+ const imgGroupLightIcon = new Image();
20
+ imgGroupLightIcon.src = groupLightIcon;
22
21
 
23
- const imgCrossLightHoverIcon = new Image();
24
- imgCrossLightHoverIcon.src = crossLightHoverIcon;
22
+ const imgGroupLightHoverIcon = new Image();
23
+ imgGroupLightHoverIcon.src = groupLightHoverIcon;
25
24
 
26
25
  export const Graph2D = ({
27
26
  graphData,
@@ -29,6 +28,7 @@ export const Graph2D = ({
29
28
  height,
30
29
  linkTarget,
31
30
  linkSource,
31
+ loading = false,
32
32
  config = {
33
33
  fontSize: 3, // Максимальний розмір шрифту при максимальному зумі
34
34
  nodeSizeBase: 30, // Базовий розмір вузла
@@ -44,6 +44,8 @@ export const Graph2D = ({
44
44
  onLinkClick,
45
45
  onBackgroundClick,
46
46
  }: Graph2DProps) => {
47
+ const theme = useTheme();
48
+
47
49
  // Стан для підсвічування вузлів і зв'язків
48
50
  const [highlightNodes, setHighlightNodes] = useState(new Set());
49
51
  const [highlightLinks, setHighlightLinks] = useState(new Set());
@@ -83,7 +85,7 @@ export const Graph2D = ({
83
85
  };
84
86
 
85
87
  // Обробка подій наведення на вузол
86
- const handleNodeHover = useCallback((node: NodeObject | null, _: NodeObject | null) => {
88
+ const handleNodeHover = (node: NodeObject | null, _: NodeObject | null) => {
87
89
  const newHighlightNodes = new Set();
88
90
  const newHighlightLinks = new Set();
89
91
 
@@ -106,10 +108,10 @@ export const Graph2D = ({
106
108
  setHoverNode(node || null);
107
109
  setHighlightNodes(newHighlightNodes);
108
110
  setHighlightLinks(newHighlightLinks);
109
- }, []);
111
+ };
110
112
 
111
113
  // Обробка подій наведення на зв'язок
112
- const handleLinkHover = useCallback((link: any) => {
114
+ const handleLinkHover = (link: any) => {
113
115
  const newHighlightNodes = new Set();
114
116
  const newHighlightLinks = new Set();
115
117
 
@@ -122,7 +124,7 @@ export const Graph2D = ({
122
124
 
123
125
  setHighlightNodes(newHighlightNodes);
124
126
  setHighlightLinks(newHighlightLinks);
125
- }, []);
127
+ };
126
128
 
127
129
  const handleEngineTick = useCallback(() => {
128
130
  if (isRendering)
@@ -131,7 +133,7 @@ export const Graph2D = ({
131
133
  fgRef.current &&
132
134
  fgRef.current.tick &&
133
135
  graphData.nodes.length > 0 &&
134
- graphData.nodes.length * ALPHA_MIN <= fgRef.current.tick
136
+ graphData.nodes.length <= fgRef.current.tick
135
137
  ) {
136
138
  if (tickTimerRef.current) {
137
139
  clearTimeout(tickTimerRef.current);
@@ -152,7 +154,7 @@ export const Graph2D = ({
152
154
  }, [graphData]);
153
155
 
154
156
  // Створення взаємозв'язків між вузлами
155
- useLayoutEffect(() => {
157
+ useEffect(() => {
156
158
  if (!graphData) return;
157
159
 
158
160
  // Прив'язка вузлів до їхніх сусідів та зв'язків
@@ -176,33 +178,14 @@ export const Graph2D = ({
176
178
  target.links.push(link);
177
179
  });
178
180
 
179
- // Налаштування відстані між вузлами
180
- fgRef.current?.d3Force('link')?.distance((link: any) => {
181
- // Отримуємо вузли на кінцях зв'язку
182
- const source = link.source;
183
- const target = link.target;
184
-
185
- // Базова відстань
186
- const baseDistance = config.nodeSizeBase * 2;
187
-
188
- // Динамічна відстань на основі розміру вузлів
189
- // Більші вузли повинні бути далі один від одного
190
- const sourceSizeBase = source.size || config.nodeSizeBase;
191
- const targetSizeBase = target.size || config.nodeSizeBase;
192
-
193
- // Відстань залежить від суми розмірів вузлів
194
- // Додаємо базову відстань 100
195
- return baseDistance + (sourceSizeBase + targetSizeBase);
196
- });
197
-
198
181
  // Додаємо різні сили для уникнення перекриття вузлів
199
182
  if (fgRef.current) {
200
183
  // 1. Додаємо силу відштовхування між всіма вузлами (charge force)
201
184
  const chargeForce = fgRef.current.d3Force('charge');
202
185
  if (chargeForce) {
203
186
  chargeForce
204
- .strength(-100) // Збільшуємо силу відштовхування (negative for repulsion)
205
- .distanceMax(100); // Максимальна дистанція, на якій діє ця сила
187
+ .strength(config.nodeSizeBase) // Збільшуємо силу відштовхування (negative for repulsion)
188
+ .distanceMax(50); // Максимальна дистанція, на якій діє ця сила
206
189
  }
207
190
 
208
191
  // 2. Додаємо силу центрування для кращої організації графа
@@ -222,13 +205,21 @@ export const Graph2D = ({
222
205
  .iterations(3) // Більше ітерацій для точнішого розрахунку
223
206
  .strength(1); // Максимальна сила (1 - тверде обмеження)
224
207
 
225
- fgRef.current.pauseAnimation().d3Force('collide', collideForce).resumeAnimation();
208
+ fgRef.current.d3Force('collide', collideForce);
226
209
  } catch (err) {
227
210
  console.error('Error setting up collision force:', err);
228
211
  }
229
212
  }
230
213
  }, [graphData]);
231
214
 
215
+ useEffect(() => {
216
+ if (!isRendering && fgRef.current) {
217
+ setIsRendering(true);
218
+ fgRef.current.tick = 0;
219
+ fgRef.current.d3ReheatSimulation();
220
+ }
221
+ }, [graphData]);
222
+
232
223
  // Функція для малювання кільця навколо підсвічених вузлів
233
224
  const paintRing = useCallback(
234
225
  (node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
@@ -238,7 +229,7 @@ export const Graph2D = ({
238
229
  // Малюємо кільце навколо вузла
239
230
  ctx.beginPath();
240
231
  ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, false);
241
- ctx.fillStyle = 'rgba(255, 165, 0, 0.3)';
232
+ ctx.fillStyle = theme.graph2D.ring.highlightFill;
242
233
  ctx.fill();
243
234
  },
244
235
  [config]
@@ -257,9 +248,9 @@ export const Graph2D = ({
257
248
  ctx.beginPath();
258
249
  ctx.arc(x, y, buttonRadius, Math.PI, Math.PI * 2, false);
259
250
  ctx.lineWidth = 1;
260
- ctx.strokeStyle = '#e5e5e5';
251
+ ctx.strokeStyle = theme.graph2D.button.stroke;
261
252
  ctx.stroke();
262
- ctx.fillStyle = hoverTopButton ? 'rgba(230, 230, 230, 0.9)' : 'rgba(255, 255, 255, 0.8)';
253
+ ctx.fillStyle = hoverTopButton ? theme.graph2D.button.hoverFill : theme.graph2D.button.normalFill;
263
254
  ctx.fill();
264
255
 
265
256
  // Лінія розділення між кнопками
@@ -267,67 +258,68 @@ export const Graph2D = ({
267
258
  ctx.moveTo(x - buttonRadius, y);
268
259
  ctx.lineTo(x + buttonRadius, y);
269
260
  ctx.lineWidth = 1;
270
- ctx.strokeStyle = '#e5e5e5';
261
+ ctx.strokeStyle = theme.graph2D.button.stroke;
271
262
  ctx.stroke();
272
263
 
273
264
  // Кнопка "згорнути" (нижня частина кільця)
274
265
  ctx.beginPath();
275
266
  ctx.arc(x, y, buttonRadius, Math.PI * 2, Math.PI, false);
276
267
  ctx.lineWidth = 1;
277
- ctx.strokeStyle = '#e5e5e5';
268
+ ctx.strokeStyle = theme.graph2D.button.stroke;
278
269
  ctx.stroke();
279
- ctx.fillStyle = hoverBottomButton ? 'rgba(230, 230, 230, 0.9)' : 'rgba(255, 255, 255, 0.8)';
270
+ ctx.fillStyle = hoverBottomButton ? theme.graph2D.button.hoverFill : theme.graph2D.button.normalFill;
280
271
  ctx.fill();
281
272
 
282
273
  // Додаємо іконку хрестика для кнопки "сховати вузол"
283
274
  const iconSize = buttonRadius * 0.3; // Розмір іконки відносно радіуса кнопки (зменшено вдвічі)
284
275
 
285
276
  // Вибір іконки в залежності від стану наведення для верхньої кнопки (сховати)
286
- const crossIcon = hoverTopButton ? imgCrossLightHoverIcon : imgCrossLightIcon;
287
- const renderCrossIcon = () => {
277
+ const groupIcon = hoverTopButton ? imgGroupLightHoverIcon : imgGroupLightIcon;
278
+ // Додаємо іконку ока для кнопки "згорнути дочірні вузли"
279
+ const eyeIcon = hoverBottomButton ? imgEyeLightHoverIcon : imgEyeLightIcon;
280
+
281
+ const renderEyeIcon = () => {
288
282
  try {
289
- ctx.drawImage(crossIcon, x - iconSize / 2, y - (buttonRadius * 2) / 4 - iconSize - 1, iconSize, iconSize);
283
+ ctx.drawImage(eyeIcon, x - iconSize / 2, y - (buttonRadius * 2) / 4 - iconSize - 1, iconSize, iconSize);
290
284
  } catch (error) {
291
- console.log('Error rendering cross icon:', error);
285
+ console.warn('Error rendering group icon:', error);
292
286
  }
293
287
  };
294
288
  // Використовуємо безпосередньо зображення, якщо воно вже завантажене
295
- if (crossIcon.complete) {
289
+ if (eyeIcon.complete) {
296
290
  // Розміщуємо іконку в центрі верхньої половини кнопки
297
- renderCrossIcon();
291
+ renderEyeIcon();
298
292
  } else {
299
293
  // Якщо зображення ще не завантажене, додаємо обробник завершення завантаження
300
- crossIcon.onload = () => {
301
- renderCrossIcon();
294
+ eyeIcon.onload = () => {
295
+ renderEyeIcon();
302
296
  };
303
297
 
304
- crossIcon.onerror = () => {
305
- console.log('Error loading cross icon image');
298
+ eyeIcon.onerror = () => {
299
+ console.warn('Error loading group icon image');
306
300
  };
307
301
  }
308
302
 
309
- // Додаємо іконку ока для кнопки "згорнути дочірні вузли"
310
- const eyeIcon = hoverBottomButton ? imgEyeLightHoverIcon : imgEyeLightIcon;
311
- const renderEyeIcon = () => {
303
+ const renderGroupIcon = () => {
312
304
  try {
313
- ctx.drawImage(eyeIcon, x - iconSize / 2, y + (buttonRadius * 2) / 4 + 1, iconSize, iconSize);
305
+ ctx.drawImage(groupIcon, x - iconSize / 2, y + (buttonRadius * 2) / 4 + 1, iconSize, iconSize);
314
306
  } catch (error) {
315
- console.log('Error rendering eye icon:', error);
307
+ console.warn('Error rendering eye icon:', error);
316
308
  }
317
309
  };
318
310
  // Використовуємо безпосередньо зображення, якщо воно вже завантажене
319
311
  if (eyeIcon.complete) {
320
312
  // Розміщуємо іконку в центрі нижньої половини кнопки
321
313
 
322
- renderEyeIcon();
314
+ renderGroupIcon();
323
315
  } else {
324
316
  // Якщо зображення ще не завантажене, додаємо обробник завершення завантаження
325
317
  eyeIcon.onload = () => {
326
- renderEyeIcon();
318
+ renderGroupIcon();
327
319
  };
328
320
 
329
321
  eyeIcon.onerror = () => {
330
- console.log('Error loading eye icon image');
322
+ console.warn('Error loading eye icon image');
331
323
  };
332
324
  }
333
325
 
@@ -581,7 +573,7 @@ export const Graph2D = ({
581
573
  const gridSpacing = config.gridSpacing;
582
574
  const dotSize = config.dotSize;
583
575
 
584
- ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
576
+ ctx.fillStyle = theme.graph2D.grid.dotColor;
585
577
 
586
578
  for (let x = 0; x < width; x += gridSpacing) {
587
579
  for (let y = 0; y < height; y += gridSpacing) {
@@ -700,7 +692,7 @@ export const Graph2D = ({
700
692
  };
701
693
 
702
694
  // Малюємо лінію зв'язку з урахуванням місця для тексту, якщо він є
703
- const lineColor = highlightLinks.has(link) ? '#ff9900' : '#999';
695
+ const lineColor = highlightLinks.has(link) ? theme.graph2D.link.highlighted : theme.graph2D.link.normal;
704
696
  const lineWidth = highlightLinks.has(link) ? 1.5 : 0.5;
705
697
 
706
698
  if (label) {
@@ -773,7 +765,7 @@ export const Graph2D = ({
773
765
  ctx.lineTo(-arrowHeadLength, -arrowHeadWidth);
774
766
  ctx.closePath();
775
767
 
776
- ctx.fillStyle = highlightLinks.has(link) ? '#ff9900' : '#999';
768
+ ctx.fillStyle = highlightLinks.has(link) ? theme.graph2D.link.highlighted : theme.graph2D.link.normal;
777
769
  ctx.fill();
778
770
  ctx.restore();
779
771
 
@@ -787,7 +779,7 @@ export const Graph2D = ({
787
779
  // Використовуємо реверсивне масштабування для тексту
788
780
  const scaledFontSize = calculateFontSize(globalScale);
789
781
  ctx.font = `${scaledFontSize}px Sans-Serif`;
790
- ctx.fillStyle = '#666'; // Колір тексту
782
+ ctx.fillStyle = theme.graph2D.link.textColor; // Колір тексту
791
783
  ctx.textAlign = 'center';
792
784
  ctx.textBaseline = 'middle';
793
785
 
@@ -807,7 +799,9 @@ export const Graph2D = ({
807
799
  // Рисуємо фон для тексту для кращої читаємості
808
800
  const textWidth = ctx.measureText(label).width;
809
801
  const padding = 2;
810
- ctx.fillStyle = highlightLinks.has(link) ? 'rgba(255, 230, 204, 0.9)' : 'rgba(255, 255, 255, 0.8)';
802
+ ctx.fillStyle = highlightLinks.has(link)
803
+ ? theme.graph2D.link.highlightedTextBgColor
804
+ : theme.graph2D.link.textBgColor;
811
805
  ctx.fillRect(
812
806
  -textWidth / 2 - padding,
813
807
  -scaledFontSize / 2 - padding,
@@ -816,7 +810,7 @@ export const Graph2D = ({
816
810
  );
817
811
 
818
812
  // Малюємо текст
819
- ctx.fillStyle = highlightLinks.has(link) ? '#663300' : '#666';
813
+ ctx.fillStyle = highlightLinks.has(link) ? theme.graph2D.link.highlightedTextColor : theme.graph2D.link.textColor;
820
814
  ctx.fillText(label, 0, 0);
821
815
 
822
816
  // Відновлення стану контексту
@@ -921,10 +915,12 @@ export const Graph2D = ({
921
915
 
922
916
  const handleBackgroundClick = (event: MouseEvent) => {
923
917
  setSelectedNode(null);
918
+ onBackgroundClick?.();
924
919
  };
925
920
 
926
921
  return (
927
922
  <Wrapper ref={wrapperRef}>
923
+ {(loading || isRendering) && <GraphLoader width={width} height={height} />}
928
924
  <ForceGraph2D
929
925
  ref={fgRef}
930
926
  width={width}
@@ -948,12 +944,14 @@ export const Graph2D = ({
948
944
  onNodeHover={handleNodeHover}
949
945
  onLinkHover={handleLinkHover}
950
946
  onEngineTick={handleEngineTick}
951
- d3AlphaMin={ALPHA_MIN}
947
+ d3AlphaMin={0.001}
952
948
  d3VelocityDecay={0.4}
953
949
  d3AlphaDecay={0.038}
954
950
  // Виділення зв'язків при наведенні
955
951
  linkWidth={(link: any) => (highlightLinks.has(link) ? 3 : 1)}
956
- linkColor={(link: any) => (highlightLinks.has(link) ? '#ff9900' : '#999')}
952
+ linkColor={(link: any) =>
953
+ highlightLinks.has(link) ? theme.graph2D.link.highlighted : theme.graph2D.link.normal
954
+ }
957
955
  onRenderFramePre={renderGrid}
958
956
  nodePointerAreaPaint={renderNodePointerAreaPaint}
959
957
  nodeCanvasObject={renderNodeCanvasObject}
@@ -984,5 +982,8 @@ export const Graph2D = ({
984
982
  };
985
983
 
986
984
  const Wrapper = styled.div`
987
- display: inline-block;
985
+ display: block;
986
+ width: 100%;
987
+ min-width: 0;
988
+ position: relative;
988
989
  `;
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ import ContentLoader from 'react-content-loader';
3
+ import styled from 'styled-components';
4
+
5
+ const LoaderWrapper = styled.div`
6
+ position: absolute;
7
+ top: 0;
8
+ left: 0;
9
+ width: 100%;
10
+ height: 100%;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ z-index: 10;
15
+ `;
16
+
17
+ interface GraphLoaderProps {
18
+ width?: number;
19
+ height?: number;
20
+ }
21
+
22
+ const GraphLoader: React.FC<GraphLoaderProps> = ({ width = 280, height = 280 }) => {
23
+ // Helper function to create a rect from line coordinates
24
+ const lineToRect = (x1: number, y1: number, x2: number, y2: number, thickness: number = 1) => {
25
+ // Calculate length and angle of the line
26
+ const length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
27
+ const angle = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI;
28
+
29
+ // Calculate center point of the line
30
+ const centerX = (x1 + x2) / 2;
31
+ const centerY = (y1 + y2) / 2;
32
+
33
+ return (
34
+ <rect
35
+ x={centerX - length / 2}
36
+ y={centerY - thickness / 2}
37
+ width={length}
38
+ height={thickness}
39
+ transform={`rotate(${angle}, ${centerX}, ${centerY})`}
40
+ />
41
+ );
42
+ };
43
+
44
+ return (
45
+ <LoaderWrapper>
46
+ <ContentLoader width={width} height={height} viewBox="0 0 280 280">
47
+ <path d="m55 38-0.97266 0.22852 7.0801 30.092-18.355-20.979-0.75195 0.6582 19.596 22.395 0.43164 1.834 0.97266-0.22852 0.75195-0.6582-0.37695-0.42969 9.625-27.912-0.94531-0.32617-9.4375 27.371-0.10547-0.12109zm8 34-0.78516 0.61914 0.0957 0.12305-12.311 13.258 0.73242 0.67969 12.205-13.145 14.277 18.084 0.78516-0.61914-14.373-18.207 0.10547-0.11328zm15 19-0.48438 0.875 46.992 25.996 8e-3 4e-3 20.506 11.592-28.182 4.5449 0.15998 0.98819 29.418-4.7441 0.25 0.14062-12.555 24.143 0.88672 0.46094 13-25 15 25 26 30v18l-11 18 0.85352 0.52148 9.8008-16.039-4.6543 33.518 0.99023 0.13867 4.7793-34.408 7.2305 16.27 0.91406-0.40625-7.9141-17.807v-17.104l18 12.316 0.56445-0.82617-18.896-12.928-25.855-29.836-14.633-24.387 0.01562-0.02344h23.805v-1h-23.152l13.848-21.234 55.201-28.791-0.45898-0.88476 0.77734 0.62305 11.402-14.25 16.668-11.842-0.58008-0.81641-16.785 11.928-11.486 14.355-55.434 28.912-14.277 21.893-7.7617-27.166-0.96094 0.27344 7.7227 27.031-1.1191 0.17969-21.604-12.211zm140.43-12.912-6.957-17.338-0.92773 0.37305 6.957 17.338zm-6.957-17.338 0.72266 0.69336 16.232-16.896-0.7207-0.69336zm-3.4766 137.25 5 15 0.94922-0.31641-5-15zm-91-63-0.48047-0.87695-31 17 0.48047 0.87695zm-31 17 5 18 0.96289-0.26758-5-18zm0 0-0.70703-0.70703-12.898 12.898-17.881 9.9336 0.48633 0.875 18-10zm5 18-0.64062-0.76758-18 15 0.64062 0.76758z" />
48
+ <circle cx="229.92" cy="63.7318" r="5" transform="rotate(173.661 229.92 63.7318)" />
49
+ <circle cx="227.711" cy="43.8541" r="5" transform="rotate(173.661 227.711 43.8541)" />
50
+ <circle cx="211.478" cy="60.7499" r="5" transform="rotate(173.661 211.478 60.7499)" />
51
+ <circle cx="218.434" cy="78.0877" r="5" transform="rotate(173.661 218.434 78.0877)" />
52
+ <circle cx="246.705" cy="51.8054" r="5" transform="rotate(173.661 246.705 51.8054)" />
53
+ <circle cx="42" cy="48" r="5" />
54
+ <circle cx="55" cy="38" r="5" />
55
+ <circle cx="73" cy="43" r="5" />
56
+ <circle cx="63" cy="72" r="5" />
57
+ <circle cx="50" cy="86" r="5" />
58
+ <circle cx="78" cy="91" r="5" />
59
+ <circle cx="73" cy="165" r="5" />
60
+ <circle cx="73" cy="185" r="5" />
61
+ <circle cx="91" cy="170" r="5" />
62
+ <circle cx="86" cy="152" r="5" />
63
+ <circle cx="148" cy="130" r="5" />
64
+ <circle cx="189" cy="185" r="5" />
65
+ <circle cx="163" cy="107" r="5" />
66
+ <circle cx="140" cy="102" r="5" />
67
+ <circle cx="117" cy="135" r="5" />
68
+ <circle cx="125" cy="117" r="5" />
69
+ <circle cx="208" cy="198" r="5" />
70
+ <circle cx="189" cy="203" r="5" />
71
+ <circle cx="198" cy="221" r="5" />
72
+ <circle cx="178" cy="221" r="5" />
73
+ <circle cx="184" cy="239" r="5" />
74
+ <circle cx="213" cy="213" r="5" />
75
+ <circle cx="173" cy="130" r="5" />
76
+ <circle cx="163" cy="155" r="5" />
77
+ <circle cx="135" cy="155" r="5" />
78
+ <circle cx="55" cy="175" r="5" />
79
+ </ContentLoader>
80
+ </LoaderWrapper>
81
+ );
82
+ };
83
+
84
+ export default GraphLoader;
Binary file
Binary file
Binary file
@@ -4,6 +4,7 @@ export interface Graph2DProps {
4
4
  graphData?: GraphData;
5
5
  linkSource?: string;
6
6
  linkTarget?: string;
7
+ loading?: boolean;
7
8
 
8
9
  // Container layout
9
10
  width?: number;
@@ -54,6 +54,9 @@ export const StyledContainer = styled.div<StyledContainerProps>(
54
54
  `
55
55
  );
56
56
 
57
- const StyledMain = styled.main`
58
- min-width: 0;
59
- `;
57
+ const StyledMain = styled.main(
58
+ ({ theme }) => `
59
+ min-width: 0;
60
+ background: ${theme.colors.backgroundBase};
61
+ `
62
+ );
@@ -1,11 +1,17 @@
1
1
  import { StyleSheetManager, ThemeProvider as ThemeProviderStyled, WebTarget } from 'styled-components';
2
- import { lightTheme } from './theme';
2
+ import { lightTheme, darkTheme } from './themes';
3
3
  import { GlobalStyle } from './GlobalStyle';
4
4
 
5
- export const ThemeProvider = ({ children }: { children: any }) => {
5
+ interface ThemeProviderProps {
6
+ children: any;
7
+ mode?: 'light' | 'LIGHT' | 'dark' | 'DARK';
8
+ }
9
+
10
+ export type ThemeMode = 'light' | 'LIGHT' | 'dark' | 'DARK';
11
+ export const ThemeProvider = ({ children, mode }: ThemeProviderProps) => {
6
12
  return (
7
13
  <StyleSheetManager shouldForwardProp={shouldForwardProp}>
8
- <ThemeProviderStyled theme={lightTheme}>
14
+ <ThemeProviderStyled theme={mode == 'light' || mode === 'LIGHT' ? lightTheme : darkTheme}>
9
15
  <GlobalStyle />
10
16
  {children}
11
17
  </ThemeProviderStyled>
@@ -2,5 +2,5 @@ export * from './GlobalStyle';
2
2
  export * from './ThemeProvider';
3
3
  export * from './utils';
4
4
  export * from './types';
5
- export * from './theme';
5
+ export * from './themes';
6
6
  export * from './componentFabric';