This commit is contained in:
lee-at-zoo-corp
2025-03-21 16:10:49 -04:00
parent 2cca1376e2
commit 4c9851efbf
7 changed files with 124 additions and 97 deletions

View File

@ -981,17 +981,21 @@ export class CameraControls {
} }
async saveRemoteCameraState() { async saveRemoteCameraState() {
const cameraViewStateResponse = await this.engineCommandManager.sendSceneCommand({ const cameraViewStateResponse =
type: 'modeling_cmd_req', await this.engineCommandManager.sendSceneCommand({
cmd_id: uuidv4(), type: 'modeling_cmd_req',
cmd: { type: 'default_camera_get_view' }, cmd_id: uuidv4(),
}) cmd: { type: 'default_camera_get_view' },
})
if (!cameraViewStateResponse) return if (!cameraViewStateResponse) return
if ('resp' in cameraViewStateResponse if (
&& 'modeling_response' in cameraViewStateResponse.resp.data 'resp' in cameraViewStateResponse &&
&& 'data' in cameraViewStateResponse.resp.data.modeling_response 'modeling_response' in cameraViewStateResponse.resp.data &&
&& 'view' in cameraViewStateResponse.resp.data.modeling_response.data) { 'data' in cameraViewStateResponse.resp.data.modeling_response &&
this.oldCameraState = cameraViewStateResponse.resp.data.modeling_response.data.view 'view' in cameraViewStateResponse.resp.data.modeling_response.data
) {
this.oldCameraState =
cameraViewStateResponse.resp.data.modeling_response.data.view
} }
} }

View File

