Type correct everything, make settings deserializer better, fmt tsc lint
This commit is contained in:
@ -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')
|
||||
|
||||
|
||||
@ -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 = '',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||
|
||||
@ -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')) {
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user