diff --git a/src/lib/viewers/box3d/model3d/Model3DRenderer.js b/src/lib/viewers/box3d/model3d/Model3DRenderer.js index 61c0467a2..d9fbb9f96 100644 --- a/src/lib/viewers/box3d/model3d/Model3DRenderer.js +++ b/src/lib/viewers/box3d/model3d/Model3DRenderer.js @@ -652,6 +652,20 @@ class Model3DRenderer extends Box3DRenderer { initVrGamepadControls() { this.vrControls = new Model3DVrControls(this.vrGamepads, this.box3d); } + + /** + * Gets any selected animation clip + * + * @return {string} selected animation clip id + */ + getAnimationClip() { + if (!this.instance) { + return ''; + } + + const component = this.instance.getComponentByScriptId('animation'); + return component.clipId; + } } export default Model3DRenderer; diff --git a/src/lib/viewers/box3d/model3d/Model3DViewer.js b/src/lib/viewers/box3d/model3d/Model3DViewer.js index 7550b73ca..94f81743b 100644 --- a/src/lib/viewers/box3d/model3d/Model3DViewer.js +++ b/src/lib/viewers/box3d/model3d/Model3DViewer.js @@ -146,6 +146,11 @@ class Model3DViewer extends Box3DViewer { */ handleSelectAnimationClip(clipId) { this.renderer.setAnimationClip(clipId); + + if (this.controls && this.getViewerOption('useReactControls')) { + this.setAnimationState(false); + this.renderUI(); + } } /** @@ -294,18 +299,18 @@ class Model3DViewer extends Box3DViewer { * @return {void} */ handleToggleAnimation(play) { - if (this.getViewerOption('useReactControls')) { - this.isAnimationPlaying = !this.isAnimationPlaying; - this.renderer.toggleAnimation(this.isAnimationPlaying); + this.setAnimationState(play); - if (this.controls) { - this.renderUI(); - } - } else { - this.renderer.toggleAnimation(play); + if (this.controls && this.getViewerOption('useReactControls')) { + this.renderUI(); } } + setAnimationState(play) { + this.isAnimationPlaying = play; + this.renderer.toggleAnimation(this.isAnimationPlaying); + } + /** * Handle canvas focus events. * @method handleCanvasClick @@ -422,13 +427,14 @@ class Model3DViewer extends Box3DViewer { } renderUI() { - if (!this.controls) { + if (!this.controls || !this.renderer) { return; } this.controls.render( void; }; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function AnimationClipsControl(props: Props): JSX.Element { +export const padLeft = (x: number, width: number): string => { + return x.length >= width ? x : new Array(width - x.length + 1).join('0') + x; +}; + +export const formatDurationStr = (duration: number): string => { + let secondsLeft = Math.floor(duration); + const hours = Math.floor(secondsLeft / 3600); + const hoursStr = padLeft(hours.toString(), 2); + + secondsLeft -= hours * 3600; + const minutes = Math.floor(secondsLeft / 60); + const minutesStr = padLeft(minutes.toString(), 2); + + secondsLeft -= minutes * 60; + const secondsStr = padLeft(secondsLeft.toString(), 2); + + return `${hoursStr}:${minutesStr}:${secondsStr}`; +}; + +export default function AnimationClipsControl({ + animationClips, + currentAnimationClipId, + onAnimationClipSelect, +}: Props): JSX.Element { return ( - <> - - {/* TODO: AnimationClipsFlyout */} - + + + {animationClips.map(({ duration, id, name }) => { + const isSelected = id === currentAnimationClipId; + return ( + + ); + })} + + ); } diff --git a/src/lib/viewers/controls/model3d/AnimationClipsToggle.tsx b/src/lib/viewers/controls/model3d/AnimationClipsToggle.tsx index 6684ca02b..69d50aa5d 100644 --- a/src/lib/viewers/controls/model3d/AnimationClipsToggle.tsx +++ b/src/lib/viewers/controls/model3d/AnimationClipsToggle.tsx @@ -6,10 +6,20 @@ export type Props = { onClick?: () => void; }; -export default function AnimationClipsToggle({ onClick }: Props): JSX.Element { +function AnimationClipsToggle(props: Props, ref: React.Ref): JSX.Element { + const { onClick } = props; + return ( - ); } + +export default React.forwardRef(AnimationClipsToggle); diff --git a/src/lib/viewers/controls/settings/Settings.tsx b/src/lib/viewers/controls/settings/Settings.tsx index c872a3889..a3e5a5beb 100644 --- a/src/lib/viewers/controls/settings/Settings.tsx +++ b/src/lib/viewers/controls/settings/Settings.tsx @@ -11,9 +11,17 @@ import { decodeKeydown } from '../../../util'; export type Props = React.PropsWithChildren<{ className?: string; + disableTransitions?: boolean; + icon?: React.ReactNode; }>; -export default function Settings({ children, className, ...rest }: Props): JSX.Element | null { +export default function Settings({ + children, + className, + disableTransitions = false, + icon: ToggleIcon = SettingsToggle, + ...rest +}: Props): JSX.Element | null { const [activeMenu, setActiveMenu] = React.useState(Menu.MAIN); const [activeRect, setActiveRect] = React.useState(); const [isFocused, setIsFocused] = React.useState(false); @@ -79,8 +87,10 @@ export default function Settings({ children, className, ...rest }: Props): JSX.E {...rest} > - - {children} + + + {children} + ); diff --git a/src/lib/viewers/controls/settings/SettingsFlyout.tsx b/src/lib/viewers/controls/settings/SettingsFlyout.tsx index 59573074c..19819e91d 100644 --- a/src/lib/viewers/controls/settings/SettingsFlyout.tsx +++ b/src/lib/viewers/controls/settings/SettingsFlyout.tsx @@ -5,14 +5,20 @@ import './SettingsFlyout.scss'; export type Props = React.PropsWithChildren<{ className?: string; + disableTransitions?: boolean; isOpen: boolean; }>; -export default function SettingsFlyout({ children, className, isOpen }: Props): JSX.Element { +export default function SettingsFlyout({ + children, + className, + disableTransitions = false, + isOpen, +}: Props): JSX.Element { const [isTransitioning, setIsTransitioning] = React.useState(false); const flyoutElRef = React.useRef(null); - const { activeRect } = React.useContext(SettingsContext); - const { height, width } = activeRect || { height: 'auto', width: 'auto' }; + const { activeRect = { height: 'auto', width: 'auto' } } = React.useContext(SettingsContext); + const { height, width } = disableTransitions ? { height: 'auto', width: 'auto' } : activeRect; React.useEffect(() => { const { current: flyoutEl } = flyoutElRef;