Skip to content

Commit

Permalink
fix(composer): bug fixes for dynamic scene
Browse files Browse the repository at this point in the history
  • Loading branch information
sheilaXu authored and mumanity committed Oct 2, 2023
1 parent e126b53 commit 2f3b396
Show file tree
Hide file tree
Showing 24 changed files with 383 additions and 205 deletions.
7 changes: 5 additions & 2 deletions packages/scene-composer/src/SceneViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
import { isEqual } from 'lodash';
import styled from 'styled-components';

import { COMPOSER_FEATURES, KnownComponentType, SceneViewerProps } from './interfaces';
import { COMPOSER_FEATURES, ISelectedDataBinding, KnownComponentType, SceneViewerProps } from './interfaces';
import { SceneComposerInternal, useSceneComposerApi } from './components/SceneComposerInternal';

const SceneComposerContainer = styled.div`
Expand All @@ -24,7 +24,7 @@ export const SceneViewer: React.FC<SceneViewerProps> = ({ sceneComposerId, confi
return sceneComposerId || uuid();
}, [sceneComposerId]);
const composerApis = useSceneComposerApi(composerId);
const prevSelectedRef: any = useRef();
const prevSelectedRef = useRef<ISelectedDataBinding | undefined>();
const [sceneLoaded, setSceneLoaded] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -68,6 +68,9 @@ export const SceneViewer: React.FC<SceneViewerProps> = ({ sceneComposerId, confi
...(config || {}),
mode: 'Viewing',
featureConfig: {
// Allow beta users to override feature config
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...((config as any)?.featureConfig || {}),
[COMPOSER_FEATURES.SceneHierarchySearch]: true,
[COMPOSER_FEATURES.SceneHierarchyReorder]: true,
[COMPOSER_FEATURES.SubModelSelection]: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ModelType } from '../../../../../models/SceneModels';
import useFeature from '../../../../../hooks/useFeature';
import { findComponentByType } from '../../../../../utils/nodeUtils';
import { sceneComposerIdContext } from '../../../../../common/sceneComposerIdContext';
import { isDynamicNode } from '../../../../../utils/entityModelUtils/sceneUtils';

import SceneNodeLabel from './SceneNodeLabel';
import { AcceptableDropTypes, EnhancedTree, EnhancedTreeItem } from './constants';
Expand Down Expand Up @@ -52,6 +53,7 @@ const SceneHierarchyTreeItem: FC<SceneHierarchyTreeItemProps> = ({
const [{ variation: subModelSelectionEnabled }] = useFeature(COMPOSER_FEATURES[COMPOSER_FEATURES.SubModelSelection]);
const showSubModel = subModelSelectionEnabled === 'T1' && isValidModelRef && !!model && !isViewing();
const isSubModel = !!findComponentByType(node, KnownComponentType.SubModelRef);
const isDynamic = isDynamicNode(node);

const { searchTerms } = useSceneHierarchyData();
const isSearching = searchTerms !== '';
Expand Down Expand Up @@ -102,9 +104,9 @@ const SceneHierarchyTreeItem: FC<SceneHierarchyTreeItemProps> = ({
selectionMode={selectionMode}
onSelected={isViewing() ? onActivated : onToggle}
onActivated={onActivated}
acceptDrop={AcceptableDropTypes}
acceptDrop={isDynamic ? [] : AcceptableDropTypes} // TODO: dynamic scene doesn't support hierarchy yet, disable drag for dynamic node
onDropped={dropHandler}
draggable={enableDragAndDrop && !isViewing() && !isSubModel}
draggable={enableDragAndDrop && !isViewing() && !isSubModel && !isDynamic} // TODO: dynamic scene doesn't support hierarchy yet, disable drag for dynamic node
dataType={componentTypes && componentTypes.length > 0 ? componentTypes[0] : /* istanbul ignore next */ 'default'} // TODO: This is somewhat based on the current assumption that items will currently only really have one componentType
data={{ ref: key }}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import React, { useCallback } from 'react';
import { render } from '@testing-library/react';

// eslint-disable-next-line import/order
import mockComponent from '../../../../../../../__mocks__/mockComponent';

jest.doMock('../constants', () => ({
EnhancedTree: mockComponent('EnhancedTree'),
EnhancedTreeItem: mockComponent('EnhancedTreeItem'),
AcceptableDropTypes: 'AcceptableDropTypes',
}));
jest.doMock('../../SubModelTree', () => mockComponent('SubModelTree'));
jest.doMock('../SceneNodeLabel', () => mockComponent('SceneNodeLabel'));

import { useSceneHierarchyData, useChildNodes } from '../../../SceneHierarchyDataProvider';
import SceneHierarchyTreeItem from '../SceneHierarchyTreeItem';
import { KnownComponentType } from '../../../../../../interfaces';
import { isDynamicNode } from '../../../../../../utils/entityModelUtils/sceneUtils';

jest.mock('../../../../../../enhancers/draggable', () => (item: any) => item);
jest.mock('../../../../../../enhancers/droppable', () => (item: any) => item);
jest.mock('../../../SceneHierarchyDataProvider');
jest.mock('../../SubModelTree', () => (props) => <div data-mocked='SubModelTree' {...props} />);
jest.mock('../constants', () => ({
EnhancedTree: 'EnhancedTree',
EnhancedTreeItem: 'EnhancedTreeItem',
AcceptableDropTypes: 'AcceptableDropTypes',
}));

jest.mock('react', () => ({
...jest.requireActual('react'),
useCallback: jest.fn(),
}));

jest.mock('../../../../../../utils/entityModelUtils/sceneUtils');

describe('SceneHierarchyTreeItem', () => {
const select = jest.fn();
const unselect = jest.fn();
Expand All @@ -46,6 +54,7 @@ describe('SceneHierarchyTreeItem', () => {
selectionMode: 'single',
remove,
isViewing,
validationErrors: {},
};
});

Expand Down Expand Up @@ -127,4 +136,15 @@ describe('SceneHierarchyTreeItem', () => {

expect(container).toMatchSnapshot();
});

it('should render dynamic node with drag and drop disabled', () => {
isViewing.mockImplementationOnce(() => false);
(isDynamicNode as jest.Mock).mockReturnValueOnce(true);

const { container } = render(
<SceneHierarchyTreeItem objectRef='1' name='Label 1' componentTypes={[KnownComponentType.Tag]} />,
);

expect(container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,63 @@

exports[`SceneHierarchyTreeItem should bubble up when expanded 1`] = `
<div>
<enhancedtreeitem
<div
acceptdrop="AcceptableDropTypes"
data="[object Object]"
data="{\\"ref\\":\\"1\\"}"
data-mocked="EnhancedTreeItem"
datatype="modelRef"
labeltext="[object Object]"
selectionmode="single"
/>
>
<div>
<div
componenttypes="[\\"modelRef\\"]"
data-mocked="SceneNodeLabel"
labeltext="Label 1"
objectref="1"
/>
</div>
</div>
</div>
`;

exports[`SceneHierarchyTreeItem should render SubModelTree when item has a model, and not in view mode 1`] = `
<div>
<enhancedtreeitem
<div
acceptdrop="AcceptableDropTypes"
data="[object Object]"
data="{\\"ref\\":\\"1\\"}"
data-mocked="EnhancedTreeItem"
datatype="ModelRef"
labeltext="[object Object]"
selectionmode="single"
/>
>
<div>
<div
componenttypes="[\\"ModelRef\\"]"
data-mocked="SceneNodeLabel"
labeltext="Label 1"
objectref="1"
/>
</div>
</div>
</div>
`;

exports[`SceneHierarchyTreeItem should render dynamic node with drag and drop disabled 1`] = `
<div>
<div
acceptdrop="[]"
data="{\\"ref\\":\\"1\\"}"
data-mocked="EnhancedTreeItem"
datatype="Tag"
selectionmode="single"
>
<div>
<div
componenttypes="[\\"Tag\\"]"
data-mocked="SceneNodeLabel"
labeltext="Label 1"
objectref="1"
/>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { findComponentByType, isEnvironmentNode } from '../../utils/nodeUtils';
import { isLinearPlaneMotionIndicator } from '../../utils/sceneComponentUtils';
import { toNumber } from '../../utils/stringUtils';
import { RecursivePartial } from '../../utils/typeUtils';
import { isDynamicNode } from '../../utils/entityModelUtils/sceneUtils';

