From 0e6519ed70c64820504f9957d3e11d7fa225c61b Mon Sep 17 00:00:00 2001 From: Jonah Bedouch Date: Fri, 4 Aug 2023 02:21:03 -0700 Subject: [PATCH] Control panel layout configuration (#70) * Implement Collapse button in sidebar * Rework sidebar API to allow toggling between collapsible and fixed sidebar * Update Example 11 to use new API * Nits, fixes * prettier --------- Co-authored-by: Brent Yi --- .vscode/settings.json | 6 + examples/.vscode/settings.json | 6 + examples/11_colmap_visualizer.py | 2 +- examples/13_theming.py | 5 +- viser/_message_api.py | 4 +- viser/_messages.py | 2 +- viser/client/src/App.tsx | 14 +- viser/client/src/CameraControls.tsx | 10 +- .../client/src/ControlPanel/ControlPanel.tsx | 149 +++++++++++++----- .../client/src/ControlPanel/FloatingPanel.tsx | 16 +- viser/client/src/ControlPanel/Generated.tsx | 4 +- viser/client/src/ControlPanel/GuiState.tsx | 8 +- .../src/ControlPanel/SceneTreeTable.tsx | 8 +- viser/client/src/SceneTree.tsx | 20 +-- viser/client/src/SceneTreeState.tsx | 14 +- viser/client/src/SearchParamsUtils.tsx | 4 +- viser/client/src/ThreeAssets.tsx | 6 +- viser/client/src/Titlebar.tsx | 9 +- viser/client/src/Utils.tsx | 4 +- viser/client/src/WebsocketFunctions.tsx | 11 +- viser/client/src/WebsocketInterface.tsx | 80 +++++----- viser/client/src/WebsocketMessages.tsx | 2 +- viser/client/src/index.tsx | 2 +- 23 files changed, 238 insertions(+), 148 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 examples/.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..3445835be --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.formatting.provider": "none" +} diff --git a/examples/.vscode/settings.json b/examples/.vscode/settings.json new file mode 100644 index 000000000..3445835be --- /dev/null +++ b/examples/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.formatting.provider": "none" +} diff --git a/examples/11_colmap_visualizer.py b/examples/11_colmap_visualizer.py index 7e62b0dc9..f46cf5dd7 100644 --- a/examples/11_colmap_visualizer.py +++ b/examples/11_colmap_visualizer.py @@ -33,7 +33,7 @@ def main( downsample_factor: Downsample factor for the images. """ server = viser.ViserServer() - server.configure_theme(titlebar_content=None, fixed_sidebar=True) + server.configure_theme(titlebar_content=None, control_layout="collapsible") # Load the colmap info. cameras = read_cameras_binary(colmap_path / "cameras.bin") diff --git a/examples/13_theming.py b/examples/13_theming.py index 9040d1bee..abddd374c 100644 --- a/examples/13_theming.py +++ b/examples/13_theming.py @@ -38,10 +38,7 @@ titlebar_theme = TitlebarConfig(buttons=buttons, image=image) -server.configure_theme( - titlebar_content=titlebar_theme, - fixed_sidebar=True, -) +server.configure_theme(titlebar_content=titlebar_theme, control_layout="fixed") server.world_axes.visible = True while True: diff --git a/viser/_message_api.py b/viser/_message_api.py index 2c9eaa380..12da10cb5 100644 --- a/viser/_message_api.py +++ b/viser/_message_api.py @@ -140,14 +140,14 @@ def configure_theme( self, *, titlebar_content: Optional[theme.TitlebarConfig] = None, - fixed_sidebar: bool = False, + control_layout: Literal["floating", "collapsible", "fixed"] = "floating", dark_mode: bool = False, ) -> None: """Configure the viser front-end's visual appearance.""" self._queue( _messages.ThemeConfigurationMessage( titlebar_content=titlebar_content, - fixed_sidebar=fixed_sidebar, + control_layout=control_layout, dark_mode=dark_mode, ), ) diff --git a/viser/_messages.py b/viser/_messages.py index 9ecad6158..51f5b86fd 100644 --- a/viser/_messages.py +++ b/viser/_messages.py @@ -424,5 +424,5 @@ class ThemeConfigurationMessage(Message): """Message from server->client to configure parts of the GUI.""" titlebar_content: Optional[theme.TitlebarConfig] - fixed_sidebar: bool + control_layout: Literal["floating", "collapsible", "fixed"] dark_mode: bool diff --git a/viser/client/src/App.tsx b/viser/client/src/App.tsx index 544a13975..fb01b2a96 100644 --- a/viser/client/src/App.tsx +++ b/viser/client/src/App.tsx @@ -48,7 +48,7 @@ type ViewerContextContents = { }>; }; export const ViewerContext = React.createContext( - null + null, ); THREE.ColorManagement.enabled = true; @@ -65,7 +65,7 @@ function SingleViewer() { return server; } const servers = new URLSearchParams(window.location.search).getAll( - searchParamKey + searchParamKey, ); const initialServer = servers.length >= 1 ? servers[0] : getDefaultServerFromUrl(); @@ -87,10 +87,10 @@ function SingleViewer() { // viewer context changes. const memoizedWebsocketInterface = React.useMemo( () => , - [] + [], ); - const fixed_sidebar = viewer.useGui((state) => state.theme.fixed_sidebar); + const control_layout = viewer.useGui((state) => state.theme.control_layout); return ( {memoizedWebsocketInterface} - + @@ -177,7 +177,7 @@ function SceneContextSetter() { const { sceneRef, cameraRef } = React.useContext(ViewerContext)!; sceneRef.current = useThree((state) => state.scene); cameraRef.current = useThree( - (state) => state.camera as THREE.PerspectiveCamera + (state) => state.camera as THREE.PerspectiveCamera, ); return <>; } diff --git a/viser/client/src/CameraControls.tsx b/viser/client/src/CameraControls.tsx index f4ac6370e..58f4f9336 100644 --- a/viser/client/src/CameraControls.tsx +++ b/viser/client/src/CameraControls.tsx @@ -14,7 +14,7 @@ export function SynchronizedCameraControls() { const sendCameraThrottled = makeThrottledMessageSender( viewer.websocketRef, - 20 + 20, ); // Callback for sending cameras. @@ -122,28 +122,28 @@ export function SynchronizedCameraControls() { cameraControls.rotate( -0.1 * THREE.MathUtils.DEG2RAD * event?.deltaTime, 0, - true + true, ); }); rightKey.addEventListener("holding", (event) => { cameraControls.rotate( 0.1 * THREE.MathUtils.DEG2RAD * event?.deltaTime, 0, - true + true, ); }); upKey.addEventListener("holding", (event) => { cameraControls.rotate( 0, -0.05 * THREE.MathUtils.DEG2RAD * event?.deltaTime, - true + true, ); }); downKey.addEventListener("holding", (event) => { cameraControls.rotate( 0, 0.05 * THREE.MathUtils.DEG2RAD * event?.deltaTime, - true + true, ); }); diff --git a/viser/client/src/ControlPanel/ControlPanel.tsx b/viser/client/src/ControlPanel/ControlPanel.tsx index 1f1220da1..2721ca182 100644 --- a/viser/client/src/ControlPanel/ControlPanel.tsx +++ b/viser/client/src/ControlPanel/ControlPanel.tsx @@ -15,10 +15,13 @@ import { IconCloudCheck, IconCloudOff, IconArrowBack, + IconChevronLeft, + IconChevronRight, } from "@tabler/icons-react"; import React from "react"; import BottomPanel from "./BottomPanel"; import FloatingPanel, { FloatingPanelContext } from "./FloatingPanel"; +import { ThemeConfigurationMessage } from "../WebsocketMessages"; // Must match constant in Python. const ROOT_CONTAINER_ID = "root"; @@ -29,7 +32,9 @@ function HideWhenCollapsed({ children }: { children: React.ReactNode }) { return expanded ? children : null; } -export default function ControlPanel(props: { fixed_sidebar: boolean }) { +export default function ControlPanel(props: { + control_layout: ThemeConfigurationMessage["control_layout"]; +}) { const theme = useMantineTheme(); const useMobileView = useMediaQuery(`(max-width: ${theme.breakpoints.xs})`); @@ -38,6 +43,7 @@ export default function ControlPanel(props: { fixed_sidebar: boolean }) { const showGenerated = Object.keys(viewer.useGui((state) => state.guiConfigFromId)).length > 0; const [showSettings, { toggle }] = useDisclosure(false); + const [collapsed, { toggle: toggleCollapse }] = useDisclosure(false); const handleContents = ( <> @@ -47,7 +53,7 @@ export default function ControlPanel(props: { fixed_sidebar: boolean }) { + + { + evt.stopPropagation(); + toggleCollapse(); + }} + > + {} + + ); + const collapsedView = ( +
+ { + evt.stopPropagation(); + toggleCollapse(); + }} + > + {} + +
+ ); + const panelContents = ( <> @@ -91,41 +144,61 @@ export default function ControlPanel(props: { fixed_sidebar: boolean }) { {panelContents} ); - } else if (props.fixed_sidebar) { + } else if (props.control_layout !== "floating") { return ( - + ); } else { return ( @@ -146,17 +219,19 @@ function ConnectionStatus() { const StatusIcon = connected ? IconCloudCheck : IconCloudOff; return ( - <> +     {label === "" ? server : label} - + ); } diff --git a/viser/client/src/ControlPanel/FloatingPanel.tsx b/viser/client/src/ControlPanel/FloatingPanel.tsx index 1b80673fc..c26d9042f 100644 --- a/viser/client/src/ControlPanel/FloatingPanel.tsx +++ b/viser/client/src/ControlPanel/FloatingPanel.tsx @@ -78,7 +78,7 @@ FloatingPanel.Handle = function FloatingPanelHandle({ const computePanelOffset = ( panelPosition: number, panelSize: number, - parentSize: number + parentSize: number, ) => Math.abs(panelPosition + panelSize / 2.0) < Math.abs(panelPosition - parentSize + panelSize / 2.0) @@ -98,12 +98,12 @@ FloatingPanel.Handle = function FloatingPanelHandle({ newX = Math.min( newX, - parent.clientWidth - panel.clientWidth - panelBoundaryPad + parent.clientWidth - panel.clientWidth - panelBoundaryPad, ); newX = Math.max(newX, panelBoundaryPad); newY = Math.min( newY, - parent.clientHeight - panel.clientHeight - panelBoundaryPad + parent.clientHeight - panel.clientHeight - panelBoundaryPad, ); newY = Math.max(newY, panelBoundaryPad); @@ -134,13 +134,13 @@ FloatingPanel.Handle = function FloatingPanelHandle({ unfixedOffset.current.x = computePanelOffset( panel.offsetLeft, panel.clientWidth, - parent.clientWidth + parent.clientWidth, ); if (unfixedOffset.current.y === undefined) unfixedOffset.current.y = computePanelOffset( panel.offsetTop, panel.clientHeight, - parent.clientHeight + parent.clientHeight, ); panel.style.maxHeight = `${( @@ -164,7 +164,7 @@ FloatingPanel.Handle = function FloatingPanelHandle({ const dragHandler = ( event: | React.TouchEvent - | React.MouseEvent + | React.MouseEvent, ) => { const state = dragInfo.current; const panel = panelWrapperRef.current; @@ -201,7 +201,7 @@ FloatingPanel.Handle = function FloatingPanelHandle({ const newY = state.startPosY + deltaY; [unfixedOffset.current.x, unfixedOffset.current.y] = setPanelLocation( newX, - newY + newY, ); } window.addEventListener(eventNames.move, dragListener); @@ -213,7 +213,7 @@ FloatingPanel.Handle = function FloatingPanelHandle({ } window.removeEventListener(eventNames.move, dragListener); }, - { once: true } + { once: true }, ); }; diff --git a/viser/client/src/ControlPanel/Generated.tsx b/viser/client/src/ControlPanel/Generated.tsx index 9b992b47a..2d7ff04bd 100644 --- a/viser/client/src/ControlPanel/Generated.tsx +++ b/viser/client/src/ControlPanel/Generated.tsx @@ -31,7 +31,7 @@ export default function GeneratedGuiContainer({ }) { const viewer = React.useContext(ViewerContext)!; const guiIdSet = viewer.useGui( - (state) => state.guiIdSetFromContainerId[containerId] + (state) => state.guiIdSetFromContainerId[containerId], ); const guiConfigFromId = viewer.useGui((state) => state.guiConfigFromId); @@ -406,7 +406,7 @@ function VectorInput( precision: number; onChange: (value: number[]) => void; disabled: boolean; - } + }, ) { return ( diff --git a/viser/client/src/ControlPanel/GuiState.tsx b/viser/client/src/ControlPanel/GuiState.tsx index 50aa219d6..0e5a11f74 100644 --- a/viser/client/src/ControlPanel/GuiState.tsx +++ b/viser/client/src/ControlPanel/GuiState.tsx @@ -55,7 +55,7 @@ const cleanGuiState: GuiState = { theme: { type: "ThemeConfigurationMessage", titlebar_content: null, - fixed_sidebar: false, + control_layout: "floating", dark_mode: false, }, label: "", @@ -125,7 +125,7 @@ export function useGuiState(initialServer: string) { if (guiIdSet === undefined) { console.log( "Tried to remove but could not find container ID", - containerId + containerId, ); return; } @@ -139,8 +139,8 @@ export function useGuiState(initialServer: string) { state.guiValueFromId = {}; state.guiAttributeFromId = {}; }), - })) - ) + })), + ), )[0]; } diff --git a/viser/client/src/ControlPanel/SceneTreeTable.tsx b/viser/client/src/ControlPanel/SceneTreeTable.tsx index 1ce969aa4..f5abf4bcf 100644 --- a/viser/client/src/ControlPanel/SceneTreeTable.tsx +++ b/viser/client/src/ControlPanel/SceneTreeTable.tsx @@ -24,7 +24,7 @@ export default function SceneTreeTable(props: { compact: boolean }) { const nodeFromName = viewer.useSceneTree((state) => state.nodeFromName); const setLabelVisibility = viewer.useSceneTree( - (state) => state.setLabelVisibility + (state) => state.setLabelVisibility, ); function setVisible(name: string, visible: boolean) { const attr = viewer.nodeAttributesFromName.current; @@ -58,7 +58,7 @@ export default function SceneTreeTable(props: { compact: boolean }) { function getSceneTreeSubRows( parentName: string, parentCount: number, - isParentVisible: boolean + isParentVisible: boolean, ): SceneTreeTableRow[] { const node = nodeFromName[parentName]; if (node === undefined) return []; @@ -106,7 +106,7 @@ export default function SceneTreeTable(props: { compact: boolean }) { subRows: getSceneTreeSubRows( childName, parentCount + 1, - isVisibleEffective + isVisibleEffective, ), }; }); @@ -156,7 +156,7 @@ export default function SceneTreeTable(props: { compact: boolean }) { }, }, ], - [] + [], ); const [sceneTreeOpened, { open: openSceneTree, close: closeSceneTree }] = diff --git a/viser/client/src/SceneTree.tsx b/viser/client/src/SceneTree.tsx index bf070c9ca..eb60358eb 100644 --- a/viser/client/src/SceneTree.tsx +++ b/viser/client/src/SceneTree.tsx @@ -12,7 +12,7 @@ import { Text } from "@mantine/core"; import { useSceneTreeState } from "./SceneTreeState"; export type MakeObject = ( - ref: React.Ref + ref: React.Ref, ) => React.ReactNode; /** Scenes will consist of nodes, which form a tree. */ @@ -25,7 +25,7 @@ export class SceneNode { constructor( public name: string, public makeObject: MakeObject, - public cleanup?: () => void + public cleanup?: () => void, ) { this.children = []; this.clickable = false; @@ -65,7 +65,7 @@ function SceneNodeThreeChildren(props: { } const unsubscribe = viewer.useSceneTree.subscribe( (state) => state.nodeFromName[props.name], - updateChildren + updateChildren, ); updateChildren(); @@ -82,7 +82,7 @@ function SceneNodeThreeChildren(props: { return ; })} , - props.parent + props.parent, ); } @@ -90,7 +90,7 @@ function SceneNodeThreeChildren(props: { function SceneNodeLabel(props: { name: string }) { const viewer = React.useContext(ViewerContext)!; const labelVisible = viewer.useSceneTree( - (state) => state.labelVisibleFromName[props.name] + (state) => state.labelVisibleFromName[props.name], ); return labelVisible ? ( @@ -113,10 +113,10 @@ function SceneNodeLabel(props: { name: string }) { export function SceneNodeThreeObject(props: { name: string }) { const viewer = React.useContext(ViewerContext)!; const makeObject = viewer.useSceneTree( - (state) => state.nodeFromName[props.name]?.makeObject + (state) => state.nodeFromName[props.name]?.makeObject, ); const cleanup = viewer.useSceneTree( - (state) => state.nodeFromName[props.name]?.cleanup + (state) => state.nodeFromName[props.name]?.cleanup, ); const clickable = viewer.useSceneTree((state) => state.nodeFromName[props.name]?.clickable) ?? @@ -126,14 +126,14 @@ export function SceneNodeThreeObject(props: { name: string }) { // Create object + children. const objNode = React.useMemo( () => makeObject && makeObject(setRef), - [setRef, makeObject] + [setRef, makeObject], ); const children = React.useMemo( () => obj === null ? null : ( ), - [props.name, obj] + [props.name, obj], ); // Update attributes on a per-frame basis. Currently does redundant work, @@ -174,7 +174,7 @@ export function SceneNodeThreeObject(props: { name: string }) { // Clicking logic. const sendClicksThrottled = makeThrottledMessageSender( viewer.websocketRef, - 50 + 50, ); const [hovered, setHovered] = React.useState(false); useCursor(hovered); diff --git a/viser/client/src/SceneTreeState.tsx b/viser/client/src/SceneTreeState.tsx index 6769e8de5..aad276b75 100644 --- a/viser/client/src/SceneTreeState.tsx +++ b/viser/client/src/SceneTreeState.tsx @@ -25,7 +25,7 @@ const makeRoot: MakeObject = (ref) => ( ); @@ -35,12 +35,12 @@ const rootAxesTemplate: MakeObject = (ref) => ( const rootNodeTemplate = new SceneNode( "", - makeRoot + makeRoot, ) as SceneNode; const rootAxesNode = new SceneNode( "/WorldAxes", - rootAxesTemplate + rootAxesTemplate, ) as SceneNode; rootNodeTemplate.children.push("/WorldAxes"); @@ -87,7 +87,7 @@ export function useSceneTreeState() { function findChildrenRecursive(name: string) { removeNames.push(name); state.nodeFromName[name]!.children.forEach( - findChildrenRecursive + findChildrenRecursive, ); } findChildrenRecursive(name); @@ -116,8 +116,8 @@ export function useSceneTreeState() { set((state) => { state.labelVisibleFromName[name] = labelVisibility; }), - })) - ) - ) + })), + ), + ), )[0]; } diff --git a/viser/client/src/SearchParamsUtils.tsx b/viser/client/src/SearchParamsUtils.tsx index 96b017889..dc9f08695 100644 --- a/viser/client/src/SearchParamsUtils.tsx +++ b/viser/client/src/SearchParamsUtils.tsx @@ -14,7 +14,7 @@ function setServerParams(serverParams: string[]) { if ( serverParams.length === 1 && window.location.host.includes( - serverParams[0].replace("ws://", "").replace("/", "") + serverParams[0].replace("ws://", "").replace("/", ""), ) ) serverParams = []; @@ -26,6 +26,6 @@ function setServerParams(serverParams: string[]) { // it. We're going to just not escape the string. :) serverParams.length === 0 ? window.location.href.split("?")[0] - : `?${serverParams.map((s) => `${searchParamKey}=${s}`).join("&")}` + : `?${serverParams.map((s) => `${searchParamKey}=${s}`).join("&")}`, ); } diff --git a/viser/client/src/ThreeAssets.tsx b/viser/client/src/ThreeAssets.tsx index 9066dec06..5e6b5d8b8 100644 --- a/viser/client/src/ThreeAssets.tsx +++ b/viser/client/src/ThreeAssets.tsx @@ -16,7 +16,7 @@ export const CoordinateFrame = React.forwardRef< } >(function CoordinateFrame( { show_axes = true, axes_length = 0.5, axes_radius = 0.0125 }, - ref + ref, ) { return ( @@ -29,7 +29,7 @@ export const CoordinateFrame = React.forwardRef< new THREE.Vector3( axes_radius * 2.5, axes_radius * 2.5, - axes_radius * 2.5 + axes_radius * 2.5, ) } /> @@ -148,7 +148,7 @@ function LineSegmentInstance(props: { const orientation = new THREE.Quaternion().setFromAxisAngle( rotationAxis, - rotationAngle + rotationAngle, ); return ( <> diff --git a/viser/client/src/Titlebar.tsx b/viser/client/src/Titlebar.tsx index 64fd30666..8f73d1ca7 100644 --- a/viser/client/src/Titlebar.tsx +++ b/viser/client/src/Titlebar.tsx @@ -28,7 +28,7 @@ function assertUnreachable(x: never): never { } function getIcon( - icon: ArrayElement>["icon"] + icon: ArrayElement>["icon"], ) { let Icon = null; switch (icon) { @@ -51,7 +51,7 @@ function getIcon( // We inherit props directly from message contents. export function TitlebarButton( - props: ArrayElement> + props: ArrayElement>, ) { const Icon = getIcon(props.icon); return ( @@ -76,7 +76,7 @@ export function TitlebarButton( } export function MobileTitlebarButton( - props: ArrayElement> + props: ArrayElement>, ) { const Icon = getIcon(props.icon); return ( @@ -97,7 +97,7 @@ export function MobileTitlebarButton( export function TitlebarImage( props: NoNull, - theme: MantineTheme + theme: MantineTheme, ) { let imageSource: string; if (props.image_url_dark == null || theme.colorScheme == "light") { @@ -147,6 +147,7 @@ export function Titlebar() { theme.colorScheme == "light" ? theme.colors.gray[4] : theme.colors.dark[4], + zIndex: 299, })} > , - message: Message + message: Message, ) { if (websocketRef.current === null) return; websocketRef.current.send(pack(message)); @@ -14,7 +14,7 @@ export function sendWebsocketMessage( /** Returns a function for sending messages, with automatic throttling. */ export function makeThrottledMessageSender( websocketRef: MutableRefObject, - throttleMilliseconds: number + throttleMilliseconds: number, ) { let readyToSend = true; let stale = false; @@ -42,7 +42,12 @@ export function makeThrottledMessageSender( /** Type guard for threejs textures. Meant to be used with `scene.background`. */ export function isTexture( - background: THREE.Color | THREE.Texture | THREE.CubeTexture | null | undefined + background: + | THREE.Color + | THREE.Texture + | THREE.CubeTexture + | null + | undefined, ): background is THREE.Texture { return ( background !== null && diff --git a/viser/client/src/WebsocketInterface.tsx b/viser/client/src/WebsocketInterface.tsx index b8a0f6883..2137879cb 100644 --- a/viser/client/src/WebsocketInterface.tsx +++ b/viser/client/src/WebsocketInterface.tsx @@ -27,7 +27,7 @@ function threeColorBufferFromUint8Buffer(colors: ArrayBuffer) { return Math.pow((value + 0.055) / 1.055, 2.4); } }), - 3 + 3, ); } @@ -58,7 +58,7 @@ function useMessageHandler() { addSceneNodeMakeParents( new SceneNode(parent_name, (ref) => ( - )) + )), ); } addSceneNode(node); @@ -86,7 +86,7 @@ function useMessageHandler() { axes_length={message.axes_length} axes_radius={message.axes_radius} /> - )) + )), ); return; } @@ -106,18 +106,18 @@ function useMessageHandler() { new Float32Array( message.points.buffer.slice( message.points.byteOffset, - message.points.byteOffset + message.points.byteLength - ) + message.points.byteOffset + message.points.byteLength, + ), ), - 3 - ) + 3, + ), ); geometry.computeBoundingSphere(); // Wrap uint8 buffer for colors. Note that we need to set normalized=true. geometry.setAttribute( "color", - threeColorBufferFromUint8Buffer(message.colors) + threeColorBufferFromUint8Buffer(message.colors), ); addSceneNodeMakeParents( @@ -136,8 +136,8 @@ function useMessageHandler() { // disposal. geometry.dispose(); pointCloudMaterial.dispose(); - } - ) + }, + ), ); return; } @@ -161,16 +161,16 @@ function useMessageHandler() { new Float32Array( message.vertices.buffer.slice( message.vertices.byteOffset, - message.vertices.byteOffset + message.vertices.byteLength - ) + message.vertices.byteOffset + message.vertices.byteLength, + ), ), - 3 - ) + 3, + ), ); if (message.vertex_colors !== null) { geometry.setAttribute( "color", - threeColorBufferFromUint8Buffer(message.vertex_colors) + threeColorBufferFromUint8Buffer(message.vertex_colors), ); } @@ -179,11 +179,11 @@ function useMessageHandler() { new Uint32Array( message.faces.buffer.slice( message.faces.byteOffset, - message.faces.byteOffset + message.faces.byteLength - ) + message.faces.byteOffset + message.faces.byteLength, + ), ), - 1 - ) + 1, + ), ); geometry.computeVertexNormals(); geometry.computeBoundingSphere(); @@ -199,8 +199,8 @@ function useMessageHandler() { // disposal. geometry.dispose(); material.dispose(); - } - ) + }, + ), ); return; } @@ -210,7 +210,7 @@ function useMessageHandler() { message.image_media_type !== null && message.image_base64_data !== null ? new TextureLoader().load( - `data:${message.image_media_type};base64,${message.image_base64_data}` + `data:${message.image_media_type};base64,${message.image_base64_data}`, ) : undefined; @@ -246,8 +246,8 @@ function useMessageHandler() { )} ), - () => texture?.dispose() - ) + () => texture?.dispose(), + ), ); return; } @@ -255,7 +255,7 @@ function useMessageHandler() { const name = message.name; const sendDragMessage = makeThrottledMessageSender( viewer.websocketRef, - 50 + 50, ); addSceneNodeMakeParents( new SceneNode(message.name, (ref) => ( @@ -294,7 +294,7 @@ function useMessageHandler() { }); }} /> - )) + )), ); return; } @@ -303,12 +303,12 @@ function useMessageHandler() { const R_threeworld_world = new THREE.Quaternion(); R_threeworld_world.setFromEuler( - new THREE.Euler(-Math.PI / 2.0, 0.0, 0.0) + new THREE.Euler(-Math.PI / 2.0, 0.0, 0.0), ); const target = new THREE.Vector3( message.look_at[0], message.look_at[1], - message.look_at[2] + message.look_at[2], ); target.applyQuaternion(R_threeworld_world); cameraControls.setTarget(target.x, target.y, target.z); @@ -319,12 +319,12 @@ function useMessageHandler() { const cameraControls = viewer.cameraControlRef.current!; const R_threeworld_world = new THREE.Quaternion(); R_threeworld_world.setFromEuler( - new THREE.Euler(-Math.PI / 2.0, 0.0, 0.0) + new THREE.Euler(-Math.PI / 2.0, 0.0, 0.0), ); const updir = new THREE.Vector3( message.position[0], message.position[1], - message.position[2] + message.position[2], ).applyQuaternion(R_threeworld_world); camera.up.set(updir.x, updir.y, updir.z); @@ -338,7 +338,7 @@ function useMessageHandler() { cameraControls.setPosition( prevPosition.x, prevPosition.y, - prevPosition.z + prevPosition.z, ); return; } @@ -349,18 +349,18 @@ function useMessageHandler() { const position_cmd = new THREE.Vector3( message.position[0], message.position[1], - message.position[2] + message.position[2], ); const R_worldthree_world = new THREE.Quaternion(); R_worldthree_world.setFromEuler( - new THREE.Euler(-Math.PI / 2.0, 0.0, 0.0) + new THREE.Euler(-Math.PI / 2.0, 0.0, 0.0), ); position_cmd.applyQuaternion(R_worldthree_world); cameraControls.setPosition( position_cmd.x, position_cmd.y, - position_cmd.z + position_cmd.z, ); return; } @@ -369,7 +369,7 @@ function useMessageHandler() { // tan(fov / 2.0) = 0.5 * film height / focal length // focal length = 0.5 * film height / tan(fov / 2.0) camera.setFocalLength( - (0.5 * camera.getFilmHeight()) / Math.tan(message.fov / 2.0) + (0.5 * camera.getFilmHeight()) / Math.tan(message.fov / 2.0), ); return; } @@ -404,7 +404,7 @@ function useMessageHandler() { if (isTexture(oldBackground)) oldBackground.dispose(); viewer.useGui.setState({ backgroundAvailable: true }); - } + }, ); return; } @@ -448,7 +448,7 @@ function useMessageHandler() { ); - }) + }), ); return; } @@ -483,10 +483,10 @@ function useMessageHandler() { ); }, - () => texture.dispose() - ) + () => texture.dispose(), + ), ); - } + }, ); return; } diff --git a/viser/client/src/WebsocketMessages.tsx b/viser/client/src/WebsocketMessages.tsx index 2aa9455f9..045522f63 100644 --- a/viser/client/src/WebsocketMessages.tsx +++ b/viser/client/src/WebsocketMessages.tsx @@ -320,7 +320,7 @@ export interface ThemeConfigurationMessage { href: string | null; } | null; } | null; - fixed_sidebar: boolean; + control_layout: "floating" | "collapsible" | "fixed"; dark_mode: boolean; } diff --git a/viser/client/src/index.tsx b/viser/client/src/index.tsx index 4c4db96ad..e8c71fe91 100644 --- a/viser/client/src/index.tsx +++ b/viser/client/src/index.tsx @@ -2,5 +2,5 @@ import ReactDOM from "react-dom/client"; import { Root } from "./App"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + , );