Compare commits
10 Commits
v0.59.0
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
7d6427ab64 | |||
4abbe0d57a | |||
a631ff689f | |||
e1d401adfe | |||
6f49c88382 | |||
374d07b995 | |||
3481252082 | |||
035f3b6aed | |||
923feadfa5 | |||
1ea66d6f23 |
@ -9,10 +9,11 @@ VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
#VITE_KC_DEV_TOKEN="optional token from dev.zoo.dev to skip auth in the app"
|
||||
#VITE_KC_DEV_TOKEN="optional token to skip auth in the app"
|
||||
#token="required token for playwright. TODO: clean up env vars in #3973"
|
||||
|
||||
RUST_BACKTRACE=1
|
||||
PYO3_PYTHON=/usr/local/bin/python3
|
||||
#KITTYCAD_API_TOKEN="required token from dev.zoo.dev for engine testing"
|
||||
#KITTYCAD_API_TOKEN="required token for engine testing"
|
||||
|
||||
FAIL_ON_CONSOLE_ERRORS=true
|
||||
|
1
.github/workflows/e2e-tests.yml
vendored
@ -229,7 +229,6 @@ jobs:
|
||||
max_attempts: 5
|
||||
env:
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
TAB_API_URL: ${{ secrets.TAB_API_URL }}
|
||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
|
@ -63,7 +63,7 @@ If you're not a Zoo employee you won't be able to access the dev environment, yo
|
||||
|
||||
### Development environment variables
|
||||
|
||||
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `dev.zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
|
||||
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
|
||||
|
||||
### Developing in Chrome
|
||||
|
||||
@ -198,15 +198,9 @@ For more information on fuzzing you can check out
|
||||
|
||||
### Playwright tests
|
||||
|
||||
You will need a `./e2e/playwright/playwright-secrets.env` file:
|
||||
Prepare these system dependencies:
|
||||
|
||||
```bash
|
||||
$ touch ./e2e/playwright/playwright-secrets.env
|
||||
$ cat ./e2e/playwright/playwright-secrets.env
|
||||
token=<dev.zoo.dev/account/api-tokens>
|
||||
snapshottoken=<zoo.dev/account/api-tokens>
|
||||
```
|
||||
or use `export` to set the environment variables `token` and `snapshottoken`.
|
||||
- Set $token from https://zoo.dev/account/api-tokens
|
||||
|
||||
#### Snapshot tests (Google Chrome on Ubuntu only)
|
||||
|
||||
@ -302,7 +296,7 @@ Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testin
|
||||
|
||||
Prepare these system dependencies:
|
||||
|
||||
- Set `$KITTYCAD_API_TOKEN` from https://dev.zoo.dev/account/api-tokens
|
||||
- Set `$KITTYCAD_API_TOKEN` from https://zoo.dev/account/api-tokens
|
||||
- Install `just` following [these instructions](https://just.systems/man/en/packages.html)
|
||||
|
||||
then run tests that target the KCL language:
|
||||
|
@ -15,12 +15,6 @@ once fixed in engine will just start working here with no language changes.
|
||||
- **Import**: Right now you can import a file, even if that file has brep data
|
||||
you cannot edit it, after v1, the engine will account for this.
|
||||
|
||||
- **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet
|
||||
cases work currently.
|
||||
|
||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||
chamfer cases work currently.
|
||||
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
|
||||
- **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
import { secrets } from '@e2e/playwright/secrets'
|
||||
import { token } from '@e2e/playwright/test-utils'
|
||||
|
||||
export class SignInPageFixture {
|
||||
public page: Page
|
||||
@ -25,7 +25,7 @@ export class SignInPageFixture {
|
||||
// Device flow: stolen from the tauri days
|
||||
// https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
|
||||
const headers = {
|
||||
Authorization: `Bearer ${secrets.token}`,
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ test.describe('Onboarding tests', () => {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await scene.connectionEstablished()
|
||||
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
|
||||
})
|
||||
|
||||
await test.step('Go home and verify we still see the tutorial button, then begin it.', async () => {
|
||||
@ -91,8 +90,6 @@ test.describe('Onboarding tests', () => {
|
||||
await test.step('Ensure we see the welcome screen in a new project', async () => {
|
||||
await expect(toolbar.projectName).toContainText('tutorial-project')
|
||||
await expect(tutorialWelcomeHeading).toBeVisible()
|
||||
await scene.connectionEstablished()
|
||||
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
|
||||
})
|
||||
|
||||
await test.step('Test the clicking through the onboarding flow', async () => {
|
||||
@ -132,8 +129,6 @@ test.describe('Onboarding tests', () => {
|
||||
await test.step('Gets to the onboarding start', async () => {
|
||||
await expect(toolbar.projectName).toContainText('tutorial-project')
|
||||
await expect(tutorialWelcomeHeading).toBeVisible()
|
||||
await scene.connectionEstablished()
|
||||
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
|
||||
})
|
||||
|
||||
await test.step('Dismiss the onboarding', async () => {
|
||||
@ -168,7 +163,6 @@ test.describe('Onboarding tests', () => {
|
||||
await expect(toolbar.projectName).toContainText('tutorial-project')
|
||||
await expect(tutorialWelcomeHeading).toBeVisible()
|
||||
await scene.connectionEstablished()
|
||||
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
|
||||
})
|
||||
|
||||
await test.step('Dismiss the onboarding', async () => {
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const secrets: Record<string, string> = {}
|
||||
const secretsPath = './e2e/playwright/playwright-secrets.env'
|
||||
try {
|
||||
const file = readFileSync(secretsPath, 'utf8')
|
||||
file
|
||||
.split('\n')
|
||||
.filter((line) => line && line.length > 1)
|
||||
.forEach((line) => {
|
||||
// Allow line comments.
|
||||
if (line.trimStart().startsWith('#')) return
|
||||
const [key, value] = line.split('=')
|
||||
// prefer env vars over secrets file
|
||||
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
void error
|
||||
// probably running in CI
|
||||
console.warn(
|
||||
`Error reading ${secretsPath}; environment variables will be used`
|
||||
)
|
||||
}
|
||||
secrets.token = secrets.token || process.env.token || ''
|
||||
secrets.snapshottoken = secrets.snapshottoken || process.env.snapshottoken || ''
|
||||
// add more env vars here to make them available in CI
|
||||
|
||||
export { secrets }
|
@ -5,7 +5,7 @@ import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
||||
import {
|
||||
getUtils,
|
||||
headerMasks,
|
||||
networkingMasks,
|
||||
lowerRightMasks,
|
||||
settingsToToml,
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
@ -88,7 +88,7 @@ const extrudeDefaultPlane = async (
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
await u.openKclCodePanel()
|
||||
}
|
||||
@ -173,7 +173,7 @@ test(
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
|
||||
const lineEndClick = () =>
|
||||
@ -200,7 +200,7 @@ test(
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
await endOfTangentClk()
|
||||
|
||||
@ -210,7 +210,7 @@ test(
|
||||
await threePointArcMidPointMv()
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
await threePointArcMidPointClk()
|
||||
await page.waitForTimeout(100)
|
||||
@ -219,7 +219,7 @@ test(
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
|
||||
await threePointArcEndPointClk()
|
||||
@ -239,7 +239,7 @@ test(
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
await arcEndClk()
|
||||
}
|
||||
@ -286,7 +286,7 @@ test(
|
||||
// Ensure the draft rectangle looks the same as it usually does
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -328,7 +328,7 @@ test(
|
||||
// Ensure the draft rectangle looks the same as it usually does
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn(XZ)profile001 = circle(sketch001, center = [366.89, -62.01], radius = 1)`
|
||||
@ -395,7 +395,7 @@ test.describe(
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
|
||||
await u.doAndWaitForImageDiff(
|
||||
@ -408,7 +408,7 @@ test.describe(
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
})
|
||||
|
||||
@ -490,7 +490,7 @@ test.describe(
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
|
||||
// exit sketch
|
||||
@ -504,7 +504,7 @@ test.describe(
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -563,7 +563,7 @@ part002 = startSketchOn(part001, face = seg01)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -599,7 +599,7 @@ test(
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -636,7 +636,7 @@ test(
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -701,7 +701,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
|
||||
await expect(stream).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [...headerMasks(page), ...networkingMasks(page)],
|
||||
mask: [...headerMasks(page), ...lowerRightMasks(page)],
|
||||
})
|
||||
})
|
||||
|
||||
@ -722,7 +722,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
|
||||
await expect(stream).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [...headerMasks(page), ...networkingMasks(page)],
|
||||
mask: [...headerMasks(page), ...lowerRightMasks(page)],
|
||||
})
|
||||
})
|
||||
|
||||
@ -761,7 +761,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
|
||||
await expect(stream).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [...headerMasks(page), ...networkingMasks(page)],
|
||||
mask: [...headerMasks(page), ...lowerRightMasks(page)],
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -829,7 +829,7 @@ test('theme persists', async ({ page, context }) => {
|
||||
|
||||
await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
})
|
||||
|
||||
@ -870,7 +870,7 @@ sweepSketch = startSketchOn(XY)
|
||||
|
||||
await expect(page, 'expect small color widget').toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
})
|
||||
|
||||
@ -923,7 +923,7 @@ sweepSketch = startSketchOn(XY)
|
||||
'expect small color widget to have window open'
|
||||
).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
@ -13,11 +13,15 @@ import fsp from 'fs/promises'
|
||||
import pixelMatch from 'pixelmatch'
|
||||
import type { Protocol } from 'playwright-core/types/protocol'
|
||||
import { PNG } from 'pngjs'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
||||
export const token = process.env.token || ''
|
||||
|
||||
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
||||
|
||||
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
|
||||
import { secrets } from '@e2e/playwright/secrets'
|
||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
||||
import { test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
@ -31,8 +35,9 @@ export const headerMasks = (page: Page) => [
|
||||
page.locator('#sidebar-bottom-ribbon'),
|
||||
]
|
||||
|
||||
export const networkingMasks = (page: Page) => [
|
||||
export const lowerRightMasks = (page: Page) => [
|
||||
page.getByTestId('network-toggle'),
|
||||
page.getByTestId('billing-remaining-bar'),
|
||||
]
|
||||
|
||||
export type TestColor = [number, number, number]
|
||||
@ -890,7 +895,7 @@ export async function setup(
|
||||
localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
token,
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: settingsToToml({
|
||||
settings: {
|
||||
@ -918,7 +923,7 @@ export async function setup(
|
||||
await context.addCookies([
|
||||
{
|
||||
name: COOKIE_NAME,
|
||||
value: secrets.token,
|
||||
value: token,
|
||||
path: '/',
|
||||
domain: 'localhost',
|
||||
secure: true,
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
createProject,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
networkingMasks,
|
||||
lowerRightMasks,
|
||||
settingsToToml,
|
||||
tomlToSettings,
|
||||
} from '@e2e/playwright/test-utils'
|
||||
@ -1061,7 +1061,7 @@ fn cube`
|
||||
'toggle-settings-initial.png',
|
||||
{
|
||||
maxDiffPixels: 15,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
}
|
||||
)
|
||||
|
||||
@ -1078,7 +1078,7 @@ fn cube`
|
||||
'toggle-settings-initial.png',
|
||||
{
|
||||
maxDiffPixels: 15,
|
||||
mask: networkingMasks(page),
|
||||
mask: lowerRightMasks(page),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
1
package-lock.json
generated
@ -2492,7 +2492,6 @@
|
||||
},
|
||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -137,8 +137,8 @@
|
||||
"test:unit:components": "jest -c jest-component-unit-tests/jest.config.ts --rootDir jest-component-unit-tests/",
|
||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
|
||||
"test:playwright:electron:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
|
||||
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
|
||||
"test:playwright:electron:local": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
|
||||
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
|
||||
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000",
|
||||
"test:unit:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
|
||||
},
|
||||
|
@ -1295,13 +1295,20 @@ impl Node<CallExpressionKw> {
|
||||
|
||||
// Build a hashmap from argument labels to the final evaluated values.
|
||||
let mut fn_args = IndexMap::with_capacity(self.arguments.len());
|
||||
let mut errors = Vec::new();
|
||||
for arg_expr in &self.arguments {
|
||||
let source_range = SourceRange::from(arg_expr.arg.clone());
|
||||
let metadata = Metadata { source_range };
|
||||
let value = ctx
|
||||
.execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
|
||||
.await?;
|
||||
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
|
||||
let arg = Arg::new(value, source_range);
|
||||
match &arg_expr.label {
|
||||
Some(l) => {
|
||||
fn_args.insert(l.name.clone(), arg);
|
||||
}
|
||||
None => errors.push(arg),
|
||||
}
|
||||
}
|
||||
let fn_args = fn_args; // remove mutability
|
||||
|
||||
@ -1321,6 +1328,7 @@ impl Node<CallExpressionKw> {
|
||||
KwArgs {
|
||||
unlabeled,
|
||||
labeled: fn_args,
|
||||
errors,
|
||||
},
|
||||
self.into(),
|
||||
ctx.clone(),
|
||||
@ -1894,6 +1902,44 @@ fn type_check_params_kw(
|
||||
}
|
||||
}
|
||||
|
||||
if !args.errors.is_empty() {
|
||||
let actuals = args.labeled.keys();
|
||||
let formals: Vec<_> = function_expression
|
||||
.params
|
||||
.iter()
|
||||
.filter_map(|p| {
|
||||
if !p.labeled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = &p.identifier.name;
|
||||
if actuals.clone().any(|a| a == name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(format!("`{name}`"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let suggestion = if formals.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("; suggested labels: {}", formals.join(", "))
|
||||
};
|
||||
|
||||
let mut errors = args.errors.iter().map(|e| {
|
||||
CompilationError::err(
|
||||
e.source_range,
|
||||
format!("This argument needs a label, but it doesn't have one{suggestion}"),
|
||||
)
|
||||
});
|
||||
|
||||
let first = errors.next().unwrap();
|
||||
errors.for_each(|e| exec_state.err(e));
|
||||
|
||||
return Err(KclError::Semantic(first.into()));
|
||||
}
|
||||
|
||||
if let Some(arg) = &mut args.unlabeled {
|
||||
if let Some(p) = function_expression.params.iter().find(|p| !p.labeled) {
|
||||
if let Some(ty) = &p.type_ {
|
||||
@ -2313,6 +2359,7 @@ mod test {
|
||||
let args = KwArgs {
|
||||
unlabeled: None,
|
||||
labeled,
|
||||
errors: Vec::new(),
|
||||
};
|
||||
let exec_ctxt = ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
@ -2552,4 +2599,30 @@ a = foo()
|
||||
|
||||
parse_execute(program).await.unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_sensible_error_when_missing_equals_in_kwarg() {
|
||||
for (i, call) in ["f(x=1,y)", "f(x=1,3,z)", "f(x=1,y,z=1)", "f(x=1, 3 + 4, z=1)"]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let program = format!(
|
||||
"fn foo() {{ return 0 }}
|
||||
y = 42
|
||||
z = 0
|
||||
fn f(x, y, z) {{ return 0 }}
|
||||
{call}"
|
||||
);
|
||||
let err = parse_execute(&program).await.unwrap_err();
|
||||
let msg = err.message();
|
||||
assert!(
|
||||
msg.contains("This argument needs a label, but it doesn't have one"),
|
||||
"failed test {i}: {msg}"
|
||||
);
|
||||
assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
|
||||
if i == 0 {
|
||||
assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +488,9 @@ impl CallExpressionKw {
|
||||
}
|
||||
hasher.update(slf.arguments.len().to_ne_bytes());
|
||||
for argument in slf.arguments.iter_mut() {
|
||||
hasher.update(argument.label.compute_digest());
|
||||
if let Some(l) = &mut argument.label {
|
||||
hasher.update(l.compute_digest());
|
||||
}
|
||||
hasher.update(argument.arg.compute_digest());
|
||||
}
|
||||
});
|
||||
|
@ -460,10 +460,12 @@ impl Node<Program> {
|
||||
crate::walk::Node::CallExpressionKw(call) => {
|
||||
if call.inner.callee.inner.name.inner.name == "appearance" {
|
||||
for arg in &call.arguments {
|
||||
if arg.label.inner.name == "color" {
|
||||
// Get the value of the argument.
|
||||
if let Expr::Literal(literal) = &arg.arg {
|
||||
add_color(literal);
|
||||
if let Some(l) = &arg.label {
|
||||
if l.inner.name == "color" {
|
||||
// Get the value of the argument.
|
||||
if let Expr::Literal(literal) = &arg.arg {
|
||||
add_color(literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1872,7 +1874,7 @@ pub struct CallExpressionKw {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct LabeledArg {
|
||||
pub label: Node<Identifier>,
|
||||
pub label: Option<Node<Identifier>>,
|
||||
pub arg: Expr,
|
||||
}
|
||||
|
||||
@ -1917,7 +1919,7 @@ impl CallExpressionKw {
|
||||
self.unlabeled
|
||||
.iter()
|
||||
.map(|e| (None, e))
|
||||
.chain(self.arguments.iter().map(|arg| (Some(&arg.label), &arg.arg)))
|
||||
.chain(self.arguments.iter().map(|arg| (arg.label.as_ref(), &arg.arg)))
|
||||
}
|
||||
|
||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||
|
@ -2714,13 +2714,18 @@ fn pipe_sep(i: &mut TokenSlice) -> PResult<()> {
|
||||
}
|
||||
|
||||
fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
|
||||
separated_pair(
|
||||
terminated(nameable_identifier, opt(whitespace)),
|
||||
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
|
||||
(
|
||||
opt((
|
||||
terminated(nameable_identifier, opt(whitespace)),
|
||||
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
|
||||
)),
|
||||
expression,
|
||||
)
|
||||
.map(|(label, arg)| LabeledArg { label, arg })
|
||||
.parse_next(i)
|
||||
.map(|(label, arg)| LabeledArg {
|
||||
label: label.map(|(l, _)| l),
|
||||
arg,
|
||||
})
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// A type of a function argument.
|
||||
@ -3040,6 +3045,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum ArgPlace {
|
||||
NonCode(Node<NonCodeNode>),
|
||||
@ -3068,24 +3074,17 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
||||
}
|
||||
ArgPlace::UnlabeledArg(arg) => {
|
||||
let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
|
||||
let err = if followed_by_equals {
|
||||
ErrMode::Cut(
|
||||
if followed_by_equals {
|
||||
return Err(ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
SourceRange::from(arg),
|
||||
"This argument has a label, but no value. Put some value after the equals sign",
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
));
|
||||
} else {
|
||||
ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
SourceRange::from(arg),
|
||||
"This argument needs a label, but it doesn't have one",
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
};
|
||||
return Err(err);
|
||||
args.push(LabeledArg { label: None, arg });
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((args, non_code_nodes))
|
||||
@ -3098,7 +3097,9 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
||||
// Validate there aren't any duplicate labels.
|
||||
let mut counted_labels = IndexMap::with_capacity(args.len());
|
||||
for arg in &args {
|
||||
*counted_labels.entry(&arg.label.inner.name).or_insert(0) += 1;
|
||||
if let Some(l) = &arg.label {
|
||||
*counted_labels.entry(&l.inner.name).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
if let Some((duplicated, n)) = counted_labels.iter().find(|(_label, n)| n > &&1) {
|
||||
let msg = format!(
|
||||
@ -4923,27 +4924,6 @@ bar = 1
|
||||
crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensible_error_when_missing_equals_in_kwarg() {
|
||||
for (i, program) in ["f(x=1,y)", "f(x=1,y,z)", "f(x=1,y,z=1)", "f(x=1, y, z=1)"]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
|
||||
let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
|
||||
let cause = err.inner().cause.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
cause.message, "This argument needs a label, but it doesn't have one",
|
||||
"failed test {i}: {program}"
|
||||
);
|
||||
assert_eq!(
|
||||
cause.source_range.start(),
|
||||
program.find("y").unwrap(),
|
||||
"failed test {i}: {program}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensible_error_when_missing_rhs_of_kw_arg() {
|
||||
for (i, program) in ["f(x, y=)"].into_iter().enumerate() {
|
||||
|
@ -62,6 +62,7 @@ pub struct KwArgs {
|
||||
pub unlabeled: Option<Arg>,
|
||||
/// Labeled args.
|
||||
pub labeled: IndexMap<String, Arg>,
|
||||
pub errors: Vec<Arg>,
|
||||
}
|
||||
|
||||
impl KwArgs {
|
||||
|
@ -88,6 +88,7 @@ async fn call_map_closure(
|
||||
let kw_args = KwArgs {
|
||||
unlabeled: Some(Arg::new(input, source_range)),
|
||||
labeled: Default::default(),
|
||||
errors: Vec::new(),
|
||||
};
|
||||
let args = Args::new_kw(
|
||||
kw_args,
|
||||
@ -233,6 +234,7 @@ async fn call_reduce_closure(
|
||||
let kw_args = KwArgs {
|
||||
unlabeled: Some(Arg::new(elem, source_range)),
|
||||
labeled,
|
||||
errors: Vec::new(),
|
||||
};
|
||||
let reduce_fn_args = Args::new_kw(
|
||||
kw_args,
|
||||
|
@ -430,6 +430,7 @@ async fn make_transform<T: GeometryTrait>(
|
||||
let kw_args = KwArgs {
|
||||
unlabeled: Some(Arg::new(repetition_num, source_range)),
|
||||
labeled: Default::default(),
|
||||
errors: Vec::new(),
|
||||
};
|
||||
let transform_fn_args = Args::new_kw(
|
||||
kw_args,
|
||||
|
@ -405,9 +405,13 @@ impl CallExpressionKw {
|
||||
|
||||
impl LabeledArg {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
|
||||
let label = &self.label.name;
|
||||
let arg = self.arg.recast(options, indentation_level, ctxt);
|
||||
format!("{label} = {arg}")
|
||||
let mut result = String::new();
|
||||
if let Some(l) = &self.label {
|
||||
result.push_str(&l.name);
|
||||
result.push_str(" = ");
|
||||
}
|
||||
result.push_str(&self.arg.recast(options, indentation_level, ctxt));
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,8 +77,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"type": "move_path_pen",
|
||||
"path": "[uuid]",
|
||||
"to": {
|
||||
"x": -10.0,
|
||||
"y": -10.0,
|
||||
"x": -8.0,
|
||||
"y": -8.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
@ -106,8 +106,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 10.0,
|
||||
"y": -10.0,
|
||||
"x": 12.0,
|
||||
"y": -8.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": false
|
||||
@ -123,8 +123,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 10.0,
|
||||
"y": 10.0,
|
||||
"x": 12.0,
|
||||
"y": 12.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": false
|
||||
@ -140,8 +140,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": -10.0,
|
||||
"y": 10.0,
|
||||
"x": -8.0,
|
||||
"y": 12.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": false
|
||||
@ -412,8 +412,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"size": 60.0,
|
||||
"clobber": false,
|
||||
@ -439,8 +439,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"adjust_camera": false,
|
||||
"planar_normal": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
"y": -1.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -453,8 +453,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"segment": {
|
||||
"type": "arc",
|
||||
"center": {
|
||||
"x": 2.0,
|
||||
"y": 2.0
|
||||
"x": 5.0,
|
||||
"y": 5.0
|
||||
},
|
||||
"radius": 2.0,
|
||||
"start": {
|
||||
@ -476,8 +476,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"type": "move_path_pen",
|
||||
"path": "[uuid]",
|
||||
"to": {
|
||||
"x": 4.0,
|
||||
"y": 2.0,
|
||||
"x": 7.0,
|
||||
"y": 5.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
@ -507,8 +507,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"adjust_camera": false,
|
||||
"planar_normal": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
"y": -1.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -518,7 +518,7 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"command": {
|
||||
"type": "extrude",
|
||||
"target": "[uuid]",
|
||||
"distance": 5.0,
|
||||
"distance": 34.0,
|
||||
"faces": null,
|
||||
"opposite": "None"
|
||||
}
|
||||
@ -594,6 +594,30 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "set_object_transform",
|
||||
"object_id": "[uuid]",
|
||||
"transforms": [
|
||||
{
|
||||
"translate": {
|
||||
"property": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 3.14
|
||||
},
|
||||
"set": false,
|
||||
"is_local": true
|
||||
},
|
||||
"rotate_rpy": null,
|
||||
"rotate_angle_axis": null,
|
||||
"scale": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
|
@ -16,8 +16,8 @@ flowchart LR
|
||||
1["Plane<br>[27, 44, 0]"]
|
||||
2["Plane<br>[372, 389, 0]"]
|
||||
12["Sweep Extrusion<br>[306, 326, 0]"]
|
||||
13["Sweep Extrusion<br>[436, 455, 0]"]
|
||||
14["CompositeSolid Subtract<br>[468, 504, 0]"]
|
||||
13["Sweep Extrusion<br>[436, 456, 0]"]
|
||||
14["CompositeSolid Subtract<br>[494, 530, 0]"]
|
||||
15[Wall]
|
||||
16[Wall]
|
||||
17[Wall]
|
||||
|
@ -765,24 +765,24 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "0",
|
||||
"raw": "2",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 0.0,
|
||||
"value": 2.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
},
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "0",
|
||||
"raw": "2",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 0.0,
|
||||
"value": 2.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
}
|
||||
@ -867,7 +867,7 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
|
||||
"name": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "XY",
|
||||
"name": "XZ",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
@ -894,24 +894,24 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "2",
|
||||
"raw": "5",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 2.0,
|
||||
"value": 5.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
},
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "2",
|
||||
"raw": "5",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 2.0,
|
||||
"value": 5.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
}
|
||||
@ -981,12 +981,12 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
|
||||
"arg": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "5",
|
||||
"raw": "34",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 5.0,
|
||||
"value": 34.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
}
|
||||
@ -1013,6 +1013,53 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
|
||||
"type": "CallExpressionKw",
|
||||
"type": "CallExpressionKw",
|
||||
"unlabeled": null
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"type": "LabeledArg",
|
||||
"label": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "z",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"arg": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "3.14",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 3.14,
|
||||
"suffix": "None"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"abs_path": false,
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "translate",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 0,
|
||||
"type": "Name"
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "CallExpressionKw",
|
||||
"type": "CallExpressionKw",
|
||||
"unlabeled": null
|
||||
}
|
||||
],
|
||||
"commentStart": 0,
|
||||
|
@ -8,9 +8,10 @@ fn cube(center) {
|
||||
|> extrude(length = 10)
|
||||
}
|
||||
|
||||
part001 = cube(center = [0, 0])
|
||||
part002 = startSketchOn(XY)
|
||||
|> circle(center = [2, 2], radius = 2)
|
||||
|> extrude(length = 5)
|
||||
part001 = cube(center = [2, 2])
|
||||
part002 = startSketchOn(XZ)
|
||||
|> circle(center = [5, 5], radius = 2)
|
||||
|> extrude(length = 34)
|
||||
|> translate(z = 3.14)
|
||||
|
||||
fullPart = subtract([part001], tools=[part002])
|
||||
|
@ -62,7 +62,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
|
||||
"value": [
|
||||
{
|
||||
"type": "Number",
|
||||
"value": 0.0,
|
||||
"value": 2.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
@ -75,7 +75,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
{
|
||||
"type": "Number",
|
||||
"value": 0.0,
|
||||
"value": 2.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
@ -112,7 +112,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
|
||||
"length": {
|
||||
"value": {
|
||||
"type": "Number",
|
||||
"value": 5.0,
|
||||
"value": 34.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
|
@ -53,13 +53,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
10.0,
|
||||
-10.0
|
||||
12.0,
|
||||
-8.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -72,13 +72,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
10.0,
|
||||
-10.0
|
||||
12.0,
|
||||
-8.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
10.0,
|
||||
10.0
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -91,13 +91,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
10.0,
|
||||
10.0
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
-10.0,
|
||||
10.0
|
||||
-8.0,
|
||||
12.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -110,13 +110,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
-10.0,
|
||||
10.0
|
||||
-8.0,
|
||||
12.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -156,12 +156,12 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
"start": {
|
||||
"from": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"to": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"units": {
|
||||
"type": "Mm"
|
||||
@ -233,13 +233,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
10.0,
|
||||
-10.0
|
||||
12.0,
|
||||
-8.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -252,13 +252,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
10.0,
|
||||
-10.0
|
||||
12.0,
|
||||
-8.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
10.0,
|
||||
10.0
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -271,13 +271,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
10.0,
|
||||
10.0
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
-10.0,
|
||||
10.0
|
||||
-8.0,
|
||||
12.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -290,13 +290,13 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
"from": [
|
||||
-10.0,
|
||||
10.0
|
||||
-8.0,
|
||||
12.0
|
||||
],
|
||||
"tag": null,
|
||||
"to": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"type": "ToPoint",
|
||||
"units": {
|
||||
@ -336,12 +336,12 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
"start": {
|
||||
"from": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"to": [
|
||||
-10.0,
|
||||
-10.0
|
||||
-8.0,
|
||||
-8.0
|
||||
],
|
||||
"units": {
|
||||
"type": "Mm"
|
||||
@ -393,18 +393,18 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
"ccw": true,
|
||||
"center": [
|
||||
2.0,
|
||||
2.0
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"from": [
|
||||
4.0,
|
||||
2.0
|
||||
7.0,
|
||||
5.0
|
||||
],
|
||||
"radius": 2.0,
|
||||
"tag": null,
|
||||
"to": [
|
||||
4.0,
|
||||
2.0
|
||||
7.0,
|
||||
5.0
|
||||
],
|
||||
"type": "Circle",
|
||||
"units": {
|
||||
@ -424,7 +424,7 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
}
|
||||
},
|
||||
"type": "plane",
|
||||
"value": "XY",
|
||||
"value": "XZ",
|
||||
"xAxis": {
|
||||
"x": 1.0,
|
||||
"y": 0.0,
|
||||
@ -435,8 +435,8 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
"yAxis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0,
|
||||
"units": {
|
||||
"type": "Unknown"
|
||||
}
|
||||
@ -444,12 +444,12 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
},
|
||||
"start": {
|
||||
"from": [
|
||||
4.0,
|
||||
2.0
|
||||
7.0,
|
||||
5.0
|
||||
],
|
||||
"to": [
|
||||
4.0,
|
||||
2.0
|
||||
7.0,
|
||||
5.0
|
||||
],
|
||||
"units": {
|
||||
"type": "Mm"
|
||||
@ -466,7 +466,7 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
|
||||
"type": "Mm"
|
||||
}
|
||||
},
|
||||
"height": 5.0,
|
||||
"height": 34.0,
|
||||
"startCapId": "[uuid]",
|
||||
"endCapId": "[uuid]",
|
||||
"units": {
|
||||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 74 KiB |
@ -12,9 +12,10 @@ fn cube(center) {
|
||||
|> extrude(length = 10)
|
||||
}
|
||||
|
||||
part001 = cube(center = [0, 0])
|
||||
part002 = startSketchOn(XY)
|
||||
|> circle(center = [2, 2], radius = 2)
|
||||
|> extrude(length = 5)
|
||||
part001 = cube(center = [2, 2])
|
||||
part002 = startSketchOn(XZ)
|
||||
|> circle(center = [5, 5], radius = 2)
|
||||
|> extrude(length = 34)
|
||||
|> translate(z = 3.14)
|
||||
|
||||
fullPart = subtract([part001], tools = [part002])
|
||||
|
@ -24,10 +24,18 @@ function errorMessage(error: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
function stackTraceMessage(error: unknown): string {
|
||||
if (error !== undefined && error instanceof Error) {
|
||||
return error.stack || ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** Generate a GitHub issue URL from the error */
|
||||
function generateToUrl(error: unknown) {
|
||||
const title: string = 'An unexpected error occurred'
|
||||
const body = errorMessage(error)
|
||||
const newLine = '%0A'
|
||||
const body = `${errorMessage(error)} ${newLine} >${stackTraceMessage(error)} ${newLine}`
|
||||
const result = `https://github.com/KittyCAD/modeling-app/issues/new?title=${title}&body=${body}`
|
||||
return result
|
||||
}
|
||||
@ -43,9 +51,12 @@ export const ErrorPage = () => {
|
||||
<h1 className="text-4xl mb-8 font-bold" data-testid="unexpected-error">
|
||||
An unexpected error occurred
|
||||
</h1>
|
||||
<p className="mb-8 w-full overflow-aut">
|
||||
<p className="mb-8 w-full overflow-auto">
|
||||
<>{errorMessage(error)}</>
|
||||
</p>
|
||||
<p className="mb-8 w-full overflow-auto">
|
||||
<>{stackTraceMessage(error)}</>
|
||||
</p>
|
||||
<div className="flex justify-between gap-2 mt-6">
|
||||
{isDesktop() && (
|
||||
<ActionButton
|
||||
|
@ -14,11 +14,7 @@ import Tooltip from '@src/components/Tooltip'
|
||||
import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath'
|
||||
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
||||
import { PATHS } from '@src/lib/paths'
|
||||
import {
|
||||
APP_VERSION,
|
||||
IS_NIGHTLY_OR_DEBUG,
|
||||
getReleaseUrl,
|
||||
} from '@src/routes/utils'
|
||||
import { APP_VERSION, getReleaseUrl } from '@src/routes/utils'
|
||||
|
||||
import { billingActor } from '@src/lib/singletons'
|
||||
|
||||
@ -39,22 +35,23 @@ export function LowerRightControls({
|
||||
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||
{children}
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
{IS_NIGHTLY_OR_DEBUG && (
|
||||
<Popover className="relative">
|
||||
<Popover.Button className="p-0 !border-transparent">
|
||||
<BillingRemaining
|
||||
mode={BillingRemainingMode.ProgressBarFixed}
|
||||
billingActor={billingActor}
|
||||
/>
|
||||
<Tooltip position="top" contentClassName="text-xs">
|
||||
Text-to-CAD credits
|
||||
</Tooltip>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch rounded-lg shadow-lg text-sm">
|
||||
<BillingDialog billingActor={billingActor} />
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
)}
|
||||
<Popover className="relative">
|
||||
<Popover.Button
|
||||
className="p-0 !border-transparent"
|
||||
data-testid="billing-remaining-bar"
|
||||
>
|
||||
<BillingRemaining
|
||||
mode={BillingRemainingMode.ProgressBarFixed}
|
||||
billingActor={billingActor}
|
||||
/>
|
||||
<Tooltip position="top" contentClassName="text-xs">
|
||||
Text-to-CAD credits
|
||||
</Tooltip>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch rounded-lg shadow-lg text-sm">
|
||||
<BillingDialog billingActor={billingActor} />
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(getReleaseUrl())}
|
||||
href={getReleaseUrl()}
|
||||
|
@ -223,7 +223,7 @@ export function mutateKwArg(
|
||||
): boolean | 'no-mutate' {
|
||||
for (let i = 0; i < node.arguments.length; i++) {
|
||||
const arg = node.arguments[i]
|
||||
if (arg.label.name === label) {
|
||||
if (arg.label?.name === label) {
|
||||
if (isLiteralArrayOrStatic(val) && isLiteralArrayOrStatic(arg.arg)) {
|
||||
node.arguments[i].arg = val
|
||||
return true
|
||||
@ -259,7 +259,7 @@ export function mutateKwArgOnly(
|
||||
): boolean {
|
||||
for (let i = 0; i < node.arguments.length; i++) {
|
||||
const arg = node.arguments[i]
|
||||
if (arg.label.name === label) {
|
||||
if (arg.label?.name === label) {
|
||||
node.arguments[i].arg = val
|
||||
return true
|
||||
}
|
||||
@ -273,7 +273,7 @@ Mutates the given node by removing the labeled arguments.
|
||||
*/
|
||||
export function removeKwArgs(labels: string[], node: CallExpressionKw) {
|
||||
for (const label of labels) {
|
||||
const i = node.arguments.findIndex((la) => la.label.name === label)
|
||||
const i = node.arguments.findIndex((la) => la.label?.name === label)
|
||||
if (i == -1) {
|
||||
continue
|
||||
}
|
||||
|
@ -773,7 +773,7 @@ export async function editEdgeTreatment(
|
||||
|
||||
// find the index of an argument to update
|
||||
const index = edgeTreatmentCall.node.arguments.findIndex(
|
||||
(arg) => arg.label.name === parameterName
|
||||
(arg) => arg.label?.name === parameterName
|
||||
)
|
||||
|
||||
// create a new argument with the updated value
|
||||
|
@ -500,7 +500,7 @@ function modifyAstWithTagForCapFace(
|
||||
|
||||
// Check for existing tag with this parameter name
|
||||
const existingTag = callExp.node.arguments.find(
|
||||
(arg) => arg.label.name === tagParamName
|
||||
(arg) => arg.label?.name === tagParamName
|
||||
)
|
||||
|
||||
if (existingTag && existingTag.arg.type === 'TagDeclarator') {
|
||||
|
@ -190,7 +190,10 @@ const commonConstraintInfoHelper = (
|
||||
if (lengthishIndex === undefined) {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
const lengthKey = callExp.arguments[lengthishIndex].label.name
|
||||
const lengthKey = callExp.arguments[lengthishIndex].label?.name
|
||||
if (lengthKey === undefined) {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
const lengthVal = callExp.arguments[lengthishIndex].arg
|
||||
// Note: The order of keys here matters.
|
||||
// Always assumes the angle was the first param, and then the length followed.
|
||||
@ -1057,7 +1060,7 @@ export const tangentialArc: SketchLineHelperKw = {
|
||||
}
|
||||
|
||||
for (const arg of callExpression.arguments) {
|
||||
if (arg.label.name !== ARG_END_ABSOLUTE && arg.label.name !== ARG_TAG) {
|
||||
if (arg.label?.name !== ARG_END_ABSOLUTE && arg.label?.name !== ARG_TAG) {
|
||||
console.debug(
|
||||
'Trying to edit unsupported tangentialArc keyword arguments; skipping'
|
||||
)
|
||||
|
@ -1375,12 +1375,12 @@ export function removeSingleConstraint({
|
||||
|
||||
// 1. Filter out any existing tag argument since it will be handled separately
|
||||
const filteredArgs = existingArgs.filter(
|
||||
(arg) => arg.label.name !== ARG_TAG
|
||||
(arg) => arg.label?.name !== ARG_TAG
|
||||
)
|
||||
|
||||
// 2. Map through the args, replacing only the one we want to change
|
||||
const labeledArgs = filteredArgs.map((arg) => {
|
||||
if (arg.label.name === toReplace) {
|
||||
if (arg.label?.name === toReplace) {
|
||||
// Find the raw value to use for the argument being replaced
|
||||
const rawArgVersion = rawArgs.find(
|
||||
(a) => a.type === 'labeledArg' && a.key === toReplace
|
||||
@ -1430,7 +1430,7 @@ export function removeSingleConstraint({
|
||||
const labeledArgs = existingArgs.map((arg) => {
|
||||
// Only modify the specific argument that matches the targeted key
|
||||
if (
|
||||
arg.label.name === targetKey &&
|
||||
arg.label?.name === targetKey &&
|
||||
arg.arg.type === 'ArrayExpression'
|
||||
) {
|
||||
// We're dealing with an array expression within a labeled argument
|
||||
@ -1560,7 +1560,7 @@ export function removeSingleConstraint({
|
||||
arrayInput[inputToReplace.key][inputToReplace.index] =
|
||||
rawLiteralArrayInObjectExpr
|
||||
let existingKwgForKey = kwArgInput.find(
|
||||
(kwArg) => kwArg.label.name === currentArg.key
|
||||
(kwArg) => kwArg.label?.name === currentArg.key
|
||||
)
|
||||
if (!existingKwgForKey) {
|
||||
existingKwgForKey = createLabeledArg(
|
||||
@ -1586,7 +1586,7 @@ export function removeSingleConstraint({
|
||||
if (!arrayInput[currentArg.key]) arrayInput[currentArg.key] = []
|
||||
arrayInput[currentArg.key][currentArg.index] = currentArgExpr
|
||||
let existingKwgForKey = kwArgInput.find(
|
||||
(kwArg) => kwArg.label.name === currentArg.key
|
||||
(kwArg) => kwArg.label?.name === currentArg.key
|
||||
)
|
||||
if (!existingKwgForKey) {
|
||||
existingKwgForKey = createLabeledArg(
|
||||
@ -2190,7 +2190,7 @@ export function getConstraintLevelFromSourceRange(
|
||||
}
|
||||
const arg = findKwArgAny(DETERMINING_ARGS, nodeMeta.node)
|
||||
if (arg === undefined) {
|
||||
const argStr = nodeMeta.node.arguments.map((a) => a.label.name)
|
||||
const argStr = nodeMeta.node.arguments.map((a) => a.label?.name)
|
||||
return new Error(
|
||||
`call to expression ${name} has unexpected args: ${argStr} `
|
||||
)
|
||||
|
@ -136,7 +136,7 @@ export function findKwArg(
|
||||
call: CallExpressionKw
|
||||
): Expr | undefined {
|
||||
return call?.arguments?.find((arg) => {
|
||||
return arg.label.name === label
|
||||
return arg.label?.name === label
|
||||
})?.arg
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ export function findKwArgWithIndex(
|
||||
call: CallExpressionKw
|
||||
): { expr: Expr; argIndex: number } | undefined {
|
||||
const index = call.arguments.findIndex((arg) => {
|
||||
return arg.label.name === label
|
||||
return arg.label?.name === label
|
||||
})
|
||||
return index >= 0
|
||||
? { expr: call.arguments[index].arg, argIndex: index }
|
||||
@ -164,7 +164,7 @@ export function findKwArgAny(
|
||||
call: CallExpressionKw
|
||||
): Expr | undefined {
|
||||
return call.arguments.find((arg) => {
|
||||
return labels.includes(arg.label.name)
|
||||
return labels.includes(arg.label?.name || '')
|
||||
})?.arg
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ export function findKwArgAnyIndex(
|
||||
call: CallExpressionKw
|
||||
): number | undefined {
|
||||
const index = call.arguments.findIndex((arg) => {
|
||||
return labels.includes(arg.label.name)
|
||||
return labels.includes(arg.label?.name || '')
|
||||
})
|
||||
if (index == -1) {
|
||||
return undefined
|
||||
|
@ -60,7 +60,7 @@ export async function retrieveArgFromPipedCallExpression(
|
||||
name: string
|
||||
): Promise<KclCommandValue | undefined> {
|
||||
const arg = callExpression.arguments.find(
|
||||
(a) => a.label.type === 'Identifier' && a.label.name === name
|
||||
(a) => a.label?.type === 'Identifier' && a.label?.name === name
|
||||
)
|
||||
if (
|
||||
arg?.type === 'LabeledArg' &&
|
||||
|
@ -41,7 +41,9 @@ export async function refreshPage(method = 'UI button') {
|
||||
* Get all labels for a keyword call expression.
|
||||
*/
|
||||
export function allLabels(callExpression: CallExpressionKw): string[] {
|
||||
return callExpression.arguments.map((a) => a.label.name)
|
||||
return callExpression.arguments
|
||||
.map((a) => a.label?.name)
|
||||
.filter((a) => a !== undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { IS_NIGHTLY_OR_DEBUG } from '@src/routes/utils'
|
||||
import type { FormEvent, HTMLProps } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
@ -351,13 +350,11 @@ const Home = () => {
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="flex flex-col">
|
||||
{IS_NIGHTLY_OR_DEBUG && (
|
||||
<li className="contents">
|
||||
<div className="my-2">
|
||||
<BillingDialog billingActor={billingActor} />
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
<li className="contents">
|
||||
<div className="my-2">
|
||||
<BillingDialog billingActor={billingActor} />
|
||||
</div>
|
||||
</li>
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
|
@ -79,7 +79,7 @@ function Welcome() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 grid items-end justify-center p-2">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 grid items-end justify-center p-2">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Welcome to Zoo Design Studio</h1>
|
||||
<p className="my-4">
|
||||
@ -149,7 +149,7 @@ function Toolbar() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] grid items-start justify-center p-16">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] grid items-start justify-center p-16">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">This is the toolbar</h1>
|
||||
<p className="my-4">
|
||||
@ -170,7 +170,7 @@ function TextToCad() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 grid items-start justify-center p-16">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 grid items-start justify-center p-16">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Text-to-CAD</h1>
|
||||
<p className="my-4">
|
||||
@ -229,7 +229,7 @@ function TextToCadPrompt() {
|
||||
useAdvanceOnboardingOnFormSubmit(thisOnboardingStatus)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Text-to-CAD prompt</h1>
|
||||
<p className="my-4">
|
||||
@ -325,7 +325,7 @@ function PromptToEdit() {
|
||||
useAdvanceOnboardingOnFormSubmit(thisOnboardingStatus)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 grid items-center justify-center p-16">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 grid items-center justify-center p-16">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">Modify with Zoo Text-to-CAD</h1>
|
||||
<p className="my-4">
|
||||
@ -381,7 +381,7 @@ function PromptToEditPrompt() {
|
||||
useAdvanceOnboardingOnFormSubmit(thisOnboardingStatus)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<OnboardingCard className="pointer-events-auto">
|
||||
<h1 className="text-xl font-bold">Modify with Text-to-CAD prompt</h1>
|
||||
{!isReady && (
|
||||
@ -429,7 +429,7 @@ function PromptToEditResult() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] p-8 grid justify-center items-end">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] p-8 grid justify-center items-end">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">Result</h1>
|
||||
<p className="my-4">
|
||||
@ -462,7 +462,7 @@ function OnboardingConclusion() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-16 grid justify-center items-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-16 grid justify-center items-center">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Download the desktop app</h1>
|
||||
<p className="my-4">
|
||||
|
@ -78,7 +78,7 @@ function Welcome() {
|
||||
}, [loaderData?.project?.name])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 grid items-end justify-center p-2">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 grid items-end justify-center p-2">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Welcome to Zoo Design Studio</h1>
|
||||
<p className="my-4">
|
||||
@ -140,7 +140,7 @@ function Toolbar() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] grid items-start justify-center p-16">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] grid items-start justify-center p-16">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">This is the toolbar</h1>
|
||||
<p className="my-4">
|
||||
@ -161,7 +161,7 @@ function TextToCad() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 grid items-start justify-center p-16">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 grid items-start justify-center p-16">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Text-to-CAD</h1>
|
||||
<p className="my-4">
|
||||
@ -220,7 +220,7 @@ function TextToCadPrompt() {
|
||||
useAdvanceOnboardingOnFormSubmit(thisOnboardingStatus, 'desktop')
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<OnboardingCard className="pointer-events-auto">
|
||||
<h1 className="text-xl font-bold">Text-to-CAD prompt</h1>
|
||||
<p className="my-4">
|
||||
@ -266,7 +266,7 @@ function FeatureTreePane() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] p-8 grid justify-center items-end">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] p-8 grid justify-center items-end">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">CPU Fan Housing</h1>
|
||||
<p className="my-4">
|
||||
@ -297,7 +297,7 @@ function CodePane() {
|
||||
useOnboardingPanes(['code'])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-8 grid justify-center items-end">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-8 grid justify-center items-end">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">KCL Code</h1>
|
||||
<p className="my-4">
|
||||
@ -328,7 +328,7 @@ function ProjectPane() {
|
||||
useOnboardingPanes(['files'])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-8 grid justify-center items-end">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-8 grid justify-center items-end">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">Files Pane</h1>
|
||||
<p className="my-4">
|
||||
@ -355,7 +355,7 @@ function OtherPanes() {
|
||||
useOnboardingPanes(['logs', 'variables'])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-8 grid justify-center items-end">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-8 grid justify-center items-end">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">Other panes</h1>
|
||||
<p className="my-4">
|
||||
@ -400,7 +400,7 @@ function PromptToEdit() {
|
||||
useAdvanceOnboardingOnFormSubmit(thisOnboardingStatus, 'desktop')
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-8 grid justify-center items-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-8 grid justify-center items-center">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">Modify with Zoo Text-to-CAD</h1>
|
||||
<p className="my-4">
|
||||
@ -456,7 +456,7 @@ function PromptToEditPrompt() {
|
||||
useAdvanceOnboardingOnFormSubmit(thisOnboardingStatus, 'desktop')
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] grid items-center justify-center">
|
||||
<OnboardingCard className="pointer-events-auto">
|
||||
<h1 className="text-xl font-bold">Modify with Text-to-CAD prompt</h1>
|
||||
{!isReady && (
|
||||
@ -510,7 +510,7 @@ function PromptToEditResult() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] p-8 grid justify-center items-end">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-[99] p-8 grid justify-center items-end">
|
||||
<OnboardingCard className="col-start-3 col-span-2">
|
||||
<h1 className="text-xl font-bold">Result</h1>
|
||||
<p className="my-4">
|
||||
@ -548,7 +548,7 @@ function Imports() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-16 flex flex-col gap-8 items-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-16 flex flex-col gap-8 items-center">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Add file(s) to project</h1>
|
||||
<p className="my-4">
|
||||
@ -579,7 +579,7 @@ function Exports() {
|
||||
useOnboardingPanes()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-16 grid justify-start items-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-16 grid justify-start items-center">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Exporting</h1>
|
||||
<p className="my-4">
|
||||
@ -607,7 +607,7 @@ function OnboardingConclusion() {
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 p-16 grid justify-center items-center">
|
||||
<div className="cursor-not-allowed fixed inset-0 z-50 p-16 grid justify-center items-center">
|
||||
<OnboardingCard>
|
||||
<h1 className="text-xl font-bold">Time to start building</h1>
|
||||
<p className="my-4">
|
||||
|
@ -54,7 +54,7 @@ export const OnboardingCard = ({
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={`relative max-w-3xl min-w-80 bg-chalkboard-10 dark:bg-chalkboard-90 py-6 px-8 rounded border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg ${className || ''}`}
|
||||
className={`relative max-w-3xl min-w-80 cursor-auto bg-chalkboard-10 dark:bg-chalkboard-90 py-6 px-8 rounded border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg ${className || ''}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|