Stream handling / Stream idle mode v2; a ton of network related changes (ping; scene indicator -> stream indicator, stream resizing (even on pause)) (#5312)
* Add back stream idle mode
* Shut up codespell
* Correct serialization; only expose at user level
* cargo fmt
* tsc lint fmt
* Move engineStreamMachine as a global actor; tons of more work
* Fix up everything after bumping kittycad/lib
* Remove camera sync
* Use pause/play iconology
* Add back better ping indicator
* wip
* Fix streamIdleMode checkbox being wonky
* yarn fmt
* Massive extinction event for waitForExecutionDone; try to stop projects view switching from crashing
* Clear diagnostics when unmounting code editor!
* wip
* Rework initial root projects dir + deflake many projects tests
* More e2e fixes
* Deflake revolve some revolve tests
* Fix the rest of the mfing tests
* yarn fmt
* yarn lint
* yarn tsc
* Fix tsc after rebase
* wip
* less flaky point and click
* wip
* Fixup after rebase
* Fix more tests
* Fix 2 more
* Fix up named-views tests
* yarn fmt lint tsc
* Fix up new changes
* Get rid of 1 cyclic dependency
* Fix another cyclic mfer!
* fmt
* fmt tsc
* Fix zoom to fit being frigged
* a new list of circular deps
* Remove NetworkHealthIndicator test that was shit
* Fix the bad reload repeat issue kevin started on
* Fix zoom to fit at the right moments...
* Fix cache count numbers in editor test
* Remove a test race - poll window info.
* Qualify fail function
* Try something
* Use scene.connectionEstablished
* Hopefully fix snapshots at least
* Add app console.log
* Fix native menu tests more
* tsc lint
* Fix camera failure
* Try again
* Test attempt number 15345203, action!
* Add back old window detection heuristic
* Remove firstWindow to complete the work of 2342d04fe2
* Tweak some tests for MacOS
* Tweak "set appearance" test for MacOS
Revert this if it messes up any other platform's color checks!
* Are you serious? This was all that needed formatting?
* More color tweaks
Local MacOS and CI MacOS don't agree
* Fixes on apperance e2e test for stream idle branch (#6168)
pierremtb/stream-idle-revamp-appearance-fixes
* Another apperance fix
* Skip one native menu test to make stream idle green (#6169)
* pierremtb/stream-idle-revamp-more-fixes
* Fix lint
* Update snapshot for test_generate_settings_docs
---------
Co-authored-by: lee-at-zoo-corp <lee@zoo.dev>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
This commit is contained in:
@ -73,9 +73,6 @@ export const KCL_DEFAULT_DEGREE = `360`
|
||||
/** The default KCL color expression */
|
||||
export const KCL_DEFAULT_COLOR = `#3c73ff`
|
||||
|
||||
/** localStorage key for the playwright test-specific app settings file */
|
||||
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
|
||||
|
||||
export const SETTINGS_FILE_NAME = 'settings.toml'
|
||||
export const TOKEN_FILE_NAME = 'token.txt'
|
||||
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
|
||||
|
||||
@ -459,12 +459,13 @@ export const getAppSettingsFilePath = async () => {
|
||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||
'TEST_SETTINGS_FILE_KEY'
|
||||
)
|
||||
if (isTestEnv && !testSettingsPath) return SETTINGS_FILE_NAME
|
||||
|
||||
const appConfig = await window.electron.getPath('appData')
|
||||
|
||||
const fullPath = isTestEnv
|
||||
? testSettingsPath
|
||||
: window.electron.path.join(appConfig, getAppFolderName())
|
||||
? window.electron.path.resolve(testSettingsPath, '..')
|
||||
: window.electron.path.resolve(appConfig, getAppFolderName())
|
||||
|
||||
try {
|
||||
await window.electron.stat(fullPath)
|
||||
} catch (e) {
|
||||
@ -480,9 +481,10 @@ const getTokenFilePath = async () => {
|
||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||
'TEST_SETTINGS_FILE_KEY'
|
||||
)
|
||||
|
||||
const appConfig = await window.electron.getPath('appData')
|
||||
const fullPath = isTestEnv
|
||||
? testSettingsPath
|
||||
? window.electron.path.resolve(testSettingsPath, '..')
|
||||
: window.electron.path.join(appConfig, getAppFolderName())
|
||||
try {
|
||||
await window.electron.stat(fullPath)
|
||||
@ -496,8 +498,15 @@ const getTokenFilePath = async () => {
|
||||
}
|
||||
|
||||
const getTelemetryFilePath = async () => {
|
||||
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||
'TEST_SETTINGS_FILE_KEY'
|
||||
)
|
||||
|
||||
const appConfig = await window.electron.getPath('appData')
|
||||
const fullPath = window.electron.path.join(appConfig, getAppFolderName())
|
||||
const fullPath = isTestEnv
|
||||
? window.electron.path.resolve(testSettingsPath, '..')
|
||||
: window.electron.path.join(appConfig, getAppFolderName())
|
||||
try {
|
||||
await window.electron.stat(fullPath)
|
||||
} catch (e) {
|
||||
@ -510,8 +519,15 @@ const getTelemetryFilePath = async () => {
|
||||
}
|
||||
|
||||
const getRawTelemetryFilePath = async () => {
|
||||
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||
'TEST_SETTINGS_FILE_KEY'
|
||||
)
|
||||
|
||||
const appConfig = await window.electron.getPath('appData')
|
||||
const fullPath = window.electron.path.join(appConfig, getAppFolderName())
|
||||
const fullPath = isTestEnv
|
||||
? window.electron.path.resolve(testSettingsPath, '..')
|
||||
: window.electron.path.join(appConfig, getAppFolderName())
|
||||
try {
|
||||
await window.electron.stat(fullPath)
|
||||
} catch (e) {
|
||||
@ -535,9 +551,17 @@ const getProjectSettingsFilePath = async (projectPath: string) => {
|
||||
}
|
||||
|
||||
export const getInitialDefaultDir = async () => {
|
||||
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||
'TEST_SETTINGS_FILE_KEY'
|
||||
)
|
||||
|
||||
if (!window.electron) {
|
||||
return ''
|
||||
}
|
||||
if (isTestEnv) {
|
||||
return testSettingsPath
|
||||
}
|
||||
const dir = await window.electron.getPath('documents')
|
||||
return window.electron.path.join(dir, PROJECT_FOLDER)
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ import {
|
||||
isOverlap,
|
||||
uuidv4,
|
||||
} from '@src/lib/utils'
|
||||
import { engineStreamActor } from '@src/machines/appMachine'
|
||||
import type { ModelingMachineEvent } from '@src/machines/modelingMachine'
|
||||
|
||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||
@ -649,12 +650,13 @@ export async function sendSelectEventToEngine(
|
||||
e: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
) {
|
||||
// No video stream to normalise against, return immediately
|
||||
if (!engineCommandManager.elVideo)
|
||||
const engineStreamState = engineStreamActor.getSnapshot().context
|
||||
if (!engineStreamState.videoRef.current)
|
||||
return Promise.reject('video element not ready')
|
||||
|
||||
const { x, y } = getNormalisedCoordinates(
|
||||
e,
|
||||
engineCommandManager.elVideo,
|
||||
engineStreamState.videoRef.current,
|
||||
engineCommandManager.streamDimensions
|
||||
)
|
||||
const res = await engineCommandManager.sendSceneCommand({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useRef } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
|
||||
import type { CameraOrbitType } from '@rust/kcl-lib/bindings/CameraOrbitType'
|
||||
import type { CameraProjectionType } from '@rust/kcl-lib/bindings/CameraProjectionType'
|
||||
@ -6,6 +6,7 @@ import type { NamedView } from '@rust/kcl-lib/bindings/NamedView'
|
||||
import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus'
|
||||
|
||||
import { CustomIcon } from '@src/components/CustomIcon'
|
||||
import { Toggle } from '@src/components/Toggle/Toggle'
|
||||
import Tooltip from '@src/components/Tooltip'
|
||||
import type { CameraSystem } from '@src/lib/cameraControls'
|
||||
import { cameraMouseDragGuards, cameraSystems } from '@src/lib/cameraControls'
|
||||
@ -123,6 +124,8 @@ export class Setting<T = unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
const MS_IN_MINUTE = 1000 * 60
|
||||
|
||||
export function createSettings() {
|
||||
return {
|
||||
/** Settings that affect the behavior of the entire app,
|
||||
@ -208,12 +211,109 @@ export function createSettings() {
|
||||
/**
|
||||
* 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',
|
||||
streamIdleMode: new Setting<number | undefined>({
|
||||
defaultValue: undefined,
|
||||
hideOnLevel: 'project',
|
||||
description: 'Save bandwidth & battery',
|
||||
validate: (v) =>
|
||||
v === undefined ||
|
||||
(typeof v === 'number' &&
|
||||
v >= 1 * MS_IN_MINUTE &&
|
||||
v <= 60 * MS_IN_MINUTE),
|
||||
Component: ({
|
||||
value: settingValueInStorage,
|
||||
updateValue: writeSettingValueToStorage,
|
||||
}) => {
|
||||
const [timeoutId, setTimeoutId] = useState<
|
||||
ReturnType<typeof setTimeout> | undefined
|
||||
>(undefined)
|
||||
const [preview, setPreview] = useState(
|
||||
settingValueInStorage === undefined
|
||||
? settingValueInStorage
|
||||
: settingValueInStorage / MS_IN_MINUTE
|
||||
)
|
||||
const onChangeRange = (e: React.SyntheticEvent) => {
|
||||
if (
|
||||
!(
|
||||
e.isTrusted &&
|
||||
'value' in e.currentTarget &&
|
||||
e.currentTarget.value
|
||||
)
|
||||
)
|
||||
return
|
||||
setPreview(Number(e.currentTarget.value))
|
||||
}
|
||||
const onSaveRange = (e: React.SyntheticEvent) => {
|
||||
if (preview === undefined) return
|
||||
if (
|
||||
!(
|
||||
e.isTrusted &&
|
||||
'value' in e.currentTarget &&
|
||||
e.currentTarget.value
|
||||
)
|
||||
)
|
||||
return
|
||||
writeSettingValueToStorage(
|
||||
Number(e.currentTarget.value) * MS_IN_MINUTE
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex item-center gap-4 m-0 py-0">
|
||||
<Toggle
|
||||
name="streamIdleModeToggle"
|
||||
offLabel="Off"
|
||||
onLabel="On"
|
||||
checked={settingValueInStorage !== undefined}
|
||||
onChange={(event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
if (timeoutId) {
|
||||
return
|
||||
}
|
||||
const isChecked = event.currentTarget.checked
|
||||
clearTimeout(timeoutId)
|
||||
setTimeoutId(
|
||||
setTimeout(() => {
|
||||
const requested = !isChecked ? undefined : 5
|
||||
setPreview(requested)
|
||||
writeSettingValueToStorage(
|
||||
requested === undefined
|
||||
? undefined
|
||||
: Number(requested) * MS_IN_MINUTE
|
||||
)
|
||||
setTimeoutId(undefined)
|
||||
}, 100)
|
||||
)
|
||||
}}
|
||||
className="block w-4 h-4"
|
||||
/>
|
||||
<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>({
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
|
||||
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
||||
|
||||
import { createSettings } from '@src/lib/settings/initialSettings'
|
||||
import {
|
||||
@ -43,11 +44,10 @@ describe(`testing settings initialization`, () => {
|
||||
},
|
||||
},
|
||||
}
|
||||
const projectConfiguration: DeepPartial<Configuration> = {
|
||||
const projectConfiguration: DeepPartial<ProjectConfiguration> = {
|
||||
settings: {
|
||||
app: {
|
||||
appearance: {
|
||||
theme: 'light',
|
||||
color: 200,
|
||||
},
|
||||
},
|
||||
@ -82,11 +82,10 @@ describe(`testing getAllCurrentSettings`, () => {
|
||||
},
|
||||
},
|
||||
}
|
||||
const projectConfiguration: DeepPartial<Configuration> = {
|
||||
const projectConfiguration: DeepPartial<ProjectConfiguration> = {
|
||||
settings: {
|
||||
app: {
|
||||
appearance: {
|
||||
theme: 'light',
|
||||
color: 200,
|
||||
},
|
||||
},
|
||||
|
||||
@ -33,6 +33,10 @@ import { appThemeToTheme } from '@src/lib/theme'
|
||||
import { err } from '@src/lib/trap'
|
||||
import type { DeepPartial } from '@src/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
|
||||
@ -49,7 +53,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
|
||||
),
|
||||
allowOrbitInSketchMode:
|
||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||
projectDirectory: configuration?.settings?.project?.directory,
|
||||
@ -128,7 +134,6 @@ export function projectConfigurationToSettingsPayload(
|
||||
: undefined,
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
allowOrbitInSketchMode:
|
||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||
namedViews: deepPartialNamedViewsToNamedViews(
|
||||
|
||||
@ -10,8 +10,8 @@ import { SceneInfra } from '@src/clientSideScene/sceneInfra'
|
||||
import type { BaseUnit } from '@src/lib/settings/settingsTypes'
|
||||
|
||||
export const codeManager = new CodeManager()
|
||||
|
||||
export const engineCommandManager = new EngineCommandManager()
|
||||
export const rustContext = new RustContext(engineCommandManager)
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -23,21 +23,32 @@ declare global {
|
||||
// Accessible for tests mostly
|
||||
window.engineCommandManager = engineCommandManager
|
||||
|
||||
// This needs to be after codeManager is created.
|
||||
export const kclManager = new KclManager(engineCommandManager)
|
||||
engineCommandManager.kclManager = kclManager
|
||||
|
||||
export const sceneInfra = new SceneInfra(engineCommandManager)
|
||||
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
|
||||
|
||||
// This needs to be after sceneInfra and engineCommandManager are is created.
|
||||
export const editorManager = new EditorManager(engineCommandManager)
|
||||
|
||||
// This needs to be after codeManager is created.
|
||||
// (lee: what??? why?)
|
||||
export const kclManager = new KclManager(engineCommandManager, {
|
||||
rustContext,
|
||||
codeManager,
|
||||
editorManager,
|
||||
sceneInfra,
|
||||
})
|
||||
|
||||
// The most obvious of cyclic dependencies.
|
||||
// This is because the handleOnViewUpdate(viewUpdate: ViewUpdate): void {
|
||||
// method requires it for the current ast.
|
||||
// CYCLIC REF
|
||||
editorManager.kclManager = kclManager
|
||||
|
||||
engineCommandManager.kclManager = kclManager
|
||||
kclManager.sceneInfraBaseUnitMultiplierSetter = (unit: BaseUnit) => {
|
||||
sceneInfra.baseUnit = unit
|
||||
}
|
||||
|
||||
// This needs to be after sceneInfra and engineCommandManager are is created.
|
||||
export const editorManager = new EditorManager(engineCommandManager, kclManager)
|
||||
|
||||
export const rustContext = new RustContext(engineCommandManager)
|
||||
|
||||
export const sceneEntitiesManager = new SceneEntities(
|
||||
engineCommandManager,
|
||||
sceneInfra,
|
||||
|
||||
3
src/lib/timings.ts
Normal file
3
src/lib/timings.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// 0.25s is the average visual reaction time for humans so we'll go a bit less
|
||||
// so those exception people don't see.
|
||||
export const REASONABLE_TIME_TO_REFRESH_STREAM_SIZE = 100
|
||||
Reference in New Issue
Block a user