From 8c08bb119dda482fa70557f5195d9766723b1099 Mon Sep 17 00:00:00 2001 From: Conrad Chan Date: Wed, 12 May 2021 15:07:19 -0700 Subject: [PATCH 1/5] feat(controls): Add Settings menu to Model3D --- src/i18n/en-US.properties | 26 +++++ .../box3d/model3d/Model3DControlsNew.tsx | 45 +++++++-- .../viewers/box3d/model3d/Model3DViewer.js | 52 +++++++++- .../controls/model3d/Model3DSettings.scss | 24 +++++ .../controls/model3d/Model3DSettings.tsx | 96 +++++++++++++++++++ .../controls/model3d/RotateAxisControl.scss | 53 ++++++++++ .../controls/model3d/RotateAxisControl.tsx | 30 ++++++ .../controls/model3d/RotateAxisControls.scss | 36 +++++++ .../controls/model3d/RotateAxisControls.tsx | 32 +++++++ .../viewers/controls/settings/Settings.tsx | 15 ++- .../controls/settings/SettingsDropdown.tsx | 26 +++-- .../__tests__/SettingsDropdown-test.tsx | 2 +- 12 files changed, 416 insertions(+), 21 deletions(-) create mode 100644 src/lib/viewers/controls/model3d/Model3DSettings.scss create mode 100644 src/lib/viewers/controls/model3d/Model3DSettings.tsx create mode 100644 src/lib/viewers/controls/model3d/RotateAxisControl.scss create mode 100644 src/lib/viewers/controls/model3d/RotateAxisControl.tsx create mode 100644 src/lib/viewers/controls/model3d/RotateAxisControls.scss create mode 100644 src/lib/viewers/controls/model3d/RotateAxisControls.tsx diff --git a/src/i18n/en-US.properties b/src/i18n/en-US.properties index 7ae81f135..a25a2fad5 100644 --- a/src/i18n/en-US.properties +++ b/src/i18n/en-US.properties @@ -154,14 +154,40 @@ auto_generated=Auto-Generated # 3D Preview # Button tooltip for showing/hiding the list of animation clips box3d_animation_clips=Animation clips +# Settings camera projection listbox item value: Perspective +box3d_camera_projection_perspective=Perspective +# Settings camera projection listbox item value: Orthographic +box3d_camera_projection_orthographic=Orthographic # Button tooltip for resetting all user modified settings in the control bar and Settings panel, to their defaults. This includes render mode, rotation, camera position box3d_reset=Reset # Button tooltip for playing and pausing animations box3d_toggle_animation=Play/pause animation # Button tooltip for toggling VR display mode in any 3D preview box3d_toggle_vr=Toggle VR display +# Settings render mode listbox item value: Lit +box3d_render_mode_lit=Lit +# Settings render mode listbox item value: Unlit +box3d_render_mode_unlit=Unlit +# Settings render mode listbox item value: Normals +box3d_render_mode_normals=Normals +# Settings render mode listbox item value: Shape +box3d_render_mode_shape=Shape +# Settings render mode listbox item value: UV Overlay +box3d_render_mode_uv_overlay=UV Overlay # Settings box3d_settings=Settings +# Settings render mode label +box3d_settings_render_label=Render mode +# Settings show grid label +box3d_settings_grid_label=Show grid +# Settings show wireframes label +box3d_settings_wireframes_label=Show wireframes +# Settings show skeletons label +box3d_settings_skeletons_label=Show skeletons +# Settings camera projection label +box3d_settings_projection_label=Camera Projection +# Settings rotate model label +box3d_settings_rotate_label=Rotate Model # Annotations # Placeholder text for create textarea in annotation dialog diff --git a/src/lib/viewers/box3d/model3d/Model3DControlsNew.tsx b/src/lib/viewers/box3d/model3d/Model3DControlsNew.tsx index c728b6500..e9fceadb6 100644 --- a/src/lib/viewers/box3d/model3d/Model3DControlsNew.tsx +++ b/src/lib/viewers/box3d/model3d/Model3DControlsNew.tsx @@ -2,27 +2,42 @@ import React from 'react'; import AnimationControls, { Props as AnimationControlsProps } from '../../controls/model3d/AnimationControls'; import ControlsBar from '../../controls/controls-bar'; import FullscreenToggle, { Props as FullscreenToggleProps } from '../../controls/fullscreen'; +import Model3DSettings, { Props as Model3DSettingsProps } from '../../controls/model3d/Model3DSettings'; import ResetControl, { Props as ResetControlProps } from '../../controls/model3d/ResetControl'; -export type Props = AnimationControlsProps & FullscreenToggleProps & ResetControlProps; +export type Props = AnimationControlsProps & + FullscreenToggleProps & + Model3DSettingsProps & + ResetControlProps & { + onSettingsClose: () => void; + onSettingsOpen: () => void; + }; export default function Model3DControls({ animationClips, + cameraProjection, currentAnimationClipId, isPlaying, onAnimationClipSelect, + onCameraProjectionChange, onFullscreenToggle, onPlayPause, + onRenderModeChange, + onRotateOnAxisChange, onReset, + onSettingsClose, + onSettingsOpen, + onShowGridToggle, + onShowSkeletonsToggle, + onShowWireframesToggle, + renderMode, + showGrid, + showSkeletons, + showWireframes, }: Props): JSX.Element { - const handleReset = (): void => { - // TODO: will need to reset the state to defaults - onReset(); - }; - return ( - + {/* TODO: VR button */} - {/* TODO: Settings button */} + ); diff --git a/src/lib/viewers/box3d/model3d/Model3DViewer.js b/src/lib/viewers/box3d/model3d/Model3DViewer.js index 2532b291e..9b318d880 100644 --- a/src/lib/viewers/box3d/model3d/Model3DViewer.js +++ b/src/lib/viewers/box3d/model3d/Model3DViewer.js @@ -43,9 +43,21 @@ class Model3DViewer extends Box3DViewer { /** @property {Object[]} - List of Box3D instances added to the scene */ instances = []; - /** @property {boolean} - Boolean indicating whether the animation is playihng */ + /** @property {boolean} - Boolean indicating whether the animation is playing */ isAnimationPlaying = false; + /** @property {boolean} - Boolean indicating whether the grid is showing */ + renderGrid = DEFAULT_RENDER_GRID; + + /** @property {string} - string indicating what the render mode is */ + renderMode = RENDER_MODE_LIT; + + /** @property {boolean} - Boolean indicating whether the skeletons are showing */ + renderSkeletons = false; + + /** @property {boolean} - Boolean indicating whether the wireframes are showing */ + renderWireframes = false; + /** @inheritdoc */ constructor(option) { super(option); @@ -363,6 +375,11 @@ class Model3DViewer extends Box3DViewer { */ handleSetRenderMode(mode = 'Lit') { this.renderer.setRenderMode(mode); + + if (this.controls && this.getViewerOption('useReactControls')) { + this.renderMode = mode; + this.renderUI(); + } } /** @@ -387,6 +404,11 @@ class Model3DViewer extends Box3DViewer { */ handleSetCameraProjection(projection) { this.renderer.setCameraProjection(projection); + + if (this.controls && this.getViewerOption('useReactControls')) { + this.cameraProjection = projection; + this.renderUI(); + } } /** @@ -398,6 +420,11 @@ class Model3DViewer extends Box3DViewer { */ handleShowSkeletons(visible) { this.renderer.setSkeletonsVisible(visible); + + if (this.controls && this.getViewerOption('useReactControls')) { + this.renderSkeletons = visible; + this.renderUI(); + } } /** @@ -409,6 +436,11 @@ class Model3DViewer extends Box3DViewer { */ handleShowWireframes(visible) { this.renderer.setWireframesVisible(visible); + + if (this.controls && this.getViewerOption('useReactControls')) { + this.renderWireframes = visible; + this.renderUI(); + } } /** @@ -420,6 +452,11 @@ class Model3DViewer extends Box3DViewer { */ handleShowGrid(visible) { this.renderer.setGridVisible(visible); + + if (this.controls && this.getViewerOption('useReactControls')) { + this.renderGrid = visible; + this.renderUI(); + } } renderUI() { @@ -430,12 +467,25 @@ class Model3DViewer extends Box3DViewer { this.controls.render( this.handleToggleHelpers(false)} + onSettingsOpen={() => this.handleToggleHelpers(true)} + onShowGridToggle={this.handleShowGrid} + onShowSkeletonsToggle={this.handleShowSkeletons} + onShowWireframesToggle={this.handleShowWireframes} + renderMode={this.renderMode} + showGrid={this.renderGrid} + showSkeletons={this.renderSkeletons} + showWireframes={this.renderWireframes} />, ); } diff --git a/src/lib/viewers/controls/model3d/Model3DSettings.scss b/src/lib/viewers/controls/model3d/Model3DSettings.scss new file mode 100644 index 000000000..d2372ccd1 --- /dev/null +++ b/src/lib/viewers/controls/model3d/Model3DSettings.scss @@ -0,0 +1,24 @@ +.bp-Model3DSettings { + position: relative; + + .bp-Settings-flyout { + right: auto; + left: 0; + flex-direction: column; + max-height: none; + + &.bp-is-open { + display: flex; + } + } + + .bp-RotateAxisControls { + margin-bottom: 10px; + } +} + +.bp-Model3DSettings-menu { + & > * { + margin-top: 8px; + } +} diff --git a/src/lib/viewers/controls/model3d/Model3DSettings.tsx b/src/lib/viewers/controls/model3d/Model3DSettings.tsx new file mode 100644 index 000000000..a0fc40237 --- /dev/null +++ b/src/lib/viewers/controls/model3d/Model3DSettings.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import Settings, { Menu, Props as SettingsProps } from '../settings'; +import RotateAxisControls from './RotateAxisControls'; +import { AxisChange } from './RotateAxisControl'; +import './Model3DSettings.scss'; + +export enum CameraProjection { + PERSPECTIVE = 'Perspective', + ORTHOGRAPHIC = 'Orthographic', +} + +export enum RenderMode { + LIT = 'Lit', + UNLIT = 'Unlit', + NORMALS = 'Normals', + SHAPE = 'Shape', + UV_OVERLAY = 'UV Overlay', +} + +export type Props = Pick & { + cameraProjection: CameraProjection; + onCameraProjectionChange: (projection: CameraProjection) => void; + onRenderModeChange: (mode: RenderMode) => void; + onRotateOnAxisChange: (change: AxisChange) => void; + onShowGridToggle: () => void; + onShowSkeletonsToggle: () => void; + onShowWireframesToggle: () => void; + renderMode: RenderMode; + showGrid: boolean; + showSkeletons: boolean; + showWireframes: boolean; +}; + +const cameraProjectionOptions = [ + { label: __('box3d_camera_projection_perspective'), value: CameraProjection.PERSPECTIVE }, + { label: __('box3d_camera_projection_orthographic'), value: CameraProjection.ORTHOGRAPHIC }, +]; + +const renderModeOptions = [ + { label: __('box3d_render_mode_lit'), value: RenderMode.LIT }, + { label: __('box3d_render_mode_unlit'), value: RenderMode.UNLIT }, + { label: __('box3d_render_mode_normals'), value: RenderMode.NORMALS }, + { label: __('box3d_render_mode_shape'), value: RenderMode.SHAPE }, + { label: __('box3d_render_mode_uv_overlay'), value: RenderMode.UV_OVERLAY }, +]; + +export default function Model3DSettings({ + cameraProjection, + onCameraProjectionChange, + onClose, + onOpen, + onRenderModeChange, + onRotateOnAxisChange, + onShowGridToggle, + onShowSkeletonsToggle, + onShowWireframesToggle, + renderMode, + showGrid, + showSkeletons, + showWireframes, +}: Props): JSX.Element { + return ( + + + + label={__('box3d_settings_render_label')} + listItems={renderModeOptions} + onSelect={onRenderModeChange} + value={renderMode} + /> + + + + + label={__('box3d_settings_projection_label')} + listItems={cameraProjectionOptions} + onSelect={onCameraProjectionChange} + value={cameraProjection} + /> + + + + ); +} diff --git a/src/lib/viewers/controls/model3d/RotateAxisControl.scss b/src/lib/viewers/controls/model3d/RotateAxisControl.scss new file mode 100644 index 000000000..ae9f1674b --- /dev/null +++ b/src/lib/viewers/controls/model3d/RotateAxisControl.scss @@ -0,0 +1,53 @@ +@import '../styles'; + +@mixin bp-RotateAxisControl-button($direction) { + position: relative; + width: 24px; + height: 25px; + background-color: transparent; + border: none; + outline: none; + cursor: pointer; + + &::after { + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-#{$direction}: 5px solid $fours; + transform: translateX(-50%) translateY(-50%); + content: ''; + } + + &:focus, + &:hover { + &::after { + border-#{$direction}: 5px solid $eights; + } + } +} + +.bp-RotateAxisControl { + display: flex; + color: $bdl-gray-62; + border: 1px solid $sf-fog; + border-bottom-width: 2px; + border-radius: 2px; +} + +.bp-RotateAxisControl-label { + display: flex; + align-items: center; + text-transform: uppercase; +} + +.bp-RotateAxisControl-left { + @include bp-RotateAxisControl-button('right'); +} + +.bp-RotateAxisControl-right { + @include bp-RotateAxisControl-button('left'); +} diff --git a/src/lib/viewers/controls/model3d/RotateAxisControl.tsx b/src/lib/viewers/controls/model3d/RotateAxisControl.tsx new file mode 100644 index 000000000..63cc0b36e --- /dev/null +++ b/src/lib/viewers/controls/model3d/RotateAxisControl.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import classNames from 'classnames'; +import './RotateAxisControl.scss'; + +export type Axis = 'x' | 'y' | 'z'; + +export type AxisChange = { + [key in Axis]?: number; +}; + +export type Props = { + axis: Axis; + className?: string; + onRotateOnAxisChange: (change: AxisChange) => void; +}; + +const ROTATION_STEP = 90; + +export default function RotateAxisControl({ axis, className, onRotateOnAxisChange }: Props): JSX.Element { + const handleClickLeft = (): void => onRotateOnAxisChange({ [axis]: -ROTATION_STEP }); + const handleClickRight = (): void => onRotateOnAxisChange({ [axis]: ROTATION_STEP }); + + return ( +
+
+ ); +} diff --git a/src/lib/viewers/controls/model3d/RotateAxisControls.scss b/src/lib/viewers/controls/model3d/RotateAxisControls.scss new file mode 100644 index 000000000..ee0199e87 --- /dev/null +++ b/src/lib/viewers/controls/model3d/RotateAxisControls.scss @@ -0,0 +1,36 @@ +@import '../styles'; + +$bp-RotateAxisControls-spacing: 4px; + +.bp-RotateAxisControls { + display: flex; + flex-direction: column; + color: $bdl-gray-62; + + .bp-RotateAxisControl + .bp-RotateAxisControl { + margin-left: $bp-RotateAxisControls-spacing; + } +} + +.bp-RotateAxisControls-label { + display: flex; + align-items: center; + margin: $bp-RotateAxisControls-spacing 0; +} + +.bp-RotateAxisControls-controls { + display: flex; + margin: $bp-RotateAxisControls-spacing 0; +} + +.bp-RotateAxisControls-rotateX { + border-bottom-color: #e33d55; +} + +.bp-RotateAxisControls-rotateY { + border-bottom-color: #26c281; +} + +.bp-RotateAxisControls-rotateZ { + border-bottom-color: #22a7f0; +} diff --git a/src/lib/viewers/controls/model3d/RotateAxisControls.tsx b/src/lib/viewers/controls/model3d/RotateAxisControls.tsx new file mode 100644 index 000000000..b98c3912f --- /dev/null +++ b/src/lib/viewers/controls/model3d/RotateAxisControls.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import RotateAxisControl, { AxisChange } from './RotateAxisControl'; +import './RotateAxisControls.scss'; + +export type Props = { + onRotateOnAxisChange: (change: AxisChange) => void; +}; + +export default function RotateAxisControls({ onRotateOnAxisChange }: Props): JSX.Element { + return ( +
+
{__('box3d_settings_rotate_label')}
+
+ + + +
+
+ ); +} diff --git a/src/lib/viewers/controls/settings/Settings.tsx b/src/lib/viewers/controls/settings/Settings.tsx index 2557c0759..25341399e 100644 --- a/src/lib/viewers/controls/settings/Settings.tsx +++ b/src/lib/viewers/controls/settings/Settings.tsx @@ -1,5 +1,6 @@ import React from 'react'; import classNames from 'classnames'; +import noop from 'lodash/noop'; import SettingsCheckboxItem from './SettingsCheckboxItem'; import SettingsContext, { Menu, Rect } from './SettingsContext'; import SettingsDropdown from './SettingsDropdown'; @@ -15,11 +16,15 @@ import { decodeKeydown } from '../../../util'; export type Props = React.PropsWithChildren<{ className?: string; toggle?: React.ElementType; + onClose?: () => void; + onOpen?: () => void; }>; export default function Settings({ children, className, + onClose = noop, + onOpen = noop, toggle: SettingsToggle = SettingsGearToggle, ...rest }: Props): JSX.Element | null { @@ -34,7 +39,9 @@ export default function Settings({ setActiveRect(undefined); setIsFocused(false); setIsOpen(false); - }, []); + + onClose(); + }, [onClose]); const { height, width } = activeRect || { height: 'auto', width: 'auto' }; const handleClick = (): void => { @@ -42,6 +49,12 @@ export default function Settings({ setActiveRect(undefined); setIsFocused(false); setIsOpen(!isOpen); + + if (!isOpen) { + onOpen(); + } else { + onClose(); + } }; const handleKeyDown = (event: React.KeyboardEvent): void => { diff --git a/src/lib/viewers/controls/settings/SettingsDropdown.tsx b/src/lib/viewers/controls/settings/SettingsDropdown.tsx index 42ce9b087..7158c3e48 100644 --- a/src/lib/viewers/controls/settings/SettingsDropdown.tsx +++ b/src/lib/viewers/controls/settings/SettingsDropdown.tsx @@ -7,20 +7,26 @@ import useClickOutside from '../hooks/useClickOutside'; import { decodeKeydown } from '../../../util'; import './SettingsDropdown.scss'; -export type ListItem = { +export type ListItem = { label: string; - value: string; + value: V; }; -export type Props = { +export type Props = { className?: string; label: string; - listItems: Array; - onSelect: (value: string) => void; - value?: string; + listItems: Array>; + onSelect: (value: V) => void; + value?: V; }; -export default function SettingsDropdown({ className, label, listItems, onSelect, value }: Props): JSX.Element { +export default function SettingsDropdown({ + className, + label, + listItems, + onSelect, + value, +}: Props): JSX.Element { const { current: id } = React.useRef(uniqueId('bp-SettingsDropdown_')); const buttonElRef = React.useRef(null); const dropdownElRef = React.useRef(null); @@ -43,18 +49,18 @@ export default function SettingsDropdown({ className, label, listItems, onSelect event.stopPropagation(); } }; - const handleSelect = (selectedOption: string): void => { + const handleSelect = (selectedOption: V): void => { setIsOpen(false); onSelect(selectedOption); }; - const createClickHandler = (selectedOption: string) => (event: React.MouseEvent): void => { + const createClickHandler = (selectedOption: V) => (event: React.MouseEvent): void => { handleSelect(selectedOption); // Prevent the event from bubbling up and triggering any upstream click handling logic, // i.e. if the dropdown is nested inside a menu flyout event.stopPropagation(); }; - const createKeyDownHandler = (selectedOption: string) => (event: React.KeyboardEvent): void => { + const createKeyDownHandler = (selectedOption: V) => (event: React.KeyboardEvent): void => { const key = decodeKeydown(event); if (key !== 'Space' && key !== 'Enter') { diff --git a/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx b/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx index 31b2cb7f2..9afae54fa 100644 --- a/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx +++ b/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx @@ -11,7 +11,7 @@ describe('SettingsDropdown', () => { { label: 'second', value: 'second' }, { label: 'third', value: 'third' }, ]; - const getDefaults = (): Props => ({ + const getDefaults = (): Props => ({ label: 'Dropdown Label', listItems, onSelect: jest.fn(), From 9527ede691c6e557ac5fdd4e19f9125476a23811 Mon Sep 17 00:00:00 2001 From: Conrad Chan Date: Thu, 13 May 2021 10:49:05 -0700 Subject: [PATCH 2/5] chore: pr comments --- .../viewers/box3d/model3d/Model3DViewer.js | 35 ++++++++++--------- .../controls/model3d/Model3DSettings.scss | 5 +-- .../viewers/controls/settings/Settings.tsx | 2 +- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/lib/viewers/box3d/model3d/Model3DViewer.js b/src/lib/viewers/box3d/model3d/Model3DViewer.js index 9b318d880..adcee758b 100644 --- a/src/lib/viewers/box3d/model3d/Model3DViewer.js +++ b/src/lib/viewers/box3d/model3d/Model3DViewer.js @@ -46,17 +46,17 @@ class Model3DViewer extends Box3DViewer { /** @property {boolean} - Boolean indicating whether the animation is playing */ isAnimationPlaying = false; - /** @property {boolean} - Boolean indicating whether the grid is showing */ - renderGrid = DEFAULT_RENDER_GRID; - /** @property {string} - string indicating what the render mode is */ renderMode = RENDER_MODE_LIT; + /** @property {boolean} - Boolean indicating whether the grid is showing */ + showGrid = DEFAULT_RENDER_GRID; + /** @property {boolean} - Boolean indicating whether the skeletons are showing */ - renderSkeletons = false; + showSkeletons = false; /** @property {boolean} - Boolean indicating whether the wireframes are showing */ - renderWireframes = false; + showWireframes = false; /** @inheritdoc */ constructor(option) { @@ -216,11 +216,11 @@ class Model3DViewer extends Box3DViewer { this.renderMode = defaults.defaultRenderMode || RENDER_MODE_LIT; this.projection = defaults.cameraProjection || CAMERA_PROJECTION_PERSPECTIVE; if (defaults.renderGrid === 'true') { - this.renderGrid = true; + this.showGrid = true; } else if (defaults.renderGrid === 'false') { - this.renderGrid = false; + this.showGrid = false; } else { - this.renderGrid = DEFAULT_RENDER_GRID; + this.showGrid = DEFAULT_RENDER_GRID; } if (this.axes.up !== DEFAULT_AXIS_UP || this.axes.forward !== DEFAULT_AXIS_FORWARD) { @@ -346,7 +346,10 @@ class Model3DViewer extends Box3DViewer { handleReset() { super.handleReset(); - this.isAnimationPlaying = false; + this.setAnimationState(false); + this.handleShowGrid(true); + this.handleShowSkeletons(false); + this.handleShowWireframes(false); if (this.controls) { if (this.getViewerOption('useReactControls')) { @@ -356,7 +359,7 @@ class Model3DViewer extends Box3DViewer { this.controls.setCurrentProjectionMode(this.projection); this.controls.handleSetSkeletonsVisible(false); this.controls.handleSetWireframesVisible(false); - this.controls.handleSetGridVisible(this.renderGrid); + this.controls.handleSetGridVisible(this.showGrid); } } @@ -422,7 +425,7 @@ class Model3DViewer extends Box3DViewer { this.renderer.setSkeletonsVisible(visible); if (this.controls && this.getViewerOption('useReactControls')) { - this.renderSkeletons = visible; + this.showSkeletons = visible; this.renderUI(); } } @@ -438,7 +441,7 @@ class Model3DViewer extends Box3DViewer { this.renderer.setWireframesVisible(visible); if (this.controls && this.getViewerOption('useReactControls')) { - this.renderWireframes = visible; + this.showWireframes = visible; this.renderUI(); } } @@ -454,7 +457,7 @@ class Model3DViewer extends Box3DViewer { this.renderer.setGridVisible(visible); if (this.controls && this.getViewerOption('useReactControls')) { - this.renderGrid = visible; + this.showGrid = visible; this.renderUI(); } } @@ -483,9 +486,9 @@ class Model3DViewer extends Box3DViewer { onShowSkeletonsToggle={this.handleShowSkeletons} onShowWireframesToggle={this.handleShowWireframes} renderMode={this.renderMode} - showGrid={this.renderGrid} - showSkeletons={this.renderSkeletons} - showWireframes={this.renderWireframes} + showGrid={this.showGrid} + showSkeletons={this.showSkeletons} + showWireframes={this.showWireframes} />, ); } diff --git a/src/lib/viewers/controls/model3d/Model3DSettings.scss b/src/lib/viewers/controls/model3d/Model3DSettings.scss index d2372ccd1..fe872e241 100644 --- a/src/lib/viewers/controls/model3d/Model3DSettings.scss +++ b/src/lib/viewers/controls/model3d/Model3DSettings.scss @@ -18,7 +18,8 @@ } .bp-Model3DSettings-menu { - & > * { - margin-top: 8px; + & .bp-SettingsCheckboxItem, + & .bp-SettingsDropdown { + margin-top: 10px; } } diff --git a/src/lib/viewers/controls/settings/Settings.tsx b/src/lib/viewers/controls/settings/Settings.tsx index 25341399e..6a5267c96 100644 --- a/src/lib/viewers/controls/settings/Settings.tsx +++ b/src/lib/viewers/controls/settings/Settings.tsx @@ -15,9 +15,9 @@ import { decodeKeydown } from '../../../util'; export type Props = React.PropsWithChildren<{ className?: string; - toggle?: React.ElementType; onClose?: () => void; onOpen?: () => void; + toggle?: React.ElementType; }>; export default function Settings({ From 5e3169879673abfc057cb59e0b8581aef2f853a0 Mon Sep 17 00:00:00 2001 From: Conrad Chan Date: Thu, 13 May 2021 13:34:19 -0700 Subject: [PATCH 3/5] chore: add unit tests --- .../viewers/box3d/model3d/Model3DViewer.js | 5 +- .../__tests__/Model3DControlsNew-test.tsx | 61 ++++++++++++- .../model3d/__tests__/Model3DViewer-test.js | 59 +++++++++++- .../controls/model3d/RotateAxisControl.tsx | 18 +++- .../controls/model3d/RotateAxisControls.tsx | 4 +- .../__tests__/Model3DSettings-test.tsx | 91 +++++++++++++++++++ .../__tests__/RotateAxisControl-test.tsx | 50 ++++++++++ .../__tests__/RotateAxisControls-test.tsx | 30 ++++++ .../settings/__tests__/Settings-test.tsx | 60 ++++++++++++ 9 files changed, 368 insertions(+), 10 deletions(-) create mode 100644 src/lib/viewers/controls/model3d/__tests__/Model3DSettings-test.tsx create mode 100644 src/lib/viewers/controls/model3d/__tests__/RotateAxisControl-test.tsx create mode 100644 src/lib/viewers/controls/model3d/__tests__/RotateAxisControls-test.tsx diff --git a/src/lib/viewers/box3d/model3d/Model3DViewer.js b/src/lib/viewers/box3d/model3d/Model3DViewer.js index adcee758b..76236b389 100644 --- a/src/lib/viewers/box3d/model3d/Model3DViewer.js +++ b/src/lib/viewers/box3d/model3d/Model3DViewer.js @@ -46,6 +46,9 @@ class Model3DViewer extends Box3DViewer { /** @property {boolean} - Boolean indicating whether the animation is playing */ isAnimationPlaying = false; + /** @property {string} - string indicating what the camera projection is */ + projection = CAMERA_PROJECTION_PERSPECTIVE; + /** @property {string} - string indicating what the render mode is */ renderMode = RENDER_MODE_LIT; @@ -409,7 +412,7 @@ class Model3DViewer extends Box3DViewer { this.renderer.setCameraProjection(projection); if (this.controls && this.getViewerOption('useReactControls')) { - this.cameraProjection = projection; + this.projection = projection; this.renderUI(); } } diff --git a/src/lib/viewers/box3d/model3d/__tests__/Model3DControlsNew-test.tsx b/src/lib/viewers/box3d/model3d/__tests__/Model3DControlsNew-test.tsx index bcfdbe1c0..36ccb1d47 100644 --- a/src/lib/viewers/box3d/model3d/__tests__/Model3DControlsNew-test.tsx +++ b/src/lib/viewers/box3d/model3d/__tests__/Model3DControlsNew-test.tsx @@ -1,31 +1,71 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import Model3DControls, { Props } from '../Model3DControlsNew'; -import ResetControl from '../../../controls/model3d/ResetControl'; import AnimationControls from '../../../controls/model3d/AnimationControls'; import FullscreenToggle from '../../../controls/fullscreen'; +import Model3DControls, { Props } from '../Model3DControlsNew'; +import ResetControl from '../../../controls/model3d/ResetControl'; +import Model3DSettings, { CameraProjection, RenderMode } from '../../../controls/model3d/Model3DSettings'; describe('lib/viewers/box3d/model3d/Model3DControlsNew', () => { const getDefaults = (): Props => ({ animationClips: [], + cameraProjection: CameraProjection.PERSPECTIVE, currentAnimationClipId: '123', isPlaying: false, onAnimationClipSelect: jest.fn(), + onCameraProjectionChange: jest.fn(), onFullscreenToggle: jest.fn(), onPlayPause: jest.fn(), + onRenderModeChange: jest.fn(), + onRotateOnAxisChange: jest.fn(), onReset: jest.fn(), + onSettingsClose: jest.fn(), + onSettingsOpen: jest.fn(), + onShowGridToggle: jest.fn(), + onShowSkeletonsToggle: jest.fn(), + onShowWireframesToggle: jest.fn(), + renderMode: RenderMode.LIT, + showGrid: true, + showSkeletons: false, + showWireframes: false, }); const getWrapper = (props: Partial): ShallowWrapper => shallow(); + describe('render()', () => { test('should return a valid wrapper', () => { const onAnimationClipSelect = jest.fn(); + const onCameraProjectionChange = jest.fn(); const onFullscreenToggle = jest.fn(); const onPlayPause = jest.fn(); + const onRenderModeChange = jest.fn(); + const onRotateOnAxisChange = jest.fn(); + const onReset = jest.fn(); + const onSettingsClose = jest.fn(); + const onSettingsOpen = jest.fn(); + const onShowGridToggle = jest.fn(); + const onShowSkeletonsToggle = jest.fn(); + const onShowWireframesToggle = jest.fn(); - const wrapper = getWrapper({ onAnimationClipSelect, onFullscreenToggle, onPlayPause }); + const wrapper = getWrapper({ + onAnimationClipSelect, + onCameraProjectionChange, + onFullscreenToggle, + onPlayPause, + onRenderModeChange, + onRotateOnAxisChange, + onReset, + onSettingsClose, + onSettingsOpen, + onShowGridToggle, + onShowSkeletonsToggle, + onShowWireframesToggle, + }); + expect(wrapper.find(ResetControl).props()).toMatchObject({ + onReset, + }); expect(wrapper.find(AnimationControls).props()).toMatchObject({ animationClips: [], currentAnimationClipId: '123', @@ -33,6 +73,21 @@ describe('lib/viewers/box3d/model3d/Model3DControlsNew', () => { onAnimationClipSelect, onPlayPause, }); + expect(wrapper.find(Model3DSettings).props()).toMatchObject({ + cameraProjection: CameraProjection.PERSPECTIVE, + onCameraProjectionChange, + onClose: onSettingsClose, + onOpen: onSettingsOpen, + onRenderModeChange, + onRotateOnAxisChange, + onShowGridToggle, + onShowSkeletonsToggle, + onShowWireframesToggle, + renderMode: RenderMode.LIT, + showGrid: true, + showSkeletons: false, + showWireframes: false, + }); expect(wrapper.find(FullscreenToggle).prop('onFullscreenToggle')).toEqual(onFullscreenToggle); }); }); diff --git a/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js b/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js index 8feb54ff1..b6c0015c1 100644 --- a/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js +++ b/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js @@ -675,6 +675,48 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { .withArgs(true); model3d.handleShowGrid(true); }); + + describe('with react controls', () => { + beforeEach(() => { + jest.spyOn(model3d, 'getViewerOption').mockImplementation(() => true); + jest.spyOn(model3d, 'renderUI').mockImplementation(() => {}); + }); + + test('should update renderMode and invoke renderUI when calling handleSetRenderMode()', () => { + model3d.handleSetRenderMode('Unlit'); + + expect(model3d.renderMode).toBe('Unlit'); + expect(model3d.renderUI).toBeCalled(); + }); + + test('should update cameraProjection and invoke renderUI when calling handleSetCameraProjection()', () => { + model3d.handleSetCameraProjection('Orthographic'); + + expect(model3d.projection).toBe('Orthographic'); + expect(model3d.renderUI).toBeCalled(); + }); + + test('should update showSkeletons and invoke renderUI when calling handleShowSkeletons()', () => { + model3d.handleShowSkeletons(true); + + expect(model3d.showSkeletons).toBe(true); + expect(model3d.renderUI).toBeCalled(); + }); + + test('should update showWireframes and invoke renderUI when calling handleShowWireframes()', () => { + model3d.handleShowWireframes(true); + + expect(model3d.showWireframes).toBe(true); + expect(model3d.renderUI).toBeCalled(); + }); + + test('should update showGrid and invoke renderUI when calling handleShowGrid()', () => { + model3d.handleShowGrid(false); + + expect(model3d.showGrid).toBe(false); + expect(model3d.renderUI).toBeCalled(); + }); + }); }); describe('scene load errors', () => { @@ -813,7 +855,7 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { expect(model3d.axes.forward).toBe('+Z'); expect(model3d.renderMode).toBe('Lit'); expect(model3d.projection).toBe('Perspective'); - expect(model3d.renderGrid).toBe(true); + expect(model3d.showGrid).toBe(true); expect(model3d.handleRotationAxisSet).not.toBeCalled(); }); @@ -831,7 +873,7 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { expect(model3d.axes.forward).toBe(defaults.forwardAxis); expect(model3d.renderMode).toBe(defaults.defaultRenderMode); expect(model3d.projection).toBe(defaults.cameraProjection); - expect(model3d.renderGrid).toBe(false); + expect(model3d.showGrid).toBe(false); expect(model3d.handleRotationAxisSet).toBeCalledWith(defaults.upAxis, defaults.forwardAxis, false); }); @@ -921,12 +963,25 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { expect(getProps(model3d)).toMatchObject({ animationClips: [], + cameraProjection: 'Perspective', currentAnimationClipId: '123', isPlaying: false, onAnimationClipSelect: model3d.handleSelectAnimationClip, + onCameraProjectionChange: model3d.handleSetCameraProjection, onFullscreenToggle: model3d.toggleFullscreen, onPlayPause: model3d.handleToggleAnimation, + onRenderModeChange: model3d.handleSetRenderMode, onReset: model3d.handleReset, + onRotateOnAxisChange: model3d.handleRotateOnAxis, + onSettingsClose: expect.any(Function), + onSettingsOpen: expect.any(Function), + onShowGridToggle: model3d.handleShowGrid, + onShowSkeletonsToggle: model3d.handleShowSkeletons, + onShowWireframesToggle: model3d.handleShowWireframes, + renderMode: 'Lit', + showGrid: true, + showSkeletons: false, + showWireframes: false, }); }); }); diff --git a/src/lib/viewers/controls/model3d/RotateAxisControl.tsx b/src/lib/viewers/controls/model3d/RotateAxisControl.tsx index 63cc0b36e..b43ddaec7 100644 --- a/src/lib/viewers/controls/model3d/RotateAxisControl.tsx +++ b/src/lib/viewers/controls/model3d/RotateAxisControl.tsx @@ -22,9 +22,21 @@ export default function RotateAxisControl({ axis, className, onRotateOnAxisChang return (
-
); } diff --git a/src/lib/viewers/controls/model3d/RotateAxisControls.tsx b/src/lib/viewers/controls/model3d/RotateAxisControls.tsx index b98c3912f..c096cbcf8 100644 --- a/src/lib/viewers/controls/model3d/RotateAxisControls.tsx +++ b/src/lib/viewers/controls/model3d/RotateAxisControls.tsx @@ -9,7 +9,9 @@ export type Props = { export default function RotateAxisControls({ onRotateOnAxisChange }: Props): JSX.Element { return (
-
{__('box3d_settings_rotate_label')}
+
+ {__('box3d_settings_rotate_label')} +
{ + const getDefaults = (): Props => ({ + cameraProjection: CameraProjection.PERSPECTIVE, + onCameraProjectionChange: jest.fn(), + onClose: jest.fn(), + onOpen: jest.fn(), + onRenderModeChange: jest.fn(), + onRotateOnAxisChange: jest.fn(), + onShowGridToggle: jest.fn(), + onShowSkeletonsToggle: jest.fn(), + onShowWireframesToggle: jest.fn(), + renderMode: RenderMode.LIT, + showGrid: true, + showSkeletons: false, + showWireframes: false, + }); + const getWrapper = (props = {}): ShallowWrapper => shallow(); + + describe('render()', () => { + test('should return a valid wrapper', () => { + const onCameraProjectionChange = jest.fn(); + const onClose = jest.fn(); + const onOpen = jest.fn(); + const onRenderModeChange = jest.fn(); + const onRotateOnAxisChange = jest.fn(); + const onShowGridToggle = jest.fn(); + const onShowSkeletonsToggle = jest.fn(); + const onShowWireframesToggle = jest.fn(); + + const wrapper = getWrapper({ + onCameraProjectionChange, + onClose, + onOpen, + onRenderModeChange, + onRotateOnAxisChange, + onShowGridToggle, + onShowSkeletonsToggle, + onShowWireframesToggle, + }); + const checkboxItems = wrapper.find(Settings.CheckboxItem); + const dropdowns = wrapper.find(Settings.Dropdown); + + expect(wrapper.find(Settings).props()).toMatchObject({ + className: 'bp-Model3DSettings', + onClose, + onOpen, + }); + expect(wrapper.find(Settings.Menu).props()).toMatchObject({ + className: 'bp-Model3DSettings-menu', + name: Menu.MAIN, + }); + + // CheckboxItems + expect(checkboxItems.at(0).props()).toMatchObject({ + isChecked: true, + label: __('box3d_settings_grid_label'), + onChange: onShowGridToggle, + }); + expect(checkboxItems.at(1).props()).toMatchObject({ + isChecked: false, + label: __('box3d_settings_wireframes_label'), + onChange: onShowWireframesToggle, + }); + expect(checkboxItems.at(2).props()).toMatchObject({ + isChecked: false, + label: __('box3d_settings_skeletons_label'), + onChange: onShowSkeletonsToggle, + }); + + // Dropdowns + expect(dropdowns.at(0).props()).toMatchObject({ + label: __('box3d_settings_render_label'), + onSelect: onRenderModeChange, + value: RenderMode.LIT, + }); + expect(dropdowns.at(1).props()).toMatchObject({ + label: __('box3d_settings_projection_label'), + onSelect: onCameraProjectionChange, + value: CameraProjection.PERSPECTIVE, + }); + + expect(wrapper.find(RotateAxisControls).props()).toMatchObject({ onRotateOnAxisChange }); + }); + }); +}); diff --git a/src/lib/viewers/controls/model3d/__tests__/RotateAxisControl-test.tsx b/src/lib/viewers/controls/model3d/__tests__/RotateAxisControl-test.tsx new file mode 100644 index 000000000..fb2d460dc --- /dev/null +++ b/src/lib/viewers/controls/model3d/__tests__/RotateAxisControl-test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import RotateAxisControl, { Props } from '../RotateAxisControl'; + +describe('RotateAxisControl', () => { + const getDefaults = (): Props => ({ + axis: 'x', + onRotateOnAxisChange: jest.fn(), + }); + const getWrapper = (props = {}): ShallowWrapper => shallow(); + + describe('onRotateOnAxisChange()', () => { + test('should indicate a negative rotation when the left button is clicked', () => { + const onRotateOnAxisChange = jest.fn(); + const wrapper = getWrapper({ onRotateOnAxisChange }); + + wrapper.find('[data-testid="bp-RotateAxisControl-left"]').simulate('click'); + + expect(onRotateOnAxisChange).toBeCalledWith({ x: -90 }); + }); + + test('should indicate a positive rotation when the right button is clicked', () => { + const onRotateOnAxisChange = jest.fn(); + const wrapper = getWrapper({ onRotateOnAxisChange }); + + wrapper.find('[data-testid="bp-RotateAxisControl-right"]').simulate('click'); + + expect(onRotateOnAxisChange).toBeCalledWith({ x: 90 }); + }); + }); + + describe('render()', () => { + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + + expect(wrapper.hasClass('bp-RotateAxisControl')).toBe(true); + expect(wrapper.find('[data-testid="bp-RotateAxisControl-left"]').props()).toMatchObject({ + className: 'bp-RotateAxisControl-left', + onClick: expect.any(Function), + type: 'button', + }); + expect(wrapper.find('[data-testid="bp-RotateAxisControl-label"]').text()).toBe('x'); + expect(wrapper.find('[data-testid="bp-RotateAxisControl-right"]').props()).toMatchObject({ + className: 'bp-RotateAxisControl-right', + onClick: expect.any(Function), + type: 'button', + }); + }); + }); +}); diff --git a/src/lib/viewers/controls/model3d/__tests__/RotateAxisControls-test.tsx b/src/lib/viewers/controls/model3d/__tests__/RotateAxisControls-test.tsx new file mode 100644 index 000000000..64caefee7 --- /dev/null +++ b/src/lib/viewers/controls/model3d/__tests__/RotateAxisControls-test.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import RotateAxisControls, { Props } from '../RotateAxisControls'; +import RotateAxisControl from '../RotateAxisControl'; + +describe('RotateAxisControls', () => { + const getDefaults = (): Props => ({ + onRotateOnAxisChange: jest.fn(), + }); + const getWrapper = (props = {}): ShallowWrapper => shallow(); + + describe('render()', () => { + test('should return a valid wrapper', () => { + const onRotateOnAxisChange = jest.fn(); + const wrapper = getWrapper({ onRotateOnAxisChange }); + const controls = wrapper.find(RotateAxisControl); + + expect(wrapper.hasClass('bp-RotateAxisControls')).toBe(true); + expect(wrapper.find('[data-testid="bp-RotateAxisControls-label"]').text()).toBe( + __('box3d_settings_rotate_label'), + ); + ['x', 'y', 'z'].forEach((axis, index) => { + expect(controls.at(index).props()).toMatchObject({ + axis, + onRotateOnAxisChange, + }); + }); + }); + }); +}); diff --git a/src/lib/viewers/controls/settings/__tests__/Settings-test.tsx b/src/lib/viewers/controls/settings/__tests__/Settings-test.tsx index d49262bc9..d348764e9 100644 --- a/src/lib/viewers/controls/settings/__tests__/Settings-test.tsx +++ b/src/lib/viewers/controls/settings/__tests__/Settings-test.tsx @@ -85,6 +85,66 @@ describe('Settings', () => { }); }); + describe('open/close callbacks', () => { + test('should call the onOpen callback when the flyout opens', () => { + const onClose = jest.fn(); + const onOpen = jest.fn(); + const wrapper = getWrapper({ onClose, onOpen }); + + expect(wrapper.find(SettingsFlyout).prop('isOpen')).toBe(false); + expect(wrapper.find(SettingsGearToggle).prop('isOpen')).toBe(false); + + wrapper.find(SettingsGearToggle).simulate('click'); + + expect(wrapper.find(SettingsFlyout).prop('isOpen')).toBe(true); + expect(onOpen).toBeCalledTimes(1); + expect(onClose).not.toBeCalled(); + }); + + test('should call the onClose callback when the flyout closes', () => { + const onClose = jest.fn(); + const onOpen = jest.fn(); + const wrapper = getWrapper({ onClose, onOpen }); + + expect(wrapper.find(SettingsFlyout).prop('isOpen')).toBe(false); + expect(wrapper.find(SettingsGearToggle).prop('isOpen')).toBe(false); + + wrapper.find(SettingsGearToggle).simulate('click'); + + expect(wrapper.find(SettingsFlyout).prop('isOpen')).toBe(true); + expect(onOpen).toBeCalledTimes(1); + expect(onClose).not.toBeCalled(); + + wrapper.find(SettingsGearToggle).simulate('click'); + + expect(wrapper.find(SettingsFlyout).prop('isOpen')).toBe(false); + expect(onOpen).toBeCalledTimes(1); + expect(onClose).toBeCalledTimes(1); + }); + + test('should call the onClose callback when the flyout is closed by clicking outside', () => { + const onClose = jest.fn(); + const onOpen = jest.fn(); + const wrapper = getWrapper({ onClose, onOpen }); + const getEvent = (target: HTMLElement): MouseEvent => { + const event = new MouseEvent('click'); + Object.defineProperty(event, 'target', { enumerable: true, value: target }); + return event; + }; + + wrapper.find(SettingsGearToggle).simulate('click'); // Open the controls + expect(wrapper.find(SettingsGearToggle).prop('isOpen')).toBe(true); + + act(() => { + document.dispatchEvent(getEvent(document.body)); // Click outside the controls + }); + wrapper.update(); + + expect(onOpen).toBeCalledTimes(1); + expect(onClose).toBeCalledTimes(1); + }); + }); + describe('render', () => { test('should return a valid wrapper', () => { const wrapper = getWrapper(); From 9ef27332df8ab8db40fbb62751e789a0f24f9a0a Mon Sep 17 00:00:00 2001 From: Conrad Chan Date: Thu, 13 May 2021 14:34:14 -0700 Subject: [PATCH 4/5] chore: pr comments --- src/lib/viewers/box3d/model3d/Model3DViewer.js | 14 ++------------ .../box3d/model3d/__tests__/Model3DViewer-test.js | 10 +++++----- .../viewers/controls/model3d/Model3DSettings.scss | 5 +++-- .../viewers/controls/model3d/Model3DSettings.tsx | 2 +- .../controls/model3d/RotateAxisControls.scss | 6 +++--- .../controls/settings/SettingsDropdown.scss | 2 +- .../viewers/controls/settings/SettingsDropdown.tsx | 13 ++++++++----- .../settings/__tests__/SettingsDropdown-test.tsx | 2 +- 8 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/lib/viewers/box3d/model3d/Model3DViewer.js b/src/lib/viewers/box3d/model3d/Model3DViewer.js index 76236b389..70f442a2d 100644 --- a/src/lib/viewers/box3d/model3d/Model3DViewer.js +++ b/src/lib/viewers/box3d/model3d/Model3DViewer.js @@ -350,22 +350,12 @@ class Model3DViewer extends Box3DViewer { super.handleReset(); this.setAnimationState(false); + this.handleSetCameraProjection(this.projection); + this.handleSetRenderMode(this.renderMode); this.handleShowGrid(true); this.handleShowSkeletons(false); this.handleShowWireframes(false); - if (this.controls) { - if (this.getViewerOption('useReactControls')) { - this.renderUI(); - } else { - this.controls.handleSetRenderMode(this.renderMode); - this.controls.setCurrentProjectionMode(this.projection); - this.controls.handleSetSkeletonsVisible(false); - this.controls.handleSetWireframesVisible(false); - this.controls.handleSetGridVisible(this.showGrid); - } - } - if (this.renderer) { this.handleRotationAxisSet(this.axes.up, this.axes.forward, false); this.renderer.stopAnimation(); diff --git a/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js b/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js index b6c0015c1..fb778b00f 100644 --- a/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js +++ b/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js @@ -786,11 +786,6 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { describe('handleReset()', () => { test('should reset control settings', () => { - sandbox.mock(model3d.controls).expects('handleSetRenderMode'); - sandbox.mock(model3d.controls).expects('setCurrentProjectionMode'); - sandbox.mock(model3d.controls).expects('handleSetSkeletonsVisible'); - sandbox.mock(model3d.controls).expects('handleSetWireframesVisible'); - sandbox.mock(model3d.controls).expects('handleSetGridVisible'); const renderMock = sandbox.mock(model3d.renderer); renderMock.expects('stopAnimation').once(); model3d.handleReset(); @@ -824,6 +819,11 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { model3d.handleReset(); expect(model3d.isAnimationPlaying).toBe(false); + expect(model3d.projection).toBe('Perspective'); + expect(model3d.renderMode).toBe('Lit'); + expect(model3d.showGrid).toBe(true); + expect(model3d.showSkeletons).toBe(false); + expect(model3d.showWireframes).toBe(false); expect(model3d.renderUI).toBeCalled(); }); }); diff --git a/src/lib/viewers/controls/model3d/Model3DSettings.scss b/src/lib/viewers/controls/model3d/Model3DSettings.scss index fe872e241..fdca82aab 100644 --- a/src/lib/viewers/controls/model3d/Model3DSettings.scss +++ b/src/lib/viewers/controls/model3d/Model3DSettings.scss @@ -18,8 +18,9 @@ } .bp-Model3DSettings-menu { - & .bp-SettingsCheckboxItem, - & .bp-SettingsDropdown { + .bp-SettingsCheckboxItem, + .bp-SettingsDropdown, + .bp-RotateAxisControls { margin-top: 10px; } } diff --git a/src/lib/viewers/controls/model3d/Model3DSettings.tsx b/src/lib/viewers/controls/model3d/Model3DSettings.tsx index a0fc40237..cb424326f 100644 --- a/src/lib/viewers/controls/model3d/Model3DSettings.tsx +++ b/src/lib/viewers/controls/model3d/Model3DSettings.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Settings, { Menu, Props as SettingsProps } from '../settings'; import RotateAxisControls from './RotateAxisControls'; +import Settings, { Menu, Props as SettingsProps } from '../settings'; import { AxisChange } from './RotateAxisControl'; import './Model3DSettings.scss'; diff --git a/src/lib/viewers/controls/model3d/RotateAxisControls.scss b/src/lib/viewers/controls/model3d/RotateAxisControls.scss index ee0199e87..b8d6fc25e 100644 --- a/src/lib/viewers/controls/model3d/RotateAxisControls.scss +++ b/src/lib/viewers/controls/model3d/RotateAxisControls.scss @@ -6,10 +6,10 @@ $bp-RotateAxisControls-spacing: 4px; display: flex; flex-direction: column; color: $bdl-gray-62; +} - .bp-RotateAxisControl + .bp-RotateAxisControl { - margin-left: $bp-RotateAxisControls-spacing; - } +.bp-RotateAxisControl + .bp-RotateAxisControl { + margin-left: $bp-RotateAxisControls-spacing; } .bp-RotateAxisControls-label { diff --git a/src/lib/viewers/controls/settings/SettingsDropdown.scss b/src/lib/viewers/controls/settings/SettingsDropdown.scss index 5b34e6741..4b81c45f3 100644 --- a/src/lib/viewers/controls/settings/SettingsDropdown.scss +++ b/src/lib/viewers/controls/settings/SettingsDropdown.scss @@ -11,7 +11,7 @@ $bp-SettingsDropdown-spacing: 5px; } .bp-SettingsDropdown-flyout { - top: initial; + top: auto; right: 0; bottom: 0; left: 0; diff --git a/src/lib/viewers/controls/settings/SettingsDropdown.tsx b/src/lib/viewers/controls/settings/SettingsDropdown.tsx index 7158c3e48..7593b3726 100644 --- a/src/lib/viewers/controls/settings/SettingsDropdown.tsx +++ b/src/lib/viewers/controls/settings/SettingsDropdown.tsx @@ -7,12 +7,14 @@ import useClickOutside from '../hooks/useClickOutside'; import { decodeKeydown } from '../../../util'; import './SettingsDropdown.scss'; -export type ListItem = { +export type Value = boolean | number | string; + +export type ListItem = { label: string; value: V; }; -export type Props = { +export type Props = { className?: string; label: string; listItems: Array>; @@ -20,7 +22,7 @@ export type Props = { value?: V; }; -export default function SettingsDropdown({ +export default function SettingsDropdown({ className, label, listItems, @@ -100,12 +102,13 @@ export default function SettingsDropdown({ tabIndex={-1} > {listItems.map(({ label: itemLabel, value: itemValue }) => { + const itemValueString = itemValue.toString(); return (
{ { label: 'second', value: 'second' }, { label: 'third', value: 'third' }, ]; - const getDefaults = (): Props => ({ + const getDefaults = (): Props => ({ label: 'Dropdown Label', listItems, onSelect: jest.fn(), From 761b6bf1d0435c47a4f954fd078cd58e00be7bd5 Mon Sep 17 00:00:00 2001 From: Conrad Chan Date: Thu, 13 May 2021 15:13:35 -0700 Subject: [PATCH 5/5] chore: revert handleReset changes --- src/lib/viewers/box3d/model3d/Model3DViewer.js | 12 ++++++++++++ .../box3d/model3d/__tests__/Model3DViewer-test.js | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/src/lib/viewers/box3d/model3d/Model3DViewer.js b/src/lib/viewers/box3d/model3d/Model3DViewer.js index 70f442a2d..466c16203 100644 --- a/src/lib/viewers/box3d/model3d/Model3DViewer.js +++ b/src/lib/viewers/box3d/model3d/Model3DViewer.js @@ -356,6 +356,18 @@ class Model3DViewer extends Box3DViewer { this.handleShowSkeletons(false); this.handleShowWireframes(false); + if (this.controls) { + if (this.getViewerOption('useReactControls')) { + this.renderUI(); + } else { + this.controls.handleSetRenderMode(this.renderMode); + this.controls.setCurrentProjectionMode(this.projection); + this.controls.handleSetSkeletonsVisible(false); + this.controls.handleSetWireframesVisible(false); + this.controls.handleSetGridVisible(this.showGrid); + } + } + if (this.renderer) { this.handleRotationAxisSet(this.axes.up, this.axes.forward, false); this.renderer.stopAnimation(); diff --git a/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js b/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js index fb778b00f..1c826d0cb 100644 --- a/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js +++ b/src/lib/viewers/box3d/model3d/__tests__/Model3DViewer-test.js @@ -786,6 +786,11 @@ describe('lib/viewers/box3d/model3d/Model3DViewer', () => { describe('handleReset()', () => { test('should reset control settings', () => { + sandbox.mock(model3d.controls).expects('handleSetRenderMode'); + sandbox.mock(model3d.controls).expects('setCurrentProjectionMode'); + sandbox.mock(model3d.controls).expects('handleSetSkeletonsVisible'); + sandbox.mock(model3d.controls).expects('handleSetWireframesVisible'); + sandbox.mock(model3d.controls).expects('handleSetGridVisible'); const renderMock = sandbox.mock(model3d.renderer); renderMock.expects('stopAnimation').once(); model3d.handleReset();