From ddb8ef2280e7cfc033746ee8110bebc966bbd868 Mon Sep 17 00:00:00 2001 From: 49lf Date: Fri, 20 Sep 2024 13:33:25 -0400 Subject: [PATCH] Type correct everything, make settings deserializer better, fmt tsc lint --- src/App.tsx | 2 +- src/Toolbar.tsx | 4 -- src/clientSideScene/CameraControls.ts | 17 +++--- src/components/CommandBarOpenButton.tsx | 1 - src/components/EngineStream.tsx | 9 +-- src/components/ModelStateIndicator.tsx | 5 +- .../ModelingSidebar/ModelingSidebar.tsx | 11 ++-- src/lang/KclSingleton.ts | 4 +- src/lang/std/engineConnection.ts | 31 +++++++--- src/lib/settings/initialSettings.tsx | 36 +++++++----- src/lib/settings/settingsUtils.ts | 12 +++- src/wasm-lib/kcl/src/settings/types/mod.rs | 56 +++++++------------ 12 files changed, 99 insertions(+), 89 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4714428711..83ff379e0d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,7 +36,7 @@ export function App() { // Stream related refs and data const videoRef = useRef(null) const canvasRef = useRef(null) - const modelingSidebarRef = useRef(null) + const modelingSidebarRef = useRef(null) let [searchParams] = useSearchParams() const pool = searchParams.get('pool') diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx index 29c0d1bc74..dcb71bc0cf 100644 --- a/src/Toolbar.tsx +++ b/src/Toolbar.tsx @@ -23,10 +23,6 @@ import { import { isDesktop } from 'lib/isDesktop' import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { EngineConnectionStateType } from 'lang/std/engineConnection' -import useEngineStreamContext, { - EngineStreamState, - EngineStreamTransition, -} from 'hooks/useEngineStreamContext' export function Toolbar({ className = '', diff --git a/src/clientSideScene/CameraControls.ts b/src/clientSideScene/CameraControls.ts index f38f8f11ef..d550ef22eb 100644 --- a/src/clientSideScene/CameraControls.ts +++ b/src/clientSideScene/CameraControls.ts @@ -66,7 +66,9 @@ const lastCmdDelay = 50 export class CameraControls { engineCommandManager: EngineCommandManager - modelingSidebarRef: MutableRefObject + modelingSidebarRef: MutableRefObject = { + current: null, + } syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient' camera: PerspectiveCamera | OrthographicCamera target: Vector3 @@ -74,9 +76,10 @@ export class CameraControls { isDragging: boolean mouseDownPosition: Vector2 mouseNewPosition: Vector2 + cameraDragStartXY = new Vector2() old: | { - camera: PerspectiveCamera + camera: PerspectiveCamera | OrthographicCamera target: Vector3 } | undefined @@ -889,10 +892,9 @@ export class CameraControls { }) await this.centerModelRelativeToPanes() - this.cameraDragStartXY = { - x: 0, - y: 0, - } + this.cameraDragStartXY = new Vector2() + this.cameraDragStartXY.x = 0 + this.cameraDragStartXY.y = 0 } async restoreCameraPosition(): Promise { @@ -937,7 +939,6 @@ export class CameraControls { responses: true, requests: [ { - type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'zoom_to_fit', @@ -946,7 +947,6 @@ export class CameraControls { }, }, { - type: 'modeling_cmd_req', cmd: { type: 'camera_drag_start', interaction: 'pan', @@ -955,7 +955,6 @@ export class CameraControls { cmd_id: uuidv4(), }, { - type: 'modeling_cmd_req', cmd: { type: 'camera_drag_move', interaction: 'pan', diff --git a/src/components/CommandBarOpenButton.tsx b/src/components/CommandBarOpenButton.tsx index e2aceffe92..9a210dd25b 100644 --- a/src/components/CommandBarOpenButton.tsx +++ b/src/components/CommandBarOpenButton.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react' import { useCommandsContext } from 'hooks/useCommandsContext' import usePlatform from 'hooks/usePlatform' import { hotkeyDisplay } from 'lib/hotkeyWrapper' diff --git a/src/components/EngineStream.tsx b/src/components/EngineStream.tsx index a878b2523e..48987c82c6 100644 --- a/src/components/EngineStream.tsx +++ b/src/components/EngineStream.tsx @@ -32,11 +32,8 @@ export const EngineStream = () => { showScaleGrid: settings.context.modeling.showScaleGrid.current, } - const { - state: modelingMachineState, - send: modelingMachineActorSend, - context: modelingMachineActorContext, - } = useModelingContext() + const { state: modelingMachineState, send: modelingMachineActorSend } = + useModelingContext() const engineStreamActor = useEngineStreamContext.useActorRef() const engineStreamState = engineStreamActor.getSnapshot() @@ -169,7 +166,7 @@ export const EngineStream = () => { }, [streamIdleMode]) useEffect(() => { - let frameId = undefined + let frameId: ReturnType = 0 const frameLoop = () => { // Do not pause if the user is in the middle of an operation if (!modelingMachineState.matches('idle')) { diff --git a/src/components/ModelStateIndicator.tsx b/src/components/ModelStateIndicator.tsx index ba031ddcee..4609f7280a 100644 --- a/src/components/ModelStateIndicator.tsx +++ b/src/components/ModelStateIndicator.tsx @@ -4,6 +4,7 @@ import { CustomIcon } from './CustomIcon' import useEngineStreamContext, { EngineStreamState, } from 'hooks/useEngineStreamContext' +import { CommandLogType } from 'lang/std/engineConnection' export const ModelStateIndicator = () => { const [commands] = useEngineCommands() @@ -15,10 +16,10 @@ export const ModelStateIndicator = () => { const lastCommandType = commands[commands.length - 1]?.type useEffect(() => { - if (lastCommandType === 'set_default_system_properties') { + if (lastCommandType === CommandLogType.SetDefaultSystemProperties) { setIsDone(false) } - if (lastCommandType === 'execution-done') { + if (lastCommandType === CommandLogType.ExecutionDone) { setIsDone(true) } }, [lastCommandType]) diff --git a/src/components/ModelingSidebar/ModelingSidebar.tsx b/src/components/ModelingSidebar/ModelingSidebar.tsx index 5253ed92c9..3fd34e5567 100644 --- a/src/components/ModelingSidebar/ModelingSidebar.tsx +++ b/src/components/ModelingSidebar/ModelingSidebar.tsx @@ -21,6 +21,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext' import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { useKclContext } from 'lang/KclProvider' import { machineManager } from 'lib/machineManager' +import { sceneInfra } from 'lib/singletons' interface ModelingSidebarProps { paneOpacity: '' | 'opacity-20' | 'opacity-40' @@ -36,10 +37,10 @@ function getPlatformString(): 'web' | 'desktop' { return isDesktop() ? 'desktop' : 'web' } -export const ModelingSidebar = forwardRef(function ModelingSidebar( - { paneOpacity }: ModelingSidebarProps, - ref -) { +export const ModelingSidebar = forwardRef< + HTMLUListElement | null, + ModelingSidebarProps +>(function ModelingSidebar({ paneOpacity }, ref) { const { commandBarSend } = useCommandsContext() const kclContext = useKclContext() const { settings } = useSettingsAuthContext() @@ -165,7 +166,7 @@ export const ModelingSidebar = forwardRef(function ModelingSidebar( useEffect(() => { // Don't send camera adjustment commands after 1 pane is open. It // won't make any difference. - if (context.store?.openPanes > 1) return + if (context.store?.openPanes.length > 1) return void sceneInfra.camControls.centerModelRelativeToPanes() }, [context.store?.openPanes]) diff --git a/src/lang/KclSingleton.ts b/src/lang/KclSingleton.ts index 1eb3f64665..70b5ac423d 100644 --- a/src/lang/KclSingleton.ts +++ b/src/lang/KclSingleton.ts @@ -2,7 +2,7 @@ import { executeAst, lintAst } from 'lang/langHelpers' import { Selections } from 'lib/selections' import { KCLError, kclErrorsToDiagnostics } from './errors' import { uuidv4 } from 'lib/utils' -import { EngineCommandManager } from './std/engineConnection' +import { EngineCommandManager, CommandLogType } from './std/engineConnection' import { err } from 'lib/trap' import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants' @@ -292,7 +292,7 @@ export class KclManager { this.ast = { ...ast } this._executeCallback() this.engineCommandManager.addCommandLog({ - type: 'execution-done', + type: CommandLogType.ExecutionDone, data: null, }) diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts index 7ae72e6db9..7c97cf6bce 100644 --- a/src/lang/std/engineConnection.ts +++ b/src/lang/std/engineConnection.ts @@ -1277,27 +1277,40 @@ export interface Subscription { ) => void } +export enum CommandLogType { + SendModeling = 'send-modeling', + SendScene = 'send-scene', + ReceiveReliable = 'receive-reliable', + ExecutionDone = 'execution-done', + ExportDone = 'export-done', + SetDefaultSystemProperties = 'set_default_system_properties', +} + export type CommandLog = | { - type: 'send-modeling' + type: CommandLogType.SendModeling data: EngineCommand } | { - type: 'send-scene' + type: CommandLogType.SendScene data: EngineCommand } | { - type: 'receive-reliable' + type: CommandLogType.ReceiveReliable data: OkWebSocketResponseData id: string cmd_type?: string } | { - type: 'execution-done' + type: CommandLogType.ExecutionDone + data: null + } + | { + type: CommandLogType.ExportDone data: null } | { - type: 'export-done' + type: CommandLogType.SetDefaultSystemProperties data: null } @@ -1697,7 +1710,7 @@ export class EngineCommandManager extends EventTarget { message.request_id ) { this.addCommandLog({ - type: 'receive-reliable', + type: CommandLogType.ReceiveReliable, data: message.resp, id: message?.request_id || '', cmd_type: pending?.command?.cmd?.type, @@ -1731,7 +1744,7 @@ export class EngineCommandManager extends EventTarget { if (!command) return if (command.type === 'modeling_cmd_req') this.addCommandLog({ - type: 'receive-reliable', + type: CommandLogType.ReceiveReliable, data: { type: 'modeling', data: { @@ -1939,7 +1952,7 @@ export class EngineCommandManager extends EventTarget { ) { // highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy this.addCommandLog({ - type: 'send-scene', + type: CommandLogType.SendScene, data: command, }) } @@ -1998,7 +2011,7 @@ export class EngineCommandManager extends EventTarget { toastId, resolve: (passThrough) => { this.addCommandLog({ - type: 'export-done', + type: CommandLogType.ExportDone, data: null, }) resolve(passThrough) diff --git a/src/lib/settings/initialSettings.tsx b/src/lib/settings/initialSettings.tsx index 05277d6444..44ebb1009f 100644 --- a/src/lib/settings/initialSettings.tsx +++ b/src/lib/settings/initialSettings.tsx @@ -170,20 +170,24 @@ export function createSettings() { /** * Stream resource saving behavior toggle */ - streamIdleMode: new Setting({ - defaultValue: null, + streamIdleMode: new Setting({ + defaultValue: undefined, description: 'Toggle stream idling, saving bandwidth and battery', validate: (v) => - v === null || - (typeof v === 'number' && Number(v) >= 1 * MS_IN_MINUTE && Number(v) <= 60 * MS_IN_MINUTE), + v === undefined || + (typeof v === 'number' && + v >= 1 * MS_IN_MINUTE && + v <= 60 * MS_IN_MINUTE), Component: ({ value, updateValue }) => (
- updateValue(!e.currentTarget.checked ? null : 5 * 1000 * 60) + updateValue( + !e.currentTarget.checked ? undefined : 5 * MS_IN_MINUTE + ) } className="block w-4 h-4" /> @@ -192,21 +196,27 @@ export function createSettings() {
updateValue(parseInt(e.currentTarget.value) * 1000 * 60)} - disabled={value === null} - value={value/MS_IN_MINUTE} + onChange={(e) => + updateValue(Number(e.currentTarget.value) * MS_IN_MINUTE) + } + disabled={value === undefined} + value={ + value !== null && value !== undefined + ? value / MS_IN_MINUTE + : 5 + } min={1} max={60} step={1} className="block flex-1" /> - {value !== null && ( + {value !== undefined && value !== null && (
- {value/MS_IN_MINUTE === 60 + {value / MS_IN_MINUTE === 60 ? '1 hour' - : value/MS_IN_MINUTE === 1 + : value / MS_IN_MINUTE === 1 ? '1 minute' - : value/MS_IN_MINUTE + ' minutes'} + : value / MS_IN_MINUTE + ' minutes'}
)}
diff --git a/src/lib/settings/settingsUtils.ts b/src/lib/settings/settingsUtils.ts index 54508a4e20..2d28cec54a 100644 --- a/src/lib/settings/settingsUtils.ts +++ b/src/lib/settings/settingsUtils.ts @@ -24,6 +24,10 @@ import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration import { BROWSER_PROJECT_NAME } from 'lib/constants' import { DeepPartial } from 'lib/types' +type OmitNull = T extends null ? undefined : T +const toUndefinedIfNull = (a: any): OmitNull => + a === null ? undefined : a + /** * Convert from a rust settings struct into the JS settings struct. * We do this because the JS settings type has all the fancy shit @@ -40,7 +44,9 @@ export function configurationToSettingsPayload( : undefined, onboardingStatus: configuration?.settings?.app?.onboarding_status, dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, - streamIdleMode: configuration?.settings?.app?.stream_idle_mode, + streamIdleMode: toUndefinedIfNull( + configuration?.settings?.app?.stream_idle_mode + ), projectDirectory: configuration?.settings?.project?.directory, enableSSAO: configuration?.settings?.modeling?.enable_ssao, }, @@ -78,7 +84,9 @@ export function projectConfigurationToSettingsPayload( : undefined, onboardingStatus: configuration?.settings?.app?.onboarding_status, dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, - streamIdleMode: configuration?.settings?.app?.stream_idle_mode, + streamIdleMode: toUndefinedIfNull( + configuration?.settings?.app?.stream_idle_mode + ), enableSSAO: configuration?.settings?.modeling?.enable_ssao, }, modeling: { diff --git a/src/wasm-lib/kcl/src/settings/types/mod.rs b/src/wasm-lib/kcl/src/settings/types/mod.rs index d722b2f300..e0dcd4f254 100644 --- a/src/wasm-lib/kcl/src/settings/types/mod.rs +++ b/src/wasm-lib/kcl/src/settings/types/mod.rs @@ -5,8 +5,7 @@ pub mod project; use anyhow::Result; use parse_display::{Display, FromStr}; use schemars::JsonSchema; -use serde::{de, Deserializer, Deserialize, Serialize}; -use std::fmt; +use serde::{Deserializer, Deserialize, Serialize}; use validator::{Validate, ValidateRange}; const DEFAULT_THEME_COLOR: f64 = 264.5; @@ -120,47 +119,34 @@ pub struct AppSettings { /// This setting only applies to the web app. And is temporary until we have Linux support. #[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")] pub dismiss_web_banner: bool, - /// When the user is idle, and this is true, the stream will be torn down. + /// When the user is idle, teardown the stream after some time. #[serde(default, deserialize_with = "deserialize_stream_idle_mode", alias = "streamIdleMode", skip_serializing_if = "is_default")] - stream_idle_mode: Option, + stream_idle_mode: Option, } -fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result, D::Error> -where D: Deserializer<'de>, +fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, { - struct StreamIdleMode; - - impl<'de> de::Visitor<'de> for StreamIdleMode - { - type Value = Option; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("boolean or integer") - } - - // In an older config, stream idle mode used to be a boolean (on/off) - // I'm willing to say almost no one used the option. - fn visit_bool(self, value: bool) -> Result, E> - where E: de::Error - { - if value { - Ok(Some(1000 * 60 * 5)) - } else { - Ok(None) - } - } + #[derive(Deserialize)] + #[serde(untagged)] + enum StreamIdleModeValue { + String(String), + Boolean(bool), + } - fn visit_i64(self, value: i64) -> Result, E> - where E: de::Error - { - Ok(Some(value.try_into().unwrap())) - } - } + const DEFAULT_TIMEOUT: u32 = 1000 * 60 * 5; - deserializer.deserialize_any(StreamIdleMode) + Ok(match StreamIdleModeValue::deserialize(deserializer) { + Ok(StreamIdleModeValue::String(value)) => Some(value.parse::().unwrap_or(DEFAULT_TIMEOUT)), + // The old type of this value. I'm willing to say no one used it but + // we can never guarantee it. + Ok(StreamIdleModeValue::Boolean(true)) => Some(DEFAULT_TIMEOUT), + Ok(StreamIdleModeValue::Boolean(false)) => None, + _ => None + }) } - #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)] #[ts(export)] #[serde(untagged)]