@centreon/ui 23.10.65-develop.0 → 23.10.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.
- package/package.json +1 -1
- package/src/Dashboard/Dashboard.styles.ts +6 -1
- package/src/Dashboard/Item.tsx +4 -1
- package/src/Graph/Gauge/Gauge.stories.tsx +10 -0
- package/src/Graph/Gauge/Gauge.tsx +10 -1
- package/src/Graph/Gauge/PieData.tsx +8 -8
- package/src/Graph/Gauge/ResponsiveGauge.tsx +32 -23
- package/src/Graph/Gauge/Thresholds.tsx +2 -25
- package/src/Graph/Gauge/models.ts +1 -0
- package/src/Graph/SingleBar/ResponsiveSingleBar.tsx +15 -9
- package/src/Graph/SingleBar/SingleBar.stories.tsx +10 -0
- package/src/Graph/SingleBar/ThresholdLine.tsx +3 -5
- package/src/Graph/SingleBar/models.ts +2 -0
- package/src/Graph/Text/Text.stories.tsx +13 -0
- package/src/Graph/Text/Text.tsx +19 -12
- package/src/Graph/common/timeSeries/index.test.ts +106 -3
- package/src/Graph/common/timeSeries/index.ts +67 -4
- package/src/Graph/common/timeSeries/models.ts +4 -1
- package/src/Graph/common/utils.ts +4 -2
- package/src/InputField/Select/Autocomplete/Multi/index.tsx +4 -2
- package/src/InputField/Select/Autocomplete/index.tsx +3 -1
- package/src/InputField/Text/index.tsx +10 -7
- package/src/RichTextEditor/ContentEditable.tsx +0 -1
- package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +5 -13
- package/src/RichTextEditor/RichTextEditor.tsx +5 -2
- package/src/RichTextEditor/plugins/AutoLinkPlugin/index.tsx +40 -1
- package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +33 -8
- package/src/RichTextEditor/plugins/ToolbarPlugin/index.tsx +10 -4
- package/src/ThemeProvider/index.tsx +6 -4
- package/src/api/useFetchQuery/index.ts +10 -8
- package/src/components/Inputs/Switch/Switch.stories.tsx +22 -0
- package/src/components/Inputs/Switch/Switch.styles.ts +13 -0
- package/src/components/Inputs/Switch/Switch.tsx +18 -0
- package/src/components/Inputs/index.ts +1 -0
- package/src/components/Modal/Modal.styles.ts +3 -1
- package/src/components/index.ts +1 -0
package/package.json
CHANGED
|
@@ -79,7 +79,12 @@ export const useDashboardItemStyles = makeStyles<{ hasHeader: boolean }>()(
|
|
|
79
79
|
'&:hover': {
|
|
80
80
|
backgroundColor: theme.palette.action.hover
|
|
81
81
|
},
|
|
82
|
-
|
|
82
|
+
'&[data-canMove="false"]': {
|
|
83
|
+
cursor: 'default'
|
|
84
|
+
},
|
|
85
|
+
'&[data-canMove="true"]': {
|
|
86
|
+
cursor: 'move'
|
|
87
|
+
},
|
|
83
88
|
padding: theme.spacing(0.5, 2)
|
|
84
89
|
},
|
|
85
90
|
widgetPadding: {
|
package/src/Dashboard/Item.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { useMemoComponent } from '../utils';
|
|
|
15
15
|
import { useDashboardItemStyles } from './Dashboard.styles';
|
|
16
16
|
|
|
17
17
|
interface DashboardItemProps {
|
|
18
|
+
canMove?: boolean;
|
|
18
19
|
children: ReactElement;
|
|
19
20
|
className?: string;
|
|
20
21
|
disablePadding?: boolean;
|
|
@@ -37,7 +38,8 @@ const Item = forwardRef(
|
|
|
37
38
|
onMouseUp,
|
|
38
39
|
onTouchEnd,
|
|
39
40
|
id,
|
|
40
|
-
disablePadding = false
|
|
41
|
+
disablePadding = false,
|
|
42
|
+
canMove = false
|
|
41
43
|
}: DashboardItemProps,
|
|
42
44
|
ref: ForwardedRef<HTMLDivElement>
|
|
43
45
|
): ReactElement => {
|
|
@@ -66,6 +68,7 @@ const Item = forwardRef(
|
|
|
66
68
|
<div
|
|
67
69
|
{...listeners}
|
|
68
70
|
className={classes.widgetHeader}
|
|
71
|
+
data-canMove={canMove}
|
|
69
72
|
data-testid={`${id}_move_panel`}
|
|
70
73
|
>
|
|
71
74
|
{header}
|
|
@@ -134,3 +134,13 @@ export const withRangeThresholds: Story = {
|
|
|
134
134
|
},
|
|
135
135
|
render: Template
|
|
136
136
|
};
|
|
137
|
+
|
|
138
|
+
export const RawValue: Story = {
|
|
139
|
+
args: {
|
|
140
|
+
data: dataLastWeek,
|
|
141
|
+
displayAsRaw: true,
|
|
142
|
+
thresholdTooltipLabels: ['Warning', 'Critical'],
|
|
143
|
+
thresholds: [0.5, 0.6]
|
|
144
|
+
},
|
|
145
|
+
render: Template
|
|
146
|
+
};
|
|
@@ -7,11 +7,18 @@ import { Metric } from '../common/timeSeries/models';
|
|
|
7
7
|
import ResponsiveGauge from './ResponsiveGauge';
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
|
+
baseColor?: string;
|
|
10
11
|
data?: LineChartData;
|
|
12
|
+
displayAsRaw?: boolean;
|
|
11
13
|
thresholds: Thresholds;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
export const Gauge = ({
|
|
16
|
+
export const Gauge = ({
|
|
17
|
+
thresholds,
|
|
18
|
+
data,
|
|
19
|
+
displayAsRaw,
|
|
20
|
+
baseColor
|
|
21
|
+
}: Props): JSX.Element | null => {
|
|
15
22
|
if (!data) {
|
|
16
23
|
return null;
|
|
17
24
|
}
|
|
@@ -22,6 +29,8 @@ export const Gauge = ({ thresholds, data }: Props): JSX.Element | null => {
|
|
|
22
29
|
<Responsive.ParentSize>
|
|
23
30
|
{({ width, height }) => (
|
|
24
31
|
<ResponsiveGauge
|
|
32
|
+
baseColor={baseColor}
|
|
33
|
+
displayAsRaw={displayAsRaw}
|
|
25
34
|
height={height}
|
|
26
35
|
metric={metric}
|
|
27
36
|
thresholds={thresholds}
|
|
@@ -16,7 +16,8 @@ const PieData = ({
|
|
|
16
16
|
metric,
|
|
17
17
|
adaptedMaxValue,
|
|
18
18
|
thresholds,
|
|
19
|
-
radius
|
|
19
|
+
radius,
|
|
20
|
+
baseColor
|
|
20
21
|
}: Omit<
|
|
21
22
|
GaugeProps,
|
|
22
23
|
'width' | 'height' | 'showTooltip' | 'hideTooltip' | 'thresholdTooltipLabels'
|
|
@@ -24,13 +25,12 @@ const PieData = ({
|
|
|
24
25
|
const theme = useTheme();
|
|
25
26
|
|
|
26
27
|
const pieData = [metric.data[0], adaptedMaxValue - metric.data[0]];
|
|
27
|
-
const pieColor =
|
|
28
|
-
|
|
29
|
-
:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
28
|
+
const pieColor = getColorFromDataAndTresholds({
|
|
29
|
+
baseColor,
|
|
30
|
+
data: metric.data[0],
|
|
31
|
+
theme,
|
|
32
|
+
thresholds
|
|
33
|
+
});
|
|
34
34
|
|
|
35
35
|
const getDataColor = scaleOrdinal({
|
|
36
36
|
domain: pieData,
|
|
@@ -7,7 +7,7 @@ import { Tooltip } from '@visx/visx';
|
|
|
7
7
|
import { Box, Fade, useTheme } from '@mui/material';
|
|
8
8
|
|
|
9
9
|
import { Metric } from '../common/timeSeries/models';
|
|
10
|
-
import {
|
|
10
|
+
import { formatMetricValueWithUnit } from '../common/timeSeries';
|
|
11
11
|
import { getColorFromDataAndTresholds } from '../common/utils';
|
|
12
12
|
import { margins } from '../common/margins';
|
|
13
13
|
|
|
@@ -15,7 +15,8 @@ import Thresholds from './Thresholds';
|
|
|
15
15
|
import PieData from './PieData';
|
|
16
16
|
import { GaugeProps } from './models';
|
|
17
17
|
|
|
18
|
-
interface Props extends Pick<GaugeProps, 'thresholds'> {
|
|
18
|
+
interface Props extends Pick<GaugeProps, 'thresholds' | 'baseColor'> {
|
|
19
|
+
displayAsRaw?: boolean;
|
|
19
20
|
height: number;
|
|
20
21
|
metric: Metric;
|
|
21
22
|
width: number;
|
|
@@ -30,7 +31,9 @@ const ResponsiveGauge = ({
|
|
|
30
31
|
width,
|
|
31
32
|
height,
|
|
32
33
|
thresholds,
|
|
33
|
-
metric
|
|
34
|
+
metric,
|
|
35
|
+
displayAsRaw,
|
|
36
|
+
baseColor
|
|
34
37
|
}: Props): JSX.Element => {
|
|
35
38
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
36
39
|
|
|
@@ -50,28 +53,36 @@ const ResponsiveGauge = ({
|
|
|
50
53
|
const centerY = innerHeight / 2;
|
|
51
54
|
const centerX = innerWidth / 2;
|
|
52
55
|
const radius = Math.min(innerWidth, innerHeight) / 2;
|
|
53
|
-
const thresholdValues =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const thresholdValues = thresholds.enabled
|
|
57
|
+
? flatten([
|
|
58
|
+
pluck('value', thresholds.warning),
|
|
59
|
+
pluck('value', thresholds.critical)
|
|
60
|
+
])
|
|
61
|
+
: [0];
|
|
57
62
|
const adaptedMaxValue = Math.max(
|
|
58
63
|
metric.maximum_value || 0,
|
|
59
64
|
Math.max(...thresholdValues) * 1.1,
|
|
60
65
|
head(metric.data) as number
|
|
61
66
|
);
|
|
62
67
|
|
|
63
|
-
const pieColor =
|
|
64
|
-
|
|
65
|
-
:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
68
|
+
const pieColor = getColorFromDataAndTresholds({
|
|
69
|
+
baseColor,
|
|
70
|
+
data: metric.data[0],
|
|
71
|
+
theme,
|
|
72
|
+
thresholds
|
|
73
|
+
});
|
|
70
74
|
|
|
71
75
|
const svgTop = svgRef.current?.getBoundingClientRect().top || 0;
|
|
72
76
|
const svgLeft = svgRef.current?.getBoundingClientRect().left || 0;
|
|
73
77
|
|
|
74
|
-
const
|
|
78
|
+
const isSmallWidget = height < 240;
|
|
79
|
+
|
|
80
|
+
const gaugeValue = formatMetricValueWithUnit({
|
|
81
|
+
base: 1000,
|
|
82
|
+
isRaw: displayAsRaw,
|
|
83
|
+
unit: metric.unit,
|
|
84
|
+
value: metric.data[0]
|
|
85
|
+
});
|
|
75
86
|
|
|
76
87
|
return (
|
|
77
88
|
<Box sx={{ position: 'relative' }}>
|
|
@@ -87,6 +98,7 @@ const ResponsiveGauge = ({
|
|
|
87
98
|
/>
|
|
88
99
|
<PieData
|
|
89
100
|
adaptedMaxValue={adaptedMaxValue}
|
|
101
|
+
baseColor={baseColor}
|
|
90
102
|
metric={metric}
|
|
91
103
|
radius={radius}
|
|
92
104
|
thresholds={thresholds}
|
|
@@ -97,18 +109,15 @@ const ResponsiveGauge = ({
|
|
|
97
109
|
style={{
|
|
98
110
|
fill: pieColor,
|
|
99
111
|
...theme.typography.h3,
|
|
100
|
-
fontSize:
|
|
112
|
+
fontSize:
|
|
113
|
+
Math.min(width, height) / 7 -
|
|
114
|
+
(isSmallWidget ? 0 : (gaugeValue?.length || 0) * 2)
|
|
101
115
|
}}
|
|
102
116
|
textAnchor="middle"
|
|
103
117
|
x="50%"
|
|
104
|
-
y={
|
|
118
|
+
y={isSmallWidget ? 130 : height - height / 2.3}
|
|
105
119
|
>
|
|
106
|
-
{
|
|
107
|
-
base: 1000,
|
|
108
|
-
unit: metric.unit,
|
|
109
|
-
value: metric.data[0]
|
|
110
|
-
})}{' '}
|
|
111
|
-
{metric.unit}
|
|
120
|
+
{gaugeValue}
|
|
112
121
|
</text>
|
|
113
122
|
</svg>
|
|
114
123
|
<Fade in={tooltipOpen && thresholds.enabled}>
|
|
@@ -16,7 +16,7 @@ const Thresholds = ({
|
|
|
16
16
|
showTooltip,
|
|
17
17
|
hideTooltip,
|
|
18
18
|
metric
|
|
19
|
-
}: Omit<GaugeProps, 'width' | 'height'>): JSX.Element => {
|
|
19
|
+
}: Omit<GaugeProps, 'width' | 'height'>): JSX.Element | null => {
|
|
20
20
|
const theme = useTheme();
|
|
21
21
|
|
|
22
22
|
const criticalThresholdValues = pluck('value', thresholds.critical);
|
|
@@ -65,30 +65,7 @@ const Thresholds = ({
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
if (!thresholds.enabled) {
|
|
68
|
-
return
|
|
69
|
-
<Pie
|
|
70
|
-
data={successThresholdArc}
|
|
71
|
-
endAngle={-Math.PI / 2}
|
|
72
|
-
innerRadius={radius - thresholdThickness}
|
|
73
|
-
outerRadius={radius}
|
|
74
|
-
pieSortValues={() => -1}
|
|
75
|
-
pieValue={(d) => d.value}
|
|
76
|
-
startAngle={Math.PI / 2}
|
|
77
|
-
>
|
|
78
|
-
{(pie) => (
|
|
79
|
-
<AnimatedPie<{ name: string; value: number }>
|
|
80
|
-
{...pie}
|
|
81
|
-
animate
|
|
82
|
-
getColor={(arc) => successThresholdScaleOrdinal(arc.data.name)}
|
|
83
|
-
getKey={(arc) => `${arc.data.name}_${arc.data.value}`}
|
|
84
|
-
hideTooltip={hideTooltip}
|
|
85
|
-
metric={metric}
|
|
86
|
-
showTooltip={showTooltip}
|
|
87
|
-
thresholds={thresholds}
|
|
88
|
-
/>
|
|
89
|
-
)}
|
|
90
|
-
</Pie>
|
|
91
|
-
);
|
|
68
|
+
return null;
|
|
92
69
|
}
|
|
93
70
|
|
|
94
71
|
const arcs = [
|
|
@@ -9,7 +9,7 @@ import { useSpring, animated } from '@react-spring/web';
|
|
|
9
9
|
import { alpha, Box, Fade, useTheme } from '@mui/material';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
formatMetricValueWithUnit,
|
|
13
13
|
getMetricWithLatestData
|
|
14
14
|
} from '../common/timeSeries';
|
|
15
15
|
import { Metric } from '../common/timeSeries/models';
|
|
@@ -34,16 +34,21 @@ const ResponsiveSingleBar = ({
|
|
|
34
34
|
data,
|
|
35
35
|
thresholds,
|
|
36
36
|
width,
|
|
37
|
-
height
|
|
37
|
+
height,
|
|
38
|
+
displayAsRaw,
|
|
39
|
+
baseColor
|
|
38
40
|
}: Props): JSX.Element => {
|
|
39
41
|
const theme = useTheme();
|
|
40
42
|
|
|
41
43
|
const metric = getMetricWithLatestData(data) as Metric;
|
|
42
44
|
const latestMetricData = head(metric.data) as number;
|
|
43
|
-
const thresholdValues =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const thresholdValues = thresholds.enabled
|
|
46
|
+
? flatten([
|
|
47
|
+
pluck('value', thresholds.warning),
|
|
48
|
+
pluck('value', thresholds.critical)
|
|
49
|
+
])
|
|
50
|
+
: [0];
|
|
51
|
+
|
|
47
52
|
const adaptedMaxValue = Math.max(
|
|
48
53
|
metric.maximum_value || 0,
|
|
49
54
|
Math.max(...thresholdValues) * 1.1,
|
|
@@ -66,6 +71,7 @@ const ResponsiveSingleBar = ({
|
|
|
66
71
|
const barColor = useMemo(
|
|
67
72
|
() =>
|
|
68
73
|
getColorFromDataAndTresholds({
|
|
74
|
+
baseColor,
|
|
69
75
|
data: latestMetricData,
|
|
70
76
|
theme,
|
|
71
77
|
thresholds
|
|
@@ -81,12 +87,12 @@ const ResponsiveSingleBar = ({
|
|
|
81
87
|
x="50%"
|
|
82
88
|
y={25}
|
|
83
89
|
>
|
|
84
|
-
{
|
|
90
|
+
{formatMetricValueWithUnit({
|
|
85
91
|
base: 1000,
|
|
92
|
+
isRaw: displayAsRaw,
|
|
86
93
|
unit: metric.unit,
|
|
87
94
|
value: metric.data[0]
|
|
88
|
-
})}
|
|
89
|
-
{metric.unit}
|
|
95
|
+
})}
|
|
90
96
|
</text>
|
|
91
97
|
);
|
|
92
98
|
|
|
@@ -156,3 +156,13 @@ export const rangeThresholds: Story = {
|
|
|
156
156
|
},
|
|
157
157
|
render: Template
|
|
158
158
|
};
|
|
159
|
+
|
|
160
|
+
export const RawValue: Story = {
|
|
161
|
+
args: {
|
|
162
|
+
data: dataLastWeek,
|
|
163
|
+
displayAsRaw: true,
|
|
164
|
+
thresholdTooltipLabels: ['Warning', 'Critical'],
|
|
165
|
+
thresholds: [0.5, 1.5]
|
|
166
|
+
},
|
|
167
|
+
render: Template
|
|
168
|
+
};
|
|
@@ -4,13 +4,11 @@ import { useTheme } from '@mui/material';
|
|
|
4
4
|
|
|
5
5
|
import { groupMargin } from './Thresholds';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const thresholdLineHeight = 60 + 2 * lineMargin;
|
|
10
|
-
|
|
7
|
+
export const barHeight = 72;
|
|
11
8
|
export const margin = 40;
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
const lineMargin = 10;
|
|
11
|
+
const thresholdLineHeight = barHeight + 2 * lineMargin;
|
|
14
12
|
|
|
15
13
|
interface Props {
|
|
16
14
|
hideTooltip: () => void;
|
|
@@ -52,3 +52,16 @@ export const critical: Story = {
|
|
|
52
52
|
},
|
|
53
53
|
render: Template
|
|
54
54
|
};
|
|
55
|
+
|
|
56
|
+
export const rawValue: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
data: dataLastWeek,
|
|
59
|
+
displayAsRaw: true,
|
|
60
|
+
labels: {
|
|
61
|
+
critical: 'Critical',
|
|
62
|
+
warning: 'Warning'
|
|
63
|
+
},
|
|
64
|
+
thresholds: [0.5, 1.5]
|
|
65
|
+
},
|
|
66
|
+
render: Template
|
|
67
|
+
};
|
package/src/Graph/Text/Text.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Typography, useTheme } from '@mui/material';
|
|
|
4
4
|
|
|
5
5
|
import { LineChartData, Thresholds } from '../common/models';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
formatMetricValueWithUnit,
|
|
8
8
|
getMetricWithLatestData
|
|
9
9
|
} from '../common/timeSeries';
|
|
10
10
|
import { getColorFromDataAndTresholds } from '../common/utils';
|
|
@@ -12,7 +12,9 @@ import { getColorFromDataAndTresholds } from '../common/utils';
|
|
|
12
12
|
import { useTextStyles } from './Text.styles';
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
|
+
baseColor?: string;
|
|
15
16
|
data?: LineChartData;
|
|
17
|
+
displayAsRaw?: boolean;
|
|
16
18
|
labels: {
|
|
17
19
|
critical: string;
|
|
18
20
|
warning: string;
|
|
@@ -23,7 +25,9 @@ interface Props {
|
|
|
23
25
|
export const Text = ({
|
|
24
26
|
thresholds,
|
|
25
27
|
data,
|
|
26
|
-
|
|
28
|
+
displayAsRaw,
|
|
29
|
+
labels,
|
|
30
|
+
baseColor
|
|
27
31
|
}: Props): JSX.Element | null => {
|
|
28
32
|
const theme = useTheme();
|
|
29
33
|
const { classes } = useTextStyles();
|
|
@@ -37,20 +41,21 @@ export const Text = ({
|
|
|
37
41
|
const metricUnit = metric?.unit ?? '';
|
|
38
42
|
|
|
39
43
|
const color = getColorFromDataAndTresholds({
|
|
44
|
+
baseColor,
|
|
40
45
|
data: metricValue,
|
|
41
46
|
theme,
|
|
42
47
|
thresholds
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
const warningThresholdLabels = thresholds.warning.map(({ value }) =>
|
|
46
|
-
|
|
51
|
+
formatMetricValueWithUnit({
|
|
47
52
|
unit: metricUnit,
|
|
48
53
|
value
|
|
49
54
|
})
|
|
50
55
|
);
|
|
51
56
|
|
|
52
57
|
const criticalThresholdLabels = thresholds.critical.map(({ value }) =>
|
|
53
|
-
|
|
58
|
+
formatMetricValueWithUnit({
|
|
54
59
|
unit: metricUnit,
|
|
55
60
|
value
|
|
56
61
|
})
|
|
@@ -58,24 +63,26 @@ export const Text = ({
|
|
|
58
63
|
|
|
59
64
|
return (
|
|
60
65
|
<div className={classes.graphText}>
|
|
61
|
-
<Typography sx={{ color }} variant="
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
<Typography sx={{ color }} variant="h2">
|
|
67
|
+
<strong>
|
|
68
|
+
{formatMetricValueWithUnit({
|
|
69
|
+
isRaw: displayAsRaw,
|
|
70
|
+
unit: metricUnit,
|
|
71
|
+
value: metricValue
|
|
72
|
+
})}
|
|
73
|
+
</strong>
|
|
67
74
|
</Typography>
|
|
68
75
|
{thresholds.enabled && (
|
|
69
76
|
<div className={classes.thresholds}>
|
|
70
77
|
<Typography sx={{ color: 'warning.main' }} variant="h5">
|
|
71
78
|
{labels.warning}
|
|
72
79
|
{': '}
|
|
73
|
-
{warningThresholdLabels.join(' - ')}
|
|
80
|
+
{warningThresholdLabels.join(' - ')}
|
|
74
81
|
</Typography>
|
|
75
82
|
<Typography sx={{ color: 'error.main' }} variant="h5">
|
|
76
83
|
{labels.critical}
|
|
77
84
|
{': '}
|
|
78
|
-
{criticalThresholdLabels.join(' - ')}
|
|
85
|
+
{criticalThresholdLabels.join(' - ')}
|
|
79
86
|
</Typography>
|
|
80
87
|
</div>
|
|
81
88
|
)}
|
|
@@ -335,10 +335,10 @@ describe('timeSeries', () => {
|
|
|
335
335
|
describe(timeSeries.formatMetricValue, () => {
|
|
336
336
|
const cases: Array<TestCase> = [
|
|
337
337
|
[218857269, '', 1000, '218.86m'],
|
|
338
|
-
[218857269, '', 1024, '208.
|
|
338
|
+
[218857269, '', 1024, '208.72 M'],
|
|
339
339
|
[0.12232323445, '', 1000, '0.12'],
|
|
340
|
-
[1024, 'B', 1000, '
|
|
341
|
-
[1024, 'B', 1024, '
|
|
340
|
+
[1024, 'B', 1000, '1 KB'],
|
|
341
|
+
[1024, 'B', 1024, '1 KB'],
|
|
342
342
|
[null, 'B', 1024, null]
|
|
343
343
|
];
|
|
344
344
|
|
|
@@ -504,3 +504,106 @@ describe('timeSeries', () => {
|
|
|
504
504
|
});
|
|
505
505
|
});
|
|
506
506
|
});
|
|
507
|
+
|
|
508
|
+
describe('Format value with unit', () => {
|
|
509
|
+
const units = [
|
|
510
|
+
'B',
|
|
511
|
+
'bytes',
|
|
512
|
+
'bytespersecond',
|
|
513
|
+
'B/s',
|
|
514
|
+
'B/sec',
|
|
515
|
+
'o',
|
|
516
|
+
'octets',
|
|
517
|
+
'b/s',
|
|
518
|
+
'b',
|
|
519
|
+
'ms',
|
|
520
|
+
'%',
|
|
521
|
+
''
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
const getExpectedResult = (unit): string => {
|
|
525
|
+
if (unit === '') {
|
|
526
|
+
return '324.23m';
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return `309.21 M${unit}`;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const humanReadableTestCases = units.map((unit) => {
|
|
533
|
+
if (unit === '%') {
|
|
534
|
+
return {
|
|
535
|
+
expectedResult: '45.56%',
|
|
536
|
+
unit,
|
|
537
|
+
value: 45.55678
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (unit === 'ms') {
|
|
542
|
+
return {
|
|
543
|
+
expectedResult: '34.23 seconds',
|
|
544
|
+
unit,
|
|
545
|
+
value: 34232
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
expectedResult: getExpectedResult(unit),
|
|
551
|
+
unit,
|
|
552
|
+
value: 324234232.34233
|
|
553
|
+
};
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const rawTestCases = units.map((unit) => {
|
|
557
|
+
if (unit === '%') {
|
|
558
|
+
return {
|
|
559
|
+
expectedResult: '45.55678%',
|
|
560
|
+
unit,
|
|
561
|
+
value: 45.55678
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (unit === 'ms') {
|
|
566
|
+
return {
|
|
567
|
+
expectedResult: '34232 ms',
|
|
568
|
+
unit,
|
|
569
|
+
value: 34232
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
expectedResult:
|
|
575
|
+
unit === '' ? '324234232.34233 ' : `324234232.34233 ${unit}`,
|
|
576
|
+
unit,
|
|
577
|
+
value: 324234232.34233
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
describe('Format the value as human readable', () => {
|
|
582
|
+
it.each(humanReadableTestCases)(
|
|
583
|
+
'formats the value with $unit',
|
|
584
|
+
({ value, unit, expectedResult }) => {
|
|
585
|
+
expect(
|
|
586
|
+
timeSeries.formatMetricValueWithUnit({
|
|
587
|
+
unit,
|
|
588
|
+
value
|
|
589
|
+
})
|
|
590
|
+
).toEqual(expectedResult);
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe('Format the value as raw', () => {
|
|
596
|
+
it.each(rawTestCases)(
|
|
597
|
+
'formats the value with $unit',
|
|
598
|
+
({ value, unit, expectedResult }) => {
|
|
599
|
+
expect(
|
|
600
|
+
timeSeries.formatMetricValueWithUnit({
|
|
601
|
+
isRaw: true,
|
|
602
|
+
unit,
|
|
603
|
+
value
|
|
604
|
+
})
|
|
605
|
+
).toEqual(expectedResult);
|
|
606
|
+
}
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
@@ -28,7 +28,10 @@ import {
|
|
|
28
28
|
lt,
|
|
29
29
|
identity,
|
|
30
30
|
head,
|
|
31
|
-
last
|
|
31
|
+
last,
|
|
32
|
+
cond,
|
|
33
|
+
always,
|
|
34
|
+
T
|
|
32
35
|
} from 'ramda';
|
|
33
36
|
|
|
34
37
|
import { margin } from '../../LineChart/common';
|
|
@@ -440,6 +443,37 @@ const getRightScale = ({
|
|
|
440
443
|
});
|
|
441
444
|
};
|
|
442
445
|
|
|
446
|
+
const formatTime = (value: number): string => {
|
|
447
|
+
if (value < 1000) {
|
|
448
|
+
return `${numeral(value).format('0.[00]a')} ms`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const t = numeral(value / 1000).format('0.[00]a');
|
|
452
|
+
|
|
453
|
+
return `${t} seconds`;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const registerMsUnitToNumeral = (): null => {
|
|
457
|
+
try {
|
|
458
|
+
numeral.register('format', 'milliseconds', {
|
|
459
|
+
format: (value) => {
|
|
460
|
+
return formatTime(value);
|
|
461
|
+
},
|
|
462
|
+
regexps: {
|
|
463
|
+
format: /(ms)/,
|
|
464
|
+
unformat: /(ms)/
|
|
465
|
+
},
|
|
466
|
+
unformat: () => ''
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
return null;
|
|
470
|
+
} catch (_) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
registerMsUnitToNumeral();
|
|
476
|
+
|
|
443
477
|
const formatMetricValue = ({
|
|
444
478
|
value,
|
|
445
479
|
unit,
|
|
@@ -463,11 +497,14 @@ const formatMetricValue = ({
|
|
|
463
497
|
|
|
464
498
|
const base1024 = base2Units.includes(unit) || Number(base) === 1024;
|
|
465
499
|
|
|
466
|
-
const formatSuffix =
|
|
500
|
+
const formatSuffix = cond([
|
|
501
|
+
[equals('ms'), always(' ms')],
|
|
502
|
+
[T, always(base1024 ? ' ib' : 'a')]
|
|
503
|
+
])(unit);
|
|
467
504
|
|
|
468
505
|
const formattedMetricValue = numeral(Math.abs(value))
|
|
469
506
|
.format(`0.[00]${formatSuffix}`)
|
|
470
|
-
.replace(
|
|
507
|
+
.replace(/iB/g, unit);
|
|
471
508
|
|
|
472
509
|
if (lt(value, 0)) {
|
|
473
510
|
return `-${formattedMetricValue}`;
|
|
@@ -476,6 +513,31 @@ const formatMetricValue = ({
|
|
|
476
513
|
return formattedMetricValue;
|
|
477
514
|
};
|
|
478
515
|
|
|
516
|
+
const formatMetricValueWithUnit = ({
|
|
517
|
+
value,
|
|
518
|
+
unit,
|
|
519
|
+
base = 1000,
|
|
520
|
+
isRaw = false
|
|
521
|
+
}: FormatMetricValueProps & { isRaw?: boolean }): string | null => {
|
|
522
|
+
if (isNil(value)) {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (isRaw) {
|
|
527
|
+
const unitText = equals('%', unit) ? unit : ` ${unit}`;
|
|
528
|
+
|
|
529
|
+
return `${value}${unitText}`;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (equals('%', unit)) {
|
|
533
|
+
return `${numeral(Math.abs(value)).format('0.[00]')}%`;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const formattedMetricValue = formatMetricValue({ base, unit, value });
|
|
537
|
+
|
|
538
|
+
return formattedMetricValue;
|
|
539
|
+
};
|
|
540
|
+
|
|
479
541
|
const getStackedYScale = ({
|
|
480
542
|
leftScale,
|
|
481
543
|
rightScale
|
|
@@ -557,5 +619,6 @@ export {
|
|
|
557
619
|
getStackedYScale,
|
|
558
620
|
getTimeValue,
|
|
559
621
|
bisectDate,
|
|
560
|
-
getMetricWithLatestData
|
|
622
|
+
getMetricWithLatestData,
|
|
623
|
+
formatMetricValueWithUnit
|
|
561
624
|
};
|
|
@@ -14,6 +14,8 @@ interface DsData {
|
|
|
14
14
|
export interface Metric {
|
|
15
15
|
average_value: number | null;
|
|
16
16
|
crit: number | null;
|
|
17
|
+
critical_high_threshold: number | null;
|
|
18
|
+
critical_low_threshold: number | null;
|
|
17
19
|
data: Array<number>;
|
|
18
20
|
ds_data: DsData;
|
|
19
21
|
legend: string;
|
|
@@ -21,7 +23,8 @@ export interface Metric {
|
|
|
21
23
|
metric: string;
|
|
22
24
|
minimum_value: number | null;
|
|
23
25
|
unit: string;
|
|
24
|
-
|
|
26
|
+
warning_high_threshold: number | null;
|
|
27
|
+
warning_low_threshold: number | null;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
type TimeSeries = { timeTick: string };
|
|
@@ -17,6 +17,7 @@ import { Theme } from '@mui/material';
|
|
|
17
17
|
import { Thresholds } from './models';
|
|
18
18
|
|
|
19
19
|
interface GetColorFromDataAndThresholdsProps {
|
|
20
|
+
baseColor?: string;
|
|
20
21
|
data: number;
|
|
21
22
|
theme: Theme;
|
|
22
23
|
thresholds: Thresholds;
|
|
@@ -25,10 +26,11 @@ interface GetColorFromDataAndThresholdsProps {
|
|
|
25
26
|
export const getColorFromDataAndTresholds = ({
|
|
26
27
|
data,
|
|
27
28
|
thresholds,
|
|
28
|
-
theme
|
|
29
|
+
theme,
|
|
30
|
+
baseColor
|
|
29
31
|
}: GetColorFromDataAndThresholdsProps): string => {
|
|
30
32
|
if (!thresholds.enabled) {
|
|
31
|
-
return theme.palette.
|
|
33
|
+
return baseColor || theme.palette.primary.main;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
const criticalValues = pluck('value', thresholds.critical).sort();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { includes, map, prop, reject } from 'ramda';
|
|
2
2
|
import { makeStyles } from 'tss-react/mui';
|
|
3
3
|
|
|
4
|
-
import { Chip } from '@mui/material';
|
|
4
|
+
import { Chip, ChipProps } from '@mui/material';
|
|
5
5
|
import { UseAutocompleteProps } from '@mui/material/useAutocomplete';
|
|
6
6
|
|
|
7
7
|
import Autocomplete, { Props as AutocompleteProps } from '..';
|
|
@@ -18,7 +18,6 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
18
18
|
width: theme.spacing(1.5)
|
|
19
19
|
},
|
|
20
20
|
tag: {
|
|
21
|
-
backgroundColor: theme.palette.divider,
|
|
22
21
|
fontSize: theme.typography.caption.fontSize
|
|
23
22
|
}
|
|
24
23
|
}));
|
|
@@ -33,6 +32,7 @@ export interface Props
|
|
|
33
32
|
UseAutocompleteProps<SelectEntry, Multiple, DisableClearable, FreeSolo>,
|
|
34
33
|
'multiple'
|
|
35
34
|
> {
|
|
35
|
+
chipProps?: ChipProps;
|
|
36
36
|
disableSortedOptions?: boolean;
|
|
37
37
|
getTagLabel?: (option) => string;
|
|
38
38
|
optionProperty?: string;
|
|
@@ -45,6 +45,7 @@ const MultiAutocompleteField = ({
|
|
|
45
45
|
optionProperty = 'name',
|
|
46
46
|
getOptionLabel = (option): string => option.name,
|
|
47
47
|
getTagLabel = (option): string => option[optionProperty],
|
|
48
|
+
chipProps,
|
|
48
49
|
...props
|
|
49
50
|
}: Props): JSX.Element => {
|
|
50
51
|
const { classes } = useStyles();
|
|
@@ -60,6 +61,7 @@ const MultiAutocompleteField = ({
|
|
|
60
61
|
label={getTagLabel(option)}
|
|
61
62
|
size="medium"
|
|
62
63
|
{...getTagProps({ index })}
|
|
64
|
+
{...chipProps}
|
|
63
65
|
/>
|
|
64
66
|
));
|
|
65
67
|
|
|
@@ -32,6 +32,7 @@ export type Props = {
|
|
|
32
32
|
displayPopupIcon?: boolean;
|
|
33
33
|
endAdornment?: React.ReactElement;
|
|
34
34
|
error?: string;
|
|
35
|
+
getOptionItemLabel?: (option) => string;
|
|
35
36
|
hideInput?: boolean;
|
|
36
37
|
label: string;
|
|
37
38
|
loading?: boolean;
|
|
@@ -158,6 +159,7 @@ const AutocompleteField = ({
|
|
|
158
159
|
autoSize = false,
|
|
159
160
|
autoSizeDefaultWidth = 0,
|
|
160
161
|
autoSizeCustomPadding,
|
|
162
|
+
getOptionItemLabel = (option) => option.name,
|
|
161
163
|
...autocompleteProps
|
|
162
164
|
}: Props): JSX.Element => {
|
|
163
165
|
const { classes, cx } = useStyles({ hideInput });
|
|
@@ -249,7 +251,7 @@ const AutocompleteField = ({
|
|
|
249
251
|
<Option
|
|
250
252
|
thumbnailUrl={displayOptionThumbnail ? option.url : undefined}
|
|
251
253
|
>
|
|
252
|
-
{option
|
|
254
|
+
{getOptionItemLabel(option)}
|
|
253
255
|
</Option>
|
|
254
256
|
</li>
|
|
255
257
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { forwardRef, useCallback } from 'react';
|
|
2
2
|
|
|
3
|
-
import { isNil } from 'ramda';
|
|
3
|
+
import { equals, isNil } from 'ramda';
|
|
4
4
|
import { makeStyles } from 'tss-react/mui';
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
TextFieldProps,
|
|
10
10
|
Theme,
|
|
11
11
|
Tooltip,
|
|
12
|
-
Typography
|
|
12
|
+
Typography,
|
|
13
|
+
Box
|
|
13
14
|
} from '@mui/material';
|
|
14
15
|
|
|
15
16
|
import { getNormalizedId } from '../../utils';
|
|
@@ -18,8 +19,9 @@ import useAutoSize from './useAutoSize';
|
|
|
18
19
|
|
|
19
20
|
const useStyles = makeStyles<{ displayAsBlock: boolean }>()(
|
|
20
21
|
(theme: Theme, { displayAsBlock }) => ({
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
autoSizeCompact: {
|
|
23
|
+
paddingRight: theme.spacing(1),
|
|
24
|
+
paddingTop: theme.spacing(0.6)
|
|
23
25
|
},
|
|
24
26
|
hiddenText: {
|
|
25
27
|
display: 'table',
|
|
@@ -140,7 +142,7 @@ const TextField = forwardRef(
|
|
|
140
142
|
}, [innerValue, debounced, defaultValue]);
|
|
141
143
|
|
|
142
144
|
return (
|
|
143
|
-
|
|
145
|
+
<Box sx={{ width: autoSize ? 'auto' : '100%' }}>
|
|
144
146
|
<Tooltip placement="top" title={tooltipTitle}>
|
|
145
147
|
<MuiTextField
|
|
146
148
|
data-testid={dataTestId}
|
|
@@ -162,7 +164,8 @@ const TextField = forwardRef(
|
|
|
162
164
|
className: cx(
|
|
163
165
|
classes.inputBase,
|
|
164
166
|
{
|
|
165
|
-
[classes.transparent]: transparent
|
|
167
|
+
[classes.transparent]: transparent,
|
|
168
|
+
[classes.autoSizeCompact]: autoSize && equals(size, 'compact')
|
|
166
169
|
},
|
|
167
170
|
className
|
|
168
171
|
),
|
|
@@ -195,7 +198,7 @@ const TextField = forwardRef(
|
|
|
195
198
|
{rest.value || externalValueForAutoSize || innerValue}
|
|
196
199
|
</Typography>
|
|
197
200
|
)}
|
|
198
|
-
|
|
201
|
+
</Box>
|
|
199
202
|
);
|
|
200
203
|
}
|
|
201
204
|
);
|
|
@@ -56,7 +56,7 @@ describe('Rich Text Editor', () => {
|
|
|
56
56
|
.should('be.visible')
|
|
57
57
|
.and('have.value', '');
|
|
58
58
|
|
|
59
|
-
cy.
|
|
59
|
+
cy.findByLabelText('RichTextEditor').parent().contains('Type here...');
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('displays changes with undo redo buttons', () => {
|
|
@@ -187,11 +187,6 @@ describe('Rich Text Editor', () => {
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
it('displays editor when editable props is false and an initialState exist', () => {
|
|
190
|
-
cy.get('[data-testid="RichTextEditor"]').should(
|
|
191
|
-
'have.attr',
|
|
192
|
-
'contenteditable',
|
|
193
|
-
'false'
|
|
194
|
-
);
|
|
195
190
|
cy.findByLabelText('Undo').should('not.exist');
|
|
196
191
|
cy.findByLabelText('Redo').should('not.exist');
|
|
197
192
|
cy.findByLabelText('bold').should('not.exist');
|
|
@@ -216,11 +211,6 @@ describe('Rich Text Editor', () => {
|
|
|
216
211
|
});
|
|
217
212
|
|
|
218
213
|
it('displays editor when editable props is false and an initialState exist', () => {
|
|
219
|
-
cy.get('[data-testid="RichTextEditor"]').should(
|
|
220
|
-
'have.attr',
|
|
221
|
-
'contenteditable',
|
|
222
|
-
'false'
|
|
223
|
-
);
|
|
224
214
|
cy.findByLabelText('Undo').should('be.disabled');
|
|
225
215
|
cy.findByLabelText('Redo').should('be.disabled');
|
|
226
216
|
cy.findByLabelText('bold').should('be.disabled');
|
|
@@ -245,12 +235,14 @@ describe('Rich Text Editor', () => {
|
|
|
245
235
|
});
|
|
246
236
|
});
|
|
247
237
|
it('displays editor with custom namespace', () => {
|
|
248
|
-
cy.get('
|
|
238
|
+
cy.get('[data-testid="CypressComponentTest"] > p').should('be.exist');
|
|
249
239
|
cy.get('[data-testid="CypressComponentTest"]').should('be.exist');
|
|
250
240
|
});
|
|
251
241
|
|
|
252
242
|
it('displays editor with custom placeholder', () => {
|
|
253
|
-
cy.get('
|
|
243
|
+
cy.get('[data-testid="CypressComponentTest"]')
|
|
244
|
+
.parent()
|
|
245
|
+
.contains('Type Cypress test...');
|
|
254
246
|
});
|
|
255
247
|
|
|
256
248
|
it('displays editor with custom minimum input height', () => {
|
|
@@ -23,6 +23,7 @@ import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
|
|
|
23
23
|
export interface RichTextEditorProps {
|
|
24
24
|
contentClassName?: string;
|
|
25
25
|
disabled?: boolean;
|
|
26
|
+
displayBlockButtons?: boolean;
|
|
26
27
|
displayMacrosButton?: boolean;
|
|
27
28
|
editable: boolean;
|
|
28
29
|
editorState?: string;
|
|
@@ -132,9 +133,10 @@ const RichTextEditor = ({
|
|
|
132
133
|
onBlur,
|
|
133
134
|
contentClassName,
|
|
134
135
|
displayMacrosButton = false,
|
|
135
|
-
disabled,
|
|
136
|
+
disabled = false,
|
|
136
137
|
openLinkInNewTab = true,
|
|
137
|
-
initialize
|
|
138
|
+
initialize,
|
|
139
|
+
displayBlockButtons = true
|
|
138
140
|
}: RichTextEditorProps): JSX.Element => {
|
|
139
141
|
const { classes } = useStyles({ toolbarPositions });
|
|
140
142
|
|
|
@@ -174,6 +176,7 @@ const RichTextEditor = ({
|
|
|
174
176
|
<div className={classes.toolbar}>
|
|
175
177
|
<ToolbarPlugin
|
|
176
178
|
disabled={disabled}
|
|
179
|
+
displayBlockButtons={displayBlockButtons}
|
|
177
180
|
displayMacrosButton={displayMacrosButton}
|
|
178
181
|
editable={editable}
|
|
179
182
|
/>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
AutoLinkPlugin,
|
|
3
5
|
LinkMatcher
|
|
4
6
|
} from '@lexical/react/LexicalAutoLinkPlugin';
|
|
7
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
8
|
+
import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from 'lexical';
|
|
5
9
|
|
|
6
10
|
interface LinkAttributes {
|
|
7
11
|
rel?: null | string;
|
|
@@ -56,7 +60,42 @@ const AutoCompleteLinkPlugin = ({
|
|
|
56
60
|
openLinkInNewTab
|
|
57
61
|
}: {
|
|
58
62
|
openLinkInNewTab: boolean;
|
|
59
|
-
}): JSX.Element => {
|
|
63
|
+
}): JSX.Element | null => {
|
|
64
|
+
const [editor] = useLexicalComposerContext();
|
|
65
|
+
const [hasFocus, setFocus] = useState(false);
|
|
66
|
+
|
|
67
|
+
useEffect(
|
|
68
|
+
() =>
|
|
69
|
+
editor.registerCommand(
|
|
70
|
+
BLUR_COMMAND,
|
|
71
|
+
() => {
|
|
72
|
+
setFocus(false);
|
|
73
|
+
|
|
74
|
+
return false;
|
|
75
|
+
},
|
|
76
|
+
COMMAND_PRIORITY_LOW
|
|
77
|
+
),
|
|
78
|
+
[]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
useEffect(
|
|
82
|
+
() =>
|
|
83
|
+
editor.registerCommand(
|
|
84
|
+
FOCUS_COMMAND,
|
|
85
|
+
() => {
|
|
86
|
+
setFocus(true);
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
},
|
|
90
|
+
COMMAND_PRIORITY_LOW
|
|
91
|
+
),
|
|
92
|
+
[]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (!hasFocus) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
60
99
|
return <AutoLinkPlugin matchers={getMatchers(openLinkInNewTab)} />;
|
|
61
100
|
};
|
|
62
101
|
|
|
@@ -11,7 +11,7 @@ import { mergeRegister } from '@lexical/utils';
|
|
|
11
11
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
|
12
12
|
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
|
-
import { equals, isNil } from 'ramda';
|
|
14
|
+
import { dec, equals, gt, isNil, replace } from 'ramda';
|
|
15
15
|
|
|
16
16
|
import { Popper, IconButton, Paper, Link, Box } from '@mui/material';
|
|
17
17
|
import EditIcon from '@mui/icons-material/Edit';
|
|
@@ -56,6 +56,7 @@ const FloatingLinkEditor = ({
|
|
|
56
56
|
x: 0,
|
|
57
57
|
y: 0
|
|
58
58
|
});
|
|
59
|
+
const [editedUrl, setEditedUrl] = useState('');
|
|
59
60
|
|
|
60
61
|
const [editMode, setEditMode] = useAtom(editLinkModeAtom);
|
|
61
62
|
const [linkUrl, setLinkUrl] = useAtom(linkValueAtom);
|
|
@@ -65,9 +66,10 @@ const FloatingLinkEditor = ({
|
|
|
65
66
|
const acceptOrCancelNewLinkValue = useCallback(
|
|
66
67
|
(event): void => {
|
|
67
68
|
const { value } = event.target;
|
|
68
|
-
if (event.key === 'Enter') {
|
|
69
|
-
event.preventDefault();
|
|
70
69
|
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
|
|
72
|
+
if (event.key === 'Enter') {
|
|
71
73
|
if (value !== '') {
|
|
72
74
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, {
|
|
73
75
|
target: openLinkInNewTab ? '_blank' : undefined,
|
|
@@ -75,14 +77,36 @@ const FloatingLinkEditor = ({
|
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
setEditMode(false);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (event.key === 'Escape') {
|
|
80
83
|
setEditMode(false);
|
|
81
84
|
}
|
|
82
85
|
},
|
|
83
86
|
[setEditMode, setLinkUrl]
|
|
84
87
|
);
|
|
85
88
|
|
|
89
|
+
const enterInEditMode = useCallback(() => {
|
|
90
|
+
setEditedUrl(linkUrl);
|
|
91
|
+
setEditMode(true);
|
|
92
|
+
}, [linkUrl]);
|
|
93
|
+
|
|
94
|
+
const changeValue = useCallback((event): void => {
|
|
95
|
+
const { value } = event.target;
|
|
96
|
+
|
|
97
|
+
const matched = value.match(/https?:\/\//g);
|
|
98
|
+
|
|
99
|
+
if (gt(matched.length, 1)) {
|
|
100
|
+
setEditedUrl(
|
|
101
|
+
replace(matched.join(''), matched[dec(matched.length)], value)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setEditedUrl(value);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
86
110
|
useEffect(() => {
|
|
87
111
|
const isPositioned =
|
|
88
112
|
rangeRect && (!equals(rangeRect.x, 0) || !equals(rangeRect.y, 0));
|
|
@@ -126,9 +150,9 @@ const FloatingLinkEditor = ({
|
|
|
126
150
|
<InputField
|
|
127
151
|
autoFocus
|
|
128
152
|
dataTestId="InputLinkField"
|
|
129
|
-
defaultValue={linkUrl}
|
|
130
153
|
label={t(labelInputLink)}
|
|
131
154
|
size="small"
|
|
155
|
+
value={editedUrl}
|
|
132
156
|
onBlur={(event): void => {
|
|
133
157
|
const { value } = event.target;
|
|
134
158
|
|
|
@@ -139,7 +163,8 @@ const FloatingLinkEditor = ({
|
|
|
139
163
|
}
|
|
140
164
|
setEditMode(false);
|
|
141
165
|
}}
|
|
142
|
-
|
|
166
|
+
onChange={changeValue}
|
|
167
|
+
onKeyUp={acceptOrCancelNewLinkValue}
|
|
143
168
|
/>
|
|
144
169
|
) : (
|
|
145
170
|
<Box component="span" sx={{ margin: '10px' }}>
|
|
@@ -156,7 +181,7 @@ const FloatingLinkEditor = ({
|
|
|
156
181
|
aria-label={labelEditLink}
|
|
157
182
|
size="small"
|
|
158
183
|
sx={{ marginLeft: '5px' }}
|
|
159
|
-
onClick={
|
|
184
|
+
onClick={enterInEditMode}
|
|
160
185
|
>
|
|
161
186
|
<EditIcon fontSize="small" />
|
|
162
187
|
</IconButton>
|
|
@@ -8,7 +8,8 @@ import MacrosButton from './MacrosButton';
|
|
|
8
8
|
import BlockButtons from './BlockButtons';
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
|
-
disabled
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
displayBlockButtons: boolean;
|
|
12
13
|
displayMacrosButton?: boolean;
|
|
13
14
|
editable: boolean;
|
|
14
15
|
}
|
|
@@ -33,15 +34,20 @@ export const useStyles = makeStyles()((theme) => ({
|
|
|
33
34
|
const ToolbarPlugin = ({
|
|
34
35
|
editable,
|
|
35
36
|
displayMacrosButton,
|
|
36
|
-
disabled
|
|
37
|
+
disabled,
|
|
38
|
+
displayBlockButtons
|
|
37
39
|
}: Props): JSX.Element | null => {
|
|
38
40
|
const { classes } = useStyles();
|
|
39
41
|
|
|
40
42
|
return editable ? (
|
|
41
43
|
<div className={classes.container}>
|
|
42
44
|
<UndoRedoButtons disabled={disabled} />
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
{displayBlockButtons && (
|
|
46
|
+
<>
|
|
47
|
+
<Divider flexItem orientation="vertical" />
|
|
48
|
+
<BlockButtons disabled={disabled} />
|
|
49
|
+
</>
|
|
50
|
+
)}
|
|
45
51
|
<Divider flexItem orientation="vertical" />
|
|
46
52
|
<FormatButtons disabled={disabled} />
|
|
47
53
|
{displayMacrosButton && (
|
|
@@ -48,8 +48,7 @@ const getInputBaseRootStyle = ({
|
|
|
48
48
|
|
|
49
49
|
if (equals(size, 'compact')) {
|
|
50
50
|
return {
|
|
51
|
-
padding: '
|
|
52
|
-
paddingRight: '0px'
|
|
51
|
+
padding: '0px 0px 0px 8px'
|
|
53
52
|
};
|
|
54
53
|
}
|
|
55
54
|
if (equals(size, 'small')) {
|
|
@@ -75,7 +74,6 @@ const getInputBaseRootStyle = ({
|
|
|
75
74
|
const getInputBaseInputStyle = ({ size }: InputBaseProps): CSSInterpolation => {
|
|
76
75
|
if (equals(size, 'compact')) {
|
|
77
76
|
return {
|
|
78
|
-
fontSize: 'x-small',
|
|
79
77
|
minHeight: '32px'
|
|
80
78
|
};
|
|
81
79
|
}
|
|
@@ -123,6 +121,7 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
123
121
|
MuiChip: {
|
|
124
122
|
styleOverrides: {
|
|
125
123
|
root: ({ ownerState, theme }) => ({
|
|
124
|
+
backgroundColor: !ownerState.color && theme.palette.divider,
|
|
126
125
|
...(equals(ownerState.size, 'medium') && {
|
|
127
126
|
borderRadius: theme.spacing(1.25),
|
|
128
127
|
fontSize: theme.typography.body2.fontSize,
|
|
@@ -189,7 +188,10 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
189
188
|
},
|
|
190
189
|
MuiInputBase: {
|
|
191
190
|
styleOverrides: {
|
|
192
|
-
root: ({ ownerState }) =>
|
|
191
|
+
root: ({ ownerState, theme }) => ({
|
|
192
|
+
...getInputBaseInputStyle(ownerState),
|
|
193
|
+
backgroundColor: theme.palette.background.paper
|
|
194
|
+
})
|
|
193
195
|
}
|
|
194
196
|
},
|
|
195
197
|
MuiList: {
|
|
@@ -90,14 +90,6 @@ const useFetchQuery = <T extends object>({
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
return (): void => {
|
|
95
|
-
queryClient.cancelQueries(getQueryKey());
|
|
96
|
-
};
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
|
-
manageError();
|
|
100
|
-
|
|
101
93
|
const prefetchQuery = ({ endpointParams, queryKey }): void => {
|
|
102
94
|
queryClient.prefetchQuery(
|
|
103
95
|
queryKey,
|
|
@@ -162,6 +154,16 @@ const useFetchQuery = <T extends object>({
|
|
|
162
154
|
|
|
163
155
|
const errorData = queryData.data as ResponseError | undefined;
|
|
164
156
|
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
return (): void => {
|
|
159
|
+
queryClient.cancelQueries(getQueryKey());
|
|
160
|
+
};
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
manageError();
|
|
165
|
+
}, [queryData.data]);
|
|
166
|
+
|
|
165
167
|
return {
|
|
166
168
|
...omit(['data'], queryData),
|
|
167
169
|
data,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import Switch from './Switch';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Switch> = {
|
|
6
|
+
component: Switch
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof Switch>;
|
|
11
|
+
|
|
12
|
+
export const notChecked: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
checked: false
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const checked: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
checked: true
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { makeStyles } from 'tss-react/mui';
|
|
2
|
+
|
|
3
|
+
export const useSwitchStyles = makeStyles()((theme) => ({
|
|
4
|
+
switch: {
|
|
5
|
+
'& .MuiSwitch-thumb': {
|
|
6
|
+
backgroundColor: theme.palette.common.white
|
|
7
|
+
},
|
|
8
|
+
'& span[data-checked="true"] + .MuiSwitch-track': {
|
|
9
|
+
backgroundColor: theme.palette.success.main,
|
|
10
|
+
opacity: 1
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SwitchProps, Switch as MuiSwitch } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
import { useSwitchStyles } from './Switch.styles';
|
|
4
|
+
|
|
5
|
+
const Switch = ({ checked, ...props }: SwitchProps): JSX.Element => {
|
|
6
|
+
const { classes } = useSwitchStyles();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<MuiSwitch
|
|
10
|
+
checked={checked}
|
|
11
|
+
className={classes.switch}
|
|
12
|
+
data-checked={checked}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default Switch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Switch } from './Switch/Switch';
|
|
@@ -48,10 +48,12 @@ const useStyles = makeStyles<{
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
modalActions: {
|
|
51
|
+
bottom: 0,
|
|
51
52
|
display: 'flex',
|
|
52
53
|
flexDirection: 'row',
|
|
53
54
|
gap: theme.spacing(2),
|
|
54
|
-
justifyContent: 'flex-end'
|
|
55
|
+
justifyContent: 'flex-end',
|
|
56
|
+
position: 'sticky'
|
|
55
57
|
},
|
|
56
58
|
modalBody: {
|
|
57
59
|
'& > p': {
|
package/src/components/index.ts
CHANGED