@centreon/ui 24.4.1-MON-34377-implement-tree-chart-on-centreon-ui.0 → 24.4.1-MON-34377-implement-tree-chart-on-centreon-ui.1
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 +1 -1
- package/src/components/Zoom/Minimap.tsx +60 -0
- package/src/components/Zoom/Zoom.cypress.spec.tsx +65 -13
- package/src/components/Zoom/Zoom.stories.tsx +6 -8
- package/src/components/Zoom/Zoom.styles.tsx +10 -7
- package/src/components/Zoom/Zoom.tsx +23 -53
- package/src/components/Zoom/constants.ts +2 -0
- package/src/components/Zoom/useMinimap.ts +96 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "24.4.1-MON-34377-implement-tree-chart-on-centreon-ui.
|
|
3
|
+
"version": "24.4.1-MON-34377-implement-tree-chart-on-centreon-ui.1",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { minimapScale, radius } from './constants';
|
|
2
|
+
import { UseMinimapProps, useMinimap } from './useMinimap';
|
|
3
|
+
import { useZoomStyles } from './Zoom.styles';
|
|
4
|
+
|
|
5
|
+
interface Props extends Omit<UseMinimapProps, 'minimapScale'> {
|
|
6
|
+
children: JSX.Element;
|
|
7
|
+
contentClientRect: {
|
|
8
|
+
height: number;
|
|
9
|
+
width: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Minimap = ({
|
|
14
|
+
zoom,
|
|
15
|
+
children,
|
|
16
|
+
height,
|
|
17
|
+
width,
|
|
18
|
+
contentClientRect
|
|
19
|
+
}: Props): JSX.Element => {
|
|
20
|
+
const { classes } = useZoomStyles();
|
|
21
|
+
|
|
22
|
+
const { transformTo, move, zoomInOut } = useMinimap({
|
|
23
|
+
height,
|
|
24
|
+
minimapScale,
|
|
25
|
+
width,
|
|
26
|
+
zoom
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<g className={classes.minimap} clipPath="url(#zoom-clip)">
|
|
31
|
+
<rect
|
|
32
|
+
className={classes.minimapBackground}
|
|
33
|
+
height={Math.max(contentClientRect?.height || 0, height)}
|
|
34
|
+
rx={radius}
|
|
35
|
+
width={Math.max(contentClientRect?.width || 0, width)}
|
|
36
|
+
/>
|
|
37
|
+
{children}
|
|
38
|
+
<rect
|
|
39
|
+
className={classes.minimapZoom}
|
|
40
|
+
fillOpacity={0.2}
|
|
41
|
+
height={height}
|
|
42
|
+
rx={radius}
|
|
43
|
+
transform={zoom.toStringInvert()}
|
|
44
|
+
width={width}
|
|
45
|
+
/>
|
|
46
|
+
<rect
|
|
47
|
+
data-testid="minimap-interaction"
|
|
48
|
+
fill="transparent"
|
|
49
|
+
height={Math.max(contentClientRect?.height || 0, height)}
|
|
50
|
+
rx={radius}
|
|
51
|
+
width={Math.max(contentClientRect?.width || 0, width)}
|
|
52
|
+
onMouseDown={transformTo}
|
|
53
|
+
onMouseMove={move}
|
|
54
|
+
onWheel={zoomInOut}
|
|
55
|
+
/>
|
|
56
|
+
</g>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default Minimap;
|
|
@@ -7,9 +7,7 @@ const content = (
|
|
|
7
7
|
);
|
|
8
8
|
|
|
9
9
|
const labels = {
|
|
10
|
-
clear: 'Clear'
|
|
11
|
-
zoomIn: '+',
|
|
12
|
-
zoomOut: '-'
|
|
10
|
+
clear: 'Clear'
|
|
13
11
|
};
|
|
14
12
|
|
|
15
13
|
const initialize = ({ showMinimap }): void => {
|
|
@@ -39,7 +37,7 @@ describe('Zoom', () => {
|
|
|
39
37
|
cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
|
|
40
38
|
cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
|
|
41
39
|
|
|
42
|
-
cy.
|
|
40
|
+
cy.findByTestId('zoom in').click();
|
|
43
41
|
cy.findByTestId('zoom-content')
|
|
44
42
|
.should('have.attr', 'transform')
|
|
45
43
|
.and('include', '1.2, 0, 0, 1.2');
|
|
@@ -53,7 +51,7 @@ describe('Zoom', () => {
|
|
|
53
51
|
cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
|
|
54
52
|
cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
|
|
55
53
|
|
|
56
|
-
cy.
|
|
54
|
+
cy.findByTestId('zoom out').click();
|
|
57
55
|
|
|
58
56
|
cy.findByTestId('zoom-content')
|
|
59
57
|
.should('have.attr', 'transform')
|
|
@@ -62,7 +60,7 @@ describe('Zoom', () => {
|
|
|
62
60
|
cy.makeSnapshot();
|
|
63
61
|
});
|
|
64
62
|
|
|
65
|
-
it('zooms out when the content is scrolled', () => {
|
|
63
|
+
it('zooms out when the content is scrolled up', () => {
|
|
66
64
|
initialize({ showMinimap: true });
|
|
67
65
|
|
|
68
66
|
cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
|
|
@@ -77,7 +75,7 @@ describe('Zoom', () => {
|
|
|
77
75
|
cy.makeSnapshot();
|
|
78
76
|
});
|
|
79
77
|
|
|
80
|
-
it('zooms in when the content is scrolled', () => {
|
|
78
|
+
it('zooms in when the content is scrolled down', () => {
|
|
81
79
|
initialize({ showMinimap: true });
|
|
82
80
|
|
|
83
81
|
cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
|
|
@@ -111,21 +109,75 @@ describe('Zoom', () => {
|
|
|
111
109
|
cy.makeSnapshot();
|
|
112
110
|
});
|
|
113
111
|
|
|
114
|
-
it('
|
|
112
|
+
it('does not display the minimap when the prop is set', () => {
|
|
113
|
+
initialize({ showMinimap: false });
|
|
114
|
+
|
|
115
|
+
cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
|
|
116
|
+
cy.get('g[clip-path="url(#zoom-clip)"]').should('not.exist');
|
|
117
|
+
|
|
118
|
+
cy.makeSnapshot();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('zooms in when the minimap is scrolled up', () => {
|
|
115
122
|
initialize({ showMinimap: true });
|
|
116
123
|
|
|
117
124
|
cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
|
|
118
125
|
|
|
119
|
-
cy.
|
|
126
|
+
cy.findByTestId('minimap-interaction').realMouseWheel({ deltaY: -20 });
|
|
127
|
+
|
|
128
|
+
cy.findByTestId('zoom-content')
|
|
129
|
+
.should('have.attr', 'transform')
|
|
130
|
+
.and('include', '1.1, 0, 0, 1.1');
|
|
120
131
|
|
|
121
132
|
cy.makeSnapshot();
|
|
122
133
|
});
|
|
123
134
|
|
|
124
|
-
it('
|
|
125
|
-
initialize({ showMinimap:
|
|
135
|
+
it('zooms out when the minimap is scrolled down', () => {
|
|
136
|
+
initialize({ showMinimap: true });
|
|
126
137
|
|
|
127
|
-
cy.get('g[
|
|
128
|
-
|
|
138
|
+
cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
|
|
139
|
+
|
|
140
|
+
cy.findByTestId('minimap-interaction').realMouseWheel({ deltaY: 20 });
|
|
141
|
+
|
|
142
|
+
cy.findByTestId('zoom-content')
|
|
143
|
+
.should('have.attr', 'transform')
|
|
144
|
+
.and('include', '0.9, 0, 0, 0.9');
|
|
145
|
+
|
|
146
|
+
cy.makeSnapshot();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('moves the view when the minimap is clicked', () => {
|
|
150
|
+
initialize({ showMinimap: true });
|
|
151
|
+
|
|
152
|
+
cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
|
|
153
|
+
cy.get('svg').should('have.attr', 'height', '400');
|
|
154
|
+
|
|
155
|
+
cy.findByTestId('minimap-interaction').click(20, 20);
|
|
156
|
+
|
|
157
|
+
cy.findByTestId('zoom-content').should(
|
|
158
|
+
'have.attr',
|
|
159
|
+
'transform',
|
|
160
|
+
'matrix(1, 0, 0, 1, -100, -100)'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
cy.makeSnapshot();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('moves the view when the mouse is hover the minimap with the correspondiong button pressed down', () => {
|
|
167
|
+
initialize({ showMinimap: true });
|
|
168
|
+
|
|
169
|
+
cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
|
|
170
|
+
cy.get('svg').should('have.attr', 'height', '400');
|
|
171
|
+
|
|
172
|
+
cy.findByTestId('minimap-interaction')
|
|
173
|
+
.realMouseDown()
|
|
174
|
+
.realMouseMove(40, 40);
|
|
175
|
+
|
|
176
|
+
cy.findByTestId('zoom-content').should(
|
|
177
|
+
'have.attr',
|
|
178
|
+
'transform',
|
|
179
|
+
'matrix(1, 0, 0, 1, -640, -200)'
|
|
180
|
+
);
|
|
129
181
|
|
|
130
182
|
cy.makeSnapshot();
|
|
131
183
|
});
|
|
@@ -33,24 +33,22 @@ const Template = ({ children, ...args }: ZoomProps): JSX.Element => (
|
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
const labels = {
|
|
36
|
-
clear: 'Clear'
|
|
37
|
-
zoomIn: '+',
|
|
38
|
-
zoomOut: '-'
|
|
36
|
+
clear: 'Clear'
|
|
39
37
|
};
|
|
40
38
|
|
|
41
|
-
export const
|
|
39
|
+
export const WithoutMinimap: Story = {
|
|
42
40
|
args: {
|
|
43
41
|
children: content,
|
|
44
|
-
labels
|
|
45
|
-
showMinimap: true
|
|
42
|
+
labels
|
|
46
43
|
},
|
|
47
44
|
render: Template
|
|
48
45
|
};
|
|
49
46
|
|
|
50
|
-
export const
|
|
47
|
+
export const WithMinimap: Story = {
|
|
51
48
|
args: {
|
|
52
49
|
children: content,
|
|
53
|
-
labels
|
|
50
|
+
labels,
|
|
51
|
+
showMinimap: true
|
|
54
52
|
},
|
|
55
53
|
render: Template
|
|
56
54
|
};
|
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
import { makeStyles } from 'tss-react/mui';
|
|
2
2
|
|
|
3
|
+
import { minimapScale } from './constants';
|
|
4
|
+
|
|
3
5
|
export const useZoomStyles = makeStyles()((theme) => ({
|
|
4
6
|
actions: {
|
|
7
|
+
bottom: theme.spacing(1),
|
|
5
8
|
display: 'flex',
|
|
6
|
-
flexDirection: '
|
|
9
|
+
flexDirection: 'row',
|
|
7
10
|
gap: theme.spacing(1),
|
|
8
|
-
left:
|
|
11
|
+
left: theme.spacing(1),
|
|
9
12
|
position: 'absolute',
|
|
10
13
|
transition: 'top 0.15s ease-out'
|
|
11
14
|
},
|
|
12
15
|
minimap: {
|
|
13
|
-
|
|
14
|
-
transform: 'scale(0.25)'
|
|
15
|
-
},
|
|
16
|
-
transform: 'scale(0.15)',
|
|
17
|
-
transition: 'transform 0.15s ease-out'
|
|
16
|
+
transform: `scale(${minimapScale})`
|
|
18
17
|
},
|
|
19
18
|
minimapBackground: {
|
|
20
19
|
fill: theme.palette.background.default,
|
|
21
20
|
stroke: theme.palette.background.default
|
|
22
21
|
},
|
|
22
|
+
minimapContainer: { left: 0, position: 'absolute', top: 0 },
|
|
23
23
|
minimapZoom: {
|
|
24
24
|
fill: theme.palette.text.primary
|
|
25
25
|
},
|
|
26
|
+
movingZone: {
|
|
27
|
+
transition: 'transform 0.1s linear'
|
|
28
|
+
},
|
|
26
29
|
svg: {
|
|
27
30
|
'&[data-is-grabbing="true"]': {
|
|
28
31
|
cursor: 'grabbing'
|
|
@@ -3,17 +3,19 @@ import { useState, useRef, useEffect } from 'react';
|
|
|
3
3
|
import { Zoom as VisxZoom } from '@visx/zoom';
|
|
4
4
|
import { RectClipPath } from '@visx/clip-path';
|
|
5
5
|
|
|
6
|
+
import ZoomInIcon from '@mui/icons-material/Add';
|
|
7
|
+
import ZoomOutIcon from '@mui/icons-material/Remove';
|
|
8
|
+
|
|
6
9
|
import { ParentSize } from '../..';
|
|
7
|
-
import { Button } from '../Button';
|
|
10
|
+
import { Button, IconButton } from '../Button';
|
|
8
11
|
|
|
9
12
|
import { useZoomStyles } from './Zoom.styles';
|
|
13
|
+
import Minimap from './Minimap';
|
|
10
14
|
|
|
11
15
|
export interface ZoomProps {
|
|
12
16
|
children: JSX.Element;
|
|
13
17
|
labels: {
|
|
14
18
|
clear: string;
|
|
15
|
-
zoomIn: string;
|
|
16
|
-
zoomOut: string;
|
|
17
19
|
};
|
|
18
20
|
scaleMax?: number;
|
|
19
21
|
scaleMin?: number;
|
|
@@ -43,7 +45,6 @@ const Zoom = ({
|
|
|
43
45
|
height: number;
|
|
44
46
|
width: number;
|
|
45
47
|
} | null>(null);
|
|
46
|
-
const [isMouseOverMinimap, setIsMouseOverMinimap] = useState(false);
|
|
47
48
|
const contentRef = useRef<SVGGElement | null>(null);
|
|
48
49
|
|
|
49
50
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -53,14 +54,6 @@ const Zoom = ({
|
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
const mouseEntersMinimap = (): void => {
|
|
57
|
-
setIsMouseOverMinimap(true);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const mouseLeavesMinimap = (): void => {
|
|
61
|
-
setIsMouseOverMinimap(false);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
57
|
useEffect(() => {
|
|
65
58
|
if (contentRef.current) {
|
|
66
59
|
resizeObserver.disconnect();
|
|
@@ -110,60 +103,37 @@ const Zoom = ({
|
|
|
110
103
|
>
|
|
111
104
|
{children}
|
|
112
105
|
</g>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
</svg>
|
|
107
|
+
<svg className={classes.minimapContainer}>
|
|
108
|
+
{showMinimap && contentClientRect && (
|
|
109
|
+
<Minimap
|
|
110
|
+
contentClientRect={contentClientRect}
|
|
111
|
+
height={height}
|
|
112
|
+
width={width}
|
|
113
|
+
zoom={zoom}
|
|
119
114
|
>
|
|
120
|
-
<rect
|
|
121
|
-
className={classes.minimapBackground}
|
|
122
|
-
height={Math.max(contentClientRect?.height || 0, height)}
|
|
123
|
-
rx={radius}
|
|
124
|
-
width={Math.max(contentClientRect?.width || 0, width)}
|
|
125
|
-
/>
|
|
126
115
|
{children}
|
|
127
|
-
|
|
128
|
-
className={classes.minimapZoom}
|
|
129
|
-
fillOpacity={0.2}
|
|
130
|
-
height={height}
|
|
131
|
-
rx={radius}
|
|
132
|
-
transform={zoom.toStringInvert()}
|
|
133
|
-
width={width}
|
|
134
|
-
/>
|
|
135
|
-
</g>
|
|
116
|
+
</Minimap>
|
|
136
117
|
)}
|
|
137
118
|
</svg>
|
|
138
|
-
<div
|
|
139
|
-
|
|
140
|
-
style={{
|
|
141
|
-
top: showMinimap
|
|
142
|
-
? Math.max(contentClientRect?.height || 0, height) *
|
|
143
|
-
(isMouseOverMinimap ? 0.28 : 0.18)
|
|
144
|
-
: 8
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
<Button
|
|
119
|
+
<div className={classes.actions}>
|
|
120
|
+
<IconButton
|
|
148
121
|
data-testid="zoom in"
|
|
122
|
+
icon={<ZoomInIcon />}
|
|
149
123
|
size="small"
|
|
150
|
-
variant="secondary"
|
|
151
124
|
onClick={() => zoom.scale({ scaleX: 1.2, scaleY: 1.2 })}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
</Button>
|
|
155
|
-
<Button
|
|
125
|
+
/>
|
|
126
|
+
<IconButton
|
|
156
127
|
data-testid="zoom out"
|
|
128
|
+
icon={<ZoomOutIcon />}
|
|
157
129
|
size="small"
|
|
158
|
-
variant="secondary"
|
|
159
130
|
onClick={() => zoom.scale({ scaleX: 0.8, scaleY: 0.8 })}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
</Button>
|
|
131
|
+
/>
|
|
132
|
+
|
|
163
133
|
<Button
|
|
164
134
|
data-testid="clear"
|
|
165
135
|
size="small"
|
|
166
|
-
variant="
|
|
136
|
+
variant="ghost"
|
|
167
137
|
onClick={zoom.clear}
|
|
168
138
|
>
|
|
169
139
|
{labels.clear}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ProvidedZoom } from '@visx/zoom/lib/types';
|
|
4
|
+
import { equals, gt } from 'ramda';
|
|
5
|
+
|
|
6
|
+
interface ZoomState {
|
|
7
|
+
transformMatrix: {
|
|
8
|
+
scaleX: number;
|
|
9
|
+
scaleY: number;
|
|
10
|
+
skewX: number;
|
|
11
|
+
skewY: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseMinimapProps {
|
|
16
|
+
height: number;
|
|
17
|
+
minimapScale: number;
|
|
18
|
+
width: number;
|
|
19
|
+
zoom: ProvidedZoom<SVGSVGElement> & ZoomState;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UseMinimapState {
|
|
23
|
+
move: (e) => void;
|
|
24
|
+
transformTo: (e) => void;
|
|
25
|
+
zoomInOut: (e) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useMinimap = ({
|
|
29
|
+
width,
|
|
30
|
+
height,
|
|
31
|
+
zoom,
|
|
32
|
+
minimapScale
|
|
33
|
+
}: UseMinimapProps): UseMinimapState => {
|
|
34
|
+
const getMatrixPoint = useCallback(
|
|
35
|
+
(event): { x: number; y: number } => {
|
|
36
|
+
const point = {
|
|
37
|
+
x: event.nativeEvent.offsetX * (1 / minimapScale),
|
|
38
|
+
y: event.nativeEvent.offsetY * (1 / minimapScale)
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
x: -(point.x * zoom.transformMatrix.scaleX - width / 2),
|
|
43
|
+
y: -(point.y * zoom.transformMatrix.scaleY - height / 2)
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
[zoom.transformMatrix]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const transformTo = useCallback(
|
|
50
|
+
(e): void => {
|
|
51
|
+
const { x, y } = getMatrixPoint(e);
|
|
52
|
+
zoom.setTransformMatrix({
|
|
53
|
+
...zoom.transformMatrix,
|
|
54
|
+
translateX: x,
|
|
55
|
+
translateY: y
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
[zoom.transformMatrix]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const move = useCallback(
|
|
62
|
+
(e): void => {
|
|
63
|
+
if (!equals(e.buttons, 1)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
transformTo(e);
|
|
67
|
+
},
|
|
68
|
+
[zoom.transformMatrix]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const zoomInOut = useCallback(
|
|
72
|
+
(e): void => {
|
|
73
|
+
const isZoomIn = gt(0, e.deltaY);
|
|
74
|
+
const { x, y } = getMatrixPoint(e);
|
|
75
|
+
|
|
76
|
+
zoom.setTransformMatrix({
|
|
77
|
+
...zoom.transformMatrix,
|
|
78
|
+
scaleX: isZoomIn
|
|
79
|
+
? zoom.transformMatrix.scaleX + 0.1
|
|
80
|
+
: zoom.transformMatrix.scaleX - 0.1,
|
|
81
|
+
scaleY: isZoomIn
|
|
82
|
+
? zoom.transformMatrix.scaleY + 0.1
|
|
83
|
+
: zoom.transformMatrix.scaleY - 0.1,
|
|
84
|
+
translateX: x,
|
|
85
|
+
translateY: y
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
[zoom.transformMatrix]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
move,
|
|
93
|
+
transformTo,
|
|
94
|
+
zoomInOut
|
|
95
|
+
};
|
|
96
|
+
};
|