Compare commits
55 Commits
kcl-77
...
lee-at-zoo
Author | SHA1 | Date | |
---|---|---|---|
2342d04fe2 | |||
ddac107dc1 | |||
313a21e82a | |||
b9875bb8cf | |||
8940f9b214 | |||
0af99af15e | |||
7f18aef49b | |||
062495c02d | |||
767fde869c | |||
49fcbdddba | |||
e06f76a6bb | |||
e98a957553 | |||
25af691911 | |||
cdc0fa4ed9 | |||
4d06a917f2 | |||
682fa50c1a | |||
496522e27d | |||
4c57cea22d | |||
70f9c8edf1 | |||
9de37879e5 | |||
91b5549a06 | |||
3471f73479 | |||
277c489e38 | |||
00122aae5e | |||
65adfd4497 | |||
8d0343c946 | |||
4c564bf332 | |||
ed1e2aedfd | |||
992fec6afb | |||
62a18dd2b3 | |||
2c673fba82 | |||
635314bb8d | |||
c75aafa60d | |||
fb192ee213 | |||
9e19d131eb | |||
81f70251e1 | |||
74f9afb2ca | |||
d03343d97d | |||
6c8a525762 | |||
c8177564e1 | |||
12bf41ab7e | |||
fbefff490c | |||
4c9851efbf | |||
2cca1376e2 | |||
c32a3edb39 | |||
955a2ffaf9 | |||
6bc8e45aa7 | |||
be0fdc53b5 | |||
cfbe805e14 | |||
2a60efc5ab | |||
deb83cf62c | |||
e3705bf7df | |||
ff887af540 | |||
1e4b6ce701 | |||
e642731529 |
@ -46,8 +46,6 @@ test.describe('Point and click for boolean workflows', () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Test coordinates for selection - these might need adjustment based on actual scene layout
|
// Test coordinates for selection - these might need adjustment based on actual scene layout
|
||||||
@ -77,6 +75,7 @@ test.describe('Point and click for boolean workflows', () => {
|
|||||||
|
|
||||||
// Select first object in the scene, expect there to be a pixel diff from the selection color change
|
// Select first object in the scene, expect there to be a pixel diff from the selection color change
|
||||||
await clickFirstObject({ pixelDiff: 50 })
|
await clickFirstObject({ pixelDiff: 50 })
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// For subtract, we need to proceed to the next step before selecting the second object
|
// For subtract, we need to proceed to the next step before selecting the second object
|
||||||
if (operationName !== 'subtract') {
|
if (operationName !== 'subtract') {
|
||||||
@ -87,6 +86,8 @@ test.describe('Point and click for boolean workflows', () => {
|
|||||||
// Select second object
|
// Select second object
|
||||||
await clickSecondObject({ pixelDiff: 50 })
|
await clickSecondObject({ pixelDiff: 50 })
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// Confirm the operation in the command bar
|
// Confirm the operation in the command bar
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { uuidv4 } from '@src/lib/utils'
|
|||||||
|
|
||||||
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
||||||
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||||
|
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||||
import { getUtils } from '@e2e/playwright/test-utils'
|
import { getUtils } from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ test.describe(
|
|||||||
page: Page,
|
page: Page,
|
||||||
homePage: HomePageFixture,
|
homePage: HomePageFixture,
|
||||||
scene: SceneFixture,
|
scene: SceneFixture,
|
||||||
|
toolbar: ToolbarFixture,
|
||||||
plane: string,
|
plane: string,
|
||||||
clickCoords: { x: number; y: number }
|
clickCoords: { x: number; y: number }
|
||||||
) => {
|
) => {
|
||||||
@ -59,9 +61,12 @@ test.describe(
|
|||||||
await u.sendCustomCmd(updateCamCommand)
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||||
await page.waitForTimeout(600) // wait for animation
|
await page.waitForTimeout(600) // wait for animation
|
||||||
|
|
||||||
|
await toolbar.waitUntilSketchingReady()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'line Line', exact: true })
|
page.getByRole('button', { name: 'line Line', exact: true })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
@ -117,11 +122,12 @@ test.describe(
|
|||||||
]
|
]
|
||||||
|
|
||||||
for (const config of planeConfigs) {
|
for (const config of planeConfigs) {
|
||||||
test(config.plane, async ({ page, homePage, scene }) => {
|
test(config.plane, async ({ page, homePage, scene, toolbar }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(
|
await sketchOnPlaneAndBackSideTest(
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
toolbar,
|
||||||
config.plane,
|
config.plane,
|
||||||
config.coords
|
config.coords
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ extrude001 = extrude(sketch001, length = 5)`
|
|||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Ensure no badge is present
|
// Ensure no badge is present
|
||||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
@ -171,6 +172,8 @@ extrude001 = extrude(sketch001, length = 5)`
|
|||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// Load the app with the working starter code
|
// Load the app with the working starter code
|
||||||
await context.addInitScript((code) => {
|
await context.addInitScript((code) => {
|
||||||
@ -180,9 +183,7 @@ extrude001 = extrude(sketch001, length = 5)`
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// FIXME: await scene.waitForExecutionDone() does not work. It still fails.
|
await scene.settled(cmdBar)
|
||||||
// I needed to increase this timeout to get this to pass.
|
|
||||||
await page.waitForTimeout(10000)
|
|
||||||
|
|
||||||
// Ensure badge is present
|
// Ensure badge is present
|
||||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
@ -317,9 +317,13 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
|||||||
test('Can switch between sketch tools via command bar', async ({
|
test('Can switch between sketch tools via command bar', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
toolbar,
|
||||||
}) => {
|
}) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
@ -343,7 +347,9 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
// Start a sketch
|
// Start a sketch
|
||||||
await sketchButton.click()
|
await sketchButton.click()
|
||||||
|
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
|
await toolbar.waitUntilSketchingReady()
|
||||||
|
|
||||||
// Switch between sketch tools via the command bar
|
// Switch between sketch tools via the command bar
|
||||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
@ -11,7 +11,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ page, context, scene, tronApp }, testInfo) => {
|
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
@ -47,7 +47,8 @@ test(
|
|||||||
const exportButton = page.getByTestId('export-pane-button')
|
const exportButton = page.getByTestId('export-pane-button')
|
||||||
await expect(exportButton).toBeVisible()
|
await expect(exportButton).toBeVisible()
|
||||||
|
|
||||||
await scene.waitForExecutionDone()
|
// Wait for the model to finish loading
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const gltfOption = page.getByText('glTF')
|
const gltfOption = page.getByText('glTF')
|
||||||
const submitButton = page.getByText('Confirm Export')
|
const submitButton = page.getByText('Confirm Export')
|
||||||
@ -120,8 +121,7 @@ test(
|
|||||||
// Close the file pane
|
// Close the file pane
|
||||||
await u.closeFilePanel()
|
await u.closeFilePanel()
|
||||||
|
|
||||||
// FIXME: await scene.waitForExecutionDone() does not work. The modeling indicator stays in -receive-reliable and not execution done
|
await scene.settled(cmdBar)
|
||||||
await page.waitForTimeout(10000)
|
|
||||||
|
|
||||||
// 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()
|
||||||
|
@ -78,12 +78,14 @@ sketch001 = startSketchOn(XY)
|
|||||||
|
|
||||||
// Ensure we execute the first time.
|
// Ensure we execute the first time.
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await expect(
|
await expect
|
||||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
.poll(() =>
|
||||||
).toHaveCount(1)
|
page.locator('[data-receive-command-type="scene_clear_all"]').count()
|
||||||
await expect(
|
)
|
||||||
page.locator('[data-message-type="execution-done"]')
|
.toBe(1)
|
||||||
).toHaveCount(2)
|
await expect
|
||||||
|
.poll(() => page.locator('[data-message-type="execution-done"]').count())
|
||||||
|
.toBe(2)
|
||||||
|
|
||||||
// Add whitespace to the end of the code.
|
// Add whitespace to the end of the code.
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
@ -110,12 +112,14 @@ sketch001 = startSketchOn(XY)
|
|||||||
test('ensure we use the cache, and do not clear on append', async ({
|
test('ensure we use the cache, and do not clear on append', async ({
|
||||||
homePage,
|
homePage,
|
||||||
page,
|
page,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type(`sketch001 = startSketchOn(XY)
|
await page.keyboard.type(`sketch001 = startSketchOn(XY)
|
||||||
@ -499,7 +503,7 @@ sketch_001 = startSketchOn(XY)
|
|||||||
await page.keyboard.press('ArrowLeft')
|
await page.keyboard.press('ArrowLeft')
|
||||||
await page.keyboard.press('ArrowRight')
|
await page.keyboard.press('ArrowRight')
|
||||||
|
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
@ -64,7 +64,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
test(
|
test(
|
||||||
'User can go to definition and go to function definition',
|
'User can go to definition and go to function definition',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, homePage, scene, editor, toolbar }) => {
|
async ({ context, homePage, scene, editor, toolbar, cmdBar, page }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'test-sample')
|
const bracketDir = join(dir, 'test-sample')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -86,9 +86,13 @@ test.describe('Feature Tree pane', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await homePage.openProject('test-sample')
|
await homePage.openProject('test-sample')
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
await editor.closePane()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await toolbar.openFeatureTreePane()
|
await toolbar.openFeatureTreePane()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Feature tree').count())
|
||||||
|
.toBeGreaterThan(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function testViewSource({
|
async function testViewSource({
|
||||||
@ -254,7 +258,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await homePage.openProject('test-sample')
|
await homePage.openProject('test-sample')
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await toolbar.openFeatureTreePane()
|
await toolbar.openFeatureTreePane()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -339,7 +343,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await homePage.openProject('test-sample')
|
await homePage.openProject('test-sample')
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await toolbar.openFeatureTreePane()
|
await toolbar.openFeatureTreePane()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -414,8 +418,7 @@ profile003 = startProfileAt([0, -4.93], sketch001)
|
|||||||
const planeColor: [number, number, number] = [74, 74, 74]
|
const planeColor: [number, number, number] = [74, 74, 74]
|
||||||
|
|
||||||
await homePage.openProject('test-sample')
|
await homePage.openProject('test-sample')
|
||||||
// FIXME: @lf94 has a better way to verify execution completion, in a PR rn
|
await scene.settled(cmdBar)
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
|
|
||||||
await test.step(`Verify we see the sketch`, async () => {
|
await test.step(`Verify we see the sketch`, async () => {
|
||||||
await scene.expectPixelColor(sketchColor, testPoint, 10)
|
await scene.expectPixelColor(sketchColor, testPoint, 10)
|
||||||
|
@ -47,6 +47,7 @@ test.describe('integrations tests', () => {
|
|||||||
await scene.connectionEstablished()
|
await scene.connectionEstablished()
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
await clickObj()
|
await clickObj()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await scene.moveNoWhere()
|
await scene.moveNoWhere()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [
|
activeLines: [
|
||||||
@ -72,11 +73,11 @@ test.describe('integrations tests', () => {
|
|||||||
})
|
})
|
||||||
await test.step('setup for next assertion', async () => {
|
await test.step('setup for next assertion', async () => {
|
||||||
await toolbar.openFile('main.kcl')
|
await toolbar.openFile('main.kcl')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await scene.settled(cmdBar)
|
|
||||||
|
|
||||||
await clickObj()
|
await clickObj()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await scene.moveNoWhere()
|
await scene.moveNoWhere()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [
|
activeLines: [
|
||||||
'|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]',
|
'|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]',
|
||||||
@ -89,7 +90,7 @@ test.describe('integrations tests', () => {
|
|||||||
await toolbar.expectFileTreeState(['main.kcl', fileName])
|
await toolbar.expectFileTreeState(['main.kcl', fileName])
|
||||||
})
|
})
|
||||||
await test.step('check sketch mode is exited when opening a different file', async () => {
|
await test.step('check sketch mode is exited when opening a different file', async () => {
|
||||||
await toolbar.openFile(fileName, { wait: false })
|
await toolbar.openFile(fileName)
|
||||||
|
|
||||||
// check we're out of sketch mode
|
// check we're out of sketch mode
|
||||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||||
|
@ -112,22 +112,16 @@ export class CmdBarFixture {
|
|||||||
* and assumes we are past the `pickCommand` step.
|
* and assumes we are past the `pickCommand` step.
|
||||||
*/
|
*/
|
||||||
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
||||||
// FIXME: Progressing the command bar is a race condition. We have an async useEffect that reports the final state via useCalculateKclExpression. If this does not run quickly enough, it will not "fail" the continue because you can press continue if the state is not ready. E2E tests do not know this.
|
await this.page.waitForTimeout(2000)
|
||||||
// Wait 1250ms to assume the await executeAst of the KCL input field is finished
|
const arrowButton = this.page.getByRole('button', {
|
||||||
await this.page.waitForTimeout(1250)
|
name: 'arrow right Continue',
|
||||||
if (shouldFuzzProgressMethod || Math.random() > 0.5) {
|
})
|
||||||
const arrowButton = this.page.getByRole('button', {
|
if (await arrowButton.isVisible()) {
|
||||||
name: 'arrow right Continue',
|
await arrowButton.click()
|
||||||
})
|
|
||||||
if (await arrowButton.isVisible()) {
|
|
||||||
await arrowButton.click()
|
|
||||||
} else {
|
|
||||||
await this.page
|
|
||||||
.getByRole('button', { name: 'checkmark Submit command' })
|
|
||||||
.click()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await this.page.keyboard.press('Enter')
|
await this.page
|
||||||
|
.getByRole('button', { name: 'checkmark Submit command' })
|
||||||
|
.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ export class AuthenticatedApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initialise(code = '') {
|
async initialise(code = '') {
|
||||||
await setup(this.context, this.page, this.testInfo)
|
const testDir = this.testInfo.outputPath('electron-test-projects-dir')
|
||||||
|
await setup(this.context, this.page, testDir, this.testInfo)
|
||||||
const u = await getUtils(this.page)
|
const u = await getUtils(this.page)
|
||||||
|
|
||||||
await this.page.addInitScript(async (code) => {
|
await this.page.addInitScript(async (code) => {
|
||||||
@ -102,11 +103,11 @@ export class ElectronZoo {
|
|||||||
return resolve(undefined)
|
return resolve(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Date.now() - timeA > 10000) {
|
if (Date.now() - timeA > 3000) {
|
||||||
return resolve(undefined)
|
return resolve(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(checkDisconnected, 0)
|
setTimeout(checkDisconnected, 1)
|
||||||
}
|
}
|
||||||
checkDisconnected()
|
checkDisconnected()
|
||||||
})
|
})
|
||||||
@ -128,11 +129,9 @@ export class ElectronZoo {
|
|||||||
const that = this
|
const that = this
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
timeout: 120000,
|
|
||||||
args: ['.', '--no-sandbox'],
|
args: ['.', '--no-sandbox'],
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
TEST_SETTINGS_FILE_KEY: this.projectDirName,
|
|
||||||
IS_PLAYWRIGHT: 'true',
|
IS_PLAYWRIGHT: 'true',
|
||||||
},
|
},
|
||||||
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
||||||
@ -155,6 +154,7 @@ export class ElectronZoo {
|
|||||||
if (!this.electron) {
|
if (!this.electron) {
|
||||||
this.electron = await electron.launch(options)
|
this.electron = await electron.launch(options)
|
||||||
|
|
||||||
|
this.page = await this.electron.firstWindow()
|
||||||
// Mac takes quite a long time to create the first window in CI.
|
// Mac takes quite a long time to create the first window in CI.
|
||||||
// Turns out we can't trust firstWindow() either. So loop.
|
// Turns out we can't trust firstWindow() either. So loop.
|
||||||
let timeoutId: ReturnType<typeof setTimeout>
|
let timeoutId: ReturnType<typeof setTimeout>
|
||||||
@ -200,7 +200,14 @@ export class ElectronZoo {
|
|||||||
|
|
||||||
await this.context.tracing.startChunk()
|
await this.context.tracing.startChunk()
|
||||||
|
|
||||||
await setup(this.context, this.page, testInfo)
|
// THIS IS ABSOLUTELY NECESSARY TO CHANGE THE PROJECT DIRECTORY BETWEEN
|
||||||
|
// TESTS BECAUSE OF THE ELECTRON INSTANCE REUSE.
|
||||||
|
await this.electron?.evaluate(({ app }, projectDirName) => {
|
||||||
|
// @ts-ignore can't declaration merge see main.ts
|
||||||
|
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
||||||
|
}, this.projectDirName)
|
||||||
|
|
||||||
|
await setup(this.context, this.page, this.projectDirName, testInfo)
|
||||||
|
|
||||||
await this.cleanProjectDir()
|
await this.cleanProjectDir()
|
||||||
|
|
||||||
@ -250,11 +257,6 @@ export class ElectronZoo {
|
|||||||
// return app.reuseWindowForTest();
|
// return app.reuseWindowForTest();
|
||||||
// });
|
// });
|
||||||
|
|
||||||
await this.electron?.evaluate(({ app }, projectDirName) => {
|
|
||||||
// @ts-ignore can't declaration merge see main.ts
|
|
||||||
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
|
||||||
}, this.projectDirName)
|
|
||||||
|
|
||||||
// Always start at the root view
|
// Always start at the root view
|
||||||
await this.page.goto(this.firstUrl)
|
await this.page.goto(this.firstUrl)
|
||||||
|
|
||||||
@ -278,8 +280,9 @@ export class ElectronZoo {
|
|||||||
// Not a problem if it already exists.
|
// Not a problem if it already exists.
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempSettingsFilePath = path.join(
|
const tempSettingsFilePath = path.resolve(
|
||||||
this.projectDirName,
|
this.projectDirName,
|
||||||
|
'..',
|
||||||
SETTINGS_FILE_NAME
|
SETTINGS_FILE_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,21 +43,13 @@ type DragFromHandler = (
|
|||||||
export class SceneFixture {
|
export class SceneFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
public streamWrapper!: Locator
|
public streamWrapper!: Locator
|
||||||
public loadingIndicator!: Locator
|
|
||||||
public networkToggleConnected!: Locator
|
public networkToggleConnected!: Locator
|
||||||
public startEditSketchBtn!: Locator
|
public startEditSketchBtn!: Locator
|
||||||
|
|
||||||
get exeIndicator() {
|
|
||||||
return this.page
|
|
||||||
.getByTestId('model-state-indicator-execution-done')
|
|
||||||
.or(this.page.getByTestId('model-state-indicator-receive-reliable'))
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.streamWrapper = page.getByTestId('stream')
|
this.streamWrapper = page.getByTestId('stream')
|
||||||
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
|
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
|
||||||
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
|
||||||
this.startEditSketchBtn = page
|
this.startEditSketchBtn = page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
||||||
@ -231,10 +223,6 @@ export class SceneFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForExecutionDone = async () => {
|
|
||||||
await expect(this.exeIndicator).toBeVisible({ timeout: 30000 })
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionEstablished = async () => {
|
connectionEstablished = async () => {
|
||||||
const timeout = 30000
|
const timeout = 30000
|
||||||
await expect(this.networkToggleConnected).toBeVisible({ timeout })
|
await expect(this.networkToggleConnected).toBeVisible({ timeout })
|
||||||
@ -243,6 +231,9 @@ export class SceneFixture {
|
|||||||
settled = async (cmdBar: CmdBarFixture) => {
|
settled = async (cmdBar: CmdBarFixture) => {
|
||||||
const u = await getUtils(this.page)
|
const u = await getUtils(this.page)
|
||||||
|
|
||||||
|
await expect(this.startEditSketchBtn).not.toBeDisabled()
|
||||||
|
await expect(this.startEditSketchBtn).toBeVisible()
|
||||||
|
|
||||||
await cmdBar.openCmdBar()
|
await cmdBar.openCmdBar()
|
||||||
await cmdBar.chooseCommand('Settings · app · show debug panel')
|
await cmdBar.chooseCommand('Settings · app · show debug panel')
|
||||||
await cmdBar.selectOption({ name: 'on' }).click()
|
await cmdBar.selectOption({ name: 'on' }).click()
|
||||||
@ -250,10 +241,6 @@ export class SceneFixture {
|
|||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await this.waitForExecutionDone()
|
|
||||||
await expect(this.startEditSketchBtn).not.toBeDisabled()
|
|
||||||
await expect(this.startEditSketchBtn).toBeVisible()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expectPixelColor = async (
|
expectPixelColor = async (
|
||||||
|
@ -84,12 +84,6 @@ export class ToolbarFixture {
|
|||||||
return this.page.getByTestId('app-logo')
|
return this.page.getByTestId('app-logo')
|
||||||
}
|
}
|
||||||
|
|
||||||
get exeIndicator() {
|
|
||||||
return this.page
|
|
||||||
.getByTestId('model-state-indicator-receive-reliable')
|
|
||||||
.or(this.page.getByTestId('model-state-indicator-execution-done'))
|
|
||||||
}
|
|
||||||
|
|
||||||
startSketchPlaneSelection = async () =>
|
startSketchPlaneSelection = async () =>
|
||||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||||
|
|
||||||
@ -165,16 +159,10 @@ export class ToolbarFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Opens file by it's name and waits for execution to finish
|
* Opens file by it's name
|
||||||
*/
|
*/
|
||||||
openFile = async (
|
openFile = async (fileName: string) => {
|
||||||
fileName: string,
|
|
||||||
{ wait }: { wait?: boolean } = { wait: true }
|
|
||||||
) => {
|
|
||||||
await this.filePane.getByText(fileName).click()
|
await this.filePane.getByText(fileName).click()
|
||||||
if (wait) {
|
|
||||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
selectCenterRectangle = async () => {
|
selectCenterRectangle = async () => {
|
||||||
await this.page
|
await this.page
|
||||||
|
@ -10,6 +10,7 @@ test.describe('Import UI tests', () => {
|
|||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const projectDir = path.join(dir, 'import-test')
|
const projectDir = path.join(dir, 'import-test')
|
||||||
@ -61,7 +62,7 @@ sketch002 = startSketchOn(extrude001, seg01)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
await homePage.openProject('import-test')
|
await homePage.openProject('import-test')
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await scene.moveCameraTo(
|
await scene.moveCameraTo(
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
test(
|
test(
|
||||||
'When machine-api server not found butt is disabled and shows the reason',
|
'When machine-api server not found butt is disabled and shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -23,10 +23,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
const notFoundText = 'Machine API server was not discovered'
|
const notFoundText = 'Machine API server was not discovered'
|
||||||
await expect(page.getByText(notFoundText).first()).not.toBeVisible()
|
await expect(page.getByText(notFoundText).first()).not.toBeVisible()
|
||||||
@ -47,7 +44,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'When machine-api server not found home screen & project status shows the reason',
|
'When machine-api server not found home screen & project status shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -71,10 +68,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(page.getByText(notFoundText).nth(1)).not.toBeVisible()
|
await expect(page.getByText(notFoundText).nth(1)).not.toBeVisible()
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ test.describe('Named view tests', () => {
|
|||||||
|
|
||||||
// Create and load project
|
// Create and load project
|
||||||
await createProject({ name: projectName, page })
|
await createProject({ name: projectName, page })
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Create named view
|
// Create named view
|
||||||
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
||||||
@ -110,14 +110,17 @@ test.describe('Named view tests', () => {
|
|||||||
expect(exists).toBe(true)
|
expect(exists).toBe(true)
|
||||||
}).toPass()
|
}).toPass()
|
||||||
|
|
||||||
// Read project.toml into memory
|
await expect(async () => {
|
||||||
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
// Read project.toml into memory
|
||||||
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
||||||
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
|
||||||
|
|
||||||
// Write the entire tomlString to a snapshot.
|
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
||||||
// There are many key/value pairs to check this is a safer match.
|
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
||||||
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
|
|
||||||
|
// Write the entire tomlString to a snapshot.
|
||||||
|
// There are many key/value pairs to check this is a safer match.
|
||||||
|
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
|
||||||
|
}).toPass()
|
||||||
})
|
})
|
||||||
test('Verify named view gets deleted', async ({
|
test('Verify named view gets deleted', async ({
|
||||||
cmdBar,
|
cmdBar,
|
||||||
@ -130,7 +133,7 @@ test.describe('Named view tests', () => {
|
|||||||
|
|
||||||
// Create project and go into the project
|
// Create project and go into the project
|
||||||
await createProject({ name: projectName, page })
|
await createProject({ name: projectName, page })
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Create a new named view
|
// Create a new named view
|
||||||
await cmdBar.openCmdBar()
|
await cmdBar.openCmdBar()
|
||||||
@ -152,14 +155,16 @@ test.describe('Named view tests', () => {
|
|||||||
expect(exists).toBe(true)
|
expect(exists).toBe(true)
|
||||||
}).toPass()
|
}).toPass()
|
||||||
|
|
||||||
// Read project.toml into memory
|
await expect(async () => {
|
||||||
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
// Read project.toml into memory
|
||||||
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
||||||
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
||||||
|
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
||||||
|
|
||||||
// Write the entire tomlString to a snapshot.
|
// Write the entire tomlString to a snapshot.
|
||||||
// There are many key/value pairs to check this is a safer match.
|
// There are many key/value pairs to check this is a safer match.
|
||||||
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
|
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
|
||||||
|
}).toPass()
|
||||||
|
|
||||||
// Delete a named view
|
// Delete a named view
|
||||||
await cmdBar.openCmdBar()
|
await cmdBar.openCmdBar()
|
||||||
@ -167,14 +172,16 @@ test.describe('Named view tests', () => {
|
|||||||
cmdBar.selectOption({ name: myNamedView2 })
|
cmdBar.selectOption({ name: myNamedView2 })
|
||||||
await cmdBar.progressCmdBar(false)
|
await cmdBar.progressCmdBar(false)
|
||||||
|
|
||||||
// Read project.toml into memory again since we deleted a named view
|
await expect(async () => {
|
||||||
tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
// Read project.toml into memory again since we deleted a named view
|
||||||
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
||||||
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
||||||
|
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
||||||
|
|
||||||
// // Write the entire tomlString to a snapshot.
|
// // Write the entire tomlString to a snapshot.
|
||||||
// // There are many key/value pairs to check this is a safer match.
|
// // There are many key/value pairs to check this is a safer match.
|
||||||
expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted')
|
expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted')
|
||||||
|
}).toPass()
|
||||||
})
|
})
|
||||||
test('Verify named view gets loaded', async ({
|
test('Verify named view gets loaded', async ({
|
||||||
cmdBar,
|
cmdBar,
|
||||||
@ -186,7 +193,7 @@ test.describe('Named view tests', () => {
|
|||||||
|
|
||||||
// Create project and go into the project
|
// Create project and go into the project
|
||||||
await createProject({ name: projectName, page })
|
await createProject({ name: projectName, page })
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Create a new named view
|
// Create a new named view
|
||||||
await cmdBar.openCmdBar()
|
await cmdBar.openCmdBar()
|
||||||
@ -208,14 +215,16 @@ test.describe('Named view tests', () => {
|
|||||||
expect(exists).toBe(true)
|
expect(exists).toBe(true)
|
||||||
}).toPass()
|
}).toPass()
|
||||||
|
|
||||||
// Read project.toml into memory
|
await expect(async () => {
|
||||||
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
// Read project.toml into memory
|
||||||
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
||||||
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
||||||
|
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
||||||
|
|
||||||
// Write the entire tomlString to a snapshot.
|
// Write the entire tomlString to a snapshot.
|
||||||
// There are many key/value pairs to check this is a safer match.
|
// There are many key/value pairs to check this is a safer match.
|
||||||
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
|
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
|
||||||
|
}).toPass()
|
||||||
|
|
||||||
// Create a load a named view
|
// Create a load a named view
|
||||||
await cmdBar.openCmdBar()
|
await cmdBar.openCmdBar()
|
||||||
@ -239,7 +248,7 @@ test.describe('Named view tests', () => {
|
|||||||
|
|
||||||
// Create and load project
|
// Create and load project
|
||||||
await createProject({ name: projectName, page })
|
await createProject({ name: projectName, page })
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Create named view
|
// Create named view
|
||||||
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
||||||
@ -282,13 +291,15 @@ test.describe('Named view tests', () => {
|
|||||||
expect(exists).toBe(true)
|
expect(exists).toBe(true)
|
||||||
}).toPass()
|
}).toPass()
|
||||||
|
|
||||||
// Read project.toml into memory
|
await expect(async () => {
|
||||||
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
// Read project.toml into memory
|
||||||
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
|
||||||
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
|
||||||
|
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
|
||||||
|
|
||||||
// Write the entire tomlString to a snapshot.
|
// Write the entire tomlString to a snapshot.
|
||||||
// There are many key/value pairs to check this is a safer match.
|
// There are many key/value pairs to check this is a safer match.
|
||||||
expect(tomlString).toMatchSnapshot('verify-two-named-view-gets-created')
|
expect(tomlString).toMatchSnapshot('verify-two-named-view-gets-created')
|
||||||
|
}).toPass()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -12,13 +12,21 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const newProject =
|
async () =>
|
||||||
app.applicationMenu.getMenuItemById('File.New project')
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
if (!newProject) fail()
|
if (!app || !app.applicationMenu) {
|
||||||
newProject.click()
|
return false
|
||||||
})
|
}
|
||||||
|
const newProject =
|
||||||
|
app.applicationMenu.getMenuItemById('File.New project')
|
||||||
|
if (!newProject) return false
|
||||||
|
newProject.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.toBe(true)
|
||||||
// Check that the command bar is opened
|
// Check that the command bar is opened
|
||||||
await expect(cmdBar.cmdBarElement).toBeVisible()
|
await expect(cmdBar.cmdBarElement).toBeVisible()
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
@ -32,13 +40,21 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const openProject =
|
async () =>
|
||||||
app.applicationMenu.getMenuItemById('File.Open project')
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
if (!openProject) fail()
|
if (!app || !app.applicationMenu) return false
|
||||||
openProject.click()
|
const openProject =
|
||||||
})
|
app.applicationMenu.getMenuItemById('File.Open project')
|
||||||
|
if (!openProject) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
openProject.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.toBe(true)
|
||||||
// Check that the command bar is opened
|
// Check that the command bar is opened
|
||||||
await expect(cmdBar.cmdBarElement).toBeVisible()
|
await expect(cmdBar.cmdBarElement).toBeVisible()
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
@ -56,14 +72,23 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const userSettings = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'File.Preferences.User settings'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
console.log(app)
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const userSettings = app.applicationMenu.getMenuItemById(
|
||||||
|
'File.Preferences.User settings'
|
||||||
|
)
|
||||||
|
if (!userSettings) return false
|
||||||
|
userSettings.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!userSettings) fail()
|
.toBe(true)
|
||||||
userSettings.click()
|
|
||||||
})
|
|
||||||
const settings = page.getByTestId('settings-dialog-panel')
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
await expect(settings).toBeVisible()
|
await expect(settings).toBeVisible()
|
||||||
// You are viewing the user tab
|
// You are viewing the user tab
|
||||||
@ -77,17 +102,27 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
if (!tronApp) fail()
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const keybindings = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'File.Preferences.Keybindings'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const keybindings = app.applicationMenu.getMenuItemById(
|
||||||
|
'File.Preferences.Keybindings'
|
||||||
|
)
|
||||||
|
if (!keybindings) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
keybindings.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!keybindings) fail()
|
.toBe(true)
|
||||||
keybindings.click()
|
|
||||||
})
|
|
||||||
const settings = page.getByTestId('settings-dialog-panel')
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
await expect(settings).toBeVisible()
|
await expect(settings).toBeVisible()
|
||||||
// You are viewing the keybindings tab
|
// You are viewing the keybindings tab
|
||||||
@ -102,14 +137,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'File.Preferences.User default units'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'File.Preferences.User default units'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
const settings = page.getByTestId('settings-dialog-panel')
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
await expect(settings).toBeVisible()
|
await expect(settings).toBeVisible()
|
||||||
const defaultUnit = settings.locator('#defaultUnit')
|
const defaultUnit = settings.locator('#defaultUnit')
|
||||||
@ -119,14 +162,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'File.Preferences.Theme'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'File.Preferences.Theme'
|
||||||
|
)
|
||||||
|
if (!menu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Check that the command bar is opened
|
// Check that the command bar is opened
|
||||||
await expect(cmdBar.cmdBarElement).toBeVisible()
|
await expect(cmdBar.cmdBarElement).toBeVisible()
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
@ -144,14 +195,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'File.Preferences.Theme color'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'File.Preferences.Theme color'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
const settings = page.getByTestId('settings-dialog-panel')
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
await expect(settings).toBeVisible()
|
await expect(settings).toBeVisible()
|
||||||
const defaultUnit = settings.locator('#themeColor')
|
const defaultUnit = settings.locator('#themeColor')
|
||||||
@ -165,13 +224,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById('File.Sign out')
|
async () =>
|
||||||
if (!menu) fail()
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
// FIXME: Add back when you can actually sign out
|
if (!app || !app.applicationMenu) return false
|
||||||
// menu.click()
|
const menu =
|
||||||
})
|
app.applicationMenu.getMenuItemById('File.Sign out')
|
||||||
|
if (!menu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// FIXME: Add back when you can actually sign out
|
||||||
|
// menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.toBe(true)
|
||||||
// FIXME: When signing out during E2E the page is not bound correctly.
|
// FIXME: When signing out during E2E the page is not bound correctly.
|
||||||
// It cannot find the button
|
// It cannot find the button
|
||||||
// const signIn = page.getByTestId('sign-in-button')
|
// const signIn = page.getByTestId('sign-in-button')
|
||||||
@ -184,14 +252,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Edit.Rename project'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Edit.Rename project'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
const actual = await cmdBar.cmdBarElement
|
const actual = await cmdBar.cmdBarElement
|
||||||
.getByTestId('command-name')
|
.getByTestId('command-name')
|
||||||
@ -203,20 +279,27 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Edit.Delete project'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Edit.Delete project'
|
||||||
|
)
|
||||||
|
if (!menu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
const actual = await cmdBar.cmdBarElement
|
const actual = async () =>
|
||||||
.getByTestId('command-name')
|
cmdBar.cmdBarElement.getByTestId('command-name').textContent()
|
||||||
.textContent()
|
|
||||||
const expected = 'Delete project'
|
const expected = 'Delete project'
|
||||||
expect(actual).toBe(expected)
|
await expect.poll(async () => await actual()).toBe(expected)
|
||||||
})
|
})
|
||||||
test('Home.Edit.Change project directory', async ({
|
test('Home.Edit.Change project directory', async ({
|
||||||
tronApp,
|
tronApp,
|
||||||
@ -226,14 +309,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Edit.Change project directory'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Edit.Change project directory'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
const settings = page.getByTestId('settings-dialog-panel')
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
await expect(settings).toBeVisible()
|
await expect(settings).toBeVisible()
|
||||||
const projectDirectory = settings.locator('#projectDirectory')
|
const projectDirectory = settings.locator('#projectDirectory')
|
||||||
@ -249,14 +340,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'View.Command Palette...'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'View.Command Palette...'
|
||||||
|
)
|
||||||
|
if (!menu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
|
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
|
||||||
await expect(actual).toBeVisible()
|
await expect(actual).toBeVisible()
|
||||||
@ -267,14 +366,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Help.Show all commands'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Help.Show all commands'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
|
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
|
||||||
await expect(actual).toBeVisible()
|
await expect(actual).toBeVisible()
|
||||||
@ -283,13 +390,21 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Help.KCL code samples'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Help.KCL code samples'
|
||||||
|
)
|
||||||
|
if (!menu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
test('Home.Help.Refresh and report a bug', async ({
|
test('Home.Help.Refresh and report a bug', async ({
|
||||||
tronApp,
|
tronApp,
|
||||||
@ -299,14 +414,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Help.Refresh and report a bug'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Help.Refresh and report a bug'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Core dump and refresh magic number timeout
|
// Core dump and refresh magic number timeout
|
||||||
await page.waitForTimeout(7000)
|
await page.waitForTimeout(7000)
|
||||||
const actual = page.getByText(
|
const actual = page.getByText(
|
||||||
@ -318,14 +441,22 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Help.Reset onboarding'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Help.Reset onboarding'
|
||||||
|
)
|
||||||
|
if (!menu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
const actual = page.getByText(
|
const actual = page.getByText(
|
||||||
`This is a hardware design tool that lets you edit visually, with code, or both. It's powered by the KittyCAD Design API, the first API created for anyone to build hardware design tools.`
|
`This is a hardware design tool that lets you edit visually, with code, or both. It's powered by the KittyCAD Design API, the first API created for anyone to build hardware design tools.`
|
||||||
@ -345,7 +476,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
}) => {
|
}) => {
|
||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -377,7 +508,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -413,7 +544,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -450,7 +581,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -487,7 +618,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -523,7 +654,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -560,7 +691,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -596,7 +727,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -630,7 +761,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -663,7 +794,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -700,7 +831,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -733,21 +864,28 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) {
|
.poll(
|
||||||
throw new Error('app or app.applicationMenu is missing')
|
async () =>
|
||||||
}
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
const menu = app.applicationMenu.getMenuItemById('File.Sign out')
|
if (!app || !app.applicationMenu) {
|
||||||
if (!menu) {
|
throw new Error('app or app.applicationMenu is missing')
|
||||||
throw new Error('File.Sign out')
|
}
|
||||||
}
|
const menu =
|
||||||
// FIXME: Add back when you can actually sign out
|
app.applicationMenu.getMenuItemById('File.Sign out')
|
||||||
// menu.click()
|
if (!menu) {
|
||||||
})
|
throw new Error('File.Sign out')
|
||||||
|
}
|
||||||
|
// FIXME: Add back when you can actually sign out
|
||||||
|
// menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.toBe(true)
|
||||||
// FIXME: When signing out during E2E the page is not bound correctly.
|
// FIXME: When signing out during E2E the page is not bound correctly.
|
||||||
// It cannot find the button
|
// It cannot find the button
|
||||||
// const signIn = page.getByTestId('sign-in-button')
|
// const signIn = page.getByTestId('sign-in-button')
|
||||||
@ -767,7 +905,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -802,7 +940,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -837,7 +975,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -867,7 +1005,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -902,7 +1040,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -937,7 +1075,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
@ -971,7 +1109,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1003,7 +1141,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1039,7 +1177,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1075,7 +1213,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1112,7 +1250,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1141,7 +1279,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1170,7 +1308,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1199,7 +1337,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1228,7 +1366,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1257,7 +1395,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1286,7 +1424,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1315,7 +1453,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1344,7 +1482,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1381,7 +1519,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1418,7 +1556,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1455,7 +1593,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1488,7 +1626,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1521,7 +1659,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1554,7 +1692,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1587,7 +1725,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1621,7 +1759,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1658,7 +1796,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1695,7 +1833,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1733,7 +1871,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1770,7 +1908,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1807,7 +1945,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1844,7 +1982,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1881,7 +2019,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1918,7 +2056,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1956,7 +2094,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -1994,7 +2132,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -2032,7 +2170,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -2071,7 +2209,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -2099,7 +2237,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
@ -2124,20 +2262,26 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
await tronApp.electron.evaluate(async ({ app }) => {
|
await expect
|
||||||
if (!app || !app.applicationMenu) fail()
|
.poll(
|
||||||
const menu = app.applicationMenu.getMenuItemById(
|
async () =>
|
||||||
'Help.Refresh and report a bug'
|
await tronApp.electron.evaluate(async ({ app }) => {
|
||||||
|
if (!app || !app.applicationMenu) return false
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(
|
||||||
|
'Help.Refresh and report a bug'
|
||||||
|
)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!menu) fail()
|
.toBe(true)
|
||||||
menu.click()
|
|
||||||
})
|
|
||||||
// Core dump and refresh magic number timeout
|
// Core dump and refresh magic number timeout
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
await expect(toolbar.startSketchBtn).toBeVisible()
|
await expect(toolbar.startSketchBtn).toBeVisible()
|
||||||
})
|
})
|
||||||
test('Modeling.Help.Reset onboarding', async ({
|
test('Modeling.Help.Reset onboarding', async ({
|
||||||
@ -2152,7 +2296,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Run electron snippet to find the Menu!
|
// Run electron snippet to find the Menu!
|
||||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||||
|
@ -5,13 +5,14 @@ import path from 'node:path'
|
|||||||
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||||
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||||
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||||
import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
// test file is for testing point an click code gen functionality that's not sketch mode related
|
// test file is for testing point an click code gen functionality that's not sketch mode related
|
||||||
|
|
||||||
test.describe('Point-and-click tests', () => {
|
test.describe('Point-and-click tests', () => {
|
||||||
test('verify extruding circle works', async ({
|
test('verify extruding circle works', async ({
|
||||||
|
page,
|
||||||
context,
|
context,
|
||||||
homePage,
|
homePage,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
@ -30,8 +31,9 @@ test.describe('Point-and-click tests', () => {
|
|||||||
await context.addInitScript((file) => {
|
await context.addInitScript((file) => {
|
||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||||
|
|
||||||
@ -72,7 +74,6 @@ test.describe('Point-and-click tests', () => {
|
|||||||
|
|
||||||
await test.step('do extrude flow and check extrude code is added to editor', async () => {
|
await test.step('do extrude flow and check extrude code is added to editor', async () => {
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
|
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'distance',
|
currentArgKey: 'distance',
|
||||||
@ -186,6 +187,7 @@ test.describe('Point-and-click tests', () => {
|
|||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const file = await fs.readFile(
|
const file = await fs.readFile(
|
||||||
path.resolve(
|
path.resolve(
|
||||||
@ -200,9 +202,7 @@ test.describe('Point-and-click tests', () => {
|
|||||||
}, file)
|
}, file)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await expect(
|
await scene.settled(cmdBar)
|
||||||
page.getByTestId('model-state-indicator-receive-reliable')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||||
|
|
||||||
@ -377,6 +377,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const file = await fs.readFile(
|
const file = await fs.readFile(
|
||||||
path.resolve(
|
path.resolve(
|
||||||
@ -392,7 +393,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||||
|
|
||||||
@ -479,6 +480,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
await page.setBodyDimensions(viewPortSize)
|
await page.setBodyDimensions(viewPortSize)
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
// These are mappings from screenspace to KCL coordinates,
|
// These are mappings from screenspace to KCL coordinates,
|
||||||
@ -537,8 +539,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
await toolbar.startSketchPlaneSelection()
|
await toolbar.startSketchPlaneSelection()
|
||||||
await moveToXzPlane()
|
await moveToXzPlane()
|
||||||
await clickOnXzPlane()
|
await clickOnXzPlane()
|
||||||
// timeout wait for engine animation is unavoidable
|
await toolbar.waitUntilSketchingReady()
|
||||||
await page.waitForTimeout(600)
|
|
||||||
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
||||||
})
|
})
|
||||||
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
||||||
@ -580,9 +581,8 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
const initialCode = `closedSketch = startSketchOn(XZ)
|
const initialCode = `closedSketch = startSketchOn(XZ)
|
||||||
|> circle(center = [8, 5], radius = 2)
|
|> circle(center = [8, 5], radius = 2)
|
||||||
openSketch = startSketchOn(XY)
|
openSketch = startSketchOn(XY)
|
||||||
@ -599,8 +599,6 @@ openSketch = startSketchOn(XY)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
|
|
||||||
const pointInsideCircle = {
|
const pointInsideCircle = {
|
||||||
x: viewPortSize.width * 0.63,
|
x: viewPortSize.width * 0.63,
|
||||||
@ -625,15 +623,16 @@ openSketch = startSketchOn(XY)
|
|||||||
const exitSketch = async () => {
|
const exitSketch = async () => {
|
||||||
await test.step(`Exit sketch mode`, async () => {
|
await test.step(`Exit sketch mode`, async () => {
|
||||||
await toolbar.exitSketchBtn.click()
|
await toolbar.exitSketchBtn.click()
|
||||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
|
||||||
await expect(toolbar.startSketchBtn).toBeEnabled()
|
await expect(toolbar.startSketchBtn).toBeEnabled()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await test.step(`Double-click on the closed sketch`, async () => {
|
await test.step(`Double-click on the closed sketch`, async () => {
|
||||||
|
await scene.settled(cmdBar)
|
||||||
await moveToCircle()
|
await moveToCircle()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await dblClickCircle()
|
await dblClickCircle()
|
||||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
await page.waitForTimeout(1000)
|
||||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [`|>circle(center=[8,5],radius=2)`],
|
activeLines: [`|>circle(center=[8,5],radius=2)`],
|
||||||
@ -670,7 +669,6 @@ openSketch = startSketchOn(XY)
|
|||||||
// There is a full execution after exiting sketch that clears the scene.
|
// There is a full execution after exiting sketch that clears the scene.
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await dblClickOpenPath()
|
await dblClickOpenPath()
|
||||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
|
||||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
// Wait for enter sketch mode to complete
|
// Wait for enter sketch mode to complete
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
@ -1031,6 +1029,9 @@ openSketch = startSketchOn(XY)
|
|||||||
})
|
})
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
await toolbar.offsetPlaneButton.click()
|
await toolbar.offsetPlaneButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'plane',
|
currentArgKey: 'plane',
|
||||||
@ -1088,6 +1089,7 @@ openSketch = startSketchOn(XY)
|
|||||||
const expectedLine = `axis=X,`
|
const expectedLine = `axis=X,`
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
await toolbar.helixButton.click()
|
await toolbar.helixButton.click()
|
||||||
@ -1106,6 +1108,7 @@ openSketch = startSketchOn(XY)
|
|||||||
commandName: 'Helix',
|
commandName: 'Helix',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await expect.poll(() => page.getByText('Axis').count()).toBe(6)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -1233,6 +1236,7 @@ openSketch = startSketchOn(XY)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
await toolbar.closePane('code')
|
await toolbar.closePane('code')
|
||||||
@ -1252,15 +1256,22 @@ openSketch = startSketchOn(XY)
|
|||||||
commandName: 'Helix',
|
commandName: 'Helix',
|
||||||
})
|
})
|
||||||
await cmdBar.selectOption({ name: 'Edge' }).click()
|
await cmdBar.selectOption({ name: 'Edge' }).click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await clickOnEdge()
|
await clickOnEdge()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await cmdBar.argumentInput.focus()
|
await cmdBar.argumentInput.focus()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await page.keyboard.insertText('20')
|
await page.keyboard.insertText('20')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await page.keyboard.insertText('0')
|
await page.keyboard.insertText('0')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await page.keyboard.insertText('1')
|
await page.keyboard.insertText('1')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.keyboard.insertText('100')
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
@ -1274,6 +1285,7 @@ openSketch = startSketchOn(XY)
|
|||||||
commandName: 'Helix',
|
commandName: 'Helix',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
@ -1369,7 +1381,7 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 620, y: 257 }
|
const testPoint = { x: 620, y: 257 }
|
||||||
@ -1530,6 +1542,9 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
if (!shouldPreselect) {
|
if (!shouldPreselect) {
|
||||||
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
|
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
|
||||||
await toolbar.loftButton.click()
|
await toolbar.loftButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'selection',
|
currentArgKey: 'selection',
|
||||||
@ -1579,6 +1594,7 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||||
|> circle(center = [0, 0], radius = 30)
|
|> circle(center = [0, 0], radius = 30)
|
||||||
@ -1592,7 +1608,7 @@ loft001 = loft([sketch001, sketch002])
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
@ -1687,7 +1703,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
@ -1707,6 +1723,9 @@ sketch002 = startSketchOn(XZ)
|
|||||||
|
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
await toolbar.sweepButton.click()
|
await toolbar.sweepButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'target',
|
currentArgKey: 'target',
|
||||||
@ -1826,7 +1845,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 700, y: 250 }
|
const testPoint = { x: 700, y: 250 }
|
||||||
@ -1843,6 +1862,9 @@ sketch002 = startSketchOn(XZ)
|
|||||||
|
|
||||||
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
|
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
|
||||||
await toolbar.sweepButton.click()
|
await toolbar.sweepButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'target',
|
currentArgKey: 'target',
|
||||||
@ -2059,6 +2081,9 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
await test.step(`Open fillet UI without selecting edges`, async () => {
|
await test.step(`Open fillet UI without selecting edges`, async () => {
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await toolbar.filletButton.click()
|
await toolbar.filletButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'selection',
|
currentArgKey: 'selection',
|
||||||
@ -2184,6 +2209,7 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn(XY)
|
const initialCode = `sketch001 = startSketchOn(XY)
|
||||||
profile001 = circle(
|
profile001 = circle(
|
||||||
@ -2200,7 +2226,7 @@ fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await test.step('Double-click in feature tree and expect error toast', async () => {
|
await test.step('Double-click in feature tree and expect error toast', async () => {
|
||||||
await toolbar.openPane('feature-tree')
|
await toolbar.openPane('feature-tree')
|
||||||
@ -2521,7 +2547,7 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 1: Command bar flow with preselected edges
|
// Test 1: Command bar flow with preselected edges
|
||||||
@ -2554,6 +2580,7 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Chamfer',
|
commandName: 'Chamfer',
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
@ -2565,7 +2592,10 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
},
|
},
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
|
await cmdBar.argumentInput.focus()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Chamfer',
|
commandName: 'Chamfer',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
@ -2649,6 +2679,9 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
await test.step(`Open chamfer UI without selecting edges`, async () => {
|
await test.step(`Open chamfer UI without selecting edges`, async () => {
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await toolbar.chamferButton.click()
|
await toolbar.chamferButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'selection',
|
currentArgKey: 'selection',
|
||||||
@ -2771,6 +2804,7 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// Code samples
|
// Code samples
|
||||||
const initialCode = `@settings(defaultLengthUnit = in)
|
const initialCode = `@settings(defaultLengthUnit = in)
|
||||||
@ -2814,7 +2848,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// verify modeling scene is loaded
|
// verify modeling scene is loaded
|
||||||
await scene.expectPixelColor(
|
await scene.expectPixelColor(
|
||||||
@ -2936,9 +2970,11 @@ extrude001 = extrude(sketch001, length = 30)
|
|||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
localStorage.setItem('persistCode', initialCode)
|
localStorage.setItem('persistCode', initialCode)
|
||||||
}, initialCode)
|
}, initialCode)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
@ -2955,6 +2991,9 @@ extrude001 = extrude(sketch001, length = 30)
|
|||||||
if (!shouldPreselect) {
|
if (!shouldPreselect) {
|
||||||
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
||||||
await toolbar.shellButton.click()
|
await toolbar.shellButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'selection',
|
currentArgKey: 'selection',
|
||||||
@ -3015,7 +3054,6 @@ extrude001 = extrude(sketch001, length = 30)
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Edit shell via feature tree selection works', async () => {
|
await test.step('Edit shell via feature tree selection works', async () => {
|
||||||
await toolbar.closePane('code')
|
|
||||||
await toolbar.openPane('feature-tree')
|
await toolbar.openPane('feature-tree')
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
'Shell',
|
'Shell',
|
||||||
@ -3044,7 +3082,6 @@ extrude001 = extrude(sketch001, length = 30)
|
|||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await toolbar.closePane('feature-tree')
|
await toolbar.closePane('feature-tree')
|
||||||
await scene.expectPixelColor([150, 150, 150], testPoint, 15)
|
await scene.expectPixelColor([150, 150, 150], testPoint, 15)
|
||||||
await toolbar.openPane('code')
|
|
||||||
await editor.expectEditor.toContain(editedShellDeclaration)
|
await editor.expectEditor.toContain(editedShellDeclaration)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
@ -3079,7 +3116,7 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 580, y: 180 }
|
const testPoint = { x: 580, y: 180 }
|
||||||
@ -3097,6 +3134,9 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
|
|
||||||
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
||||||
await toolbar.shellButton.click()
|
await toolbar.shellButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'selection',
|
currentArgKey: 'selection',
|
||||||
@ -3108,6 +3148,9 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
highlightedHeaderArg: 'selection',
|
highlightedHeaderArg: 'selection',
|
||||||
commandName: 'Shell',
|
commandName: 'Shell',
|
||||||
})
|
})
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await clickOnWall()
|
await clickOnWall()
|
||||||
@ -3116,6 +3159,7 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
@ -3124,7 +3168,9 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
},
|
},
|
||||||
commandName: 'Shell',
|
commandName: 'Shell',
|
||||||
})
|
})
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
@ -3139,7 +3185,6 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Edit shell via feature tree selection works', async () => {
|
await test.step('Edit shell via feature tree selection works', async () => {
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
|
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
|
||||||
await operationButton.dblclick({ button: 'left' })
|
await operationButton.dblclick({ button: 'left' })
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -3154,6 +3199,7 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
})
|
})
|
||||||
await page.keyboard.insertText('1')
|
await page.keyboard.insertText('1')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
@ -3164,7 +3210,6 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await toolbar.closePane('feature-tree')
|
await toolbar.closePane('feature-tree')
|
||||||
await scene.expectPixelColor([150, 150, 150], testPoint, 15)
|
await scene.expectPixelColor([150, 150, 150], testPoint, 15)
|
||||||
await toolbar.openPane('code')
|
|
||||||
await editor.expectEditor.toContain(editedShellDeclaration)
|
await editor.expectEditor.toContain(editedShellDeclaration)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
@ -3218,7 +3263,7 @@ extrude002 = extrude(sketch002, length = 50)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 580, y: 320 }
|
const testPoint = { x: 580, y: 320 }
|
||||||
@ -3243,12 +3288,13 @@ extrude002 = extrude(sketch002, length = 50)
|
|||||||
highlightedHeaderArg: 'selection',
|
highlightedHeaderArg: 'selection',
|
||||||
commandName: 'Shell',
|
commandName: 'Shell',
|
||||||
})
|
})
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(1000)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
@ -3306,7 +3352,7 @@ profile001 = startProfileAt([-20, 20], sketch001)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await toolbar.openPane('feature-tree')
|
await toolbar.openPane('feature-tree')
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
@ -3386,7 +3432,7 @@ sweep001 = sweep(sketch001, path = sketch002)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 500, y: 250 }
|
const testPoint = { x: 500, y: 250 }
|
||||||
@ -3399,6 +3445,9 @@ sweep001 = sweep(sketch001, path = sketch002)
|
|||||||
|
|
||||||
await test.step(`Go through the Shell flow and fail validation with a toast`, async () => {
|
await test.step(`Go through the Shell flow and fail validation with a toast`, async () => {
|
||||||
await toolbar.shellButton.click()
|
await toolbar.shellButton.click()
|
||||||
|
await expect
|
||||||
|
.poll(() => page.getByText('Please select one').count())
|
||||||
|
.toBe(1)
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'selection',
|
currentArgKey: 'selection',
|
||||||
@ -3462,12 +3511,13 @@ segAng(rectangleSegmentA002),
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// select line of code
|
// select line of code
|
||||||
const codeToSelecton = `segAng(rectangleSegmentA002) - 90,`
|
const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
|
||||||
// revolve
|
// revolve
|
||||||
await page.getByText(codeToSelecton).click()
|
await editor.scrollToText(codeToSelection)
|
||||||
|
await page.getByText(codeToSelection).click()
|
||||||
await toolbar.revolveButton.click()
|
await toolbar.revolveButton.click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -3541,15 +3591,17 @@ sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// select line of code
|
// select line of code
|
||||||
const codeToSelecton = `center = [-11.34, 10.0]`
|
const codeToSelection = `center = [-11.34, 10.0]`
|
||||||
// revolve
|
// revolve
|
||||||
await page.getByText(codeToSelecton).click()
|
await editor.scrollToText(codeToSelection)
|
||||||
|
await page.getByText(codeToSelection).click()
|
||||||
await toolbar.revolveButton.click()
|
await toolbar.revolveButton.click()
|
||||||
await page.getByText('Edge', { exact: true }).click()
|
await page.getByText('Edge', { exact: true }).click()
|
||||||
const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)`
|
const lineCodeToSelection = `angledLine([0, 202.6], %, $rectangleSegmentA001)`
|
||||||
await page.getByText(lineCodeToSelection).click()
|
await page.getByText(lineCodeToSelection).click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -3595,6 +3647,7 @@ sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|
|||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
newCodeToFind.replace('angle = 360', 'angle = angle001')
|
newCodeToFind.replace('angle = 360', 'angle = angle001')
|
||||||
)
|
)
|
||||||
|
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||||
})
|
})
|
||||||
test('revolve sketch circle around line segment from startProfileAt sketch', async ({
|
test('revolve sketch circle around line segment from startProfileAt sketch', async ({
|
||||||
context,
|
context,
|
||||||
@ -3628,15 +3681,20 @@ sketch003 = startSketchOn(extrude001, 'START')
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// select line of code
|
// select line of code
|
||||||
const codeToSelecton = `center = [-0.69, 0.56]`
|
const codeToSelection = `center = [-0.69, 0.56]`
|
||||||
// revolve
|
// revolve
|
||||||
await page.getByText(codeToSelecton).click()
|
|
||||||
await toolbar.revolveButton.click()
|
await toolbar.revolveButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await editor.scrollToText(codeToSelection)
|
||||||
|
await page.getByText(codeToSelection).click()
|
||||||
|
await expect.poll(() => page.getByText('AxisOrEdge').count()).toBe(2)
|
||||||
await page.getByText('Edge', { exact: true }).click()
|
await page.getByText('Edge', { exact: true }).click()
|
||||||
const lineCodeToSelection = `|> xLine(length = 2.6)`
|
const lineCodeToSelection = `length = 2.6`
|
||||||
|
await editor.scrollToText(lineCodeToSelection)
|
||||||
await page.getByText(lineCodeToSelection).click()
|
await page.getByText(lineCodeToSelection).click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -3703,7 +3761,7 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 500, y: 250 }
|
const testPoint = { x: 500, y: 250 }
|
||||||
|
@ -83,7 +83,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'click help/keybindings from project page',
|
'click help/keybindings from project page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -95,17 +95,11 @@ test(
|
|||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
// expect to see the text bracket
|
// expect to see the text bracket
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// click ? button
|
// click ? button
|
||||||
await page.getByTestId('help-button').click()
|
await page.getByTestId('help-button').click()
|
||||||
@ -120,7 +114,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page, editor }, testInfo) => {
|
async ({ scene, cmdBar, context, page, editor }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -149,24 +143,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
|
||||||
// user way we can verify it (pixel color)
|
|
||||||
await expect
|
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [110, 110, 110]), {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
.toBeLessThan(20)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||||
@ -209,7 +186,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -235,24 +212,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
|
||||||
// user way we can verify it (pixel color)
|
|
||||||
await expect
|
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||||
@ -352,7 +312,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
if (runningOnWindows()) {
|
if (runningOnWindows()) {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
}
|
}
|
||||||
@ -380,10 +340,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -443,10 +400,10 @@ test(
|
|||||||
await expect(page.getByText('broken-code')).toBeVisible()
|
await expect(page.getByText('broken-code')).toBeVisible()
|
||||||
await page.getByText('broken-code').click()
|
await page.getByText('broken-code').click()
|
||||||
|
|
||||||
// Gotcha: You can not use scene.waitForExecutionDone() since the KCL code is going to fail
|
// Gotcha: You can not use scene.settled() since the KCL code is going to fail
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
await expect(
|
||||||
timeout: 20_000,
|
page.getByTestId('model-state-indicator-playing')
|
||||||
})
|
).toBeAttached()
|
||||||
|
|
||||||
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
|
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
|
||||||
await editor.scrollToText(
|
await editor.scrollToText(
|
||||||
@ -469,7 +426,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
test(
|
test(
|
||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ context, page, tronApp }, testInfo) => {
|
async ({ scene, cmdBar, context, page, tronApp }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
@ -499,10 +456,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -812,7 +766,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Rename from project page`,
|
`Rename from project page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_rename`
|
const projectName = `my_project_to_rename`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -821,7 +775,6 @@ test.describe(`Project management commands`, () => {
|
|||||||
`${dir}/${projectName}/main.kcl`
|
`${dir}/${projectName}/main.kcl`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectHomeLink = page.getByTestId('project-link')
|
const projectHomeLink = page.getByTestId('project-link')
|
||||||
@ -843,7 +796,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await projectHomeLink.click()
|
await projectHomeLink.click()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Run rename command via command palette`, async () => {
|
await test.step(`Run rename command via command palette`, async () => {
|
||||||
@ -882,7 +835,6 @@ test.describe(`Project management commands`, () => {
|
|||||||
`${dir}/${projectName}/main.kcl`
|
`${dir}/${projectName}/main.kcl`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectHomeLink = page.getByTestId('project-link')
|
const projectHomeLink = page.getByTestId('project-link')
|
||||||
@ -900,9 +852,9 @@ test.describe(`Project management commands`, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await page.waitForTimeout(3000)
|
||||||
|
|
||||||
await projectHomeLink.click()
|
await projectHomeLink.click()
|
||||||
await u.waitForPageLoad()
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -926,7 +878,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Rename from home page`,
|
`Rename from home page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page, homePage }, testInfo) => {
|
async ({ context, page, homePage, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_rename`
|
const projectName = `my_project_to_rename`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -982,7 +934,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Delete from home page`,
|
`Delete from home page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_delete`
|
const projectName = `my_project_to_delete`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -1033,6 +985,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
homePage,
|
homePage,
|
||||||
toolbar,
|
toolbar,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const projectName = 'test-project'
|
const projectName = 'test-project'
|
||||||
await test.step(`Setup`, async () => {
|
await test.step(`Setup`, async () => {
|
||||||
@ -1072,10 +1025,11 @@ test.describe(`Project management commands`, () => {
|
|||||||
})
|
})
|
||||||
await cmdBar.argumentInput.fill(projectName)
|
await cmdBar.argumentInput.fill(projectName)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
await toolbar.logoLink.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Check the project was created with a non-colliding name`, async () => {
|
await test.step(`Check the project was created with a non-colliding name`, async () => {
|
||||||
await toolbar.logoLink.click()
|
|
||||||
await homePage.expectState({
|
await homePage.expectState({
|
||||||
projectCards: [
|
projectCards: [
|
||||||
{
|
{
|
||||||
@ -1106,10 +1060,11 @@ test.describe(`Project management commands`, () => {
|
|||||||
})
|
})
|
||||||
await cmdBar.argumentInput.fill(projectName)
|
await cmdBar.argumentInput.fill(projectName)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
await toolbar.logoLink.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Check the second project was created with a non-colliding name`, async () => {
|
await test.step(`Check the second project was created with a non-colliding name`, async () => {
|
||||||
await toolbar.logoLink.click()
|
|
||||||
await homePage.expectState({
|
await homePage.expectState({
|
||||||
projectCards: [
|
projectCards: [
|
||||||
{
|
{
|
||||||
@ -1195,7 +1150,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'Nested directories in project without main.kcl do not create main.kcl',
|
'Nested directories in project without main.kcl do not create main.kcl',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
let testDir: string | undefined
|
let testDir: string | undefined
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(path.join(dir, 'router-template-slate', 'nested'), {
|
await fsp.mkdir(path.join(dir, 'router-template-slate', 'nested'), {
|
||||||
@ -1218,10 +1173,7 @@ test(
|
|||||||
|
|
||||||
await test.step('Open the project', async () => {
|
await test.step('Open the project', async () => {
|
||||||
await page.getByText('router-template-slate').click()
|
await page.getByText('router-template-slate').click()
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// It actually loads.
|
// It actually loads.
|
||||||
await expect(u.codeLocator).toContainText('mounting bracket')
|
await expect(u.codeLocator).toContainText('mounting bracket')
|
||||||
@ -1334,7 +1286,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'Can load a file with CRLF line endings',
|
'Can load a file with CRLF line endings',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
if (runningOnWindows()) {
|
if (runningOnWindows()) {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
}
|
}
|
||||||
@ -1357,13 +1309,8 @@ test(
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
await page.getByText('router-template-slate').click()
|
await page.getByText('router-template-slate').click()
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(u.codeLocator).toContainText('routerDiameter')
|
await expect(u.codeLocator).toContainText('routerDiameter')
|
||||||
await expect(u.codeLocator).toContainText('templateGap')
|
await expect(u.codeLocator).toContainText('templateGap')
|
||||||
@ -1578,7 +1525,7 @@ extrude001 = extrude(sketch001, length = 200)`)
|
|||||||
test(
|
test(
|
||||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page, cmdBar, homePage }, testInfo) => {
|
async ({ context, page, cmdBar, homePage, scene }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(path.join(dir, 'router-template-slate'), {
|
await fsp.mkdir(path.join(dir, 'router-template-slate'), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@ -1607,13 +1554,10 @@ test(
|
|||||||
path.join(dir, 'bracket', 'main.kcl')
|
path.join(dir, 'bracket', 'main.kcl')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
const pointOnModel = { x: 630, y: 280 }
|
|
||||||
|
|
||||||
await test.step('Opening the bracket project via command palette should load the stream', async () => {
|
await test.step('Opening the bracket project via command palette should load the stream', async () => {
|
||||||
await homePage.expectState({
|
await homePage.expectState({
|
||||||
projectCards: [
|
projectCards: [
|
||||||
@ -1647,15 +1591,7 @@ test(
|
|||||||
stage: 'commandBarClosed',
|
stage: 'commandBarClosed',
|
||||||
})
|
})
|
||||||
|
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
|
||||||
// user way we can verify it (pixel color)
|
|
||||||
await expect
|
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||||
@ -1672,15 +1608,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('router-template-slate').click()
|
await page.getByText('router-template-slate').click()
|
||||||
|
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
|
||||||
// user way we can verify it (pixel color)
|
|
||||||
await expect
|
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('The projects on the home page should still be normal', async () => {
|
await test.step('The projects on the home page should still be normal', async () => {
|
||||||
@ -1733,8 +1661,6 @@ test(
|
|||||||
})
|
})
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
// we'll grab this from the settings on screen before we switch
|
// we'll grab this from the settings on screen before we switch
|
||||||
let originalProjectDirName: string
|
let originalProjectDirName: string
|
||||||
const newProjectDirName = testInfo.outputPath(
|
const newProjectDirName = testInfo.outputPath(
|
||||||
@ -1875,7 +1801,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'file pane is scrollable when there are many files',
|
'file pane is scrollable when there are many files',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = path.join(dir, 'testProject')
|
const testDir = path.join(dir, 'testProject')
|
||||||
await fsp.mkdir(testDir, { recursive: true })
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
@ -1954,10 +1880,8 @@ test(
|
|||||||
|
|
||||||
await test.step('setup, open file pane', async () => {
|
await test.step('setup, open file pane', async () => {
|
||||||
await page.getByText('testProject').click()
|
await page.getByText('testProject').click()
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
await scene.settled(cmdBar)
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.getByTestId('files-pane-button').click()
|
await page.getByTestId('files-pane-button').click()
|
||||||
})
|
})
|
||||||
|
@ -63,7 +63,7 @@ test.describe('edit with AI example snapshots', () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 351 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
|
@ -61,7 +61,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 311 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
const greenCheckCoords = { x: 565, y: 305 }
|
const greenCheckCoords = { x: 565, y: 305 }
|
||||||
@ -156,7 +156,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 311 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
@ -212,7 +212,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||||
const successToast = page.getByText('Prompt to edit successful')
|
const successToast = page.getByText('Prompt to edit successful')
|
||||||
@ -281,7 +281,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||||
const successToast = page.getByText('Prompt to edit successful')
|
const successToast = page.getByText('Prompt to edit successful')
|
||||||
|
@ -689,6 +689,7 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
viewport,
|
||||||
}) => {
|
}) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const legoDir = path.join(dir, 'lego')
|
const legoDir = path.join(dir, 'lego')
|
||||||
@ -703,8 +704,8 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
await homePage.openProject('lego')
|
await homePage.openProject('lego')
|
||||||
await toolbar.closePane('code')
|
await toolbar.closePane('code')
|
||||||
})
|
})
|
||||||
await test.step(`Waiting for the loading spinner to disappear`, async () => {
|
await test.step(`Waiting for scene to settle`, async () => {
|
||||||
await scene.loadingIndicator.waitFor({ state: 'detached' })
|
await scene.connectionEstablished()
|
||||||
})
|
})
|
||||||
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
|
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
|
||||||
// TODO: use the viewport size to pick the center point, but the `viewport` fixture's values were wrong.
|
// TODO: use the viewport size to pick the center point, but the `viewport` fixture's values were wrong.
|
||||||
@ -762,7 +763,7 @@ plane002 = offsetPlane(XZ, offset = -2 * x)`
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await homePage.openProject('test-sample')
|
await homePage.openProject('test-sample')
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 20_000 })
|
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 20_000 })
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
'Offset Plane',
|
'Offset Plane',
|
||||||
|
@ -22,6 +22,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
context,
|
context,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const selectionsSnippets = {
|
const selectionsSnippets = {
|
||||||
@ -82,7 +83,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -108,6 +109,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -122,7 +124,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
})
|
})
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 587, y: 270 }, 15)
|
await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 587, y: 270 }, 15)
|
||||||
|
|
||||||
@ -673,6 +675,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -689,7 +692,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
})
|
})
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -1614,7 +1617,7 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
|||||||
test(
|
test(
|
||||||
`snapToProfile start only works for current profile`,
|
`snapToProfile start only works for current profile`,
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ context, page, scene, toolbar, editor, homePage }) => {
|
async ({ context, page, scene, toolbar, editor, homePage, cmdBar }) => {
|
||||||
// We seed the scene with a single offset plane
|
// We seed the scene with a single offset plane
|
||||||
await context.addInitScript(() => {
|
await context.addInitScript(() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -1630,6 +1633,8 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
|||||||
})
|
})
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -1651,9 +1656,13 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
|||||||
const codeFromTangentialArc = ` |> tangentialArcTo([39.49, 88.22], %)`
|
const codeFromTangentialArc = ` |> tangentialArcTo([39.49, 88.22], %)`
|
||||||
await test.step('check that tangential tool does not snap to other profile starts', async () => {
|
await test.step('check that tangential tool does not snap to other profile starts', async () => {
|
||||||
await toolbar.tangentialArcBtn.click()
|
await toolbar.tangentialArcBtn.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await endOfLowerSegMove()
|
await endOfLowerSegMove()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await endOfLowerSegClick()
|
await endOfLowerSegClick()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await profileStartOfHigherSegClick()
|
await profileStartOfHigherSegClick()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await editor.expectEditor.toContain(codeFromTangentialArc)
|
await editor.expectEditor.toContain(codeFromTangentialArc)
|
||||||
await editor.expectEditor.not.toContain(
|
await editor.expectEditor.not.toContain(
|
||||||
`[profileStartX(%), profileStartY(%)]`
|
`[profileStartX(%), profileStartY(%)]`
|
||||||
@ -2242,8 +2251,9 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
|
|||||||
|
|
||||||
await test.step('enter sketch and setup', async () => {
|
await test.step('enter sketch and setup', async () => {
|
||||||
await moveToClearToolBarPopover()
|
await moveToClearToolBarPopover()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await pointOnSegment({ shouldDbClick: true })
|
await pointOnSegment({ shouldDbClick: true })
|
||||||
await page.waitForTimeout(600)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
await toolbar.lineBtn.click()
|
await toolbar.lineBtn.click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
@ -2359,7 +2369,7 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
|
|||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -2965,6 +2975,7 @@ test.describe(`Click based selection don't brick the app when clicked out of ran
|
|||||||
toolbar,
|
toolbar,
|
||||||
editor,
|
editor,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// We seed the scene with a single offset plane
|
// We seed the scene with a single offset plane
|
||||||
await context.addInitScript(() => {
|
await context.addInitScript(() => {
|
||||||
@ -2982,7 +2993,7 @@ test.describe(`Click based selection don't brick the app when clicked out of ran
|
|||||||
})
|
})
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await test.step(`format the code`, async () => {
|
await test.step(`format the code`, async () => {
|
||||||
// doesn't contain condensed version
|
// doesn't contain condensed version
|
||||||
@ -3047,6 +3058,7 @@ test.describe('Redirecting to home page and back to the original file should cle
|
|||||||
toolbar,
|
toolbar,
|
||||||
editor,
|
editor,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// We seed the scene with a single offset plane
|
// We seed the scene with a single offset plane
|
||||||
await context.addInitScript(() => {
|
await context.addInitScript(() => {
|
||||||
@ -3059,7 +3071,7 @@ test.describe('Redirecting to home page and back to the original file should cle
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const [objClick] = scene.makeMouseHelpers(634, 274)
|
const [objClick] = scene.makeMouseHelpers(634, 274)
|
||||||
await objClick()
|
await objClick()
|
||||||
|
@ -103,7 +103,6 @@ part001 = startSketchOn(-XZ)
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
|
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
|
||||||
@ -369,7 +368,6 @@ const extrudeDefaultPlane = async (
|
|||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -421,8 +419,6 @@ test(
|
|||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
const [endOfTangentClk, endOfTangentMv] = scene.makeMouseHelpers(
|
const [endOfTangentClk, endOfTangentMv] = scene.makeMouseHelpers(
|
||||||
startXPx + PUR * 30,
|
startXPx + PUR * 30,
|
||||||
@ -551,8 +547,6 @@ test(
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.doAndWaitForImageDiff(
|
await u.doAndWaitForImageDiff(
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
@ -598,8 +592,6 @@ test(
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
|
|
||||||
await u.doAndWaitForImageDiff(
|
await u.doAndWaitForImageDiff(
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
200
|
200
|
||||||
@ -650,8 +642,6 @@ test.describe(
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
|
|
||||||
await u.doAndWaitForImageDiff(
|
await u.doAndWaitForImageDiff(
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
200
|
200
|
||||||
@ -744,7 +734,6 @@ test.describe(
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.doAndWaitForImageDiff(
|
await u.doAndWaitForImageDiff(
|
||||||
@ -846,7 +835,6 @@ part002 = startSketchOn(part001, seg01)
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Wait for the second extrusion to appear
|
// Wait for the second extrusion to appear
|
||||||
@ -902,7 +890,6 @@ test(
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Wait for the second extrusion to appear
|
// Wait for the second extrusion to appear
|
||||||
@ -943,7 +930,6 @@ test(
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Wait for the second extrusion to appear
|
// Wait for the second extrusion to appear
|
||||||
@ -976,7 +962,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
@ -1041,7 +1026,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
@ -1086,7 +1070,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
@ -1205,7 +1188,6 @@ sweepSketch = startSketchOn(XY)
|
|||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await expect(page, 'expect small color widget').toHaveScreenshot({
|
await expect(page, 'expect small color widget').toHaveScreenshot({
|
||||||
@ -1255,7 +1237,6 @@ sweepSketch = startSketchOn(XY)
|
|||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await scene.connectionEstablished()
|
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
||||||
|
@ -89,7 +89,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
test(
|
test(
|
||||||
'Engine disconnect & reconnect in sketch mode',
|
'Engine disconnect & reconnect in sketch mode',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage, toolbar }) => {
|
async ({ page, homePage, toolbar, scene, cmdBar }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
// Expect the network to be up
|
// Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// Click off the code pane.
|
// Click off the code pane.
|
||||||
await page.mouse.click(100, 100)
|
await page.mouse.click(100, 100)
|
||||||
|
@ -74,7 +74,10 @@ async function waitForPageLoadWithRetry(page: Page) {
|
|||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
const errorMessage = 'App failed to load - 🔃 Retrying ...'
|
const errorMessage = 'App failed to load - 🔃 Retrying ...'
|
||||||
await expect(page.getByTestId('loading'), errorMessage).not.toBeAttached({
|
await expect(
|
||||||
|
page.getByTestId('model-state-indicator-playing'),
|
||||||
|
errorMessage
|
||||||
|
).toBeAttached({
|
||||||
timeout: 20_000,
|
timeout: 20_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -87,9 +90,10 @@ async function waitForPageLoadWithRetry(page: Page) {
|
|||||||
}).toPass({ timeout: 70_000, intervals: [1_000] })
|
}).toPass({ timeout: 70_000, intervals: [1_000] })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lee: This needs to be replaced by scene.settled() eventually.
|
||||||
async function waitForPageLoad(page: Page) {
|
async function waitForPageLoad(page: Page) {
|
||||||
// wait for all spinners to be gone
|
// wait for all spinners to be gone
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
await expect(page.getByTestId('model-state-indicator-playing')).toBeVisible({
|
||||||
timeout: 20_000,
|
timeout: 20_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -871,9 +875,10 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
|
|||||||
export async function setup(
|
export async function setup(
|
||||||
context: BrowserContext,
|
context: BrowserContext,
|
||||||
page: Page,
|
page: Page,
|
||||||
|
testDir: string,
|
||||||
testInfo?: TestInfo
|
testInfo?: TestInfo
|
||||||
) {
|
) {
|
||||||
await context.addInitScript(
|
await page.addInitScript(
|
||||||
async ({
|
async ({
|
||||||
token,
|
token,
|
||||||
settingsKey,
|
settingsKey,
|
||||||
@ -914,7 +919,7 @@ export async function setup(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
IS_PLAYWRIGHT_KEY,
|
IS_PLAYWRIGHT_KEY,
|
||||||
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.project?.directory || '',
|
PLAYWRIGHT_TEST_DIR: testDir,
|
||||||
PERSIST_MODELING_CONTEXT,
|
PERSIST_MODELING_CONTEXT,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -934,7 +939,7 @@ export async function setup(
|
|||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
|
|
||||||
// Trigger a navigation, since loading file:// doesn't.
|
// Trigger a navigation, since loading file:// doesn't.
|
||||||
// await page.reload()
|
await page.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||||
|
@ -5,12 +5,18 @@ import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
|||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
||||||
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
test('Can move camera reliably', async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
})
|
})
|
||||||
.toBe(false)
|
.toBe(false)
|
||||||
})
|
})
|
||||||
test(`Remove constraints`, async ({ page, homePage }) => {
|
test(`Remove constraints`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -101,7 +101,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4], tag = $seg01)').click()
|
await page.getByText('line(end = [74.36, 130.4], tag = $seg01)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -142,7 +142,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, offset } of cases) {
|
for (const { testName, offset } of cases) {
|
||||||
test(`${testName}`, async ({ page, homePage }) => {
|
test(`${testName}`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -166,7 +166,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4], tag = $seg01)').click()
|
await page.getByText('line(end = [74.36, 130.4], tag = $seg01)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -250,7 +250,12 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, value, constraint } of cases) {
|
for (const { testName, value, constraint } of cases) {
|
||||||
test(`${constraint} - ${testName}`, async ({ page, homePage }) => {
|
test(`${constraint} - ${testName}`, async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -274,7 +279,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -361,7 +366,12 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${constraint} - ${testName}`, async ({ page, homePage }) => {
|
test(`${constraint} - ${testName}`, async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -385,7 +395,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -475,7 +485,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, axisSelect } of cases) {
|
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||||
test(`${testName}`, async ({ page, homePage }) => {
|
test(`${testName}`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -499,7 +509,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -578,7 +588,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${testName}`, async ({ page, homePage }) => {
|
test(`${testName}`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -602,7 +612,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -655,7 +665,14 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${testName}`, async ({ context, homePage, page, editor }) => {
|
test(`${testName}`, async ({
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
page,
|
||||||
|
editor,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
// constants and locators
|
// constants and locators
|
||||||
const cmdBarKclInput = page
|
const cmdBarKclInput = page
|
||||||
.getByTestId('cmd-bar-arg-value')
|
.getByTestId('cmd-bar-arg-value')
|
||||||
@ -689,7 +706,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await editor.scrollToText('line(end = [74.36, 130.4])', true)
|
await editor.scrollToText('line(end = [74.36, 130.4])', true)
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
@ -746,7 +763,7 @@ part002 = startSketchOn(XZ)
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { codeAfter, constraintName } of cases) {
|
for (const { codeAfter, constraintName } of cases) {
|
||||||
test(`${constraintName}`, async ({ page, homePage }) => {
|
test(`${constraintName}`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async (customCode) => {
|
await page.addInitScript(async (customCode) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -770,7 +787,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -848,7 +865,7 @@ part002 = startSketchOn(XZ)
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { codeAfter, constraintName } of cases) {
|
for (const { codeAfter, constraintName } of cases) {
|
||||||
test(`${constraintName}`, async ({ page, homePage }) => {
|
test(`${constraintName}`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -871,7 +888,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -930,7 +947,7 @@ part002 = startSketchOn(XZ)
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { codeAfter, constraintName, axisClick } of cases) {
|
for (const { codeAfter, constraintName, axisClick } of cases) {
|
||||||
test(`${constraintName}`, async ({ page, homePage }) => {
|
test(`${constraintName}`, async ({ page, homePage, scene, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -953,7 +970,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -994,6 +1011,8 @@ part002 = startSketchOn(XZ)
|
|||||||
test('Horizontally constrained line remains selected after applying constraint', async ({
|
test('Horizontally constrained line remains selected after applying constraint', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
test.setTimeout(70_000)
|
test.setTimeout(70_000)
|
||||||
@ -1010,7 +1029,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await page.getByText('line(end = [3.79, 2.68], tag = $seg01)').click()
|
await page.getByText('line(end = [3.79, 2.68], tag = $seg01)').click()
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||||
@ -1129,7 +1148,7 @@ test.describe('Electron constraint tests', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await homePage.openProject('test-sample')
|
await homePage.openProject('test-sample')
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function clickOnFirstSegmentLabel() {
|
async function clickOnFirstSegmentLabel() {
|
||||||
|
@ -11,7 +11,8 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
* Note this test implicitly depends on the KCL sample "a-parametric-bearing-pillow-block",
|
* Note this test implicitly depends on the KCL sample "a-parametric-bearing-pillow-block",
|
||||||
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
|
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
|
||||||
*/
|
*/
|
||||||
test('Web: should overwrite current code, cannot create new file', async ({
|
// We have no more web tests
|
||||||
|
test.skip('Web: should overwrite current code, cannot create new file', async ({
|
||||||
editor,
|
editor,
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -78,7 +79,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
test(
|
test(
|
||||||
'Desktop: should create new file by default, optionally overwrite',
|
'Desktop: should create new file by default, optionally overwrite',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ editor, context, page }, testInfo) => {
|
async ({ editor, context, page, scene, cmdBar }, testInfo) => {
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -125,7 +126,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await projectCard.click()
|
await projectCard.click()
|
||||||
await u.waitForPageLoad()
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Precondition: check the initial code`, async () => {
|
await test.step(`Precondition: check the initial code`, async () => {
|
||||||
@ -140,11 +141,14 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
|
|
||||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||||
await commandBarButton.click()
|
await commandBarButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandOption.click()
|
await commandOption.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandSampleOption(sampleOne.title).click()
|
await commandSampleOption(sampleOne.title).click()
|
||||||
await expect(overwriteWarning).not.toBeVisible()
|
await expect(overwriteWarning).not.toBeVisible()
|
||||||
await expect(newFileWarning).toBeVisible()
|
await expect(newFileWarning).toBeVisible()
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||||
@ -155,14 +159,20 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
|
|
||||||
await test.step(`Now overwrite the current file`, async () => {
|
await test.step(`Now overwrite the current file`, async () => {
|
||||||
await commandBarButton.click()
|
await commandBarButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandOption.click()
|
await commandOption.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandSampleOption(sampleTwo.title).click()
|
await commandSampleOption(sampleTwo.title).click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandMethodArgButton.click()
|
await commandMethodArgButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandMethodOption.click()
|
await commandMethodOption.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await expect(commandMethodArgButton).toContainText('overwrite')
|
await expect(commandMethodArgButton).toContainText('overwrite')
|
||||||
await expect(newFileWarning).not.toBeVisible()
|
await expect(newFileWarning).not.toBeVisible()
|
||||||
await expect(overwriteWarning).toBeVisible()
|
await expect(overwriteWarning).toBeVisible()
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
||||||
|
@ -1520,6 +1520,8 @@ part001 = startSketchOn(XZ)
|
|||||||
page,
|
page,
|
||||||
editor,
|
editor,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
async ({ lineToBeDeleted }) => {
|
async ({ lineToBeDeleted }) => {
|
||||||
@ -1541,7 +1543,8 @@ part001 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
await page.getByText(before).click()
|
await page.getByText(before).click()
|
||||||
|
@ -528,6 +528,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
|
|||||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
@ -779,11 +781,7 @@ part001 = startSketchOn(XZ)
|
|||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
await expect(
|
await scene.settled(cmdBar)
|
||||||
page
|
|
||||||
.getByTestId('model-state-indicator-receive-reliable')
|
|
||||||
.or(page.getByTestId('model-state-indicator-execution-done'))
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -953,6 +951,7 @@ part001 = startSketchOn(XZ)
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
@ -989,7 +988,7 @@ part001 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -1024,6 +1023,7 @@ part001 = startSketchOn(XZ)
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -1043,7 +1043,7 @@ part001 = startSketchOn(XZ)
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
|
@ -55,7 +55,8 @@ test.describe('Testing settings', () => {
|
|||||||
// Check that the invalid settings were changed to good defaults
|
// Check that the invalid settings were changed to good defaults
|
||||||
expect(storedSettings.settings?.modeling?.base_unit).toBe('in')
|
expect(storedSettings.settings?.modeling?.base_unit).toBe('in')
|
||||||
expect(storedSettings.settings?.modeling?.mouse_controls).toBe('zoo')
|
expect(storedSettings.settings?.modeling?.mouse_controls).toBe('zoo')
|
||||||
expect(storedSettings.settings?.project?.directory).toBe('')
|
// Commenting this out because tests need this to be set to work properly.
|
||||||
|
// expect(storedSettings.settings?.app?.project_directory).toBe('')
|
||||||
expect(storedSettings.settings?.project?.default_project_name).toBe(
|
expect(storedSettings.settings?.project?.default_project_name).toBe(
|
||||||
'untitled'
|
'untitled'
|
||||||
)
|
)
|
||||||
@ -865,6 +866,8 @@ test.describe('Testing settings', () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -886,6 +889,7 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
||||||
@ -897,6 +901,7 @@ test.describe('Testing settings', () => {
|
|||||||
|
|
||||||
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
||||||
await commandsButton.click()
|
await commandsButton.click()
|
||||||
|
await debugPaneOption.scrollIntoViewIfNeeded()
|
||||||
await debugPaneOption.click()
|
await debugPaneOption.click()
|
||||||
await page.getByRole('option', { name: value }).click()
|
await page.getByRole('option', { name: value }).click()
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -17,7 +17,6 @@ declare module '@playwright/test' {
|
|||||||
}
|
}
|
||||||
interface Page {
|
interface Page {
|
||||||
dir: string
|
dir: string
|
||||||
TEST_SETTINGS_FILE_KEY?: string
|
|
||||||
setBodyDimensions: (dims: {
|
setBodyDimensions: (dims: {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
1
interface.d.ts
vendored
1
interface.d.ts
vendored
@ -72,7 +72,6 @@ export interface IElectronAPI {
|
|||||||
process: {
|
process: {
|
||||||
env: {
|
env: {
|
||||||
BASE_URL: string
|
BASE_URL: string
|
||||||
TEST_SETTINGS_FILE_KEY: string
|
|
||||||
IS_PLAYWRIGHT: string
|
IS_PLAYWRIGHT: string
|
||||||
VITE_KC_DEV_TOKEN: string
|
VITE_KC_DEV_TOKEN: string
|
||||||
VITE_KC_API_WS_MODELING_URL: string
|
VITE_KC_API_WS_MODELING_URL: string
|
||||||
|
@ -4,10 +4,10 @@ $ dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx
|
|||||||
02) src/lang/std/sketch.ts -> src/lang/modifyAst.ts
|
02) src/lang/std/sketch.ts -> src/lang/modifyAst.ts
|
||||||
03) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts
|
03) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts
|
||||||
04) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
|
04) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
|
||||||
05) src/lib/singletons.ts -> src/lang/KclSingleton.ts
|
05) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/engineStreamMachine.ts
|
||||||
06) src/lib/singletons.ts -> src/lang/codeManager.ts
|
06) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/settingsMachine.ts
|
||||||
07) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
|
07) src/machines/appMachine.ts -> src/machines/settingsMachine.ts -> src/machines/commandBarMachine.ts -> src/lib/commandBarConfigs/authCommandConfig.ts
|
||||||
08) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/machines/commandBarMachine.ts -> src/lib/commandBarConfigs/authCommandConfig.ts -> src/machines/appMachine.ts -> src/machines/settingsMachine.ts
|
08) src/lib/singletons.ts -> src/lang/codeManager.ts
|
||||||
09) src/machines/commandBarMachine.ts -> src/lib/commandBarConfigs/authCommandConfig.ts -> src/machines/appMachine.ts -> src/machines/settingsMachine.ts
|
09) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
|
||||||
10) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts
|
10) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts
|
||||||
11) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx
|
11) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx
|
||||||
|
@ -5,7 +5,7 @@ pub mod project;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use validator::{Validate, ValidateRange};
|
use validator::{Validate, ValidateRange};
|
||||||
|
|
||||||
const DEFAULT_THEME_COLOR: f64 = 264.5;
|
const DEFAULT_THEME_COLOR: f64 = 264.5;
|
||||||
@ -131,9 +131,14 @@ 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.
|
/// When the user is idle, teardown the stream after some time.
|
||||||
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
#[serde(
|
||||||
pub stream_idle_mode: bool,
|
default,
|
||||||
|
deserialize_with = "deserialize_stream_idle_mode",
|
||||||
|
alias = "streamIdleMode",
|
||||||
|
skip_serializing_if = "is_default"
|
||||||
|
)]
|
||||||
|
stream_idle_mode: Option<u32>,
|
||||||
/// When the user is idle, and this is true, the stream will be torn down.
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
||||||
pub allow_orbit_in_sketch_mode: bool,
|
pub allow_orbit_in_sketch_mode: bool,
|
||||||
@ -143,7 +148,31 @@ pub struct AppSettings {
|
|||||||
pub show_debug_panel: bool,
|
pub show_debug_panel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum StreamIdleModeValue {
|
||||||
|
Number(u32),
|
||||||
|
String(String),
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TIMEOUT: u32 = 1000 * 60 * 5;
|
||||||
|
|
||||||
|
Ok(match StreamIdleModeValue::deserialize(deserializer) {
|
||||||
|
Ok(StreamIdleModeValue::Number(value)) => Some(value),
|
||||||
|
Ok(StreamIdleModeValue::String(value)) => Some(value.parse::<u32>().unwrap_or(DEFAULT_TIMEOUT)),
|
||||||
|
// The old type of this value. I'm willing to say no one used it but
|
||||||
|
// we can never guarantee it.
|
||||||
|
Ok(StreamIdleModeValue::Boolean(true)) => Some(DEFAULT_TIMEOUT),
|
||||||
|
Ok(StreamIdleModeValue::Boolean(false)) => None,
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@ -626,7 +655,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,
|
stream_idle_mode: None,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
show_debug_panel: true,
|
show_debug_panel: true,
|
||||||
},
|
},
|
||||||
@ -691,7 +720,7 @@ includeSettings = false
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
show_debug_panel: true,
|
show_debug_panel: true,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: None,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
@ -759,7 +788,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,
|
stream_idle_mode: None,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
show_debug_panel: true,
|
show_debug_panel: true,
|
||||||
},
|
},
|
||||||
@ -841,7 +870,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
show_debug_panel: false,
|
show_debug_panel: false,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: None,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
|
31
src/App.tsx
31
src/App.tsx
@ -6,16 +6,17 @@ import {
|
|||||||
useLoaderData,
|
useLoaderData,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
useRouteLoaderData,
|
useRouteLoaderData,
|
||||||
|
useSearchParams,
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
|
|
||||||
import { AppHeader } from '@src/components/AppHeader'
|
import { AppHeader } from '@src/components/AppHeader'
|
||||||
import { CameraProjectionToggle } from '@src/components/CameraProjectionToggle'
|
import { CameraProjectionToggle } from '@src/components/CameraProjectionToggle'
|
||||||
import { useEngineCommands } from '@src/components/EngineCommands'
|
import { useEngineCommands } from '@src/components/EngineCommands'
|
||||||
|
import { EngineStream } from '@src/components/EngineStream'
|
||||||
import Gizmo from '@src/components/Gizmo'
|
import Gizmo from '@src/components/Gizmo'
|
||||||
import { LowerRightControls } from '@src/components/LowerRightControls'
|
import { LowerRightControls } from '@src/components/LowerRightControls'
|
||||||
import { useLspContext } from '@src/components/LspProvider'
|
import { useLspContext } from '@src/components/LspProvider'
|
||||||
import { ModelingSidebar } from '@src/components/ModelingSidebar/ModelingSidebar'
|
import { ModelingSidebar } from '@src/components/ModelingSidebar/ModelingSidebar'
|
||||||
import { Stream } from '@src/components/Stream'
|
|
||||||
import { UnitsMenu } from '@src/components/UnitsMenu'
|
import { UnitsMenu } from '@src/components/UnitsMenu'
|
||||||
import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath'
|
||||||
import { useCreateFileLinkQuery } from '@src/hooks/useCreateFileLinkQueryWatcher'
|
import { useCreateFileLinkQuery } from '@src/hooks/useCreateFileLinkQueryWatcher'
|
||||||
@ -31,13 +32,22 @@ import {
|
|||||||
codeManager,
|
codeManager,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
rustContext,
|
rustContext,
|
||||||
|
sceneInfra,
|
||||||
} from '@src/lib/singletons'
|
} from '@src/lib/singletons'
|
||||||
import { maybeWriteToDisk } from '@src/lib/telemetry'
|
import { maybeWriteToDisk } from '@src/lib/telemetry'
|
||||||
import { type IndexLoaderData } from '@src/lib/types'
|
import { type IndexLoaderData } from '@src/lib/types'
|
||||||
import { useSettings, useToken } from '@src/machines/appMachine'
|
import {
|
||||||
|
engineStreamActor,
|
||||||
|
useSettings,
|
||||||
|
useToken,
|
||||||
|
} from '@src/machines/appMachine'
|
||||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||||
|
import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
|
||||||
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
||||||
|
|
||||||
|
// CYCLIC REF
|
||||||
|
sceneInfra.camControls.engineStreamActor = engineStreamActor
|
||||||
|
|
||||||
maybeWriteToDisk()
|
maybeWriteToDisk()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
@ -64,6 +74,10 @@ export function App() {
|
|||||||
// the coredump.
|
// the coredump.
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
// Stream related refs and data
|
||||||
|
let [searchParams] = useSearchParams()
|
||||||
|
const pool = searchParams.get('pool')
|
||||||
|
|
||||||
const projectName = project?.name || null
|
const projectName = project?.name || null
|
||||||
const projectPath = project?.path || null
|
const projectPath = project?.path || null
|
||||||
|
|
||||||
@ -78,7 +92,7 @@ export function App() {
|
|||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
|
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const token = useToken()
|
const authToken = useToken()
|
||||||
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -86,7 +100,7 @@ export function App() {
|
|||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
codeManager,
|
codeManager,
|
||||||
rustContext,
|
rustContext,
|
||||||
token
|
authToken
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@ -140,6 +154,13 @@ export function App() {
|
|||||||
}
|
}
|
||||||
}, [lastCommandType])
|
}, [lastCommandType])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// When leaving the modeling scene, cut the engine stream.
|
||||||
|
return () => {
|
||||||
|
engineStreamActor.send({ type: EngineStreamTransition.Pause })
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full flex flex-col" ref={ref}>
|
<div className="relative h-full flex flex-col" ref={ref}>
|
||||||
<AppHeader
|
<AppHeader
|
||||||
@ -149,7 +170,7 @@ export function App() {
|
|||||||
/>
|
/>
|
||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||||
<Stream />
|
<EngineStream pool={pool} authToken={authToken} />
|
||||||
{/* <CamToggle /> */}
|
{/* <CamToggle /> */}
|
||||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||||
<UnitsMenu />
|
<UnitsMenu />
|
||||||
|
@ -11,6 +11,7 @@ import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
|||||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
||||||
import { useKclContext } from '@src/lang/KclProvider'
|
import { useKclContext } from '@src/lang/KclProvider'
|
||||||
import { isCursorInFunctionDefinition } from '@src/lang/queryAst'
|
import { isCursorInFunctionDefinition } from '@src/lang/queryAst'
|
||||||
|
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
||||||
import { isCursorInSketchCommandRange } from '@src/lang/util'
|
import { isCursorInSketchCommandRange } from '@src/lang/util'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
||||||
@ -52,7 +53,7 @@ export function Toolbar({
|
|||||||
}, [kclManager.artifactGraph, context.selectionRanges])
|
}, [kclManager.artifactGraph, context.selectionRanges])
|
||||||
|
|
||||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||||
const { overallState } = useNetworkContext()
|
const { overallState, immediateState } = useNetworkContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { isStreamReady } = useAppState()
|
const { isStreamReady } = useAppState()
|
||||||
const [showRichContent, setShowRichContent] = useState(false)
|
const [showRichContent, setShowRichContent] = useState(false)
|
||||||
@ -61,6 +62,7 @@ export function Toolbar({
|
|||||||
(overallState !== NetworkHealthState.Ok &&
|
(overallState !== NetworkHealthState.Ok &&
|
||||||
overallState !== NetworkHealthState.Weak) ||
|
overallState !== NetworkHealthState.Weak) ||
|
||||||
isExecuting ||
|
isExecuting ||
|
||||||
|
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
|
||||||
!isStreamReady
|
!isStreamReady
|
||||||
|
|
||||||
const currentMode =
|
const currentMode =
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import type { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
import type {
|
||||||
|
CameraDragInteractionType_type,
|
||||||
|
CameraViewState_type,
|
||||||
|
} from '@kittycad/lib/dist/types/src/models'
|
||||||
|
import type { EngineStreamActor } from '@src/machines/engineStreamMachine'
|
||||||
import * as TWEEN from '@tweenjs/tween.js'
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
import {
|
import {
|
||||||
Euler,
|
Euler,
|
||||||
@ -97,6 +101,7 @@ class CameraRateLimiter {
|
|||||||
|
|
||||||
export class CameraControls {
|
export class CameraControls {
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
engineStreamActor?: EngineStreamActor
|
||||||
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
||||||
camera: PerspectiveCamera | OrthographicCamera
|
camera: PerspectiveCamera | OrthographicCamera
|
||||||
target: Vector3
|
target: Vector3
|
||||||
@ -105,6 +110,7 @@ export class CameraControls {
|
|||||||
wasDragging: boolean
|
wasDragging: boolean
|
||||||
mouseDownPosition: Vector2
|
mouseDownPosition: Vector2
|
||||||
mouseNewPosition: Vector2
|
mouseNewPosition: Vector2
|
||||||
|
oldCameraState: undefined | CameraViewState_type
|
||||||
rotationSpeed = 0.3
|
rotationSpeed = 0.3
|
||||||
enableRotate = true
|
enableRotate = true
|
||||||
enablePan = true
|
enablePan = true
|
||||||
@ -285,6 +291,7 @@ export class CameraControls {
|
|||||||
camSettings.center.y,
|
camSettings.center.y,
|
||||||
camSettings.center.z
|
camSettings.center.z
|
||||||
)
|
)
|
||||||
|
|
||||||
const orientation = new Quaternion(
|
const orientation = new Quaternion(
|
||||||
camSettings.orientation.x,
|
camSettings.orientation.x,
|
||||||
camSettings.orientation.y,
|
camSettings.orientation.y,
|
||||||
@ -468,12 +475,13 @@ export class CameraControls {
|
|||||||
if (this.syncDirection === 'engineToClient') {
|
if (this.syncDirection === 'engineToClient') {
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
|
|
||||||
|
const videoRef = this.engineStreamActor?.getSnapshot().context.videoRef
|
||||||
// Nonsense to do anything until the video stream is established.
|
// Nonsense to do anything until the video stream is established.
|
||||||
if (!this.engineCommandManager.elVideo) return
|
if (!videoRef?.current) return
|
||||||
|
|
||||||
const { x, y } = getNormalisedCoordinates(
|
const { x, y } = getNormalisedCoordinates(
|
||||||
event,
|
event,
|
||||||
this.engineCommandManager.elVideo,
|
videoRef.current,
|
||||||
this.engineCommandManager.streamDimensions
|
this.engineCommandManager.streamDimensions
|
||||||
)
|
)
|
||||||
this.throttledEngCmd({
|
this.throttledEngCmd({
|
||||||
@ -956,6 +964,46 @@ export class CameraControls {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async restoreRemoteCameraStateAndTriggerSync() {
|
||||||
|
if (this.oldCameraState) {
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_set_view',
|
||||||
|
view: this.oldCameraState,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveRemoteCameraState() {
|
||||||
|
const cameraViewStateResponse =
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_get_view' },
|
||||||
|
})
|
||||||
|
if (!cameraViewStateResponse) return
|
||||||
|
if (
|
||||||
|
'resp' in cameraViewStateResponse &&
|
||||||
|
'modeling_response' in cameraViewStateResponse.resp.data &&
|
||||||
|
'data' in cameraViewStateResponse.resp.data.modeling_response &&
|
||||||
|
'view' in cameraViewStateResponse.resp.data.modeling_response.data
|
||||||
|
) {
|
||||||
|
this.oldCameraState =
|
||||||
|
cameraViewStateResponse.resp.data.modeling_response.data.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async tweenCameraToQuaternion(
|
async tweenCameraToQuaternion(
|
||||||
targetQuaternion: Quaternion,
|
targetQuaternion: Quaternion,
|
||||||
targetPosition = new Vector3(),
|
targetPosition = new Vector3(),
|
||||||
|
@ -7,6 +7,8 @@ import CommandBarReview from '@src/components/CommandBar/CommandBarReview'
|
|||||||
import CommandComboBox from '@src/components/CommandComboBox'
|
import CommandComboBox from '@src/components/CommandComboBox'
|
||||||
import { CustomIcon } from '@src/components/CustomIcon'
|
import { CustomIcon } from '@src/components/CustomIcon'
|
||||||
import Tooltip from '@src/components/Tooltip'
|
import Tooltip from '@src/components/Tooltip'
|
||||||
|
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||||
|
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
||||||
import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
|
import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
|
||||||
import {
|
import {
|
||||||
commandBarActor,
|
commandBarActor,
|
||||||
@ -18,6 +20,7 @@ export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
|||||||
export const CommandBar = () => {
|
export const CommandBar = () => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const commandBarState = useCommandBarState()
|
const commandBarState = useCommandBarState()
|
||||||
|
const { immediateState } = useNetworkContext()
|
||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, commands },
|
context: { selectedCommand, currentArgument, commands },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
@ -32,6 +35,14 @@ export const CommandBar = () => {
|
|||||||
commandBarActor.send({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
|
||||||
|
) {
|
||||||
|
commandBarActor.send({ type: 'Close' })
|
||||||
|
}
|
||||||
|
}, [immediateState])
|
||||||
|
|
||||||
// Hook up keyboard shortcuts
|
// Hook up keyboard shortcuts
|
||||||
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
||||||
if (commandBarState.context.commands.length === 0) return
|
if (commandBarState.context.commands.length === 0) return
|
||||||
|
431
src/components/EngineStream.tsx
Normal file
431
src/components/EngineStream.tsx
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
import { useAppState } from '@src/AppState'
|
||||||
|
import { ClientSideScene } from '@src/clientSideScene/ClientSideSceneComp'
|
||||||
|
import { ViewControlContextMenu } from '@src/components/ViewControlMenu'
|
||||||
|
import { useModelingContext } from '@src/hooks/useModelingContext'
|
||||||
|
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||||
|
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
||||||
|
import { getArtifactOfTypes } from '@src/lang/std/artifactGraph'
|
||||||
|
import { EngineCommandManagerEvents } from '@src/lang/std/engineConnection'
|
||||||
|
import { btnName } from '@src/lib/cameraControls'
|
||||||
|
import { PATHS } from '@src/lib/paths'
|
||||||
|
import { sendSelectEventToEngine } from '@src/lib/selections'
|
||||||
|
import {
|
||||||
|
engineCommandManager,
|
||||||
|
kclManager,
|
||||||
|
sceneInfra,
|
||||||
|
} from '@src/lib/singletons'
|
||||||
|
import { REASONABLE_TIME_TO_REFRESH_STREAM_SIZE } from '@src/lib/timings'
|
||||||
|
import { err, reportRejection, trap } from '@src/lib/trap'
|
||||||
|
import type { IndexLoaderData } from '@src/lib/types'
|
||||||
|
import { uuidv4 } from '@src/lib/utils'
|
||||||
|
import { engineStreamActor, useSettings } from '@src/machines/appMachine'
|
||||||
|
import { useCommandBarState } from '@src/machines/commandBarMachine'
|
||||||
|
import {
|
||||||
|
EngineStreamState,
|
||||||
|
EngineStreamTransition,
|
||||||
|
} from '@src/machines/engineStreamMachine'
|
||||||
|
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
import type { MouseEventHandler } from 'react'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useRouteLoaderData } from 'react-router-dom'
|
||||||
|
|
||||||
|
export const EngineStream = (props: {
|
||||||
|
pool: string | null
|
||||||
|
authToken: string | undefined
|
||||||
|
}) => {
|
||||||
|
const { setAppState } = useAppState()
|
||||||
|
const [firstPlay, setFirstPlay] = useState(true)
|
||||||
|
|
||||||
|
const { overallState } = useNetworkContext()
|
||||||
|
const settings = useSettings()
|
||||||
|
|
||||||
|
const engineStreamState = useSelector(engineStreamActor, (state) => state)
|
||||||
|
|
||||||
|
const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
|
const last = useRef<number>(Date.now())
|
||||||
|
const videoWrapperRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const settingsEngine = {
|
||||||
|
theme: settings.app.theme.current,
|
||||||
|
enableSSAO: settings.modeling.enableSSAO.current,
|
||||||
|
highlightEdges: settings.modeling.highlightEdges.current,
|
||||||
|
showScaleGrid: settings.modeling.showScaleGrid.current,
|
||||||
|
cameraProjection: settings.modeling.cameraProjection.current,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { state: modelingMachineState, send: modelingMachineActorSend } =
|
||||||
|
useModelingContext()
|
||||||
|
|
||||||
|
const commandBarState = useCommandBarState()
|
||||||
|
|
||||||
|
const streamIdleMode = settings.app.streamIdleMode.current
|
||||||
|
|
||||||
|
const startOrReconfigureEngine = () => {
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.StartOrReconfigureEngine,
|
||||||
|
modelingMachineActorSend,
|
||||||
|
settings: settingsEngine,
|
||||||
|
setAppState,
|
||||||
|
|
||||||
|
// It's possible a reconnect happens as we drag the window :')
|
||||||
|
onMediaStream(mediaStream: MediaStream) {
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.SetMediaStream,
|
||||||
|
mediaStream,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the scene is ready play the stream and execute!
|
||||||
|
const play = () => {
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.Play,
|
||||||
|
})
|
||||||
|
|
||||||
|
const kmp = kclManager.executeCode().catch(trap)
|
||||||
|
|
||||||
|
if (!firstPlay) return
|
||||||
|
setFirstPlay(false)
|
||||||
|
console.log('scene is ready, fire!')
|
||||||
|
|
||||||
|
kmp
|
||||||
|
.then(() =>
|
||||||
|
// It makes sense to also call zoom to fit here, when a new file is
|
||||||
|
// loaded for the first time, but not overtaking the work kevin did
|
||||||
|
// so the camera isn't moving all the time.
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'zoom_to_fit',
|
||||||
|
object_ids: [], // leave empty to zoom to all objects
|
||||||
|
padding: 0.1, // padding around the objects
|
||||||
|
animated: false, // don't animate the zoom for now
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(trap)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
engineCommandManager.addEventListener(
|
||||||
|
EngineCommandManagerEvents.SceneReady,
|
||||||
|
play
|
||||||
|
)
|
||||||
|
return () => {
|
||||||
|
engineCommandManager.removeEventListener(
|
||||||
|
EngineCommandManagerEvents.SceneReady,
|
||||||
|
play
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [firstPlay])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
engineCommandManager.addEventListener(
|
||||||
|
EngineCommandManagerEvents.SceneReady,
|
||||||
|
play
|
||||||
|
)
|
||||||
|
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.SetPool,
|
||||||
|
data: { pool: props.pool },
|
||||||
|
})
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.SetAuthToken,
|
||||||
|
data: { authToken: props.authToken },
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
engineCommandManager.tearDown()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// In the past we'd try to play immediately, but the proper thing is to way
|
||||||
|
// for the 'canplay' event to tell us data is ready.
|
||||||
|
useEffect(() => {
|
||||||
|
const videoRef = engineStreamState.context.videoRef.current
|
||||||
|
if (!videoRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const play = () => {
|
||||||
|
videoRef.play().catch(console.error)
|
||||||
|
}
|
||||||
|
videoRef.addEventListener('canplay', play)
|
||||||
|
return () => {
|
||||||
|
videoRef.removeEventListener('canplay', play)
|
||||||
|
}
|
||||||
|
}, [engineStreamState.context.videoRef.current])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (engineStreamState.value === EngineStreamState.Reconfiguring) return
|
||||||
|
const video = engineStreamState.context.videoRef?.current
|
||||||
|
if (!video) return
|
||||||
|
const canvas = engineStreamState.context.canvasRef?.current
|
||||||
|
if (!canvas) return
|
||||||
|
|
||||||
|
new ResizeObserver(() => {
|
||||||
|
// Prevents:
|
||||||
|
// `Uncaught ResizeObserver loop completed with undelivered notifications`
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (Date.now() - last.current < REASONABLE_TIME_TO_REFRESH_STREAM_SIZE)
|
||||||
|
return
|
||||||
|
last.current = Date.now()
|
||||||
|
|
||||||
|
if (
|
||||||
|
Math.abs(video.width - window.innerWidth) > 4 ||
|
||||||
|
Math.abs(video.height - window.innerHeight) > 4
|
||||||
|
) {
|
||||||
|
timeoutStart.current = Date.now()
|
||||||
|
startOrReconfigureEngine()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).observe(document.body)
|
||||||
|
}, [engineStreamState.value])
|
||||||
|
|
||||||
|
// When the video and canvas element references are set, start the engine.
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
engineStreamState.context.canvasRef.current &&
|
||||||
|
engineStreamState.context.videoRef.current
|
||||||
|
) {
|
||||||
|
startOrReconfigureEngine()
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
engineStreamState.context.canvasRef.current,
|
||||||
|
engineStreamState.context.videoRef.current,
|
||||||
|
])
|
||||||
|
|
||||||
|
// On settings change, reconfigure the engine. When paused this gets really tricky,
|
||||||
|
// and also requires onMediaStream to be set!
|
||||||
|
useEffect(() => {
|
||||||
|
startOrReconfigureEngine()
|
||||||
|
}, Object.values(settingsEngine))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to execute code when the file changes
|
||||||
|
* but only if the scene is already ready.
|
||||||
|
* See onSceneReady for the initial scene setup.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (engineCommandManager.engineConnection?.isReady() && file?.path) {
|
||||||
|
console.log('file changed, executing code')
|
||||||
|
kclManager
|
||||||
|
.executeCode()
|
||||||
|
.catch(trap)
|
||||||
|
.then(() =>
|
||||||
|
// It makes sense to also call zoom to fit here, when a new file is
|
||||||
|
// loaded for the first time, but not overtaking the work kevin did
|
||||||
|
// so the camera isn't moving all the time.
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'zoom_to_fit',
|
||||||
|
object_ids: [], // leave empty to zoom to all objects
|
||||||
|
padding: 0.1, // padding around the objects
|
||||||
|
animated: false, // don't animate the zoom for now
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(trap)
|
||||||
|
}
|
||||||
|
}, [file?.path])
|
||||||
|
|
||||||
|
const IDLE_TIME_MS = Number(streamIdleMode)
|
||||||
|
|
||||||
|
// When streamIdleMode is changed, setup or teardown the timeouts
|
||||||
|
const timeoutStart = useRef<number | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
timeoutStart.current = streamIdleMode ? Date.now() : null
|
||||||
|
}, [streamIdleMode])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let frameId: ReturnType<typeof window.requestAnimationFrame> = 0
|
||||||
|
const frameLoop = () => {
|
||||||
|
// Do not pause if the user is in the middle of an operation
|
||||||
|
if (!modelingMachineState.matches('idle')) {
|
||||||
|
// In fact, stop the timeout, because we don't want to trigger the
|
||||||
|
// pause when we exit the operation.
|
||||||
|
timeoutStart.current = null
|
||||||
|
} else if (timeoutStart.current) {
|
||||||
|
const elapsed = Date.now() - timeoutStart.current
|
||||||
|
if (elapsed >= IDLE_TIME_MS) {
|
||||||
|
timeoutStart.current = null
|
||||||
|
engineStreamActor.send({ type: EngineStreamTransition.Pause })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameId = window.requestAnimationFrame(frameLoop)
|
||||||
|
}
|
||||||
|
frameId = window.requestAnimationFrame(frameLoop)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.cancelAnimationFrame(frameId)
|
||||||
|
}
|
||||||
|
}, [modelingMachineState])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!streamIdleMode) return
|
||||||
|
|
||||||
|
const onAnyInput = () => {
|
||||||
|
// Just in case it happens in the middle of the user turning off
|
||||||
|
// idle mode.
|
||||||
|
if (!streamIdleMode) {
|
||||||
|
timeoutStart.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engineStreamState.value === EngineStreamState.Paused) {
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.StartOrReconfigureEngine,
|
||||||
|
modelingMachineActorSend,
|
||||||
|
settings: settingsEngine,
|
||||||
|
setAppState,
|
||||||
|
onMediaStream(mediaStream: MediaStream) {
|
||||||
|
engineStreamActor.send({
|
||||||
|
type: EngineStreamTransition.SetMediaStream,
|
||||||
|
mediaStream,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutStart.current = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's possible after a reconnect, the user doesn't move their mouse at
|
||||||
|
// all, meaning the timer is not reset to run. We need to set it every
|
||||||
|
// time our effect dependencies change then.
|
||||||
|
timeoutStart.current = Date.now()
|
||||||
|
|
||||||
|
window.document.addEventListener('keydown', onAnyInput)
|
||||||
|
window.document.addEventListener('keyup', onAnyInput)
|
||||||
|
window.document.addEventListener('mousemove', onAnyInput)
|
||||||
|
window.document.addEventListener('mousedown', onAnyInput)
|
||||||
|
window.document.addEventListener('mouseup', onAnyInput)
|
||||||
|
window.document.addEventListener('scroll', onAnyInput)
|
||||||
|
window.document.addEventListener('touchstart', onAnyInput)
|
||||||
|
window.document.addEventListener('touchstop', onAnyInput)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
timeoutStart.current = null
|
||||||
|
window.document.removeEventListener('keydown', onAnyInput)
|
||||||
|
window.document.removeEventListener('keyup', onAnyInput)
|
||||||
|
window.document.removeEventListener('mousemove', onAnyInput)
|
||||||
|
window.document.removeEventListener('mousedown', onAnyInput)
|
||||||
|
window.document.removeEventListener('mouseup', onAnyInput)
|
||||||
|
window.document.removeEventListener('scroll', onAnyInput)
|
||||||
|
window.document.removeEventListener('touchstart', onAnyInput)
|
||||||
|
window.document.removeEventListener('touchstop', onAnyInput)
|
||||||
|
}
|
||||||
|
}, [streamIdleMode, engineStreamState.value])
|
||||||
|
|
||||||
|
const isNetworkOkay =
|
||||||
|
overallState === NetworkHealthState.Ok ||
|
||||||
|
overallState === NetworkHealthState.Weak
|
||||||
|
|
||||||
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
|
if (!isNetworkOkay) return
|
||||||
|
if (!engineStreamState.context.videoRef.current) return
|
||||||
|
// If we're in sketch mode, don't send a engine-side select event
|
||||||
|
if (modelingMachineState.matches('Sketch')) return
|
||||||
|
// Only respect default plane selection if we're on a selection command argument
|
||||||
|
if (
|
||||||
|
modelingMachineState.matches({ idle: 'showPlanes' }) &&
|
||||||
|
!(
|
||||||
|
commandBarState.matches('Gathering arguments') &&
|
||||||
|
commandBarState.context.currentArgument?.inputType === 'selection'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
// If we're mousing up from a camera drag, don't send a select event
|
||||||
|
if (sceneInfra.camControls.wasDragging === true) return
|
||||||
|
|
||||||
|
if (btnName(e.nativeEvent).left) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
sendSelectEventToEngine(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On double-click of sketch entities we automatically enter sketch mode with the selected sketch,
|
||||||
|
* allowing for quick editing of sketches. TODO: This should be moved to a more central place.
|
||||||
|
*/
|
||||||
|
const enterSketchModeIfSelectingSketch: MouseEventHandler<HTMLDivElement> = (
|
||||||
|
e
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!isNetworkOkay ||
|
||||||
|
!engineStreamState.context.videoRef.current ||
|
||||||
|
modelingMachineState.matches('Sketch') ||
|
||||||
|
modelingMachineState.matches({ idle: 'showPlanes' }) ||
|
||||||
|
sceneInfra.camControls.wasDragging === true ||
|
||||||
|
!btnName(e.nativeEvent).left
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSelectEventToEngine(e)
|
||||||
|
.then(({ entity_id }) => {
|
||||||
|
if (!entity_id) {
|
||||||
|
// No entity selected. This is benign
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const path = getArtifactOfTypes(
|
||||||
|
{ key: entity_id, types: ['path', 'solid2d', 'segment', 'helix'] },
|
||||||
|
kclManager.artifactGraph
|
||||||
|
)
|
||||||
|
if (err(path)) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
sceneInfra.modelingSend({ type: 'Enter sketch' })
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
|
<div
|
||||||
|
ref={videoWrapperRef}
|
||||||
|
className="absolute inset-0 z-0"
|
||||||
|
id="stream"
|
||||||
|
data-testid="stream"
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onDoubleClick={enterSketchModeIfSelectingSketch}
|
||||||
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
|
onContextMenuCapture={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
key={engineStreamActor.id + 'video'}
|
||||||
|
ref={engineStreamState.context.videoRef}
|
||||||
|
controls={false}
|
||||||
|
className="w-full cursor-pointer h-full"
|
||||||
|
disablePictureInPicture
|
||||||
|
id="video-stream"
|
||||||
|
/>
|
||||||
|
<canvas
|
||||||
|
key={engineStreamActor.id + 'canvas'}
|
||||||
|
ref={engineStreamState.context.canvasRef}
|
||||||
|
className="cursor-pointer"
|
||||||
|
id="freeze-frame"
|
||||||
|
>
|
||||||
|
No canvas support
|
||||||
|
</canvas>
|
||||||
|
<ClientSideScene
|
||||||
|
cameraControls={settings.modeling.mouseControls.current}
|
||||||
|
/>
|
||||||
|
<ViewControlContextMenu
|
||||||
|
event="mouseup"
|
||||||
|
guard={(e) =>
|
||||||
|
sceneInfra.camControls.wasDragging === false &&
|
||||||
|
btnName(e).right === true
|
||||||
|
}
|
||||||
|
menuTargetElement={videoWrapperRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -71,13 +71,6 @@ export const FileMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Only create the native file menus on desktop
|
|
||||||
useEffect(() => {
|
|
||||||
if (isDesktop()) {
|
|
||||||
window.electron.createModelingPageMenu().catch(reportRejection)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const {
|
const {
|
||||||
createNamedViewCommand,
|
createNamedViewCommand,
|
||||||
|
@ -1,31 +1,45 @@
|
|||||||
import { CustomIcon } from '@src/components/CustomIcon'
|
import { engineStreamActor } from '@src/machines/appMachine'
|
||||||
import { useEngineCommands } from '@src/components/EngineCommands'
|
import { EngineStreamState } from '@src/machines/engineStreamMachine'
|
||||||
import { Spinner } from '@src/components/Spinner'
|
import { useSelector } from '@xstate/react'
|
||||||
|
|
||||||
|
import { faPause, faPlay, faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
|
||||||
export const ModelStateIndicator = () => {
|
export const ModelStateIndicator = () => {
|
||||||
const [commands] = useEngineCommands()
|
const engineStreamState = useSelector(engineStreamActor, (state) => state)
|
||||||
const lastCommandType = commands[commands.length - 1]?.type
|
|
||||||
|
|
||||||
let className = 'w-6 h-6 '
|
let className = 'w-6 h-6 '
|
||||||
let icon = <Spinner className={className} />
|
let icon = <div className={className}></div>
|
||||||
let dataTestId = 'model-state-indicator'
|
let dataTestId = 'model-state-indicator'
|
||||||
|
|
||||||
if (lastCommandType === 'receive-reliable') {
|
if (engineStreamState.value === EngineStreamState.Paused) {
|
||||||
className +=
|
className += 'text-secondary'
|
||||||
'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 = (
|
icon = (
|
||||||
<CustomIcon
|
<FontAwesomeIcon
|
||||||
data-testid={dataTestId + '-receive-reliable'}
|
data-testid={dataTestId + '-paused'}
|
||||||
name="checkmark"
|
icon={faPause}
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (lastCommandType === 'execution-done') {
|
} else if (engineStreamState.value === EngineStreamState.Playing) {
|
||||||
className +=
|
className += 'text-secondary'
|
||||||
'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 = (
|
icon = (
|
||||||
<CustomIcon
|
<FontAwesomeIcon
|
||||||
data-testid={dataTestId + '-execution-done'}
|
data-testid={dataTestId + '-playing'}
|
||||||
name="checkmark"
|
icon={faPlay}
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
className += 'text-secondary'
|
||||||
|
icon = (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
data-testid={dataTestId + '-resuming'}
|
||||||
|
icon={faSpinner}
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||||
import type { Actor, ContextFrom, Prop, SnapshotFrom, StateFrom } from 'xstate'
|
import type { Actor, ContextFrom, Prop, SnapshotFrom, StateFrom } from 'xstate'
|
||||||
import { assign, fromPromise } from 'xstate'
|
import { assign, fromPromise } from 'xstate'
|
||||||
|
|
||||||
@ -45,7 +45,6 @@ import {
|
|||||||
useSketchModeMenuEnableDisable,
|
useSketchModeMenuEnableDisable,
|
||||||
} from '@src/hooks/useMenu'
|
} from '@src/hooks/useMenu'
|
||||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||||
import { useSetupEngineManager } from '@src/hooks/useSetupEngineManager'
|
|
||||||
import useStateMachineCommands from '@src/hooks/useStateMachineCommands'
|
import useStateMachineCommands from '@src/hooks/useStateMachineCommands'
|
||||||
import { useKclContext } from '@src/lang/KclProvider'
|
import { useKclContext } from '@src/lang/KclProvider'
|
||||||
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
||||||
@ -140,14 +139,7 @@ export const ModelingMachineProvider = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
app: { theme, allowOrbitInSketchMode },
|
app: { theme, allowOrbitInSketchMode },
|
||||||
modeling: {
|
modeling: { defaultUnit, cameraProjection, highlightEdges, cameraOrbit },
|
||||||
defaultUnit,
|
|
||||||
cameraProjection,
|
|
||||||
highlightEdges,
|
|
||||||
showScaleGrid,
|
|
||||||
cameraOrbit,
|
|
||||||
enableSSAO,
|
|
||||||
},
|
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { context, send: fileMachineSend } = useFileContext()
|
const { context, send: fileMachineSend } = useFileContext()
|
||||||
@ -156,13 +148,11 @@ export const ModelingMachineProvider = ({
|
|||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||||
|
|
||||||
let [searchParams] = useSearchParams()
|
|
||||||
const pool = searchParams.get('pool')
|
|
||||||
|
|
||||||
const isCommandBarClosed = useSelector(
|
const isCommandBarClosed = useSelector(
|
||||||
commandBarActor,
|
commandBarActor,
|
||||||
commandBarIsClosedSelector
|
commandBarIsClosedSelector
|
||||||
)
|
)
|
||||||
|
|
||||||
// Settings machine setup
|
// Settings machine setup
|
||||||
// const retrievedSettings = useRef(
|
// const retrievedSettings = useRef(
|
||||||
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
||||||
@ -1913,22 +1903,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}, [modelingActor])
|
}, [modelingActor])
|
||||||
|
|
||||||
useSetupEngineManager(
|
|
||||||
streamRef,
|
|
||||||
modelingSend,
|
|
||||||
modelingState.context,
|
|
||||||
{
|
|
||||||
pool: pool,
|
|
||||||
theme: theme.current,
|
|
||||||
highlightEdges: highlightEdges.current,
|
|
||||||
enableSSAO: enableSSAO.current,
|
|
||||||
showScaleGrid: showScaleGrid.current,
|
|
||||||
cameraProjection: cameraProjection.current,
|
|
||||||
cameraOrbit: cameraOrbit.current,
|
|
||||||
},
|
|
||||||
token
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
kclManager.registerExecuteCallback(() => {
|
kclManager.registerExecuteCallback(() => {
|
||||||
modelingSend({ type: 'Re-execute' })
|
modelingSend({ type: 'Re-execute' })
|
||||||
|
@ -90,6 +90,7 @@ export const KclEditorPane = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
kclEditorActor.send({ type: 'setKclEditorMounted', data: false })
|
kclEditorActor.send({ type: 'setKclEditorMounted', data: false })
|
||||||
kclEditorActor.send({ type: 'setLastSelectionEvent', data: undefined })
|
kclEditorActor.send({ type: 'setLastSelectionEvent', data: undefined })
|
||||||
|
kclManager.diagnostics = []
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
|
||||||
|
|
||||||
import {
|
|
||||||
NETWORK_HEALTH_TEXT,
|
|
||||||
NetworkHealthIndicator,
|
|
||||||
} from '@src/components/NetworkHealthIndicator'
|
|
||||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
|
||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
|
||||||
// wrap in router and xState context
|
|
||||||
return <BrowserRouter>{children}</BrowserRouter>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our Playwright tests for this are much more comprehensive.
|
|
||||||
describe('NetworkHealthIndicator tests', () => {
|
|
||||||
test('Renders the network indicator', () => {
|
|
||||||
render(
|
|
||||||
<TestWrap>
|
|
||||||
<NetworkHealthIndicator />
|
|
||||||
</TestWrap>
|
|
||||||
)
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId('network-toggle'))
|
|
||||||
|
|
||||||
// Starts as disconnected
|
|
||||||
expect(screen.getByTestId('network')).toHaveTextContent(
|
|
||||||
NETWORK_HEALTH_TEXT[NetworkHealthState.Disconnected]
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
@ -86,6 +86,7 @@ export const NetworkHealthIndicator = () => {
|
|||||||
error,
|
error,
|
||||||
setHasCopied,
|
setHasCopied,
|
||||||
hasCopied,
|
hasCopied,
|
||||||
|
ping,
|
||||||
} = useNetworkContext()
|
} = useNetworkContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -129,6 +130,19 @@ export const NetworkHealthIndicator = () => {
|
|||||||
{NETWORK_HEALTH_TEXT[overallState]}
|
{NETWORK_HEALTH_TEXT[overallState]}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={`flex items-center justify-between p-2 rounded-t-sm`}>
|
||||||
|
<h2
|
||||||
|
className={`text-xs font-sans font-normal ${overallConnectionStateColor[overallState].icon}`}
|
||||||
|
>
|
||||||
|
Ping
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
data-testid="network"
|
||||||
|
className={`font-bold text-xs uppercase px-2 py-1 rounded-sm ${overallConnectionStateColor[overallState].icon}`}
|
||||||
|
>
|
||||||
|
{ping ?? 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||||
{Object.keys(steps).map((name) => (
|
{Object.keys(steps).map((name) => (
|
||||||
<li
|
<li
|
||||||
|
@ -1,414 +0,0 @@
|
|||||||
import { useAppStream } from '@src/AppState'
|
|
||||||
import type { MouseEventHandler } from 'react'
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import { useRouteLoaderData } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { ClientSideScene } from '@src/clientSideScene/ClientSideSceneComp'
|
|
||||||
import Loading from '@src/components/Loading'
|
|
||||||
import { ViewControlContextMenu } from '@src/components/ViewControlMenu'
|
|
||||||
import { useModelingContext } from '@src/hooks/useModelingContext'
|
|
||||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
|
||||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
|
||||||
import { getArtifactOfTypes } from '@src/lang/std/artifactGraph'
|
|
||||||
import {
|
|
||||||
DisconnectingType,
|
|
||||||
EngineCommandManagerEvents,
|
|
||||||
EngineConnectionStateType,
|
|
||||||
} from '@src/lang/std/engineConnection'
|
|
||||||
import { btnName } from '@src/lib/cameraControls'
|
|
||||||
import { PATHS } from '@src/lib/paths'
|
|
||||||
import { sendSelectEventToEngine } from '@src/lib/selections'
|
|
||||||
import {
|
|
||||||
engineCommandManager,
|
|
||||||
kclManager,
|
|
||||||
sceneInfra,
|
|
||||||
} from '@src/lib/singletons'
|
|
||||||
import { err, reportRejection } from '@src/lib/trap'
|
|
||||||
import type { IndexLoaderData } from '@src/lib/types'
|
|
||||||
import { uuidv4 } from '@src/lib/utils'
|
|
||||||
import { useSettings } from '@src/machines/appMachine'
|
|
||||||
import { useCommandBarState } from '@src/machines/commandBarMachine'
|
|
||||||
|
|
||||||
enum StreamState {
|
|
||||||
Playing = 'playing',
|
|
||||||
Paused = 'paused',
|
|
||||||
Resuming = 'resuming',
|
|
||||||
Unset = 'unset',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Stream = () => {
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
|
||||||
const videoWrapperRef = useRef<HTMLDivElement>(null)
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
|
||||||
const settings = useSettings()
|
|
||||||
const { state, send } = useModelingContext()
|
|
||||||
const commandBarState = useCommandBarState()
|
|
||||||
const { mediaStream } = useAppStream()
|
|
||||||
const { overallState, immediateState } = useNetworkContext()
|
|
||||||
const [streamState, setStreamState] = useState(StreamState.Unset)
|
|
||||||
const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
|
||||||
|
|
||||||
const IDLE = settings.app.streamIdleMode.current
|
|
||||||
|
|
||||||
const isNetworkOkay =
|
|
||||||
overallState === NetworkHealthState.Ok ||
|
|
||||||
overallState === NetworkHealthState.Weak
|
|
||||||
|
|
||||||
engineCommandManager.elVideo = videoRef.current
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute code and show a "building scene message"
|
|
||||||
* in Stream.tsx in the meantime.
|
|
||||||
*
|
|
||||||
* I would like for this to live somewhere more central,
|
|
||||||
* but it seems to me that we need the video element ref
|
|
||||||
* to be able to play the video after the code has been
|
|
||||||
* executed. If we can find a way to do this from a more
|
|
||||||
* central place, we can move this code there.
|
|
||||||
*/
|
|
||||||
function executeCodeAndPlayStream() {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
kclManager.executeCode().then(async () => {
|
|
||||||
await videoRef.current?.play().catch((e) => {
|
|
||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
|
||||||
})
|
|
||||||
setStreamState(StreamState.Playing)
|
|
||||||
|
|
||||||
// Only call zoom_to_fit once when the stream starts to center the scene.
|
|
||||||
await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'zoom_to_fit',
|
|
||||||
object_ids: [], // leave empty to zoom to all objects
|
|
||||||
padding: 0.1, // padding around the objects
|
|
||||||
animated: false, // don't animate the zoom for now
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe to execute code when the file changes
|
|
||||||
* but only if the scene is already ready.
|
|
||||||
* See onSceneReady for the initial scene setup.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
if (engineCommandManager.engineConnection?.isReady() && file?.path) {
|
|
||||||
console.log('execute on file change')
|
|
||||||
executeCodeAndPlayStream()
|
|
||||||
}
|
|
||||||
}, [file?.path, engineCommandManager.engineConnection])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
immediateState.type === EngineConnectionStateType.Disconnecting &&
|
|
||||||
immediateState.value.type === DisconnectingType.Pause
|
|
||||||
) {
|
|
||||||
setStreamState(StreamState.Paused)
|
|
||||||
}
|
|
||||||
}, [immediateState])
|
|
||||||
|
|
||||||
// Linux has a default behavior to paste text on middle mouse up
|
|
||||||
// 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
|
|
||||||
// middle mouse drag with a text input focused without pasting.
|
|
||||||
useEffect(() => {
|
|
||||||
const handlePaste = (e: ClipboardEvent) => {
|
|
||||||
const isHtmlElement = e.target && e.target instanceof HTMLElement
|
|
||||||
const isEditable =
|
|
||||||
(isHtmlElement && !('explicitOriginalTarget' in e)) ||
|
|
||||||
('explicitOriginalTarget' in e &&
|
|
||||||
((e.explicitOriginalTarget as HTMLElement).contentEditable ===
|
|
||||||
'true' ||
|
|
||||||
['INPUT', 'TEXTAREA'].some(
|
|
||||||
(tagName) =>
|
|
||||||
tagName === (e.explicitOriginalTarget as HTMLElement).tagName
|
|
||||||
)))
|
|
||||||
if (!isEditable) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
e.stopImmediatePropagation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis?.window?.document?.addEventListener('paste', handlePaste, {
|
|
||||||
capture: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const IDLE_TIME_MS = 1000 * 60 * 2
|
|
||||||
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
|
||||||
|
|
||||||
const teardown = () => {
|
|
||||||
// Already paused
|
|
||||||
if (streamState === StreamState.Paused) return
|
|
||||||
|
|
||||||
videoRef.current?.pause()
|
|
||||||
setStreamState(StreamState.Paused)
|
|
||||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
|
||||||
// Give video time to pause
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
engineCommandManager.tearDown({ idleMode: true })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onVisibilityChange = () => {
|
|
||||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
|
||||||
clearTimeout(timeoutIdIdleA)
|
|
||||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
|
||||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
|
||||||
clearTimeout(timeoutIdIdleA)
|
|
||||||
setStreamState(StreamState.Resuming)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Teardown everything if we go hidden or reconnect
|
|
||||||
if (IDLE) {
|
|
||||||
globalThis?.window?.document?.addEventListener(
|
|
||||||
'visibilitychange',
|
|
||||||
onVisibilityChange
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
|
||||||
|
|
||||||
const onAnyInput = () => {
|
|
||||||
if (streamState === StreamState.Playing) {
|
|
||||||
// Clear both timers
|
|
||||||
clearTimeout(timeoutIdIdleA)
|
|
||||||
clearTimeout(timeoutIdIdleB)
|
|
||||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
|
||||||
}
|
|
||||||
if (streamState === StreamState.Paused) {
|
|
||||||
setStreamState(StreamState.Resuming)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IDLE) {
|
|
||||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
|
||||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
|
||||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
|
||||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
|
||||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IDLE) {
|
|
||||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener to execute code and play the stream
|
|
||||||
* on initial stream setup.
|
|
||||||
*/
|
|
||||||
engineCommandManager.addEventListener(
|
|
||||||
EngineCommandManagerEvents.SceneReady,
|
|
||||||
executeCodeAndPlayStream
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
engineCommandManager.removeEventListener(
|
|
||||||
EngineCommandManagerEvents.SceneReady,
|
|
||||||
executeCodeAndPlayStream
|
|
||||||
)
|
|
||||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
|
||||||
capture: true,
|
|
||||||
})
|
|
||||||
if (IDLE) {
|
|
||||||
clearTimeout(timeoutIdIdleA)
|
|
||||||
clearTimeout(timeoutIdIdleB)
|
|
||||||
|
|
||||||
globalThis?.window?.document?.removeEventListener(
|
|
||||||
'visibilitychange',
|
|
||||||
onVisibilityChange
|
|
||||||
)
|
|
||||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
|
||||||
globalThis?.window?.document?.removeEventListener(
|
|
||||||
'mousemove',
|
|
||||||
onAnyInput
|
|
||||||
)
|
|
||||||
globalThis?.window?.document?.removeEventListener(
|
|
||||||
'mousedown',
|
|
||||||
onAnyInput
|
|
||||||
)
|
|
||||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
|
||||||
globalThis?.window?.document?.removeEventListener(
|
|
||||||
'touchstart',
|
|
||||||
onAnyInput
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [IDLE, streamState])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
typeof window === 'undefined' ||
|
|
||||||
typeof RTCPeerConnection === 'undefined'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if (!videoRef.current) return
|
|
||||||
if (!mediaStream) return
|
|
||||||
|
|
||||||
// The browser complains if we try to load a new stream without pausing first.
|
|
||||||
// Do not immediately play the stream!
|
|
||||||
// we instead use a setTimeout to play the stream in the next event loop
|
|
||||||
try {
|
|
||||||
videoRef.current.srcObject = mediaStream
|
|
||||||
videoRef.current.pause()
|
|
||||||
setTimeout(() => {
|
|
||||||
videoRef.current?.play().catch((e) => {
|
|
||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Attempted to pause stream while play was still loading', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: 'Set context',
|
|
||||||
data: {
|
|
||||||
videoElement: videoRef.current,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
}, [mediaStream])
|
|
||||||
|
|
||||||
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
||||||
// If we've got no stream or connection, don't do anything
|
|
||||||
if (!isNetworkOkay) return
|
|
||||||
if (!videoRef.current) return
|
|
||||||
// If we're in sketch mode, don't send a engine-side select event
|
|
||||||
if (state.matches('Sketch')) return
|
|
||||||
// Only respect default plane selection if we're on a selection command argument
|
|
||||||
if (
|
|
||||||
state.matches({ idle: 'showPlanes' }) &&
|
|
||||||
!(
|
|
||||||
commandBarState.matches('Gathering arguments') &&
|
|
||||||
commandBarState.context.currentArgument?.inputType === 'selection'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
// If we're mousing up from a camera drag, don't send a select event
|
|
||||||
if (sceneInfra.camControls.wasDragging === true) return
|
|
||||||
|
|
||||||
if (btnName(e.nativeEvent).left) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
sendSelectEventToEngine(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On double-click of sketch entities we automatically enter sketch mode with the selected sketch,
|
|
||||||
* allowing for quick editing of sketches. TODO: This should be moved to a more central place.
|
|
||||||
*/
|
|
||||||
const enterSketchModeIfSelectingSketch: MouseEventHandler<HTMLDivElement> = (
|
|
||||||
e
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
!isNetworkOkay ||
|
|
||||||
!videoRef.current ||
|
|
||||||
state.matches('Sketch') ||
|
|
||||||
state.matches({ idle: 'showPlanes' }) ||
|
|
||||||
sceneInfra.camControls.wasDragging === true ||
|
|
||||||
!btnName(e.nativeEvent).left
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSelectEventToEngine(e)
|
|
||||||
.then(({ entity_id }) => {
|
|
||||||
if (!entity_id) {
|
|
||||||
// No entity selected. This is benign
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const path = getArtifactOfTypes(
|
|
||||||
{ key: entity_id, types: ['path', 'solid2d', 'segment', 'helix'] },
|
|
||||||
kclManager.artifactGraph
|
|
||||||
)
|
|
||||||
if (err(path)) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
sceneInfra.modelingSend({ type: 'Enter sketch' })
|
|
||||||
})
|
|
||||||
.catch(reportRejection)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
||||||
<div
|
|
||||||
ref={videoWrapperRef}
|
|
||||||
className="absolute inset-0 z-0"
|
|
||||||
id="stream"
|
|
||||||
data-testid="stream"
|
|
||||||
onClick={handleClick}
|
|
||||||
onDoubleClick={enterSketchModeIfSelectingSketch}
|
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
|
||||||
onContextMenuCapture={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<video
|
|
||||||
ref={videoRef}
|
|
||||||
muted
|
|
||||||
autoPlay
|
|
||||||
controls={false}
|
|
||||||
onPlay={() => setIsLoading(false)}
|
|
||||||
className="w-full cursor-pointer h-full"
|
|
||||||
disablePictureInPicture
|
|
||||||
id="video-stream"
|
|
||||||
/>
|
|
||||||
<ClientSideScene
|
|
||||||
cameraControls={settings.modeling.mouseControls.current}
|
|
||||||
/>
|
|
||||||
{(streamState === StreamState.Paused ||
|
|
||||||
streamState === StreamState.Resuming) && (
|
|
||||||
<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">
|
|
||||||
{streamState === StreamState.Paused && 'Paused'}
|
|
||||||
{streamState === StreamState.Resuming && 'Resuming'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{(!isNetworkOkay || isLoading) && (
|
|
||||||
<div className="text-center absolute inset-0">
|
|
||||||
<Loading>
|
|
||||||
{!isNetworkOkay && !isLoading ? (
|
|
||||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
|
||||||
) : (
|
|
||||||
!isLoading && (
|
|
||||||
<span data-testid="loading-stream">Loading stream...</span>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Loading>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ViewControlContextMenu
|
|
||||||
event="mouseup"
|
|
||||||
guard={(e) =>
|
|
||||||
sceneInfra.camControls.wasDragging === false &&
|
|
||||||
btnName(e).right === true
|
|
||||||
}
|
|
||||||
menuTargetElement={videoWrapperRef}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -47,7 +47,6 @@ export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
|||||||
export default class EditorManager {
|
export default class EditorManager {
|
||||||
private _copilotEnabled: boolean = true
|
private _copilotEnabled: boolean = true
|
||||||
private engineCommandManager: EngineCommandManager
|
private engineCommandManager: EngineCommandManager
|
||||||
private kclManager: KclManager
|
|
||||||
|
|
||||||
private _isAllTextSelected: boolean = false
|
private _isAllTextSelected: boolean = false
|
||||||
private _isShiftDown: boolean = false
|
private _isShiftDown: boolean = false
|
||||||
@ -67,13 +66,10 @@ export default class EditorManager {
|
|||||||
private _highlightRange: Array<[number, number]> = [[0, 0]]
|
private _highlightRange: Array<[number, number]> = [[0, 0]]
|
||||||
|
|
||||||
public _editorView: EditorView | null = null
|
public _editorView: EditorView | null = null
|
||||||
|
public kclManager?: KclManager
|
||||||
|
|
||||||
constructor(
|
constructor(engineCommandManager: EngineCommandManager) {
|
||||||
engineCommandManager: EngineCommandManager,
|
|
||||||
kclManager: KclManager
|
|
||||||
) {
|
|
||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
this.kclManager = kclManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCopilotEnabled(enabled: boolean) {
|
setCopilotEnabled(enabled: boolean) {
|
||||||
@ -387,6 +383,11 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!this.kclManager) {
|
||||||
|
console.error('unreachable')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const eventInfo = processCodeMirrorRanges({
|
const eventInfo = processCodeMirrorRanges({
|
||||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||||
selectionRanges: this._selectionRanges,
|
selectionRanges: this._selectionRanges,
|
||||||
|
@ -25,7 +25,7 @@ export const NetworkContext = createContext<NetworkStatus>({
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
setHasCopied: (b: boolean) => {},
|
setHasCopied: (b: boolean) => {},
|
||||||
hasCopied: false,
|
hasCopied: false,
|
||||||
pingPongHealth: undefined,
|
ping: undefined,
|
||||||
} as NetworkStatus)
|
} as NetworkStatus)
|
||||||
export const useNetworkContext = () => {
|
export const useNetworkContext = () => {
|
||||||
return useContext(NetworkContext)
|
return useContext(NetworkContext)
|
||||||
|
@ -32,7 +32,7 @@ export interface NetworkStatus {
|
|||||||
error: ErrorType | undefined
|
error: ErrorType | undefined
|
||||||
setHasCopied: (b: boolean) => void
|
setHasCopied: (b: boolean) => void
|
||||||
hasCopied: boolean
|
hasCopied: boolean
|
||||||
pingPongHealth: undefined | 'OK' | 'TIMEOUT'
|
ping: undefined | number
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be called from one place in the application.
|
// Must be called from one place in the application.
|
||||||
@ -48,9 +48,7 @@ export function useNetworkStatus() {
|
|||||||
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
||||||
NetworkHealthState.Disconnected
|
NetworkHealthState.Disconnected
|
||||||
)
|
)
|
||||||
const [pingPongHealth, setPingPongHealth] = useState<
|
const [ping, setPing] = useState<undefined | number>(undefined)
|
||||||
undefined | 'OK' | 'TIMEOUT'
|
|
||||||
>(undefined)
|
|
||||||
const [hasCopied, setHasCopied] = useState<boolean>(false)
|
const [hasCopied, setHasCopied] = useState<boolean>(false)
|
||||||
|
|
||||||
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
||||||
@ -73,11 +71,11 @@ export function useNetworkStatus() {
|
|||||||
? NetworkHealthState.Disconnected
|
? NetworkHealthState.Disconnected
|
||||||
: hasIssues || hasIssues === undefined
|
: hasIssues || hasIssues === undefined
|
||||||
? NetworkHealthState.Issue
|
? NetworkHealthState.Issue
|
||||||
: pingPongHealth === 'TIMEOUT'
|
: (ping ?? 0) > 16.6 * 3 // we consider ping longer than 3 frames as weak
|
||||||
? NetworkHealthState.Weak
|
? NetworkHealthState.Weak
|
||||||
: NetworkHealthState.Ok
|
: NetworkHealthState.Ok
|
||||||
)
|
)
|
||||||
}, [hasIssues, internetConnected, pingPongHealth])
|
}, [hasIssues, internetConnected, ping])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onlineCallback = () => {
|
const onlineCallback = () => {
|
||||||
@ -128,7 +126,7 @@ export function useNetworkStatus() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onPingPongChange = ({ detail: state }: CustomEvent) => {
|
const onPingPongChange = ({ detail: state }: CustomEvent) => {
|
||||||
setPingPongHealth(state)
|
setPing(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onConnectionStateChange = ({
|
const onConnectionStateChange = ({
|
||||||
@ -233,6 +231,6 @@ export function useNetworkStatus() {
|
|||||||
error,
|
error,
|
||||||
setHasCopied,
|
setHasCopied,
|
||||||
hasCopied,
|
hasCopied,
|
||||||
pingPongHealth,
|
ping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,183 +0,0 @@
|
|||||||
import { useAppState, useAppStream } from '@src/AppState'
|
|
||||||
import { useEffect, useLayoutEffect, useRef } from 'react'
|
|
||||||
|
|
||||||
import type { useModelingContext } from '@src/hooks/useModelingContext'
|
|
||||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
|
||||||
import {
|
|
||||||
DisconnectingType,
|
|
||||||
EngineConnectionStateType,
|
|
||||||
} from '@src/lang/std/engineConnection'
|
|
||||||
import type { SettingsViaQueryString } from '@src/lib/settings/settingsTypes'
|
|
||||||
import { engineCommandManager } from '@src/lib/singletons'
|
|
||||||
import { Themes } from '@src/lib/theme'
|
|
||||||
import { deferExecution } from '@src/lib/utils'
|
|
||||||
|
|
||||||
export function useSetupEngineManager(
|
|
||||||
streamRef: React.RefObject<HTMLDivElement>,
|
|
||||||
modelingSend: ReturnType<typeof useModelingContext>['send'],
|
|
||||||
modelingContext: ReturnType<typeof useModelingContext>['context'],
|
|
||||||
settings: SettingsViaQueryString = {
|
|
||||||
pool: null,
|
|
||||||
theme: Themes.System,
|
|
||||||
highlightEdges: true,
|
|
||||||
enableSSAO: true,
|
|
||||||
showScaleGrid: false,
|
|
||||||
cameraProjection: 'perspective',
|
|
||||||
cameraOrbit: 'spherical',
|
|
||||||
},
|
|
||||||
token?: string
|
|
||||||
) {
|
|
||||||
const networkContext = useNetworkContext()
|
|
||||||
const { pingPongHealth, immediateState } = networkContext
|
|
||||||
const { setAppState } = useAppState()
|
|
||||||
const { setMediaStream } = useAppStream()
|
|
||||||
|
|
||||||
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
|
||||||
|
|
||||||
if (settings.pool) {
|
|
||||||
// override the pool param (?pool=) to request a specific engine instance
|
|
||||||
// from a particular pool.
|
|
||||||
engineCommandManager.settings.pool = settings.pool
|
|
||||||
}
|
|
||||||
|
|
||||||
const startEngineInstance = () => {
|
|
||||||
// Load the engine command manager once with the initial width and height,
|
|
||||||
// then we do not want to reload it.
|
|
||||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
|
||||||
streamRef?.current?.offsetWidth ?? 0,
|
|
||||||
streamRef?.current?.offsetHeight ?? 0
|
|
||||||
)
|
|
||||||
engineCommandManager.start({
|
|
||||||
setMediaStream: (mediaStream) => setMediaStream(mediaStream),
|
|
||||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
|
||||||
width: quadWidth,
|
|
||||||
height: quadHeight,
|
|
||||||
token,
|
|
||||||
settings,
|
|
||||||
})
|
|
||||||
hasSetNonZeroDimensions.current = true
|
|
||||||
}
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
|
||||||
streamRef?.current?.offsetWidth ?? 0,
|
|
||||||
streamRef?.current?.offsetHeight ?? 0
|
|
||||||
)
|
|
||||||
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
|
||||||
startEngineInstance()
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
streamRef?.current?.offsetWidth,
|
|
||||||
streamRef?.current?.offsetHeight,
|
|
||||||
modelingSend,
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (pingPongHealth === 'TIMEOUT') {
|
|
||||||
engineCommandManager.tearDown()
|
|
||||||
}
|
|
||||||
}, [pingPongHealth])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
if (immediateState.type === EngineConnectionStateType.Disconnected) {
|
|
||||||
engineCommandManager.engineConnection = undefined
|
|
||||||
startEngineInstance()
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
return () => {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
}
|
|
||||||
}, [immediateState])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
engineCommandManager.settings = settings
|
|
||||||
|
|
||||||
const handleResize = deferExecution(() => {
|
|
||||||
engineCommandManager.handleResize(
|
|
||||||
getDimensions(
|
|
||||||
streamRef?.current?.offsetWidth ?? 0,
|
|
||||||
streamRef?.current?.offsetHeight ?? 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}, 500)
|
|
||||||
|
|
||||||
const onOnline = () => {
|
|
||||||
startEngineInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onVisibilityChange = () => {
|
|
||||||
if (window.document.visibilityState === 'visible') {
|
|
||||||
if (
|
|
||||||
!engineCommandManager.engineConnection?.isReady() &&
|
|
||||||
!engineCommandManager.engineConnection?.isConnecting()
|
|
||||||
) {
|
|
||||||
startEngineInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.document.addEventListener('visibilitychange', onVisibilityChange)
|
|
||||||
|
|
||||||
const onAnyInput = () => {
|
|
||||||
const isEngineNotReadyOrConnecting =
|
|
||||||
!engineCommandManager.engineConnection?.isReady() &&
|
|
||||||
!engineCommandManager.engineConnection?.isConnecting()
|
|
||||||
|
|
||||||
const conn = engineCommandManager.engineConnection
|
|
||||||
|
|
||||||
const isStreamPaused =
|
|
||||||
conn?.state.type === EngineConnectionStateType.Disconnecting &&
|
|
||||||
conn?.state.value.type === DisconnectingType.Pause
|
|
||||||
|
|
||||||
if (isEngineNotReadyOrConnecting || isStreamPaused) {
|
|
||||||
engineCommandManager.engineConnection = undefined
|
|
||||||
startEngineInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.document.addEventListener('keydown', onAnyInput)
|
|
||||||
window.document.addEventListener('mousemove', onAnyInput)
|
|
||||||
window.document.addEventListener('mousedown', onAnyInput)
|
|
||||||
window.document.addEventListener('scroll', onAnyInput)
|
|
||||||
window.document.addEventListener('touchstart', onAnyInput)
|
|
||||||
|
|
||||||
const onOffline = () => {
|
|
||||||
engineCommandManager.tearDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('online', onOnline)
|
|
||||||
window.addEventListener('offline', onOffline)
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
return () => {
|
|
||||||
window.document.removeEventListener(
|
|
||||||
'visibilitychange',
|
|
||||||
onVisibilityChange
|
|
||||||
)
|
|
||||||
window.document.removeEventListener('keydown', onAnyInput)
|
|
||||||
window.document.removeEventListener('mousemove', onAnyInput)
|
|
||||||
window.document.removeEventListener('mousedown', onAnyInput)
|
|
||||||
window.document.removeEventListener('scroll', onAnyInput)
|
|
||||||
window.document.removeEventListener('touchstart', onAnyInput)
|
|
||||||
window.removeEventListener('online', onOnline)
|
|
||||||
window.removeEventListener('offline', onOffline)
|
|
||||||
window.removeEventListener('resize', handleResize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Engine relies on many settings so we should rebind events when it changes
|
|
||||||
// We have to list out the ones we care about because the settings object holds
|
|
||||||
// non-settings too...
|
|
||||||
}, [...Object.values(settings)])
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
|
||||||
const factorOf = 4
|
|
||||||
const maxResolution = 2000
|
|
||||||
const width = streamWidth ? streamWidth : 0
|
|
||||||
const height = streamHeight ? streamHeight : 0
|
|
||||||
const ratio = Math.min(
|
|
||||||
Math.min(maxResolution / width, maxResolution / height),
|
|
||||||
1.0
|
|
||||||
)
|
|
||||||
const quadWidth = Math.round((width * ratio) / factorOf) * factorOf
|
|
||||||
const quadHeight = Math.round((height * ratio) / factorOf) * factorOf
|
|
||||||
return { width: quadWidth, height: quadHeight }
|
|
||||||
}
|
|
@ -3,6 +3,10 @@ import type {
|
|||||||
EntityType_type,
|
EntityType_type,
|
||||||
ModelingCmdReq_type,
|
ModelingCmdReq_type,
|
||||||
} from '@kittycad/lib/dist/types/src/models'
|
} from '@kittycad/lib/dist/types/src/models'
|
||||||
|
import type { SceneInfra } from '@src/clientSideScene/sceneInfra'
|
||||||
|
import type EditorManager from '@src/editor/manager'
|
||||||
|
import type CodeManager from '@src/lang/codeManager'
|
||||||
|
import type RustContext from '@src/lib/rustContext'
|
||||||
|
|
||||||
import type { KclValue } from '@rust/kcl-lib/bindings/KclValue'
|
import type { KclValue } from '@rust/kcl-lib/bindings/KclValue'
|
||||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
@ -16,6 +20,7 @@ import {
|
|||||||
import { executeAst, executeAstMock, lintAst } from '@src/lang/langHelpers'
|
import { executeAst, executeAstMock, lintAst } from '@src/lang/langHelpers'
|
||||||
import { getNodeFromPath, getSettingsAnnotation } from '@src/lang/queryAst'
|
import { getNodeFromPath, getSettingsAnnotation } from '@src/lang/queryAst'
|
||||||
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
|
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
|
||||||
|
import { CommandLogType } from '@src/lang/std/engineConnection'
|
||||||
import { topLevelRange } from '@src/lang/util'
|
import { topLevelRange } from '@src/lang/util'
|
||||||
import type {
|
import type {
|
||||||
ArtifactGraph,
|
ArtifactGraph,
|
||||||
@ -46,12 +51,7 @@ import type {
|
|||||||
KclSettingsAnnotation,
|
KclSettingsAnnotation,
|
||||||
} from '@src/lib/settings/settingsTypes'
|
} from '@src/lib/settings/settingsTypes'
|
||||||
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
|
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
|
||||||
import {
|
|
||||||
codeManager,
|
|
||||||
editorManager,
|
|
||||||
rustContext,
|
|
||||||
sceneInfra,
|
|
||||||
} from '@src/lib/singletons'
|
|
||||||
import { err, reportRejection } from '@src/lib/trap'
|
import { err, reportRejection } from '@src/lib/trap'
|
||||||
import { deferExecution, isOverlap, uuidv4 } from '@src/lib/utils'
|
import { deferExecution, isOverlap, uuidv4 } from '@src/lib/utils'
|
||||||
|
|
||||||
@ -60,6 +60,15 @@ interface ExecuteArgs {
|
|||||||
executionId?: number
|
executionId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each of our singletons has dependencies on _other_ singletons, so importing
|
||||||
|
// can easily become cyclic. Each will have its own Singletons type.
|
||||||
|
interface Singletons {
|
||||||
|
rustContext: RustContext
|
||||||
|
codeManager: CodeManager
|
||||||
|
editorManager: EditorManager
|
||||||
|
sceneInfra: SceneInfra
|
||||||
|
}
|
||||||
|
|
||||||
export class KclManager {
|
export class KclManager {
|
||||||
/**
|
/**
|
||||||
* The artifactGraph is a client-side representation of the commands that have been sent
|
* The artifactGraph is a client-side representation of the commands that have been sent
|
||||||
@ -98,6 +107,7 @@ export class KclManager {
|
|||||||
private _switchedFiles = false
|
private _switchedFiles = false
|
||||||
private _fileSettings: KclSettingsAnnotation = {}
|
private _fileSettings: KclSettingsAnnotation = {}
|
||||||
private _kclVersion: string | undefined = undefined
|
private _kclVersion: string | undefined = undefined
|
||||||
|
private singletons: Singletons
|
||||||
|
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
|
||||||
@ -188,7 +198,7 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDiagnosticsForCurrentErrors() {
|
setDiagnosticsForCurrentErrors() {
|
||||||
editorManager?.setDiagnostics(this.diagnostics)
|
this.singletons.editorManager?.setDiagnostics(this.diagnostics)
|
||||||
this._diagnosticsCallback(this.diagnostics)
|
this._diagnosticsCallback(this.diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,12 +235,16 @@ export class KclManager {
|
|||||||
this._wasmInitFailedCallback(wasmInitFailed)
|
this._wasmInitFailedCallback(wasmInitFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(engineCommandManager: EngineCommandManager) {
|
constructor(
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
singletons: Singletons
|
||||||
|
) {
|
||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
|
this.singletons = singletons
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.ensureWasmInit().then(async () => {
|
this.ensureWasmInit().then(async () => {
|
||||||
await this.safeParse(codeManager.code).then((ast) => {
|
await this.safeParse(this.singletons.codeManager.code).then((ast) => {
|
||||||
if (ast) {
|
if (ast) {
|
||||||
this.ast = ast
|
this.ast = ast
|
||||||
}
|
}
|
||||||
@ -296,9 +310,9 @@ export class KclManager {
|
|||||||
// If we were switching files and we hit an error on parse we need to bust
|
// If we were switching files and we hit an error on parse we need to bust
|
||||||
// the cache and clear the scene.
|
// the cache and clear the scene.
|
||||||
if (this._astParseFailed && this._switchedFiles) {
|
if (this._astParseFailed && this._switchedFiles) {
|
||||||
await rustContext.clearSceneAndBustCache(
|
await this.singletons.rustContext.clearSceneAndBustCache(
|
||||||
{ settings: await jsAppSettings() },
|
{ settings: await jsAppSettings() },
|
||||||
codeManager.currentFilePath || undefined
|
this.singletons.codeManager.currentFilePath || undefined
|
||||||
)
|
)
|
||||||
} else if (this._switchedFiles) {
|
} else if (this._switchedFiles) {
|
||||||
// Reset the switched files boolean.
|
// Reset the switched files boolean.
|
||||||
@ -421,8 +435,8 @@ export class KclManager {
|
|||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
const { logs, errors, execState, isInterrupted } = await executeAst({
|
const { logs, errors, execState, isInterrupted } = await executeAst({
|
||||||
ast,
|
ast,
|
||||||
path: codeManager.currentFilePath || undefined,
|
path: this.singletons.codeManager.currentFilePath || undefined,
|
||||||
rustContext,
|
rustContext: this.singletons.rustContext,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Program was not interrupted, setup the scene
|
// Program was not interrupted, setup the scene
|
||||||
@ -470,10 +484,12 @@ export class KclManager {
|
|||||||
await this.updateArtifactGraph(execState.artifactGraph)
|
await this.updateArtifactGraph(execState.artifactGraph)
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
this.singletons.sceneInfra.modelingSend({
|
||||||
|
type: 'code edit during sketch',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: CommandLogType.ExecutionDone,
|
||||||
data: null,
|
data: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -492,7 +508,7 @@ export class KclManager {
|
|||||||
this.isExecuting = false
|
this.isExecuting = false
|
||||||
this.executeIsStale = null
|
this.executeIsStale = null
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: CommandLogType.ExecutionDone,
|
||||||
data: null,
|
data: null,
|
||||||
})
|
})
|
||||||
markOnce('code/endExecuteAst')
|
markOnce('code/endExecuteAst')
|
||||||
@ -518,7 +534,7 @@ export class KclManager {
|
|||||||
|
|
||||||
const { logs, errors, execState } = await executeAstMock({
|
const { logs, errors, execState } = await executeAstMock({
|
||||||
ast: newAst,
|
ast: newAst,
|
||||||
rustContext,
|
rustContext: this.singletons.rustContext,
|
||||||
})
|
})
|
||||||
|
|
||||||
this._logs = logs
|
this._logs = logs
|
||||||
@ -535,7 +551,7 @@ export class KclManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
async executeCode(): Promise<void> {
|
async executeCode(): Promise<void> {
|
||||||
const ast = await this.safeParse(codeManager.code)
|
const ast = await this.safeParse(this.singletons.codeManager.code)
|
||||||
|
|
||||||
if (!ast) {
|
if (!ast) {
|
||||||
// By clearing the AST we indicate to our callers that there was an issue with execution and
|
// By clearing the AST we indicate to our callers that there was an issue with execution and
|
||||||
@ -549,7 +565,7 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async format() {
|
async format() {
|
||||||
const originalCode = codeManager.code
|
const originalCode = this.singletons.codeManager.code
|
||||||
const ast = await this.safeParse(originalCode)
|
const ast = await this.safeParse(originalCode)
|
||||||
if (!ast) {
|
if (!ast) {
|
||||||
this.clearAst()
|
this.clearAst()
|
||||||
@ -563,10 +579,10 @@ export class KclManager {
|
|||||||
if (originalCode === code) return
|
if (originalCode === code) return
|
||||||
|
|
||||||
// Update the code state and the editor.
|
// Update the code state and the editor.
|
||||||
codeManager.updateCodeStateEditor(code)
|
this.singletons.codeManager.updateCodeStateEditor(code)
|
||||||
|
|
||||||
// Write back to the file system.
|
// Write back to the file system.
|
||||||
void codeManager
|
void this.singletons.codeManager
|
||||||
.writeToFile()
|
.writeToFile()
|
||||||
.then(() => this.executeCode())
|
.then(() => this.executeCode())
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
@ -642,7 +658,7 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get defaultPlanes() {
|
get defaultPlanes() {
|
||||||
return rustContext.defaultPlanes
|
return this.singletons.rustContext.defaultPlanes
|
||||||
}
|
}
|
||||||
|
|
||||||
showPlanes(all = false) {
|
showPlanes(all = false) {
|
||||||
|
@ -20,8 +20,7 @@ import {
|
|||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { binaryToUuid, uuidv4 } from '@src/lib/utils'
|
import { binaryToUuid, uuidv4 } from '@src/lib/utils'
|
||||||
|
|
||||||
// TODO(paultag): This ought to be tweakable.
|
const pingIntervalMs = 1_000
|
||||||
const pingIntervalMs = 5_000
|
|
||||||
|
|
||||||
function isHighlightSetEntity_type(
|
function isHighlightSetEntity_type(
|
||||||
data: any
|
data: any
|
||||||
@ -191,8 +190,6 @@ export type EngineConnectionState =
|
|||||||
| State<EngineConnectionStateType.Disconnecting, DisconnectingValue>
|
| State<EngineConnectionStateType.Disconnecting, DisconnectingValue>
|
||||||
| State<EngineConnectionStateType.Disconnected, void>
|
| State<EngineConnectionStateType.Disconnected, void>
|
||||||
|
|
||||||
export type PingPongState = 'OK' | 'TIMEOUT'
|
|
||||||
|
|
||||||
export enum EngineConnectionEvents {
|
export enum EngineConnectionEvents {
|
||||||
// Fires for each ping-pong success or failure.
|
// Fires for each ping-pong success or failure.
|
||||||
PingPongChanged = 'ping-pong-changed', // (state: PingPongState) => void
|
PingPongChanged = 'ping-pong-changed', // (state: PingPongState) => void
|
||||||
@ -301,13 +298,18 @@ class EngineConnection extends EventTarget {
|
|||||||
public webrtcStatsCollector?: () => Promise<ClientMetrics>
|
public webrtcStatsCollector?: () => Promise<ClientMetrics>
|
||||||
private engineCommandManager: EngineCommandManager
|
private engineCommandManager: EngineCommandManager
|
||||||
|
|
||||||
private pingPongSpan: { ping?: Date; pong?: Date }
|
private pingPongSpan: { ping?: number; pong?: number }
|
||||||
private pingIntervalId: ReturnType<typeof setInterval> = setInterval(
|
private pingIntervalId: ReturnType<typeof setInterval> = setInterval(
|
||||||
() => {},
|
() => {},
|
||||||
60_000
|
60_000
|
||||||
)
|
)
|
||||||
isUsingConnectionLite: boolean = false
|
isUsingConnectionLite: boolean = false
|
||||||
|
|
||||||
|
timeoutToForceConnectId: ReturnType<typeof setTimeout> = setTimeout(
|
||||||
|
() => {},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
url,
|
url,
|
||||||
@ -333,74 +335,26 @@ class EngineConnection extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Without an interval ping, our connection will timeout.
|
|
||||||
// If this.idleMode is true we skip this logic so only reconnect
|
|
||||||
// happens on mouse move
|
|
||||||
this.pingIntervalId = setInterval(() => {
|
this.pingIntervalId = setInterval(() => {
|
||||||
if (this.idleMode) return
|
// Only start a new ping when the other is fulfilled.
|
||||||
|
if (this.pingPongSpan.ping) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch (this.state.type as EngineConnectionStateType) {
|
// Don't start pinging until we're connected.
|
||||||
case EngineConnectionStateType.ConnectionEstablished:
|
if (this.state.type !== EngineConnectionStateType.ConnectionEstablished) {
|
||||||
// If there was no reply to the last ping, report a timeout and
|
return
|
||||||
// teardown the connection.
|
}
|
||||||
if (this.pingPongSpan.ping && !this.pingPongSpan.pong) {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
|
||||||
detail: 'TIMEOUT',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
this.state = {
|
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
|
||||||
value: {
|
|
||||||
type: DisconnectingType.Timeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
this.disconnectAll()
|
|
||||||
|
|
||||||
// Otherwise check the time between was >= pingIntervalMs,
|
this.send({ type: 'ping' })
|
||||||
// and if it was, then it's bad network health.
|
this.pingPongSpan = {
|
||||||
} else if (this.pingPongSpan.ping && this.pingPongSpan.pong) {
|
ping: Date.now(),
|
||||||
if (
|
pong: undefined,
|
||||||
Math.abs(
|
|
||||||
this.pingPongSpan.pong.valueOf() -
|
|
||||||
this.pingPongSpan.ping.valueOf()
|
|
||||||
) >= pingIntervalMs
|
|
||||||
) {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
|
||||||
detail: 'TIMEOUT',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
|
||||||
detail: 'OK',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.send({ type: 'ping' })
|
|
||||||
this.pingPongSpan.ping = new Date()
|
|
||||||
this.pingPongSpan.pong = undefined
|
|
||||||
break
|
|
||||||
case EngineConnectionStateType.Disconnecting:
|
|
||||||
case EngineConnectionStateType.Disconnected:
|
|
||||||
// We will do reconnection elsewhere, because we basically need
|
|
||||||
// to destroy this EngineConnection, and this setInterval loop
|
|
||||||
// lives inside it. (lee) I might change this in the future so it's
|
|
||||||
// outside this class.
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
if (this.isConnecting()) break
|
|
||||||
// Means we never could do an initial connection. Reconnect everything.
|
|
||||||
if (!this.pingPongSpan.ping) this.connect().catch(reportRejection)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}, pingIntervalMs)
|
}, pingIntervalMs)
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.connect()
|
this.connect({ reconnect: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// SHOULD ONLY BE USED FOR VITESTS
|
// SHOULD ONLY BE USED FOR VITESTS
|
||||||
@ -511,7 +465,9 @@ class EngineConnection extends EventTarget {
|
|||||||
this.idleMode = opts?.idleMode ?? false
|
this.idleMode = opts?.idleMode ?? false
|
||||||
clearInterval(this.pingIntervalId)
|
clearInterval(this.pingIntervalId)
|
||||||
|
|
||||||
if (opts?.idleMode) {
|
this.disconnectAll()
|
||||||
|
|
||||||
|
if (this.idleMode) {
|
||||||
this.state = {
|
this.state = {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
@ -530,8 +486,6 @@ class EngineConnection extends EventTarget {
|
|||||||
type: DisconnectingType.Quit,
|
type: DisconnectingType.Quit,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disconnectAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initiateConnectionExclusive(): boolean {
|
initiateConnectionExclusive(): boolean {
|
||||||
@ -585,8 +539,8 @@ class EngineConnection extends EventTarget {
|
|||||||
* This will attempt the full handshake, and retry if the connection
|
* This will attempt the full handshake, and retry if the connection
|
||||||
* did not establish.
|
* did not establish.
|
||||||
*/
|
*/
|
||||||
connect(reconnecting?: boolean): Promise<void> {
|
connect(args: { reconnect: boolean }): Promise<void> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line
|
||||||
const that = this
|
const that = this
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.isConnecting() || this.isReady()) {
|
if (this.isConnecting() || this.isReady()) {
|
||||||
@ -647,7 +601,7 @@ class EngineConnection extends EventTarget {
|
|||||||
|
|
||||||
// Sometimes the remote end doesn't report the end of candidates.
|
// Sometimes the remote end doesn't report the end of candidates.
|
||||||
// They have 3 seconds to.
|
// They have 3 seconds to.
|
||||||
setTimeout(() => {
|
this.timeoutToForceConnectId = setTimeout(() => {
|
||||||
if (that.initiateConnectionExclusive()) {
|
if (that.initiateConnectionExclusive()) {
|
||||||
console.warn('connected after 3 second delay')
|
console.warn('connected after 3 second delay')
|
||||||
}
|
}
|
||||||
@ -958,7 +912,7 @@ class EngineConnection extends EventTarget {
|
|||||||
|
|
||||||
// Send an initial ping
|
// Send an initial ping
|
||||||
this.send({ type: 'ping' })
|
this.send({ type: 'ping' })
|
||||||
this.pingPongSpan.ping = new Date()
|
this.pingPongSpan.ping = Date.now()
|
||||||
}
|
}
|
||||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||||
|
|
||||||
@ -1053,7 +1007,20 @@ class EngineConnection extends EventTarget {
|
|||||||
|
|
||||||
switch (resp.type) {
|
switch (resp.type) {
|
||||||
case 'pong':
|
case 'pong':
|
||||||
this.pingPongSpan.pong = new Date()
|
this.pingPongSpan.pong = Date.now()
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
||||||
|
detail: Math.min(
|
||||||
|
999,
|
||||||
|
Math.floor(
|
||||||
|
this.pingPongSpan.pong - (this.pingPongSpan.ping ?? 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
// Clear the initial ping so our interval ping loop can fire again
|
||||||
|
// But only after using it above!
|
||||||
|
this.pingPongSpan.ping = undefined
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'modeling_session_data':
|
case 'modeling_session_data':
|
||||||
@ -1197,7 +1164,7 @@ class EngineConnection extends EventTarget {
|
|||||||
this.websocket.addEventListener('message', this.onWebSocketMessage)
|
this.websocket.addEventListener('message', this.onWebSocketMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reconnecting) {
|
if (args.reconnect) {
|
||||||
createWebSocketConnection()
|
createWebSocketConnection()
|
||||||
} else {
|
} else {
|
||||||
this.onNetworkStatusReady = () => {
|
this.onNetworkStatusReady = () => {
|
||||||
@ -1210,9 +1177,12 @@ class EngineConnection extends EventTarget {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not change this back to an object or any, we should only be sending the
|
// Do not change this back to an object or any, we should only be sending the
|
||||||
// WebSocketRequest type!
|
// WebSocketRequest type!
|
||||||
unreliableSend(message: Models['WebSocketRequest_type']) {
|
unreliableSend(message: Models['WebSocketRequest_type']) {
|
||||||
|
if (this.unreliableDataChannel?.readyState !== 'open') return
|
||||||
|
|
||||||
// TODO(paultag): Add in logic to determine the connection state and
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
// take actions if needed?
|
// take actions if needed?
|
||||||
this.unreliableDataChannel?.send(
|
this.unreliableDataChannel?.send(
|
||||||
@ -1223,7 +1193,7 @@ class EngineConnection extends EventTarget {
|
|||||||
// WebSocketRequest type!
|
// WebSocketRequest type!
|
||||||
send(message: Models['WebSocketRequest_type']) {
|
send(message: Models['WebSocketRequest_type']) {
|
||||||
// Not connected, don't send anything
|
// Not connected, don't send anything
|
||||||
if (this.websocket?.readyState === 3) return
|
if (this.websocket?.readyState !== 1) return
|
||||||
|
|
||||||
// TODO(paultag): Add in logic to determine the connection state and
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
// take actions if needed?
|
// take actions if needed?
|
||||||
@ -1232,6 +1202,8 @@ class EngineConnection extends EventTarget {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
disconnectAll() {
|
disconnectAll() {
|
||||||
|
clearTimeout(this.timeoutToForceConnectId)
|
||||||
|
|
||||||
if (this.websocket?.readyState === 1) {
|
if (this.websocket?.readyState === 1) {
|
||||||
this.websocket?.close()
|
this.websocket?.close()
|
||||||
}
|
}
|
||||||
@ -1261,8 +1233,17 @@ class EngineConnection extends EventTarget {
|
|||||||
this.websocket?.readyState === 3
|
this.websocket?.readyState === 3
|
||||||
|
|
||||||
if (closedPc && closedUDC && closedWS) {
|
if (closedPc && closedUDC && closedWS) {
|
||||||
// Do not notify the rest of the program that we have cut off anything.
|
if (!this.idleMode) {
|
||||||
this.state = { type: EngineConnectionStateType.Disconnected }
|
// Do not notify the rest of the program that we have cut off anything.
|
||||||
|
this.state = { type: EngineConnectionStateType.Disconnected }
|
||||||
|
} else {
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
|
value: {
|
||||||
|
type: DisconnectingType.Pause,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
this.triggeredStart = false
|
this.triggeredStart = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1288,23 +1269,32 @@ export interface Subscription<T extends ModelTypes> {
|
|||||||
) => void
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CommandLogType {
|
||||||
|
SendModeling = 'send-modeling',
|
||||||
|
SendScene = 'send-scene',
|
||||||
|
ReceiveReliable = 'receive-reliable',
|
||||||
|
ExecutionDone = 'execution-done',
|
||||||
|
ExportDone = 'export-done',
|
||||||
|
SetDefaultSystemProperties = 'set_default_system_properties',
|
||||||
|
}
|
||||||
|
|
||||||
export type CommandLog =
|
export type CommandLog =
|
||||||
| {
|
| {
|
||||||
type: 'send-modeling'
|
type: CommandLogType.SendModeling
|
||||||
data: EngineCommand
|
data: EngineCommand
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'send-scene'
|
type: CommandLogType.SendScene
|
||||||
data: EngineCommand
|
data: EngineCommand
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'receive-reliable'
|
type: CommandLogType.ReceiveReliable
|
||||||
data: OkWebSocketResponseData
|
data: OkWebSocketResponseData
|
||||||
id: string
|
id: string
|
||||||
cmd_type?: string
|
cmd_type?: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'execution-done'
|
type: CommandLogType.ExecutionDone
|
||||||
data: null
|
data: null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1371,8 +1361,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
height: 1337,
|
height: 1337,
|
||||||
}
|
}
|
||||||
|
|
||||||
elVideo: HTMLVideoElement | null = null
|
|
||||||
|
|
||||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||||
|
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
@ -1525,6 +1513,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this._camControlsCameraChange()
|
this._camControlsCameraChange()
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.sendSceneCommand({
|
this.sendSceneCommand({
|
||||||
// CameraControls subscribes to default_camera_get_settings response events
|
// CameraControls subscribes to default_camera_get_settings response events
|
||||||
@ -1535,6 +1524,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
type: 'default_camera_get_settings',
|
type: 'default_camera_get_settings',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsStreamReady(true)
|
setIsStreamReady(true)
|
||||||
|
|
||||||
// Other parts of the application should use this to react on scene ready.
|
// Other parts of the application should use this to react on scene ready.
|
||||||
@ -1646,7 +1636,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
message.request_id
|
message.request_id
|
||||||
) {
|
) {
|
||||||
this.addCommandLog({
|
this.addCommandLog({
|
||||||
type: 'receive-reliable',
|
type: CommandLogType.ReceiveReliable,
|
||||||
data: message.resp,
|
data: message.resp,
|
||||||
id: message?.request_id || '',
|
id: message?.request_id || '',
|
||||||
cmd_type: pending?.command?.cmd?.type,
|
cmd_type: pending?.command?.cmd?.type,
|
||||||
@ -1680,7 +1670,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
if (!command) return
|
if (!command) return
|
||||||
if (command.type === 'modeling_cmd_req')
|
if (command.type === 'modeling_cmd_req')
|
||||||
this.addCommandLog({
|
this.addCommandLog({
|
||||||
type: 'receive-reliable',
|
type: CommandLogType.ReceiveReliable,
|
||||||
data: {
|
data: {
|
||||||
type: 'modeling',
|
type: 'modeling',
|
||||||
data: {
|
data: {
|
||||||
@ -1722,7 +1712,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.engineConnection?.connect()
|
this.engineConnection?.connect({ reconnect: false })
|
||||||
}
|
}
|
||||||
this.engineConnection.addEventListener(
|
this.engineConnection.addEventListener(
|
||||||
EngineConnectionEvents.ConnectionStarted,
|
EngineConnectionEvents.ConnectionStarted,
|
||||||
@ -1748,7 +1738,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
cmd: {
|
cmd: {
|
||||||
type: 'reconfigure_stream',
|
type: 'reconfigure_stream',
|
||||||
...this.streamDimensions,
|
...this.streamDimensions,
|
||||||
fps: 60,
|
fps: 60, // This is required but it does next to nothing
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.engineConnection?.send(resizeCmd)
|
this.engineConnection?.send(resizeCmd)
|
||||||
@ -1789,7 +1779,10 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
} else if (this.engineCommandManager?.engineConnection) {
|
} else if (this.engineCommandManager?.engineConnection) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.engineCommandManager?.engineConnection?.tearDown(opts)
|
this.engineCommandManager?.engineConnection?.tearDown(opts)
|
||||||
|
// @ts-ignore
|
||||||
|
this.engineCommandManager.engineConnection = null
|
||||||
}
|
}
|
||||||
|
this.engineConnection = undefined
|
||||||
}
|
}
|
||||||
async startNewSession() {
|
async startNewSession() {
|
||||||
this.responseMap = {}
|
this.responseMap = {}
|
||||||
@ -1864,7 +1857,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
) {
|
) {
|
||||||
// highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
|
// highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
|
||||||
this.addCommandLog({
|
this.addCommandLog({
|
||||||
type: 'send-scene',
|
type: CommandLogType.SendScene,
|
||||||
data: command,
|
data: command,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,6 @@ export const KCL_DEFAULT_DEGREE = `360`
|
|||||||
/** The default KCL color expression */
|
/** The default KCL color expression */
|
||||||
export const KCL_DEFAULT_COLOR = `#3c73ff`
|
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 SETTINGS_FILE_NAME = 'settings.toml'
|
||||||
export const TOKEN_FILE_NAME = 'token.txt'
|
export const TOKEN_FILE_NAME = 'token.txt'
|
||||||
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
|
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
|
||||||
|
@ -459,12 +459,13 @@ export const getAppSettingsFilePath = async () => {
|
|||||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||||
'TEST_SETTINGS_FILE_KEY'
|
'TEST_SETTINGS_FILE_KEY'
|
||||||
)
|
)
|
||||||
if (isTestEnv && !testSettingsPath) return SETTINGS_FILE_NAME
|
|
||||||
|
|
||||||
const appConfig = await window.electron.getPath('appData')
|
const appConfig = await window.electron.getPath('appData')
|
||||||
|
|
||||||
const fullPath = isTestEnv
|
const fullPath = isTestEnv
|
||||||
? testSettingsPath
|
? window.electron.path.resolve(testSettingsPath, '..')
|
||||||
: window.electron.path.join(appConfig, getAppFolderName())
|
: window.electron.path.resolve(appConfig, getAppFolderName())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.electron.stat(fullPath)
|
await window.electron.stat(fullPath)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -480,9 +481,10 @@ const getTokenFilePath = async () => {
|
|||||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||||
'TEST_SETTINGS_FILE_KEY'
|
'TEST_SETTINGS_FILE_KEY'
|
||||||
)
|
)
|
||||||
|
|
||||||
const appConfig = await window.electron.getPath('appData')
|
const appConfig = await window.electron.getPath('appData')
|
||||||
const fullPath = isTestEnv
|
const fullPath = isTestEnv
|
||||||
? testSettingsPath
|
? window.electron.path.resolve(testSettingsPath, '..')
|
||||||
: window.electron.path.join(appConfig, getAppFolderName())
|
: window.electron.path.join(appConfig, getAppFolderName())
|
||||||
try {
|
try {
|
||||||
await window.electron.stat(fullPath)
|
await window.electron.stat(fullPath)
|
||||||
@ -496,8 +498,15 @@ const getTokenFilePath = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTelemetryFilePath = 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 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 {
|
try {
|
||||||
await window.electron.stat(fullPath)
|
await window.electron.stat(fullPath)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -510,8 +519,15 @@ const getTelemetryFilePath = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getRawTelemetryFilePath = 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 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 {
|
try {
|
||||||
await window.electron.stat(fullPath)
|
await window.electron.stat(fullPath)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -535,9 +551,17 @@ const getProjectSettingsFilePath = async (projectPath: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getInitialDefaultDir = async () => {
|
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) {
|
if (!window.electron) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
if (isTestEnv) {
|
||||||
|
return testSettingsPath
|
||||||
|
}
|
||||||
const dir = await window.electron.getPath('documents')
|
const dir = await window.electron.getPath('documents')
|
||||||
return window.electron.path.join(dir, PROJECT_FOLDER)
|
return window.electron.path.join(dir, PROJECT_FOLDER)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
isOverlap,
|
isOverlap,
|
||||||
uuidv4,
|
uuidv4,
|
||||||
} from '@src/lib/utils'
|
} from '@src/lib/utils'
|
||||||
|
import { engineStreamActor } from '@src/machines/appMachine'
|
||||||
import type { ModelingMachineEvent } from '@src/machines/modelingMachine'
|
import type { ModelingMachineEvent } from '@src/machines/modelingMachine'
|
||||||
|
|
||||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||||
@ -649,12 +650,13 @@ export async function sendSelectEventToEngine(
|
|||||||
e: React.MouseEvent<HTMLDivElement, MouseEvent>
|
e: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||||
) {
|
) {
|
||||||
// No video stream to normalise against, return immediately
|
// 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')
|
return Promise.reject('video element not ready')
|
||||||
|
|
||||||
const { x, y } = getNormalisedCoordinates(
|
const { x, y } = getNormalisedCoordinates(
|
||||||
e,
|
e,
|
||||||
engineCommandManager.elVideo,
|
engineStreamState.videoRef.current,
|
||||||
engineCommandManager.streamDimensions
|
engineCommandManager.streamDimensions
|
||||||
)
|
)
|
||||||
const res = await engineCommandManager.sendSceneCommand({
|
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 { CameraOrbitType } from '@rust/kcl-lib/bindings/CameraOrbitType'
|
||||||
import type { CameraProjectionType } from '@rust/kcl-lib/bindings/CameraProjectionType'
|
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 type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus'
|
||||||
|
|
||||||
import { CustomIcon } from '@src/components/CustomIcon'
|
import { CustomIcon } from '@src/components/CustomIcon'
|
||||||
|
import { Toggle } from '@src/components/Toggle/Toggle'
|
||||||
import Tooltip from '@src/components/Tooltip'
|
import Tooltip from '@src/components/Tooltip'
|
||||||
import type { CameraSystem } from '@src/lib/cameraControls'
|
import type { CameraSystem } from '@src/lib/cameraControls'
|
||||||
import { cameraMouseDragGuards, cameraSystems } 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() {
|
export function createSettings() {
|
||||||
return {
|
return {
|
||||||
/** Settings that affect the behavior of the entire app,
|
/** Settings that affect the behavior of the entire app,
|
||||||
@ -208,12 +211,109 @@ export function createSettings() {
|
|||||||
/**
|
/**
|
||||||
* Stream resource saving behavior toggle
|
* Stream resource saving behavior toggle
|
||||||
*/
|
*/
|
||||||
streamIdleMode: new Setting<boolean>({
|
streamIdleMode: new Setting<number | undefined>({
|
||||||
defaultValue: false,
|
defaultValue: undefined,
|
||||||
description: 'Toggle stream idling, saving bandwidth and battery',
|
hideOnLevel: 'project',
|
||||||
validate: (v) => typeof v === 'boolean',
|
description: 'Save bandwidth & battery',
|
||||||
commandConfig: {
|
validate: (v) =>
|
||||||
inputType: 'boolean',
|
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>({
|
allowOrbitInSketchMode: new Setting<boolean>({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
|
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 { createSettings } from '@src/lib/settings/initialSettings'
|
||||||
import {
|
import {
|
||||||
@ -43,11 +44,10 @@ describe(`testing settings initialization`, () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const projectConfiguration: DeepPartial<Configuration> = {
|
const projectConfiguration: DeepPartial<ProjectConfiguration> = {
|
||||||
settings: {
|
settings: {
|
||||||
app: {
|
app: {
|
||||||
appearance: {
|
appearance: {
|
||||||
theme: 'light',
|
|
||||||
color: 200,
|
color: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -82,11 +82,10 @@ describe(`testing getAllCurrentSettings`, () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const projectConfiguration: DeepPartial<Configuration> = {
|
const projectConfiguration: DeepPartial<ProjectConfiguration> = {
|
||||||
settings: {
|
settings: {
|
||||||
app: {
|
app: {
|
||||||
appearance: {
|
appearance: {
|
||||||
theme: 'light',
|
|
||||||
color: 200,
|
color: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,10 @@ import { appThemeToTheme } from '@src/lib/theme'
|
|||||||
import { err } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
import type { DeepPartial } from '@src/lib/types'
|
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.
|
* Convert from a rust settings struct into the JS settings struct.
|
||||||
* We do this because the JS settings type has all the fancy shit
|
* We do this because the JS settings type has all the fancy shit
|
||||||
@ -49,7 +53,9 @@ export 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,
|
streamIdleMode: toUndefinedIfNull(
|
||||||
|
configuration?.settings?.app?.stream_idle_mode
|
||||||
|
),
|
||||||
allowOrbitInSketchMode:
|
allowOrbitInSketchMode:
|
||||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||||
projectDirectory: configuration?.settings?.project?.directory,
|
projectDirectory: configuration?.settings?.project?.directory,
|
||||||
@ -128,7 +134,6 @@ export 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,
|
|
||||||
allowOrbitInSketchMode:
|
allowOrbitInSketchMode:
|
||||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||||
namedViews: deepPartialNamedViewsToNamedViews(
|
namedViews: deepPartialNamedViewsToNamedViews(
|
||||||
|
@ -10,8 +10,8 @@ import { SceneInfra } from '@src/clientSideScene/sceneInfra'
|
|||||||
import type { BaseUnit } from '@src/lib/settings/settingsTypes'
|
import type { BaseUnit } from '@src/lib/settings/settingsTypes'
|
||||||
|
|
||||||
export const codeManager = new CodeManager()
|
export const codeManager = new CodeManager()
|
||||||
|
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
export const rustContext = new RustContext(engineCommandManager)
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -23,21 +23,32 @@ declare global {
|
|||||||
// Accessible for tests mostly
|
// Accessible for tests mostly
|
||||||
window.engineCommandManager = engineCommandManager
|
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)
|
export const sceneInfra = new SceneInfra(engineCommandManager)
|
||||||
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
|
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) => {
|
kclManager.sceneInfraBaseUnitMultiplierSetter = (unit: BaseUnit) => {
|
||||||
sceneInfra.baseUnit = unit
|
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(
|
export const sceneEntitiesManager = new SceneEntities(
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
sceneInfra,
|
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
|
@ -3,13 +3,19 @@ import { createActor, setup, spawnChild } from 'xstate'
|
|||||||
|
|
||||||
import { createSettings } from '@src/lib/settings/initialSettings'
|
import { createSettings } from '@src/lib/settings/initialSettings'
|
||||||
import { authMachine } from '@src/machines/authMachine'
|
import { authMachine } from '@src/machines/authMachine'
|
||||||
|
import type { EngineStreamActor } from '@src/machines/engineStreamMachine'
|
||||||
|
import {
|
||||||
|
engineStreamContextCreate,
|
||||||
|
engineStreamMachine,
|
||||||
|
} from '@src/machines/engineStreamMachine'
|
||||||
import { ACTOR_IDS } from '@src/machines/machineConstants'
|
import { ACTOR_IDS } from '@src/machines/machineConstants'
|
||||||
import { settingsMachine } from '@src/machines/settingsMachine'
|
import { settingsMachine } from '@src/machines/settingsMachine'
|
||||||
|
|
||||||
const { AUTH, SETTINGS } = ACTOR_IDS
|
const { AUTH, SETTINGS, ENGINE_STREAM } = ACTOR_IDS
|
||||||
const appMachineActors = {
|
const appMachineActors = {
|
||||||
[AUTH]: authMachine,
|
[AUTH]: authMachine,
|
||||||
[SETTINGS]: settingsMachine,
|
[SETTINGS]: settingsMachine,
|
||||||
|
[ENGINE_STREAM]: engineStreamMachine,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const appMachine = setup({
|
const appMachine = setup({
|
||||||
@ -29,6 +35,11 @@ const appMachine = setup({
|
|||||||
systemId: SETTINGS,
|
systemId: SETTINGS,
|
||||||
input: createSettings(),
|
input: createSettings(),
|
||||||
}),
|
}),
|
||||||
|
spawnChild(ENGINE_STREAM, {
|
||||||
|
id: ENGINE_STREAM,
|
||||||
|
systemId: ENGINE_STREAM,
|
||||||
|
input: engineStreamContextCreate(),
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -61,3 +72,7 @@ export const useSettings = () =>
|
|||||||
const { currentProject, ...settings } = state.context
|
const { currentProject, ...settings } = state.context
|
||||||
return settings
|
return settings
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const engineStreamActor = appActor.system.get(
|
||||||
|
ENGINE_STREAM
|
||||||
|
) as EngineStreamActor
|
||||||
|
320
src/machines/engineStreamMachine.ts
Normal file
320
src/machines/engineStreamMachine.ts
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
|
||||||
|
import {
|
||||||
|
codeManager,
|
||||||
|
engineCommandManager,
|
||||||
|
rustContext,
|
||||||
|
sceneInfra,
|
||||||
|
} from '@src/lib/singletons'
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { ActorRefFrom } from 'xstate'
|
||||||
|
import { assign, fromPromise, setup } from 'xstate'
|
||||||
|
|
||||||
|
export enum EngineStreamState {
|
||||||
|
Off = 'off',
|
||||||
|
On = 'on',
|
||||||
|
WaitForMediaStream = 'wait-for-media-stream',
|
||||||
|
Playing = 'playing',
|
||||||
|
Reconfiguring = 'reconfiguring',
|
||||||
|
Paused = 'paused',
|
||||||
|
// The is the state in-between Paused and Playing *specifically that order*.
|
||||||
|
Resuming = 'resuming',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EngineStreamTransition {
|
||||||
|
SetMediaStream = 'set-media-stream',
|
||||||
|
SetPool = 'set-pool',
|
||||||
|
SetAuthToken = 'set-auth-token',
|
||||||
|
Play = 'play',
|
||||||
|
Resume = 'resume',
|
||||||
|
Pause = 'pause',
|
||||||
|
StartOrReconfigureEngine = 'start-or-reconfigure-engine',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EngineStreamContext {
|
||||||
|
pool: string | null
|
||||||
|
authToken: string | undefined
|
||||||
|
mediaStream: MediaStream | null
|
||||||
|
videoRef: MutableRefObject<HTMLVideoElement | null>
|
||||||
|
canvasRef: MutableRefObject<HTMLCanvasElement | null>
|
||||||
|
zoomToFit: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const engineStreamContextCreate = (): EngineStreamContext => ({
|
||||||
|
pool: null,
|
||||||
|
authToken: undefined,
|
||||||
|
mediaStream: null,
|
||||||
|
videoRef: { current: null },
|
||||||
|
canvasRef: { current: null },
|
||||||
|
zoomToFit: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function getDimensions(streamWidth: number, streamHeight: number) {
|
||||||
|
const factorOf = 4
|
||||||
|
const maxResolution = 2160
|
||||||
|
const ratio = Math.min(
|
||||||
|
Math.min(maxResolution / streamWidth, maxResolution / streamHeight),
|
||||||
|
1.0
|
||||||
|
)
|
||||||
|
const quadWidth = Math.round((streamWidth * ratio) / factorOf) * factorOf
|
||||||
|
const quadHeight = Math.round((streamHeight * ratio) / factorOf) * factorOf
|
||||||
|
return { width: quadWidth, height: quadHeight }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function holdOntoVideoFrameInCanvas(
|
||||||
|
video: HTMLVideoElement,
|
||||||
|
canvas: HTMLCanvasElement
|
||||||
|
) {
|
||||||
|
await video.pause()
|
||||||
|
canvas.width = video.videoWidth
|
||||||
|
canvas.height = video.videoHeight
|
||||||
|
canvas.style.width = video.videoWidth + 'px'
|
||||||
|
canvas.style.height = video.videoHeight + 'px'
|
||||||
|
canvas.style.display = 'block'
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
if (!ctx) return
|
||||||
|
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const engineStreamMachine = setup({
|
||||||
|
types: {
|
||||||
|
context: {} as EngineStreamContext,
|
||||||
|
input: {} as EngineStreamContext,
|
||||||
|
},
|
||||||
|
actors: {
|
||||||
|
[EngineStreamTransition.Play]: fromPromise(
|
||||||
|
async ({
|
||||||
|
input: { context, params },
|
||||||
|
}: {
|
||||||
|
input: { context: EngineStreamContext; params: { zoomToFit: boolean } }
|
||||||
|
}) => {
|
||||||
|
const canvas = context.canvasRef.current
|
||||||
|
if (!canvas) return false
|
||||||
|
|
||||||
|
const video = context.videoRef.current
|
||||||
|
if (!video) return false
|
||||||
|
|
||||||
|
const mediaStream = context.mediaStream
|
||||||
|
if (!mediaStream) return false
|
||||||
|
|
||||||
|
// If the video is already playing it means we're doing a reconfigure.
|
||||||
|
// We don't want to re-run the KCL or touch the video element at all.
|
||||||
|
if (!video.paused) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await sceneInfra.camControls.restoreRemoteCameraStateAndTriggerSync()
|
||||||
|
|
||||||
|
video.style.display = 'block'
|
||||||
|
canvas.style.display = 'none'
|
||||||
|
|
||||||
|
video.srcObject = mediaStream
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[EngineStreamTransition.Pause]: fromPromise(
|
||||||
|
async ({
|
||||||
|
input: { context },
|
||||||
|
}: {
|
||||||
|
input: { context: EngineStreamContext }
|
||||||
|
}) => {
|
||||||
|
const video = context.videoRef.current
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
await video.pause()
|
||||||
|
|
||||||
|
const canvas = context.canvasRef.current
|
||||||
|
if (!canvas) return
|
||||||
|
|
||||||
|
await holdOntoVideoFrameInCanvas(video, canvas)
|
||||||
|
video.style.display = 'none'
|
||||||
|
|
||||||
|
// Before doing anything else clear the cache
|
||||||
|
// Originally I (lee) had this on the reconnect but it was interfering
|
||||||
|
// with kclManager.executeCode()?
|
||||||
|
await rustContext.clearSceneAndBustCache(
|
||||||
|
{ settings: await jsAppSettings() },
|
||||||
|
codeManager.currentFilePath || undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
await sceneInfra.camControls.saveRemoteCameraState()
|
||||||
|
|
||||||
|
// Make sure we're on the next frame for no flickering between canvas
|
||||||
|
// and the video elements.
|
||||||
|
window.requestAnimationFrame(
|
||||||
|
() =>
|
||||||
|
void (async () => {
|
||||||
|
// Destroy the media stream. We will re-establish it. We could
|
||||||
|
// leave everything at pausing, preventing video decoders from running
|
||||||
|
// but we can do even better by significantly reducing network
|
||||||
|
// cards also.
|
||||||
|
context.mediaStream?.getVideoTracks()[0].stop()
|
||||||
|
context.mediaStream = null
|
||||||
|
video.srcObject = null
|
||||||
|
|
||||||
|
engineCommandManager.tearDown({ idleMode: true })
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[EngineStreamTransition.StartOrReconfigureEngine]: fromPromise(
|
||||||
|
async ({
|
||||||
|
input: { context, event },
|
||||||
|
}: {
|
||||||
|
input: { context: EngineStreamContext; event: any }
|
||||||
|
}) => {
|
||||||
|
if (!context.authToken) return
|
||||||
|
|
||||||
|
const video = context.videoRef.current
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
const canvas = context.canvasRef.current
|
||||||
|
if (!canvas) return
|
||||||
|
|
||||||
|
const { width, height } = getDimensions(
|
||||||
|
window.innerWidth,
|
||||||
|
window.innerHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
video.width = width
|
||||||
|
video.height = height
|
||||||
|
|
||||||
|
const settingsNext = {
|
||||||
|
// override the pool param (?pool=) to request a specific engine instance
|
||||||
|
// from a particular pool.
|
||||||
|
pool: context.pool,
|
||||||
|
...event.settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
engineCommandManager.settings = settingsNext
|
||||||
|
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
engineCommandManager.start({
|
||||||
|
setMediaStream: event.onMediaStream,
|
||||||
|
setIsStreamReady: (isStreamReady: boolean) => {
|
||||||
|
event.setAppState({ isStreamReady })
|
||||||
|
},
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
token: context.authToken,
|
||||||
|
settings: settingsNext,
|
||||||
|
})
|
||||||
|
|
||||||
|
event.modelingMachineActorSend({
|
||||||
|
type: 'Set context',
|
||||||
|
data: {
|
||||||
|
streamDimensions: {
|
||||||
|
streamWidth: width,
|
||||||
|
streamHeight: height,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}).createMachine({
|
||||||
|
initial: EngineStreamState.Off,
|
||||||
|
context: (initial) => initial.input,
|
||||||
|
states: {
|
||||||
|
[EngineStreamState.Off]: {
|
||||||
|
reenter: true,
|
||||||
|
on: {
|
||||||
|
[EngineStreamTransition.SetPool]: {
|
||||||
|
target: EngineStreamState.Off,
|
||||||
|
actions: [assign({ pool: ({ context, event }) => event.data.pool })],
|
||||||
|
},
|
||||||
|
[EngineStreamTransition.SetAuthToken]: {
|
||||||
|
target: EngineStreamState.Off,
|
||||||
|
actions: [
|
||||||
|
assign({ authToken: ({ context, event }) => event.data.authToken }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[EngineStreamTransition.StartOrReconfigureEngine]: {
|
||||||
|
target: EngineStreamState.On,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[EngineStreamState.On]: {
|
||||||
|
reenter: true,
|
||||||
|
invoke: {
|
||||||
|
src: EngineStreamTransition.StartOrReconfigureEngine,
|
||||||
|
input: (args) => args,
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
// Transition requested by engineConnection
|
||||||
|
[EngineStreamTransition.SetMediaStream]: {
|
||||||
|
target: EngineStreamState.On,
|
||||||
|
actions: [
|
||||||
|
assign({ mediaStream: ({ context, event }) => event.mediaStream }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[EngineStreamTransition.Play]: {
|
||||||
|
target: EngineStreamState.Playing,
|
||||||
|
actions: [assign({ zoomToFit: () => true })],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[EngineStreamState.Playing]: {
|
||||||
|
invoke: {
|
||||||
|
src: EngineStreamTransition.Play,
|
||||||
|
input: (args) => ({
|
||||||
|
context: args.context,
|
||||||
|
params: { zoomToFit: args.context.zoomToFit },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
[EngineStreamTransition.StartOrReconfigureEngine]: {
|
||||||
|
target: EngineStreamState.Reconfiguring,
|
||||||
|
},
|
||||||
|
[EngineStreamTransition.Pause]: {
|
||||||
|
target: EngineStreamState.Paused,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[EngineStreamState.Reconfiguring]: {
|
||||||
|
invoke: {
|
||||||
|
src: EngineStreamTransition.StartOrReconfigureEngine,
|
||||||
|
input: (args) => args,
|
||||||
|
onDone: {
|
||||||
|
target: EngineStreamState.Playing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[EngineStreamState.Paused]: {
|
||||||
|
invoke: {
|
||||||
|
src: EngineStreamTransition.Pause,
|
||||||
|
input: (args) => args,
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
[EngineStreamTransition.StartOrReconfigureEngine]: {
|
||||||
|
target: EngineStreamState.Resuming,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[EngineStreamState.Resuming]: {
|
||||||
|
reenter: true,
|
||||||
|
invoke: {
|
||||||
|
src: EngineStreamTransition.StartOrReconfigureEngine,
|
||||||
|
input: (args) => args,
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
// The stream can be paused as it's resuming.
|
||||||
|
[EngineStreamTransition.Pause]: {
|
||||||
|
target: EngineStreamState.Paused,
|
||||||
|
},
|
||||||
|
[EngineStreamTransition.SetMediaStream]: {
|
||||||
|
actions: [
|
||||||
|
assign({ mediaStream: ({ context, event }) => event.mediaStream }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[EngineStreamTransition.Play]: {
|
||||||
|
target: EngineStreamState.Playing,
|
||||||
|
actions: [assign({ zoomToFit: () => false })],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type EngineStreamActor = ActorRefFrom<typeof engineStreamMachine>
|
@ -1,4 +1,5 @@
|
|||||||
export const ACTOR_IDS = {
|
export const ACTOR_IDS = {
|
||||||
AUTH: 'auth',
|
AUTH: 'auth',
|
||||||
SETTINGS: 'settings',
|
SETTINGS: 'settings',
|
||||||
|
ENGINE_STREAM: 'engine_stream',
|
||||||
} as const
|
} as const
|
||||||
|
@ -103,6 +103,7 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
|
|||||||
|
|
||||||
if (reuse) {
|
if (reuse) {
|
||||||
newWindow = mainWindow
|
newWindow = mainWindow
|
||||||
|
Menu.setApplicationMenu(null)
|
||||||
}
|
}
|
||||||
if (!newWindow) {
|
if (!newWindow) {
|
||||||
const primaryDisplay = screen.getPrimaryDisplay()
|
const primaryDisplay = screen.getPrimaryDisplay()
|
||||||
|
@ -278,6 +278,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
'VITE_KC_SKIP_AUTH',
|
'VITE_KC_SKIP_AUTH',
|
||||||
'VITE_KC_CONNECTION_TIMEOUT_MS',
|
'VITE_KC_CONNECTION_TIMEOUT_MS',
|
||||||
'VITE_KC_DEV_TOKEN',
|
'VITE_KC_DEV_TOKEN',
|
||||||
|
|
||||||
'IS_PLAYWRIGHT',
|
'IS_PLAYWRIGHT',
|
||||||
|
|
||||||
// Really we shouldn't use these and our code should use NODE_ENV
|
// Really we shouldn't use these and our code should use NODE_ENV
|
||||||
|
Reference in New Issue
Block a user