Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
f441998f1a | |||
531496420e | |||
13bb482904 | |||
562959ee22 | |||
b044f6faef | |||
d916c79874 | |||
4fd5e26abe | |||
8f9bef922f | |||
545e610bbc | |||
55a3e2a4ed | |||
591f17b182 | |||
a7a88bd762 | |||
0916f990cb | |||
75ae4b4a4a | |||
4a490d5900 | |||
4d9cdc6b40 | |||
0d3880233c | |||
8a029605bd | |||
f26adee360 | |||
0f2a01b6c8 |
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- tauri
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
schedule:
|
schedule:
|
||||||
@ -123,7 +123,7 @@ jobs:
|
|||||||
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
|
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
|
||||||
git push
|
git push
|
||||||
git push origin ${{ github.head_ref }}
|
git push origin ${{ github.head_ref }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -412,17 +412,6 @@ jobs:
|
|||||||
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
||||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
|
||||||
- name: Run e2e tests (windows only)
|
|
||||||
if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
|
||||||
run: |
|
|
||||||
cargo install tauri-driver --force
|
|
||||||
yarn wdio run wdio.conf.ts
|
|
||||||
env:
|
|
||||||
E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\Zoo Modeling App.exe"
|
|
||||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
|
||||||
VITE_KC_API_BASE_URL: ${{ env.BUILD_RELEASE == 'true' && 'https://api.zoo.dev' || 'https://api.dev.zoo.dev' }}
|
|
||||||
E2E_TAURI_ENABLED: true
|
|
||||||
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}'
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||||
|
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
@ -3,7 +3,7 @@ name: Create Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- tauri
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
|
@ -117,6 +117,7 @@ Which commands from setup are one off vs need to be run every time?
|
|||||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
|
yarn wasm-prep
|
||||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||||
```
|
```
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,6 +3,10 @@ import { test, expect, Page } from '@playwright/test'
|
|||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import {
|
||||||
|
PLAYWRIGHT_MOCK_EXPORT_DURATION,
|
||||||
|
PLAYWRIGHT_TOAST_DURATION,
|
||||||
|
} from 'lib/constants'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
await setup(context, page)
|
await setup(context, page)
|
||||||
@ -250,7 +254,7 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
await expect(exportButton).toBeVisible()
|
await expect(exportButton).toBeVisible()
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
exportButton.click()
|
await exportButton.click()
|
||||||
|
|
||||||
// Click the stl.
|
// Click the stl.
|
||||||
const stlOption = page.getByText('glTF')
|
const stlOption = page.getByText('glTF')
|
||||||
@ -279,7 +283,7 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
await expect(exportingToastMessage).not.toBeVisible()
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
// Click the code editor
|
// Click the code editor
|
||||||
page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
await page.waitForTimeout(2000)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
@ -288,8 +292,7 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
// Now add in code that works.
|
// Now add in code that works.
|
||||||
page.locator('.cm-content').fill(bracket)
|
await page.locator('.cm-content').fill(bracket)
|
||||||
page.locator('.cm-content').click()
|
|
||||||
await page.keyboard.press('End')
|
await page.keyboard.press('End')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
@ -302,7 +305,7 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
// Now try exporting
|
// Now try exporting
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
exportButton.click()
|
await exportButton.click()
|
||||||
|
|
||||||
// Click the stl.
|
// Click the stl.
|
||||||
await expect(stlOption).toBeVisible()
|
await expect(stlOption).toBeVisible()
|
||||||
@ -330,84 +333,108 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async (code) => {
|
await test.step('Set up the code and durations', async () => {
|
||||||
localStorage.setItem('persistCode', code)
|
await page.addInitScript(
|
||||||
}, bracket)
|
async ({ code, toastDurationKey, exportDurationKey }) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
// Normally we make these durations short to speed up PW tests
|
||||||
|
// to superhuman speeds. But in this case we want to make sure
|
||||||
|
// the export toast is visible for a while, and the export
|
||||||
|
// duration is long enough to make sure the export toast is visible
|
||||||
|
localStorage.setItem(toastDurationKey, '1500')
|
||||||
|
localStorage.setItem(exportDurationKey, '750')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: bracket,
|
||||||
|
toastDurationKey: PLAYWRIGHT_TOAST_DURATION,
|
||||||
|
exportDurationKey: PLAYWRIGHT_MOCK_EXPORT_DURATION,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
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()
|
||||||
|
|
||||||
// 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()
|
||||||
|
})
|
||||||
|
|
||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||||
|
|
||||||
await clickExportButton(page)
|
|
||||||
|
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
|
||||||
|
|
||||||
await clickExportButton(page)
|
|
||||||
|
|
||||||
// Find the toast.
|
|
||||||
// Look out for the toast message
|
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
|
||||||
await expect(alreadyExportingToastMessage).toBeVisible()
|
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
|
|
||||||
// Expect it to succeed.
|
|
||||||
await expect(exportingToastMessage).not.toBeVisible()
|
|
||||||
await expect(errorToastMessage).not.toBeVisible()
|
|
||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Exported successfully`)
|
const successToastMessage = page.getByText(`Exported successfully`)
|
||||||
await expect(successToastMessage).toBeVisible()
|
|
||||||
|
|
||||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
await test.step('Blocked second export', async () => {
|
||||||
|
await clickExportButton(page)
|
||||||
|
|
||||||
// Try exporting again.
|
await expect(exportingToastMessage).toBeVisible()
|
||||||
await clickExportButton(page)
|
|
||||||
|
|
||||||
// Find the toast.
|
await clickExportButton(page)
|
||||||
// Look out for the toast message
|
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
|
||||||
|
|
||||||
// Expect it to succeed.
|
await test.step('The second export is blocked', async () => {
|
||||||
await expect(exportingToastMessage).not.toBeVisible()
|
// Find the toast.
|
||||||
await expect(errorToastMessage).not.toBeVisible()
|
// Look out for the toast message
|
||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
await expect(exportingToastMessage).toBeVisible()
|
||||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
await expect(alreadyExportingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await expect(successToastMessage).toBeVisible()
|
await page.waitForTimeout(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('The first export still succeeds', async () => {
|
||||||
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
await expect(errorToastMessage).not.toBeVisible()
|
||||||
|
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(successToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Successful, unblocked export', async () => {
|
||||||
|
// Try exporting again.
|
||||||
|
await clickExportButton(page)
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
await expect(exportingToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
// Expect it to succeed.
|
||||||
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
await expect(errorToastMessage).not.toBeVisible()
|
||||||
|
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||||
|
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(successToastMessage).toBeVisible()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function clickExportButton(page: Page) {
|
async function clickExportButton(page: Page) {
|
||||||
// export the model
|
await test.step('Running export flow', async () => {
|
||||||
const exportButton = page.getByTestId('export-pane-button')
|
// export the model
|
||||||
await expect(exportButton).toBeVisible()
|
const exportButton = page.getByTestId('export-pane-button')
|
||||||
|
await expect(exportButton).toBeEnabled()
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
exportButton.click()
|
await exportButton.click()
|
||||||
|
|
||||||
// Click the stl.
|
// Click the stl.
|
||||||
const gltfOption = page.getByText('glTF')
|
const gltfOption = page.getByRole('option', { name: 'glTF' })
|
||||||
await expect(gltfOption).toBeVisible()
|
await expect(gltfOption).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
const submitButton = page.getByText('Confirm Export')
|
const submitButton = page.getByText('Confirm Export')
|
||||||
await expect(submitButton).toBeVisible()
|
await expect(submitButton).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -29,15 +29,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
)
|
)
|
||||||
await expect(submittingToastMessage).toBeVisible()
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await page.waitForTimeout(5000)
|
|
||||||
|
|
||||||
const generatingToastMessage = page.getByText(
|
const generatingToastMessage = page.getByText(
|
||||||
`Generating parametric model...`
|
`Generating parametric model...`
|
||||||
)
|
)
|
||||||
await expect(generatingToastMessage).toBeVisible()
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||||
|
|
||||||
@ -78,6 +76,52 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(successToastMessage).not.toBeVisible()
|
await expect(successToastMessage).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x6 lego')
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
const submittingToastMessage = page.getByText(
|
||||||
|
`Submitting to Text-to-CAD API...`
|
||||||
|
)
|
||||||
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
const generatingToastMessage = page.getByText(
|
||||||
|
`Generating parametric model...`
|
||||||
|
)
|
||||||
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
|
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(successToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
// Can send a new prompt from the command bar.
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
|
// Expect 2 success toasts.
|
||||||
|
await expect(successToastMessage).toHaveCount(2, {
|
||||||
|
timeout: 15000,
|
||||||
|
})
|
||||||
|
await expect(page.getByText('a 2x4 lego')).toBeVisible()
|
||||||
|
await expect(page.getByText('a 2x6 lego')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('you can reject text-to-cad output and it does nothing', async ({
|
test('you can reject text-to-cad output and it does nothing', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -96,15 +140,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
)
|
)
|
||||||
await expect(submittingToastMessage).toBeVisible()
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await page.waitForTimeout(5000)
|
|
||||||
|
|
||||||
const generatingToastMessage = page.getByText(
|
const generatingToastMessage = page.getByText(
|
||||||
`Generating parametric model...`
|
`Generating parametric model...`
|
||||||
)
|
)
|
||||||
await expect(generatingToastMessage).toBeVisible()
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
// Hit copy to clipboard.
|
// Hit copy to clipboard.
|
||||||
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||||
@ -184,7 +226,9 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(failureToastMessage).not.toBeVisible()
|
await expect(failureToastMessage).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('sending a bad prompt fails, can start over', async ({ page }) => {
|
test('sending a bad prompt fails, can start over from toast', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
@ -266,11 +310,86 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
await expect(submittingToastMessage).toBeVisible()
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await page.waitForTimeout(5000)
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
|
await expect(commandBarButton).toBeVisible()
|
||||||
|
// Click the command bar button
|
||||||
|
await commandBarButton.click()
|
||||||
|
|
||||||
|
// Wait for the command bar to appear
|
||||||
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
|
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||||
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
|
// Click the Text-to-CAD command
|
||||||
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
|
// Enter the prompt.
|
||||||
|
const prompt = page.getByText('Prompt')
|
||||||
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
|
const badPrompt =
|
||||||
|
'akjsndladf lajbhflauweyfa;wieufjn;wieJNUF;.wjdfn weh Fwhefb'
|
||||||
|
|
||||||
|
// Type the prompt.
|
||||||
|
await page.keyboard.type(badPrompt)
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
const submittingToastMessage = page.getByText(
|
||||||
|
`Submitting to Text-to-CAD API...`
|
||||||
|
)
|
||||||
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
const generatingToastMessage = page.getByText(
|
||||||
|
`Generating parametric model...`
|
||||||
|
)
|
||||||
await expect(generatingToastMessage).toBeVisible()
|
await expect(generatingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await expect(successToastMessage).toBeVisible()
|
const failureToastMessage = page.getByText(
|
||||||
|
`The prompt must clearly describe a CAD model`
|
||||||
|
)
|
||||||
|
await expect(failureToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
// Make sure the toast did not say it was successful.
|
||||||
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
|
await expect(successToastMessage).not.toBeVisible()
|
||||||
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
|
|
||||||
|
// They should be able to try again from the command bar.
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
|
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||||
|
|
||||||
|
// old failure toast should stick around.
|
||||||
|
await expect(failureToastMessage).toBeVisible()
|
||||||
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ensure you can shift+enter in the prompt box', async ({ page }) => {
|
test('ensure you can shift+enter in the prompt box', async ({ page }) => {
|
||||||
@ -317,18 +436,210 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
)
|
)
|
||||||
await expect(submittingToastMessage).toBeVisible()
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
const generatingToastMessage = page.getByText(
|
||||||
|
`Generating parametric model...`
|
||||||
|
)
|
||||||
|
await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
|
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can do many at once and get many prompts back, and interact with many', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x8 lego')
|
||||||
|
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x10 lego')
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
const submittingToastMessage = page.getByText(
|
||||||
|
`Submitting to Text-to-CAD API...`
|
||||||
|
)
|
||||||
|
await expect(submittingToastMessage.first()).toBeVisible()
|
||||||
|
|
||||||
const generatingToastMessage = page.getByText(
|
const generatingToastMessage = page.getByText(
|
||||||
`Generating parametric model...`
|
`Generating parametric model...`
|
||||||
)
|
)
|
||||||
await expect(generatingToastMessage).toBeVisible()
|
await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 })
|
||||||
await page.waitForTimeout(5000)
|
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
await expect(successToastMessage).toBeVisible()
|
// We should have three success toasts.
|
||||||
|
await expect(successToastMessage).toHaveCount(3, { timeout: 15000 })
|
||||||
|
|
||||||
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
||||||
|
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
|
||||||
|
|
||||||
|
// Ensure if you reject one, the others stay.
|
||||||
|
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||||
|
await expect(rejectButton.first()).toBeVisible()
|
||||||
|
// Click the reject button on the first toast.
|
||||||
|
await rejectButton.first().click()
|
||||||
|
|
||||||
|
// The first toast should disappear, but not the others.
|
||||||
|
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||||
|
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
||||||
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
|
||||||
|
// Ensure you can copy the code for one of the models remaining.
|
||||||
|
const copyToClipboardButton = page.getByRole('button', {
|
||||||
|
name: 'Copy to clipboard',
|
||||||
|
})
|
||||||
|
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||||
|
// Click the button.
|
||||||
|
await copyToClipboardButton.first().click()
|
||||||
|
|
||||||
|
// Expect the code to be copied.
|
||||||
|
await expect(page.getByText('Copied')).toBeVisible()
|
||||||
|
|
||||||
|
// Click in the code editor.
|
||||||
|
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||||
|
|
||||||
|
// Paste the code.
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('KeyV')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
// Expect the code to be pasted.
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
||||||
|
|
||||||
|
// Find the toast close button.
|
||||||
|
const closeButton = page.getByRole('button', { name: 'Close' })
|
||||||
|
await expect(closeButton).toBeVisible()
|
||||||
|
await closeButton.click()
|
||||||
|
|
||||||
|
// Ensure the final toast remains.
|
||||||
|
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||||
|
await expect(page.getByText(`a 2x8 lego`)).not.toBeVisible()
|
||||||
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
|
||||||
|
// Ensure you can copy the code for the final model.
|
||||||
|
await expect(copyToClipboardButton).toBeVisible()
|
||||||
|
// Click the button.
|
||||||
|
await copyToClipboardButton.click()
|
||||||
|
|
||||||
|
// Expect the code to be copied.
|
||||||
|
await expect(page.getByText('Copied')).toBeVisible()
|
||||||
|
|
||||||
|
// Click in the code editor.
|
||||||
|
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||||
|
|
||||||
|
// Paste the code.
|
||||||
|
await page.keyboard.press('ControlOrMeta+a')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.press('ControlOrMeta+v')
|
||||||
|
|
||||||
|
// Expect the code to be pasted.
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||||
|
|
||||||
|
// Expect the toast to disappear.
|
||||||
|
// Find the toast close button.
|
||||||
|
await expect(closeButton).toBeVisible()
|
||||||
|
await closeButton.click()
|
||||||
|
await expect(successToastMessage).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
|
await sendPromptFromCommandBar(
|
||||||
|
page,
|
||||||
|
'alkjsdnlajshdbfjlhsbdf a;skjdnf;askjdnf'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the toast.
|
||||||
|
// Look out for the toast message
|
||||||
|
const submittingToastMessage = page.getByText(
|
||||||
|
`Submitting to Text-to-CAD API...`
|
||||||
|
)
|
||||||
|
await expect(submittingToastMessage.first()).toBeVisible()
|
||||||
|
|
||||||
|
const generatingToastMessage = page.getByText(
|
||||||
|
`Generating parametric model...`
|
||||||
|
)
|
||||||
|
await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 })
|
||||||
|
|
||||||
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
|
// We should have three success toasts.
|
||||||
|
await expect(successToastMessage).toHaveCount(1, { timeout: 15000 })
|
||||||
|
|
||||||
|
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||||
|
|
||||||
|
const failureToastMessage = page.getByText(
|
||||||
|
`The prompt must clearly describe a CAD model`
|
||||||
|
)
|
||||||
|
await expect(failureToastMessage).toBeVisible()
|
||||||
|
|
||||||
|
// Make sure the toast did not say it was successful.
|
||||||
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
|
||||||
|
// Ensure if you dismiss the error the others stay.
|
||||||
|
const dismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||||
|
await expect(dismissButton).toBeVisible()
|
||||||
|
// Click the dismiss button on the first toast.
|
||||||
|
await dismissButton.first().click()
|
||||||
|
|
||||||
|
// Make sure the failure toast disappears.
|
||||||
|
await expect(failureToastMessage).not.toBeVisible()
|
||||||
|
await expect(page.getByText(`Text-to-CAD failed`)).not.toBeVisible()
|
||||||
|
|
||||||
|
// The first toast should disappear, but not the others.
|
||||||
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
|
||||||
|
// Ensure you can copy the code for one of the models remaining.
|
||||||
|
const copyToClipboardButton = page.getByRole('button', {
|
||||||
|
name: 'Copy to clipboard',
|
||||||
|
})
|
||||||
|
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||||
|
// Click the button.
|
||||||
|
await copyToClipboardButton.first().click()
|
||||||
|
|
||||||
|
// Expect the code to be copied.
|
||||||
|
await expect(page.getByText('Copied')).toBeVisible()
|
||||||
|
|
||||||
|
// Click in the code editor.
|
||||||
|
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||||
|
|
||||||
|
// Paste the code.
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('KeyV')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
// Expect the code to be pasted.
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||||
|
|
||||||
|
// Find the toast close button.
|
||||||
|
const closeButton = page.getByRole('button', { name: 'Close' })
|
||||||
|
await expect(closeButton).toBeVisible()
|
||||||
|
await closeButton.click()
|
||||||
|
|
||||||
|
// Expect the toast to disappear.
|
||||||
|
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||||
|
await expect(successToastMessage).not.toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -342,7 +653,7 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API ')
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
@ -264,6 +264,8 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// This test can run long if it takes a little too long to load
|
// This test can run long if it takes a little too long to load
|
||||||
// the engine.
|
// the engine.
|
||||||
test.setTimeout(90000)
|
test.setTimeout(90000)
|
||||||
@ -273,131 +275,162 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
|||||||
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
|
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
|
||||||
)
|
)
|
||||||
// Load the app with the code pane open
|
// Load the app with the code pane open
|
||||||
await page.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
await test.step(`Set up test`, async () => {
|
||||||
'store',
|
await page.addInitScript(async () => {
|
||||||
JSON.stringify({
|
localStorage.setItem(
|
||||||
state: {
|
'store',
|
||||||
openPanes: ['code'],
|
JSON.stringify({
|
||||||
},
|
state: {
|
||||||
version: 0,
|
openPanes: ['code'],
|
||||||
})
|
},
|
||||||
)
|
version: 0,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for the app to be ready for use
|
const codePane = page.locator('.cm-content')
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
const codePane = page.getByRole('textbox').locator('div')
|
|
||||||
const codePaneButton = page.getByTestId('code-pane-button')
|
|
||||||
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
|
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
|
||||||
const arcButton = page.getByRole('button', {
|
const arcButton = page.getByRole('button', {
|
||||||
name: 'Tangential Arc',
|
name: 'Tangential Arc',
|
||||||
exact: true,
|
exact: true,
|
||||||
})
|
})
|
||||||
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
|
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
|
||||||
|
const commandBarComboBox = page.getByPlaceholder('Search commands')
|
||||||
|
const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
|
||||||
// Test that the hotkeys do nothing when
|
await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => {
|
||||||
// focus is on the code pane
|
await codePane.click()
|
||||||
await codePane.click()
|
await page.keyboard.type('//')
|
||||||
await page.keyboard.press('/')
|
await page.keyboard.press('s')
|
||||||
await page.keyboard.press('/')
|
await expect(commandBarComboBox).not.toBeVisible()
|
||||||
await page.keyboard.press('s')
|
await page.keyboard.press('e')
|
||||||
await page.keyboard.press('l')
|
await expect(commandBarComboBox).not.toBeVisible()
|
||||||
await page.keyboard.press('a')
|
await expect(codePane).toHaveText('//se')
|
||||||
await page.keyboard.press('e')
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText('//slae')
|
|
||||||
await page.keyboard.press('Meta+/')
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
// Test these hotkeys perform actions when
|
|
||||||
// focus is on the canvas
|
|
||||||
await page.mouse.move(600, 250)
|
|
||||||
await page.mouse.click(600, 250)
|
|
||||||
|
|
||||||
// work-around: to stop "keyboard.press('s')" from typing in the editor even when it should be blurred
|
|
||||||
await page.getByRole('button', { name: 'Commands' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('Escape')
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
// end work-around
|
|
||||||
|
|
||||||
// Start a sketch
|
|
||||||
await page.keyboard.press('s')
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await page.mouse.move(800, 300, { steps: 5 })
|
|
||||||
await page.mouse.click(800, 300)
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
|
||||||
timeout: 15_000,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Blur focus from the code editor, use the s command to sketch
|
||||||
|
await test.step(`Blur editor focus, enter sketch`, async () => {
|
||||||
|
/**
|
||||||
|
* TODO: There is a bug somewhere that causes this test to fail
|
||||||
|
* if you toggle the codePane closed before your trigger the
|
||||||
|
* start of the sketch.
|
||||||
|
* and a separate Safari-only bug that causes the test to fail
|
||||||
|
* if the pane is open the entire test. The maintainer of CodeMirror
|
||||||
|
* has pinpointed this to the unusual browser behavior:
|
||||||
|
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
||||||
|
*/
|
||||||
|
await blurCodeEditor()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await page.keyboard.press('s')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await page.mouse.move(800, 300, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 300)
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||||
|
timeout: 15_000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use some sketch hotkeys to create a sketch (l and a for now)
|
||||||
|
await test.step(`Incomplete sketch with hotkeys`, async () => {
|
||||||
|
await test.step(`Draw a line`, async () => {
|
||||||
|
await page.mouse.move(700, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.mouse.move(800, 250, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 250)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Unequip line tool`, async () => {
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Draw a tangential arc`, async () => {
|
||||||
|
await page.keyboard.press('a')
|
||||||
|
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
await page.mouse.move(1000, 100, { steps: 5 })
|
||||||
|
await page.mouse.click(1000, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Unequip with escape, equip line tool`, async () => {
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await page.waitForTimeout(50)
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
||||||
|
// Since there's code now, we have to get to the end of the line
|
||||||
|
await page.locator('.cm-line').last().click()
|
||||||
|
await page.keyboard.press('ControlOrMeta+ArrowRight')
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('//')
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await page.keyboard.press('a')
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await expect(codePane).toContainText('//la')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Close profile and exit sketch`, async () => {
|
||||||
|
await blurCodeEditor()
|
||||||
|
await page.mouse.move(700, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
// On close it will unequip the line tool.
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||||
|
await expect(exitSketchButton).toBeEnabled()
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Extrude with e
|
||||||
|
await test.step(`Extrude the sketch`, async () => {
|
||||||
|
await page.mouse.click(750, 150)
|
||||||
|
await blurCodeEditor()
|
||||||
|
await expect(extrudeButton).toBeEnabled()
|
||||||
|
await page.keyboard.press('e')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.mouse.move(800, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 200)
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Submit command' })
|
||||||
|
).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||||
|
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||||
|
})
|
||||||
|
|
||||||
|
// await codePaneButton.click()
|
||||||
|
// await expect(u.codeLocator).not.toBeVisible()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: There is a bug somewhere that causes this test to fail
|
* work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred
|
||||||
* if you toggle the codePane closed before your trigger the
|
|
||||||
* start of the sketch.
|
|
||||||
* and a separate Safari-only bug that causes the test to fail
|
|
||||||
* if the pane is open the entire test. The maintainer of CodeMirror
|
|
||||||
* has pinpointed this to the unusual browser behavior:
|
|
||||||
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
|
||||||
*/
|
*/
|
||||||
await codePaneButton.click()
|
async function blurCodeEditor() {
|
||||||
await expect(u.codeLocator).not.toBeVisible()
|
await page.getByRole('button', { name: 'Commands' }).click()
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
// Draw a line
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.move(700, 200, { steps: 5 })
|
}
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await page.mouse.move(800, 250, { steps: 5 })
|
|
||||||
await page.mouse.click(800, 250)
|
|
||||||
// Unequip line tool
|
|
||||||
await page.keyboard.press('l')
|
|
||||||
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
|
||||||
// Equip arc tool
|
|
||||||
await page.keyboard.press('a')
|
|
||||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
await page.mouse.move(1000, 100, { steps: 5 })
|
|
||||||
await page.mouse.click(1000, 100)
|
|
||||||
await page.keyboard.press('Escape')
|
|
||||||
await page.keyboard.press('l')
|
|
||||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
|
||||||
// Close profile
|
|
||||||
await page.mouse.move(700, 200, { steps: 5 })
|
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
// On close it will unequip the line tool.
|
|
||||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
|
||||||
// Exit sketch
|
|
||||||
await page.keyboard.press('Escape')
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
|
||||||
).not.toBeVisible()
|
|
||||||
await page.waitForTimeout(400)
|
|
||||||
|
|
||||||
// Extrude
|
|
||||||
await page.mouse.click(750, 150)
|
|
||||||
await expect(extrudeButton).not.toBeDisabled()
|
|
||||||
await page.keyboard.press('e')
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.move(800, 200, { steps: 5 })
|
|
||||||
await page.mouse.click(800, 200)
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click()
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Submit command' })
|
|
||||||
).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
|
||||||
|
|
||||||
await codePaneButton.click()
|
|
||||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Delete key does not navigate back', async ({ page }) => {
|
test('Delete key does not navigate back', async ({ page }) => {
|
||||||
|
@ -618,6 +618,67 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": true,
|
||||||
|
"description": "Ams change filament.",
|
||||||
|
"properties": {
|
||||||
|
"command": {
|
||||||
|
"enum": [
|
||||||
|
"ams_change_filament"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"errorno": {
|
||||||
|
"description": "The error number.",
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Reason"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The reason for the message.",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The result of the command."
|
||||||
|
},
|
||||||
|
"sequence_id": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/SequenceId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The sequence id."
|
||||||
|
},
|
||||||
|
"tar_temp": {
|
||||||
|
"description": "The target temperature.",
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"description": "The target.",
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"command",
|
||||||
|
"errorno",
|
||||||
|
"result",
|
||||||
|
"sequence_id",
|
||||||
|
"tar_temp",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"additionalProperties": true,
|
"additionalProperties": true,
|
||||||
"description": "Calibration.",
|
"description": "Calibration.",
|
||||||
@ -1192,6 +1253,54 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": true,
|
||||||
|
"description": "Print speed.",
|
||||||
|
"properties": {
|
||||||
|
"command": {
|
||||||
|
"enum": [
|
||||||
|
"print_speed"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"param": {
|
||||||
|
"description": "The param.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Reason"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The reason for the message.",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The result of the command."
|
||||||
|
},
|
||||||
|
"sequence_id": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/SequenceId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The sequence id."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"command",
|
||||||
|
"param",
|
||||||
|
"result",
|
||||||
|
"sequence_id"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"additionalProperties": true,
|
"additionalProperties": true,
|
||||||
"description": "Resume the print.",
|
"description": "Resume the print.",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.24.11",
|
"version": "0.24.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.17.0",
|
"@codemirror/autocomplete": "^6.17.0",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"html2canvas-pro": "^1.5.5",
|
"html2canvas-pro": "^1.5.8",
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"re-resizable": "^6.9.11",
|
"re-resizable": "^6.9.11",
|
||||||
|
@ -80,5 +80,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.24.11"
|
"version": "0.24.13"
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import { PATHS } from 'lib/paths'
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
@ -50,7 +50,7 @@ export function App() {
|
|||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ import { KclContextProvider } from 'lang/KclProvider'
|
|||||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
import { getState, setState } from 'lib/tauri'
|
import { getState, setState } from 'lib/tauri'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
@ -181,7 +181,7 @@ function CoreDump() {
|
|||||||
const { auth } = useSettingsAuthContext()
|
const { auth } = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
useHotkeyWrapper(['meta + shift + .'], () => {
|
||||||
|
@ -21,11 +21,13 @@ import {
|
|||||||
rename,
|
rename,
|
||||||
create,
|
create,
|
||||||
writeTextFile,
|
writeTextFile,
|
||||||
|
exists,
|
||||||
} from '@tauri-apps/plugin-fs'
|
} from '@tauri-apps/plugin-fs'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { join, sep } from '@tauri-apps/api/path'
|
import { join, sep } from '@tauri-apps/api/path'
|
||||||
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
|
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
|
||||||
import { getProjectInfo } from 'lib/tauri'
|
import { getProjectInfo } from 'lib/tauri'
|
||||||
|
import { getNextDirName, getNextFileName } from 'lib/tauriFS'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -105,14 +107,20 @@ export const FileMachineProvider = ({
|
|||||||
let createdPath: string
|
let createdPath: string
|
||||||
|
|
||||||
if (event.data.makeDir) {
|
if (event.data.makeDir) {
|
||||||
createdPath = await join(context.selectedDirectory.path, createdName)
|
let { name, path } = await getNextDirName({
|
||||||
|
entryName: createdName,
|
||||||
|
baseDir: context.selectedDirectory.path,
|
||||||
|
})
|
||||||
|
createdName = name
|
||||||
|
createdPath = path
|
||||||
await mkdir(createdPath)
|
await mkdir(createdPath)
|
||||||
} else {
|
} else {
|
||||||
createdPath =
|
const { name, path } = await getNextFileName({
|
||||||
context.selectedDirectory.path +
|
entryName: createdName,
|
||||||
sep() +
|
baseDir: context.selectedDirectory.path,
|
||||||
createdName +
|
})
|
||||||
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
|
createdName = name
|
||||||
|
createdPath = path
|
||||||
await create(createdPath)
|
await create(createdPath)
|
||||||
if (event.data.content) {
|
if (event.data.content) {
|
||||||
await writeTextFile(createdPath, event.data.content)
|
await writeTextFile(createdPath, event.data.content)
|
||||||
@ -129,14 +137,20 @@ export const FileMachineProvider = ({
|
|||||||
let createdPath: string
|
let createdPath: string
|
||||||
|
|
||||||
if (event.data.makeDir) {
|
if (event.data.makeDir) {
|
||||||
createdPath = await join(context.selectedDirectory.path, createdName)
|
let { name, path } = await getNextDirName({
|
||||||
|
entryName: createdName,
|
||||||
|
baseDir: context.selectedDirectory.path,
|
||||||
|
})
|
||||||
|
createdName = name
|
||||||
|
createdPath = path
|
||||||
await mkdir(createdPath)
|
await mkdir(createdPath)
|
||||||
} else {
|
} else {
|
||||||
createdPath =
|
const { name, path } = await getNextFileName({
|
||||||
context.selectedDirectory.path +
|
entryName: createdName,
|
||||||
sep() +
|
baseDir: context.selectedDirectory.path,
|
||||||
createdName +
|
})
|
||||||
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
|
createdName = name
|
||||||
|
createdPath = path
|
||||||
await create(createdPath)
|
await create(createdPath)
|
||||||
if (event.data.content) {
|
if (event.data.content) {
|
||||||
await writeTextFile(createdPath, event.data.content)
|
await writeTextFile(createdPath, event.data.content)
|
||||||
|
@ -382,11 +382,17 @@ export const FileTreeMenu = () => {
|
|||||||
const { send } = useFileContext()
|
const { send } = useFileContext()
|
||||||
|
|
||||||
async function createFile() {
|
async function createFile() {
|
||||||
send({ type: 'Create file', data: { name: '', makeDir: false } })
|
send({
|
||||||
|
type: 'Create file',
|
||||||
|
data: { name: '', makeDir: false },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createFolder() {
|
async function createFolder() {
|
||||||
send({ type: 'Create file', data: { name: '', makeDir: true } })
|
send({
|
||||||
|
type: 'Create file',
|
||||||
|
data: { name: '', makeDir: true },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useHotkeyWrapper(['meta + n'], createFile)
|
useHotkeyWrapper(['meta + n'], createFile)
|
||||||
|
@ -85,6 +85,7 @@ import {
|
|||||||
} from 'lang/std/engineConnection'
|
} from 'lang/std/engineConnection'
|
||||||
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
|
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
|
||||||
import { useFileContext } from 'hooks/useFileContext'
|
import { useFileContext } from 'hooks/useFileContext'
|
||||||
|
import { PLAYWRIGHT_MOCK_EXPORT_DURATION } from 'lib/constants'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -393,10 +394,24 @@ export const ModelingMachineProvider = ({
|
|||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mockExportDuration = window.localStorage.getItem(
|
||||||
|
PLAYWRIGHT_MOCK_EXPORT_DURATION
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('mockExportDuration', mockExportDuration)
|
||||||
|
|
||||||
|
// Artificially delay the export in playwright tests
|
||||||
toast.promise(
|
toast.promise(
|
||||||
exportFromEngine({
|
Promise.all([
|
||||||
format: format,
|
exportFromEngine({
|
||||||
}),
|
format: format,
|
||||||
|
}),
|
||||||
|
mockExportDuration
|
||||||
|
? new Promise((resolve) =>
|
||||||
|
setTimeout(resolve, Number(mockExportDuration))
|
||||||
|
)
|
||||||
|
: Promise.resolve(),
|
||||||
|
]),
|
||||||
{
|
{
|
||||||
loading: 'Starting print...',
|
loading: 'Starting print...',
|
||||||
success: 'Started print successfully',
|
success: 'Started print successfully',
|
||||||
@ -467,12 +482,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Add pending text-to-cad prompt': assign({
|
|
||||||
pendingTextToCad: (_, event) => event.data.prompt.trim(),
|
|
||||||
}),
|
|
||||||
'Remove pending text-to-cad prompt': assign({
|
|
||||||
pendingTextToCad: undefined,
|
|
||||||
}),
|
|
||||||
'Submit to Text-to-CAD API': async (_, { data }) => {
|
'Submit to Text-to-CAD API': async (_, { data }) => {
|
||||||
const trimmedPrompt = data.prompt.trim()
|
const trimmedPrompt = data.prompt.trim()
|
||||||
if (!trimmedPrompt) return
|
if (!trimmedPrompt) return
|
||||||
@ -549,8 +558,6 @@ export const ModelingMachineProvider = ({
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Has no pending text-to-cad submissions': ({ pendingTextToCad }) =>
|
|
||||||
!pendingTextToCad,
|
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { coreDump } from 'lang/wasm'
|
import { coreDump } from 'lang/wasm'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
@ -11,7 +11,7 @@ export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
|||||||
const { auth } = useSettingsAuthContext()
|
const { auth } = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { PATHS } from 'lib/paths'
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
import { TextToCad_type } from '@kittycad/lib/dist/types/src/models'
|
import { TextToCad_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Box3,
|
Box3,
|
||||||
Color,
|
Color,
|
||||||
@ -36,10 +36,12 @@ const FRUSTUM_SIZE = 0.5
|
|||||||
const OUTPUT_KEY = 'source.glb'
|
const OUTPUT_KEY = 'source.glb'
|
||||||
|
|
||||||
export function ToastTextToCadError({
|
export function ToastTextToCadError({
|
||||||
|
toastId,
|
||||||
message,
|
message,
|
||||||
prompt,
|
prompt,
|
||||||
commandBarSend,
|
commandBarSend,
|
||||||
}: {
|
}: {
|
||||||
|
toastId: string
|
||||||
message: string
|
message: string
|
||||||
prompt: string
|
prompt: string
|
||||||
commandBarSend: (
|
commandBarSend: (
|
||||||
@ -63,7 +65,7 @@ export function ToastTextToCadError({
|
|||||||
}}
|
}}
|
||||||
name="Dismiss"
|
name="Dismiss"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toast.dismiss()
|
toast.dismiss(toastId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Dismiss
|
Dismiss
|
||||||
@ -85,7 +87,7 @@ export function ToastTextToCadError({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
toast.dismiss()
|
toast.dismiss(toastId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit prompt
|
Edit prompt
|
||||||
@ -96,6 +98,7 @@ export function ToastTextToCadError({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ToastTextToCadSuccess({
|
export function ToastTextToCadSuccess({
|
||||||
|
toastId,
|
||||||
data,
|
data,
|
||||||
navigate,
|
navigate,
|
||||||
context,
|
context,
|
||||||
@ -103,7 +106,7 @@ export function ToastTextToCadSuccess({
|
|||||||
fileMachineSend,
|
fileMachineSend,
|
||||||
settings,
|
settings,
|
||||||
}: {
|
}: {
|
||||||
// TODO: update this type to match the actual data when API is done
|
toastId: string
|
||||||
data: TextToCad_type & { fileName: string }
|
data: TextToCad_type & { fileName: string }
|
||||||
navigate: (to: string) => void
|
navigate: (to: string) => void
|
||||||
context: ReturnType<typeof useFileContext>['context']
|
context: ReturnType<typeof useFileContext>['context']
|
||||||
@ -119,10 +122,40 @@ export function ToastTextToCadSuccess({
|
|||||||
}) {
|
}) {
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
|
const animationRequestRef = useRef<number>()
|
||||||
const [hasCopied, setHasCopied] = useState(false)
|
const [hasCopied, setHasCopied] = useState(false)
|
||||||
const [showCopiedUi, setShowCopiedUi] = useState(false)
|
const [showCopiedUi, setShowCopiedUi] = useState(false)
|
||||||
const modelId = data.id
|
const modelId = data.id
|
||||||
|
|
||||||
|
const animate = useCallback(
|
||||||
|
({
|
||||||
|
renderer,
|
||||||
|
scene,
|
||||||
|
camera,
|
||||||
|
controls,
|
||||||
|
isFirstRender = false,
|
||||||
|
}: {
|
||||||
|
renderer: WebGLRenderer
|
||||||
|
scene: Scene
|
||||||
|
camera: OrthographicCamera
|
||||||
|
controls: OrbitControls
|
||||||
|
isFirstRender?: boolean
|
||||||
|
}) => {
|
||||||
|
if (
|
||||||
|
!wrapperRef.current ||
|
||||||
|
!(isFirstRender || animationRequestRef.current)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
animationRequestRef.current = requestAnimationFrame(() =>
|
||||||
|
animate({ renderer, scene, camera, controls })
|
||||||
|
)
|
||||||
|
// required if controls.enableDamping or controls.autoRotate are set to true
|
||||||
|
controls.update()
|
||||||
|
renderer.render(scene, camera)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
|
|
||||||
@ -130,7 +163,6 @@ export function ToastTextToCadSuccess({
|
|||||||
const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true })
|
const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true })
|
||||||
renderer.setSize(CANVAS_SIZE, CANVAS_SIZE)
|
renderer.setSize(CANVAS_SIZE, CANVAS_SIZE)
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
||||||
renderer.setAnimationLoop(animate)
|
|
||||||
|
|
||||||
const scene = new Scene()
|
const scene = new Scene()
|
||||||
const ambientLight = new DirectionalLight(new Color('white'), 8.0)
|
const ambientLight = new DirectionalLight(new Color('white'), 8.0)
|
||||||
@ -153,13 +185,6 @@ export function ToastTextToCadSuccess({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate)
|
|
||||||
// required if controls.enableDamping or controls.autoRotate are set to true
|
|
||||||
controls.update()
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
loader.parse(
|
loader.parse(
|
||||||
buffer,
|
buffer,
|
||||||
'',
|
'',
|
||||||
@ -210,6 +235,8 @@ export function ToastTextToCadSuccess({
|
|||||||
|
|
||||||
camera.updateProjectionMatrix()
|
camera.updateProjectionMatrix()
|
||||||
controls.update()
|
controls.update()
|
||||||
|
// render the scene once...
|
||||||
|
renderer.render(scene, camera)
|
||||||
},
|
},
|
||||||
// called when loading has errors
|
// called when loading has errors
|
||||||
function (error) {
|
function (error) {
|
||||||
@ -219,8 +246,26 @@ export function ToastTextToCadSuccess({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ...and set a mouseover listener on the canvas to enable the orbit controls
|
||||||
|
canvasRef.current.addEventListener('mouseover', () => {
|
||||||
|
renderer.setAnimationLoop(() =>
|
||||||
|
animate({ renderer, scene, camera, controls, isFirstRender: true })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
canvasRef.current.addEventListener('mouseout', () => {
|
||||||
|
renderer.setAnimationLoop(null)
|
||||||
|
if (animationRequestRef.current) {
|
||||||
|
cancelAnimationFrame(animationRequestRef.current)
|
||||||
|
animationRequestRef.current = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
renderer.dispose()
|
renderer.dispose()
|
||||||
|
if (animationRequestRef.current) {
|
||||||
|
cancelAnimationFrame(animationRequestRef.current)
|
||||||
|
animationRequestRef.current = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -265,7 +310,7 @@ export function ToastTextToCadSuccess({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
toast.dismiss()
|
toast.dismiss(toastId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasCopied ? 'Close' : 'Reject'}
|
{hasCopied ? 'Close' : 'Reject'}
|
||||||
@ -284,7 +329,7 @@ export function ToastTextToCadSuccess({
|
|||||||
`${context.project.path}${sep()}${data.fileName}`
|
`${context.project.path}${sep()}${data.fileName}`
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
toast.dismiss()
|
toast.dismiss(toastId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Accept
|
Accept
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
createUpdaterRestartModal,
|
createUpdaterRestartModal,
|
||||||
} from 'components/UpdaterRestartModal'
|
} from 'components/UpdaterRestartModal'
|
||||||
import { AppStreamProvider } from 'AppState'
|
import { AppStreamProvider } from 'AppState'
|
||||||
|
import { PLAYWRIGHT_KEY, PLAYWRIGHT_TOAST_DURATION } from 'lib/constants'
|
||||||
|
|
||||||
// uncomment for xstate inspector
|
// uncomment for xstate inspector
|
||||||
// import { DEV } from 'env'
|
// import { DEV } from 'env'
|
||||||
@ -24,6 +25,9 @@ import { AppStreamProvider } from 'AppState'
|
|||||||
// })
|
// })
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||||
|
const maybePlaywrightToastDuration = Number(
|
||||||
|
window?.localStorage.getItem(PLAYWRIGHT_TOAST_DURATION)
|
||||||
|
)
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<HotkeysProvider>
|
<HotkeysProvider>
|
||||||
@ -44,8 +48,10 @@ root.render(
|
|||||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||||
},
|
},
|
||||||
duration:
|
duration:
|
||||||
window?.localStorage.getItem('playwright') === 'true'
|
window?.localStorage.getItem(PLAYWRIGHT_KEY) === 'true'
|
||||||
? 10 // speed up e2e tests
|
? maybePlaywrightToastDuration > 0
|
||||||
|
? maybePlaywrightToastDuration
|
||||||
|
: 10 // optionally speed up e2e tests
|
||||||
: 1500,
|
: 1500,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -56,3 +56,10 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
|||||||
/** The default KCL length expression */
|
/** The default KCL length expression */
|
||||||
export const KCL_DEFAULT_LENGTH = `5`
|
export const KCL_DEFAULT_LENGTH = `5`
|
||||||
export const COOKIE_NAME = '__Secure-next-auth.session-token'
|
export const COOKIE_NAME = '__Secure-next-auth.session-token'
|
||||||
|
|
||||||
|
/** localStorage key to determine if we're in Playwright tests */
|
||||||
|
export const PLAYWRIGHT_KEY = 'playwright'
|
||||||
|
/** localStorage key to set toast duration in Playwright tests */
|
||||||
|
export const PLAYWRIGHT_TOAST_DURATION = 'playwright-toast-duration'
|
||||||
|
/** localStorage key to set mock export pause duration in Playwright tests */
|
||||||
|
export const PLAYWRIGHT_MOCK_EXPORT_DURATION = 'playwright-mock-export-duration'
|
||||||
|
@ -11,6 +11,7 @@ import { APP_VERSION } from 'routes/Settings'
|
|||||||
import { UAParser } from 'ua-parser-js'
|
import { UAParser } from 'ua-parser-js'
|
||||||
import screenshot from 'lib/screenshot'
|
import screenshot from 'lib/screenshot'
|
||||||
import { VITE_KC_API_BASE_URL } from 'env'
|
import { VITE_KC_API_BASE_URL } from 'env'
|
||||||
|
import CodeManager from 'lang/codeManager'
|
||||||
|
|
||||||
/* eslint-disable suggest-no-throw/suggest-no-throw --
|
/* eslint-disable suggest-no-throw/suggest-no-throw --
|
||||||
* All the throws in CoreDumpManager are intentional and should be caught and handled properly
|
* All the throws in CoreDumpManager are intentional and should be caught and handled properly
|
||||||
@ -32,14 +33,17 @@ import { VITE_KC_API_BASE_URL } from 'env'
|
|||||||
// TODO: Throw more
|
// TODO: Throw more
|
||||||
export class CoreDumpManager {
|
export class CoreDumpManager {
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
codeManager: CodeManager
|
||||||
token: string | undefined
|
token: string | undefined
|
||||||
baseUrl: string = VITE_KC_API_BASE_URL
|
baseUrl: string = VITE_KC_API_BASE_URL
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
|
codeManager: CodeManager,
|
||||||
token: string | undefined
|
token: string | undefined
|
||||||
) {
|
) {
|
||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
|
this.codeManager = codeManager
|
||||||
this.token = token
|
this.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +65,10 @@ export class CoreDumpManager {
|
|||||||
return APP_VERSION
|
return APP_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kclCode(): string {
|
||||||
|
return this.codeManager.code
|
||||||
|
}
|
||||||
|
|
||||||
// Get the backend pool we've requested.
|
// Get the backend pool we've requested.
|
||||||
pool(): string {
|
pool(): string {
|
||||||
return this.engineCommandManager.settings.pool || ''
|
return this.engineCommandManager.settings.pool || ''
|
||||||
|
41
src/lib/machine-api.d.ts
vendored
41
src/lib/machine-api.d.ts
vendored
@ -264,6 +264,33 @@ export interface components {
|
|||||||
} & {
|
} & {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
})
|
})
|
||||||
|
| ({
|
||||||
|
/** @enum {string} */
|
||||||
|
command: 'ams_change_filament'
|
||||||
|
/**
|
||||||
|
* Format: int64
|
||||||
|
* @description The error number.
|
||||||
|
*/
|
||||||
|
errorno: number
|
||||||
|
/** @description The reason for the message. */
|
||||||
|
reason?: components['schemas']['Reason'] | null
|
||||||
|
/** @description The result of the command. */
|
||||||
|
result: components['schemas']['Result']
|
||||||
|
/** @description The sequence id. */
|
||||||
|
sequence_id: components['schemas']['SequenceId']
|
||||||
|
/**
|
||||||
|
* Format: int64
|
||||||
|
* @description The target temperature.
|
||||||
|
*/
|
||||||
|
tar_temp: number
|
||||||
|
/**
|
||||||
|
* Format: int64
|
||||||
|
* @description The target.
|
||||||
|
*/
|
||||||
|
target: number
|
||||||
|
} & {
|
||||||
|
[key: string]: unknown
|
||||||
|
})
|
||||||
| ({
|
| ({
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
command: 'calibration'
|
command: 'calibration'
|
||||||
@ -528,6 +555,20 @@ export interface components {
|
|||||||
} & {
|
} & {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
})
|
})
|
||||||
|
| ({
|
||||||
|
/** @enum {string} */
|
||||||
|
command: 'print_speed'
|
||||||
|
/** @description The param. */
|
||||||
|
param: string
|
||||||
|
/** @description The reason for the message. */
|
||||||
|
reason?: components['schemas']['Reason'] | null
|
||||||
|
/** @description The result of the command. */
|
||||||
|
result: components['schemas']['Result']
|
||||||
|
/** @description The sequence id. */
|
||||||
|
sequence_id: components['schemas']['SequenceId']
|
||||||
|
} & {
|
||||||
|
[key: string]: unknown
|
||||||
|
})
|
||||||
| ({
|
| ({
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
command: 'resume'
|
command: 'resume'
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { appConfigDir } from '@tauri-apps/api/path'
|
import { appConfigDir, join } from '@tauri-apps/api/path'
|
||||||
import { isTauri } from './isTauri'
|
import { isTauri } from './isTauri'
|
||||||
import type { FileEntry } from 'lib/types'
|
import type { FileEntry } from 'lib/types'
|
||||||
import {
|
import {
|
||||||
|
FILE_EXT,
|
||||||
INDEX_IDENTIFIER,
|
INDEX_IDENTIFIER,
|
||||||
MAX_PADDING,
|
MAX_PADDING,
|
||||||
ONBOARDING_PROJECT_NAME,
|
ONBOARDING_PROJECT_NAME,
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
readAppSettingsFile,
|
readAppSettingsFile,
|
||||||
} from './tauri'
|
} from './tauri'
|
||||||
import { engineCommandManager } from './singletons'
|
import { engineCommandManager } from './singletons'
|
||||||
|
import { exists } from '@tauri-apps/plugin-fs'
|
||||||
|
|
||||||
export const isHidden = (fileOrDir: FileEntry) =>
|
export const isHidden = (fileOrDir: FileEntry) =>
|
||||||
!!fileOrDir.name?.startsWith('.')
|
!!fileOrDir.name?.startsWith('.')
|
||||||
@ -162,3 +164,56 @@ export async function createAndOpenNewProject({
|
|||||||
)
|
)
|
||||||
return newProject
|
return newProject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next available file name by appending a hyphen and number to the end of the name
|
||||||
|
* @todo move this to the equivalent of tauriFS.ts for Electron migration
|
||||||
|
*/
|
||||||
|
export async function getNextFileName({
|
||||||
|
entryName,
|
||||||
|
baseDir,
|
||||||
|
}: {
|
||||||
|
entryName: string
|
||||||
|
baseDir: string
|
||||||
|
}) {
|
||||||
|
// Remove any existing index from the name before adding a new one
|
||||||
|
let createdName = entryName.replace(FILE_EXT, '') + FILE_EXT
|
||||||
|
let createdPath = await join(baseDir, createdName)
|
||||||
|
let i = 1
|
||||||
|
while (await exists(createdPath)) {
|
||||||
|
const matchOnIndexAndExtension = new RegExp(`(-\\d+)?(${FILE_EXT})?$`)
|
||||||
|
createdName =
|
||||||
|
entryName.replace(matchOnIndexAndExtension, '') + `-${i}` + FILE_EXT
|
||||||
|
createdPath = await join(baseDir, createdName)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: createdName,
|
||||||
|
path: createdPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next available directory name by appending a hyphen and number to the end of the name
|
||||||
|
* @todo move this to the equivalent of tauriFS.ts for Electron migration
|
||||||
|
*/
|
||||||
|
export async function getNextDirName({
|
||||||
|
entryName,
|
||||||
|
baseDir,
|
||||||
|
}: {
|
||||||
|
entryName: string
|
||||||
|
baseDir: string
|
||||||
|
}) {
|
||||||
|
let createdName = entryName
|
||||||
|
let createdPath = await join(baseDir, createdName)
|
||||||
|
let i = 1
|
||||||
|
while (await exists(createdPath)) {
|
||||||
|
createdName = entryName.replace(/-\d+$/, '') + `-${i}`
|
||||||
|
createdPath = await join(baseDir, createdName)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: createdName,
|
||||||
|
path: createdPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ import crossPlatformFetch from './crossPlatformFetch'
|
|||||||
import { isTauri } from './isTauri'
|
import { isTauri } from './isTauri'
|
||||||
import { Themes } from './theme'
|
import { Themes } from './theme'
|
||||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||||
|
import { getNextFileName } from './tauriFS'
|
||||||
|
|
||||||
export async function submitTextToCadPrompt(
|
export async function submitTextToCadPrompt(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
@ -91,6 +92,7 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
toast.error(
|
toast.error(
|
||||||
() =>
|
() =>
|
||||||
ToastTextToCadError({
|
ToastTextToCadError({
|
||||||
|
toastId,
|
||||||
message,
|
message,
|
||||||
commandBarSend,
|
commandBarSend,
|
||||||
prompt: trimmedPrompt,
|
prompt: trimmedPrompt,
|
||||||
@ -165,7 +167,7 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
showFailureToast('Failed to generate parametric model')
|
showFailureToast('Failed to generate parametric model')
|
||||||
return e
|
return e
|
||||||
})
|
})
|
||||||
.then((value) => {
|
.then(async (value) => {
|
||||||
if (value.code === undefined || !value.code || value.code.length === 0) {
|
if (value.code === undefined || !value.code || value.code.length === 0) {
|
||||||
// We want to show the real error message to the user.
|
// We want to show the real error message to the user.
|
||||||
if (value.error && value.error.length > 0) {
|
if (value.error && value.error.length > 0) {
|
||||||
@ -179,13 +181,23 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TRUNCATED_PROMPT_LENGTH = 24
|
const TRUNCATED_PROMPT_LENGTH = 24
|
||||||
const newFileName = `${value.prompt
|
let newFileName = `${value.prompt
|
||||||
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
||||||
.replace(/\s/gi, '-')
|
.replace(/\s/gi, '-')
|
||||||
.replace(/\W/gi, '-')
|
.replace(/\W/gi, '-')
|
||||||
.toLowerCase()}`
|
.toLowerCase()}${FILE_EXT}`
|
||||||
|
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
|
// We have to pre-emptively run our unique file name logic,
|
||||||
|
// so that we can pass the unique file name to the toast,
|
||||||
|
// and by extension the file-deletion-on-reject logic.
|
||||||
|
newFileName = (
|
||||||
|
await getNextFileName({
|
||||||
|
entryName: newFileName,
|
||||||
|
baseDir: context.selectedDirectory.path,
|
||||||
|
})
|
||||||
|
).name
|
||||||
|
|
||||||
fileMachineSend({
|
fileMachineSend({
|
||||||
type: 'Create file',
|
type: 'Create file',
|
||||||
data: {
|
data: {
|
||||||
@ -193,14 +205,13 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
makeDir: false,
|
makeDir: false,
|
||||||
content: value.code,
|
content: value.code,
|
||||||
silent: true,
|
silent: true,
|
||||||
makeUnique: true,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
fileName: newFileName + FILE_EXT,
|
fileName: newFileName,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -214,6 +225,7 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
toast.success(
|
toast.success(
|
||||||
() =>
|
() =>
|
||||||
ToastTextToCadSuccess({
|
ToastTextToCadSuccess({
|
||||||
|
toastId,
|
||||||
data: textToCadOutputCreated,
|
data: textToCadOutputCreated,
|
||||||
token,
|
token,
|
||||||
navigate,
|
navigate,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user