From c246e7e23d46d5f0600e084b4719f93d432944f3 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 12 Nov 2025 15:35:16 -0500 Subject: [PATCH 1/8] added customization to settings panel --- .../react-viewers/helpers/reactUtils.ts | 32 +- .../settings/settingsInputBox.tsx | 41 ++ .../react-viewers/settings/settingsItem.ts | 38 ++ .../react-viewers/settings/settingsKeys.ts | 42 ++ .../react-viewers/settings/settingsPanel.tsx | 387 ++++++++++-------- .../react-viewers/settings/settingsState.ts | 7 +- .../settings/settingsSubtitle.tsx | 5 + .../react-viewers/settings/settingsToggle.tsx | 36 ++ .../react-viewers/state/sharedIsolation.ts | 4 +- src/vim-web/react-viewers/webgl/viewer.tsx | 7 +- src/vim-web/react-viewers/webgl/viewerRef.ts | 7 + 11 files changed, 415 insertions(+), 191 deletions(-) create mode 100644 src/vim-web/react-viewers/settings/settingsInputBox.tsx create mode 100644 src/vim-web/react-viewers/settings/settingsItem.ts create mode 100644 src/vim-web/react-viewers/settings/settingsKeys.ts create mode 100644 src/vim-web/react-viewers/settings/settingsSubtitle.tsx create mode 100644 src/vim-web/react-viewers/settings/settingsToggle.tsx diff --git a/src/vim-web/react-viewers/helpers/reactUtils.ts b/src/vim-web/react-viewers/helpers/reactUtils.ts index 0796e856..24c605ff 100644 --- a/src/vim-web/react-viewers/helpers/reactUtils.ts +++ b/src/vim-web/react-viewers/helpers/reactUtils.ts @@ -89,19 +89,27 @@ export function useRefresher() : StateRefresher{ * The reference provides access to the state, along with event dispatching, validation, and confirmation logic. * * @param initialValue - The initial state value. + * @param isLazy - Whether to treat the initialValue as a lazy initializer function. * @returns An object implementing StateRef along with additional helper hooks. */ -export function useStateRef(initialValue: T | (() => T)) { - const [value, setValue] = useState(initialValue); - - // https://react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents - const ref = useRef(undefined); - if(ref.current === undefined) { - if (typeof initialValue === "function") { - ref.current = (initialValue as () => T)(); - } else { - ref.current = initialValue; + +export function useStateRef(initialValue: T | (() => T), isLazy = false) { + const getInitialValue = (): T => { + if (isLazy && typeof initialValue === 'function') { + return (initialValue as () => T)(); } + return initialValue as T; + }; + + // Box the state value to prevent React from ever calling it + type Box = { current: T }; + const [box, setBox] = useState>(() => ({ + current: getInitialValue() + })); + + const ref = useRef(undefined!); + if (ref.current === undefined) { + ref.current = getInitialValue(); } const event = useRef(new SimpleEventDispatcher()); @@ -119,7 +127,7 @@ export function useStateRef(initialValue: T | (() => T)) { if (finalValue === ref.current) return; ref.current = finalValue; - setValue(finalValue); + setBox({ current: finalValue }); event.current.dispatch(finalValue); }; @@ -164,7 +172,7 @@ export function useStateRef(initialValue: T | (() => T)) { * @returns The memoized value. */ useMemo(on: (value: T) => TOut, deps?: any[]) { - return useMemo(() => on(value), [...(deps || []), value]); + return useMemo(() => on(box.current), [...(deps || []), box.current]); }, /** diff --git a/src/vim-web/react-viewers/settings/settingsInputBox.tsx b/src/vim-web/react-viewers/settings/settingsInputBox.tsx new file mode 100644 index 00000000..908c9626 --- /dev/null +++ b/src/vim-web/react-viewers/settings/settingsInputBox.tsx @@ -0,0 +1,41 @@ +import React, { useEffect } from 'react' +import { SettingsBox } from './settingsItem' +import * as Core from '../../core-viewers' +import { SettingsState } from './settingsState' + +export function renderSettingsInputBox(viewer: Core.Webgl.Viewer, settings: SettingsState, item: SettingsBox) { + const ref = React.useRef(null) + + useEffect(() => { + ref.current.value = item.getter(settings.value)?.toString() + }, []) + + const update = (event: React.FocusEvent) => { + const str = event.target.value + const n = Number.parseFloat(str) + if (Number.isNaN(n)) { + event.target.value = item.getter(settings.value).toString() + } else { + const value = item.transform(n) + event.target.value = value.toString() + settings.update((s) => item.setter(s, value)) + } + } + + return ( +
+ + + +
+ ) +} \ No newline at end of file diff --git a/src/vim-web/react-viewers/settings/settingsItem.ts b/src/vim-web/react-viewers/settings/settingsItem.ts new file mode 100644 index 00000000..f8acb211 --- /dev/null +++ b/src/vim-web/react-viewers/settings/settingsItem.ts @@ -0,0 +1,38 @@ +import { Settings } from './settings' +import { UserBoolean } from './userBoolean' + + +export type SettingsCustomizer = (items: SettingsItem[]) => SettingsItem[] + +export type SettingsItem = SettingsSubtitle | SettingsToggle | SettingsBox | SettingsElement + +export type BaseSettingsItem = { + type: string + key: string +} + +export type SettingsSubtitle = BaseSettingsItem & { + type: 'subtitle' + title: string +} + +export type SettingsToggle = BaseSettingsItem & { + type: 'toggle' + label: string + getter: (settings: Settings) => UserBoolean + setter: (settings: Settings, b: boolean) => void +} + +export type SettingsBox = BaseSettingsItem & { + type: 'box' + label: string + info: string + transform: (value: number) => number + getter: (settings: Settings) => number + setter: (settings: Settings, b: number) => void +} + +export type SettingsElement = BaseSettingsItem & { + type: 'element' + element: JSX.Element +} \ No newline at end of file diff --git a/src/vim-web/react-viewers/settings/settingsKeys.ts b/src/vim-web/react-viewers/settings/settingsKeys.ts new file mode 100644 index 00000000..131d068d --- /dev/null +++ b/src/vim-web/react-viewers/settings/settingsKeys.ts @@ -0,0 +1,42 @@ + +export class SettingsPanelKeys { + // === Inputs === + static InputsSubtitle = 'inputs' + static InputsScrollSpeedBox = 'scrollSpeed' + + // === Panels === + static PanelsSubtitle = 'panels' + static PanelsShowLogoToggle = 'logo' + static PanelsShowBimTreeToggle = 'bimTree' + static PanelsShowBimInfoToggle = 'bimInfo' + static PanelsShowAxesPanelToggle = 'axesPanel' + static PanelsShowPerformancePanelToggle = 'performance' + + // === Axes === + static AxesSubtitle = 'axes' + static AxesShowOrthographicButtonToggle = 'orthographic' + static AxesShowResetCameraButtonToggle = 'resetCamera' + + // === Control Bar === + static ControlBarSubtitle = 'controlBar' + static ControlBarShowControlBarToggle = 'controlBarVisible' + + // --- Control Bar - Cursors --- + static ControlBarCursorsSubtitle = 'controlBarCursors' + static ControlBarCursorsShowOrbitButtonToggle = 'orbit' + static ControlBarCursorsShowLookAroundButtonToggle = 'lookAround' + static ControlBarCursorsShowPanButtonToggle = 'pan' + static ControlBarCursorsShowZoomButtonToggle = 'zoom' + static ControlBarCursorsShowZoomWindowButtonToggle = 'zoomWindow' + + // --- Control Bar - Tools --- + static ControlBarToolsSubtitle = 'controlBarTools' + static ControlBarToolsShowMeasuringModeButtonToggle = 'measuringMode' + + // --- Control Bar - Settings --- + static ControlBarSettingsSubtitle = 'controlBarSettings' + static ControlBarSettingsShowProjectInspectorButtonToggle = 'projectInspector' + static ControlBarSettingsShowSettingsButtonToggle = 'settingsButton' + static ControlBarSettingsShowHelpButtonToggle = 'help' + static ControlBarSettingsShowMaximiseButtonToggle = 'maximise' +} diff --git a/src/vim-web/react-viewers/settings/settingsPanel.tsx b/src/vim-web/react-viewers/settings/settingsPanel.tsx index c6143317..ba83a715 100644 --- a/src/vim-web/react-viewers/settings/settingsPanel.tsx +++ b/src/vim-web/react-viewers/settings/settingsPanel.tsx @@ -2,14 +2,15 @@ * @module viw-webgl-react */ -import React, { useEffect } from 'react' +import React from 'react' import * as Core from '../../core-viewers' -import { Settings } from './settings' -import { UserBoolean } from './userBoolean' import { SettingsState } from './settingsState' import * as THREE from 'three' - -// TODO Use generic panels for all settings +import { SettingsPanelKeys } from './settingsKeys' +import { renderSettingsInputBox } from './settingsInputBox' +import { renderSettingsToggle } from './settingsToggle' +import { SettingsItem } from './settingsItem' +import { renderSettingsSubtitle } from './settingsSubtitle' /** * JSX Component to interact with settings. @@ -18,189 +19,225 @@ import * as THREE from 'three' * @param visible will return null if this is false. * @returns */ -export function SettingsPanel (props: { +export function SettingsPanel(props: { viewer: Core.Webgl.Viewer settings: SettingsState visible: boolean }) { if (!props.visible) return null - const toggleElement = (label: string, state: boolean, action: () => void) => { - return ( - - ) - } - - const settingsToggle = ( - label: string, - getter: (settings: Settings) => UserBoolean, - setter: (settings: Settings, b: boolean) => void - ) => { - const value = getter(props.settings.value) - if (value === 'AlwaysTrue' || value === 'AlwaysFalse') { - return null - } - return toggleElement(label, value, () => { - const value = getter(props.settings.value) - props.settings.update((s) => setter(s, !value)) - }) - } - const settingsBox = (label: string, - info: string, - transform : (value:number) => number, - getter: (settings: Settings) => number, - setter: (settings: Settings, b: number) => void) => { - const ref = React.useRef(null) - - useEffect(() => { - ref.current.value = props.viewer.inputs.scrollSpeed.toFixed(2) - },[]) - - const value = getter(props.settings.value).toString() - const update = (event: React.FocusEvent) => { - const str = event.target.value - const n = Number.parseFloat(str) - if (Number.isNaN(n)) { - event.target.value = getter(props.settings.value).toString() - } else { - const value = transform(n) - event.target.value = value.toString() - props.settings.update(s => setter(s, value)) - } - } - - return
- - update(e)}/> - -
- } - - function settingsSubtitle (title: string) { + function renderItem( + viewer: Core.Webgl.Viewer, + settings: SettingsState, + item: SettingsItem, + ): JSX.Element | null { return ( -

{title}

+ + {(() => { + switch (item.type) { + case 'subtitle': return renderSettingsSubtitle(item) + case 'toggle': return renderSettingsToggle(viewer, settings, item) + case 'box': return renderSettingsInputBox(viewer, settings, item) + case 'element': return item.element + default: return null + } + })()} + ) } + var customizer = props.settings.customizer.get() + var content = GetSettingsContent(props.viewer) + content = customizer ? customizer(content) : content + return ( -
-

Settings

+
+

Settings

- {settingsSubtitle('Inputs')} - {settingsBox( - 'Scroll Speed', - '[0.1,10]', - n => THREE.MathUtils.clamp(n, 0.1, 10), - s => props.viewer.inputs.scrollSpeed, - (s, v) => { props.viewer.inputs.scrollSpeed = v } - )} - {settingsSubtitle('Panels')} - {settingsToggle( - 'Show Logo', - (settings) => settings.ui.logo, - (settings, value) => (settings.ui.logo = value) - )} - {settingsToggle( - 'Show Bim Tree', - (settings) => settings.ui.bimTreePanel, - (settings, value) => (settings.ui.bimTreePanel = value) - )} - {settingsToggle( - 'Show Bim Info', - (settings) => settings.ui.bimInfoPanel, - (settings, value) => (settings.ui.bimInfoPanel = value) - )} - {settingsToggle( - 'Show Axes Panel', - (settings) => settings.ui.axesPanel, - (settings, value) => (settings.ui.axesPanel = value) - )} - {settingsToggle( - 'Show Performance Panel', - (settings) => settings.ui.performance, - (settings, value) => (settings.ui.performance = value) - )} - - {settingsSubtitle('Axes')} - {settingsToggle( - 'Show Orthographic Button', - (settings) => settings.ui.orthographic, - (settings, value) => (settings.ui.orthographic = value) - )} - {settingsToggle( - 'Show Reset Camera Button', - (settings) => settings.ui.resetCamera, - (settings, value) => (settings.ui.resetCamera = value) - )} - {settingsSubtitle('Control Bar')} - {settingsToggle( - 'Show Control Bar', - (settings) => settings.ui.controlBar, - (settings, value) => (settings.ui.controlBar = value) - )} - {settingsSubtitle('Control Bar - Cursors')} - {settingsToggle( - 'Show Orbit Button', - (settings) => settings.ui.orbit, - (settings, value) => (settings.ui.orbit = value) - )} - {settingsToggle( - 'Show Look Around Button', - (settings) => settings.ui.lookAround, - (settings, value) => (settings.ui.lookAround = value) - )} - {settingsToggle( - 'Show Pan Button', - (settings) => settings.ui.pan, - (settings, value) => (settings.ui.pan = value) - )} - {settingsToggle( - 'Show Zoom Button', - (settings) => settings.ui.zoom, - (settings, value) => (settings.ui.zoom = value) - )} - {settingsToggle( - 'Show Zoom Window Button', - (settings) => settings.ui.zoomWindow, - (settings, value) => (settings.ui.zoomWindow = value) - )} - {settingsSubtitle('Control Bar - Tools')} - {settingsToggle( - 'Show Measuring Mode Button', - (settings) => settings.ui.measuringMode, - (settings, value) => (settings.ui.measuringMode = value) - )} - {settingsSubtitle('Control Bar - Settings')} - {settingsToggle( - 'Show Project Inspector Button', - (settings) => settings.ui.projectInspector, - (settings, value) => (settings.ui.projectInspector = value) - )} - {settingsToggle( - 'Show Settings Button', - (settings) => settings.ui.settings, - (settings, value) => (settings.ui.settings = value) - )} - {settingsToggle( - 'Show Help Button', - (settings) => settings.ui.help, - (settings, value) => (settings.ui.help = value) - )} - {settingsToggle( - 'Show Maximise Button', - (settings) => settings.ui.maximise, - (settings, value) => (settings.ui.maximise = value) - )} + {content.map(item => renderItem(props.viewer, props.settings, item))}
) } + +function GetSettingsContent(viewer: Core.Webgl.Viewer): SettingsItem[] { + return [ + { + type: 'subtitle', + key: SettingsPanelKeys.InputsSubtitle, + title: 'Inputs' + }, + { + type: 'box', + key: SettingsPanelKeys.InputsScrollSpeedBox, + label: 'Scroll Speed', + info: '[0.1,10]', + transform: (n) => THREE.MathUtils.clamp(n, 0.1, 10), + getter: (_s) => viewer.inputs.scrollSpeed, + setter: (_s, v) => { + viewer.inputs.scrollSpeed = v + } + }, + { + type: 'subtitle', + key: SettingsPanelKeys.PanelsSubtitle, + title: 'Panels' + }, + { + type: 'toggle', + key: SettingsPanelKeys.PanelsShowLogoToggle, + label: 'Show Logo', + getter: (s) => s.ui.logo, + setter: (s, v) => (s.ui.logo = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.PanelsShowBimTreeToggle, + label: 'Show Bim Tree', + getter: (s) => s.ui.bimTreePanel, + setter: (s, v) => (s.ui.bimTreePanel = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.PanelsShowBimInfoToggle, + label: 'Show Bim Info', + getter: (s) => s.ui.bimInfoPanel, + setter: (s, v) => (s.ui.bimInfoPanel = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.PanelsShowAxesPanelToggle, + label: 'Show Axes Panel', + getter: (s) => s.ui.axesPanel, + setter: (s, v) => (s.ui.axesPanel = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.PanelsShowPerformancePanelToggle, + label: 'Show Performance Panel', + getter: (s) => s.ui.performance, + setter: (s, v) => (s.ui.performance = v) + }, + + { + type: 'subtitle', + key: SettingsPanelKeys.AxesSubtitle, + title: 'Axes' + }, + { + type: 'toggle', + key: SettingsPanelKeys.AxesShowOrthographicButtonToggle, + label: 'Show Orthographic Button', + getter: (s) => s.ui.orthographic, + setter: (s, v) => (s.ui.orthographic = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.AxesShowResetCameraButtonToggle, + label: 'Show Reset Camera Button', + getter: (s) => s.ui.resetCamera, + setter: (s, v) => (s.ui.resetCamera = v) + }, + + { + type: 'subtitle', + key: SettingsPanelKeys.ControlBarSubtitle, + title: 'Control Bar' + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarShowControlBarToggle, + label: 'Show Control Bar', + getter: (s) => s.ui.controlBar, + setter: (s, v) => (s.ui.controlBar = v) + }, + + { + type: 'subtitle', + key: SettingsPanelKeys.ControlBarCursorsSubtitle, + title: 'Control Bar - Cursors' + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarCursorsShowOrbitButtonToggle, + label: 'Show Orbit Button', + getter: (s) => s.ui.orbit, + setter: (s, v) => (s.ui.orbit = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarCursorsShowLookAroundButtonToggle, + label: 'Show Look Around Button', + getter: (s) => s.ui.lookAround, + setter: (s, v) => (s.ui.lookAround = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarCursorsShowPanButtonToggle, + label: 'Show Pan Button', + getter: (s) => s.ui.pan, + setter: (s, v) => (s.ui.pan = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarCursorsShowZoomButtonToggle, + label: 'Show Zoom Button', + getter: (s) => s.ui.zoom, + setter: (s, v) => (s.ui.zoom = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarCursorsShowZoomWindowButtonToggle, + label: 'Show Zoom Window Button', + getter: (s) => s.ui.zoomWindow, + setter: (s, v) => (s.ui.zoomWindow = v) + }, + + { + type: 'subtitle', + key: SettingsPanelKeys.ControlBarToolsSubtitle, + title: 'Control Bar - Tools' + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarToolsShowMeasuringModeButtonToggle, + label: 'Show Measuring Mode Button', + getter: (s) => s.ui.measuringMode, + setter: (s, v) => (s.ui.measuringMode = v) + }, + + { + type: 'subtitle', + key: SettingsPanelKeys.ControlBarSettingsSubtitle, + title: 'Control Bar - Settings' + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarSettingsShowProjectInspectorButtonToggle, + label: 'Show Project Inspector Button', + getter: (s) => s.ui.projectInspector, + setter: (s, v) => (s.ui.projectInspector = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarSettingsShowSettingsButtonToggle, + label: 'Show Settings Button', + getter: (s) => s.ui.settings, + setter: (s, v) => (s.ui.settings = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarSettingsShowHelpButtonToggle, + label: 'Show Help Button', + getter: (s) => s.ui.help, + setter: (s, v) => (s.ui.help = v) + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarSettingsShowMaximiseButtonToggle, + label: 'Show Maximise Button', + getter: (s) => s.ui.maximise, + setter: (s, v) => (s.ui.maximise = v) + } + ] +} \ No newline at end of file diff --git a/src/vim-web/react-viewers/settings/settingsState.ts b/src/vim-web/react-viewers/settings/settingsState.ts index 68bb9551..a7b4a54a 100644 --- a/src/vim-web/react-viewers/settings/settingsState.ts +++ b/src/vim-web/react-viewers/settings/settingsState.ts @@ -7,11 +7,14 @@ import * as Core from '../../core-viewers' import { Settings, PartialSettings, createSettings } from './settings' import { isTrue } from './userBoolean' import { saveSettingsToLocal } from './settingsStorage' +import { ArgFuncRef, StateRef, useArgFuncRef, useFuncRef, useStateRef } from '../helpers/reactUtils' +import { SettingsCustomizer, SettingsItem } from './settingsItem' export type SettingsState = { value: Settings update: (updater: (s: Settings) => void) => void register: (action: (s: Settings) => void) => void + customizer : StateRef } /** @@ -24,6 +27,7 @@ export function useSettings ( const merged = createSettings(value) const [settings, setSettings] = useState(merged) const onUpdate = useRef<(s: Settings) => void>() + const customizer = useStateRef(settings => settings) const update = function (updater: (s: Settings) => void) { const next = { ...settings } // Shallow copy @@ -47,7 +51,8 @@ export function useSettings ( () => ({ value: settings, update, - register: (v) => (onUpdate.current = v) + register: (v) => (onUpdate.current = v), + customizer }), [settings] ) diff --git a/src/vim-web/react-viewers/settings/settingsSubtitle.tsx b/src/vim-web/react-viewers/settings/settingsSubtitle.tsx new file mode 100644 index 00000000..6e19304b --- /dev/null +++ b/src/vim-web/react-viewers/settings/settingsSubtitle.tsx @@ -0,0 +1,5 @@ + import { SettingsSubtitle } from './settingsItem' + + export function renderSettingsSubtitle(item: SettingsSubtitle) { + return

{item.title}

+ } \ No newline at end of file diff --git a/src/vim-web/react-viewers/settings/settingsToggle.tsx b/src/vim-web/react-viewers/settings/settingsToggle.tsx new file mode 100644 index 00000000..a7e5b968 --- /dev/null +++ b/src/vim-web/react-viewers/settings/settingsToggle.tsx @@ -0,0 +1,36 @@ +import { SettingsToggle } from './settingsItem' +import * as Core from '../../core-viewers' +import { SettingsState } from './settingsState' + +/** + * Renders a toggle (checkbox) UI element for a given SettingsToggle item. + * @param viewer The WebGL viewer instance (for future consistency). + * @param settings The current settings state object. + * @param item The SettingsToggle configuration. + * @returns JSX.Element | null + */ +export function renderSettingsToggle( + viewer: Core.Webgl.Viewer, + settings: SettingsState, + item: SettingsToggle +): JSX.Element | null { + const value = item.getter(settings.value) + if (value === 'AlwaysTrue' || value === 'AlwaysFalse') return null + + const handleChange = () => { + const current = item.getter(settings.value) + settings.update((s) => item.setter(s, !current)) + } + + return ( + + ) +} diff --git a/src/vim-web/react-viewers/state/sharedIsolation.ts b/src/vim-web/react-viewers/state/sharedIsolation.ts index a8551c29..2f602e95 100644 --- a/src/vim-web/react-viewers/state/sharedIsolation.ts +++ b/src/vim-web/react-viewers/state/sharedIsolation.ts @@ -48,12 +48,12 @@ export interface IsolationAdapter{ export function useSharedIsolation(adapter : IsolationAdapter){ const _adapter = useRef(adapter); - const visibility = useStateRef(() => adapter.computeVisibility()); + const visibility = useStateRef(() => adapter.computeVisibility(), true); const autoIsolate = useStateRef(false); const showPanel = useStateRef(false); const showRooms = useStateRef(false); const showGhost = useStateRef(false); - const ghostOpacity = useStateRef(() => adapter.getGhostOpacity()); + const ghostOpacity = useStateRef(() => adapter.getGhostOpacity(), true); const onAutoIsolate = useFuncRef(() => { if(adapter.hasSelection()){ diff --git a/src/vim-web/react-viewers/webgl/viewer.tsx b/src/vim-web/react-viewers/webgl/viewer.tsx index bc1d9a75..e4ccae0c 100644 --- a/src/vim-web/react-viewers/webgl/viewer.tsx +++ b/src/vim-web/react-viewers/webgl/viewer.tsx @@ -44,6 +44,7 @@ import { IsolationPanel } from '../panels/isolationPanel' import { useWebglIsolation } from './isolation' import { GenericPanelHandle } from '../generic' import { ControllablePromise } from '../../utils' +import { SettingsCustomizer } from '../settings/settingsItem' /** * Creates a UI container along with a VIM.Viewer and its associated React viewer. @@ -176,7 +177,11 @@ export function Viewer (props: { loader: loader.current, isolation: isolationRef, camera, - settings, + settings: { + update : settings.update, + register : settings.register, + customize : (c: SettingsCustomizer) => settings.customizer.set(c) + }, get isolationPanel(){ return isolationPanelHandle.current }, diff --git a/src/vim-web/react-viewers/webgl/viewerRef.ts b/src/vim-web/react-viewers/webgl/viewerRef.ts index d083707c..5075cb29 100644 --- a/src/vim-web/react-viewers/webgl/viewerRef.ts +++ b/src/vim-web/react-viewers/webgl/viewerRef.ts @@ -14,6 +14,7 @@ import { ModalHandle } from '../panels/modal' import { SectionBoxRef } from '../state/sectionBoxState' import { IsolationRef } from '../state/sharedIsolation' import { GenericPanelHandle } from '../generic' +import { SettingsItem } from '../settings/settingsItem' /** * Settings API managing settings applied to the viewer. */ @@ -33,6 +34,12 @@ export type SettingsRef = { */ register : (callback: (settings: Settings) => void) => void + /** + * Customizes the settings panel by providing a customizer function. + * @param customizer A function that modifies the settings items. + */ + customize : (customizer: (items: SettingsItem[]) => SettingsItem[]) => void + } From 83e38c5d59aa251e3dbdfccf8e2e601c3c2d13cd Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 14 Nov 2025 15:13:02 -0500 Subject: [PATCH 2/8] settings --- src/main.tsx | 82 +++- src/vim-web/core-viewers/ultra/viewport.ts | 11 + .../viewer/gizmos/markers/gizmoMarker.ts | 4 + .../webgl/viewer/rendering/renderer.ts | 10 +- .../core-viewers/webgl/viewer/viewport.ts | 2 +- .../react-viewers/errors/errorStyle.tsx | 9 +- .../react-viewers/panels/isolationPanel.tsx | 27 +- .../react-viewers/panels/sidePanel.tsx | 11 +- .../react-viewers/settings/settings.ts | 131 +++-- .../settings/settingsInputBox.tsx | 7 +- .../react-viewers/settings/settingsItem.ts | 18 +- .../react-viewers/settings/settingsKeys.ts | 25 + .../react-viewers/settings/settingsPanel.tsx | 450 ++++++++++++++---- .../react-viewers/settings/settingsState.ts | 35 +- .../react-viewers/settings/settingsStorage.ts | 14 +- .../react-viewers/settings/settingsToggle.tsx | 6 +- .../react-viewers/state/controlBarState.tsx | 136 ++++-- .../react-viewers/state/sharedIsolation.ts | 9 +- src/vim-web/react-viewers/style.css | 9 +- src/vim-web/react-viewers/ultra/controlBar.ts | 17 +- .../ultra/errors/fileLoadingError.tsx | 2 +- .../ultra/errors/fileOpeningError.tsx | 2 +- .../ultra/errors/serverCompatibilityError.tsx | 2 +- .../ultra/errors/serverConnectionError.tsx | 2 +- .../errors/serverFileDownloadingError.tsx | 2 +- .../ultra/errors/serverStreamError.tsx | 2 +- src/vim-web/react-viewers/ultra/isolation.ts | 3 + src/vim-web/react-viewers/ultra/settings.ts | 0 src/vim-web/react-viewers/ultra/viewer.tsx | 45 +- src/vim-web/react-viewers/urls.ts | 1 - src/vim-web/react-viewers/webgl/isolation.ts | 81 ++-- src/vim-web/react-viewers/webgl/viewer.tsx | 18 +- 32 files changed, 871 insertions(+), 302 deletions(-) create mode 100644 src/vim-web/react-viewers/ultra/settings.ts diff --git a/src/main.tsx b/src/main.tsx index 11310a6d..5455fae7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,9 @@ import React, { MutableRefObject, useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; import * as VIM from './vim-web' +import * as THREE from 'three' +import { Icons } from "./vim-web/react-viewers"; +import { useStateRef } from "./vim-web/react-viewers/helpers/reactUtils"; type ViewerRef = VIM.React.Webgl.ViewerRef | VIM.React.Ultra.ViewerRef @@ -19,20 +22,57 @@ console.log("Root container found", container); // Render your App root.render( // - + // ); function App() { - const div = useRef(null) const viewerRef = useRef() + + var customToggle = useStateRef(false) + var customValue = useStateRef(10) + useEffect(() => { - if(window.location.pathname.includes('ultra')){ - createUltra(viewerRef, div.current!) + if (window.location.pathname.includes('ultra')) { + createUltra(viewerRef, div.current!) } - else{ - createWebgl(viewerRef, div.current!) + else { + createWebgl(viewerRef, div.current!).then((viewer) => { + viewer.settings.customize((s) => [ + { + type: 'subtitle', + key: 'custom-subtitle', + title: 'Custom Section' + }, + { + type: 'toggle', + key: 'custom-toggle', + label: 'Custom Toggle', + getter: (s) => customToggle.get(), + setter: (s, v) => (customToggle.set(!v)) + }, + { + type: 'box', + key: 'custom-box', + label: 'Custom Box', + info: 'closest even number', + transform: (n) => Math.round(n / 2) * 2, + getter: (s) => customValue.get(), + setter: (s, v) => (customValue.set(v)) + }, + { + type: 'element', + key: 'custom-element', + // example custom text + element: ( +
+ This is a custom element added to the settings panel. +
) + }, + ...s // keep all exising settings + ]) + }) } return () => { @@ -41,35 +81,46 @@ function App() { }, []) return ( -
+
) } -async function createWebgl (viewerRef: MutableRefObject, div: HTMLDivElement) { +async function createWebgl(viewerRef: MutableRefObject, div: HTMLDivElement) { const viewer = await VIM.React.Webgl.createViewer(div) viewerRef.current = viewer globalThis.viewer = viewer // for testing in browser console const url = getPathFromUrl() ?? 'https://storage.cdn.vimaec.com/samples/residence.v1.2.75.vim' const request = viewer.loader.request( - { url }, + { url }, ) + const result = await request.getResult() if (result.isSuccess()) { viewer.loader.add(result.result) viewer.camera.frameScene.call() } + + return viewer } -async function createUltra (viewerRef: MutableRefObject, div: HTMLDivElement) { - const viewer = await VIM.React.Ultra.createViewer(div) +async function createUltra(viewerRef: MutableRefObject, div: HTMLDivElement) { + const viewer = await VIM.React.Ultra.createViewer(div, + {ui:{ + sectioningEnable: true, + + }} + ) + viewer.camera.autoCamera.set(true) // Enable auto camera framing + viewer.isolation.autoIsolate.set(true) // Enable auto isolation + await viewer.core.connect() viewerRef.current = viewer globalThis.viewer = viewer // for testing in browser console const url = getPathFromUrl() ?? 'https://storage.cdn.vimaec.com/samples/residence.v1.2.75.vim' const request = viewer.load( - { url }, + { url }, ) const result = await request.getResult() if (result.isSuccess) { @@ -77,10 +128,7 @@ async function createUltra (viewerRef: MutableRefObject, div: HTMLDiv } } - - -function getPathFromUrl () { +function getPathFromUrl() { const params = new URLSearchParams(window.location.search) return params.get('vim') ?? undefined -} - +} \ No newline at end of file diff --git a/src/vim-web/core-viewers/ultra/viewport.ts b/src/vim-web/core-viewers/ultra/viewport.ts index 93cd4266..275e5501 100644 --- a/src/vim-web/core-viewers/ultra/viewport.ts +++ b/src/vim-web/core-viewers/ultra/viewport.ts @@ -8,8 +8,12 @@ import { RpcSafeClient } from "./rpcSafeClient"; export interface IViewport { /** The HTML canvas element used for rendering */ canvas: HTMLCanvasElement + /** Updates the aspect ratio of the viewport on the server */ update(): void + + /** Resizes the viewport to match its parent's dimensions */ + resizeToParent(): void } /** @@ -57,6 +61,13 @@ export class Viewport { } } + /** + * Resizes the viewport to match its parent's dimensions + */ + resizeToParent() { + this.update() + } + /** * Cleans up resources by removing resize observer and clearing timeouts */ diff --git a/src/vim-web/core-viewers/webgl/viewer/gizmos/markers/gizmoMarker.ts b/src/vim-web/core-viewers/webgl/viewer/gizmos/markers/gizmoMarker.ts index 009d9f76..43d64b89 100644 --- a/src/vim-web/core-viewers/webgl/viewer/gizmos/markers/gizmoMarker.ts +++ b/src/vim-web/core-viewers/webgl/viewer/gizmos/markers/gizmoMarker.ts @@ -43,6 +43,10 @@ export class Marker implements IVimElement { return this._submesh.index } + get isRoom(): boolean { + return false + } + private _outlineAttribute: WebglAttribute private _visibleAttribute: WebglAttribute private _coloredAttribute: WebglAttribute diff --git a/src/vim-web/core-viewers/webgl/viewer/rendering/renderer.ts b/src/vim-web/core-viewers/webgl/viewer/rendering/renderer.ts index 7a529686..edb2d77c 100644 --- a/src/vim-web/core-viewers/webgl/viewer/rendering/renderer.ts +++ b/src/vim-web/core-viewers/webgl/viewer/rendering/renderer.ts @@ -146,14 +146,22 @@ export class Renderer implements IRenderer { this.needsUpdate = true } + /** + * Sets the material used to render models. If set to undefined, the default model or mesh material is used. + */ get modelMaterial () { return this._scene.modelMaterial } set modelMaterial (material: ModelMaterial) { - this._scene.modelMaterial = material + this._scene.modelMaterial = material ?? this.defaultModelMaterial } + /** + * The material that will be used when setting model material to undefined. + */ + defaultModelMaterial: ModelMaterial + /** * Signal dispatched at the end of each frame if the scene was updated, such as visibility changes. */ diff --git a/src/vim-web/core-viewers/webgl/viewer/viewport.ts b/src/vim-web/core-viewers/webgl/viewer/viewport.ts index 0bea9fea..84191812 100644 --- a/src/vim-web/core-viewers/webgl/viewer/viewport.ts +++ b/src/vim-web/core-viewers/webgl/viewer/viewport.ts @@ -150,7 +150,7 @@ export class Viewport { /** * Resizes the canvas and updates the camera to match new parent dimensions. */ - ResizeToParent () { + resizeToParent () { this._onResize.dispatch() } diff --git a/src/vim-web/react-viewers/errors/errorStyle.tsx b/src/vim-web/react-viewers/errors/errorStyle.tsx index 9db7f117..8ac9620f 100644 --- a/src/vim-web/react-viewers/errors/errorStyle.tsx +++ b/src/vim-web/react-viewers/errors/errorStyle.tsx @@ -5,13 +5,8 @@ export const vcLink = `${vcColorLink} vc-underline` export const vcLabel = 'vc-text-[#3F444F]' export const vcRoboto = 'vc-font-[\'Roboto\',sans-serif]' -export function footer (url: string) { - return ( -

- More troubleshooting tips can be found{' '} - {link(url, 'here')} -

- ) +export function footer () { + return <> } export function mainText (text: JSX.Element) { diff --git a/src/vim-web/react-viewers/panels/isolationPanel.tsx b/src/vim-web/react-viewers/panels/isolationPanel.tsx index c61b46ef..b002023f 100644 --- a/src/vim-web/react-viewers/panels/isolationPanel.tsx +++ b/src/vim-web/react-viewers/panels/isolationPanel.tsx @@ -5,9 +5,10 @@ import { GenericPanel, GenericPanelHandle } from "../generic/genericPanel"; export const Ids = { showGhost: "isolationPanel.showGhost", ghostOpacity: "isolationPanel.ghostOpacity", + transparency: "isolationPanel.transparency", } -export const IsolationPanel = forwardRef( +export const IsolationPanel = forwardRef( (props, ref) => { return ( props.state.showGhost.get(), min: 0, max: 1, - step: 0.05 }, + step: 0.05 + }, + { + type: "bool", + visible: () => props.transparency, + id: Ids.transparency, + label: "Transparency", + state: props.state.transparency + }, ]} /> ); diff --git a/src/vim-web/react-viewers/panels/sidePanel.tsx b/src/vim-web/react-viewers/panels/sidePanel.tsx index d882d4d9..e713cc40 100644 --- a/src/vim-web/react-viewers/panels/sidePanel.tsx +++ b/src/vim-web/react-viewers/panels/sidePanel.tsx @@ -22,7 +22,7 @@ export const SidePanelMemo = React.memo(SidePanel) export function SidePanel (props: { container: Container side: SideState - viewer: Core.Webgl.Viewer + viewer: Core.Webgl.Viewer | Core.Ultra.Viewer content: () => JSX.Element }) { const resizeTimeOut = useRef() @@ -36,7 +36,7 @@ export function SidePanel (props: { props.container.gfx.style.left = '0px' } - props.viewer.viewport.ResizeToParent() + props.viewer.viewport.resizeToParent() } const getMaxSize = () => { @@ -104,9 +104,10 @@ export function SidePanel (props: { style={{ position: 'absolute' }} - className={`vim-side-panel vc-top-0 vc-left-0 vc-z-20 vc-bg-gray-lightest vc-text-gray-darker ${ - props.side.getContent() !== 'none' ? '' : 'vc-hidden' - }`} + className={`vim-side-panel vc-top-0 vc-left-0 vc-z-20 + vc-bg-gray-lightest vc-text-gray-darker + vc-border-r vc-border-gray-light + ${props.side.getContent() !== 'none' ? '' : 'vc-hidden'}`} > ) - const hidden = isTrue(props.settings.value.ui.axesPanel) ? '' : ' vc-hidden' + const hidden = isTrue(props.settings.value.ui.panelAxes) ? '' : ' vc-hidden' const empty = !anyUiAxesButton(props.settings.value) const createBar = () => { @@ -106,9 +107,9 @@ function AxesPanel (props: { viewer: Core.Webgl.Viewer, camera: CameraRef, setti
{whenAllTrue([ - props.settings.value.ui.orthographic + props.settings.value.ui.axesOrthographic ], btnOrtho)} - {whenTrue(props.settings.value.ui.resetCamera, btnHome)} + {whenTrue(props.settings.value.ui.axesHome, btnHome)}
) diff --git a/src/vim-web/react-viewers/settings/settingsKeys.ts b/src/vim-web/react-viewers/settings/settingsKeys.ts index 68f20d2f..e8b0b9af 100644 --- a/src/vim-web/react-viewers/settings/settingsKeys.ts +++ b/src/vim-web/react-viewers/settings/settingsKeys.ts @@ -58,9 +58,9 @@ export class SettingsPanelKeys { static ControlBarVisibilitySettings = 'controlBar.visibility.settings' // --- Control Bar - Settings --- - static ControlBarSettingsSubtitle = 'controlBarSettings' - static ControlBarSettingsShowProjectInspectorButtonToggle = 'projectInspector' - static ControlBarSettingsShowSettingsButtonToggle = 'settingsButton' - static ControlBarSettingsShowHelpButtonToggle = 'help' - static ControlBarSettingsShowMaximiseButtonToggle = 'maximise' + static ControlBarMiscSubtitle = 'controlBarSettings' + static ControlBarMiscShowProjectInspectorButtonToggle = 'projectInspector' + static ControlBarMiscShowSettingsButtonToggle = 'settingsButton' + static ControlBarMiscShowHelpButtonToggle = 'help' + static ControlBarMiscShowMaximiseButtonToggle = 'maximise' } diff --git a/src/vim-web/react-viewers/state/controlBarState.tsx b/src/vim-web/react-viewers/state/controlBarState.tsx index 5a7996e1..44aec542 100644 --- a/src/vim-web/react-viewers/state/controlBarState.tsx +++ b/src/vim-web/react-viewers/state/controlBarState.tsx @@ -17,9 +17,10 @@ import { PointerMode } from '../../core-viewers/shared'; import * as ControlBar from '../controlbar' import Style = ControlBar.Style; import Ids = ControlBar.Ids; -import { isTrue, UserBoolean } from "../settings/userBoolean"; +import { isFalse, isTrue, UserBoolean } from "../settings/userBoolean"; import { UltraSettings } from "../ultra/settings"; import { WebglSettings } from "../webgl/settings"; +import { AnySettings } from "../settings"; export type ControlBarSectionBoxSettings = { sectioningEnable: UserBoolean @@ -40,61 +41,61 @@ export function controlBarSectionBox( ): ControlBar.IControlBarSection { return { - id: Ids.sectionSectionBox, + id: Ids.sectioningSpan, style: section.enable.get()? Style.sectionNoPadStyle : Style.sectionDefaultStyle, //enable: () => section.getEnable(), buttons: [ { - id: Ids.buttonSectionBoxEnable, + id: Ids.sectioningEnable, enabled: () => isTrue(settings.sectioningEnable), tip: 'Enable Section Box', isOn: () => section.enable.get(), - style: (on) => Style.buttonExpandStyle(on), + style: Style.buttonExpandStyle, action: () => section.enable.set(!section.enable.get()), icon: Icons.sectionBox, }, { - id: Ids.buttonSectionBoxToSelection, + id: Ids.sectioningFitSelection, tip: 'Fit Section', enabled: () => section.enable.get() && isTrue(settings.sectioningFitToSelection), isOn: () => hasSelection, - style: (on) => Style.buttonDisableStyle(on), + style: Style.buttonDisableStyle, action: () => section.sectionSelection.call(), icon: Icons.sectionBoxShrink, }, { - id: Ids.buttonSectionBoxToScene, + id: Ids.sectioningFitScene, tip: 'Reset Section', enabled: () => section.enable.get() && isTrue(settings.sectioningReset), - style: (on) => Style.buttonDefaultStyle(on), + style: Style.buttonDefaultStyle, action: () => section.sectionScene.call(), icon: Icons.sectionBoxReset, }, { - id: Ids.buttonSectionBoxVisible, + id: Ids.sectioningVisible, tip: 'Show Section Box', enabled: () => section.enable.get() && isTrue(settings.sectioningShow), isOn: () => section.visible.get(), - style: (on) => Style.buttonDefaultStyle(on), + style: Style.buttonDefaultStyle, action: () => section.visible.set(!section.visible.get()), icon: Icons.visible, }, { - id: Ids.buttonSectionBoxAuto, + id: Ids.sectioningAuto, tip: 'Auto Section', enabled: () => section.enable.get() && isTrue(settings.sectioningAuto), isOn: () => section.auto.get(), - style: (on) => Style.buttonDefaultStyle(on), + style: Style.buttonDefaultStyle, action: () => section.auto.set(!section.auto.get()), icon: Icons.sectionBoxAuto, }, { - id: Ids.buttonSectionBoxSettings, + id: Ids.sectioningSettings, tip: 'Section Settings', enabled: () => section.enable.get() && isTrue(settings.sectioningSettings), isOn: () => section.showOffsetPanel.get(), - style: (on) => Style.buttonDefaultStyle(on), + style: Style.buttonDefaultStyle, action: () => section.showOffsetPanel.set(!section.showOffsetPanel.get()), icon: Icons.slidersHoriz, }, @@ -114,19 +115,17 @@ export type ControlBarCursorSettings = { */ function controlBarPointer( viewer: Core.Webgl.Viewer, - camera: CameraRef, settings: ControlBarCursorSettings, - section: SectionBoxRef ): ControlBar.IControlBarSection { const pointer = getPointerState(viewer); return { - id: Ids.sectionInputs, + id: Ids.cursorSpan, enable: () => anyUiCursorButton(settings), style: Style.sectionDefaultStyle, buttons: [ { - id: Ids.buttonCameraOrbit, + id: Ids.cursorOrbit, enabled: () => isTrue(settings.cursorOrbit), tip: 'Orbit', action: () => pointer.onButton(PointerMode.ORBIT), @@ -135,7 +134,7 @@ function controlBarPointer( style: Style.buttonDefaultStyle, }, { - id: Ids.buttonCameraLook, + id: Ids.cursorLook, enabled: () => isTrue(settings.cursorLookAround), tip: 'Look Around', action: () => pointer.onButton(PointerMode.LOOK), @@ -144,7 +143,7 @@ function controlBarPointer( style: Style.buttonDefaultStyle, }, { - id: Ids.buttonCameraPan, + id: Ids.cursorPan, enabled: () => isTrue(settings.cursorPan), tip: 'Pan', action: () => pointer.onButton(PointerMode.PAN), @@ -153,7 +152,7 @@ function controlBarPointer( style: Style.buttonDefaultStyle, }, { - id: Ids.buttonCameraZoom, + id: Ids.cursorZoom, enabled: () => isTrue(settings.cursorZoom), tip: 'Zoom', action: () => pointer.onButton(PointerMode.ZOOM), @@ -166,7 +165,7 @@ function controlBarPointer( } export type ControlBarMeasureSettings = { - measuringMode: UserBoolean + measureEnable: UserBoolean } export function controlBarMeasure( @@ -174,13 +173,13 @@ export function controlBarMeasure( settings: ControlBarMeasureSettings ){ return { - id: Ids.sectionActions, + id: Ids.measureSpan, enable: () => true, style: Style.sectionDefaultStyle, buttons: [ { - id: Ids.buttonMeasure, - enabled: () => isTrue(settings.measuringMode), + id: Ids.measureEnable, + enabled: () => isTrue(settings.measureEnable), isOn: () => measure.active, tip: 'Measuring Mode', action: () => measure.toggle(), @@ -190,70 +189,79 @@ export function controlBarMeasure( ] } } - -export function controlBarSettingsUltra( +// Shared misc button builders +function createMiscSettingsButton( side: SideState, - settings: UltraSettings): ControlBar.IControlBarSection { + settings: AnySettings +) { + return { + id: Ids.miscSettings, + enabled: () => isTrue(settings.ui.miscSettings), + tip: 'Settings', + action: () => side.toggleContent('settings'), + icon: Icons.settings, + style: Style.buttonDefaultStyle + }; +} +function createMiscHelpButton( + modal : ModalHandle, + settings: AnySettings, +){ + return { + id: Ids.miscHelp, + enabled: () => isTrue(settings.ui.miscHelp), + tip: 'Help', + action: () => modal.help(true), + icon: Icons.help, + style: Style.buttonDefaultStyle + }; +} + +// Ultra version +export function controlBarMiscUltra( + modal : ModalHandle, + side: SideState, + settings: UltraSettings +): ControlBar.IControlBarSection { return { - id: Ids.sectionSettings, - enable: () => isTrue(settings.ui.settings), + id: Ids.miscSpan, + enable: () => anyUltraMiscButton(settings), style: Style.sectionDefaultStyle, buttons: [ - { - id: Ids.buttonSettings, - enabled: () => isTrue(settings.ui.settings), - tip: 'Settings', - action: () => side.toggleContent('settings'), - icon: Icons.settings, - style: Style.buttonDefaultStyle - }, + createMiscSettingsButton(side, settings), + createMiscHelpButton(modal, settings) ] - } + }; } -function controlBarSettings( +// WebGL version +function controlBarMisc( modal: ModalHandle, side: SideState, - settings: WebglSettings): ControlBar.IControlBarSection { + settings: WebglSettings +): ControlBar.IControlBarSection { const fullScreen = getFullScreenState(); return { - id: Ids.sectionSettings, - enable: () => anyUiSettingButton(settings), + id: Ids.miscSpan, + enable: () => anyWebglMiscButton(settings), style: Style.sectionDefaultStyle, buttons: [ { - id: Ids.buttonProjectInspector, - enabled: () => isTrue(settings.ui.projectInspector) && ( - isTrue(settings.ui.bimTreePanel) || - isTrue(settings.ui.bimInfoPanel) - ), + id: Ids.miscInspector, + enabled: () => showBimButton(settings), tip: 'Project Inspector', action: () => side.toggleContent('bim'), icon: Icons.treeView, style: Style.buttonDefaultStyle }, + createMiscSettingsButton(side, settings), + createMiscHelpButton(modal, settings), { - id: Ids.buttonSettings, - enabled: () => isTrue(settings.ui.settings), - tip: 'Settings', - action: () => side.toggleContent('settings'), - icon: Icons.settings, - style: Style.buttonDefaultStyle - }, - { - id: Ids.buttonHelp, - enabled: () => isTrue(settings.ui.help), - tip: 'Help', - action: () => modal.help(true), - icon: Icons.help, - style: Style.buttonDefaultStyle - }, - { - id: Ids.buttonMaximize, + id: Ids.miscMaximize, enabled: () => - isTrue(settings.ui.maximise) && + isTrue(settings.ui.miscMaximise) && settings.capacity.canGoFullScreen, tip: fullScreen.get() ? 'Minimize' : 'Fullscreen', action: () => fullScreen.toggle(), @@ -261,7 +269,7 @@ function controlBarSettings( style: Style.buttonDefaultStyle } ] - } + }; } export type ControlBarCameraSettings ={ @@ -272,12 +280,12 @@ export type ControlBarCameraSettings ={ export function controlBarCamera(camera: CameraRef, settings: ControlBarCameraSettings): ControlBar.IControlBarSection { return { - id: Ids.sectionCamera, + id: Ids.cameraSpan, enable: () => true, style: Style.sectionDefaultStyle, buttons: [ { - id: Ids.buttonCameraAuto, + id: Ids.cameraAuto, enabled: () => isTrue(settings.cameraAuto), tip: 'Auto Camera', isOn: () => camera.autoCamera.get(), @@ -286,7 +294,7 @@ export function controlBarCamera(camera: CameraRef, settings: ControlBarCameraSe style: Style.buttonDefaultStyle, }, { - id: Ids.buttonCameraFrameSelection, + id: Ids.cameraFrameSelection, enabled: () => isTrue(settings.cameraFrameSelection), tip: 'Frame Selection', action: () => camera.frameSelection.call(), @@ -295,7 +303,7 @@ export function controlBarCamera(camera: CameraRef, settings: ControlBarCameraSe style: Style.buttonDefaultStyle, }, { - id: Ids.buttonCameraFrameScene, + id: Ids.cameraFrameScene, enabled: () => isTrue(settings.cameraFrameScene), tip: 'Frame All', action: () => camera.frameScene.call(), @@ -308,7 +316,6 @@ export function controlBarCamera(camera: CameraRef, settings: ControlBarCameraSe } export type ControlBarVisibilitySettings = { - visibilityEnable: UserBoolean visibilityClearSelection: UserBoolean visibilityShowAll: UserBoolean visibilityToggle: UserBoolean @@ -322,12 +329,12 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB const someVisible = adapter.hasVisibleSelection() || !adapter.hasHiddenSelection() return { - id: Ids.sectionSelection, + id: Ids.visibilitySpan, enable: () => true, - style: `${Style.sectionDefaultStyle}`, + style: Style.sectionDefaultStyle, buttons: [ { - id: Ids.buttonClearSelection, + id: Ids.visibilityClearSelection, enabled: () => isTrue(settings.visibilityClearSelection), tip: 'Clear Selection', action: () => adapter.clearSelection(), @@ -336,7 +343,7 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB style: Style.buttonDisableDefaultStyle, }, { - id: Ids.buttonShowAll, + id: Ids.visibilityShowAll, tip: 'Show All', enabled: () => isTrue(settings.visibilityShowAll), action: () => adapter.showAll(), @@ -346,7 +353,7 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB }, { - id: Ids.buttonHideSelection, + id: Ids.visibilityHideSelection, enabled: () => someVisible && isTrue(settings.visibilityToggle), tip: 'Hide Selection', action: () => adapter.hideSelection(), @@ -355,7 +362,7 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB style: Style.buttonDisableStyle, }, { - id: Ids.buttonShowSelection, + id: Ids.visibilityShowSelection, enabled: () => !someVisible && isTrue(settings.visibilityToggle), tip: 'Show Selection', action: () => adapter.showSelection(), @@ -364,7 +371,7 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB style: Style.buttonDisableStyle, }, { - id: Ids.buttonIsolateSelection, + id: Ids.visibilityIsolateSelection, enabled: () => isTrue(settings.visibilityIsolate), tip: 'Isolate Selection', action: () => adapter.isolateSelection(), @@ -373,7 +380,7 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB style: Style.buttonDisableStyle, }, { - id: Ids.buttonAutoIsolate, + id: Ids.visibilityAutoIsolate, enabled: () => isTrue(settings.visibilityAutoIsolate), tip: 'Auto Isolate', action: () => isolation.autoIsolate.set(!isolation.autoIsolate.get()), @@ -381,7 +388,7 @@ export function controlBarVisibility(isolation: IsolationRef, settings: ControlB icon: Icons.autoIsolate, }, { - id: Ids.buttonIsolationSettings, + id: Ids.visibilitySettings, enabled: () => isTrue(settings.visibilitySettings), tip: 'Isolation Settings', action: () => isolation.showPanel.set(!isolation.showPanel.get()), @@ -410,17 +417,26 @@ export function useControlBar( // Apply user customization (note that pointerSection is added twice per original design) let controlBarSections = [ - controlBarPointer(viewer, camera, settings.ui, section), + controlBarPointer(viewer, settings.ui), controlBarCamera(camera, settings.ui), controlBarVisibility(isolationRef, settings.ui), controlBarMeasure(measure, settings.ui), controlBarSectionBox(section, viewer.selection.any(), settings.ui), - controlBarSettings(modal, side, settings) + controlBarMisc(modal, side, settings) ]; controlBarSections = customization?.(controlBarSections) ?? controlBarSections; return controlBarSections; } + +function showBimButton(settings: WebglSettings){ +if(isFalse(settings.ui.miscProjectInspector)) return false + if(isTrue(settings.ui.panelBimTree) ) return true + if(isTrue(settings.ui.panelBimInfo) ) return true + return false +} + + /** * Checks if any cursor-related UI buttons are enabled * @param {Settings} settings - The viewer settings to check @@ -440,11 +456,18 @@ function anyUiCursorButton (settings: ControlBarCursorSettings) { * @param {Settings} settings - The viewer settings to check * @returns {boolean} True if any settings buttons are enabled */ -export function anyUiSettingButton (settings: WebglSettings) { +export function anyWebglMiscButton (settings: WebglSettings) { return ( - isTrue(settings.ui.projectInspector) || - isTrue(settings.ui.settings) || - isTrue(settings.ui.help) || - isTrue(settings.ui.maximise) + isTrue(settings.ui.miscProjectInspector) || + isTrue(settings.ui.miscSettings) || + isTrue(settings.ui.miscHelp) || + isTrue(settings.ui.miscMaximise) ) -} \ No newline at end of file +} + +export function anyUltraMiscButton (settings: UltraSettings) { + return ( + isTrue(settings.ui.miscSettings) || + isTrue(settings.ui.miscHelp) + ) +} diff --git a/src/vim-web/react-viewers/ultra/controlBar.ts b/src/vim-web/react-viewers/ultra/controlBar.ts index faeda5b6..6c144f35 100644 --- a/src/vim-web/react-viewers/ultra/controlBar.ts +++ b/src/vim-web/react-viewers/ultra/controlBar.ts @@ -1,8 +1,9 @@ import * as Core from '../../core-viewers/ultra' import { ControlBarCustomization } from '../controlbar/controlBar' +import { ModalHandle } from '../panels' import { CameraRef } from '../state/cameraState' -import { controlBarCamera, controlBarSectionBox, controlBarSettingsUltra, controlBarVisibility } from '../state/controlBarState' +import { controlBarCamera, controlBarSectionBox, controlBarMiscUltra, controlBarVisibility } from '../state/controlBarState' import { SectionBoxRef } from '../state/sectionBoxState' import { IsolationRef } from '../state/sharedIsolation' import { SideState } from '../state/sideState' @@ -15,13 +16,14 @@ export function useUltraControlBar ( camera: CameraRef, settings: UltraSettings, side: SideState, + modal: ModalHandle, customization: ControlBarCustomization | undefined ) { let bar = [ controlBarCamera(camera, settings.ui), controlBarVisibility(isolation, settings.ui), controlBarSectionBox(section, viewer.selection.any(), settings.ui), - controlBarSettingsUltra(side, settings) + controlBarMiscUltra(modal, side, settings) ] bar = customization?.(bar) ?? bar return bar diff --git a/src/vim-web/react-viewers/ultra/settings.ts b/src/vim-web/react-viewers/ultra/settings.ts index 9cadd585..f3d8a01f 100644 --- a/src/vim-web/react-viewers/ultra/settings.ts +++ b/src/vim-web/react-viewers/ultra/settings.ts @@ -9,8 +9,13 @@ export type UltraSettings = { ControlBarCursorSettings & ControlBarSectionBoxSettings & ControlBarVisibilitySettings & { + // Panels + panelLogo: UserBoolean + panelControlBar: UserBoolean - settings: UserBoolean + // Control bar - misc + miscSettings: UserBoolean + miscHelp: UserBoolean } } @@ -18,6 +23,10 @@ export function getDefaultUltraSettings(): UltraSettings { return { ui: { + // panels + panelLogo: true, + panelControlBar: true, + // Control bar - cursors cursorOrbit: true, cursorLookAround: true, @@ -38,7 +47,6 @@ export function getDefaultUltraSettings(): UltraSettings { sectioningSettings : true, // Control bar - Visibility - visibilityEnable: true, visibilityClearSelection: true, visibilityShowAll: true, visibilityToggle: true, @@ -46,7 +54,9 @@ export function getDefaultUltraSettings(): UltraSettings { visibilityAutoIsolate: true, visibilitySettings: true, - settings: true + // Control bar - misc + miscSettings: true, + miscHelp: true, } } } \ No newline at end of file diff --git a/src/vim-web/react-viewers/ultra/settingsPanel.ts b/src/vim-web/react-viewers/ultra/settingsPanel.ts index 9fd50883..279e0ef2 100644 --- a/src/vim-web/react-viewers/ultra/settingsPanel.ts +++ b/src/vim-web/react-viewers/ultra/settingsPanel.ts @@ -8,15 +8,22 @@ export function getControlBarUltraSettings(): SettingsItem[] { return [ { type: 'subtitle', - key: SettingsPanelKeys.ControlBarSettingsSubtitle, + key: SettingsPanelKeys.ControlBarMiscSubtitle, title: 'Control Bar - Settings', }, { type: 'toggle', - key: SettingsPanelKeys.ControlBarSettingsShowSettingsButtonToggle, + key: SettingsPanelKeys.ControlBarMiscShowSettingsButtonToggle, label: 'Settings', - getter: (s) => s.ui.settings, - setter: (s, v) => (s.ui.settings = v), + getter: (s) => s.ui.miscSettings, + setter: (s, v) => (s.ui.miscSettings = v), + }, + { + type: 'toggle', + key: SettingsPanelKeys.ControlBarMiscShowHelpButtonToggle, + label: 'Help', + getter: (s) => s.ui.miscHelp, + setter: (s, v) => (s.ui.miscHelp = v), }, ] } diff --git a/src/vim-web/react-viewers/ultra/viewer.tsx b/src/vim-web/react-viewers/ultra/viewer.tsx index 2c7039d1..2ccc19c1 100644 --- a/src/vim-web/react-viewers/ultra/viewer.tsx +++ b/src/vim-web/react-viewers/ultra/viewer.tsx @@ -27,8 +27,10 @@ import { GenericPanelHandle } from '../generic/genericPanel' import { ControllablePromise } from '../../utils' import { SettingsPanel } from '../settings/settingsPanel' import { SidePanelMemo } from '../panels/sidePanel' -import { getDefaultUltraSettings, PartialUltraSettings } from './settings' +import { getDefaultUltraSettings, PartialUltraSettings, UltraSettings } from './settings' import { getUltraSettingsContent } from './settingsPanel' +import { SettingsCustomizer } from '../settings/settingsItem' +import { isTrue } from '../settings/userBoolean' /** @@ -104,6 +106,7 @@ export function Viewer (props: { camera, settings.value, side, + modalHandle.current, _ =>_ ) @@ -133,6 +136,11 @@ export function Viewer (props: { isolation: isolationRef, sectionBox: sectionBoxRef, camera, + settings: { + update : settings.update, + register : settings.register, + customize : (c: SettingsCustomizer) => settings.customizer.set(c) + }, get isolationPanel(){ return isolationPanelHandle.current }, @@ -166,11 +174,11 @@ export function Viewer (props: { /> { return <> - {whenTrue(true, )} + {whenTrue(settings.value.ui.panelLogo, )} diff --git a/src/vim-web/react-viewers/ultra/viewerRef.ts b/src/vim-web/react-viewers/ultra/viewerRef.ts index df5eade2..99cac71a 100644 --- a/src/vim-web/react-viewers/ultra/viewerRef.ts +++ b/src/vim-web/react-viewers/ultra/viewerRef.ts @@ -6,6 +6,8 @@ import { SectionBoxRef } from '../state/sectionBoxState'; import { IsolationRef } from '../state/sharedIsolation'; import { ControlBarRef } from '../controlbar'; import { GenericPanelHandle } from '../generic/'; +import { SettingsRef } from '../webgl'; +import { UltraSettings } from './settings'; export type ViewerRef = { /** @@ -34,6 +36,8 @@ export type ViewerRef = { camera: CameraRef isolation: IsolationRef + + settings: SettingsRef /** * API to interact with the isolation panel. diff --git a/src/vim-web/react-viewers/webgl/loading.ts b/src/vim-web/react-viewers/webgl/loading.ts index 7edecba9..fc71ccbd 100644 --- a/src/vim-web/react-viewers/webgl/loading.ts +++ b/src/vim-web/react-viewers/webgl/loading.ts @@ -7,7 +7,7 @@ import * as Core from '../../core-viewers' import { LoadRequest } from '../helpers/loadRequest' import { ModalHandle } from '../panels/modal' import { UltraSuggestion } from '../panels/loadingBox' -import { Settings } from '../settings' +import { WebglSettings } from './settings' type AddSettings = { /** @@ -39,7 +39,7 @@ export class ComponentLoader { private _modal: React.RefObject private _addLink : boolean = false - constructor (viewer : Core.Webgl.Viewer, modal: React.RefObject, settings: Settings) { + constructor (viewer : Core.Webgl.Viewer, modal: React.RefObject, settings: WebglSettings) { this._viewer = viewer this._modal = modal // TODO: Enable this when we are ready to support it diff --git a/src/vim-web/react-viewers/webgl/settings.ts b/src/vim-web/react-viewers/webgl/settings.ts index 81a9f7d1..58bd49b9 100644 --- a/src/vim-web/react-viewers/webgl/settings.ts +++ b/src/vim-web/react-viewers/webgl/settings.ts @@ -22,22 +22,22 @@ export type WebglSettings = { ControlBarMeasureSettings & { // panels - logo: UserBoolean - bimTreePanel: UserBoolean - bimInfoPanel: UserBoolean - performance: UserBoolean - axesPanel: UserBoolean - controlBar: UserBoolean + panelLogo: UserBoolean + panelBimTree: UserBoolean + panelBimInfo: UserBoolean + panelPerformance: UserBoolean + panelAxes: UserBoolean + panelControlBar: UserBoolean // axesPanel - orthographic: UserBoolean - resetCamera: UserBoolean + axesOrthographic: UserBoolean + axesHome: UserBoolean // Control bar - settings - projectInspector: UserBoolean - settings: UserBoolean - help: UserBoolean - maximise: UserBoolean + miscProjectInspector: UserBoolean + miscSettings: UserBoolean + miscHelp: UserBoolean + miscMaximise: UserBoolean } } @@ -56,18 +56,16 @@ export function getDefaultSettings(): WebglSettings { canReadLocalStorage: true }, ui: { - logo: true, - performance: false, - bimTreePanel: true, - bimInfoPanel: true, + panelLogo: true, + panelPerformance: false, + panelBimTree: true, + panelBimInfo: true, + panelAxes: true, + panelControlBar: true, - // axesPanel - axesPanel: true, - orthographic: true, - resetCamera: true, - - // Control bar - controlBar: true, + axesOrthographic: true, + axesHome: true, + // Control bar - cursors cursorOrbit: true, cursorLookAround: true, @@ -87,10 +85,9 @@ export function getDefaultSettings(): WebglSettings { sectioningAuto : true, sectioningSettings : true, - measuringMode: true, + measureEnable: true, // Control bar - Visibility - visibilityEnable: true, visibilityClearSelection: true, visibilityShowAll: true, visibilityToggle: true, @@ -99,10 +96,10 @@ export function getDefaultSettings(): WebglSettings { visibilitySettings: true, // Control bar - settings - projectInspector: true, - settings: true, - help: true, - maximise: true + miscProjectInspector: true, + miscSettings: true, + miscHelp: true, + miscMaximise: true } } } diff --git a/src/vim-web/react-viewers/webgl/settingsPanel.ts b/src/vim-web/react-viewers/webgl/settingsPanel.ts index 914bc7f7..8f6faee5 100644 --- a/src/vim-web/react-viewers/webgl/settingsPanel.ts +++ b/src/vim-web/react-viewers/webgl/settingsPanel.ts @@ -10,36 +10,36 @@ export function getControlBarVariousSettings(): SettingsItem[] { return [ { type: 'subtitle', - key: SettingsPanelKeys.ControlBarSettingsSubtitle, + key: SettingsPanelKeys.ControlBarMiscSubtitle, title: 'Control Bar - Settings', }, { type: 'toggle', - key: SettingsPanelKeys.ControlBarSettingsShowProjectInspectorButtonToggle, + key: SettingsPanelKeys.ControlBarMiscShowProjectInspectorButtonToggle, label: 'Project Inspector', - getter: (s) => s.ui.projectInspector, - setter: (s, v) => (s.ui.projectInspector = v), + getter: (s) => s.ui.miscProjectInspector, + setter: (s, v) => (s.ui.miscProjectInspector = v), }, { type: 'toggle', - key: SettingsPanelKeys.ControlBarSettingsShowSettingsButtonToggle, + key: SettingsPanelKeys.ControlBarMiscShowSettingsButtonToggle, label: 'Settings', - getter: (s) => s.ui.settings, - setter: (s, v) => (s.ui.settings = v), + getter: (s) => s.ui.miscSettings, + setter: (s, v) => (s.ui.miscSettings = v), }, { type: 'toggle', - key: SettingsPanelKeys.ControlBarSettingsShowHelpButtonToggle, + key: SettingsPanelKeys.ControlBarMiscShowHelpButtonToggle, label: 'Help', - getter: (s) => s.ui.help, - setter: (s, v) => (s.ui.help = v), + getter: (s) => s.ui.miscHelp, + setter: (s, v) => (s.ui.miscHelp = v), }, { type: 'toggle', - key: SettingsPanelKeys.ControlBarSettingsShowMaximiseButtonToggle, + key: SettingsPanelKeys.ControlBarMiscShowMaximiseButtonToggle, label: 'Maximise', - getter: (s) => s.ui.maximise, - setter: (s, v) => (s.ui.maximise = v), + getter: (s) => s.ui.miscMaximise, + setter: (s, v) => (s.ui.miscMaximise = v), }, ] } @@ -57,43 +57,43 @@ export function getPanelsVisibilitySettings(): SettingsItem[] { type: 'toggle', key: SettingsPanelKeys.PanelsShowLogoToggle, label: 'Logo', - getter: (s) => s.ui.logo, - setter: (s, v) => (s.ui.logo = v), + getter: (s) => s.ui.panelLogo, + setter: (s, v) => (s.ui.panelLogo = v), }, { type: 'toggle', key: SettingsPanelKeys.PanelsShowBimTreeToggle, label: 'Bim Tree', - getter: (s) => s.ui.bimTreePanel, - setter: (s, v) => (s.ui.bimTreePanel = v), + getter: (s) => s.ui.panelBimTree, + setter: (s, v) => (s.ui.panelBimTree = v), }, { type: 'toggle', key: SettingsPanelKeys.PanelsShowBimInfoToggle, label: 'Bim Info', - getter: (s) => s.ui.bimInfoPanel, - setter: (s, v) => (s.ui.bimInfoPanel = v), + getter: (s) => s.ui.panelBimInfo, + setter: (s, v) => (s.ui.panelBimInfo = v), }, { type: 'toggle', key: SettingsPanelKeys.PanelsShowAxesPanelToggle, label: 'Axes', - getter: (s) => s.ui.axesPanel, - setter: (s, v) => (s.ui.axesPanel = v), + getter: (s) => s.ui.panelAxes, + setter: (s, v) => (s.ui.panelAxes = v), }, { type: 'toggle', key: SettingsPanelKeys.PanelsShowPerformancePanelToggle, label: 'Performance', - getter: (s) => s.ui.performance, - setter: (s, v) => (s.ui.performance = v), + getter: (s) => s.ui.panelPerformance, + setter: (s, v) => (s.ui.panelPerformance = v), }, { type: 'toggle', key: SettingsPanelKeys.ControlBarShowControlBarToggle, label: 'Control Bar', - getter: (s) => s.ui.controlBar, - setter: (s, v) => (s.ui.controlBar = v), + getter: (s) => s.ui.panelControlBar, + setter: (s, v) => (s.ui.panelControlBar = v), }, ] } @@ -132,15 +132,15 @@ function getAxesPanelSettings(): SettingsItem[] { type: 'toggle', key: SettingsPanelKeys.AxesShowOrthographicButtonToggle, label: 'Orthographic Camera', - getter: (s) => s.ui.orthographic, - setter: (s, v) => (s.ui.orthographic = v), + getter: (s) => s.ui.axesOrthographic, + setter: (s, v) => (s.ui.axesOrthographic = v), }, { type: 'toggle', key: SettingsPanelKeys.AxesShowResetCameraButtonToggle, label: 'Reset Camera', - getter: (s) => s.ui.resetCamera, - setter: (s, v) => (s.ui.resetCamera = v), + getter: (s) => s.ui.axesHome, + setter: (s, v) => (s.ui.axesHome = v), }, ] } @@ -156,8 +156,8 @@ export function getControlBarMeasureSettings(): SettingsItem[] { type: 'toggle', key: SettingsPanelKeys.ControlBarToolsShowMeasuringModeButtonToggle, label: 'Enable', - getter: (s) => s.ui.measuringMode, - setter: (s, v) => (s.ui.measuringMode = v), + getter: (s) => s.ui.measureEnable, + setter: (s, v) => (s.ui.measureEnable = v), }, ] } @@ -185,7 +185,7 @@ export function applyWebglSettings (settings: WebglSettings) { // Show/Hide performance gizmo const performance = document.getElementsByClassName('vim-performance-div')[0] if (performance) { - if (isTrue(settings.ui.performance)) { + if (isTrue(settings.ui.panelPerformance)) { performance.classList.remove('vc-hidden') } else { performance.classList.add('vc-hidden') diff --git a/src/vim-web/react-viewers/webgl/viewer.tsx b/src/vim-web/react-viewers/webgl/viewer.tsx index 265cb38b..0ded9cc0 100644 --- a/src/vim-web/react-viewers/webgl/viewer.tsx +++ b/src/vim-web/react-viewers/webgl/viewer.tsx @@ -123,8 +123,8 @@ export function Viewer (props: { useViewerInput(props.viewer.inputs, camera) const side = useSideState( - isTrue(settings.value.ui.bimTreePanel) || - isTrue(settings.value.ui.bimInfoPanel), + isTrue(settings.value.ui.panelBimTree) || + isTrue(settings.value.ui.panelBimInfo), Math.min(props.container.root.clientWidth * 0.25, 340) ) const [contextMenu, setcontextMenu] = useState() @@ -239,10 +239,10 @@ export function Viewer (props: { { return <> - {whenTrue(settings.value.ui.logo, )} + {whenTrue(settings.value.ui.panelLogo, )} diff --git a/src/vim-web/react-viewers/webgl/viewerRef.ts b/src/vim-web/react-viewers/webgl/viewerRef.ts index c4bcc862..0b561457 100644 --- a/src/vim-web/react-viewers/webgl/viewerRef.ts +++ b/src/vim-web/react-viewers/webgl/viewerRef.ts @@ -4,7 +4,7 @@ import * as Core from '../../core-viewers' import { ContextMenuRef } from '../panels/contextMenu' -import { Settings } from '../settings/anySettings' +import { AnySettings } from '../settings/anySettings' import { CameraRef } from '../state/cameraState' import { Container } from '../container' import { BimInfoPanelRef } from '../bim/bimInfoData' @@ -15,10 +15,11 @@ import { SectionBoxRef } from '../state/sectionBoxState' import { IsolationRef } from '../state/sharedIsolation' import { GenericPanelHandle } from '../generic' import { SettingsItem } from '../settings/settingsItem' +import { WebglSettings } from './settings' /** * Settings API managing settings applied to the viewer. */ -export type SettingsRef = { +export type SettingsRef = { // Double lambda is required to prevent react from using reducer pattern // https://stackoverflow.com/questions/59040989/usestate-with-a-lambda-invokes-the-lambda-when-set @@ -26,19 +27,19 @@ export type SettingsRef = { * Allows updating settings by providing a callback function. * @param updater A function that updates the current settings. */ - update : (updater: (settings: Settings) => void) => void + update : (updater: (settings: T) => void) => void /** * Registers a callback function to be notified when settings are updated. * @param callback A function to be called when settings are updated, receiving the updated settings. */ - register : (callback: (settings: Settings) => void) => void + register : (callback: (settings: T) => void) => void /** * Customizes the settings panel by providing a customizer function. * @param customizer A function that modifies the settings items. */ - customize : (customizer: (items: SettingsItem[]) => SettingsItem[]) => void + customize : (customizer: (items: SettingsItem[]) => SettingsItem[]) => void } @@ -104,7 +105,7 @@ export type ViewerRef = { /** * Settings API managing settings applied to the viewer. */ - settings: SettingsRef + settings: SettingsRef /** * Message API to interact with the loading box. From 2679298a36eff993b0eb87c153e50e6670982c0a Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 18 Nov 2025 13:25:17 -0500 Subject: [PATCH 6/8] reverted test code in main --- src/main.tsx | 82 ++++--------------- .../react-viewers/state/controlBarState.tsx | 2 +- 2 files changed, 18 insertions(+), 66 deletions(-) diff --git a/src/main.tsx b/src/main.tsx index 5455fae7..11310a6d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,9 +1,6 @@ import React, { MutableRefObject, useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; import * as VIM from './vim-web' -import * as THREE from 'three' -import { Icons } from "./vim-web/react-viewers"; -import { useStateRef } from "./vim-web/react-viewers/helpers/reactUtils"; type ViewerRef = VIM.React.Webgl.ViewerRef | VIM.React.Ultra.ViewerRef @@ -22,57 +19,20 @@ console.log("Root container found", container); // Render your App root.render( // - + // ); function App() { + const div = useRef(null) const viewerRef = useRef() - - var customToggle = useStateRef(false) - var customValue = useStateRef(10) - useEffect(() => { - if (window.location.pathname.includes('ultra')) { - createUltra(viewerRef, div.current!) + if(window.location.pathname.includes('ultra')){ + createUltra(viewerRef, div.current!) } - else { - createWebgl(viewerRef, div.current!).then((viewer) => { - viewer.settings.customize((s) => [ - { - type: 'subtitle', - key: 'custom-subtitle', - title: 'Custom Section' - }, - { - type: 'toggle', - key: 'custom-toggle', - label: 'Custom Toggle', - getter: (s) => customToggle.get(), - setter: (s, v) => (customToggle.set(!v)) - }, - { - type: 'box', - key: 'custom-box', - label: 'Custom Box', - info: 'closest even number', - transform: (n) => Math.round(n / 2) * 2, - getter: (s) => customValue.get(), - setter: (s, v) => (customValue.set(v)) - }, - { - type: 'element', - key: 'custom-element', - // example custom text - element: ( -
- This is a custom element added to the settings panel. -
) - }, - ...s // keep all exising settings - ]) - }) + else{ + createWebgl(viewerRef, div.current!) } return () => { @@ -81,46 +41,35 @@ function App() { }, []) return ( -
+
) } -async function createWebgl(viewerRef: MutableRefObject, div: HTMLDivElement) { +async function createWebgl (viewerRef: MutableRefObject, div: HTMLDivElement) { const viewer = await VIM.React.Webgl.createViewer(div) viewerRef.current = viewer globalThis.viewer = viewer // for testing in browser console const url = getPathFromUrl() ?? 'https://storage.cdn.vimaec.com/samples/residence.v1.2.75.vim' const request = viewer.loader.request( - { url }, + { url }, ) - const result = await request.getResult() if (result.isSuccess()) { viewer.loader.add(result.result) viewer.camera.frameScene.call() } - - return viewer } -async function createUltra(viewerRef: MutableRefObject, div: HTMLDivElement) { - const viewer = await VIM.React.Ultra.createViewer(div, - {ui:{ - sectioningEnable: true, - - }} - ) - viewer.camera.autoCamera.set(true) // Enable auto camera framing - viewer.isolation.autoIsolate.set(true) // Enable auto isolation - +async function createUltra (viewerRef: MutableRefObject, div: HTMLDivElement) { + const viewer = await VIM.React.Ultra.createViewer(div) await viewer.core.connect() viewerRef.current = viewer globalThis.viewer = viewer // for testing in browser console const url = getPathFromUrl() ?? 'https://storage.cdn.vimaec.com/samples/residence.v1.2.75.vim' const request = viewer.load( - { url }, + { url }, ) const result = await request.getResult() if (result.isSuccess) { @@ -128,7 +77,10 @@ async function createUltra(viewerRef: MutableRefObject, div: HTMLDivE } } -function getPathFromUrl() { + + +function getPathFromUrl () { const params = new URLSearchParams(window.location.search) return params.get('vim') ?? undefined -} \ No newline at end of file +} + diff --git a/src/vim-web/react-viewers/state/controlBarState.tsx b/src/vim-web/react-viewers/state/controlBarState.tsx index 44aec542..6e294251 100644 --- a/src/vim-web/react-viewers/state/controlBarState.tsx +++ b/src/vim-web/react-viewers/state/controlBarState.tsx @@ -109,7 +109,7 @@ export type ControlBarCursorSettings = { cursorPan: UserBoolean cursorZoom: UserBoolean } - + /** * Returns a control bar section for pointer/camera modes. */ From e082c115c83cf3952a596aceaf029dbfc4b4f84d Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 21 Nov 2025 14:42:25 -0500 Subject: [PATCH 7/8] improved click detection when dragging --- package.json | 2 +- .../core-viewers/shared/mouseHandler.ts | 45 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 92a73670..eb42dd7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.17", + "version": "0.5.0-dev.24", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ diff --git a/src/vim-web/core-viewers/shared/mouseHandler.ts b/src/vim-web/core-viewers/shared/mouseHandler.ts index 4d1de34c..63145587 100644 --- a/src/vim-web/core-viewers/shared/mouseHandler.ts +++ b/src/vim-web/core-viewers/shared/mouseHandler.ts @@ -11,6 +11,7 @@ export class MouseHandler extends BaseInputHandler { private _capture: CaptureHandler; private _dragHandler: DragHandler; private _doubleClickHandler: DoubleClickHandler = new DoubleClickHandler(); + private _clickHandler: ClickHandler = new ClickHandler(); onButtonDown: (pos: THREE.Vector2, button: number) => void; onButtonUp: (pos: THREE.Vector2, button: number) => void; @@ -47,27 +48,36 @@ export class MouseHandler extends BaseInputHandler { this._lastMouseDownPosition = pos; // Start drag this._dragHandler.onPointerDown(pos, event.button); + this._clickHandler.onPointerDown(pos); this._capture.onPointerDown(event); event.preventDefault(); } private handlePointerUp(event: PointerEvent): void { if (event.pointerType !== 'mouse') return; + event.preventDefault(); + const pos = this.relativePosition(event); // Button up event this.onButtonUp?.(pos, event.button); this._capture.onPointerUp(event); this._dragHandler.onPointerUp(); + this._clickHandler.onPointerUp(); + // Click type event - if(this._doubleClickHandler.checkForDoubleClick(event)){ + if(this._doubleClickHandler.isDoubleClick(event)){ this.handleDoubleClick(event); - }else{ + return + } + if(this._clickHandler.isClick(event)){ this.handleMouseClick(event); - this.handleContextMenu(event); + return } - event.preventDefault(); + + this.handleContextMenu(event); + } private async handleMouseClick(event: PointerEvent): Promise { @@ -102,8 +112,8 @@ export class MouseHandler extends BaseInputHandler { if (event.pointerType !== 'mouse') return; this._canvas.focus(); const pos = this.relativePosition(event); - this._dragHandler.onPointerMove(pos); + this._clickHandler.onPointerMove(pos); this.onMouseMove?.(pos); } @@ -157,13 +167,36 @@ class CaptureHandler { } } +class ClickHandler { + private _moved: boolean = false; + private _startPosition: THREE.Vector2 = new THREE.Vector2(); + private _clickThreshold: number = 0.003 ; + + onPointerDown(pos: THREE.Vector2): void { + this._moved = false; + this._startPosition.copy(pos); + } + onPointerMove(pos: THREE.Vector2): void { + if (pos.distanceTo(this._startPosition) > this._clickThreshold) { + this._moved = true; + } + } + + onPointerUp(): void { } + + isClick(event: PointerEvent): boolean { + if (event.button !== 0) return false; // Only left button + return !this._moved; + } +} + class DoubleClickHandler { private _lastClickTime: number = 0; private _clickDelay: number = 300; // Max time between clicks for double-click private _lastClickPosition: THREE.Vector2 | null = null; private _positionThreshold: number = 5; // Max pixel distance between clicks - checkForDoubleClick(event: MouseEvent): boolean { + isDoubleClick(event: MouseEvent): boolean { const currentTime = Date.now(); const currentPosition = new THREE.Vector2(event.clientX, event.clientY); const timeDiff = currentTime - this._lastClickTime; From 90292331a6bf1561b064ab4bc6fc4fbccae01c38 Mon Sep 17 00:00:00 2001 From: Martin Ashton Date: Mon, 24 Nov 2025 10:59:42 -0500 Subject: [PATCH 8/8] Fixed formatting error --- .../react-viewers/ultra/errors/serverConnectionError.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vim-web/react-viewers/ultra/errors/serverConnectionError.tsx b/src/vim-web/react-viewers/ultra/errors/serverConnectionError.tsx index c567936c..d184cc0f 100644 --- a/src/vim-web/react-viewers/ultra/errors/serverConnectionError.tsx +++ b/src/vim-web/react-viewers/ultra/errors/serverConnectionError.tsx @@ -20,7 +20,7 @@ function body (url: string, local: boolean): JSX.Element { )} {style.subTitle('Tips')} {style.numList([ - `Ensure that VIM Ultra is running at ${style.detailText(url)}`, + <>Ensure that VIM Ultra is running at {style.detailText(url)}, 'Check your network connection and access policies' ])}