diff --git a/Makefile b/Makefile index 20d11334f..5b01a2652 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,14 @@ XSTATE_TYPEGENS := $(wildcard src/machines/*.typegen.ts) dev: node_modules public/wasm_lib_bg.wasm $(XSTATE_TYPEGENS) yarn start +# I'm sorry this is so specific to my setup you may as well ignore this. +# This is so you don't have to deal with electron windows popping up constantly. +# It should work for you other Linux users. +lee-electron-test: + Xephyr -br -ac -noreset -screen 1200x500 :2 & + DISPLAY=:2 NODE_ENV=development PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn tron:test -g "when using the file tree" + killall Xephyr + $(XSTATE_TYPEGENS): $(TS_SRC) yarn xstate typegen 'src/**/*.ts?(x)' diff --git a/e2e/playwright/desktop-export.spec.ts b/e2e/playwright/desktop-export.spec.ts index 225b96b7b..6dafd041f 100644 --- a/e2e/playwright/desktop-export.spec.ts +++ b/e2e/playwright/desktop-export.spec.ts @@ -43,12 +43,6 @@ test( // open the project await page.getByText(`bracket`).click() - // wait for the project to load - await expect(page.getByTestId('loading')).toBeAttached() - await expect(page.getByTestId('loading')).not.toBeAttached({ - timeout: 20_000, - }) - // expect zero errors in guter await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() @@ -56,6 +50,12 @@ test( const exportButton = page.getByTestId('export-pane-button') await expect(exportButton).toBeVisible() + // Wait for the model to finish loading + const modelStateIndicator = page.getByTestId( + 'model-state-indicator-execution-done' + ) + await expect(modelStateIndicator).toBeVisible({ timeout: 60000 }) + const gltfOption = page.getByText('glTF') const submitButton = page.getByText('Confirm Export') const exportingToastMessage = page.getByText(`Exporting...`) diff --git a/e2e/playwright/projects.spec.ts b/e2e/playwright/projects.spec.ts index 3ed547b42..c274882d9 100644 --- a/e2e/playwright/projects.spec.ts +++ b/e2e/playwright/projects.spec.ts @@ -495,10 +495,6 @@ test( await file.click() - await expect(page.getByTestId('loading')).toBeAttached() - await expect(page.getByTestId('loading')).not.toBeAttached({ - timeout: 20_000, - }) await expect(u.codeLocator).toContainText( 'A mounting bracket for the Focusrite Scarlett Solo audio interface' ) diff --git a/src/components/EngineCommands.tsx b/src/components/EngineCommands.tsx index 23ebc9072..29041228e 100644 --- a/src/components/EngineCommands.tsx +++ b/src/components/EngineCommands.tsx @@ -2,7 +2,7 @@ import { CommandLog } from 'lang/std/engineConnection' import { engineCommandManager } from 'lib/singletons' import { useState, useEffect } from 'react' -function useEngineCommands(): [CommandLog[], () => void] { +export function useEngineCommands(): [CommandLog[], () => void] { const [engineCommands, setEngineCommands] = useState( engineCommandManager.commandLogs ) diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index 1b9b608c8..8ecf4764e 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -179,10 +179,7 @@ const FileTreeItem = ({ codeManager.writeToFile() // Prevent seeing the model built one piece at a time when changing files - kclManager.isFirstRender = true - kclManager.executeCode(true).then(() => { - kclManager.isFirstRender = false - }) + kclManager.executeCode(true) } else { // Let the lsp servers know we closed a file. onFileClose(currentFile?.path || null, project?.path || null) diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx index 5135245e2..a097956f6 100644 --- a/src/components/Loading.tsx +++ b/src/components/Loading.tsx @@ -11,6 +11,8 @@ import { import { engineCommandManager } from '../lib/singletons' +import { Spinner } from './Spinner' + const Loading = ({ children }: React.PropsWithChildren) => { const [error, setError] = useState(ConnectionError.Unset) @@ -65,17 +67,7 @@ const Loading = ({ children }: React.PropsWithChildren) => { className="body-bg flex flex-col items-center justify-center h-screen" data-testid="loading" > - - - +

{children || 'Loading'}

{children}

+ {!location.pathname.startsWith(PATHS.HOME) && } { + const [commands] = useEngineCommands() + + const lastCommandType = commands[commands.length - 1]?.type + + let className = 'w-6 h-6 ' + let icon = + let dataTestId = 'model-state-indicator' + + if (lastCommandType === 'receive-reliable') { + className += + 'bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 rounded-sm bg-succeed-10/30 dark:bg-succeed' + icon = ( + + ) + } else if (lastCommandType === 'execution-done') { + className += + 'border-6 border border-solid border-chalkboard-60 dark:border-chalkboard-80 bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 rounded-sm bg-succeed-10/30 dark:bg-succeed' + icon = ( + + ) + } + + return ( +
+ {icon} +
+ ) +} diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 7f0a49dc6..00c4091a1 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -160,9 +160,7 @@ export const ModelingMachineProvider = ({ store.videoElement?.pause() - kclManager.isFirstRender = true kclManager.executeCode().then(() => { - kclManager.isFirstRender = false if (engineCommandManager.engineConnection?.idleMode) return store.videoElement?.play().catch((e) => { diff --git a/src/components/SettingsAuthProvider.tsx b/src/components/SettingsAuthProvider.tsx index 83ea0f692..54ad27929 100644 --- a/src/components/SettingsAuthProvider.tsx +++ b/src/components/SettingsAuthProvider.tsx @@ -193,10 +193,7 @@ export const SettingsAuthProviderBase = ({ resetSettingsIncludesUnitChange ) { // Unit changes requires a re-exec of code - kclManager.isFirstRender = true - kclManager.executeCode(true).then(() => { - kclManager.isFirstRender = false - }) + kclManager.executeCode(true) } else { // For any future logging we'd like to do // console.log( diff --git a/src/components/Spinner.tsx b/src/components/Spinner.tsx new file mode 100644 index 000000000..dea0c20fd --- /dev/null +++ b/src/components/Spinner.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from 'react' + +export const Spinner = (props: SVGProps) => { + return ( + + + + ) +} diff --git a/src/components/Stream.tsx b/src/components/Stream.tsx index 32ecf1aa1..eb712f1e9 100644 --- a/src/components/Stream.tsx +++ b/src/components/Stream.tsx @@ -54,12 +54,10 @@ export const Stream = () => { * central place, we can move this code there. */ async function executeCodeAndPlayStream() { - kclManager.isFirstRender = true kclManager.executeCode(true).then(() => { videoRef.current?.play().catch((e) => { console.warn('Video playing was prevented', e, videoRef.current) }) - kclManager.isFirstRender = false setStreamState(StreamState.Playing) }) } @@ -219,7 +217,7 @@ export const Stream = () => { * Play the vid */ useEffect(() => { - if (!kclManager.isFirstRender) { + if (!kclManager.isExecuting) { setTimeout(() => // execute in the next event loop videoRef.current?.play().catch((e) => { @@ -227,7 +225,7 @@ export const Stream = () => { }) ) } - }, [kclManager.isFirstRender]) + }, [kclManager.isExecuting]) useEffect(() => { if ( @@ -382,15 +380,15 @@ export const Stream = () => { )} - {(!isNetworkOkay || isLoading || kclManager.isFirstRender) && ( + {(!isNetworkOkay || isLoading) && (
- {!isNetworkOkay && !isLoading && !kclManager.isFirstRender ? ( + {!isNetworkOkay && !isLoading ? ( Stream disconnected... - ) : !isLoading && kclManager.isFirstRender ? ( - Building scene... ) : ( - Loading stream... + !isLoading && ( + Loading stream... + ) )}
diff --git a/src/lang/KclSingleton.ts b/src/lang/KclSingleton.ts index 7e16c6ed7..c9bbcf7cf 100644 --- a/src/lang/KclSingleton.ts +++ b/src/lang/KclSingleton.ts @@ -60,8 +60,6 @@ export class KclManager { private _wasmInitFailedCallback: (arg: boolean) => void = () => {} private _executeCallback: () => void = () => {} - isFirstRender = true - get ast() { return this._ast } diff --git a/src/lib/singletons.ts b/src/lib/singletons.ts index 2e6df784e..092350120 100644 --- a/src/lib/singletons.ts +++ b/src/lib/singletons.ts @@ -16,7 +16,6 @@ window.tearDown = engineCommandManager.tearDown // This needs to be after codeManager is created. export const kclManager = new KclManager(engineCommandManager) -kclManager.isFirstRender = true engineCommandManager.kclManager = kclManager engineCommandManager.getAstCb = () => kclManager.ast diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx index e06c8e87f..29ed153d3 100644 --- a/src/routes/Onboarding/Introduction.tsx +++ b/src/routes/Onboarding/Introduction.tsx @@ -107,10 +107,7 @@ function OnboardingWarningWeb(props: OnboardingResetWarningProps) { codeManager.updateCodeStateEditor(bracket) await codeManager.writeToFile() - kclManager.isFirstRender = true - await kclManager.executeCode(true).then(() => { - kclManager.isFirstRender = false - }) + await kclManager.executeCode(true) props.setShouldShowWarning(false) }} nextText="Overwrite code and continue" diff --git a/src/routes/Onboarding/Sketching.tsx b/src/routes/Onboarding/Sketching.tsx index 1bb1f7e01..983d00a20 100644 --- a/src/routes/Onboarding/Sketching.tsx +++ b/src/routes/Onboarding/Sketching.tsx @@ -13,10 +13,7 @@ export default function Sketching() { async function clearEditor() { // We do want to update both the state and editor here. codeManager.updateCodeStateEditor('') - kclManager.isFirstRender = true - await kclManager.executeCode(true).then(() => { - kclManager.isFirstRender = false - }) + await kclManager.executeCode(true) } clearEditor() diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx index 5be7929cf..46f2d880c 100644 --- a/src/routes/Onboarding/index.tsx +++ b/src/routes/Onboarding/index.tsx @@ -82,10 +82,7 @@ export function useDemoCode() { if (!editorManager.editorView || codeManager.code === bracket) return setTimeout(async () => { codeManager.updateCodeStateEditor(bracket) - kclManager.isFirstRender = true - await kclManager.executeCode(true).then(() => { - kclManager.isFirstRender = false - }) + await kclManager.executeCode(true) await codeManager.writeToFile() }) }, [editorManager.editorView])