Skip to content

Commit 40b8a1f

Browse files
committed
feat(dashboard): default viewport setting
1 parent 5b04497 commit 40b8a1f

File tree

23 files changed

+244
-71
lines changed

23 files changed

+244
-71
lines changed

packages/core-util/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ export { colorPalette } from './sdks/colorPalette';
44
export { round } from './sdks/number';
55
export { isNumeric } from './sdks/number';
66
export { Colorizer } from './sdks/colorizer';
7+
export {
8+
viewportToDateRange,
9+
relativeViewportOptions,
10+
dateRangeToViewport,
11+
getViewportDateRelativeToAbsolute,
12+
} from './sdks/viewportAdapters';
13+
export { rangeValidator } from './sdks/rangeValidator';

packages/react-components/src/components/time-sync/viewportAdapter.spec.ts packages/core-util/src/sdks/viewportAdapters.spec.ts

+21-17
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
11
import {
22
dateRangeToViewport,
33
viewportToDateRange,
4-
relativeOptions,
4+
relativeViewportOptions,
55
getViewportDateRelativeToAbsolute,
6-
} from './viewportAdapter';
6+
} from './viewportAdapters';
77

88
describe('dateRangeToViewport', () => {
99
it('can convert a relative date range option to the correct viewport', () => {
10-
expect(dateRangeToViewport(relativeOptions[0])).toEqual({
10+
expect(dateRangeToViewport(relativeViewportOptions[0])).toEqual({
1111
duration: '1 minute',
1212
});
1313

14-
expect(dateRangeToViewport(relativeOptions[1])).toEqual({
14+
expect(dateRangeToViewport(relativeViewportOptions[1])).toEqual({
1515
duration: '5 minute',
1616
});
1717

18-
expect(dateRangeToViewport(relativeOptions[2])).toEqual({
18+
expect(dateRangeToViewport(relativeViewportOptions[2])).toEqual({
1919
duration: '10 minute',
2020
});
2121

22-
expect(dateRangeToViewport(relativeOptions[3])).toEqual({
22+
expect(dateRangeToViewport(relativeViewportOptions[3])).toEqual({
2323
duration: '30 minute',
2424
});
2525

26-
expect(dateRangeToViewport(relativeOptions[4])).toEqual({
26+
expect(dateRangeToViewport(relativeViewportOptions[4])).toEqual({
2727
duration: '1 hour',
2828
});
2929

30-
expect(dateRangeToViewport(relativeOptions[5])).toEqual({
30+
expect(dateRangeToViewport(relativeViewportOptions[5])).toEqual({
3131
duration: '1 day',
3232
});
3333

34-
expect(dateRangeToViewport(relativeOptions[6])).toEqual({
34+
expect(dateRangeToViewport(relativeViewportOptions[6])).toEqual({
3535
duration: '7 day',
3636
});
3737

38-
expect(dateRangeToViewport(relativeOptions[7])).toEqual({
38+
expect(dateRangeToViewport(relativeViewportOptions[7])).toEqual({
3939
duration: '30 day',
4040
});
4141

42-
expect(dateRangeToViewport(relativeOptions[8])).toEqual({
42+
expect(dateRangeToViewport(relativeViewportOptions[8])).toEqual({
4343
duration: '90 day',
4444
});
4545
});
@@ -90,23 +90,27 @@ describe('dateRangeToViewport', () => {
9090
describe('viewportToDateRange', () => {
9191
it('can convert a relative duration to a date range', () => {
9292
expect(viewportToDateRange({ duration: '1 minute' })).toEqual(
93-
relativeOptions[0]
93+
relativeViewportOptions[0]
9494
);
9595

96-
expect(viewportToDateRange({ duration: '1m' })).toEqual(relativeOptions[0]);
96+
expect(viewportToDateRange({ duration: '1m' })).toEqual(
97+
relativeViewportOptions[0]
98+
);
9799

98100
expect(viewportToDateRange({ duration: '5 minute' })).toEqual(
99-
relativeOptions[1]
101+
relativeViewportOptions[1]
100102
);
101103

102-
expect(viewportToDateRange({ duration: '5m' })).toEqual(relativeOptions[1]);
104+
expect(viewportToDateRange({ duration: '5m' })).toEqual(
105+
relativeViewportOptions[1]
106+
);
103107

104108
expect(viewportToDateRange({ duration: '10 minute' })).toEqual(
105-
relativeOptions[2]
109+
relativeViewportOptions[2]
106110
);
107111

108112
expect(viewportToDateRange({ duration: '10m' })).toEqual(
109-
relativeOptions[2]
113+
relativeViewportOptions[2]
110114
);
111115

112116
expect(viewportToDateRange({ duration: '1m 60s' })).toEqual({

packages/react-components/src/components/time-sync/viewportAdapter.tsx packages/core-util/src/sdks/viewportAdapters.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const relativeOption = (
1717
type: 'relative',
1818
});
1919

20-
export const relativeOptions: DateRangePickerProps.RelativeOption[] = [
20+
export const relativeViewportOptions: DateRangePickerProps.RelativeOption[] = [
2121
relativeOption(1, 'minute'),
2222
relativeOption(5, 'minute'),
2323
relativeOption(10, 'minute'),
@@ -121,7 +121,7 @@ export const viewportToDateRange = (
121121
const key = relativeOptionKey(amount, unit);
122122
return {
123123
// If key is undefined, cloudscape will default to selecting the custom option.
124-
key: relativeOptions.find((ro) => ro.key === key)?.key,
124+
key: relativeViewportOptions.find((ro) => ro.key === key)?.key,
125125
amount,
126126
unit,
127127
type: 'relative',

packages/dashboard/src/components/actions/index.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { DashboardState } from '~/store/state';
1111
import { DashboardSave } from '~/types';
1212
import DashboardSettings from './settings';
1313
import CustomOrangeButton from '../customOrangeButton';
14+
import { parseViewport } from '~/util/parseViewport';
1415

1516
const DEFAULT_VIEWPORT = { duration: '10m' };
1617

@@ -50,6 +51,7 @@ const Actions: React.FC<ActionsProps> = ({
5051
significantDigits,
5152
},
5253
...dashboardConfiguration,
54+
defaultViewport: parseViewport(dashboardConfiguration.defaultViewport),
5355
viewport: viewport ?? DEFAULT_VIEWPORT,
5456
},
5557
readOnly ? 'preview' : 'edit'

packages/dashboard/src/components/actions/settings.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useGridSettings } from './useGridSettings';
77
import { numberFromDetail } from '~/util/inputEvent';
88
import DecimalPlaces from '../decimalPlaces';
99
import { isNumeric } from '@iot-app-kit/core-util';
10+
import { DefaultViewport } from '../defaultViewport';
1011

1112
export type DashboardSettingsProps = {
1213
onClose: () => void;
@@ -44,6 +45,7 @@ const DashboardSettings: React.FC<DashboardSettingsProps> = ({
4445
>
4546
<Box>
4647
<SpaceBetween direction='vertical' size='l'>
48+
<DefaultViewport />
4749
<DecimalPlaces
4850
showFormFieldLabel={true}
4951
onSignificantDigitsChange={onSignificantDigitsChange}

packages/dashboard/src/components/dashboard/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const Dashboard: React.FC<DashboardProperties> = ({
4747
name,
4848
}) => {
4949
useDashboardPlugins();
50-
useDashboardViewport(dashboardConfiguration.viewport);
50+
useDashboardViewport(dashboardConfiguration.defaultViewport);
5151

5252
const readOnly = initialViewMode && initialViewMode === 'preview';
5353
return (
@@ -77,7 +77,7 @@ const Dashboard: React.FC<DashboardProperties> = ({
7777
editable={true}
7878
name={name}
7979
propertiesPanel={<PropertiesPanel />}
80-
viewport={dashboardConfiguration.viewport}
80+
defaultViewport={dashboardConfiguration.defaultViewport}
8181
/>
8282
</DndProvider>
8383
</Provider>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const messages = {
2+
title: 'Default viewport',
3+
dateRangeIncompleteError:
4+
'The selected date range is incomplete. Select a start and end date for the date range.',
5+
dateRangeInvalidError:
6+
'The selected date range is invalid. The start date must be before the end date.',
7+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { DateRangePicker, FormField } from '@cloudscape-design/components';
2+
import {
3+
relativeViewportOptions,
4+
viewportToDateRange,
5+
dateRangeToViewport,
6+
rangeValidator,
7+
} from '@iot-app-kit/core-util';
8+
import { Viewport } from '@iot-app-kit/core';
9+
import React, { useEffect } from 'react';
10+
import { Controller, useForm } from 'react-hook-form';
11+
import { messages } from './constants';
12+
import { useDefaultViewport } from './useDefaultViewport';
13+
14+
export const DefaultViewport = () => {
15+
const { defaultViewport, onUpdateDefaultViewport } = useDefaultViewport();
16+
17+
const { control, setValue, clearErrors } = useForm<{
18+
defaultViewport: Viewport | undefined;
19+
}>({
20+
mode: 'onChange',
21+
});
22+
23+
useEffect(() => {
24+
setValue('defaultViewport', defaultViewport);
25+
clearErrors();
26+
}, [clearErrors, setValue, defaultViewport]);
27+
28+
const { title, dateRangeIncompleteError, dateRangeInvalidError } = messages;
29+
30+
return (
31+
<Controller
32+
control={control}
33+
name='defaultViewport'
34+
render={({ field, fieldState }) => {
35+
return (
36+
<FormField label={title} errorText={fieldState.error?.message}>
37+
<DateRangePicker
38+
expandToViewport={true}
39+
onChange={({ detail: { value } }) => {
40+
if (!value) return;
41+
field.onChange(value);
42+
onUpdateDefaultViewport(
43+
JSON.stringify(dateRangeToViewport(value))
44+
);
45+
}}
46+
value={viewportToDateRange(defaultViewport)}
47+
showClearButton={false}
48+
relativeOptions={relativeViewportOptions}
49+
isValidRange={rangeValidator({
50+
dateRangeIncompleteError,
51+
dateRangeInvalidError,
52+
})}
53+
/>
54+
</FormField>
55+
);
56+
}}
57+
/>
58+
);
59+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { DefaultViewport } from './defaultViewport';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useDispatch, useSelector } from 'react-redux';
2+
import { onUpdateDefaultViewportAction } from '~/store/actions/updateDefaultViewport';
3+
import { DashboardState } from '~/store/state';
4+
import { parseViewport } from '~/util/parseViewport';
5+
6+
export const useDefaultViewport = () => {
7+
const dispatch = useDispatch();
8+
9+
const { defaultViewport } = useSelector(
10+
(state: DashboardState) => state.dashboardConfiguration
11+
);
12+
13+
const onUpdateDefaultViewport = (updatedDefaultViewport?: string) => {
14+
dispatch(
15+
onUpdateDefaultViewportAction({
16+
defaultViewport: updatedDefaultViewport,
17+
})
18+
);
19+
};
20+
21+
return {
22+
defaultViewport: parseViewport(defaultViewport),
23+
onUpdateDefaultViewport,
24+
};
25+
};

packages/dashboard/src/components/internalDashboard/index.tsx

+34-30
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
spaceScaledXxxs,
1111
} from '@cloudscape-design/design-tokens';
1212
import { ContentLayout } from '@cloudscape-design/components';
13+
import messages from '@cloudscape-design/components/i18n/messages/all.all';
14+
import { I18nProvider } from '@cloudscape-design/components/i18n';
1315

1416
import { selectedRect } from '~/util/select';
1517

@@ -65,7 +67,7 @@ type InternalDashboardProperties = {
6567
editable?: boolean;
6668
name?: string;
6769
propertiesPanel?: ReactNode;
68-
viewport?: Viewport;
70+
defaultViewport?: Viewport;
6971
};
7072

7173
const defaultUserSelect: CSSProperties = { userSelect: 'initial' };
@@ -76,7 +78,7 @@ const InternalDashboard: React.FC<InternalDashboardProperties> = ({
7678
editable,
7779
name,
7880
propertiesPanel,
79-
viewport,
81+
defaultViewport,
8082
}) => {
8183
const { iotSiteWiseClient, iotTwinMakerClient } = useClients();
8284

@@ -384,36 +386,38 @@ const InternalDashboard: React.FC<InternalDashboardProperties> = ({
384386
);
385387

386388
return (
387-
<TimeSync
388-
initialViewport={viewport ?? { duration: '5m' }}
389-
group='dashboard-timesync'
390-
>
391-
{readOnly ? ReadOnlyComponent : EditComponent}
392-
<ConfirmDeleteModal
393-
visible={visible}
394-
headerTitle={`Delete selected widget${
395-
selectedWidgets.length > 1 ? 's' : ''
396-
}?`}
397-
cancelTitle='Cancel'
398-
submitTitle='Delete'
399-
description={
400-
<Box>
401-
<Box variant='p'>
402-
{`Are you sure you want to delete the selected widget${
403-
selectedWidgets.length > 1 ? 's' : ''
404-
}? You'll lose all the progress you made to the
389+
<I18nProvider locale='en' messages={[messages]}>
390+
<TimeSync
391+
initialViewport={defaultViewport ?? { duration: '5m' }}
392+
group='dashboard-timesync'
393+
>
394+
{readOnly ? ReadOnlyComponent : EditComponent}
395+
<ConfirmDeleteModal
396+
visible={visible}
397+
headerTitle={`Delete selected widget${
398+
selectedWidgets.length > 1 ? 's' : ''
399+
}?`}
400+
cancelTitle='Cancel'
401+
submitTitle='Delete'
402+
description={
403+
<Box>
404+
<Box variant='p'>
405+
{`Are you sure you want to delete the selected widget${
406+
selectedWidgets.length > 1 ? 's' : ''
407+
}? You'll lose all the progress you made to the
405408
widget${selectedWidgets.length > 1 ? 's' : ''}`}
409+
</Box>
410+
<Box variant='p' padding={{ top: 'm' }}>
411+
You cannot undo this action.
412+
</Box>
406413
</Box>
407-
<Box variant='p' padding={{ top: 'm' }}>
408-
You cannot undo this action.
409-
</Box>
410-
</Box>
411-
}
412-
handleDismiss={() => setVisible(false)}
413-
handleCancel={() => setVisible(false)}
414-
handleSubmit={onDelete}
415-
/>
416-
</TimeSync>
414+
}
415+
handleDismiss={() => setVisible(false)}
416+
handleCancel={() => setVisible(false)}
417+
handleSubmit={onDelete}
418+
/>
419+
</TimeSync>
420+
</I18nProvider>
417421
);
418422
};
419423

packages/dashboard/src/store/actions/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { SendWidgetsToBackAction } from './sendToBack';
1717
import type { UpdateWidgetsAction } from './updateWidget';
1818
import type { UpdateSignificantDigitsAction } from './updateSignificantDigits';
1919
import { UpdateRefreshRateAction } from './changeRefreshRate';
20+
import { UpdateDefaultViewportAction } from './updateDefaultViewport';
2021

2122
export * from './createWidget';
2223
export * from './deleteWidgets';
@@ -32,6 +33,7 @@ export * from './changeDashboardGrid';
3233
export * from './toggleReadOnly';
3334
export * from './updateSignificantDigits';
3435
export * from './changeRefreshRate';
36+
export * from './updateDefaultViewport';
3537

3638
export type DashboardAction =
3739
| CreateWidgetsAction
@@ -50,4 +52,5 @@ export type DashboardAction =
5052
| ChangeDashboardCellSizeAction
5153
| ChangeDashboardGridEnabledAction
5254
| UpdateSignificantDigitsAction
53-
| UpdateRefreshRateAction;
55+
| UpdateRefreshRateAction
56+
| UpdateDefaultViewportAction;

0 commit comments

Comments
 (0)