Lf94/pause improvements (#3032)
* Add stream idle mode as a setting (default is off) * Add pause icon
This commit is contained in:
@ -166,7 +166,7 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
store.videoElement?.pause()
|
store.videoElement?.pause()
|
||||||
kclManager.executeCode(true).then(() => {
|
kclManager.executeCode(true).then(() => {
|
||||||
if (engineCommandManager.engineConnection?.freezeFrame) return
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
store.videoElement?.play()
|
store.videoElement?.play()
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { DEV } from 'env'
|
|
||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||||
import { getNormalisedCoordinates } from '../lib/utils'
|
import { getNormalisedCoordinates } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
@ -11,6 +10,10 @@ import { btnName } from 'lib/cameraControls'
|
|||||||
import { sendSelectEventToEngine } from 'lib/selections'
|
import { sendSelectEventToEngine } from 'lib/selections'
|
||||||
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||||
import { useAppStream } from 'AppState'
|
import { useAppStream } from 'AppState'
|
||||||
|
import {
|
||||||
|
EngineConnectionStateType,
|
||||||
|
DisconnectingType,
|
||||||
|
} from 'lang/std/engineConnection'
|
||||||
|
|
||||||
export const Stream = () => {
|
export const Stream = () => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -20,15 +23,28 @@ export const Stream = () => {
|
|||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const { mediaStream } = useAppStream()
|
const { mediaStream } = useAppStream()
|
||||||
const { overallState } = useNetworkContext()
|
const { overallState, immediateState } = useNetworkContext()
|
||||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||||
|
const [isPaused, setIsPaused] = useState(false)
|
||||||
|
|
||||||
const IDLE = true
|
const IDLE = settings.context.app.streamIdleMode.current
|
||||||
|
|
||||||
const isNetworkOkay =
|
const isNetworkOkay =
|
||||||
overallState === NetworkHealthState.Ok ||
|
overallState === NetworkHealthState.Ok ||
|
||||||
overallState === NetworkHealthState.Weak
|
overallState === NetworkHealthState.Weak
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
immediateState.type === EngineConnectionStateType.Disconnecting &&
|
||||||
|
immediateState.value.type === DisconnectingType.Pause
|
||||||
|
) {
|
||||||
|
setIsPaused(true)
|
||||||
|
}
|
||||||
|
if (immediateState.type === EngineConnectionStateType.Connecting) {
|
||||||
|
setIsPaused(false)
|
||||||
|
}
|
||||||
|
}, [immediateState])
|
||||||
|
|
||||||
// Linux has a default behavior to paste text on middle mouse up
|
// Linux has a default behavior to paste text on middle mouse up
|
||||||
// This adds a listener to block that pasting if the click target
|
// This adds a listener to block that pasting if the click target
|
||||||
// is not a text input, so users can move in the 3D scene with
|
// is not a text input, so users can move in the 3D scene with
|
||||||
@ -65,14 +81,11 @@ export const Stream = () => {
|
|||||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||||
// Give video time to pause
|
// Give video time to pause
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
engineCommandManager.tearDown()
|
engineCommandManager.tearDown({ idleMode: true })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Teardown everything if we go hidden or reconnect
|
const onVisibilityChange = () => {
|
||||||
if (IDLE && DEV) {
|
|
||||||
if (globalThis?.window?.document) {
|
|
||||||
globalThis.window.document.onvisibilitychange = () => {
|
|
||||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||||
clearTimeout(timeoutIdIdleA)
|
clearTimeout(timeoutIdIdleA)
|
||||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||||
@ -81,7 +94,13 @@ export const Stream = () => {
|
|||||||
engineCommandManager.engineConnection?.connect(true)
|
engineCommandManager.engineConnection?.connect(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Teardown everything if we go hidden or reconnect
|
||||||
|
if (IDLE) {
|
||||||
|
globalThis?.window?.document?.addEventListener(
|
||||||
|
'visibilitychange',
|
||||||
|
onVisibilityChange
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||||
@ -93,7 +112,7 @@ export const Stream = () => {
|
|||||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IDLE && DEV) {
|
if (IDLE) {
|
||||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||||
@ -101,7 +120,7 @@ export const Stream = () => {
|
|||||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IDLE && DEV) {
|
if (IDLE) {
|
||||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +128,14 @@ export const Stream = () => {
|
|||||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||||
capture: true,
|
capture: true,
|
||||||
})
|
})
|
||||||
if (IDLE && DEV) {
|
if (IDLE) {
|
||||||
|
clearTimeout(timeoutIdIdleA)
|
||||||
|
clearTimeout(timeoutIdIdleB)
|
||||||
|
|
||||||
|
globalThis?.window?.document?.removeEventListener(
|
||||||
|
'visibilitychange',
|
||||||
|
onVisibilityChange
|
||||||
|
)
|
||||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||||
globalThis?.window?.document?.removeEventListener(
|
globalThis?.window?.document?.removeEventListener(
|
||||||
'mousemove',
|
'mousemove',
|
||||||
@ -126,7 +152,7 @@ export const Stream = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [IDLE])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsFirstRender(kclManager.isFirstRender)
|
setIsFirstRender(kclManager.isFirstRender)
|
||||||
@ -249,6 +275,32 @@ export const Stream = () => {
|
|||||||
<ClientSideScene
|
<ClientSideScene
|
||||||
cameraControls={settings.context.modeling.mouseControls.current}
|
cameraControls={settings.context.modeling.mouseControls.current}
|
||||||
/>
|
/>
|
||||||
|
{isPaused && (
|
||||||
|
<div className="text-center absolute inset-0">
|
||||||
|
<div
|
||||||
|
className="flex flex-col items-center justify-center h-screen"
|
||||||
|
data-testid="paused"
|
||||||
|
>
|
||||||
|
<div className="border-primary border p-2 rounded-sm">
|
||||||
|
<svg
|
||||||
|
width="8"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 8 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M2 12V0H0V12H2ZM8 12V0H6V12H8Z"
|
||||||
|
fill="var(--primary)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="text-base mt-2 text-primary bold">Paused</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{(!isNetworkOkay || isLoading || isFirstRender) && !isFreezeFrame && (
|
{(!isNetworkOkay || isLoading || isFirstRender) && !isFreezeFrame && (
|
||||||
<div className="text-center absolute inset-0">
|
<div className="text-center absolute inset-0">
|
||||||
<Loading>
|
<Loading>
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { createContext, useContext } from 'react'
|
import { createContext, useContext } from 'react'
|
||||||
import {
|
import {
|
||||||
ConnectingTypeGroup,
|
ConnectingTypeGroup,
|
||||||
|
EngineConnectionStateType,
|
||||||
|
EngineConnectionState,
|
||||||
initialConnectingTypeGroupState,
|
initialConnectingTypeGroupState,
|
||||||
} from '../lang/std/engineConnection'
|
} from '../lang/std/engineConnection'
|
||||||
import { NetworkStatus, NetworkHealthState } from './useNetworkStatus'
|
import { NetworkStatus, NetworkHealthState } from './useNetworkStatus'
|
||||||
|
|
||||||
export const NetworkContext = createContext<NetworkStatus>({
|
export const NetworkContext = createContext<NetworkStatus>({
|
||||||
|
immediateState: {
|
||||||
|
type: EngineConnectionStateType.Disconnected,
|
||||||
|
} as EngineConnectionState,
|
||||||
hasIssues: undefined,
|
hasIssues: undefined,
|
||||||
overallState: NetworkHealthState.Disconnected,
|
overallState: NetworkHealthState.Disconnected,
|
||||||
internetConnected: true,
|
internetConnected: true,
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
EngineCommandManagerEvents,
|
EngineCommandManagerEvents,
|
||||||
EngineConnectionEvents,
|
EngineConnectionEvents,
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
|
EngineConnectionState,
|
||||||
ErrorType,
|
ErrorType,
|
||||||
initialConnectingTypeGroupState,
|
initialConnectingTypeGroupState,
|
||||||
} from '../lang/std/engineConnection'
|
} from '../lang/std/engineConnection'
|
||||||
@ -19,6 +20,7 @@ export enum NetworkHealthState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkStatus {
|
export interface NetworkStatus {
|
||||||
|
immediateState: EngineConnectionState
|
||||||
hasIssues: boolean | undefined
|
hasIssues: boolean | undefined
|
||||||
overallState: NetworkHealthState
|
overallState: NetworkHealthState
|
||||||
internetConnected: boolean
|
internetConnected: boolean
|
||||||
@ -33,6 +35,9 @@ export interface NetworkStatus {
|
|||||||
// Must be called from one place in the application.
|
// Must be called from one place in the application.
|
||||||
// We've chosen the <Router /> component for this.
|
// We've chosen the <Router /> component for this.
|
||||||
export function useNetworkStatus() {
|
export function useNetworkStatus() {
|
||||||
|
const [immediateState, setImmediateState] = useState<EngineConnectionState>({
|
||||||
|
type: EngineConnectionStateType.Disconnected,
|
||||||
|
})
|
||||||
const [steps, setSteps] = useState(
|
const [steps, setSteps] = useState(
|
||||||
structuredClone(initialConnectingTypeGroupState)
|
structuredClone(initialConnectingTypeGroupState)
|
||||||
)
|
)
|
||||||
@ -126,6 +131,7 @@ export function useNetworkStatus() {
|
|||||||
const onConnectionStateChange = ({
|
const onConnectionStateChange = ({
|
||||||
detail: engineConnectionState,
|
detail: engineConnectionState,
|
||||||
}: CustomEvent) => {
|
}: CustomEvent) => {
|
||||||
|
setImmediateState(engineConnectionState)
|
||||||
setSteps((steps) => {
|
setSteps((steps) => {
|
||||||
let nextSteps = structuredClone(steps)
|
let nextSteps = structuredClone(steps)
|
||||||
|
|
||||||
@ -215,6 +221,7 @@ export function useNetworkStatus() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
immediateState,
|
||||||
hasIssues,
|
hasIssues,
|
||||||
overallState,
|
overallState,
|
||||||
internetConnected,
|
internetConnected,
|
||||||
|
@ -143,6 +143,7 @@ export enum DisconnectingType {
|
|||||||
Error = 'error',
|
Error = 'error',
|
||||||
Timeout = 'timeout',
|
Timeout = 'timeout',
|
||||||
Quit = 'quit',
|
Quit = 'quit',
|
||||||
|
Pause = 'pause',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorted by severity
|
// Sorted by severity
|
||||||
@ -200,6 +201,7 @@ export type DisconnectingValue =
|
|||||||
| State<DisconnectingType.Error, ErrorType>
|
| State<DisconnectingType.Error, ErrorType>
|
||||||
| State<DisconnectingType.Timeout, void>
|
| State<DisconnectingType.Timeout, void>
|
||||||
| State<DisconnectingType.Quit, void>
|
| State<DisconnectingType.Quit, void>
|
||||||
|
| State<DisconnectingType.Pause, void>
|
||||||
|
|
||||||
// These are ordered by the expected sequence.
|
// These are ordered by the expected sequence.
|
||||||
export enum ConnectingType {
|
export enum ConnectingType {
|
||||||
@ -300,7 +302,7 @@ class EngineConnection extends EventTarget {
|
|||||||
pc?: RTCPeerConnection
|
pc?: RTCPeerConnection
|
||||||
unreliableDataChannel?: RTCDataChannel
|
unreliableDataChannel?: RTCDataChannel
|
||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
freezeFrame: boolean = false
|
idleMode: boolean = false
|
||||||
|
|
||||||
onIceCandidate = function (
|
onIceCandidate = function (
|
||||||
this: RTCPeerConnection,
|
this: RTCPeerConnection,
|
||||||
@ -391,10 +393,10 @@ class EngineConnection extends EventTarget {
|
|||||||
this.pingPongSpan = { ping: undefined, pong: undefined }
|
this.pingPongSpan = { ping: undefined, pong: undefined }
|
||||||
|
|
||||||
// Without an interval ping, our connection will timeout.
|
// Without an interval ping, our connection will timeout.
|
||||||
// If this.freezeFrame is true we skip this logic so only reconnect
|
// If this.idleMode is true we skip this logic so only reconnect
|
||||||
// happens on mouse move
|
// happens on mouse move
|
||||||
this.pingIntervalId = setInterval(() => {
|
this.pingIntervalId = setInterval(() => {
|
||||||
if (this.freezeFrame) return
|
if (this.idleMode) return
|
||||||
|
|
||||||
switch (this.state.type as EngineConnectionStateType) {
|
switch (this.state.type as EngineConnectionStateType) {
|
||||||
case EngineConnectionStateType.ConnectionEstablished:
|
case EngineConnectionStateType.ConnectionEstablished:
|
||||||
@ -456,8 +458,8 @@ class EngineConnection extends EventTarget {
|
|||||||
return this.state.type === EngineConnectionStateType.ConnectionEstablished
|
return this.state.type === EngineConnectionStateType.ConnectionEstablished
|
||||||
}
|
}
|
||||||
|
|
||||||
tearDown(opts?: { freeze: boolean }) {
|
tearDown(opts?: { idleMode: boolean }) {
|
||||||
this.freezeFrame = opts?.freeze ?? false
|
this.idleMode = opts?.idleMode ?? false
|
||||||
this.disconnectAll()
|
this.disconnectAll()
|
||||||
clearInterval(this.pingIntervalId)
|
clearInterval(this.pingIntervalId)
|
||||||
|
|
||||||
@ -497,9 +499,18 @@ class EngineConnection extends EventTarget {
|
|||||||
this.onNetworkStatusReady
|
this.onNetworkStatusReady
|
||||||
)
|
)
|
||||||
|
|
||||||
this.state = {
|
this.state = opts?.idleMode
|
||||||
|
? {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: { type: DisconnectingType.Quit },
|
value: {
|
||||||
|
type: DisconnectingType.Pause,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
|
value: {
|
||||||
|
type: DisconnectingType.Quit,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1099,8 +1110,6 @@ class EngineConnection extends EventTarget {
|
|||||||
this.unreliableDataChannel?.readyState === 'closed'
|
this.unreliableDataChannel?.readyState === 'closed'
|
||||||
if (allClosed) {
|
if (allClosed) {
|
||||||
// Do not notify the rest of the program that we have cut off anything.
|
// Do not notify the rest of the program that we have cut off anything.
|
||||||
if (this.freezeFrame) return
|
|
||||||
|
|
||||||
this.state = { type: EngineConnectionStateType.Disconnected }
|
this.state = { type: EngineConnectionStateType.Disconnected }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1738,7 +1747,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tearDown() {
|
tearDown(opts?: { idleMode: boolean }) {
|
||||||
if (this.engineConnection) {
|
if (this.engineConnection) {
|
||||||
this.engineConnection.removeEventListener(
|
this.engineConnection.removeEventListener(
|
||||||
EngineConnectionEvents.Opened,
|
EngineConnectionEvents.Opened,
|
||||||
@ -1757,7 +1766,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.onEngineConnectionNewTrack as EventListener
|
this.onEngineConnectionNewTrack as EventListener
|
||||||
)
|
)
|
||||||
|
|
||||||
this.engineConnection?.tearDown()
|
this.engineConnection?.tearDown(opts)
|
||||||
this.engineConnection = undefined
|
this.engineConnection = undefined
|
||||||
|
|
||||||
// Our window.tearDown assignment causes this case to happen which is
|
// Our window.tearDown assignment causes this case to happen which is
|
||||||
@ -1765,7 +1774,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} else if (this.engineCommandManager?.engineConnection) {
|
} else if (this.engineCommandManager?.engineConnection) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.engineCommandManager?.engineConnection?.tearDown()
|
this.engineCommandManager?.engineConnection?.tearDown(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async startNewSession() {
|
async startNewSession() {
|
||||||
|
@ -163,6 +163,17 @@ export function createSettings() {
|
|||||||
validate: (v) => typeof v === 'boolean',
|
validate: (v) => typeof v === 'boolean',
|
||||||
hideOnPlatform: 'both', //for now
|
hideOnPlatform: 'both', //for now
|
||||||
}),
|
}),
|
||||||
|
/**
|
||||||
|
* Stream resource saving behavior toggle
|
||||||
|
*/
|
||||||
|
streamIdleMode: new Setting<boolean>({
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Toggle stream idling, saving bandwidth and battery',
|
||||||
|
validate: (v) => typeof v === 'boolean',
|
||||||
|
commandConfig: {
|
||||||
|
inputType: 'boolean',
|
||||||
|
},
|
||||||
|
}),
|
||||||
onboardingStatus: new Setting<string>({
|
onboardingStatus: new Setting<string>({
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
validate: (v) => typeof v === 'string',
|
validate: (v) => typeof v === 'string',
|
||||||
|
@ -38,6 +38,7 @@ 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,
|
||||||
projectDirectory: configuration?.settings?.project?.directory,
|
projectDirectory: configuration?.settings?.project?.directory,
|
||||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||||
},
|
},
|
||||||
@ -75,6 +76,7 @@ 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,
|
||||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
|
@ -1081,7 +1081,7 @@ export const modelingMachine = createMachine(
|
|||||||
type: 'start_path',
|
type: 'start_path',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!engineCommandManager.engineConnection?.freezeFrame) {
|
if (!engineCommandManager.engineConnection?.idleMode) {
|
||||||
store.videoElement?.play()
|
store.videoElement?.play()
|
||||||
}
|
}
|
||||||
if (updatedAst?.selections) {
|
if (updatedAst?.selections) {
|
||||||
|
@ -63,6 +63,12 @@ export const settingsMachine = createMachine(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'set.app.streamIdleMode': {
|
||||||
|
target: 'persisting settings',
|
||||||
|
|
||||||
|
actions: ['setSettingAtLevel', 'toastSuccess'],
|
||||||
|
},
|
||||||
|
|
||||||
'set.modeling.highlightEdges': {
|
'set.modeling.highlightEdges': {
|
||||||
target: 'persisting settings',
|
target: 'persisting settings',
|
||||||
|
|
||||||
|
@ -234,6 +234,9 @@ 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.
|
||||||
|
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
||||||
|
stream_idle_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
||||||
@ -651,6 +654,7 @@ textWrapping = true
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
stream_idle_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::In,
|
base_unit: UnitLength::In,
|
||||||
@ -710,6 +714,7 @@ includeSettings = false
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
stream_idle_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
@ -774,6 +779,7 @@ defaultProjectName = "projects-$nnn"
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
stream_idle_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
@ -850,6 +856,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
stream_idle_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Mm,
|
base_unit: UnitLength::Mm,
|
||||||
|
@ -123,6 +123,7 @@ includeSettings = false
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
stream_idle_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
|
Reference in New Issue
Block a user