@centreon/ui 24.4.1-MON-38152-listing.0 → 24.4.1-MON-34377-implement-tree-chart-on-centreon-ui.0

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-38152-listing.0",
3
+ "version": "24.4.1-MON-34377-implement-tree-chart-on-centreon-ui.0",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -114,6 +114,7 @@
114
114
  "@lexical/selection": "^0.12.6",
115
115
  "@lexical/utils": "^0.12.6",
116
116
  "@react-spring/web": "^9.7.3",
117
+ "@visx/clip-path": "^3.3.0",
117
118
  "@visx/curve": "^2.1.0",
118
119
  "@visx/group": "^3.3.0",
119
120
  "@visx/legend": "^3.5.0",
@@ -123,6 +124,7 @@
123
124
  "@visx/text": "^3.3.0",
124
125
  "@visx/threshold": "^2.12.2",
125
126
  "@visx/visx": "2.16.0",
127
+ "@visx/zoom": "^3.3.0",
126
128
  "anylogger": "^1.0.11",
127
129
  "d3-array": "3.2.4",
128
130
  "humanize-duration": "^3.31.0",
@@ -184,6 +184,9 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
184
184
  padding: 0;
185
185
  width: 100%;
186
186
  }
187
+ #root {
188
+ background-color: ${theme.palette.background.paper};
189
+ }
187
190
  @font-face {
188
191
  font-family: 'Roboto';
189
192
  font-style: normal;
@@ -0,0 +1,132 @@
1
+ import Zoom from './Zoom';
2
+
3
+ const content = (
4
+ <g style={{ transform: 'translate(300px, 150px)' }}>
5
+ <circle fill="blue" r={50} stroke="black" />
6
+ </g>
7
+ );
8
+
9
+ const labels = {
10
+ clear: 'Clear',
11
+ zoomIn: '+',
12
+ zoomOut: '-'
13
+ };
14
+
15
+ const initialize = ({ showMinimap }): void => {
16
+ cy.mount({
17
+ Component: (
18
+ <div style={{ height: '400px', width: '100%' }}>
19
+ <Zoom labels={labels} showMinimap={showMinimap}>
20
+ {content}
21
+ </Zoom>
22
+ </div>
23
+ )
24
+ });
25
+ };
26
+
27
+ describe('Zoom', () => {
28
+ it('displays the minimap when the prop is set', () => {
29
+ initialize({ showMinimap: true });
30
+
31
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
32
+
33
+ cy.makeSnapshot();
34
+ });
35
+
36
+ it('zooms in when the corresponding buttom is clicked', () => {
37
+ initialize({ showMinimap: true });
38
+
39
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
40
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
41
+
42
+ cy.contains('+').click();
43
+ cy.findByTestId('zoom-content')
44
+ .should('have.attr', 'transform')
45
+ .and('include', '1.2, 0, 0, 1.2');
46
+
47
+ cy.makeSnapshot();
48
+ });
49
+
50
+ it('zooms out when the corresponding buttom is clicked', () => {
51
+ initialize({ showMinimap: true });
52
+
53
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
54
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
55
+
56
+ cy.contains('-').click();
57
+
58
+ cy.findByTestId('zoom-content')
59
+ .should('have.attr', 'transform')
60
+ .and('include', '0.8, 0, 0, 0.8');
61
+
62
+ cy.makeSnapshot();
63
+ });
64
+
65
+ it('zooms out when the content is scrolled', () => {
66
+ initialize({ showMinimap: true });
67
+
68
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
69
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
70
+
71
+ cy.findByTestId('zoom-content').realMouseWheel({ deltaY: 20 });
72
+
73
+ cy.findByTestId('zoom-content')
74
+ .should('have.attr', 'transform')
75
+ .and('include', '0.9, 0, 0, 0.9');
76
+
77
+ cy.makeSnapshot();
78
+ });
79
+
80
+ it('zooms in when the content is scrolled', () => {
81
+ initialize({ showMinimap: true });
82
+
83
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
84
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
85
+
86
+ cy.findByTestId('zoom-content').realMouseWheel({ deltaY: -20 });
87
+
88
+ cy.findByTestId('zoom-content')
89
+ .should('have.attr', 'transform')
90
+ .and('include', '1.1, 0, 0, 1.1');
91
+
92
+ cy.makeSnapshot();
93
+ });
94
+
95
+ it('clears the zoom when the corresponding button is clicked', () => {
96
+ initialize({ showMinimap: true });
97
+
98
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
99
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
100
+
101
+ cy.findByTestId('zoom-content').realMouseWheel({ deltaY: -20 });
102
+
103
+ cy.findByTestId('zoom-content')
104
+ .should('have.attr', 'transform')
105
+ .and('include', '1.1, 0, 0, 1.1');
106
+
107
+ cy.contains('Clear').click();
108
+
109
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
110
+
111
+ cy.makeSnapshot();
112
+ });
113
+
114
+ it('highlights the minimap when the mouse is over the minimap', () => {
115
+ initialize({ showMinimap: true });
116
+
117
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('be.visible');
118
+
119
+ cy.get('g[clip-path="url(#zoom-clip)"]').realHover();
120
+
121
+ cy.makeSnapshot();
122
+ });
123
+
124
+ it('does not display the minimap the prop is set', () => {
125
+ initialize({ showMinimap: false });
126
+
127
+ cy.get('g[transform="matrix(1, 0, 0, 1, 0, 0)"]');
128
+ cy.get('g[clip-path="url(#zoom-clip)"]').should('not.exist');
129
+
130
+ cy.makeSnapshot();
131
+ });
132
+ });
@@ -0,0 +1,56 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import Zoom, { ZoomProps } from './Zoom';
4
+
5
+ const meta: Meta<typeof Zoom> = {
6
+ argTypes: {
7
+ scaleMax: {
8
+ control: { max: 16, min: 0.4, step: 0.2, type: 'range' }
9
+ },
10
+ scaleMin: {
11
+ control: { max: 16, min: 0.4, step: 0.2, type: 'range' }
12
+ },
13
+ showMinimap: {
14
+ control: 'boolean'
15
+ }
16
+ },
17
+ component: Zoom
18
+ };
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof Zoom>;
22
+
23
+ const content = (
24
+ <g style={{ transform: 'translate(300px, 150px)' }}>
25
+ <circle fill="blue" r={50} stroke="black" />
26
+ </g>
27
+ );
28
+
29
+ const Template = ({ children, ...args }: ZoomProps): JSX.Element => (
30
+ <div style={{ height: '400px', width: '100%' }}>
31
+ <Zoom {...args}>{children}</Zoom>
32
+ </div>
33
+ );
34
+
35
+ const labels = {
36
+ clear: 'Clear',
37
+ zoomIn: '+',
38
+ zoomOut: '-'
39
+ };
40
+
41
+ export const Default: Story = {
42
+ args: {
43
+ children: content,
44
+ labels,
45
+ showMinimap: true
46
+ },
47
+ render: Template
48
+ };
49
+
50
+ export const WithoutMinimap: Story = {
51
+ args: {
52
+ children: content,
53
+ labels
54
+ },
55
+ render: Template
56
+ };
@@ -0,0 +1,33 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useZoomStyles = makeStyles()((theme) => ({
4
+ actions: {
5
+ display: 'flex',
6
+ flexDirection: 'column',
7
+ gap: theme.spacing(1),
8
+ left: 0,
9
+ position: 'absolute',
10
+ transition: 'top 0.15s ease-out'
11
+ },
12
+ minimap: {
13
+ '&:hover': {
14
+ transform: 'scale(0.25)'
15
+ },
16
+ transform: 'scale(0.15)',
17
+ transition: 'transform 0.15s ease-out'
18
+ },
19
+ minimapBackground: {
20
+ fill: theme.palette.background.default,
21
+ stroke: theme.palette.background.default
22
+ },
23
+ minimapZoom: {
24
+ fill: theme.palette.text.primary
25
+ },
26
+ svg: {
27
+ '&[data-is-grabbing="true"]': {
28
+ cursor: 'grabbing'
29
+ },
30
+ cursor: 'grab',
31
+ touchAction: 'none'
32
+ }
33
+ }));
@@ -0,0 +1,180 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+
3
+ import { Zoom as VisxZoom } from '@visx/zoom';
4
+ import { RectClipPath } from '@visx/clip-path';
5
+
6
+ import { ParentSize } from '../..';
7
+ import { Button } from '../Button';
8
+
9
+ import { useZoomStyles } from './Zoom.styles';
10
+
11
+ export interface ZoomProps {
12
+ children: JSX.Element;
13
+ labels: {
14
+ clear: string;
15
+ zoomIn: string;
16
+ zoomOut: string;
17
+ };
18
+ scaleMax?: number;
19
+ scaleMin?: number;
20
+ showMinimap?: boolean;
21
+ }
22
+
23
+ const initialTransform = {
24
+ scaleX: 1,
25
+ scaleY: 1,
26
+ skewX: 0,
27
+ skewY: 0,
28
+ translateX: 0,
29
+ translateY: 0
30
+ };
31
+ const radius = 30;
32
+
33
+ const Zoom = ({
34
+ children,
35
+ scaleMin = 0.5,
36
+ scaleMax = 4,
37
+ showMinimap = false,
38
+ labels
39
+ }: ZoomProps): JSX.Element => {
40
+ const { classes } = useZoomStyles();
41
+
42
+ const [contentClientRect, setContentClientRect] = useState<{
43
+ height: number;
44
+ width: number;
45
+ } | null>(null);
46
+ const [isMouseOverMinimap, setIsMouseOverMinimap] = useState(false);
47
+ const contentRef = useRef<SVGGElement | null>(null);
48
+
49
+ const resizeObserver = new ResizeObserver(() => {
50
+ setContentClientRect({
51
+ height: contentRef.current?.getBoundingClientRect().height || 0,
52
+ width: contentRef.current?.getBoundingClientRect().width || 0
53
+ });
54
+ });
55
+
56
+ const mouseEntersMinimap = (): void => {
57
+ setIsMouseOverMinimap(true);
58
+ };
59
+
60
+ const mouseLeavesMinimap = (): void => {
61
+ setIsMouseOverMinimap(false);
62
+ };
63
+
64
+ useEffect(() => {
65
+ if (contentRef.current) {
66
+ resizeObserver.disconnect();
67
+ setContentClientRect({
68
+ height: contentRef.current?.getBoundingClientRect().height || 0,
69
+ width: contentRef.current?.getBoundingClientRect().width || 0
70
+ });
71
+ resizeObserver.observe(contentRef.current);
72
+ }
73
+
74
+ return () => {
75
+ resizeObserver.disconnect();
76
+ };
77
+ }, [contentRef.current]);
78
+
79
+ return (
80
+ <ParentSize>
81
+ {({ width, height }) => (
82
+ <VisxZoom<SVGSVGElement>
83
+ height={height}
84
+ initialTransformMatrix={initialTransform}
85
+ scaleXMax={scaleMax}
86
+ scaleXMin={scaleMin}
87
+ scaleYMax={scaleMax}
88
+ scaleYMin={scaleMin}
89
+ width={width}
90
+ >
91
+ {(zoom) => (
92
+ <div style={{ position: 'relative' }}>
93
+ <svg
94
+ className={classes.svg}
95
+ data-is-grabbing={zoom.isDragging}
96
+ height={height}
97
+ ref={zoom.containerRef}
98
+ width={width}
99
+ >
100
+ <RectClipPath
101
+ height={Math.max(contentClientRect?.height || 0, height)}
102
+ id="zoom-clip"
103
+ rx={radius}
104
+ width={Math.max(contentClientRect?.width || 0, width)}
105
+ />
106
+ <g
107
+ data-testid="zoom-content"
108
+ ref={contentRef}
109
+ transform={zoom.toString()}
110
+ >
111
+ {children}
112
+ </g>
113
+ {showMinimap && (
114
+ <g
115
+ className={classes.minimap}
116
+ clipPath="url(#zoom-clip)"
117
+ onMouseLeave={mouseLeavesMinimap}
118
+ onMouseOver={mouseEntersMinimap}
119
+ >
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
+ {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>
136
+ )}
137
+ </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
148
+ data-testid="zoom in"
149
+ size="small"
150
+ variant="secondary"
151
+ onClick={() => zoom.scale({ scaleX: 1.2, scaleY: 1.2 })}
152
+ >
153
+ {labels.zoomIn}
154
+ </Button>
155
+ <Button
156
+ data-testid="zoom out"
157
+ size="small"
158
+ variant="secondary"
159
+ onClick={() => zoom.scale({ scaleX: 0.8, scaleY: 0.8 })}
160
+ >
161
+ {labels.zoomOut}
162
+ </Button>
163
+ <Button
164
+ data-testid="clear"
165
+ size="small"
166
+ variant="secondary"
167
+ onClick={zoom.clear}
168
+ >
169
+ {labels.clear}
170
+ </Button>
171
+ </div>
172
+ </div>
173
+ )}
174
+ </VisxZoom>
175
+ )}
176
+ </ParentSize>
177
+ );
178
+ };
179
+
180
+ export default Zoom;
@@ -53,7 +53,7 @@ export const useFullscreen = (): UseFullscreenState => {
53
53
  };
54
54
 
55
55
  const toggleFullscreen = (element: HTMLElement | null): void => {
56
- if (isFullscreenActivated) {
56
+ if (isFullscreenActivated || document.fullscreenElement) {
57
57
  exitFullscreen();
58
58
 
59
59
  return;