Skip to content

Commit

Permalink
feat(scene): add ground plane button
Browse files Browse the repository at this point in the history
  • Loading branch information
haweston committed Nov 15, 2023
1 parent 72fc3bd commit c282c41
Show file tree
Hide file tree
Showing 33 changed files with 1,140 additions and 8 deletions.
1 change: 1 addition & 0 deletions packages/scene-composer/src/common/entityModelConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const componentTypeToId: Record<KnownComponentType, string> = {
Light: `${SCENE_COMPONENT_TYPE_ID_PREFIX}.component.light`,
Camera: `${SCENE_COMPONENT_TYPE_ID_PREFIX}.component.camera`,
EntityBinding: NODE_COMPONENT_TYPE_ID, // EntityBinding is saved at node component
PlaneGeometry: `test.${SCENE_COMPONENT_TYPE_ID_PREFIX}.component.planegeometry`, //Remove .test with GA
};
export const DEFAULT_ENTITY_BINDING_RELATIONSHIP_NAME = 'isVisualOf';
export const DEFAULT_PARENT_RELATIONSHIP_NAME = 'isChildOf';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const WebGLCanvasManager: React.FC = () => {
renderOrder={enableMatterportViewer ? 1 : undefined}
>
<planeGeometry args={[1000, 1000]} />
<meshBasicMaterial colorWrite={false} />
<meshBasicMaterial transparent={true} opacity={0} />
</mesh>
{enableMatterportViewer && <MatterportModel onClick={onClick} />}
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ jest.mock('./scene-components/EntityBindingComponentEditor', () => ({
),
}));

jest.mock('./scene-components/PlaneGeometryComponentEditor', () => ({
PlaneGeometryComponentEditor: (props) => (
<div data-mocked='PlaneGeometryComponentEditor'>{JSON.stringify(props)}</div>
),
}));