@ -3,11 +3,7 @@ import { useEngineCommands } from '@src/components/EngineCommands'
import { Spinner } from '@src/components/Spinner' import { Spinner } from '@src/components/Spinner'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { import { faPlay, faPause, faSpinner } from '@fortawesome/free-solid-svg-icons'
faPlay,
faPause,
faSpinner,
} from '@fortawesome/free-solid-svg-icons'
export const ModelStateIndicator = () => { export const ModelStateIndicator = () => {
const engineStreamState = useSelector(engineStreamActor, (state) => state) const engineStreamState = useSelector(engineStreamActor, (state) => state)
@ -18,30 +14,34 @@ export const ModelStateIndicator = () => {
if (engineStreamState.value === EngineStreamState.Paused) { if (engineStreamState.value === EngineStreamState.Paused) {
className += 'text-secondary' className += 'text-secondary'
icon = <FontAwesomeIcon icon = (
data-testid={dataTestId + '-paused'} <FontAwesomeIcon
icon={faPause} data-testid={dataTestId + '-paused'}
width="20" icon={faPause}
height="20" width="20"
/> height="20"
/>
)
} else if (engineStreamState.value === EngineStreamState.Playing) { } else if (engineStreamState.value === EngineStreamState.Playing) {
className += 'text-secondary' className += 'text-secondary'
icon = ( icon = (
<FontAwesomeIcon <FontAwesomeIcon
data-testid={dataTestId + '-execution-done'} data-testid={dataTestId + '-execution-done'}
icon={faPlay} icon={faPlay}
width="20" width="20"
height="20" height="20"
/> />
) )
} else { } else {
className += 'text-secondary' className += 'text-secondary'
icon = <FontAwesomeIcon icon = (
data-testid={dataTestId + '-resuming'} <FontAwesomeIcon
icon={faSpinner} data-testid={dataTestId + '-resuming'}
width="20" icon={faSpinner}
height="20" width="20"
/> height="20"
/>
)
} }
return ( return (

View File

@ -86,7 +86,7 @@ export const NetworkHealthIndicator = () => {
error, error,
setHasCopied, setHasCopied,
hasCopied, hasCopied,
ping ping,
} = useNetworkContext() } = useNetworkContext()
return ( return (
@ -130,10 +130,12 @@ export const NetworkHealthIndicator = () => {
{NETWORK_HEALTH_TEXT[overallState]} {NETWORK_HEALTH_TEXT[overallState]}
</p> </p>
</div> </div>
<div <div className={`flex items-center justify-between p-2 rounded-t-sm`}>
className={`flex items-center justify-between p-2 rounded-t-sm`} <h2
> className={`text-xs font-sans font-normal ${overallConnectionStateColor[overallState].icon}`}
<h2 className={`text-xs font-sans font-normal ${overallConnectionStateColor[overallState].icon}`}>Ping</h2> >
Ping
</h2>
<p <p
data-testid="network" data-testid="network"
className={`font-bold text-xs uppercase px-2 py-1 rounded-sm ${overallConnectionStateColor[overallState].icon}`} className={`font-bold text-xs uppercase px-2 py-1 rounded-sm ${overallConnectionStateColor[overallState].icon}`}

View File

@ -71,7 +71,7 @@ export function useNetworkStatus() {
? NetworkHealthState.Disconnected ? NetworkHealthState.Disconnected
: hasIssues || hasIssues === undefined : hasIssues || hasIssues === undefined
? NetworkHealthState.Issue ? NetworkHealthState.Issue
: ping > (16.6*3) // we consider ping longer than 3 frames as weak : ping > 16.6 * 3 // we consider ping longer than 3 frames as weak
? NetworkHealthState.Weak ? NetworkHealthState.Weak
: NetworkHealthState.Ok : NetworkHealthState.Ok
) )

View File

@ -332,15 +332,19 @@ class EngineConnection extends EventTarget {
this.pingIntervalId = setInterval(() => { this.pingIntervalId = setInterval(() => {
// Only start a new ping when the other is fulfilled. // Only start a new ping when the other is fulfilled.
if (this.pingPongSpan.ping) { return } if (this.pingPongSpan.ping) {
return
}
// Don't start pinging until we're connected. // Don't start pinging until we're connected.
if (this.state.type !== EngineConnectionStateType.ConnectionEstablished) { return } if (this.state.type !== EngineConnectionStateType.ConnectionEstablished) {
return
}
this.send({ type: 'ping' }) this.send({ type: 'ping' })
this.pingPongSpan = { this.pingPongSpan = {
ping: new Date(), ping: new Date(),
pong: undefined pong: undefined,
} }
}, pingIntervalMs) }, pingIntervalMs)
@ -1000,7 +1004,10 @@ class EngineConnection extends EventTarget {
this.pingPongSpan.pong = new Date() this.pingPongSpan.pong = new Date()
this.dispatchEvent( this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.PingPongChanged, { new CustomEvent(EngineConnectionEvents.PingPongChanged, {
detail: Math.min(999, Math.floor(this.pingPongSpan.pong - this.pingPongSpan.ping)), detail: Math.min(
999,
Math.floor(this.pingPongSpan.pong - this.pingPongSpan.ping)
),
}) })
) )
// Clear the initial ping so our interval ping loop can fire again // Clear the initial ping so our interval ping loop can fire again

View File

@ -219,67 +219,83 @@ export function createSettings() {
(typeof v === 'number' && (typeof v === 'number' &&
v >= 1 * MS_IN_MINUTE && v >= 1 * MS_IN_MINUTE &&
v <= 60 * MS_IN_MINUTE), v <= 60 * MS_IN_MINUTE),
Component: ({ value: settingValueInStorage, updateValue: writeSettingValueToStorage }) => { Component: ({
value: settingValueInStorage,
updateValue: writeSettingValueToStorage,
}) => {
const [timeoutId, setTimeoutId] = useState(undefined) const [timeoutId, setTimeoutId] = useState(undefined)
const [preview, setPreview] = useState(settingValueInStorage === undefined ? settingValueInStorage : (settingValueInStorage / MS_IN_MINUTE)) const [preview, setPreview] = useState(
const onChangeRange = (e: React.SyntheticEvent) => setPreview(e.currentTarget.value) settingValueInStorage === undefined
? settingValueInStorage
: settingValueInStorage / MS_IN_MINUTE
)
const onChangeRange = (e: React.SyntheticEvent) =>
setPreview(e.currentTarget.value)
const onSaveRange = (e: React.SyntheticEvent) => { const onSaveRange = (e: React.SyntheticEvent) => {
if (preview === undefined) return if (preview === undefined) return
writeSettingValueToStorage(Number(e.currentTarget.value) * MS_IN_MINUTE) writeSettingValueToStorage(
Number(e.currentTarget.value) * MS_IN_MINUTE
)
} }
return ( return (
<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={settingValueInStorage !== undefined} checked={settingValueInStorage !== undefined}
onChange={(event) => { onChange={(event) => {
if (timeoutId) { return } if (timeoutId) {
return
}
const isChecked = event.currentTarget.checked const isChecked = event.currentTarget.checked
clearTimeout(timeoutId) clearTimeout(timeoutId)
setTimeoutId(setTimeout(() => { setTimeoutId(
const requested = !isChecked ? undefined : 5 setTimeout(() => {
setPreview(requested) const requested = !isChecked ? undefined : 5
writeSettingValueToStorage(requested === undefined ? undefined : Number(requested) * MS_IN_MINUTE) setPreview(requested)
setTimeoutId(undefined) writeSettingValueToStorage(
}, 100)) requested === undefined
? undefined
: Number(requested) * MS_IN_MINUTE
)
setTimeoutId(undefined)
}, 100)
)
}}
className="block w-4 h-4"
/>
<div></div>
</div>
<div className="flex flex-col grow">
<input
type="range"
onChange={onChangeRange}
onMouseUp={onSaveRange}
onKeyUp={onSaveRange}
onPointerUp={onSaveRange}
disabled={preview === undefined}
value={
preview !== null && preview !== undefined ? preview : 5
} }
} min={1}
className="block w-4 h-4" max={60}
/> step={1}
<div></div> className="block flex-1"
/>
{preview !== undefined && preview !== null && (
<div>
{preview / MS_IN_MINUTE === 60
? '1 hour'
: preview / MS_IN_MINUTE === 1
? '1 minute'
: preview + ' minutes'}
</div>
)}
</div>
</div> </div>
<div className="flex flex-col grow"> )
<input },
type="range"
onChange={onChangeRange}
onMouseUp={onSaveRange}
onKeyUp={onSaveRange}
onPointerUp={onSaveRange}
disabled={preview === undefined}
value={
preview !== null && preview !== undefined
? preview
: 5
}
min={1}
max={60}
step={1}
className="block flex-1"
/>
{preview !== undefined && preview !== null && (
<div>
{preview / MS_IN_MINUTE === 60
? '1 hour'
: preview / MS_IN_MINUTE === 1
? '1 minute'
: preview + ' minutes'}
</div>
)}
</div>
</div>
)},
}), }),
allowOrbitInSketchMode: new Setting<boolean>({ allowOrbitInSketchMode: new Setting<boolean>({
defaultValue: false, defaultValue: false,

View File

@ -118,9 +118,7 @@ export const engineStreamMachine = setup({
// will not reexecute. // will not reexecute.
// When calling cache before _any_ executions it errors, but non-fatal. // When calling cache before _any_ executions it errors, but non-fatal.
await rustContext await rustContext
.clearSceneAndBustCache( .clearSceneAndBustCache({ settings: await jsAppSettings() })
{ settings: await jsAppSettings() },
)
.catch(console.warn) .catch(console.warn)
await kclManager.executeCode(params.zoomToFit) await kclManager.executeCode(params.zoomToFit)