Remove building scene, add code execution loading indicator to bottom-right controls (#3691)
* Remove isFirstRender because isExecuting means the same and remove building scene * add small makefile util * Remove waiting for building scene prompt * Add a new model state indicator * fmt lint tsc --------- Co-authored-by: Frank Noirot <frank@kittycad.io>
This commit is contained in:
8
Makefile
8
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)'
|
||||
|
||||
|
@ -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...`)
|
||||
|
@ -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'
|
||||
)
|
||||
|
@ -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<CommandLog[]>(
|
||||
engineCommandManager.commandLogs
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -11,6 +11,8 @@ import {
|
||||
|
||||
import { engineCommandManager } from '../lib/singletons'
|
||||
|
||||
import { Spinner } from './Spinner'
|
||||
|
||||
const Loading = ({ children }: React.PropsWithChildren) => {
|
||||
const [error, setError] = useState<ConnectionError>(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"
|
||||
>
|
||||
<svg viewBox="0 0 10 10" className="w-8 h-8">
|
||||
<circle
|
||||
cx="5"
|
||||
cy="5"
|
||||
r="4"
|
||||
stroke="var(--primary)"
|
||||
fill="none"
|
||||
strokeDasharray="4, 4"
|
||||
className="animate-spin origin-center"
|
||||
/>
|
||||
</svg>
|
||||
<Spinner />
|
||||
<p className="text-base mt-4 text-primary">{children || 'Loading'}</p>
|
||||
<p
|
||||
className={
|
||||
|
@ -11,6 +11,7 @@ import toast from 'react-hot-toast'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
||||
import { ModelStateIndicator } from './ModelStateIndicator'
|
||||
|
||||
export function LowerRightControls({
|
||||
children,
|
||||
@ -65,6 +66,7 @@ export function LowerRightControls({
|
||||
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||
{children}
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
|
39
src/components/ModelStateIndicator.tsx
Normal file
39
src/components/ModelStateIndicator.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { useEngineCommands } from './EngineCommands'
|
||||
import { Spinner } from './Spinner'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
|
||||
export const ModelStateIndicator = () => {
|
||||
const [commands] = useEngineCommands()
|
||||
|
||||
const lastCommandType = commands[commands.length - 1]?.type
|
||||
|
||||
let className = 'w-6 h-6 '
|
||||
let icon = <Spinner className={className} />
|
||||
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 = (
|
||||
<CustomIcon
|
||||
data-testid={dataTestId + '-receive-reliable'}
|
||||
name="checkmark"
|
||||
/>
|
||||
)
|
||||
} 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 = (
|
||||
<CustomIcon
|
||||
data-testid={dataTestId + '-execution-done'}
|
||||
name="checkmark"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} data-testid="model-state-indicator">
|
||||
{icon}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -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) => {
|
||||
|
@ -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(
|
||||
|
17
src/components/Spinner.tsx
Normal file
17
src/components/Spinner.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
export const Spinner = (props: SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg viewBox="0 0 10 10" className={'w-8 h-8'} {...props}>
|
||||
<circle
|
||||
cx="5"
|
||||
cy="5"
|
||||
r="4"
|
||||
stroke="var(--primary)"
|
||||
fill="none"
|
||||
strokeDasharray="4, 4"
|
||||
className="animate-spin origin-center"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(!isNetworkOkay || isLoading || kclManager.isFirstRender) && (
|
||||
{(!isNetworkOkay || isLoading) && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<Loading>
|
||||
{!isNetworkOkay && !isLoading && !kclManager.isFirstRender ? (
|
||||
{!isNetworkOkay && !isLoading ? (
|
||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
||||
) : !isLoading && kclManager.isFirstRender ? (
|
||||
<span data-testid="loading-stream">Building scene...</span>
|
||||
) : (
|
||||
!isLoading && (
|
||||
<span data-testid="loading-stream">Loading stream...</span>
|
||||
)
|
||||
)}
|
||||
</Loading>
|
||||
</div>
|
||||
|
@ -60,8 +60,6 @@ export class KclManager {
|
||||
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
||||
private _executeCallback: () => void = () => {}
|
||||
|
||||
isFirstRender = true
|
||||
|
||||
get ast() {
|
||||
return this._ast
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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])
|
||||
|
Reference in New Issue
Block a user