From eeda501d456c10f61123f9c4f77618bb870816f5 Mon Sep 17 00:00:00 2001 From: Xinyi Xu Date: Fri, 3 Mar 2023 15:15:31 -0800 Subject: [PATCH] feat(composer): add runtime tag settings change support --- packages/scene-composer/package.json | 2 +- .../three-fiber/anchor/AnchorWidget.tsx | 6 +- .../anchor/__tests__/AnchorWidget.spec.tsx | 13 +- .../SceneComposerInternal.spec.tsx.snap | 1 + .../src/components/panels/SettingsPanel.tsx | 74 ++++---- .../src/components/panels/TopBar.tsx | 37 +--- .../panels/__snapshots__/TopBar.spec.tsx.snap | 159 ------------------ .../panels/__tests__/SettingsPanel.spec.tsx | 63 +++++-- .../panels/{ => __tests__}/TopBar.spec.tsx | 72 +------- .../__snapshots__/SettingsPanel.spec.tsx.snap | 98 +++++++---- .../__snapshots__/TopBar.spec.tsx.snap | 34 ++++ .../MotionIndicatorVisibilityToggle.spec.tsx | 28 +++ .../MotionIndicatorVisibilityToggle.tsx | 34 ++++ .../SceneTagSettingsEditor.spec.tsx | 104 +++++++++++- .../scene-settings/SceneTagSettingsEditor.tsx | 30 ++-- ...ionIndicatorVisibilityToggle.spec.tsx.snap | 19 +++ .../src/hooks/useTagSettings.spec.tsx | 51 ++++++ .../src/hooks/useTagSettings.ts | 26 +++ .../src/layouts/SceneLayout/SceneLayout.tsx | 3 + .../__snapshots__/SceneLayout.spec.tsx.snap | 10 ++ packages/scene-composer/src/store/Store.ts | 2 + .../src/store/StoreOperations.ts | 4 +- .../store/slices/ViewOptionStateSlice.spec.ts | 37 ++++ .../src/store/slices/ViewOptionStateSlice.ts | 12 +- .../store/slices/ViewOptionStateSlice.spec.ts | 21 --- .../IotAppKitSceneComposer.en_US.json | 20 ++- 26 files changed, 566 insertions(+), 394 deletions(-) delete mode 100644 packages/scene-composer/src/components/panels/__snapshots__/TopBar.spec.tsx.snap rename packages/scene-composer/src/components/panels/{ => __tests__}/TopBar.spec.tsx (54%) create mode 100644 packages/scene-composer/src/components/panels/__tests__/__snapshots__/TopBar.spec.tsx.snap create mode 100644 packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.spec.tsx create mode 100644 packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.tsx create mode 100644 packages/scene-composer/src/components/panels/scene-settings/__snapshots__/MotionIndicatorVisibilityToggle.spec.tsx.snap create mode 100644 packages/scene-composer/src/hooks/useTagSettings.spec.tsx create mode 100644 packages/scene-composer/src/hooks/useTagSettings.ts create mode 100644 packages/scene-composer/src/store/slices/ViewOptionStateSlice.spec.ts delete mode 100644 packages/scene-composer/tests/store/slices/ViewOptionStateSlice.spec.ts diff --git a/packages/scene-composer/package.json b/packages/scene-composer/package.json index a7682e79d..7ef32246f 100644 --- a/packages/scene-composer/package.json +++ b/packages/scene-composer/package.json @@ -50,7 +50,7 @@ "fix": "eslint --fix .", "test": "jest --config jest.config.ts --coverage --silent", "test:dev": "jest --config jest.config.ts --coverage", - "posttest": "jest-coverage-ratchet", + "test:update-threashold": "jest-coverage-ratchet", "test:coverage": "npm test -- --updateSnapshot --coverage", "test:update": "jest --config jest.config.ts --updateSnapshot", "test:unit": "jest --config jest.config.ts", diff --git a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx index 1357ac0b2..3c868c5c0 100644 --- a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx +++ b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx @@ -29,7 +29,7 @@ import { getSceneResourceInfo } from '../../../../utils/sceneResourceUtils'; import svgIconToWidgetSprite from '../common/SvgIconToWidgetSprite'; import { findComponentByType } from '../../../../utils/nodeUtils'; import { Layers } from '../../../../common/constants'; -import { componentSettingsSelector } from '../../../../utils/componentSettingsUtils'; +import useTagSettings from '../../../../hooks/useTagSettings'; export interface AnchorWidgetProps { node: ISceneNodeInternal; @@ -61,9 +61,7 @@ export function AsyncLoadedAnchorWidget({ dataBindingTemplate, } = useStore(sceneComposerId)((state) => state); const isViewing = useStore(sceneComposerId)((state) => state.isViewing()); - const tagSettings: ITagSettings = useStore(sceneComposerId)((state) => - componentSettingsSelector(state, KnownComponentType.Tag), - ); + const tagSettings: ITagSettings = useTagSettings(); const autoRescale = useMemo(() => { return tagSettings.autoRescale; }, [tagSettings.autoRescale]); diff --git a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx index 62d22ac81..4366e27e0 100644 --- a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx +++ b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx @@ -4,13 +4,16 @@ import * as THREE from 'three'; import { useLoader } from '@react-three/fiber'; import { AnchorWidget } from '../AnchorWidget'; -import { DefaultAnchorStatus } from '../../../../..'; +import { DefaultAnchorStatus, DEFAULT_TAG_GLOBAL_SETTINGS } from '../../../../..'; import { useStore } from '../../../../../store'; +import useTagSettings from '../../../../../hooks/useTagSettings'; -jest.mock('../../../three-fiber/common/SvgIconToWidgetSprite', () => +jest.mock('../../common/SvgIconToWidgetSprite', () => jest.fn(((data, name, alwaysVisible, props) =>
) as any), ); +jest.mock('../../../../../hooks/useTagSettings', () => jest.fn()); + jest.mock('@react-three/fiber', () => { const originalModule = jest.requireActual('@react-three/fiber'); return { @@ -27,7 +30,6 @@ describe('AnchorWidget', () => { const setHighlightedSceneNodeRef = jest.fn(); const setSelectedSceneNodeRef = jest.fn(); const getObject3DBySceneNodeRef = jest.fn(); - const getSceneProperty = jest.fn(); const node = { ref: 'test-ref', @@ -52,13 +54,12 @@ describe('AnchorWidget', () => { getEditorConfig: () => ({ onWidgetClick }), dataInput: 'dataInput' as any, getObject3DBySceneNodeRef, - getSceneProperty, } as any); }; beforeEach(() => { (useLoader as unknown as jest.Mock).mockReturnValue(['TestSvgData']); - getSceneProperty.mockReturnValue(undefined); + (useTagSettings as jest.Mock).mockReturnValue(DEFAULT_TAG_GLOBAL_SETTINGS); jest.clearAllMocks(); }); @@ -98,7 +99,7 @@ describe('AnchorWidget', () => { it('should render correctly with non default tag settings', () => { setStore('test-ref', 'test-ref'); - getSceneProperty.mockReturnValue({ Tag: { scale: 3, autoRescale: true } }); + (useTagSettings as jest.Mock).mockReturnValue({ scale: 3, autoRescale: true }); const container = renderer.create(); expect(container).toMatchSnapshot(); diff --git a/packages/scene-composer/src/components/__snapshots__/SceneComposerInternal.spec.tsx.snap b/packages/scene-composer/src/components/__snapshots__/SceneComposerInternal.spec.tsx.snap index 3e226af4f..008d3ec1b 100644 --- a/packages/scene-composer/src/components/__snapshots__/SceneComposerInternal.spec.tsx.snap +++ b/packages/scene-composer/src/components/__snapshots__/SceneComposerInternal.spec.tsx.snap @@ -278,6 +278,7 @@ exports[`SceneComposerInternal should render correctly with an empty scene in vi leftPanel={ } + Settings={} /> } mainContent={ diff --git a/packages/scene-composer/src/components/panels/SettingsPanel.tsx b/packages/scene-composer/src/components/panels/SettingsPanel.tsx index 557f62b95..bb9213d7b 100644 --- a/packages/scene-composer/src/components/panels/SettingsPanel.tsx +++ b/packages/scene-composer/src/components/panels/SettingsPanel.tsx @@ -12,15 +12,19 @@ import { getGlobalSettings } from '../../common/GlobalSettings'; import { ExpandableInfoSection } from './CommonPanelComponents'; import { SceneDataBindingTemplateEditor, SceneTagSettingsEditor } from './scene-settings'; +import { MotionIndicatorVisibilityToggle } from './scene-settings/MotionIndicatorVisibilityToggle'; export interface SettingsPanelProps { valueDataBindingProvider?: IValueDataBindingProvider; } +const NO_PRESET_VALUE = 'n/a'; + export const SettingsPanel: React.FC = ({ valueDataBindingProvider }) => { const log = useLifecycleLogging('SettingsPanel'); const sceneComposerId = useContext(sceneComposerIdContext); const setSceneProperty = useStore(sceneComposerId)((state) => state.setSceneProperty); + const isEditing = useStore(sceneComposerId)((state) => state.isEditing()); const intl = useIntl(); const tagResizeEnabled = getGlobalSettings().featureConfig[COMPOSER_FEATURES.TagResize]; @@ -51,7 +55,7 @@ export const SettingsPanel: React.FC = ({ valueDataBindingPr }); const presetOptions = [ - { label: intl.formatMessage(i18nPresetsStrings['No Preset']), value: 'n/a' }, + { label: intl.formatMessage(i18nPresetsStrings['No Preset']), value: NO_PRESET_VALUE }, ...Object.keys(presets).map((preset) => ({ label: intl.formatMessage(i18nPresetsStrings[preset]) || pascalCase(preset), value: preset, @@ -67,51 +71,63 @@ export const SettingsPanel: React.FC = ({ valueDataBindingPr return ( - - - { + if (e.detail.selectedOption.value === NO_PRESET_VALUE) { + setSceneProperty(KnownSceneProperty.EnvironmentPreset, undefined); + } else { + setSceneProperty(KnownSceneProperty.EnvironmentPreset, e.detail.selectedOption.value); + } + }} + options={presetOptions} + selectedAriaLabel={intl.formatMessage({ defaultMessage: 'Selected', description: 'label' })} + disabled={presetOptions.length === 0} + placeholder={intl.formatMessage({ + defaultMessage: 'Choose an environment', + description: 'choose environment placeholder', + })} + expandToViewport + /> + + + + )} + {tagResizeEnabled && ( )} - {valueDataBindingProvider && ( + {valueDataBindingProvider && isEditing && ( diff --git a/packages/scene-composer/src/components/panels/TopBar.tsx b/packages/scene-composer/src/components/panels/TopBar.tsx index d7b36d9c7..2dadbee30 100644 --- a/packages/scene-composer/src/components/panels/TopBar.tsx +++ b/packages/scene-composer/src/components/panels/TopBar.tsx @@ -5,8 +5,7 @@ import { useIntl } from 'react-intl'; import { KnownComponentType } from '../../interfaces'; import { sceneComposerIdContext } from '../../common/sceneComposerIdContext'; -import { ICameraComponentInternal, useStore, useViewOptionState } from '../../store'; -import { Checked } from '../../assets/auto-gen/icons'; +import { ICameraComponentInternal, useStore } from '../../store'; import useActiveCamera from '../../hooks/useActiveCamera'; import { findComponentByType } from '../../utils/nodeUtils'; import { getCameraSettings } from '../../utils/cameraUtils'; @@ -20,10 +19,8 @@ const StyledSpaceBetween = styled(SpaceBetween)` export const TopBar: FC = () => { const sceneComposerId = useContext(sceneComposerIdContext); - const { motionIndicatorVisible, toggleMotionIndicatorVisibility } = useViewOptionState(sceneComposerId); const nodeMap = useStore(sceneComposerId)((state) => state.document.nodeMap); const getSceneNodeByRef = useStore(sceneComposerId)((state) => state.getSceneNodeByRef); - const getComponentRefByType = useStore(sceneComposerId)((state) => state.getComponentRefByType); const getObject3DBySceneNodeRef = useStore(sceneComposerId)((state) => state.getObject3DBySceneNodeRef); const { setActiveCameraSettings } = useActiveCamera(); const intl = useIntl(); @@ -42,19 +39,7 @@ export const TopBar: FC = () => { }, [nodeMap]); const hasCameraView = cameraItems.length > 0; - const hasMotionIndicator = Object.keys(getComponentRefByType(KnownComponentType.MotionIndicator)).length > 0; - const showTopBar = hasMotionIndicator || hasCameraView; - - const settingsOnItemClick = useCallback( - ({ detail }) => { - switch (detail.id) { - case KnownComponentType.MotionIndicator: - toggleMotionIndicatorVisibility(); - break; - } - }, - [toggleMotionIndicatorVisibility], - ); + const showTopBar = hasCameraView; const setActiveCameraOnItemClick = useCallback( ({ detail }) => { @@ -70,24 +55,6 @@ export const TopBar: FC = () => { if (showTopBar) { return ( - {hasMotionIndicator && ( - : <>, - }, - ]} - onItemClick={settingsOnItemClick} - > - {intl.formatMessage({ defaultMessage: 'View Options', description: 'view options dropdown button text' })} - - )} {hasCameraView && ( {intl.formatMessage({ defaultMessage: 'Camera View', description: 'camera views dropdown button text' })} diff --git a/packages/scene-composer/src/components/panels/__snapshots__/TopBar.spec.tsx.snap b/packages/scene-composer/src/components/panels/__snapshots__/TopBar.spec.tsx.snap deleted file mode 100644 index 28ece930e..000000000 --- a/packages/scene-composer/src/components/panels/__snapshots__/TopBar.spec.tsx.snap +++ /dev/null @@ -1,159 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should not render without motion indicator or camera view 1`] = `
`; - -exports[` should render Cameras Drop down with camera view 1`] = ` -.c0 { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - -webkit-box-pack: right; - -webkit-justify-content: right; - -ms-flex-pack: right; - justify-content: right; -} - -
-
-
    -
  • - Camera -
  • -
-
-
-`; - -exports[` should render View Options Drop down with motion indicator 1`] = ` -.c0 { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - -webkit-box-pack: right; - -webkit-justify-content: right; - -ms-flex-pack: right; - justify-content: right; -} - -
-
-
    -
  • - - - - Motion indicator -
  • -
-
-
-`; - -exports[` should render both drop downs with motion indicator and camera view 1`] = ` -.c0 { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - -webkit-box-pack: right; - -webkit-justify-content: right; - -ms-flex-pack: right; - justify-content: right; -} - -
-
-
    -
  • - - - - Motion indicator -
  • -
-
    -
  • - Camera -
  • -
-
-
-`; - -exports[` should render with motion indicator off option 1`] = ` -.c0 { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - -webkit-box-pack: right; - -webkit-justify-content: right; - -ms-flex-pack: right; - justify-content: right; -} - -
-
-
    -
  • - Motion indicator -
  • -
-
-
-`; diff --git a/packages/scene-composer/src/components/panels/__tests__/SettingsPanel.spec.tsx b/packages/scene-composer/src/components/panels/__tests__/SettingsPanel.spec.tsx index af1033388..fc1a2ac8c 100644 --- a/packages/scene-composer/src/components/panels/__tests__/SettingsPanel.spec.tsx +++ b/packages/scene-composer/src/components/panels/__tests__/SettingsPanel.spec.tsx @@ -5,8 +5,16 @@ import { SettingsPanel } from '..'; import { useStore } from '../../../store'; import { setFeatureConfig } from '../../../common/GlobalSettings'; import { COMPOSER_FEATURES } from '../../../interfaces'; +import { mockProvider } from '../../../../tests/components/panels/scene-components/MockComponents'; -jest.spyOn(React, 'useContext').mockReturnValue('sceneComponserId' as any); +jest.mock('../scene-settings', () => { + const originalModule = jest.requireActual('../scene-settings'); + return { + ...originalModule, + SceneDataBindingTemplateEditor: 'SceneDataBindingTemplateEditor', + SceneTagSettingsEditor: 'SceneTagSettingsEditor', + }; +}); jest.mock('../CommonPanelComponents', () => ({ ...jest.requireActual('../CommonPanelComponents'), @@ -14,19 +22,52 @@ jest.mock('../CommonPanelComponents', () => ({ })); describe('SettingsPanel contains expected elements.', () => { - [{ [COMPOSER_FEATURES.TagResize]: true }, { [COMPOSER_FEATURES.TagResize]: false }].forEach((featureConfig) => { - it(`SettingsPanel contains expected elements for features ${featureConfig} `, async () => { - setFeatureConfig(featureConfig); + it('should contains default elements in editing mode.', async () => { + setFeatureConfig({ [COMPOSER_FEATURES.TagResize]: false }); + const setSceneProperty = jest.fn(); + useStore('default').setState({ + setSceneProperty: setSceneProperty, + getSceneProperty: jest.fn().mockReturnValue('neutral'), + isEditing: jest.fn().mockReturnValue(true), + }); + + const { container, queryByTitle } = render(); + + expect(queryByTitle('Current View Settings')).toBeTruthy(); + expect(queryByTitle('Scene Settings')).toBeTruthy(); + expect(queryByTitle('Tag Settings')).toBeFalsy(); + expect(queryByTitle('Data Binding Template')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); - const setSceneProperty = jest.fn(); - useStore('sceneComponserId').setState({ - setSceneProperty: setSceneProperty, - getSceneProperty: jest.fn().mockReturnValue('neutral'), - }); + it('should contains default elements in viewing mode.', async () => { + const setSceneProperty = jest.fn(); + useStore('default').setState({ + setSceneProperty: setSceneProperty, + getSceneProperty: jest.fn().mockReturnValue('neutral'), + isEditing: jest.fn().mockReturnValue(false), + }); + + const { container, queryByTitle } = render(); - const { container } = render(); + expect(queryByTitle('Current View Settings')).toBeTruthy(); + expect(queryByTitle('Scene Settings')).toBeFalsy(); + expect(queryByTitle('Tag Settings')).toBeFalsy(); + expect(queryByTitle('Data Binding Template')).toBeFalsy(); + expect(container).toMatchSnapshot(); + }); - expect(container).toMatchSnapshot(); + it('should contains tag settings element.', async () => { + setFeatureConfig({ [COMPOSER_FEATURES.TagResize]: true }); + const setSceneProperty = jest.fn(); + useStore('default').setState({ + setSceneProperty: setSceneProperty, + getSceneProperty: jest.fn().mockReturnValue('neutral'), }); + + const { container, queryByTitle } = render(); + + expect(queryByTitle('Tag Settings')).toBeTruthy(); + expect(container).toMatchSnapshot(); }); }); diff --git a/packages/scene-composer/src/components/panels/TopBar.spec.tsx b/packages/scene-composer/src/components/panels/__tests__/TopBar.spec.tsx similarity index 54% rename from packages/scene-composer/src/components/panels/TopBar.spec.tsx rename to packages/scene-composer/src/components/panels/__tests__/TopBar.spec.tsx index 3805fb0b8..2243db493 100644 --- a/packages/scene-composer/src/components/panels/TopBar.spec.tsx +++ b/packages/scene-composer/src/components/panels/__tests__/TopBar.spec.tsx @@ -2,21 +2,19 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import * as THREE from 'three'; -// eslint-disable-next-line import/order -import { TopBar } from './TopBar'; -import { useStore } from '../../store'; +import { TopBar } from '../TopBar'; +import { useStore } from '../../../store'; import { COMPOSER_FEATURES, DEFAULT_CAMERA_OPTIONS, DEFAULT_CAMERA_POSITION, KnownComponentType, setFeatureConfig, -} from '../..'; -import useActiveCamera from '../../hooks/useActiveCamera'; +} from '../../..'; +import useActiveCamera from '../../../hooks/useActiveCamera'; -jest.mock('../../hooks/useActiveCamera', () => jest.fn().mockReturnValue({ setActiveCameraSettings: jest.fn() })); +jest.mock('../../../hooks/useActiveCamera', () => jest.fn().mockReturnValue({ setActiveCameraSettings: jest.fn() })); -const toggleMotionIndicatorVisibilityMock = jest.fn(); const cameraSettings = { cameraType: 'Perspective', fov: DEFAULT_CAMERA_OPTIONS.fov, @@ -53,16 +51,10 @@ object3D.rotation.set(0, 0, 0); object3D.scale.set(1, 1, 1); const getObject3DBySceneNodeRef = jest.fn().mockReturnValue(object3D); -const getComponentRefByType = jest.fn(); const baseState = { - noHistoryStates: { - motionIndicatorVisible: true, - toggleMotionIndicatorVisibility: toggleMotionIndicatorVisibilityMock, - }, getSceneNodeByRef: jest.fn().mockReturnValue(cameraNode), getObject3DBySceneNodeRef, - getComponentRefByType, document: { nodeMap: { 'test-uuid': node, @@ -71,52 +63,14 @@ const baseState = { } as any; describe('', () => { - it('should render with motion indicator off option', () => { - getComponentRefByType.mockReturnValue({ type: KnownComponentType.MotionIndicator }); - useStore('default').setState({ - ...baseState, - noHistoryStates: { - motionIndicatorVisible: false, - }, - } as any); - - const { container } = render(); - - expect(container).toMatchSnapshot(); - }); - - it('should not render without motion indicator or camera view', () => { - getComponentRefByType.mockReturnValue({}); + it('should not render without camera view', () => { useStore('default').setState(baseState); const { container } = render(); expect(container).toMatchSnapshot(); }); - it('should render View Options Drop down with motion indicator', () => { - getComponentRefByType.mockReturnValue({ type: KnownComponentType.MotionIndicator }); - useStore('default').setState({ ...baseState }); - - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - it('should render Cameras Drop down with camera view', () => { - getComponentRefByType.mockReturnValue({}); - const document = { - nodeMap: { - 'camera-uuid': cameraNode, - }, - }; - - useStore('default').setState({ ...baseState, document }); - - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - - it('should render both drop downs with motion indicator and camera view', () => { - getComponentRefByType.mockReturnValue({ type: KnownComponentType.MotionIndicator }); const document = { nodeMap: { 'camera-uuid': cameraNode, @@ -129,21 +83,7 @@ describe('', () => { expect(container).toMatchSnapshot(); }); - it('should toggle motion indicator visibility', () => { - getComponentRefByType.mockReturnValue({ type: KnownComponentType.MotionIndicator }); - useStore('default').setState(baseState); - - const { container } = render(); - - const item = container.querySelector(`#${KnownComponentType.MotionIndicator}`); - - fireEvent.click(item!); - - expect(toggleMotionIndicatorVisibilityMock).toBeCalled(); - }); - it('should call setActiveCameraSettings when camera clicked', () => { - getComponentRefByType.mockReturnValue({}); const document = { nodeMap: { 'camera-uuid': cameraNode, diff --git a/packages/scene-composer/src/components/panels/__tests__/__snapshots__/SettingsPanel.spec.tsx.snap b/packages/scene-composer/src/components/panels/__tests__/__snapshots__/SettingsPanel.spec.tsx.snap index f6f9c4519..bf16441c9 100644 --- a/packages/scene-composer/src/components/panels/__tests__/__snapshots__/SettingsPanel.spec.tsx.snap +++ b/packages/scene-composer/src/components/panels/__tests__/__snapshots__/SettingsPanel.spec.tsx.snap @@ -1,7 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SettingsPanel contains expected elements. SettingsPanel contains expected elements for features [object Object] 1`] = ` +exports[`SettingsPanel contains expected elements. should contains default elements in editing mode. 1`] = `
+
+
+ Motion indicator +
+
+ Visibility +
+
+ +
+
+`; + +exports[`SettingsPanel contains expected elements. should contains default elements in viewing mode. 1`] = ` +
+
-
-
-
- Auto rescale -
-
+ Motion indicator +
+
+ Visibility
`; -exports[`SettingsPanel contains expected elements. SettingsPanel contains expected elements for features [object Object] 2`] = ` +exports[`SettingsPanel contains expected elements. should contains tag settings element. 1`] = `
-
-
-
+ Motion indicator +
+
+ Visibility
+
+ +
`; diff --git a/packages/scene-composer/src/components/panels/__tests__/__snapshots__/TopBar.spec.tsx.snap b/packages/scene-composer/src/components/panels/__tests__/__snapshots__/TopBar.spec.tsx.snap new file mode 100644 index 000000000..42974adfd --- /dev/null +++ b/packages/scene-composer/src/components/panels/__tests__/__snapshots__/TopBar.spec.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should not render without camera view 1`] = `
`; + +exports[` should render Cameras Drop down with camera view 1`] = ` +.c0 { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-box-pack: right; + -webkit-justify-content: right; + -ms-flex-pack: right; + justify-content: right; +} + +
+
+
    +
  • + Camera +
  • +
+
+
+`; diff --git a/packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.spec.tsx b/packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.spec.tsx new file mode 100644 index 000000000..acb8eac79 --- /dev/null +++ b/packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.spec.tsx @@ -0,0 +1,28 @@ +import { render } from '@testing-library/react'; +import React from 'react'; + +import { KnownComponentType } from '../../../interfaces'; +import { useStore } from '../../../store'; + +import { MotionIndicatorVisibilityToggle } from './MotionIndicatorVisibilityToggle'; + +describe('MotionIndicatorVisibilityToggle', () => { + const getComponentRefByType = jest.fn(); + + const createState = (visible: boolean) => ({ + noHistoryStates: { + ...useStore('default').getState().noHistoryStates, + motionIndicatorVisible: visible, + toggleMotionIndicatorVisibility: jest.fn(), + }, + getComponentRefByType, + }); + + it('should render correctly', async () => { + getComponentRefByType.mockReturnValue({ type: KnownComponentType.MotionIndicator }); + useStore('default').setState(createState(true)); + const { container } = render(); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.tsx b/packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.tsx new file mode 100644 index 000000000..c415638aa --- /dev/null +++ b/packages/scene-composer/src/components/panels/scene-settings/MotionIndicatorVisibilityToggle.tsx @@ -0,0 +1,34 @@ +import React, { useContext, useMemo } from 'react'; +import { useIntl } from 'react-intl'; +import { Box, Toggle } from '@awsui/components-react'; + +import { useStore, useViewOptionState } from '../../../store'; +import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext'; +import { KnownComponentType } from '../../../interfaces'; + +export const MotionIndicatorVisibilityToggle: React.FC = () => { + const sceneComposerId = useContext(sceneComposerIdContext); + const { motionIndicatorVisible, toggleMotionIndicatorVisibility } = useViewOptionState(sceneComposerId); + const getComponentRefByType = useStore(sceneComposerId)((state) => state.getComponentRefByType); + const componentNodeMap = useStore(sceneComposerId)((state) => state.document.componentNodeMap); + const { formatMessage } = useIntl(); + + const hasMotionIndicator = useMemo(() => { + return Object.keys(getComponentRefByType(KnownComponentType.MotionIndicator)).length > 0; + }, [componentNodeMap, getComponentRefByType]); + + return ( + + + {formatMessage({ description: 'Sub section label', defaultMessage: 'Motion indicator' })} + + + {formatMessage({ description: 'Toggle label', defaultMessage: 'Visibility' })} + + + ); +}; diff --git a/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.spec.tsx b/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.spec.tsx index c96052f80..e2b4061f1 100644 --- a/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.spec.tsx +++ b/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.spec.tsx @@ -6,6 +6,7 @@ import wrapper from '@awsui/components-react/test-utils/dom'; import { useStore } from '../../../store'; import { KnownComponentType, KnownSceneProperty } from '../../../interfaces'; import { DEFAULT_TAG_GLOBAL_SETTINGS } from '../../../common/constants'; +import useTagSettings from '../../../hooks/useTagSettings'; import { SceneTagSettingsEditor } from './SceneTagSettingsEditor'; @@ -13,6 +14,8 @@ jest.mock('@awsui/components-react', () => ({ ...jest.requireActual('@awsui/components-react'), })); +jest.mock('../../../hooks/useTagSettings', () => jest.fn()); + const sleep = async (timeout: number) => { return new Promise((resolve) => setTimeout(resolve, timeout)); }; @@ -20,9 +23,16 @@ const sleep = async (timeout: number) => { describe('SceneTagSettingsEditor', () => { const setScenePropertyMock = jest.fn(); const getScenePropertyMock = jest.fn(); + const isViewingMock = jest.fn(); + const setTagSettingsMock = jest.fn(); const baseState = { setSceneProperty: setScenePropertyMock, getSceneProperty: getScenePropertyMock, + isViewing: isViewingMock, + noHistoryStates: { + ...useStore('default').getState().noHistoryStates, + setTagSettings: setTagSettingsMock, + }, }; beforeEach(() => { @@ -30,10 +40,12 @@ describe('SceneTagSettingsEditor', () => { getScenePropertyMock.mockReturnValue({ [KnownComponentType.Tag]: DEFAULT_TAG_GLOBAL_SETTINGS, }); + (useTagSettings as jest.Mock).mockReturnValue(DEFAULT_TAG_GLOBAL_SETTINGS); + isViewingMock.mockReturnValue(false); jest.clearAllMocks(); }); - it('should update store when input value changed', async () => { + it('should update store and view options when input value changed', async () => { useStore('default').setState(baseState); const { container } = render(); const polarisWrapper = wrapper(container); @@ -47,6 +59,11 @@ describe('SceneTagSettingsEditor', () => { expect(setScenePropertyMock).toBeCalledWith(KnownSceneProperty.ComponentSettings, { [KnownComponentType.Tag]: { ...DEFAULT_TAG_GLOBAL_SETTINGS, scale: 11 }, }); + expect(setTagSettingsMock).toBeCalledTimes(1); + expect(setTagSettingsMock).toBeCalledWith({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + scale: 11, + }); // change input should update store with 0 when value is invalid input?.setInputValue('-11'); @@ -54,22 +71,47 @@ describe('SceneTagSettingsEditor', () => { expect(setScenePropertyMock).toBeCalledWith(KnownSceneProperty.ComponentSettings, { [KnownComponentType.Tag]: { ...DEFAULT_TAG_GLOBAL_SETTINGS, scale: 0 }, }); + expect(setTagSettingsMock).toBeCalledTimes(2); + expect(setTagSettingsMock).toBeCalledWith({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + scale: 0, + }); }); - it('should update input value when store value changed', async () => { + it('should only store input value change to view options when in viewing mode', async () => { useStore('default').setState(baseState); + isViewingMock.mockReturnValue(true); const { container } = render(); const polarisWrapper = wrapper(container); const input = polarisWrapper.findInput(); + expect(input).toBeDefined(); + + // change input should update store when value is valid + input?.setInputValue('11'); + expect(setScenePropertyMock).not.toBeCalled(); + expect(setTagSettingsMock).toBeCalledTimes(1); + expect(setTagSettingsMock).toBeCalledWith({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + scale: 11, + }); + }); + + it('should update input value when store value changed', async () => { + useStore('default').setState(baseState); + const { container, rerender } = render(); + const polarisWrapper = wrapper(container); + const input = polarisWrapper.findInput(); + expect(input).toBeDefined(); expect(input?.findNativeInput().getElement().value).toEqual(String(DEFAULT_TAG_GLOBAL_SETTINGS.scale)); - getScenePropertyMock.mockReturnValue({ - [KnownComponentType.Tag]: { ...DEFAULT_TAG_GLOBAL_SETTINGS, scale: 6 }, + (useTagSettings as jest.Mock).mockReturnValue({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + scale: 6, }); act(() => { - useStore('default').setState({ ...baseState }); + rerender(); }); expect(polarisWrapper.findInput()?.findNativeInput().getElement().value).toEqual('6'); @@ -92,6 +134,11 @@ describe('SceneTagSettingsEditor', () => { autoRescale: !DEFAULT_TAG_GLOBAL_SETTINGS.autoRescale, }, }); + expect(setTagSettingsMock).toBeCalledTimes(1); + expect(setTagSettingsMock).toBeCalledWith({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + autoRescale: !DEFAULT_TAG_GLOBAL_SETTINGS.autoRescale, + }); }); it('should update store when slider value changed', async () => { @@ -117,14 +164,27 @@ describe('SceneTagSettingsEditor', () => { // wait for setTimeOut in code await sleep(2); + fireEvent.mouseDown(slider[0]); // update slider value should update store fireEvent.change(slider[0], { target: { value: '3' } }); }); + // set value only after mouse up + expect(setScenePropertyMock).not.toBeCalled(); + + act(() => { + fireEvent.mouseUp(slider[0]); + }); + expect(setScenePropertyMock).toBeCalledTimes(1); expect(setScenePropertyMock).toBeCalledWith(KnownSceneProperty.ComponentSettings, { [KnownComponentType.Tag]: { ...DEFAULT_TAG_GLOBAL_SETTINGS, scale: 3 }, }); + expect(setTagSettingsMock).toBeCalledTimes(1); + expect(setTagSettingsMock).toBeCalledWith({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + scale: 3, + }); // hide slider when slider lost focus act(() => { @@ -132,4 +192,38 @@ describe('SceneTagSettingsEditor', () => { }); expect(screen.queryAllByTestId('slider').length).toBe(0); }); + + it('should update view options immediately when slider value changed in viewing mode', async () => { + useStore('default').setState(baseState); + isViewingMock.mockReturnValue(true); + const { container } = render(); + const polarisWrapper = wrapper(container); + + const input = polarisWrapper.findInput(); + act(() => { + input?.focus(); + }); + + const slider = screen.queryAllByTestId('slider'); + expect(slider.length).toBe(1); + + await act(async () => { + slider[0].focus(); + + input?.blur(); + // wait for setTimeOut in code + await sleep(2); + + fireEvent.mouseDown(slider[0]); + // update slider value should update store + fireEvent.change(slider[0], { target: { value: '3' } }); + }); + + expect(setScenePropertyMock).not.toBeCalled(); + expect(setTagSettingsMock).toBeCalledTimes(1); + expect(setTagSettingsMock).toBeCalledWith({ + ...DEFAULT_TAG_GLOBAL_SETTINGS, + scale: 3, + }); + }); }); diff --git a/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.tsx b/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.tsx index 0bc054abb..8dbdeac3a 100644 --- a/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.tsx +++ b/packages/scene-composer/src/components/panels/scene-settings/SceneTagSettingsEditor.tsx @@ -3,11 +3,11 @@ import { useIntl } from 'react-intl'; import { Checkbox, FormField, Grid, Input } from '@awsui/components-react'; import useLifecycleLogging from '../../../logger/react-logger/hooks/useLifecycleLogging'; -import { useStore } from '../../../store'; +import { useStore, useViewOptionState } from '../../../store'; import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext'; import { IComponentSettingsMap, ITagSettings, KnownComponentType, KnownSceneProperty } from '../../../interfaces'; -import { componentSettingsSelector } from '../../../utils/componentSettingsUtils'; import { Slider } from '../Slider'; +import useTagSettings from '../../../hooks/useTagSettings'; export const SceneTagSettingsEditor: React.FC = () => { useLifecycleLogging('SceneTagSettingsEditor'); @@ -16,9 +16,9 @@ export const SceneTagSettingsEditor: React.FC = () => { const intl = useIntl(); const setSceneProperty = useStore(sceneComposerId)((state) => state.setSceneProperty); const getSceneProperty = useStore(sceneComposerId)((state) => state.getSceneProperty); - const tagSettings: ITagSettings = useStore(sceneComposerId)((state) => - componentSettingsSelector(state, KnownComponentType.Tag), - ); + const isViewing = useStore(sceneComposerId)((state) => state.isViewing()); + const setTagSettings = useViewOptionState(sceneComposerId).setTagSettings; + const tagSettings: ITagSettings = useTagSettings(); const [dirty, setDirty] = useState(false); const [focusInput, setFocusInput] = useState(false); const [focusSlider, setFocusSlider] = useState(false); @@ -32,12 +32,15 @@ export const SceneTagSettingsEditor: React.FC = () => { ...tagSettings, ...settingsPartial, }; - const newComponentSettings: IComponentSettingsMap = { - ...getSceneProperty(KnownSceneProperty.ComponentSettings), - [KnownComponentType.Tag]: newTagSettings, - }; + if (!isViewing) { + const newComponentSettings: IComponentSettingsMap = { + ...getSceneProperty(KnownSceneProperty.ComponentSettings), + [KnownComponentType.Tag]: newTagSettings, + }; - setSceneProperty(KnownSceneProperty.ComponentSettings, newComponentSettings); + setSceneProperty(KnownSceneProperty.ComponentSettings, newComponentSettings); + } + setTagSettings(newTagSettings); }, [tagSettings, getSceneProperty, setSceneProperty], ); @@ -75,13 +78,14 @@ export const SceneTagSettingsEditor: React.FC = () => { [setInternalScale, setDirty], ); - // Save scale changes to scene file + // Save scale changes to settings useEffect(() => { - if (dirty && !focusInput && !draggingSlider) { + // In viewing mode, dragging slider will update component immediately. + if (dirty && !focusInput && (!draggingSlider || isViewing)) { updateSettings({ scale: internalScale }); setDirty(false); } - }, [updateSettings, setDirty, dirty, focusInput, internalScale, draggingSlider]); + }, [updateSettings, setDirty, dirty, focusInput, internalScale, draggingSlider, isViewing]); // Update internal when scale in store is changed useEffect(() => { diff --git a/packages/scene-composer/src/components/panels/scene-settings/__snapshots__/MotionIndicatorVisibilityToggle.spec.tsx.snap b/packages/scene-composer/src/components/panels/scene-settings/__snapshots__/MotionIndicatorVisibilityToggle.spec.tsx.snap new file mode 100644 index 000000000..0f1ebd70b --- /dev/null +++ b/packages/scene-composer/src/components/panels/scene-settings/__snapshots__/MotionIndicatorVisibilityToggle.spec.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MotionIndicatorVisibilityToggle should render correctly 1`] = ` +
+
+ Motion indicator +
+
+ Visibility +
+
+`; diff --git a/packages/scene-composer/src/hooks/useTagSettings.spec.tsx b/packages/scene-composer/src/hooks/useTagSettings.spec.tsx new file mode 100644 index 000000000..a751b1e48 --- /dev/null +++ b/packages/scene-composer/src/hooks/useTagSettings.spec.tsx @@ -0,0 +1,51 @@ +import { renderHook } from '@testing-library/react'; + +import { useStore } from '../store'; +import { componentSettingsSelector } from '../utils/componentSettingsUtils'; + +import useTagSettings from './useTagSettings'; + +jest.mock('../utils/componentSettingsUtils'); + +describe('useTagSettings', () => { + const isViewingMock = jest.fn(); + const settingsDocument = { scale: 2, autoScale: true }; + const settingsViewOption = { scale: 4, autoRescale: false }; + + beforeEach(() => { + (componentSettingsSelector as jest.Mock).mockReturnValue(settingsDocument); + useStore('default').setState({ + noHistoryStates: { + ...useStore('default').getState().noHistoryStates, + tagSettings: settingsViewOption, + }, + isViewing: isViewingMock, + }); + isViewingMock.mockReturnValue(true); + }); + + it('should get tag settings from document in editing mode', () => { + isViewingMock.mockReturnValue(false); + const settings = renderHook(() => useTagSettings()).result.current; + + expect(settings).toEqual(settingsDocument); + }); + + it('should get tag settings from document in viewing mode when viewing settings is not defined', () => { + useStore('default').setState({ + noHistoryStates: { + ...useStore('default').getState().noHistoryStates, + tagSettings: undefined, + }, + }); + + const settings = renderHook(() => useTagSettings()).result.current; + expect(settings).toEqual(settingsDocument); + }); + + it('should get tag settings from view options in viewing mode', () => { + const settings = renderHook(() => useTagSettings()).result.current; + + expect(settings).toEqual(settingsViewOption); + }); +}); diff --git a/packages/scene-composer/src/hooks/useTagSettings.ts b/packages/scene-composer/src/hooks/useTagSettings.ts new file mode 100644 index 000000000..0d69aa8fb --- /dev/null +++ b/packages/scene-composer/src/hooks/useTagSettings.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; + +import { useSceneComposerId } from '../common/sceneComposerIdContext'; +import { ITagSettings, KnownComponentType } from '../interfaces'; +import { useStore, useViewOptionState } from '../store'; +import { componentSettingsSelector } from '../utils/componentSettingsUtils'; + +const useTagSettings = () => { + const sceneComposerId = useSceneComposerId(); + const isViewing = useStore(sceneComposerId)((state) => state.isViewing()); + const documentTagSettings: ITagSettings = useStore(sceneComposerId)((state) => + componentSettingsSelector(state, KnownComponentType.Tag), + ); + const viewingTagSettings: ITagSettings | undefined = useViewOptionState(sceneComposerId).tagSettings; + const tagSettings: ITagSettings = useMemo(() => { + if (isViewing && viewingTagSettings) { + return viewingTagSettings; + } else { + return documentTagSettings; + } + }, [isViewing, documentTagSettings, viewingTagSettings]); + + return tagSettings; +}; + +export default useTagSettings; diff --git a/packages/scene-composer/src/layouts/SceneLayout/SceneLayout.tsx b/packages/scene-composer/src/layouts/SceneLayout/SceneLayout.tsx index 19fd76b3b..eeda696fa 100644 --- a/packages/scene-composer/src/layouts/SceneLayout/SceneLayout.tsx +++ b/packages/scene-composer/src/layouts/SceneLayout/SceneLayout.tsx @@ -132,6 +132,9 @@ const SceneLayout: FC = ({ }; const leftPanelViewModeProps = { [intl.formatMessage({ defaultMessage: 'Hierarchy', description: 'Panel Tab title' })]: , + [intl.formatMessage({ defaultMessage: 'Settings', description: 'Panel Tab title' })]: ( + + ), }; const rightPanelProps = { [intl.formatMessage({ defaultMessage: 'Inspector', description: 'Panel Tab title' })]: , diff --git a/packages/scene-composer/src/layouts/SceneLayout/__snapshots__/SceneLayout.spec.tsx.snap b/packages/scene-composer/src/layouts/SceneLayout/__snapshots__/SceneLayout.spec.tsx.snap index d85b9877b..5f1f7097a 100644 --- a/packages/scene-composer/src/layouts/SceneLayout/__snapshots__/SceneLayout.spec.tsx.snap +++ b/packages/scene-composer/src/layouts/SceneLayout/__snapshots__/SceneLayout.spec.tsx.snap @@ -1815,6 +1815,11 @@ exports[`SceneLayout should render correctly in Viewing mode 1`] = ` "id": "Hierarchy", "label": "Hierarchy", }, + Object { + "content": , + "id": "Settings", + "label": "Settings", + }, ] } /> @@ -2423,6 +2428,11 @@ exports[`SceneLayout should render correctly in Viewing mode with modal 1`] = ` "id": "Hierarchy", "label": "Hierarchy", }, + Object { + "content": , + "id": "Settings", + "label": "Settings", + }, ] } /> diff --git a/packages/scene-composer/src/store/Store.ts b/packages/scene-composer/src/store/Store.ts index 1d6372f6b..3943d0ccc 100644 --- a/packages/scene-composer/src/store/Store.ts +++ b/packages/scene-composer/src/store/Store.ts @@ -148,6 +148,8 @@ const nodeErrorStateSelector = (state: RootState) => ({ const viewOptionStateSelector = (state: RootState) => ({ motionIndicatorVisible: state.noHistoryStates.motionIndicatorVisible, toggleMotionIndicatorVisibility: state.noHistoryStates.toggleMotionIndicatorVisibility, + tagSettings: state.noHistoryStates.tagSettings, + setTagSettings: state.noHistoryStates.setTagSettings, }); /** diff --git a/packages/scene-composer/src/store/StoreOperations.ts b/packages/scene-composer/src/store/StoreOperations.ts index 8a85d6f72..897b30ccf 100644 --- a/packages/scene-composer/src/store/StoreOperations.ts +++ b/packages/scene-composer/src/store/StoreOperations.ts @@ -39,7 +39,7 @@ export type SceneComposerDocumentOperation = export type SceneComposerDataOperation = 'setDataInput' | 'setDataBindingTemplate'; -export type SceneComposerViewOptionOperation = 'toggleMotionIndicatorVisibility'; +export type SceneComposerViewOptionOperation = 'toggleMotionIndicatorVisibility' | 'setTagSettings'; export type SceneComposerOperation = | SceneComposerEditorOperation @@ -87,5 +87,7 @@ export const SceneComposerOperationTypeMap: Record { + it('should be able to change motioon indicator visibility', () => { + const draft = { lastOperation: undefined, noHistoryStates: { motionIndicatorVisible: false } }; + + const get = jest.fn(); + const set = jest.fn((callback) => callback(draft)); + + const { toggleMotionIndicatorVisibility } = createViewOptionStateSlice(set, get); + toggleMotionIndicatorVisibility(); + + expect(draft.lastOperation!).toEqual('toggleMotionIndicatorVisibility'); + expect(draft.noHistoryStates.motionIndicatorVisible).toBeTruthy(); + + toggleMotionIndicatorVisibility(); + + expect(draft.lastOperation!).toEqual('toggleMotionIndicatorVisibility'); + expect(draft.noHistoryStates.motionIndicatorVisible).toBeFalsy(); + }); + + it('should be able to change tag settings', () => { + const draft = { lastOperation: undefined, noHistoryStates: { tagSettings: {} } }; + + const get = jest.fn(); + const set = jest.fn((callback) => callback(draft)); + + const { setTagSettings } = createViewOptionStateSlice(set, get); + setTagSettings({ scale: 3.3, autoRescale: true }); + + expect(draft.lastOperation!).toEqual('setTagSettings'); + expect((draft.noHistoryStates.tagSettings as ITagSettings).scale).toEqual(3.3); + expect((draft.noHistoryStates.tagSettings as ITagSettings).autoRescale).toBeTruthy(); + }); +}); diff --git a/packages/scene-composer/src/store/slices/ViewOptionStateSlice.ts b/packages/scene-composer/src/store/slices/ViewOptionStateSlice.ts index 0359a2083..ca353d0a8 100644 --- a/packages/scene-composer/src/store/slices/ViewOptionStateSlice.ts +++ b/packages/scene-composer/src/store/slices/ViewOptionStateSlice.ts @@ -1,19 +1,23 @@ import { GetState, SetState, StoreApi } from 'zustand'; +import { ITagSettings } from '../../interfaces'; import { RootState } from '../Store'; export interface IViewOptionStateSlice { motionIndicatorVisible: boolean; + tagSettings?: ITagSettings; toggleMotionIndicatorVisibility: () => void; + setTagSettings: (settings: ITagSettings) => void; } export const createViewOptionStateSlice = ( set: SetState, get: GetState, - api: StoreApi, + api?: StoreApi, ): IViewOptionStateSlice => ({ motionIndicatorVisible: true, + tagSettings: undefined, toggleMotionIndicatorVisibility: () => { set((draft) => { @@ -21,4 +25,10 @@ export const createViewOptionStateSlice = ( draft.lastOperation = 'toggleMotionIndicatorVisibility'; }); }, + setTagSettings: (settings) => { + set((draft) => { + draft.noHistoryStates.tagSettings = settings; + draft.lastOperation = 'setTagSettings'; + }); + }, }); diff --git a/packages/scene-composer/tests/store/slices/ViewOptionStateSlice.spec.ts b/packages/scene-composer/tests/store/slices/ViewOptionStateSlice.spec.ts deleted file mode 100644 index 7cd857913..000000000 --- a/packages/scene-composer/tests/store/slices/ViewOptionStateSlice.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createViewOptionStateSlice } from '../../../src/store/slices/ViewOptionStateSlice'; - -describe('createViewOptionStateSlice', () => { - it('should be able to change motioon indicator visibility', () => { - const draft = { lastOperation: undefined, noHistoryStates: { motionIndicatorVisible: false } }; - - const get = jest.fn(); - const set = jest.fn(((callback) => callback(draft)) as any); - - const { toggleMotionIndicatorVisibility } = createViewOptionStateSlice(set, get, undefined as any); - toggleMotionIndicatorVisibility(); - - expect(draft.lastOperation!).toEqual('toggleMotionIndicatorVisibility'); - expect(draft.noHistoryStates.motionIndicatorVisible).toBeTruthy(); - - toggleMotionIndicatorVisibility(); - - expect(draft.lastOperation!).toEqual('toggleMotionIndicatorVisibility'); - expect(draft.noHistoryStates.motionIndicatorVisible).toBeFalsy(); - }); -}); diff --git a/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json b/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json index 4b2834f31..ce85d34f2 100644 --- a/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json +++ b/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json @@ -27,6 +27,10 @@ "note": "Menu Item", "text": "Rotate object" }, + "0JZgMB": { + "note": "Sub section label", + "text": "Motion indicator" + }, "0awbFz": { "note": "ExpandableInfoSection Title", "text": "Tag Settings" @@ -163,6 +167,10 @@ "note": "Status indicator label", "text": "Camera view saved" }, + "As6z+e": { + "note": "ExpandableInfoSection Title", + "text": "Current View Settings" + }, "B5EPL8": { "note": "Menu Item", "text": "Add 3D model" @@ -191,10 +199,6 @@ "note": "Menu Item label", "text": "3D model" }, - "DVJ3oz": { - "note": "view options dropdown button text", - "text": "View Options" - }, "ENcryP": { "note": "Panel Tab title", "text": "Hierarchy" @@ -659,10 +663,6 @@ "note": "Motion Indicator Shape in a dropdown menu", "text": "Linear Plane" }, - "oGsfgK": { - "note": "dropdown button option text for motion indicator component", - "text": "Motion indicator" - }, "oiGDox": { "note": "Field label", "text": "Value" @@ -719,6 +719,10 @@ "note": "Menu label", "text": "Rotate" }, + "sEEHSK": { + "note": "Toggle label", + "text": "Visibility" + }, "sI/yrM": { "note": "Distance Units in a dropdown menu", "text": "centimeters"