@centreon/ui 24.4.64 → 24.4.65

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.
@@ -0,0 +1,52 @@
1
+ export interface Node<T> {
2
+ children?: Array<Node<T>>;
3
+ data: T;
4
+ isExpanded?: boolean;
5
+ }
6
+
7
+ export interface LinkProps<T> {
8
+ source: T;
9
+ target: T;
10
+ }
11
+
12
+ export interface BaseProp {
13
+ id: number;
14
+ name: string;
15
+ }
16
+
17
+ export interface ChildrenProps<TData> {
18
+ ancestors: Array<Node<TData>>;
19
+ depth: number;
20
+ expandCollapseNode: (targetNode: Node<TData>) => void;
21
+ isExpanded: boolean;
22
+ node: Node<TData>;
23
+ nodeSize: {
24
+ height: number;
25
+ width: number;
26
+ };
27
+ onMouseDown: (e) => void;
28
+ onMouseUp: (callback) => (e) => void;
29
+ }
30
+
31
+ export interface TreeProps<TData> {
32
+ changeTree?: (newTree: Node<TData>) => void;
33
+ children: (props: ChildrenProps<TData>) => JSX.Element;
34
+ containerHeight: number;
35
+ containerWidth: number;
36
+ contentHeight?: number;
37
+ contentWidth?: number;
38
+ node: {
39
+ height: number;
40
+ isDefaultExpanded?: (data: TData) => boolean;
41
+ width: number;
42
+ };
43
+ tree: Node<TData>;
44
+ treeLink?: {
45
+ getStroke?: (props: LinkProps<TData>) => string | undefined;
46
+ getStrokeDasharray?: (
47
+ props: LinkProps<TData>
48
+ ) => string | number | undefined;
49
+ getStrokeOpacity?: (props: LinkProps<TData>) => string | number | undefined;
50
+ getStrokeWidth?: (props: LinkProps<TData>) => string | number | undefined;
51
+ };
52
+ }
@@ -0,0 +1,164 @@
1
+ import { always, cond, equals, has, T } from 'ramda';
2
+
3
+ import { Avatar, Paper, Typography, useTheme } from '@mui/material';
4
+ import SpaIcon from '@mui/icons-material/Spa';
5
+
6
+ import { ChildrenProps } from '..';
7
+
8
+ import { ComplexData, SimpleData } from './datas';
9
+
10
+ export const SimpleContent = ({
11
+ node,
12
+ depth,
13
+ isExpanded,
14
+ nodeSize,
15
+ expandCollapseNode
16
+ }: ChildrenProps<SimpleData>): JSX.Element => {
17
+ const theme = useTheme();
18
+ const fillColor = cond([
19
+ [equals('critical'), always(theme.palette.error.main)],
20
+ [equals('warning'), always(theme.palette.warning.main)],
21
+ [T, always(theme.palette.success.main)]
22
+ ])(node.data.status);
23
+
24
+ if (equals(depth, 0)) {
25
+ return (
26
+ <div
27
+ style={{
28
+ alignItems: 'center',
29
+ display: 'flex',
30
+ height: '100%',
31
+ justifyContent: 'center',
32
+ width: '100%'
33
+ }}
34
+ >
35
+ <Avatar
36
+ sx={{
37
+ backgroundColor: fillColor,
38
+ color: theme.palette.text.primary,
39
+ cursor: 'pointer'
40
+ }}
41
+ >
42
+ {node.data.name}
43
+ </Avatar>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ return (
49
+ <Paper
50
+ sx={{
51
+ alignItems: 'center',
52
+ backgroundColor: fillColor,
53
+ cursor: node.children ? 'pointer' : 'default',
54
+ display: 'flex',
55
+ flexDirection: 'column',
56
+ height: nodeSize.height,
57
+ justifyContent: 'center',
58
+ p: 1,
59
+ position: 'relative',
60
+ width: nodeSize.width
61
+ }}
62
+ onClick={() => {
63
+ expandCollapseNode(node);
64
+ }}
65
+ >
66
+ {!node.children && (
67
+ <SpaIcon
68
+ fontSize="small"
69
+ sx={{ position: 'absolute', right: 8, top: 8 }}
70
+ />
71
+ )}
72
+ <Typography>{node.data.name}</Typography>
73
+ {node.children && (
74
+ <Typography>{isExpanded ? 'Expanded' : 'Collapsed'}</Typography>
75
+ )}
76
+ </Paper>
77
+ );
78
+ };
79
+
80
+ export const ComplexContent = ({
81
+ node,
82
+ depth,
83
+ nodeSize,
84
+ expandCollapseNode,
85
+ onMouseDown,
86
+ onMouseUp
87
+ }: ChildrenProps<ComplexData>): JSX.Element => {
88
+ const theme = useTheme();
89
+ const fillColor = cond([
90
+ [equals('critical'), always(theme.palette.error.main)],
91
+ [equals('warning'), always(theme.palette.warning.main)],
92
+ [T, always(theme.palette.success.main)]
93
+ ])(node.data.status);
94
+
95
+ if (equals(depth, 0)) {
96
+ return (
97
+ <Paper
98
+ sx={{
99
+ alignItems: 'center',
100
+ backgroundColor: fillColor,
101
+ display: 'flex',
102
+ flexDirection: 'column',
103
+ height: nodeSize.height,
104
+ justifyContent: 'center',
105
+ p: 1,
106
+ position: 'relative',
107
+ width: nodeSize.width
108
+ }}
109
+ >
110
+ <Typography>{node.data.name}</Typography>
111
+ </Paper>
112
+ );
113
+ }
114
+
115
+ if (has('count', node.data)) {
116
+ return (
117
+ <div
118
+ style={{
119
+ alignItems: 'center',
120
+ display: 'flex',
121
+ height: '100%',
122
+ justifyContent: 'center',
123
+ width: '100%'
124
+ }}
125
+ >
126
+ <Avatar
127
+ sx={{
128
+ backgroundColor: fillColor,
129
+ color: theme.palette.text.primary,
130
+ cursor: 'pointer'
131
+ }}
132
+ onMouseDown={onMouseDown}
133
+ onMouseUp={onMouseUp(() => expandCollapseNode(node))}
134
+ >
135
+ {node.data.count}
136
+ </Avatar>
137
+ </div>
138
+ );
139
+ }
140
+
141
+ return (
142
+ <Paper
143
+ sx={{
144
+ alignItems: 'center',
145
+ backgroundColor: fillColor,
146
+ display: 'flex',
147
+ flexDirection: 'column',
148
+ height: nodeSize.height,
149
+ justifyContent: 'center',
150
+ p: 1,
151
+ position: 'relative',
152
+ width: nodeSize.width
153
+ }}
154
+ >
155
+ {!node.children && (
156
+ <SpaIcon
157
+ fontSize="small"
158
+ sx={{ position: 'absolute', right: 8, top: 8 }}
159
+ />
160
+ )}
161
+ <Typography>{node.data.name}</Typography>
162
+ </Paper>
163
+ );
164
+ };
@@ -0,0 +1,305 @@
1
+ import { Node } from '../models';
2
+
3
+ export interface SimpleData {
4
+ id: number;
5
+ name: string;
6
+ status: 'critical' | 'warning' | 'ok';
7
+ }
8
+
9
+ export interface ComplexData {
10
+ count?: number;
11
+ id: number;
12
+ name: string;
13
+ status: 'critical' | 'warning' | 'ok';
14
+ }
15
+
16
+ export const simpleData: Node<SimpleData> = {
17
+ children: [
18
+ {
19
+ children: [
20
+ { data: { id: 2, name: 'A1', status: 'ok' } },
21
+ { data: { id: 3, name: 'A2', status: 'ok' } },
22
+ { data: { id: 4, name: 'A3', status: 'critical' } },
23
+ {
24
+ children: [
25
+ {
26
+ data: { id: 10, name: 'C1', status: 'warning' }
27
+ },
28
+ {
29
+ children: [
30
+ {
31
+ data: { id: 11, name: 'D1', status: 'ok' }
32
+ },
33
+ {
34
+ data: { id: 12, name: 'D2', status: 'ok' }
35
+ },
36
+ {
37
+ data: { id: 13, name: 'D3', status: 'ok' }
38
+ }
39
+ ],
40
+ data: { id: 14, name: 'D', status: 'ok' }
41
+ },
42
+ {
43
+ children: [
44
+ {
45
+ data: { id: 21, name: 'E1', status: 'critical' }
46
+ }
47
+ ],
48
+ data: { id: 20, name: 'E', status: 'critical' }
49
+ }
50
+ ],
51
+ data: { id: 3, name: 'C', status: 'critical' }
52
+ }
53
+ ],
54
+ data: { id: 6273286320, name: 'A', status: 'critical' }
55
+ },
56
+ { data: { id: 1, name: 'Z', status: 'ok' } },
57
+ {
58
+ children: [{ data: { id: 50, name: 'B1', status: 'warning' } }],
59
+ data: { id: 2, name: 'B', status: 'warning' }
60
+ }
61
+ ],
62
+ data: {
63
+ id: 0,
64
+ name: 'T',
65
+ status: 'critical'
66
+ }
67
+ };
68
+
69
+ export const complexData: Node<ComplexData> = {
70
+ children: [
71
+ {
72
+ children: [
73
+ { data: { id: 2, name: 'Indicator 1', status: 'critical' } },
74
+ {
75
+ children: [
76
+ {
77
+ children: [
78
+ { data: { id: 6, name: 'Indicator 6', status: 'critical' } },
79
+ { data: { id: 7, name: 'Indicator 7', status: 'ok' } }
80
+ ],
81
+ data: { count: 2, id: 0, name: 'critical', status: 'critical' }
82
+ },
83
+ {
84
+ children: [
85
+ {
86
+ children: [
87
+ {
88
+ children: [
89
+ {
90
+ data: {
91
+ id: 8,
92
+ name: 'Indicator 8',
93
+ status: 'warning'
94
+ }
95
+ }
96
+ ],
97
+ data: {
98
+ count: 1,
99
+ id: 4,
100
+ name: 'warning',
101
+ status: 'warning'
102
+ }
103
+ },
104
+ {
105
+ children: [
106
+ {
107
+ data: {
108
+ id: 10,
109
+ name: 'Indicator 10',
110
+ status: 'ok'
111
+ }
112
+ }
113
+ ],
114
+ data: {
115
+ count: 1,
116
+ id: 0,
117
+ name: 'ok',
118
+ status: 'ok'
119
+ }
120
+ }
121
+ ],
122
+ data: {
123
+ id: 6,
124
+ name: 'BA 3',
125
+ status: 'warning'
126
+ }
127
+ }
128
+ ],
129
+ data: {
130
+ count: 1,
131
+ id: 4,
132
+ name: 'warning',
133
+ status: 'warning'
134
+ }
135
+ },
136
+ {
137
+ children: [
138
+ { data: { id: 9, name: 'Indicator 9', status: 'ok' } }
139
+ ],
140
+ data: { count: 1, id: 0, name: 'ok', status: 'ok' }
141
+ }
142
+ ],
143
+ data: { id: 3, name: 'BA 2', status: 'critical' }
144
+ }
145
+ ],
146
+ data: { count: 2, id: 6, name: 'critical', status: 'critical' }
147
+ },
148
+ {
149
+ children: [
150
+ { data: { id: 4, name: 'Indicator 4', status: 'warning' } },
151
+ { data: { id: 5, name: 'Indicator 5', status: 'warning' } }
152
+ ],
153
+ data: { count: 2, id: 4, name: 'warning', status: 'warning' }
154
+ },
155
+ {
156
+ children: [
157
+ { data: { id: 6, name: 'Indicator 2', status: 'ok' } },
158
+ {
159
+ children: [
160
+ {
161
+ children: [
162
+ { data: { id: 6, name: 'Indicator 2', status: 'ok' } }
163
+ ],
164
+ data: {
165
+ count: 1,
166
+ name: 'ok',
167
+ status: 'ok'
168
+ }
169
+ }
170
+ ],
171
+ data: { id: 0, name: 'BA 4', status: 'ok' }
172
+ }
173
+ ],
174
+ data: { count: 2, id: 0, name: 'ok', status: 'ok' }
175
+ }
176
+ ],
177
+ data: {
178
+ id: 1,
179
+ name: 'BA 1',
180
+ status: 'critical'
181
+ }
182
+ };
183
+
184
+ export const moreComplexData: Node<ComplexData> = {
185
+ children: [
186
+ {
187
+ children: [
188
+ { data: { id: 2, name: 'Indicator 1', status: 'critical' } },
189
+ {
190
+ children: [
191
+ {
192
+ children: [
193
+ { data: { id: 6, name: 'Indicator 6', status: 'critical' } },
194
+ { data: { id: 7, name: 'Indicator 7', status: 'ok' } }
195
+ ],
196
+ data: { count: 2, id: 0, name: 'critical', status: 'critical' }
197
+ },
198
+ {
199
+ children: [
200
+ {
201
+ children: [
202
+ {
203
+ children: [
204
+ {
205
+ data: {
206
+ id: 8,
207
+ name: 'Indicator 8',
208
+ status: 'warning'
209
+ }
210
+ }
211
+ ],
212
+ data: {
213
+ count: 1,
214
+ id: 4,
215
+ name: 'warning',
216
+ status: 'warning'
217
+ }
218
+ },
219
+ {
220
+ children: [
221
+ {
222
+ data: {
223
+ id: 10,
224
+ name: 'Indicator 10',
225
+ status: 'ok'
226
+ }
227
+ }
228
+ ],
229
+ data: {
230
+ count: 1,
231
+ id: 0,
232
+ name: 'ok',
233
+ status: 'ok'
234
+ }
235
+ }
236
+ ],
237
+ data: {
238
+ id: 6,
239
+ name: 'BA 3',
240
+ status: 'warning'
241
+ }
242
+ }
243
+ ],
244
+ data: {
245
+ count: 1,
246
+ id: 4,
247
+ name: 'warning',
248
+ status: 'warning'
249
+ }
250
+ },
251
+ {
252
+ children: [
253
+ { data: { id: 9, name: 'Indicator 9', status: 'ok' } }
254
+ ],
255
+ data: { count: 1, id: 0, name: 'ok', status: 'ok' }
256
+ }
257
+ ],
258
+ data: { id: 3, name: 'BA 2', status: 'critical' }
259
+ }
260
+ ],
261
+ data: { count: 2, id: 6, name: 'critical', status: 'critical' }
262
+ },
263
+ {
264
+ children: [
265
+ { data: { id: 4, name: 'Indicator 4', status: 'warning' } },
266
+ { data: { id: 5, name: 'Indicator 5', status: 'warning' } }
267
+ ],
268
+ data: { count: 2, id: 4, name: 'warning', status: 'warning' }
269
+ },
270
+ {
271
+ children: [
272
+ {
273
+ children: [
274
+ {
275
+ children: [
276
+ { data: { id: 11, name: 'Indicator 11', status: 'ok' } }
277
+ ],
278
+ data: {
279
+ count: 1,
280
+ name: 'ok',
281
+ status: 'ok'
282
+ }
283
+ }
284
+ ],
285
+ data: { id: 0, name: 'BA 3', status: 'ok' }
286
+ },
287
+ { data: { id: 30, name: 'Indicator 30', status: 'ok' } },
288
+ { data: { id: 31, name: 'Indicator 31', status: 'ok' } },
289
+ { data: { id: 32, name: 'Indicator 32', status: 'ok' } },
290
+ { data: { id: 33, name: 'Indicator 33', status: 'ok' } },
291
+ { data: { id: 34, name: 'Indicator 34', status: 'ok' } },
292
+ { data: { id: 35, name: 'Indicator 35', status: 'ok' } },
293
+ { data: { id: 36, name: 'Indicator 36', status: 'ok' } },
294
+ { data: { id: 37, name: 'Indicator 37', status: 'ok' } },
295
+ { data: { id: 38, name: 'Indicator 38', status: 'ok' } }
296
+ ],
297
+ data: { count: 9, id: 0, name: 'ok', status: 'ok' }
298
+ }
299
+ ],
300
+ data: {
301
+ id: 1,
302
+ name: 'BA 1',
303
+ status: 'critical'
304
+ }
305
+ };
@@ -0,0 +1,49 @@
1
+ import { equals, omit } from 'ramda';
2
+
3
+ import { BaseProp, Node } from './models';
4
+
5
+ interface UpdateNodeFromTreeProps<TData> {
6
+ callback: (tree: Node<TData>) => Partial<Node<TData>>;
7
+ targetNode: Node<TData>;
8
+ tree: Node<TData>;
9
+ }
10
+
11
+ export const updateNodeFromTree = <TData extends BaseProp>({
12
+ tree,
13
+ targetNode,
14
+ callback
15
+ }: UpdateNodeFromTreeProps<TData>): Node<TData> => {
16
+ if (!tree.children) {
17
+ return tree;
18
+ }
19
+
20
+ if (
21
+ equals(tree.data, targetNode.data) &&
22
+ equals(tree.children, targetNode.children)
23
+ ) {
24
+ return {
25
+ ...tree,
26
+ ...callback(tree)
27
+ };
28
+ }
29
+
30
+ return {
31
+ ...tree,
32
+ children: tree.children?.map((child) =>
33
+ updateNodeFromTree({ callback, targetNode, tree: child })
34
+ )
35
+ };
36
+ };
37
+
38
+ export const cleanUpTree = <TData extends BaseProp>(
39
+ tree: Node<TData>
40
+ ): Node<TData> => {
41
+ if (!tree.children) {
42
+ return tree;
43
+ }
44
+
45
+ return {
46
+ ...omit(['isExpanded'], tree),
47
+ children: tree.children?.map((child) => cleanUpTree(child))
48
+ };
49
+ };
@@ -6,3 +6,4 @@ export { Text as GraphText } from './Text';
6
6
  export { HeatMap } from './HeatMap';
7
7
  export { BarStack } from './BarStack';
8
8
  export { PieChart } from './PieChart';
9
+ export * from './Tree';
@@ -0,0 +1,127 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { scaleLinear } from '@visx/scale';
4
+
5
+ import { minimapScale, radius } from './constants';
6
+ import { UseMinimapProps, useMinimap } from './useMinimap';
7
+ import { useZoomStyles } from './Zoom.styles';
8
+
9
+ interface Props extends Omit<UseMinimapProps, 'minimapScale' | 'scale'> {
10
+ children: JSX.Element;
11
+ contentClientRect: {
12
+ height: number;
13
+ width: number;
14
+ };
15
+ diffBetweenContentAndSvg: {
16
+ left: number;
17
+ top: number;
18
+ };
19
+ isDraggingFromContainer: boolean;
20
+ }
21
+
22
+ const Minimap = ({
23
+ zoom,
24
+ children,
25
+ height,
26
+ width,
27
+ contentClientRect,
28
+ isDraggingFromContainer,
29
+ diffBetweenContentAndSvg
30
+ }: Props): JSX.Element => {
31
+ const { classes } = useZoomStyles();
32
+
33
+ const yMinimapScale = useMemo(
34
+ () => contentClientRect.height / zoom.transformMatrix.scaleY / height,
35
+ [contentClientRect.height, height]
36
+ );
37
+ const xMinimapScale = useMemo(
38
+ () => contentClientRect.width / zoom.transformMatrix.scaleX / width,
39
+ [contentClientRect.width, width]
40
+ );
41
+
42
+ const scale = Math.max(yMinimapScale, xMinimapScale);
43
+ const invertedScale = 1 / scale;
44
+ const scaleToUse = (invertedScale > 1 ? 1 : invertedScale) || 1;
45
+
46
+ const { move, zoomInOut, dragStart, dragEnd } = useMinimap({
47
+ height,
48
+ isDraggingFromContainer,
49
+ minimapScale,
50
+ scale: (invertedScale > 1 ? 1 : scale) || 1,
51
+ width,
52
+ zoom
53
+ });
54
+
55
+ const finalHeight = height;
56
+ const finalWidth = width;
57
+
58
+ const additionalScaleScale = scaleLinear({
59
+ clamp: true,
60
+ domain: [contentClientRect.height, 0],
61
+ range: [0, 0.05]
62
+ });
63
+
64
+ const additionalScale =
65
+ additionalScaleScale(contentClientRect.height - height) /
66
+ 2 /
67
+ zoom.transformMatrix.scaleY;
68
+
69
+ const translateX = useMemo(
70
+ () =>
71
+ -diffBetweenContentAndSvg.left /
72
+ zoom.transformMatrix.scaleX /
73
+ minimapScale,
74
+ [diffBetweenContentAndSvg.left]
75
+ );
76
+ const translateY = useMemo(
77
+ () =>
78
+ -diffBetweenContentAndSvg.top /
79
+ zoom.transformMatrix.scaleX /
80
+ minimapScale,
81
+ [diffBetweenContentAndSvg.top]
82
+ );
83
+
84
+ return (
85
+ <g className={classes.minimap} clipPath="url(#zoom-clip)">
86
+ <rect
87
+ className={classes.minimapBackground}
88
+ height={finalHeight}
89
+ rx={radius}
90
+ width={finalWidth}
91
+ />
92
+ <g
93
+ className={classes.movingZone}
94
+ style={{
95
+ transform: `scale(${scaleToUse - additionalScale}) translate(${translateX}px, ${translateY}px)`
96
+ }}
97
+ >
98
+ {children}
99
+ <g>
100
+ <rect
101
+ className={classes.minimapZoom}
102
+ fillOpacity={0.2}
103
+ height={height}
104
+ rx={radius}
105
+ transform={zoom.toStringInvert()}
106
+ width={width}
107
+ />
108
+ </g>
109
+ </g>
110
+ <rect
111
+ data-testid="minimap-interaction"
112
+ fill="transparent"
113
+ height={finalHeight}
114
+ rx={radius}
115
+ width={finalWidth}
116
+ onMouseDown={dragStart}
117
+ onMouseEnter={dragStart}
118
+ onMouseLeave={dragEnd}
119
+ onMouseMove={move}
120
+ onMouseUp={dragEnd}
121
+ onWheel={zoomInOut}
122
+ />
123
+ </g>
124
+ );
125
+ };
126
+
127
+ export default Minimap;