describe('ComponentEditor renders correct component', () => {
it('render DefaultComponentEditor correctly', async () => {
const { container } = render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import CameraComponentEditor from './scene-components/CameraComponentEditor';
import { DataOverlayComponentEditor } from './scene-components/DataOverlayComponentEditor';
import { EntityBindingComponentEditor } from './scene-components/EntityBindingComponentEditor';
import { AnimationComponentEditor } from './scene-components/AnimationComponentEditor';

import { PlaneGeometryComponentEditor } from './scene-components/PlaneGeometryComponentEditor';
export interface IComponentEditorProps {
node: ISceneNodeInternal;
component: ISceneComponentInternal;
Expand Down Expand Up @@ -61,6 +61,8 @@ export const ComponentEditor: React.FC<IComponentEditorProps> = ({ node, compone
return <EntityBindingComponentEditor node={node} component={component as IEntityBindingComponentInternal} />;
case KnownComponentType.Animation:
return <AnimationComponentEditor node={node} component={component as IAnimationComponentInternal} />;
case KnownComponentType.PlaneGeometry:
return <PlaneGeometryComponentEditor node={node} component={component} />;
default:
return <DefaultComponentEditor node={node} component={component} />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export const SceneNodeInspectorPanel: React.FC = () => {
defaultMessage: 'Animation',
description: 'Expandable Section title',
},
[KnownComponentType.PlaneGeometry]: {
defaultMessage: 'Plane Geometry',
description: 'Expandable Section title',
},
});

log?.verbose('render inspect panel with selected scene node ', selectedSceneNodeRef, selectedSceneNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ exports[`ComponentEditor renders correct component should render the "MotionIndi
</div>
`;

exports[`ComponentEditor renders correct component should render the "PlaneGeometry" editor correctly 1`] = `
<div>
<div
data-mocked="PlaneGeometryComponentEditor"
>
{"node":{},"component":{"ref":"refId","type":"PlaneGeometry"}}
</div>
</div>
`;

exports[`ComponentEditor renders correct component should render the "SubModelRef" editor correctly 1`] = `
<div>
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import wrapper from '@awsui/components-react/test-utils/dom';
import { render } from '@testing-library/react';
import React from 'react';

import { IPlaneGeometryComponentInternal, useStore } from '../../../store';
import { KnownComponentType } from '../../../interfaces';
import { mockNode, mockComponent } from '../../../../tests/components/panels/scene-components/MockComponents';

import { PlaneGeometryComponentEditor } from './PlaneGeometryComponentEditor';

jest.mock('@awsui/components-react', () => ({
...jest.requireActual('@awsui/components-react'),
}));

describe('PlaneGeometryComponentEditor', () => {
const component: IPlaneGeometryComponentInternal = {
...mockComponent,
type: KnownComponentType.PlaneGeometry,
width: 10,
height: 20,
};

const componentWithColor: IPlaneGeometryComponentInternal = {
...mockComponent,
type: KnownComponentType.PlaneGeometry,
width: 10,
height: 20,
color: '#abcdef',
};

const updateComponentInternalFn = jest.fn();

const baseState = {
updateComponentInternal: updateComponentInternalFn,
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should update width when width changes', async () => {
useStore('default').setState(baseState);
const { container } = render(
<PlaneGeometryComponentEditor node={{ ...mockNode, components: [component] }} component={component} />,
);
const polarisWrapper = wrapper(container);
const widthInput = polarisWrapper.findInput('[data-testid="plane-width-input"]');

expect(widthInput).toBeDefined();

widthInput!.focus();
widthInput!.setInputValue('2');
widthInput!.blur();

expect(updateComponentInternalFn).toBeCalledTimes(1);
expect(updateComponentInternalFn).toBeCalledWith(
mockNode.ref,
{ ...component, ref: component.ref, width: 2, height: component.height },
true,
);
});

it('should update height when height changes', async () => {
useStore('default').setState(baseState);
const { container } = render(
<PlaneGeometryComponentEditor node={{ ...mockNode, components: [component] }} component={component} />,
);
const polarisWrapper = wrapper(container);
const heightInput = polarisWrapper.findInput('[data-testid="plane-height-input"]');

expect(heightInput).toBeDefined();

heightInput!.focus();
heightInput!.setInputValue('2');
heightInput!.blur();

expect(updateComponentInternalFn).toBeCalledTimes(1);
expect(updateComponentInternalFn).toBeCalledWith(
mockNode.ref,
{ ...component, ref: component.ref, width: component.width, height: 2 },
true,
);
});

it('should update background when colors changes', async () => {
useStore('default').setState(baseState);
const { container } = render(
<PlaneGeometryComponentEditor
node={{ ...mockNode, components: [componentWithColor] }}
component={componentWithColor}
/>,
);
const polarisWrapper = wrapper(container);
const colorInput = polarisWrapper.findInput('[data-testid="hexcode"]');

expect(colorInput).toBeDefined();

// click checkbox should update store
colorInput!.focus();
colorInput!.setInputValue('#FFFFFF');
colorInput!.blur();
expect(updateComponentInternalFn).toBeCalledTimes(1);
expect(updateComponentInternalFn).toBeCalledWith(
mockNode.ref,
{ ...component, ref: component.ref, width: component.width, height: component.height, color: '#FFFFFF' },
true,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useCallback, useContext, useState } from 'react';
import { FormField, Input, SpaceBetween } from '@awsui/components-react';
import { useIntl } from 'react-intl';

import { IComponentEditorProps } from '../ComponentEditor';
import { KnownSceneProperty } from '../../../interfaces';
import { IPlaneGeometryComponentInternal, ISceneComponentInternal, useStore } from '../../../store';
import { sceneComposerIdContext } from '../../../common/sceneComposerIdContext';
import { ColorSelectorCombo } from '../scene-components/tag-style/ColorSelectorCombo/ColorSelectorCombo';

export type IPlaneGeometryComponentEditorProps = IComponentEditorProps;

export const PlaneGeometryComponentEditor: React.FC<IPlaneGeometryComponentEditorProps> = ({
node,
component,
}: IPlaneGeometryComponentEditorProps) => {
const sceneComposerId = useContext(sceneComposerIdContext);
const updateComponentInternal = useStore(sceneComposerId)((state) => state.updateComponentInternal);
const planeGeometryComponent = component as IPlaneGeometryComponentInternal;
const intl = useIntl();

const geometryColors = useStore(sceneComposerId)((state) =>
state.getSceneProperty<string[]>(KnownSceneProperty.GeometryCustomColors, []),
);
const setGeometryColorsSceneProperty = useStore(sceneComposerId)((state) => state.setSceneProperty<string[]>);

const [internalWidth, setInternalWidth] = useState(planeGeometryComponent.width);
const [internalHeight, setInternalHeight] = useState(planeGeometryComponent.height);
const [internalColor, setInternalColor] = useState(planeGeometryComponent.color || '#cccccc');

const onUpdateCallback = useCallback(
(componentPartial: IPlaneGeometryComponentInternal, replace?: boolean) => {
const componentPartialWithRef: ISceneComponentInternal = { ...componentPartial, ref: component.ref };
updateComponentInternal(node.ref, componentPartialWithRef, replace);
},
[node.ref, component.ref],
);

const onColorChange = useCallback(
(color: string) => {
if (color !== internalColor) {
setInternalColor(color);
const updatedComponent = { ...planeGeometryComponent, color };
onUpdateCallback(updatedComponent, true);
}
},
[internalColor, planeGeometryComponent],
);

const onInputBlur = useCallback(() => {
const updatedComponent = { ...planeGeometryComponent, width: internalWidth, height: internalHeight };
onUpdateCallback(updatedComponent, true);
}, [planeGeometryComponent, internalWidth, internalHeight]);

const onWidthChange = useCallback(
(event) => {
const value = Number(event.detail.value);
if (value !== internalWidth) {
setInternalWidth(value);
}
},
[internalWidth],
);

const onHeightChange = useCallback(
(event) => {
const value = Number(event.detail.value);
if (value !== internalHeight) {
setInternalHeight(value);
}
},
[internalHeight],
);

return (
<SpaceBetween size='s'>
<FormField label={intl.formatMessage({ defaultMessage: 'Width', description: 'Form Field label' })}>
<Input
data-testid='plane-width-input'
value={String(internalWidth)}
type='number'
onBlur={onInputBlur}
onChange={onWidthChange}
onKeyDown={(e) => {
if (e.detail.key === 'Enter') onInputBlur();
}}
/>
</FormField>
<FormField label={intl.formatMessage({ defaultMessage: 'Height', description: 'Form Field label' })}>
<Input
data-testid='plane-height-input'
value={String(internalHeight)}
type='number'
onBlur={onInputBlur}
onChange={onHeightChange}
onKeyDown={(e) => {
if (e.detail.key === 'Enter') onInputBlur();
}}
/>
</FormField>
<ColorSelectorCombo
color={internalColor}
onSelectColor={(pickedColor) => onColorChange(pickedColor)}
onUpdateCustomColors={(chosenCustomColors) =>
setGeometryColorsSceneProperty(KnownSceneProperty.GeometryCustomColors, chosenCustomColors)
}
customColors={geometryColors}
colorPickerLabel={intl.formatMessage({ defaultMessage: 'Color', description: 'Color' })}
customColorLabel={intl.formatMessage({ defaultMessage: 'Custom colors', description: 'Custom colors' })}
/>
</SpaceBetween>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { render } from '@testing-library/react';
import React from 'react';

import { IPlaneGeometryComponentInternal, useStore } from '../../../store';
import { KnownComponentType } from '../../../interfaces';
import { mockNode, mockComponent } from '../../../../tests/components/panels/scene-components/MockComponents';

import { PlaneGeometryComponentEditor } from './PlaneGeometryComponentEditor';

jest.mock('../scene-components/tag-style/ColorSelectorCombo/ColorSelectorCombo', () => {
return {
ColorSelectorCombo: (...props: []) => <div id='ColorSelectorCombo'>{JSON.stringify(props)}</div>,
};
});

describe('PlaneGeometryComponentEditor', () => {
const component: IPlaneGeometryComponentInternal = {
...mockComponent,
type: KnownComponentType.PlaneGeometry,
width: 10,
height: 20,
};

const componentWithColor: IPlaneGeometryComponentInternal = {
...mockComponent,
type: KnownComponentType.PlaneGeometry,
width: 10,
height: 20,
color: '#abcdef',
};

const updateComponentInternalFn = jest.fn();

const baseState = {
updateComponentInternal: updateComponentInternalFn,
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should render correctly', () => {
useStore('default').setState(baseState);

const { container } = render(
<PlaneGeometryComponentEditor node={{ ...mockNode, components: [component] }} component={component} />,
);
expect(container).toMatchSnapshot();
});

it('should render correctly with color', () => {
useStore('default').setState(baseState);

const { container } = render(
<PlaneGeometryComponentEditor
node={{ ...mockNode, components: [componentWithColor] }}
component={componentWithColor}
/>,
);
expect(container).toMatchSnapshot();
});
});

0 comments on commit c282c41

Please sign in to comment.