@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,246 @@
1
+ import Zoom from './Zoom';
2
+
3
+ const Content = (): JSX.Element => (
4
+ <g style={{ transform: 'translate(300px, 150px)' }}>
5
+ <circle fill="blue" r={50} stroke="black" />
6
+ </g>
7
+ );
8
+
9
+ const ContentWithMultipleShapes = (): JSX.Element => {
10
+ return (
11
+ <g>
12
+ <g style={{ transform: 'translate(300px, 150px)' }}>
13
+ <circle fill="blue" r={50} stroke="black" />
14
+ </g>
15
+ <g style={{ transform: 'translate(600px, 500px)' }}>
16
+ <circle fill="green" r={70} />
17
+ </g>
18
+ <g style={{ transform: 'translate(150px, 600px)' }}>
19
+ <circle fill="red" r={70} />
20
+ </g>
21
+ </g>
22
+ );
23
+ };
24
+ const ContentWithMultipleShapesWithNegativeTranslations = (): JSX.Element => {
25
+ return (
26
+ <g>
27
+ <g style={{ transform: 'translate(-300px, -150px)' }}>
28
+ <circle fill="blue" r={50} stroke="black" />
29
+ </g>
30
+ <g style={{ transform: 'translate(600px, 500px)' }}>
31
+ <circle fill="green" r={70} />
32
+ </g>
33
+ <g style={{ transform: 'translate(150px, 600px)' }}>
34
+ <circle fill="red" r={70} />
35
+ </g>
36
+ </g>
37
+ );
38
+ };
39
+
40
+ interface Props {
41
+ minimapPosition?;
42
+ showMinimap: boolean;
43
+ tenplate?: () => JSX.Element;
44
+ }
45
+
46
+ const initialize = ({
47
+ showMinimap,
48
+ minimapPosition,
49
+ template = Content
50
+ }: Props): void => {
51
+ cy.mount({
52
+ Component: (
53
+ <div style={{ height: '400px', width: '100%' }}>
54
+ <Zoom minimapPosition={minimapPosition} showMinimap={showMinimap}>
55
+ {template}
56
+ </Zoom>
57
+ </div>
58
+ )
59
+ });
60
+ };
61
+
62
+ describe('Zoom', () => {
63
+ it('displays the minimap when the prop is set', () => {
64
+ initialize({ showMinimap: true });
65
+
66
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
67
+
68
+ cy.makeSnapshot();
69
+ });
70
+
71
+ it('zooms in when the corresponding buttom is clicked', () => {
72
+ initialize({ showMinimap: true });
73
+
74
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
75
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
76
+
77
+ cy.findByTestId('zoom in').click();
78
+ cy.findByTestId('zoom-content')
79
+ .should('have.attr', 'transform')
80
+ .and('include', '1.2, 0, 0, 1.2');
81
+
82
+ cy.makeSnapshot();
83
+ });
84
+
85
+ it('zooms out when the corresponding buttom is clicked', () => {
86
+ initialize({ showMinimap: true });
87
+
88
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
89
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
90
+
91
+ cy.findByTestId('zoom out').click();
92
+
93
+ cy.findByTestId('zoom-content')
94
+ .should('have.attr', 'transform')
95
+ .and('include', '0.8, 0, 0, 0.8');
96
+
97
+ cy.makeSnapshot();
98
+ });
99
+
100
+ it('zooms out when the content is scrolled up', () => {
101
+ initialize({ showMinimap: true });
102
+
103
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
104
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
105
+
106
+ cy.findByTestId('zoom-content').realMouseWheel({ deltaY: 20 });
107
+
108
+ cy.findByTestId('zoom-content')
109
+ .should('have.attr', 'transform')
110
+ .and('include', '0.9, 0, 0, 0.9');
111
+
112
+ cy.makeSnapshot();
113
+ });
114
+
115
+ it('zooms in when the content is scrolled down', () => {
116
+ initialize({ showMinimap: true });
117
+
118
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
119
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
120
+
121
+ cy.findByTestId('zoom-content').realMouseWheel({ deltaY: -20 });
122
+
123
+ cy.findByTestId('zoom-content')
124
+ .should('have.attr', 'transform')
125
+ .and('include', '1.1, 0, 0, 1.1');
126
+
127
+ cy.makeSnapshot();
128
+ });
129
+
130
+ it('clears the zoom when the corresponding button is clicked', () => {
131
+ initialize({ showMinimap: true });
132
+
133
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
134
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
135
+
136
+ cy.findByTestId('zoom-content').realMouseWheel({ deltaY: -20 });
137
+
138
+ cy.findByTestId('zoom-content')
139
+ .should('have.attr', 'transform')
140
+ .and('include', '1.1, 0, 0, 1.1');
141
+
142
+ cy.findByTestId('clear').click();
143
+
144
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
145
+
146
+ cy.makeSnapshot();
147
+ });
148
+
149
+ it('does not display the minimap when the prop is set', () => {
150
+ initialize({ showMinimap: false });
151
+
152
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
153
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('not.exist');
154
+
155
+ cy.makeSnapshot();
156
+ });
157
+
158
+ it('zooms in when the minimap is scrolled up', () => {
159
+ initialize({ showMinimap: true });
160
+
161
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
162
+
163
+ cy.findByTestId('minimap-interaction').realMouseWheel({ deltaY: -20 });
164
+
165
+ cy.findByTestId('zoom-content')
166
+ .should('have.attr', 'transform')
167
+ .and('include', '1.1, 0, 0, 1.1');
168
+
169
+ cy.makeSnapshot();
170
+ });
171
+
172
+ it('zooms out when the minimap is scrolled down', () => {
173
+ initialize({ showMinimap: true });
174
+
175
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
176
+
177
+ cy.findByTestId('minimap-interaction').realMouseWheel({ deltaY: 20 });
178
+
179
+ cy.findByTestId('zoom-content')
180
+ .should('have.attr', 'transform')
181
+ .and('include', '0.9, 0, 0, 0.9');
182
+
183
+ cy.makeSnapshot();
184
+ });
185
+
186
+ it('moves the view when the mouse is hover the content with the corresponding button pressed down', () => {
187
+ initialize({ showMinimap: true });
188
+
189
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
190
+ cy.get('svg').should('have.attr', 'height', '400');
191
+
192
+ cy.findByTestId('zoom-container')
193
+ .trigger('mousedown', 400, 200)
194
+ .trigger('mousemove', 600, 200);
195
+
196
+ cy.findByTestId('zoom-content').should(
197
+ 'have.attr',
198
+ 'transform',
199
+ 'matrix(1, 0, 0, 1, 0, 0)'
200
+ );
201
+
202
+ cy.makeSnapshot();
203
+ });
204
+
205
+ it('displays the minimap in the bottom right when the prop to the corresponding value', () => {
206
+ initialize({ minimapPosition: 'bottom-right', showMinimap: true });
207
+
208
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
209
+ cy.get('svg').should('have.attr', 'height', '400');
210
+
211
+ cy.makeSnapshot();
212
+ });
213
+
214
+ it('applies a scale down on the minimap when the content is higher than the original height', () => {
215
+ initialize({ showMinimap: true, template: ContentWithMultipleShapes });
216
+
217
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
218
+ cy.get('svg').should('have.attr', 'height', '400');
219
+
220
+ cy.findByTestId('minimap-interaction')
221
+ .parent()
222
+ .find('g')
223
+ .should('have.attr', 'style')
224
+ .and('include', 'transform: scale(0.684211) translate(-85px, -105px);');
225
+
226
+ cy.makeSnapshot();
227
+ });
228
+
229
+ it('applies a scale down on the minimap when the content has negative translation values', () => {
230
+ initialize({
231
+ showMinimap: true,
232
+ template: ContentWithMultipleShapesWithNegativeTranslations
233
+ });
234
+
235
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
236
+ cy.get('svg').should('have.attr', 'height', '400');
237
+
238
+ cy.findByTestId('minimap-interaction')
239
+ .parent()
240
+ .find('g')
241
+ .should('have.attr', 'style')
242
+ .and('include', 'transform: scale(0.448276) translate(345px, 195px);');
243
+
244
+ cy.makeSnapshot();
245
+ });
246
+ });
@@ -0,0 +1,115 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import Zoom, { ZoomProps } from './Zoom';
4
+
5
+ const meta: Meta<typeof Zoom> = {
6
+ argTypes: {
7
+ minimapPosition: {
8
+ control: 'select',
9
+ options: ['top-left', 'top-right', 'bottom-left', 'bottom-right']
10
+ },
11
+ scaleMax: {
12
+ control: { max: 16, min: 0.4, step: 0.2, type: 'range' }
13
+ },
14
+ scaleMin: {
15
+ control: { max: 16, min: 0.4, step: 0.2, type: 'range' }
16
+ },
17
+ showMinimap: {
18
+ control: 'boolean'
19
+ }
20
+ },
21
+ component: Zoom
22
+ };
23
+
24
+ export default meta;
25
+ type Story = StoryObj<typeof Zoom>;
26
+
27
+ const Content = (): JSX.Element => {
28
+ return (
29
+ <g style={{ transform: 'translate(-150px, -100px)' }}>
30
+ <g style={{ transform: 'translate(300px, 150px)' }}>
31
+ <circle fill="blue" r={50} stroke="black" />
32
+ </g>
33
+ <g style={{ transform: 'translate(600px, 400px)' }}>
34
+ <circle fill="green" r={70} />
35
+ </g>
36
+ <g style={{ transform: `translate(2400px, 1400px)` }}>
37
+ <circle fill="red" r={70} />
38
+ </g>
39
+ </g>
40
+ );
41
+ };
42
+
43
+ const Template = ({ children, ...args }: ZoomProps): JSX.Element => (
44
+ <div style={{ height: '400px', width: '100%' }}>
45
+ <Zoom {...args}>{children}</Zoom>
46
+ </div>
47
+ );
48
+
49
+ const TemplateResponsive = ({
50
+ redCircleXPosition,
51
+ redCircleYPosition,
52
+ ...args
53
+ }: ZoomProps): JSX.Element => (
54
+ <div style={{ height: '90vh', width: '100%' }}>
55
+ <Zoom {...args}>
56
+ {() => (
57
+ <g>
58
+ <g style={{ transform: 'translate(500px, 100px)' }}>
59
+ <circle fill="blue" r={50} stroke="black" />
60
+ </g>
61
+ <g style={{ transform: 'translate(600px, 400px)' }}>
62
+ <circle fill="green" r={70} />
63
+ </g>
64
+ <g
65
+ style={{
66
+ transform: `translate(${redCircleXPosition || 100}px, ${redCircleYPosition || 100}px)`
67
+ }}
68
+ >
69
+ <circle fill="red" r={70} />
70
+ </g>
71
+ </g>
72
+ )}
73
+ </Zoom>
74
+ </div>
75
+ );
76
+
77
+ export const WithoutMinimap: Story = {
78
+ args: {
79
+ children: Content
80
+ },
81
+ render: Template
82
+ };
83
+
84
+ export const WithMinimap: Story = {
85
+ args: {
86
+ children: Content,
87
+ showMinimap: true
88
+ },
89
+ render: Template
90
+ };
91
+
92
+ export const WithMinimapPosition: Story = {
93
+ args: {
94
+ children: Content,
95
+ minimapPosition: 'bottom-right',
96
+ showMinimap: true
97
+ },
98
+ render: Template
99
+ };
100
+
101
+ export const Playground: Story = {
102
+ argTypes: {
103
+ redCircleXPosition: {
104
+ control: { max: 2600, min: 400, step: 10, type: 'range' }
105
+ },
106
+ redCircleYPosition: {
107
+ control: { max: 2600, min: 400, step: 10, type: 'range' }
108
+ }
109
+ },
110
+ args: {
111
+ children: Content,
112
+ showMinimap: true
113
+ },
114
+ render: TemplateResponsive
115
+ };
@@ -0,0 +1,68 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ import { alpha } from '@mui/system';
4
+
5
+ import { minimapScale } from './constants';
6
+
7
+ export const useZoomStyles = makeStyles()((theme) => ({
8
+ actions: {
9
+ backgroundColor: alpha(theme.palette.background.paper, 0.8),
10
+ display: 'flex',
11
+ flexDirection: 'row'
12
+ },
13
+ actionsAndZoom: {
14
+ '&[data-position="bottom-left"]': {
15
+ alignItems: 'flex-start',
16
+ bottom: 0,
17
+ flexDirection: 'column-reverse',
18
+ left: 0
19
+ },
20
+ '&[data-position="bottom-right"]': {
21
+ alignItems: 'flex-end',
22
+ bottom: 0,
23
+ flexDirection: 'column-reverse',
24
+ right: 0
25
+ },
26
+ '&[data-position="top-left"]': {
27
+ alignItems: 'flex-start',
28
+ flexDirection: 'column',
29
+ left: 0,
30
+ top: 0
31
+ },
32
+ '&[data-position="top-right"]': {
33
+ alignItems: 'flex-end',
34
+ flexDirection: 'column',
35
+ right: 0,
36
+ top: 0
37
+ },
38
+ display: 'flex',
39
+ position: 'absolute',
40
+ width: 'fit-content'
41
+ },
42
+ minimap: {
43
+ transform: `scale(${minimapScale})`
44
+ },
45
+ minimapBackground: {
46
+ fill: theme.palette.background.paper,
47
+ stroke: theme.palette.background.paper
48
+ },
49
+ minimapContainer: {
50
+ border: `1px solid ${theme.palette.divider}`,
51
+ borderRadius: theme.shape.borderRadius
52
+ },
53
+ minimapZoom: {
54
+ fill: theme.palette.primary.main,
55
+ stroke: theme.palette.primary.main,
56
+ strokeWidth: 10
57
+ },
58
+ movingZone: {
59
+ transition: 'transform 0.1s linear'
60
+ },
61
+ svg: {
62
+ '&[data-is-grabbing="true"]': {
63
+ cursor: 'grabbing'
64
+ },
65
+ cursor: 'grab',
66
+ touchAction: 'none'
67
+ }
68
+ }));
@@ -0,0 +1,61 @@
1
+ import { Zoom as VisxZoom } from '@visx/zoom';
2
+
3
+ import { ParentSize } from '../..';
4
+
5
+ import ZoomContent from './ZoomContent';
6
+ import { MinimapPosition } from './models';
7
+
8
+ export interface ZoomProps {
9
+ children: JSX.Element | (({ width, height }) => JSX.Element);
10
+ minimapPosition?: MinimapPosition;
11
+ scaleMax?: number;
12
+ scaleMin?: number;
13
+ showMinimap?: boolean;
14
+ }
15
+
16
+ const initialTransform = {
17
+ scaleX: 1,
18
+ scaleY: 1,
19
+ skewX: 0,
20
+ skewY: 0,
21
+ translateX: 0,
22
+ translateY: 0
23
+ };
24
+
25
+ const Zoom = ({
26
+ children,
27
+ scaleMin = 0.5,
28
+ scaleMax = 4,
29
+ showMinimap = false,
30
+ minimapPosition = 'top-left'
31
+ }: ZoomProps): JSX.Element => {
32
+ return (
33
+ <ParentSize>
34
+ {({ width, height }) => (
35
+ <VisxZoom<SVGSVGElement>
36
+ height={height}
37
+ initialTransformMatrix={initialTransform}
38
+ scaleXMax={scaleMax}
39
+ scaleXMin={scaleMin}
40
+ scaleYMax={scaleMax}
41
+ scaleYMin={scaleMin}
42
+ width={width}
43
+ >
44
+ {(zoom) => (
45
+ <ZoomContent
46
+ height={height}
47
+ minimapPosition={minimapPosition}
48
+ showMinimap={showMinimap}
49
+ width={width}
50
+ zoom={zoom}
51
+ >
52
+ {children}
53
+ </ZoomContent>
54
+ )}
55
+ </VisxZoom>
56
+ )}
57
+ </ParentSize>
58
+ );
59
+ };
60
+
61
+ export default Zoom;
@@ -0,0 +1,167 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import { RectClipPath } from '@visx/clip-path';
4
+ import { ProvidedZoom } from '@visx/zoom/lib/types';
5
+
6
+ import ZoomInIcon from '@mui/icons-material/Add';
7
+ import ZoomOutIcon from '@mui/icons-material/Remove';
8
+ import ReplayIcon from '@mui/icons-material/Replay';
9
+
10
+ import { IconButton } from '../Button';
11
+
12
+ import { minimapScale, radius } from './constants';
13
+ import { useZoom } from './useZoom';
14
+ import { useZoomStyles } from './Zoom.styles';
15
+ import Minimap from './Minimap';
16
+ import { ChildrenProps, MinimapPosition, ZoomState } from './models';
17
+
18
+ export interface Props {
19
+ children: ({ width, height, transformMatrix }: ChildrenProps) => JSX.Element;
20
+ height: number;
21
+ minimapPosition: MinimapPosition;
22
+ showMinimap?: boolean;
23
+ width: number;
24
+ zoom: ProvidedZoom<SVGSVGElement> & ZoomState;
25
+ }
26
+
27
+ const ZoomContent = ({
28
+ zoom,
29
+ width,
30
+ height,
31
+ children,
32
+ showMinimap,
33
+ minimapPosition
34
+ }: Props): JSX.Element => {
35
+ const { classes } = useZoomStyles();
36
+ const contentRef = useRef<SVGGElement | null>(null);
37
+ const minimapSvgRef = useRef<SVGSVGElement | null>(null);
38
+ const minimapContentRef = useRef<SVGSVGElement | null>(null);
39
+ const [contentClientRect, setContentClientRect] = useState<{
40
+ height: number;
41
+ width: number;
42
+ } | null>(null);
43
+
44
+ const resizeObserver = new ResizeObserver(() => {
45
+ const contentBoundingClientRect = (
46
+ contentRef.current as SVGGElement
47
+ ).getBoundingClientRect();
48
+
49
+ setContentClientRect({
50
+ height: contentBoundingClientRect.height,
51
+ width: contentBoundingClientRect.width
52
+ });
53
+ });
54
+
55
+ useEffect(() => {
56
+ if (contentRef.current) {
57
+ resizeObserver.disconnect();
58
+ resizeObserver.observe(contentRef.current);
59
+ }
60
+
61
+ return () => {
62
+ resizeObserver.disconnect();
63
+ };
64
+ }, [contentRef.current]);
65
+
66
+ const { move, dragEnd, dragStart, isDragging } = useZoom();
67
+
68
+ const diffBetweenContentAndSvg = minimapSvgRef.current &&
69
+ minimapContentRef.current && {
70
+ left:
71
+ minimapContentRef.current.getBoundingClientRect().left -
72
+ minimapSvgRef.current.getBoundingClientRect().left,
73
+ top:
74
+ minimapContentRef.current.getBoundingClientRect().top -
75
+ minimapSvgRef.current.getBoundingClientRect().top
76
+ };
77
+
78
+ return (
79
+ <div style={{ position: 'relative' }}>
80
+ <svg
81
+ className={classes.svg}
82
+ data-is-grabbing={isDragging}
83
+ data-testid="zoom-container"
84
+ height={height}
85
+ width={width}
86
+ onMouseDown={dragStart(zoom)}
87
+ onMouseEnter={dragStart(zoom)}
88
+ onMouseLeave={dragEnd}
89
+ onMouseMove={move(zoom)}
90
+ onMouseUp={dragEnd}
91
+ onWheel={zoom.handleWheel}
92
+ >
93
+ <RectClipPath
94
+ height={Math.max(contentClientRect?.height || 0, height)}
95
+ id="zoom-clip"
96
+ rx={radius}
97
+ width={Math.max(contentClientRect?.width || 0, width)}
98
+ />
99
+ <g
100
+ data-testid="zoom-content"
101
+ ref={contentRef}
102
+ transform={zoom.toString()}
103
+ >
104
+ {children({
105
+ contentClientRect,
106
+ height,
107
+ transformMatrix: zoom.transformMatrix,
108
+ width
109
+ })}
110
+ </g>
111
+ </svg>
112
+ <div className={classes.actionsAndZoom} data-position={minimapPosition}>
113
+ {showMinimap && contentClientRect && (
114
+ <svg
115
+ className={classes.minimapContainer}
116
+ data-testid="minimap"
117
+ height={height * minimapScale}
118
+ ref={minimapSvgRef}
119
+ width={width * minimapScale}
120
+ >
121
+ <Minimap
122
+ contentClientRect={contentClientRect}
123
+ diffBetweenContentAndSvg={
124
+ diffBetweenContentAndSvg || { left: 0, top: 0 }
125
+ }
126
+ height={height}
127
+ isDraggingFromContainer={isDragging}
128
+ width={width}
129
+ zoom={zoom}
130
+ >
131
+ <g ref={minimapContentRef}>
132
+ {children({
133
+ contentClientRect,
134
+ height,
135
+ transformMatrix: zoom.transformMatrix,
136
+ width
137
+ })}
138
+ </g>
139
+ </Minimap>
140
+ </svg>
141
+ )}
142
+ <div className={classes.actions}>
143
+ <IconButton
144
+ data-testid="zoom in"
145
+ icon={<ZoomInIcon />}
146
+ size="small"
147
+ onClick={() => zoom.scale({ scaleX: 1.2, scaleY: 1.2 })}
148
+ />
149
+ <IconButton
150
+ data-testid="zoom out"
151
+ icon={<ZoomOutIcon />}
152
+ size="small"
153
+ onClick={() => zoom.scale({ scaleX: 0.8, scaleY: 0.8 })}
154
+ />
155
+ <IconButton
156
+ data-testid="clear"
157
+ icon={<ReplayIcon />}
158
+ size="small"
159
+ onClick={zoom.reset}
160
+ />
161
+ </div>
162
+ </div>
163
+ </div>
164
+ );
165
+ };
166
+
167
+ export default ZoomContent;
@@ -0,0 +1,2 @@
1
+ export const radius = 16;
2
+ export const minimapScale = 0.2;