@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 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.0",
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.contains('+').click();
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.contains('-').click();
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('highlights the minimap when the mouse is over the minimap', () => {
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.get('g[clip-path="url(#zoom-clip)"]').realHover();
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('does not display the minimap the prop is set', () => {
125
- initialize({ showMinimap: false });
135
+ it('zooms out when the minimap is scrolled down', () => {
136
+ initialize({ showMinimap: true });
126
137
 
127
- cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
128
- cy.get('g[clip-path="url(#zoom-clip)"]').should('not.exist');
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 Default: Story = {
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 WithoutMinimap: Story = {
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: 'column',
9
+ flexDirection: 'row',
7
10
  gap: theme.spacing(1),
8
- left: 0,
11
+ left: theme.spacing(1),
9
12
  position: 'absolute',
10
13
  transition: 'top 0.15s ease-out'
11
14
  },
12
15
  minimap: {
13
- '&:hover': {
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
- {showMinimap && (
114
- <g
115
- className={classes.minimap}
116
- clipPath="url(#zoom-clip)"
117
- onMouseLeave={mouseLeavesMinimap}
118
- onMouseOver={mouseEntersMinimap}
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
- <rect
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
- className={classes.actions}
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
- {labels.zoomIn}
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
- {labels.zoomOut}
162
- </Button>
131
+ />
132
+
163
133
  <Button
164
134
  data-testid="clear"
165
135
  size="small"
166
- variant="secondary"
136
+ variant="ghost"
167
137
  onClick={zoom.clear}
168
138
  >
169
139
  {labels.clear}
@@ -0,0 +1,2 @@
1
+ export const radius = 30;
2
+ export const minimapScale = 0.2;
@@ -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
+ };