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)
|
dev: node_modules public/wasm_lib_bg.wasm $(XSTATE_TYPEGENS)
|
||||||
yarn start
|
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)
|
$(XSTATE_TYPEGENS): $(TS_SRC)
|
||||||
yarn xstate typegen 'src/**/*.ts?(x)'
|
yarn xstate typegen 'src/**/*.ts?(x)'
|
||||||
|
|
||||||
|
@ -43,12 +43,6 @@ test(
|
|||||||
// open the project
|
// open the project
|
||||||
await page.getByText(`bracket`).click()
|
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
|
// expect zero errors in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
@ -56,6 +50,12 @@ test(
|
|||||||
const exportButton = page.getByTestId('export-pane-button')
|
const exportButton = page.getByTestId('export-pane-button')
|
||||||
await expect(exportButton).toBeVisible()
|
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 gltfOption = page.getByText('glTF')
|
||||||
const submitButton = page.getByText('Confirm Export')
|
const submitButton = page.getByText('Confirm Export')
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||||
|
@ -495,10 +495,6 @@ test(
|
|||||||
|
|
||||||
await file.click()
|
await file.click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
await expect(u.codeLocator).toContainText(
|
await expect(u.codeLocator).toContainText(
|
||||||
'A mounting bracket for the Focusrite Scarlett Solo audio interface'
|
'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 { engineCommandManager } from 'lib/singletons'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
function useEngineCommands(): [CommandLog[], () => void] {
|
export function useEngineCommands(): [CommandLog[], () => void] {
|
||||||
const [engineCommands, setEngineCommands] = useState<CommandLog[]>(
|
const [engineCommands, setEngineCommands] = useState<CommandLog[]>(
|
||||||
engineCommandManager.commandLogs
|
engineCommandManager.commandLogs
|
||||||
)
|
)
|
||||||
|
@ -179,10 +179,7 @@ const FileTreeItem = ({
|
|||||||
codeManager.writeToFile()
|
codeManager.writeToFile()
|
||||||
|
|
||||||
// Prevent seeing the model built one piece at a time when changing files
|
// Prevent seeing the model built one piece at a time when changing files
|
||||||
kclManager.isFirstRender = true
|
kclManager.executeCode(true)
|
||||||
kclManager.executeCode(true).then(() => {
|
|
||||||
kclManager.isFirstRender = false
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// Let the lsp servers know we closed a file.
|
// Let the lsp servers know we closed a file.
|
||||||
onFileClose(currentFile?.path || null, project?.path || null)
|
onFileClose(currentFile?.path || null, project?.path || null)
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
|
|
||||||
import { engineCommandManager } from '../lib/singletons'
|
import { engineCommandManager } from '../lib/singletons'
|
||||||
|
|
||||||
|
import { Spinner } from './Spinner'
|
||||||
|
|
||||||
const Loading = ({ children }: React.PropsWithChildren) => {
|
const Loading = ({ children }: React.PropsWithChildren) => {
|
||||||
const [error, setError] = useState<ConnectionError>(ConnectionError.Unset)
|
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"
|
className="body-bg flex flex-col items-center justify-center h-screen"
|
||||||
data-testid="loading"
|
data-testid="loading"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 10 10" className="w-8 h-8">
|
<Spinner />
|
||||||
<circle
|
|
||||||
cx="5"
|
|
||||||
cy="5"
|
|
||||||
r="4"
|
|
||||||
stroke="var(--primary)"
|
|
||||||
fill="none"
|
|
||||||
strokeDasharray="4, 4"
|
|
||||||
className="animate-spin origin-center"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<p className="text-base mt-4 text-primary">{children || 'Loading'}</p>
|
<p className="text-base mt-4 text-primary">{children || 'Loading'}</p>
|
||||||
<p
|
<p
|
||||||
className={
|
className={
|
||||||
|
@ -11,6 +11,7 @@ import toast from 'react-hot-toast'
|
|||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
||||||
|
import { ModelStateIndicator } from './ModelStateIndicator'
|
||||||
|
|
||||||
export function LowerRightControls({
|
export function LowerRightControls({
|
||||||
children,
|
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">
|
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||||
{children}
|
{children}
|
||||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||||
|
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
||||||
<a
|
<a
|
||||||
onClick={openExternalBrowserIfDesktop(
|
onClick={openExternalBrowserIfDesktop(
|
||||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
`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()
|
store.videoElement?.pause()
|
||||||
|
|
||||||
kclManager.isFirstRender = true
|
|
||||||
kclManager.executeCode().then(() => {
|
kclManager.executeCode().then(() => {
|
||||||
kclManager.isFirstRender = false
|
|
||||||
if (engineCommandManager.engineConnection?.idleMode) return
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
store.videoElement?.play().catch((e) => {
|
store.videoElement?.play().catch((e) => {
|
||||||
|
@ -193,10 +193,7 @@ export const SettingsAuthProviderBase = ({
|
|||||||
resetSettingsIncludesUnitChange
|
resetSettingsIncludesUnitChange
|
||||||
) {
|
) {
|
||||||
// Unit changes requires a re-exec of code
|
// Unit changes requires a re-exec of code
|
||||||
kclManager.isFirstRender = true
|
kclManager.executeCode(true)
|
||||||
kclManager.executeCode(true).then(() => {
|
|
||||||
kclManager.isFirstRender = false
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// For any future logging we'd like to do
|
// For any future logging we'd like to do
|
||||||
// console.log(
|
// 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.
|
* central place, we can move this code there.
|
||||||
*/
|
*/
|
||||||
async function executeCodeAndPlayStream() {
|
async function executeCodeAndPlayStream() {
|
||||||
kclManager.isFirstRender = true
|
|
||||||
kclManager.executeCode(true).then(() => {
|
kclManager.executeCode(true).then(() => {
|
||||||
videoRef.current?.play().catch((e) => {
|
videoRef.current?.play().catch((e) => {
|
||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
console.warn('Video playing was prevented', e, videoRef.current)
|
||||||
})
|
})
|
||||||
kclManager.isFirstRender = false
|
|
||||||
setStreamState(StreamState.Playing)
|
setStreamState(StreamState.Playing)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -219,7 +217,7 @@ export const Stream = () => {
|
|||||||
* Play the vid
|
* Play the vid
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kclManager.isFirstRender) {
|
if (!kclManager.isExecuting) {
|
||||||
setTimeout(() =>
|
setTimeout(() =>
|
||||||
// execute in the next event loop
|
// execute in the next event loop
|
||||||
videoRef.current?.play().catch((e) => {
|
videoRef.current?.play().catch((e) => {
|
||||||
@ -227,7 +225,7 @@ export const Stream = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [kclManager.isFirstRender])
|
}, [kclManager.isExecuting])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -382,15 +380,15 @@ export const Stream = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!isNetworkOkay || isLoading || kclManager.isFirstRender) && (
|
{(!isNetworkOkay || isLoading) && (
|
||||||
<div className="text-center absolute inset-0">
|
<div className="text-center absolute inset-0">
|
||||||
<Loading>
|
<Loading>
|
||||||
{!isNetworkOkay && !isLoading && !kclManager.isFirstRender ? (
|
{!isNetworkOkay && !isLoading ? (
|
||||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
<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>
|
<span data-testid="loading-stream">Loading stream...</span>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,8 +60,6 @@ export class KclManager {
|
|||||||
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
||||||
private _executeCallback: () => void = () => {}
|
private _executeCallback: () => void = () => {}
|
||||||
|
|
||||||
isFirstRender = true
|
|
||||||
|
|
||||||
get ast() {
|
get ast() {
|
||||||
return this._ast
|
return this._ast
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ window.tearDown = engineCommandManager.tearDown
|
|||||||
|
|
||||||
// This needs to be after codeManager is created.
|
// This needs to be after codeManager is created.
|
||||||
export const kclManager = new KclManager(engineCommandManager)
|
export const kclManager = new KclManager(engineCommandManager)
|
||||||
kclManager.isFirstRender = true
|
|
||||||
engineCommandManager.kclManager = kclManager
|
engineCommandManager.kclManager = kclManager
|
||||||
|
|
||||||
engineCommandManager.getAstCb = () => kclManager.ast
|
engineCommandManager.getAstCb = () => kclManager.ast
|
||||||
|
@ -107,10 +107,7 @@ function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
|
|||||||
codeManager.updateCodeStateEditor(bracket)
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
await codeManager.writeToFile()
|
await codeManager.writeToFile()
|
||||||
|
|
||||||
kclManager.isFirstRender = true
|
await kclManager.executeCode(true)
|
||||||
await kclManager.executeCode(true).then(() => {
|
|
||||||
kclManager.isFirstRender = false
|
|
||||||
})
|
|
||||||
props.setShouldShowWarning(false)
|
props.setShouldShowWarning(false)
|
||||||
}}
|
}}
|
||||||
nextText="Overwrite code and continue"
|
nextText="Overwrite code and continue"
|
||||||
|
@ -13,10 +13,7 @@ export default function Sketching() {
|
|||||||
async function clearEditor() {
|
async function clearEditor() {
|
||||||
// We do want to update both the state and editor here.
|
// We do want to update both the state and editor here.
|
||||||
codeManager.updateCodeStateEditor('')
|
codeManager.updateCodeStateEditor('')
|
||||||
kclManager.isFirstRender = true
|
await kclManager.executeCode(true)
|
||||||
await kclManager.executeCode(true).then(() => {
|
|
||||||
kclManager.isFirstRender = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearEditor()
|
clearEditor()
|
||||||
|
@ -82,10 +82,7 @@ export function useDemoCode() {
|
|||||||
if (!editorManager.editorView || codeManager.code === bracket) return
|
if (!editorManager.editorView || codeManager.code === bracket) return
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
codeManager.updateCodeStateEditor(bracket)
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
kclManager.isFirstRender = true
|
await kclManager.executeCode(true)
|
||||||
await kclManager.executeCode(true).then(() => {
|
|
||||||
kclManager.isFirstRender = false
|
|
||||||
})
|
|
||||||
await codeManager.writeToFile()
|
await codeManager.writeToFile()
|
||||||
})
|
})
|
||||||
}, [editorManager.editorView])
|
}, [editorManager.editorView])
|
||||||
|
Reference in New Issue
Block a user