import { AddComponentMenu } from './AddComponentMenu';
import { ExpandableInfoSection, Matrix3XInputGrid, TextInput, Triplet } from './CommonPanelComponents';
Expand Down Expand Up @@ -94,6 +95,8 @@ export const SceneNodeInspectorPanel: React.FC = () => {
const isOverlayComponent = !!findComponentByType(selectedSceneNode, KnownComponentType.DataOverlay);
const isSubModelComponent = !!findComponentByType(selectedSceneNode, KnownComponentType.SubModelRef);

const debounceInterval = isDynamicNode(selectedSceneNode) ? 1000 : 100;

const transformVisible = !isSubModelComponent || subModelMovementEnabled;

const shouldShowScale = !(isTagComponent || isCameraComponent);
Expand Down Expand Up @@ -198,7 +201,7 @@ export const SceneNodeInspectorPanel: React.FC = () => {
onChange={debounce((items) => {
handleInputChanges({ transform: { position: items } });
applySnapToFloorConstraint();
}, 100)}
}, debounceInterval)}
/>
<Matrix3XInputGrid
name={intl.formatMessage({ defaultMessage: 'Rotation', description: 'Input Grid title name' })}
Expand All @@ -211,7 +214,7 @@ export const SceneNodeInspectorPanel: React.FC = () => {
onChange={debounce((items) => {
handleInputChanges({ transform: { rotation: items } });
applySnapToFloorConstraint();
}, 100)}
}, debounceInterval)}
/>
{shouldShowScale && (
<Matrix3XInputGrid
Expand All @@ -229,7 +232,7 @@ export const SceneNodeInspectorPanel: React.FC = () => {
onChange={debounce((items) => {
handleInputChanges({ transform: { scale: items } });
applySnapToFloorConstraint();
}, 100)}
}, debounceInterval)}
/>
)}
{isModelComponent && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { defineMessages, useIntl } from 'react-intl';
import { getGlobalSettings } from '../../../common/GlobalSettings';
import { SCENE_ICONS } from '../../../common/constants';
import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext';
import useDynamicScene from '../../../hooks/useDynamicScene';
import {
COMPOSER_FEATURES,
DefaultAnchorStatus,
Expand All @@ -22,6 +21,7 @@ import { convertToIotTwinMakerNamespace, getSceneResourceInfo } from '../../../u
import { colors } from '../../../utils/styleUtils';
import { TextInput } from '../CommonPanelComponents';
import { IComponentEditorProps } from '../ComponentEditor';
import { isDynamicNode } from '../../../utils/entityModelUtils/sceneUtils';

import { ValueDataBindingBuilder } from './common/ValueDataBindingBuilder';
import { ColorSelectorCombo } from './tag-style/ColorSelectorCombo/ColorSelectorCombo';
Expand Down Expand Up @@ -64,7 +64,7 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({

const intl = useIntl();

const dynamicSceneEnabled = useDynamicScene();
const isDynamic = isDynamicNode(node);

const ruleMapIds = listSceneRuleMapIds();
const filteredList: string[] = useMemo(
Expand All @@ -88,7 +88,7 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({
const componentPartialWithRef: ISceneComponentInternal = { ref: component.ref, ...componentPartial };
updateComponentInternal(node.ref, componentPartialWithRef, replace);
},
dynamicSceneEnabled ? 1000 : 100,
isDynamic ? 1000 : 100,
), // TODO: Temporary solution for the error when updating entity too frequent. Will implement a better solution for GA.
[node.ref, component.ref],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('DataOverlayComponentEditor', () => {
};
const node = {
ref: 'node-ref',
properties: {},
} as ISceneNodeInternal;
const updateComponentInternalMock = jest.fn();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext';
import { Component } from '../../../models/SceneModels';
import { IDataOverlayComponentInternal, ISceneComponentInternal, useStore } from '../../../store';
import { IComponentEditorProps } from '../ComponentEditor';
import useDynamicScene from '../../../hooks/useDynamicScene';
import { isDynamicNode } from '../../../utils/entityModelUtils/sceneUtils';

import { ComponentWithDataBindings, DataBindingMapEditor } from './common/DataBindingMapEditor';

Expand All @@ -26,7 +26,7 @@ export const DataOverlayComponentEditor: React.FC<IDataOverlayComponentEditorPro
);
const [newRows, setNewRows] = useState<Component.DataOverlayMarkdownRow[]>(component.dataRows);

const dynamicSceneEnabled = useDynamicScene();
const isDynamic = isDynamicNode(node);

const { formatMessage } = useIntl();
const onUpdateCallback = useCallback(
Expand All @@ -35,7 +35,7 @@ export const DataOverlayComponentEditor: React.FC<IDataOverlayComponentEditorPro
const componentPartialWithRef = { ref: component.ref, ...componentPartial };
updateComponentInternal(node.ref, componentPartialWithRef as ISceneComponentInternal, replace);
},
dynamicSceneEnabled ? 1000 : 100,
isDynamic ? 1000 : 100,
), // TODO: Temporary solution for the error when updating entity too frequent. Will implement a better solution for GA.
[node.ref, component.ref],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('DataOverlayComponentEditorSnap', () => {
};
const node = {
ref: 'node-ref',
properties: {},
} as ISceneNodeInternal;

const baseState = {
Expand Down

0 comments on commit 2f3b396

Please sign in to comment.