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
const videoRef = useRef<HTMLVideoElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const modelingSidebarRef = useRef<HTMLDivElement>(null)
const modelingSidebarRef = useRef<HTMLUListElement>(null)
let [searchParams] = useSearchParams()
const pool = searchParams.get('pool')

View File

@ -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 = '',

View File

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

View File

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

View File

@ -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<typeof window.requestAnimationFrame> = 0
const frameLoop = () => {
// Do not pause if the user is in the middle of an operation
if (!modelingMachineState.matches('idle')) {

View File

@ -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])

View File

@ -23,6 +23,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import { sceneInfra } from 'lib/singletons'
interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -38,10 +39,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 machineManager = useContext(MachineManagerContext)
const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext()
@ -168,7 +169,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])

View File

@ -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'
@ -314,7 +314,7 @@ export class KclManager {
this.ast = { ...ast }
this._executeCallback()
this.engineCommandManager.addCommandLog({
type: 'execution-done',
type: CommandLogType.ExecutionDone,
data: null,
})

View File

@ -1288,27 +1288,40 @@ export interface Subscription<T extends ModelTypes> {
) => 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: 'export-done'
type: CommandLogType.ExportDone
data: null
}
| {
type: CommandLogType.SetDefaultSystemProperties
data: null
}
@ -1719,7 +1732,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,
@ -1753,7 +1766,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: {
@ -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
this.addCommandLog({
type: 'send-scene',
type: CommandLogType.SendScene,
data: command,
})
}
@ -2024,7 +2037,7 @@ export class EngineCommandManager extends EventTarget {
toastId,
resolve: (passThrough) => {
this.addCommandLog({
type: 'export-done',
type: CommandLogType.ExportDone,
data: null,
})
resolve(passThrough)

View File

@ -183,20 +183,24 @@ export function createSettings() {
/**
* Stream resource saving behavior toggle
*/
streamIdleMode: new Setting<number | null>({
defaultValue: null,
streamIdleMode: new Setting<number | undefined>({
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 }) => (
<div className="flex item-center gap-4 px-2 m-0 py-0">
<div className="flex flex-col">
<input
type="checkbox"
checked={value !== null}
checked={value !== undefined}
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"
/>
@ -205,21 +209,27 @@ export function createSettings() {
<div className="flex flex-col grow">
<input
type="range"
onChange={(e) => 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 && (
<div>
{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'}
</div>
)}
</div>

View File

@ -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> = 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.
* 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,
},
@ -79,7 +85,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: {

View File

@ -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<u64>,
stream_idle_mode: Option<u32>,
}
fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where D: Deserializer<'de>,
fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where
D: Deserializer<'de>,
{
struct StreamIdleMode;
#[derive(Deserialize)]
#[serde(untagged)]
enum StreamIdleModeValue {
String(String),
Boolean(bool),
}
impl<'de> de::Visitor<'de> for StreamIdleMode
{
type Value = Option<u64>;
const DEFAULT_TIMEOUT: u32 = 1000 * 60 * 5;
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<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>
where E: de::Error
{
Ok(Some(value.try_into().unwrap()))
}
}
deserializer.deserialize_any(StreamIdleMode)
Ok(match StreamIdleModeValue::deserialize(deserializer) {
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
// 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)]