Type correct everything, make settings deserializer better, fmt tsc lint

This commit is contained in:
49lf
2024-09-20 13:33:25 -04:00
parent 351df2f306
commit 0a3a8afbbd
12 changed files with 99 additions and 89 deletions

View File

@ -37,7 +37,7 @@ export function App() {
// Stream related refs and data // Stream related refs and data
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null) const canvasRef = useRef<HTMLCanvasElement>(null)
const modelingSidebarRef = useRef<HTMLDivElement>(null) const modelingSidebarRef = useRef<HTMLUListElement>(null)
let [searchParams] = useSearchParams() let [searchParams] = useSearchParams()
const pool = searchParams.get('pool') const pool = searchParams.get('pool')

View File

@ -23,10 +23,6 @@ import {
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { EngineConnectionStateType } from 'lang/std/engineConnection' import { EngineConnectionStateType } from 'lang/std/engineConnection'
import useEngineStreamContext, {
EngineStreamState,
EngineStreamTransition,
} from 'hooks/useEngineStreamContext'
export function Toolbar({ export function Toolbar({
className = '', className = '',

View File

@ -88,7 +88,9 @@ class CameraRateLimiter {
export class CameraControls { export class CameraControls {
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
modelingSidebarRef: MutableRefObject<HTMLDivElement> modelingSidebarRef: MutableRefObject<HTMLUListElement | null> = {
current: null,
}
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient' syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
camera: PerspectiveCamera | OrthographicCamera camera: PerspectiveCamera | OrthographicCamera
target: Vector3 target: Vector3
@ -97,9 +99,10 @@ export class CameraControls {
wasDragging: boolean wasDragging: boolean
mouseDownPosition: Vector2 mouseDownPosition: Vector2
mouseNewPosition: Vector2 mouseNewPosition: Vector2
cameraDragStartXY = new Vector2()
old: old:
| { | {
camera: PerspectiveCamera camera: PerspectiveCamera | OrthographicCamera
target: Vector3 target: Vector3
} }
| undefined | undefined
@ -921,10 +924,9 @@ export class CameraControls {
await this.centerModelRelativeToPanes() await this.centerModelRelativeToPanes()
this.cameraDragStartXY = { this.cameraDragStartXY = new Vector2()
x: 0, this.cameraDragStartXY.x = 0
y: 0, this.cameraDragStartXY.y = 0
}
} }
async restoreCameraPosition(): Promise<void> { async restoreCameraPosition(): Promise<void> {
@ -969,7 +971,6 @@ export class CameraControls {
responses: true, responses: true,
requests: [ requests: [
{ {
type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: {
type: 'zoom_to_fit', type: 'zoom_to_fit',
@ -978,7 +979,6 @@ export class CameraControls {
}, },
}, },
{ {
type: 'modeling_cmd_req',
cmd: { cmd: {
type: 'camera_drag_start', type: 'camera_drag_start',
interaction: 'pan', interaction: 'pan',
@ -987,7 +987,6 @@ export class CameraControls {
cmd_id: uuidv4(), cmd_id: uuidv4(),
}, },
{ {
type: 'modeling_cmd_req',
cmd: { cmd: {
type: 'camera_drag_move', type: 'camera_drag_move',
interaction: 'pan', interaction: 'pan',

View File

@ -1,4 +1,3 @@
import { useEffect } from 'react'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { hotkeyDisplay } from 'lib/hotkeyWrapper' import { hotkeyDisplay } from 'lib/hotkeyWrapper'

View File

@ -32,11 +32,8 @@ export const EngineStream = () => {
showScaleGrid: settings.context.modeling.showScaleGrid.current, showScaleGrid: settings.context.modeling.showScaleGrid.current,
} }
const { const { state: modelingMachineState, send: modelingMachineActorSend } =
state: modelingMachineState, useModelingContext()
send: modelingMachineActorSend,
context: modelingMachineActorContext,
} = useModelingContext()
const engineStreamActor = useEngineStreamContext.useActorRef() const engineStreamActor = useEngineStreamContext.useActorRef()
const engineStreamState = engineStreamActor.getSnapshot() const engineStreamState = engineStreamActor.getSnapshot()
@ -169,7 +166,7 @@ export const EngineStream = () => {
}, [streamIdleMode]) }, [streamIdleMode])
useEffect(() => { useEffect(() => {
let frameId = undefined let frameId: ReturnType<typeof window.requestAnimationFrame> = 0
const frameLoop = () => { const frameLoop = () => {
// Do not pause if the user is in the middle of an operation // Do not pause if the user is in the middle of an operation
if (!modelingMachineState.matches('idle')) { if (!modelingMachineState.matches('idle')) {

View File

@ -4,6 +4,7 @@ import { CustomIcon } from './CustomIcon'
import useEngineStreamContext, { import useEngineStreamContext, {
EngineStreamState, EngineStreamState,
} from 'hooks/useEngineStreamContext' } from 'hooks/useEngineStreamContext'
import { CommandLogType } from 'lang/std/engineConnection'
export const ModelStateIndicator = () => { export const ModelStateIndicator = () => {
const [commands] = useEngineCommands() const [commands] = useEngineCommands()
@ -15,10 +16,10 @@ export const ModelStateIndicator = () => {
const lastCommandType = commands[commands.length - 1]?.type const lastCommandType = commands[commands.length - 1]?.type
useEffect(() => { useEffect(() => {
if (lastCommandType === 'set_default_system_properties') { if (lastCommandType === CommandLogType.SetDefaultSystemProperties) {
setIsDone(false) setIsDone(false)
} }
if (lastCommandType === 'execution-done') { if (lastCommandType === CommandLogType.ExecutionDone) {
setIsDone(true) setIsDone(true)
} }
}, [lastCommandType]) }, [lastCommandType])

View File

@ -23,6 +23,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { MachineManagerContext } from 'components/MachineManagerProvider'
import { sceneInfra } from 'lib/singletons'
interface ModelingSidebarProps { interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40' paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -38,10 +39,10 @@ function getPlatformString(): 'web' | 'desktop' {
return isDesktop() ? 'desktop' : 'web' return isDesktop() ? 'desktop' : 'web'
} }
export const ModelingSidebar = forwardRef(function ModelingSidebar( export const ModelingSidebar = forwardRef<
{ paneOpacity }: ModelingSidebarProps, HTMLUListElement | null,
ref ModelingSidebarProps
) { >(function ModelingSidebar({ paneOpacity }, ref) {
const machineManager = useContext(MachineManagerContext) const machineManager = useContext(MachineManagerContext)
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext() const kclContext = useKclContext()
@ -168,7 +169,7 @@ export const ModelingSidebar = forwardRef(function ModelingSidebar(
useEffect(() => { useEffect(() => {
// Don't send camera adjustment commands after 1 pane is open. It // Don't send camera adjustment commands after 1 pane is open. It
// won't make any difference. // won't make any difference.
if (context.store?.openPanes > 1) return if (context.store?.openPanes.length > 1) return
void sceneInfra.camControls.centerModelRelativeToPanes() void sceneInfra.camControls.centerModelRelativeToPanes()
}, [context.store?.openPanes]) }, [context.store?.openPanes])

View File

@ -2,7 +2,7 @@ import { executeAst, lintAst } from 'lang/langHelpers'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { KCLError, kclErrorsToDiagnostics } from './errors' import { KCLError, kclErrorsToDiagnostics } from './errors'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection' import { EngineCommandManager, CommandLogType } from './std/engineConnection'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants' import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
@ -314,7 +314,7 @@ export class KclManager {
this.ast = { ...ast } this.ast = { ...ast }
this._executeCallback() this._executeCallback()
this.engineCommandManager.addCommandLog({ this.engineCommandManager.addCommandLog({
type: 'execution-done', type: CommandLogType.ExecutionDone,
data: null, data: null,
}) })

View File

@ -1288,27 +1288,40 @@ export interface Subscription<T extends ModelTypes> {
) => void ) => 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 = export type CommandLog =
| { | {
type: 'send-modeling' type: CommandLogType.SendModeling
data: EngineCommand data: EngineCommand
} }
| { | {
type: 'send-scene' type: CommandLogType.SendScene
data: EngineCommand data: EngineCommand
} }
| { | {
type: 'receive-reliable' type: CommandLogType.ReceiveReliable
data: OkWebSocketResponseData data: OkWebSocketResponseData
id: string id: string
cmd_type?: string cmd_type?: string
} }
| { | {
type: 'execution-done' type: CommandLogType.ExecutionDone
data: null data: null
} }
| { | {
type: 'export-done' type: CommandLogType.ExportDone
data: null
}
| {
type: CommandLogType.SetDefaultSystemProperties
data: null data: null
} }
@ -1719,7 +1732,7 @@ export class EngineCommandManager extends EventTarget {
message.request_id message.request_id
) { ) {
this.addCommandLog({ this.addCommandLog({
type: 'receive-reliable', type: CommandLogType.ReceiveReliable,
data: message.resp, data: message.resp,
id: message?.request_id || '', id: message?.request_id || '',
cmd_type: pending?.command?.cmd?.type, cmd_type: pending?.command?.cmd?.type,
@ -1753,7 +1766,7 @@ export class EngineCommandManager extends EventTarget {
if (!command) return if (!command) return
if (command.type === 'modeling_cmd_req') if (command.type === 'modeling_cmd_req')
this.addCommandLog({ this.addCommandLog({
type: 'receive-reliable', type: CommandLogType.ReceiveReliable,
data: { data: {
type: 'modeling', type: 'modeling',
data: { data: {
@ -1965,7 +1978,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 // highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
this.addCommandLog({ this.addCommandLog({
type: 'send-scene', type: CommandLogType.SendScene,
data: command, data: command,
}) })
} }
@ -2024,7 +2037,7 @@ export class EngineCommandManager extends EventTarget {
toastId, toastId,
resolve: (passThrough) => { resolve: (passThrough) => {
this.addCommandLog({ this.addCommandLog({
type: 'export-done', type: CommandLogType.ExportDone,
data: null, data: null,
}) })
resolve(passThrough) resolve(passThrough)

View File

@ -183,20 +183,24 @@ export function createSettings() {
/** /**
* Stream resource saving behavior toggle * Stream resource saving behavior toggle
*/ */
streamIdleMode: new Setting<number | null>({ streamIdleMode: new Setting<number | undefined>({
defaultValue: null, defaultValue: undefined,
description: 'Toggle stream idling, saving bandwidth and battery', description: 'Toggle stream idling, saving bandwidth and battery',
validate: (v) => validate: (v) =>
v === null || v === undefined ||
(typeof v === 'number' && Number(v) >= 1 * MS_IN_MINUTE && Number(v) <= 60 * MS_IN_MINUTE), (typeof v === 'number' &&
v >= 1 * MS_IN_MINUTE &&
v <= 60 * MS_IN_MINUTE),
Component: ({ value, updateValue }) => ( Component: ({ value, updateValue }) => (
<div className="flex item-center gap-4 px-2 m-0 py-0"> <div className="flex item-center gap-4 px-2 m-0 py-0">
<div className="flex flex-col"> <div className="flex flex-col">
<input <input
type="checkbox" type="checkbox"
checked={value !== null} checked={value !== undefined}
onChange={(e) => onChange={(e) =>
updateValue(!e.currentTarget.checked ? null : 5 * 1000 * 60) updateValue(
!e.currentTarget.checked ? undefined : 5 * MS_IN_MINUTE
)
} }
className="block w-4 h-4" className="block w-4 h-4"
/> />
@ -205,15 +209,21 @@ export function createSettings() {
<div className="flex flex-col grow"> <div className="flex flex-col grow">
<input <input
type="range" type="range"
onChange={(e) => updateValue(parseInt(e.currentTarget.value) * 1000 * 60)} onChange={(e) =>
disabled={value === null} updateValue(Number(e.currentTarget.value) * MS_IN_MINUTE)
value={value/MS_IN_MINUTE} }
disabled={value === undefined}
value={
value !== null && value !== undefined
? value / MS_IN_MINUTE
: 5
}
min={1} min={1}
max={60} max={60}
step={1} step={1}
className="block flex-1" className="block flex-1"
/> />
{value !== null && ( {value !== undefined && value !== null && (
<div> <div>
{value / MS_IN_MINUTE === 60 {value / MS_IN_MINUTE === 60
? '1 hour' ? '1 hour'

View File

@ -24,6 +24,10 @@ import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration
import { BROWSER_PROJECT_NAME } from 'lib/constants' import { BROWSER_PROJECT_NAME } from 'lib/constants'
import { DeepPartial } from 'lib/types' import { DeepPartial } from 'lib/types'
type OmitNull<T> = T extends null ? undefined : T
const toUndefinedIfNull = (a: any): OmitNull<any> =>
a === null ? undefined : a
/** /**
* Convert from a rust settings struct into the JS settings struct. * Convert from a rust settings struct into the JS settings struct.
* We do this because the JS settings type has all the fancy shit * We do this because the JS settings type has all the fancy shit
@ -40,7 +44,9 @@ export function configurationToSettingsPayload(
: undefined, : undefined,
onboardingStatus: configuration?.settings?.app?.onboarding_status, onboardingStatus: configuration?.settings?.app?.onboarding_status,
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, 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, projectDirectory: configuration?.settings?.project?.directory,
enableSSAO: configuration?.settings?.modeling?.enable_ssao, enableSSAO: configuration?.settings?.modeling?.enable_ssao,
}, },
@ -79,7 +85,9 @@ export function projectConfigurationToSettingsPayload(
: undefined, : undefined,
onboardingStatus: configuration?.settings?.app?.onboarding_status, onboardingStatus: configuration?.settings?.app?.onboarding_status,
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, 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, enableSSAO: configuration?.settings?.modeling?.enable_ssao,
}, },
modeling: { modeling: {

View File

@ -5,8 +5,7 @@ pub mod project;
use anyhow::Result; use anyhow::Result;
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{de, Deserializer, Deserialize, Serialize}; use serde::{Deserializer, Deserialize, Serialize};
use std::fmt;
use validator::{Validate, ValidateRange}; use validator::{Validate, ValidateRange};
const DEFAULT_THEME_COLOR: f64 = 264.5; const DEFAULT_THEME_COLOR: f64 = 264.5;
@ -120,46 +119,33 @@ pub struct AppSettings {
/// This setting only applies to the web app. And is temporary until we have Linux support. /// 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")] #[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
pub dismiss_web_banner: bool, 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")] #[serde(default, deserialize_with = "deserialize_stream_idle_mode", alias = "streamIdleMode", skip_serializing_if = "is_default")]
stream_idle_mode: Option<u64>, stream_idle_mode: Option<u32>,
} }
fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error> fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where D: Deserializer<'de>, where
D: Deserializer<'de>,
{ {
struct StreamIdleMode; #[derive(Deserialize)]
#[serde(untagged)]
impl<'de> de::Visitor<'de> for StreamIdleMode enum StreamIdleModeValue {
{ String(String),
type Value = Option<u64>; Boolean(bool),
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) const DEFAULT_TIMEOUT: u32 = 1000 * 60 * 5;
// I'm willing to say almost no one used the option.
fn visit_bool<E>(self, value: bool) -> Result<Option<u64>, E>
where E: de::Error
{
if value {
Ok(Some(1000 * 60 * 5))
} else {
Ok(None)
}
}
fn visit_i64<E>(self, value: i64) -> Result<Option<u64>, E> Ok(match StreamIdleModeValue::deserialize(deserializer) {
where E: de::Error Ok(StreamIdleModeValue::String(value)) => Some(value.parse::<u32>().unwrap_or(DEFAULT_TIMEOUT)),
{ // The old type of this value. I'm willing to say no one used it but
Ok(Some(value.try_into().unwrap())) // we can never guarantee it.
Ok(StreamIdleModeValue::Boolean(true)) => Some(DEFAULT_TIMEOUT),
Ok(StreamIdleModeValue::Boolean(false)) => None,
_ => None
})
} }
}
deserializer.deserialize_any(StreamIdleMode)
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)] #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
#[ts(export)] #[ts(export)]