Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(data overlay): add onWidgetClick and onSelectionChange event support to release 3.x #1776

Merged
merged 1 commit into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 21 additions & 45 deletions packages/scene-composer/src/components/StateManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import { ThreeEvent } from '@react-three/fiber';
import ab2str from 'arraybuffer-to-string';
import { combineProviders, DataStream, ProviderWithViewport, TimeSeriesData } from '@iot-app-kit/core';

import useLifecycleLogging from '../logger/react-logger/hooks/useLifecycleLogging';
import {
AdditionalComponentData,
ExternalLibraryConfig,
KnownComponentType,
KnownSceneProperty,
SceneComposerInternalProps,
} from '../interfaces';
import {
setDracoDecoder,
setFeatureConfig,
Expand All @@ -20,24 +12,32 @@ import {
setMetricRecorder,
setTwinMakerSceneMetadataModule,
} from '../common/GlobalSettings';
import { useSceneComposerId } from '../common/sceneComposerIdContext';
import { IAnchorComponentInternal, ICameraComponentInternal, RootState, useStore, useViewOptionState } from '../store';
import { createStandardUriModifier } from '../utils/uriModifiers';
import sceneDocumentSnapshotCreator from '../utils/sceneDocumentSnapshotCreator';
import { SceneLayout } from '../layouts/SceneLayout';
import { findComponentByType } from '../utils/nodeUtils';
import { applyDataBindingTemplate } from '../utils/dataBindingTemplateUtils';
import { combineTimeSeriesData, convertDataStreamsToDataInput } from '../utils/dataStreamUtils';
import useActiveCamera from '../hooks/useActiveCamera';
import useMatterportViewer from '../hooks/useMatterportViewer';
import { getCameraSettings } from '../utils/cameraUtils';
import {
MATTERPORT_ACCESS_TOKEN,
MATTERPORT_APPLICATION_KEY,
MATTERPORT_ERROR,
MATTERPORT_SECRET_ARN,
} from '../common/constants';
import { DisplayMessageCategory, IEntityBindingComponentInternal } from '../store/internalInterfaces';
import { DisplayMessageCategory } from '../store/internalInterfaces';
import { useSceneComposerId } from '../common/sceneComposerIdContext';
import useActiveCamera from '../hooks/useActiveCamera';
import useMatterportViewer from '../hooks/useMatterportViewer';
import {
AdditionalComponentData,
ExternalLibraryConfig,
KnownComponentType,
KnownSceneProperty,
SceneComposerInternalProps,
} from '../interfaces';
import { SceneLayout } from '../layouts/SceneLayout';
import useLifecycleLogging from '../logger/react-logger/hooks/useLifecycleLogging';
import { ICameraComponentInternal, RootState, useStore, useViewOptionState } from '../store';
import { getCameraSettings } from '../utils/cameraUtils';
import { getAdditionalComponentData } from '../utils/eventDataUtils';
import { combineTimeSeriesData, convertDataStreamsToDataInput } from '../utils/dataStreamUtils';
import { findComponentByType } from '../utils/nodeUtils';
import sceneDocumentSnapshotCreator from '../utils/sceneDocumentSnapshotCreator';
import { createStandardUriModifier } from '../utils/uriModifiers';

import IntlProvider from './IntlProvider';
import { LoadingProgress } from './three-fiber/LoadingProgress';
Expand Down Expand Up @@ -150,32 +150,8 @@ const StateManager: React.FC<SceneComposerInternalProps> = ({
if (onSelectionChanged) {
const node = getSceneNodeByRef(selectedSceneNodeRef);
const componentTypes = node?.components.map((component) => component.type) ?? [];
const additionalComponentData: AdditionalComponentData[] = getAdditionalComponentData(node, dataBindingTemplate);

const tagComponent = findComponentByType(node, KnownComponentType.Tag) as IAnchorComponentInternal;
const entityBindingComponent = findComponentByType(
node,
KnownComponentType.EntityBinding,
) as IEntityBindingComponentInternal;
const additionalComponentData: AdditionalComponentData[] = [];
if (tagComponent) {
additionalComponentData.push({
chosenColor: tagComponent.chosenColor,
navLink: tagComponent.navLink,
dataBindingContext: !tagComponent.valueDataBinding?.dataBindingContext
? undefined
: applyDataBindingTemplate(tagComponent.valueDataBinding, dataBindingTemplate),
});
}
// Add entityID info part of additional component data
// We assumed IDataBindingMap will have only one mapping as data binding
// will always have only one entity data.
if (entityBindingComponent) {
additionalComponentData.push({
dataBindingContext: !entityBindingComponent?.valueDataBinding?.dataBindingContext
? undefined
: entityBindingComponent?.valueDataBinding.dataBindingContext,
});
}
onSelectionChanged({
componentTypes,
nodeRef: selectedSceneNodeRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('AddComponentMenu', () => {
],
});
render(<AddComponentMenu />);
const addButton = screen.getByTestId('add-component-data-binding');
const addButton = screen.getByTestId('add-component-entity-binding');

act(() => {
fireEvent.pointerUp(addButton);
Expand All @@ -105,7 +105,7 @@ describe('AddComponentMenu', () => {
valueDataBinding: { dataBindingContext: '' },
});
expect(mockMetricRecorder.recordClick).toBeCalledTimes(1);
expect(mockMetricRecorder.recordClick).toBeCalledWith('add-component-data-binding');
expect(mockMetricRecorder.recordClick).toBeCalledWith('add-component-entity-binding');
});

it('should add no addition binding to data binding component when clicked', () => {
Expand All @@ -118,11 +118,11 @@ describe('AddComponentMenu', () => {
],
});
render(<AddComponentMenu />);
expect(screen.getByTestId('add-component-data-binding')).not.toBeNull();
screen.getByTestId('add-component-data-binding').click();
expect(screen.getByTestId('add-component-entity-binding')).not.toBeNull();
screen.getByTestId('add-component-entity-binding').click();
fireEvent.mouseOver(screen.getByTestId('add-component'));
expect(addComponentInternal).not.toBeCalled();
expect(mockMetricRecorder.recordClick).not.toBeCalledWith('add-component-data-binding');
expect(mockMetricRecorder.recordClick).not.toBeCalledWith('add-component-entity-binding');
});

it('should not see add data binding item when feature is not enabled', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface AddComponentMenuProps {
enum ObjectTypes {
Component = 'add-component',
Overlay = 'add-component-overlay',
DataBinding = 'add-component-data-binding',
EntityBinding = 'add-component-entity-binding',
}

type AddComponentMenuItem = ToolbarItemOptions & {
Expand All @@ -31,12 +31,12 @@ type AddComponentMenuItem = ToolbarItemOptions & {
const labelStrings: { [key in ObjectTypes]: MessageDescriptor } = defineMessages({
[ObjectTypes.Component]: { defaultMessage: 'Add component', description: 'Menu Item label' },
[ObjectTypes.Overlay]: { defaultMessage: 'Overlay', description: 'Menu Item label' },
[ObjectTypes.DataBinding]: { defaultMessage: 'Add entity binding', description: 'Menu Item label' },
[ObjectTypes.EntityBinding]: { defaultMessage: 'Add entity binding', description: 'Menu Item label' },
});

const textStrings = defineMessages({
[ObjectTypes.Overlay]: { defaultMessage: 'Add overlay', description: 'Menu Item' },
[ObjectTypes.DataBinding]: { defaultMessage: 'Add entity binding', description: 'Menu Item' },
[ObjectTypes.EntityBinding]: { defaultMessage: 'Add entity binding', description: 'Menu Item' },
});

export const AddComponentMenu: React.FC<AddComponentMenuProps> = ({ onSelect }) => {
Expand Down Expand Up @@ -76,10 +76,10 @@ export const AddComponentMenu: React.FC<AddComponentMenuProps> = ({ onSelect })
},
]
: [];
const addDataBindingItem = entityBindingComponentEnabled
const addEntityBindingItem = entityBindingComponentEnabled
? [
{
uuid: ObjectTypes.DataBinding,
uuid: ObjectTypes.EntityBinding,
isDisabled: isEntityBindingComponent,
},
]
Expand All @@ -91,7 +91,7 @@ export const AddComponentMenu: React.FC<AddComponentMenuProps> = ({ onSelect })
uuid: ObjectTypes.Component,
},
...addOverlayItem,
...addDataBindingItem,
...addEntityBindingItem,
].map(mapToMenuItem);
}, [selectedSceneNodeRef, selectedSceneNode, isOverlayComponent, isTagComponent, entityBindingComponentEnabled]);

Expand All @@ -114,7 +114,7 @@ export const AddComponentMenu: React.FC<AddComponentMenuProps> = ({ onSelect })
addComponentInternal(selectedSceneNodeRef, component);
}, [selectedSceneNodeRef, selectedSceneNode]);

const handleAddDataBinding = useCallback(() => {
const handleAddEntityBinding = useCallback(() => {
if (!selectedSceneNodeRef) return;

const entityBindingComponent = findComponentByType(selectedSceneNode, KnownComponentType.EntityBinding);
Expand Down Expand Up @@ -142,8 +142,8 @@ export const AddComponentMenu: React.FC<AddComponentMenuProps> = ({ onSelect })
type='action-select'
onClick={({ uuid }) => {
switch (uuid) {
case ObjectTypes.DataBinding:
handleAddDataBinding();
case ObjectTypes.EntityBinding:
handleAddEntityBinding();
break;
case ObjectTypes.Overlay:
handleAddOverlay();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { KnownComponentType } from '../../interfaces';
import { ToolbarItem } from '../toolbars/common/ToolbarItem';
import { getGlobalSettings } from '../../common/GlobalSettings';
import { Component } from '../../models/SceneModels';
import { IEntityBindingComponentInternal, ISceneComponentInternal } from '../../store/internalInterfaces';
import { ISceneComponentInternal } from '../../store/internalInterfaces';
import { generateUUID } from '../../utils/mathUtils';

interface ComponentEditMenuProps {
Expand Down Expand Up @@ -128,7 +128,7 @@ export const ComponentEditMenu: React.FC<ComponentEditMenuProps> = ({ nodeRef, c
}
}, [nodeRef, currentComponent]);

const handleRemoveAllDataBinding = useCallback(() => {
const handleRemoveEntityBinding = useCallback(() => {
if (currentComponent.type == KnownComponentType.EntityBinding) {
removeComponent(nodeRef, currentComponent.ref);
return;
Expand All @@ -149,7 +149,7 @@ export const ComponentEditMenu: React.FC<ComponentEditMenuProps> = ({ nodeRef, c
handleAddDataBinding();
break;
case ObjectTypes.RemoveEntityBinding:
handleRemoveAllDataBinding();
handleRemoveEntityBinding();
break;
case ObjectTypes.RemoveOverlay:
removeComponent(nodeRef, currentComponent.ref);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ReactElement, useContext, useRef } from 'react';
import { Html } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import { Object3D } from 'three';
import { Group } from 'three';

import { ISceneNodeInternal } from '../../../store';
import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext';
Expand All @@ -18,7 +18,7 @@ export interface DataOverlayComponentProps {
export const DataOverlayComponent = ({ node, component }: DataOverlayComponentProps): ReactElement => {
const sceneComposerId = useContext(sceneComposerIdContext);
const htmlRef = useRef<HTMLDivElement>(null);
const groupRef = useRef<Object3D>();
const groupRef = useRef<Group>();

const getOffsetFromTag = () => {
const tagComponent: IAnchorComponentInternal | undefined = node.components.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Component } from '../../../models/SceneModels';
import { useStore, useViewOptionState } from '../../../store';
import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext';
import useCallbackWhenNotPanning from '../../../hooks/useCallbackWhenNotPanning';
import { applyDataBindingTemplate } from '../../../utils/dataBindingTemplateUtils';

import { DataOverlayRows } from './DataOverlayRows';
import {
Expand Down Expand Up @@ -35,6 +36,10 @@ export const DataOverlayContainer = ({ component, node }: DataOverlayContainerPr
const componentVisible = useViewOptionState(sceneComposerId).componentVisibilities[subType];
const initialVisibilitySkipped = useRef(false);

const onWidgetClick = useStore(sceneComposerId)((state) => state.getEditorConfig().onWidgetClick);
const isViewing = useStore(sceneComposerId)((state) => state.isViewing());
const dataBindingTemplate = useStore(sceneComposerId)((state) => state.dataBindingTemplate);

// TODO: config.isPinned is not supported in milestone 1
// const [visible, setVisible] = useState(component.config?.isPinned || componentVisible);
const [visible, setVisible] = useState(componentVisible);
Expand All @@ -58,12 +63,34 @@ export const DataOverlayContainer = ({ component, node }: DataOverlayContainerPr
// Same behavior as other components to select node when clicked on the panel
const [onPointerDown, onPointerUp] = useCallbackWhenNotPanning(
(e) => {
// Anchor only has special onClick handling in viewing mode
if (isViewing) {
if (onWidgetClick) {
const dataBindingContexts: unknown[] = [];
component.valueDataBindings.forEach((item) => {
if (item.valueDataBinding) {
dataBindingContexts.push(applyDataBindingTemplate(item.valueDataBinding, dataBindingTemplate));
}
});
const componentTypes = node.components.map((component) => component.type) ?? [];
onWidgetClick({
componentTypes,
nodeRef: node.ref,
additionalComponentData: [
{
dataBindingContexts,
},
],
});
}
}

e.stopPropagation();
if (selectedSceneNodeRef !== node.ref) {
setSelectedSceneNodeRef(node.ref);
}
},
[selectedSceneNodeRef, node.ref],
[selectedSceneNodeRef, node.ref, onWidgetClick],
);

const onClickCloseButton = useCallback(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
import { BoxGeometry, Group, Mesh } from 'three';
import { Canvas } from '@react-three/fiber';
import ReactThreeTestRenderer from '@react-three/test-renderer';

import { DataOverlayComponent } from '../DataOverlayComponent';
import { Component } from '../../../../models/SceneModels';
Expand Down
7 changes: 6 additions & 1 deletion packages/scene-composer/src/interfaces/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,15 @@ export interface ITagData {
export interface IEntityBindingInfo {
dataBindingContext?: unknown;
}

export interface IDataOverlayInfo {
dataBindingContexts?: unknown[];
}

/**
* Type that can be represented by different additional component data types such as ITagData | IFutureComponentData
*/
export type AdditionalComponentData = ITagData | IEntityBindingInfo;
export type AdditionalComponentData = ITagData | IEntityBindingInfo | IDataOverlayInfo;

/**
* Callback signature for selection of with Widgets.
Expand Down
Loading
Loading