Compare commits

...

10 Commits

Author SHA1 Message Date
6e10f75ff6 KCL executor: accept and send 'replay' flag, track session data (#3646)
Part of https://github.com/KittyCAD/engine/issues/2504:

The engine accepts this 'replay' flag now, so, accept it too and send it up to the engine.

Part of https://github.com/KittyCAD/cli/issues/847

The engine sends 'session data' now (like the API Call ID). The CLI executes KCL using this executor, and would like to get the session data after execution.
2024-08-23 17:40:30 -05:00
03e289af20 Fix Commands button to show correct shortcut on Windows and Linux (#3625)
* Fix Commands button to show correct shortcut

* Fix onboarding to use the same shortcut reference

* Rename test file to be more general

* Add test for commands button text

* Remove outdated reference to Ctrl+/

* Change shortcut separator to be + and no spaces

* Add JSDocs and improve comments

* Add unit tests

* Change control modifier to regular ASCII caret

* Add browser test and fix platform detection

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Add useful debug info to the error message

* Fix to display metaKey as Super on Linux

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)"

This reverts commit f8da90d5d2.

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Approve snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-08-23 16:20:22 -04:00
efc140abbf Test: Can load a file with CRLF line endings (#3636)
* Test: Can load a file with CRLF line endings #3616

* first arg stuff??

* Fix paths in playwright for windows

* Fix line ending replace on windows

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

---------

Co-authored-by: Adam Sunderland <iterion@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2024-08-23 13:51:30 -04:00
4dfad19b7e add hollow (#3642)
* add hollow

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-08-23 16:57:02 +00:00
e56c634b35 Fix existing: Zoom should be consistent when exiting or entering sketches (#3638)
#3637
2024-08-23 16:10:46 +00:00
00292abc98 Bump @babel/preset-env from 7.25.3 to 7.25.4 (#3630)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.3 to 7.25.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.4/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 09:00:56 -07:00
483d6903d6 Bump @kittycad/lib from 2.0.0 to 2.0.1 (#3631)
Bumps [@kittycad/lib](https://github.com/KittyCAD/kittycad.ts) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/KittyCAD/kittycad.ts/releases)
- [Commits](https://github.com/KittyCAD/kittycad.ts/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: "@kittycad/lib"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 09:00:48 -07:00
3780996374 Fix opening bottom right version link in external browser (#3633) 2024-08-23 09:00:18 -07:00
2fde71228a Bump quote from 1.0.36 to 1.0.37 in /src/wasm-lib (#3628)
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.36 to 1.0.37.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.36...1.0.37)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 08:48:53 -07:00
5cd8ab3812 Enable all disabled win32 tests (#3618)
* Enable all disabled win32 tests

* Skip one test
2024-08-23 10:50:40 -04:00
68 changed files with 6808 additions and 456 deletions

835
docs/kcl/hollow.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -44,6 +44,7 @@ layout: manual
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/helix)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`import`](kcl/import)
* [`inch`](kcl/inch)
* [`int`](kcl/int)

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,39 @@ test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test.describe('Electron user sidebar menu tests', () => {
test.describe('Electron app header tests', () => {
test(
'Open Command Palette button has correct shortcut',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async () => {},
})
await page.setViewportSize({ width: 1200, height: 500 })
// No space before the shortcut since it checks textContent.
let text
switch (process.platform) {
case 'darwin':
text = 'Commands⌘K'
break
case 'win32':
text = 'CommandsCtrl+K'
break
default: // 'linux' etc.
text = 'CommandsCtrl+K'
break
}
const commandsButton = page.getByRole('button', { name: 'Commands' })
await expect(commandsButton).toBeVisible()
await expect(commandsButton).toHaveText(text)
await electronApp.close()
}
)
test(
'User settings has correct shortcut',
{ tag: '@electron' },

View File

@ -1,6 +1,13 @@
import { test, expect } from '@playwright/test'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { join } from 'path'
import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises'
@ -223,26 +230,24 @@ test(
'Opening multiple panes persists when switching projects',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
// Setup multiple projects.
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate')
const bracketDir = join(dir, 'bracket')
await Promise.all([
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
fsp.mkdir(routerTemplateDir, { recursive: true }),
fsp.mkdir(bracketDir, { recursive: true }),
])
await Promise.all([
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/router-template-slate/main.kcl`
executorInputPath('router-template-slate.kcl'),
join(routerTemplateDir, 'main.kcl')
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
),
])
},

View File

@ -1,5 +1,11 @@
import { test, expect } from '@playwright/test'
import { getUtils, setupElectron, tearDown } from './test-utils'
import { join } from 'path'
import {
getUtils,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import fsp from 'fs/promises'
test.afterEach(async ({ page }, testInfo) => {
@ -10,22 +16,19 @@ test(
'export works on the first try',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await Promise.all([fsp.mkdir(`${dir}/bracket`, { recursive: true })])
const bracketDir = join(dir, 'bracket')
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
await Promise.all([
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/bracket/other.kcl`
executorInputPath('router-template-slate.kcl'),
join(bracketDir, 'other.kcl')
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
),
])
},

View File

@ -1,5 +1,6 @@
import { test, expect } from '@playwright/test'
import { setupElectron, tearDown } from './test-utils'
import { setupElectron, tearDown, executorInputPath } from './test-utils'
import { join } from 'path'
import fsp from 'fs/promises'
test.afterEach(async ({ page }, testInfo) => {
@ -10,17 +11,14 @@ test(
'When machine-api server not found butt is disabled and shows the reason',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -58,17 +56,14 @@ test(
'When machine-api server not found home screen & project status shows the reason',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})

View File

@ -1,6 +1,13 @@
import { test, expect } from '@playwright/test'
import { join } from 'path'
import fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths'
import {
@ -347,17 +354,14 @@ test(
'Restarting onboarding on desktop takes one attempt',
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/router-template-slate/main.kcl`
executorInputPath('router-template-slate.kcl'),
join(routerTemplateDir, 'main.kcl')
)
},
})

View File

@ -1,6 +1,7 @@
import { test, expect, Page } from '@playwright/test'
import {
doExport,
executorInputPath,
getUtils,
isOutOfViewInScrollContainer,
Paths,
@ -49,17 +50,11 @@ test(
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'bracket'), { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs',
'focusrite_scarlett_mounting_braket.kcl'
),
join(dir, 'bracket', 'main.kcl')
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -99,14 +94,7 @@ test(
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
await fsp.copyFile(
join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs',
'broken-code-test.kcl'
),
executorInputPath('broken-code-test.kcl'),
join(dir, 'broken-code', 'main.kcl')
)
},
@ -143,17 +131,14 @@ test.describe('Can export from electron app', () => {
`Can export using ${method}`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -605,6 +590,48 @@ test(
}
)
test(
'Can load a file with CRLF line endings',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true })
const file = await fsp.readFile(
executorInputPath('router-template-slate.kcl'),
'utf-8'
)
// Replace both \r optionally so we don't end up with \r\r\n
const fileWithCRLF = file.replace(/\r?\n/g, '\r\n')
await fsp.writeFile(
join(routerTemplateDir, 'main.kcl'),
fileWithCRLF,
'utf-8'
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await page.getByText('router-template-slate').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(u.codeLocator).toContainText('routerDiameter')
await expect(u.codeLocator).toContainText('templateGap')
await expect(u.codeLocator).toContainText('minClampingDistance')
await electronApp.close()
}
)
test(
'Can sort projects on home page',
{ tag: '@electron' },
@ -1032,10 +1059,6 @@ test(
'Search projects on desktop home',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const projectData = [
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['basic-cube', 'basic_fillet_cube_end.kcl'],
@ -1050,7 +1073,7 @@ test(
for (const [name, file] of projectData) {
await fsp.mkdir(join(dir, name), { recursive: true })
await fsp.copyFile(
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', file),
executorInputPath(file),
join(dir, name, `main.kcl`)
)
}
@ -1097,14 +1120,11 @@ test(
'file pane is scrollable when there are many files',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
const fileNames = [
'angled_line.kcl',
'basic_fillet_cube_close_opposite.kcl',
@ -1168,8 +1188,8 @@ test(
]
for (const fileName of fileNames) {
await fsp.copyFile(
`src/wasm-lib/tests/executor/inputs/${fileName}`,
`${dir}/testProject/${fileName}`
executorInputPath(fileName),
join(testDir, fileName)
)
}
},
@ -1210,19 +1230,16 @@ test(
'select all in code editor does not actually select all, just what is visible (regression)',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
const name = 'mike_stress_test'
await fsp.mkdir(`${dir}/${name}`, { recursive: true })
const testDir = join(dir, name)
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
`src/wasm-lib/tests/executor/inputs/${name}.kcl`,
`${dir}/${name}/main.kcl`
executorInputPath(`${name}.kcl`),
join(testDir, 'main.kcl')
)
},
})
@ -1320,27 +1337,16 @@ test.describe('Renaming in the file tree', () => {
'A file you have open',
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page, dir } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
const exampleDir = join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs'
)
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'fileToRename.kcl')
)
},
@ -1425,27 +1431,16 @@ test.describe('Renaming in the file tree', () => {
'A file you do not have open',
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page, dir } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
const exampleDir = join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs'
)
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'fileToRename.kcl')
)
},
@ -1527,10 +1522,6 @@ test.describe('Renaming in the file tree', () => {
`A folder you're not inside`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page, dir } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -1538,19 +1529,12 @@ test.describe('Renaming in the file tree', () => {
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
recursive: true,
})
const exampleDir = join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs'
)
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
)
},
@ -1625,11 +1609,6 @@ test.describe('Renaming in the file tree', () => {
`A folder you are inside`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const exampleDir = join('src', 'wasm-lib', 'tests', 'executor', 'inputs')
const { electronApp, page, dir } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -1638,11 +1617,11 @@ test.describe('Renaming in the file tree', () => {
recursive: true,
})
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
)
},
@ -1738,21 +1717,18 @@ test.describe('Deleting files from the file pane', () => {
`when main.kcl exists, navigate to main.kcl`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
`${dir}/testProject/main.kcl`
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/basic_fillet_cube_end.kcl',
`${dir}/testProject/fileToDelete.kcl`
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'fileToDelete.kcl')
)
},
})

View File

@ -1,6 +1,13 @@
import { test, expect, Page } from '@playwright/test'
import { join } from 'path'
import * as fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
import { bracket } from 'lib/exampleKcl'
@ -425,17 +432,14 @@ const sketch001 = startSketchAt([-0, -0])
`Network health indicator only appears in modeling view`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -912,3 +912,7 @@ export async function createProjectAndRenameIt({
await page.getByLabel('checkmark').last().click()
}
export function executorInputPath(fileName: string): string {
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
}

View File

@ -174,168 +174,166 @@ test.describe('Testing Camera Movement', () => {
}, [0, -85, -85])
})
// TODO fixme something is wrong with sketch here
test.fixme(
'Zoom should be consistent when exiting or entering sketches',
async ({ page }) => {
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
// than again for sketching
test('Zoom should be consistent when exiting or entering sketches', async ({
page,
}) => {
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
// than again for sketching
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
// select a plane
await page.mouse.click(700, 325)
let code = `const sketch001 = startSketchOn('XY')`
await expect(u.codeLocator).toHaveText(code)
await u.closeDebugPanel()
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
// move the camera slightly
await page.keyboard.down('Shift')
await page.mouse.move(700, 300)
await page.mouse.down({ button: 'right' })
await page.mouse.move(800, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
let y = 350,
x = 948
await u.canvasLocator.click({ position: { x: 783, y } })
code += `\n |> startProfileAt([8.12, -12.98], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y } })
code += `\n |> line([11.18, 0], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y: 275 } })
code += `\n |> line([0, 6.99], %)`
// await expect(u.codeLocator).toHaveText(code)
// click the line button
await page.getByRole('button', { name: 'line Line', exact: true }).click()
const hoverOverNothing = async () => {
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
await page.mouse.move(700, 325)
await page.waitForTimeout(100)
// select a plane
await page.mouse.click(700, 325)
let code = `const sketch001 = startSketchOn('XY')`
await expect(u.codeLocator).toHaveText(code)
await u.closeDebugPanel()
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
// move the camera slightly
await page.keyboard.down('Shift')
await page.mouse.move(700, 300)
await page.mouse.down({ button: 'right' })
await page.mouse.move(800, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
let y = 350,
x = 948
await u.canvasLocator.click({ position: { x: 783, y } })
code += `\n |> startProfileAt([8.12, -12.98], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y } })
code += `\n |> line([11.18, 0], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y: 275 } })
code += `\n |> line([0, 6.99], %)`
// await expect(u.codeLocator).toHaveText(code)
// click the line button
await page.getByRole('button', { name: 'line Line', exact: true }).click()
const hoverOverNothing = async () => {
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
await page.mouse.move(700, 325)
await page.waitForTimeout(100)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
timeout: 10_000,
})
}
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
// hover over horizontal line
await u.canvasLocator.hover({ position: { x: 800, y } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over vertical line
await u.canvasLocator.hover({ position: { x, y: 325 } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// click exit sketch
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over horizontal line
await page.mouse.move(858, y, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(x, 325)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(857, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
// now click it
await page.mouse.click(857, y)
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
x = 975
y = 468
await page.waitForTimeout(100)
await page.mouse.move(x, 419, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
await page.mouse.move(x, 419)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
timeout: 10_000,
})
}
)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
// hover over horizontal line
await u.canvasLocator.hover({ position: { x: 800, y } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over vertical line
await u.canvasLocator.hover({ position: { x, y: 325 } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// click exit sketch
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over horizontal line
await page.mouse.move(858, y, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(x, 325)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(857, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
// now click it
await page.mouse.click(857, y)
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
x = 975
y = 468
await page.waitForTimeout(100)
await page.mouse.move(x, 419, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
await page.mouse.move(x, 419)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
})
})

View File

@ -1,6 +1,13 @@
import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import { join } from 'path'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
import * as TOML from '@iarna/toml'
@ -115,6 +122,36 @@ test.describe('Testing settings', () => {
).not.toBeChecked()
})
test('Keybindings display the correct hotkey for Command Palette', async ({
page,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await test.step('Open keybindings settings', async () => {
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('ControlOrMeta+Shift+,')
// Go to Keybindings tab.
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
await keybindingsTab.click()
})
// Go to the hotkey for Command Palette.
const commandPalette = page.getByText('Toggle Command Palette')
await commandPalette.scrollIntoViewIfNeeded()
// The heading is above it and should be in view now.
const commandPaletteHeading = page.getByRole('heading', {
name: 'Command Palette',
})
// The hotkey is in a kbd element next to the heading.
const hotkey = commandPaletteHeading.locator('+ div kbd')
const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K'
await expect(hotkey).toHaveText(text)
})
test('Project and user settings can be reset', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
@ -203,10 +240,11 @@ test.describe('Testing settings', () => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})

View File

@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^2.0.0",
"@kittycad/lib": "^2.0.1",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1",
@ -119,7 +119,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.24.3",
"@babel/preset-env": "^7.25.4",
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0",

View File

@ -9,6 +9,8 @@ import useHotkeyWrapper from 'lib/hotkeyWrapper'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
export const CommandBar = () => {
const { pathname } = useLocation()
const { commandBarState, commandBarSend } = useCommandsContext()
@ -24,7 +26,7 @@ export const CommandBar = () => {
}, [pathname])
// Hook up keyboard shortcuts
useHotkeyWrapper(['mod+k'], () => {
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
if (commandBarState.context.commands.length === 0) return
if (commandBarState.matches('Closed')) {
commandBarSend({ type: 'Open' })

View File

@ -1,5 +1,7 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform'
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
export function CommandBarOpenButton() {
const { commandBarSend } = useCommandsContext()
@ -12,7 +14,7 @@ export function CommandBarOpenButton() {
>
<span>Commands</span>
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
{platform === 'macos' ? '⌘K' : '^/'}
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
</kbd>
</button>
)

View File

@ -9,7 +9,7 @@ import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { coreDump } from 'lang/wasm'
import toast from 'react-hot-toast'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
export function LowerRightControls({
@ -66,6 +66,9 @@ export function LowerRightControls({
{children}
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
target="_blank"
rel="noopener noreferrer"

View File

@ -0,0 +1,35 @@
import { hotkeyDisplay } from './hotkeyWrapper'
describe('hotkeyDisplay', () => {
it('displays mod', async () => {
expect(hotkeyDisplay('mod+c', 'macos')).toEqual('⌘C')
expect(hotkeyDisplay('mod+c', 'windows')).toEqual('Ctrl+C')
expect(hotkeyDisplay('mod+c', 'linux')).toEqual('Ctrl+C')
})
it('displays shift', async () => {
expect(hotkeyDisplay('shift+c', 'macos')).toEqual('⬆C')
expect(hotkeyDisplay('shift+c', 'windows')).toEqual('Shift+C')
expect(hotkeyDisplay('shift+c', 'linux')).toEqual('Shift+C')
})
it('displays meta', async () => {
expect(hotkeyDisplay('meta+c', 'macos')).toEqual('⌘C')
expect(hotkeyDisplay('meta+c', 'windows')).toEqual('Win+C')
// That's correct. What browsers call meta is actually super.
expect(hotkeyDisplay('meta+c', 'linux')).toEqual('Super+C')
})
it('displays alt', async () => {
expect(hotkeyDisplay('alt+c', 'macos')).toEqual('⌥C')
expect(hotkeyDisplay('alt+c', 'windows')).toEqual('Alt+C')
expect(hotkeyDisplay('alt+c', 'linux')).toEqual('Alt+C')
})
it('displays ctrl', async () => {
expect(hotkeyDisplay('ctrl+c', 'macos')).toEqual('^C')
expect(hotkeyDisplay('ctrl+c', 'windows')).toEqual('Ctrl+C')
expect(hotkeyDisplay('ctrl+c', 'linux')).toEqual('Ctrl+C')
})
it('displays multiple modifiers', async () => {
expect(hotkeyDisplay('shift+alt+ctrl+c', 'windows')).toEqual(
'Shift+Alt+Ctrl+C'
)
})
})

View File

@ -1,9 +1,10 @@
import { Options, useHotkeys } from 'react-hotkeys-hook'
import { useEffect } from 'react'
import { codeManager } from './singletons'
import { Platform } from './utils'
// Hotkey wrapper wraps hotkeys for the app (outside of the editor)
// With hotkeys inside the editor.
// with hotkeys inside the editor.
// This way we can have hotkeys defined in one place and not have to worry about
// conflicting hotkeys, or them only being implemented for the app but not
// inside the editor.
@ -37,3 +38,48 @@ function mapHotkeyToCodeMirrorHotkey(hotkey: string): string {
.replaceAll('shift', 'Shift')
.replaceAll('alt', 'Alt')
}
const LOWER_CASE_LETTER = /[a-z]/
const WHITESPACE = /\s+/g
/**
* Convert hotkey to display text.
*
* TODO: We should handle capitalized single letter hotkeys like K as Shift+K,
* but we don't.
*/
export function hotkeyDisplay(hotkey: string, platform: Platform): string {
const isMac = platform === 'macos'
const isWindows = platform === 'windows'
// Browsers call it metaKey, but that's a misnomer.
const meta = isWindows ? 'Win' : 'Super'
const outputSeparator = isMac ? '' : '+'
const display = hotkey
// Capitalize letters. We want Ctrl+K, not Ctrl+k, since Shift should be
// shown as a separate modifier.
.split('+')
.map((word) => {
if (word.length === 1 && LOWER_CASE_LETTER.test(word)) {
return word.toUpperCase()
}
return word
})
.join(outputSeparator)
// Collapse multiple spaces into one.
.replaceAll(WHITESPACE, ' ')
.replaceAll('mod', isMac ? '⌘' : 'Ctrl')
.replaceAll('meta', isMac ? '⌘' : meta)
// This is technically the wrong arrow for control, but it's more visible
// and recognizable. May want to change this in the future.
//
// The correct arrow is ⌃ "UP ARROWHEAD" Unicode: U+2303
.replaceAll('ctrl', isMac ? '^' : 'Ctrl')
// This is technically the wrong arrow for shift, but it's more visible and
// recognizable. May want to change this in the future.
//
// The correct arrow is ⇧ "UPWARDS WHITE ARROW" Unicode: U+21E7
.replaceAll('shift', isMac ? '⬆' : 'Shift')
.replaceAll('alt', isMac ? '⌥' : 'Alt')
return display
}

View File

@ -61,7 +61,7 @@ export const interactionMap: Record<
name: 'toggle-command-palette',
sequence: `${PRIMARY}+K`,
title: 'Toggle Command Palette',
description: 'Always available. Use Ctrl+/ on Windows/Linux.',
description: 'Always available.',
},
],
Panes: [

View File

@ -151,6 +151,32 @@ export function platform(): Platform {
return ''
}
}
// navigator.platform is deprecated, but many browsers still support it, and
// it's more accurate than userAgent and userAgentData in Playwright.
if (
navigator.platform?.indexOf('Mac') === 0 ||
navigator.platform === 'iPhone'
) {
return 'macos'
}
if (navigator.platform === 'Win32') {
return 'windows'
}
// Chrome only, but more accurate than userAgent.
let userAgentDataPlatform: unknown
if (
'userAgentData' in navigator &&
navigator.userAgentData &&
typeof navigator.userAgentData === 'object' &&
'platform' in navigator.userAgentData
) {
userAgentDataPlatform = navigator.userAgentData.platform
if (userAgentDataPlatform === 'macOS') return 'macos'
if (userAgentDataPlatform === 'Windows') return 'windows'
}
if (navigator.userAgent.indexOf('Mac') !== -1) {
return 'macos'
} else if (navigator.userAgent.indexOf('Win') !== -1) {
@ -158,7 +184,12 @@ export function platform(): Platform {
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
return 'linux'
}
console.error('Unknown platform userAgent:', navigator.userAgent)
console.error(
'Unknown platform userAgent:',
navigator.platform,
userAgentDataPlatform,
navigator.userAgent
)
return ''
}

View File

@ -2,6 +2,8 @@ import usePlatform from 'hooks/usePlatform'
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { useModelingContext } from 'hooks/useModelingContext'
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar'
export default function CmdK() {
const { context } = useModelingContext()
@ -20,15 +22,9 @@ export default function CmdK() {
<h2 className="text-2xl font-bold">Command Bar</h2>
<p className="my-4">
Press{' '}
{platformName === 'macos' ? (
<>
<kbd className={kbdClasses}>K</kbd>
</>
) : (
<>
<kbd className={kbdClasses}>Ctrl + /</kbd>
</>
)}{' '}
<kbd className={kbdClasses}>
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platformName)}
</kbd>{' '}
to open the command bar. Try changing your theme with it.
</p>
<p className="my-4">

View File

@ -724,7 +724,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.24"
version = "0.1.25"
dependencies = [
"Inflector",
"anyhow",
@ -1397,7 +1397,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.7"
version = "0.2.10"
dependencies = [
"anyhow",
"approx",
@ -1469,7 +1469,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.8"
version = "0.1.9"
dependencies = [
"anyhow",
"hyper",
@ -1482,9 +1482,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9621df809fa105ae28b01586c52ce46561e166702babb50e5bcc5097a8ce62"
checksum = "fbb7c076d64ad00a29ae900108707d1bbb583944d4b2d005e1eca9914a18c7c2"
dependencies = [
"anyhow",
"async-trait",
@ -1870,7 +1870,7 @@ dependencies = [
"bincode",
"either",
"fnv",
"itertools 0.12.1",
"itertools 0.10.5",
"lazy_static",
"nom",
"quick-xml",
@ -2096,9 +2096,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]

View File

@ -71,7 +71,7 @@ members = [
[workspace.dependencies]
http = "0.2.12"
kittycad = { version = "0.3.16", default-features = false, features = ["js", "requests"] }
kittycad = { version = "0.3.17", default-features = false, features = ["js", "requests"] }
kittycad-modeling-session = "0.1.4"
[[test]]

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.24"
version = "0.1.25"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.8"
version = "0.1.9"
edition = "2021"
license = "MIT"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.7"
version = "0.2.10"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -9,7 +9,7 @@ use std::{
use anyhow::{anyhow, Result};
use dashmap::DashMap;
use futures::{SinkExt, StreamExt};
use kittycad::types::{WebSocketRequest, WebSocketResponse};
use kittycad::types::{ModelingSessionData, WebSocketRequest, WebSocketResponse};
use tokio::sync::{mpsc, oneshot, RwLock};
use tokio_tungstenite::tungstenite::Message as WsMsg;
@ -27,10 +27,10 @@ enum SocketHealth {
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
#[derive(Debug, Clone)]
#[allow(dead_code)] // for the TcpReadHandle
pub struct EngineConnection {
engine_req_tx: mpsc::Sender<ToEngineReq>,
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
#[allow(dead_code)]
tcp_read_handle: Arc<TcpReadHandle>,
socket_health: Arc<Mutex<SocketHealth>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
@ -38,6 +38,8 @@ pub struct EngineConnection {
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
/// If the server sends session data, it'll be copied to here.
session_data: Arc<Mutex<Option<ModelingSessionData>>>,
}
pub struct TcpRead {
@ -181,6 +183,8 @@ impl EngineConnection {
let mut tcp_read = TcpRead { stream: tcp_read };
let session_data: Arc<Mutex<Option<ModelingSessionData>>> = Arc::new(Mutex::new(None));
let session_data2 = session_data.clone();
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
let responses_clone = responses.clone();
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
@ -192,35 +196,40 @@ impl EngineConnection {
match tcp_read.read().await {
Ok(ws_resp) => {
// If we got a batch response, add all the inner responses.
if let Some(kittycad::types::OkWebSocketResponseData::ModelingBatch { responses }) =
&ws_resp.resp
{
for (resp_id, batch_response) in responses {
let id: uuid::Uuid = resp_id.parse().unwrap();
if let Some(response) = &batch_response.response {
responses_clone.insert(
id,
kittycad::types::WebSocketResponse {
request_id: Some(id),
resp: Some(kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: response.clone(),
}),
errors: None,
success: Some(true),
},
);
} else {
responses_clone.insert(
id,
kittycad::types::WebSocketResponse {
request_id: Some(id),
resp: None,
errors: batch_response.errors.clone(),
success: Some(false),
},
);
match &ws_resp.resp {
Some(kittycad::types::OkWebSocketResponseData::ModelingBatch { responses }) => {
for (resp_id, batch_response) in responses {
let id: uuid::Uuid = resp_id.parse().unwrap();
if let Some(response) = &batch_response.response {
responses_clone.insert(
id,
kittycad::types::WebSocketResponse {
request_id: Some(id),
resp: Some(kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: response.clone(),
}),
errors: None,
success: Some(true),
},
);
} else {
responses_clone.insert(
id,
kittycad::types::WebSocketResponse {
request_id: Some(id),
resp: None,
errors: batch_response.errors.clone(),
success: Some(false),
},
);
}
}
}
Some(kittycad::types::OkWebSocketResponseData::ModelingSessionData { session }) => {
let mut sd = session_data2.lock().unwrap();
sd.replace(session.clone());
}
_ => {}
}
if let Some(id) = ws_resp.request_id {
@ -249,6 +258,7 @@ impl EngineConnection {
batch: Arc::new(Mutex::new(Vec::new())),
batch_end: Arc::new(Mutex::new(HashMap::new())),
default_planes: Default::default(),
session_data,
})
}
}
@ -345,4 +355,8 @@ impl EngineManager for EngineConnection {
source_ranges: vec![source_range],
}))
}
fn get_session_data(&self) -> Option<ModelingSessionData> {
self.session_data.lock().unwrap().clone()
}
}

View File

@ -13,7 +13,9 @@ use std::{
sync::{Arc, Mutex},
};
use kittycad::types::{Color, ModelingCmd, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest};
use kittycad::types::{
Color, ModelingCmd, ModelingCmdReq, ModelingSessionData, OkWebSocketResponseData, WebSocketRequest,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -490,6 +492,12 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(())
}
/// Get session data, if it has been received.
/// Returns None if the server never sent it.
fn get_session_data(&self) -> Option<ModelingSessionData> {
None
}
}
#[derive(Debug, Hash, Eq, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]

View File

@ -4,6 +4,7 @@ use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use async_recursion::async_recursion;
use kittycad::types::ModelingSessionData;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -1613,8 +1614,11 @@ pub struct ExecutorSettings {
pub highlight_edges: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
pub enable_ssao: bool,
// Show grid?
/// Show grid?
pub show_grid: bool,
/// Should engine store this for replay?
/// If so, under what name?
pub replay: Option<String>,
}
impl Default for ExecutorSettings {
@ -1624,6 +1628,7 @@ impl Default for ExecutorSettings {
highlight_edges: true,
enable_ssao: false,
show_grid: false,
replay: None,
}
}
}
@ -1635,6 +1640,7 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid,
replay: None,
}
}
}
@ -1646,6 +1652,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid,
replay: None,
}
}
}
@ -1657,6 +1664,7 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
highlight_edges: modeling.highlight_edges.into(),
enable_ssao: modeling.enable_ssao.into(),
show_grid: modeling.show_scale_grid,
replay: None,
}
}
}
@ -1665,11 +1673,8 @@ impl ExecutorContext {
/// Create a new default executor context.
/// Also returns the response HTTP headers from the server.
#[cfg(not(target_arch = "wasm32"))]
pub async fn new_with_headers(
client: &kittycad::Client,
settings: ExecutorSettings,
) -> Result<(Self, http::HeaderMap)> {
let (ws, headers) = client
pub async fn new(client: &kittycad::Client, settings: ExecutorSettings) -> Result<Self> {
let (ws, _headers) = client
.modeling()
.commands_ws(
None,
@ -1679,6 +1684,7 @@ impl ExecutorContext {
} else {
None
},
settings.replay.clone(),
if settings.show_grid { Some(true) } else { None },
None,
None,
@ -1701,21 +1707,13 @@ impl ExecutorContext {
)
.await?;
let slf = Self {
Ok(Self {
engine,
fs: Arc::new(FileManager::new()),
stdlib: Arc::new(StdLib::new()),
settings,
is_mock: false,
};
Ok((slf, headers))
}
#[cfg(not(target_arch = "wasm32"))]
/// Create a new default executor context.
pub async fn new(client: &kittycad::Client, settings: ExecutorSettings) -> Result<Self> {
let (slf, _headers) = Self::new_with_headers(client, settings).await?;
Ok(slf)
})
}
/// For executing unit tests.
@ -1755,6 +1753,7 @@ impl ExecutorContext {
highlight_edges: true,
enable_ssao: false,
show_grid: false,
replay: None,
},
)
.await?;
@ -1774,6 +1773,16 @@ impl ExecutorContext {
program: &crate::ast::types::Program,
memory: Option<ProgramMemory>,
) -> Result<ProgramMemory, KclError> {
self.run_with_session_data(program, memory).await.map(|x| x.0)
}
/// Perform the execution of a program.
/// You can optionally pass in some initialization memory.
/// Kurt uses this for partial execution.
pub async fn run_with_session_data(
&self,
program: &crate::ast::types::Program,
memory: Option<ProgramMemory>,
) -> Result<(ProgramMemory, Option<ModelingSessionData>), KclError> {
// Before we even start executing the program, set the units.
self.engine
.batch_modeling_cmd(
@ -1790,13 +1799,16 @@ impl ExecutorContext {
Default::default()
};
let mut dynamic_state = DynamicState::default();
self.inner_execute(
program,
&mut memory,
&mut dynamic_state,
crate::executor::BodyType::Root,
)
.await
let final_memory = self
.inner_execute(
program,
&mut memory,
&mut dynamic_state,
crate::executor::BodyType::Root,
)
.await?;
let session_data = self.engine.get_session_data();
Ok((final_memory, session_data))
}
/// Execute an AST's program.

View File

@ -95,6 +95,7 @@ lazy_static! {
Box::new(crate::std::fillet::GetPreviousAdjacentEdge),
Box::new(crate::std::helix::Helix),
Box::new(crate::std::shell::Shell),
Box::new(crate::std::shell::Hollow),
Box::new(crate::std::revolve::Revolve),
Box::new(crate::std::import::Import),
Box::new(crate::std::math::Cos),

View File

@ -129,3 +129,64 @@ async fn inner_shell(
Ok(extrude_group)
}
/// Make the inside of a 3D object hollow.
pub async fn hollow(args: Args) -> Result<KclValue, KclError> {
let (thickness, extrude_group): (f64, Box<ExtrudeGroup>) = args.get_data_and_extrude_group()?;
let extrude_group = inner_hollow(thickness, extrude_group, args).await?;
Ok(KclValue::ExtrudeGroup(extrude_group))
}
/// Make the inside of a 3D object hollow.
///
/// Remove volume from a 3-dimensional shape such that a wall of the
/// provided thickness remains around the exterior of the shape.
///
/// ```no_run
/// const firstSketch = startSketchOn('XY')
/// |> startProfileAt([-12, 12], %)
/// |> line([24, 0], %)
/// |> line([0, -24], %)
/// |> line([-24, 0], %)
/// |> close(%)
/// |> extrude(6, %)
/// |> hollow (0.25, %)
/// ```
///
/// ```no_run
/// const firstSketch = startSketchOn('-XZ')
/// |> startProfileAt([-12, 12], %)
/// |> line([24, 0], %)
/// |> line([0, -24], %)
/// |> line([-24, 0], %)
/// |> close(%)
/// |> extrude(6, %)
/// |> hollow (0.5, %)
/// ```
#[stdlib {
name = "hollow",
}]
async fn inner_hollow(
thickness: f64,
extrude_group: Box<ExtrudeGroup>,
args: Args,
) -> Result<Box<ExtrudeGroup>, KclError> {
// Flush the batch for our fillets/chamfers if there are any.
// If we do not do these for sketch on face, things will fail with face does not exist.
args.flush_batch_for_extrude_group_set(extrude_group.clone().into())
.await?;
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::Solid3DShellFace {
hollow: true,
face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
object_id: extrude_group.id,
shell_thickness: thickness,
},
)
.await?;
Ok(extrude_group)
}

View File

@ -63,6 +63,7 @@ async fn new_context(units: UnitLength) -> anyhow::Result<ExecutorContext> {
highlight_edges: true,
enable_ssao: false,
show_grid: false,
replay: None,
},
)
.await?;

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

196
yarn.lock
View File

@ -28,10 +28,10 @@
"@babel/highlight" "^7.24.7"
picocolors "^1.0.0"
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5"
integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb"
integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==
"@babel/core@^7.16.0", "@babel/core@^7.21.4", "@babel/core@^7.24.5":
version "7.25.2"
@ -73,6 +73,16 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/generator@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.4.tgz#1dc63c1c9caae9e6dc24e264eac254eb25005669"
integrity sha512-NFtZmZsyzDPJnk9Zg3BbTfKKc9UlHYzD0E//p2Z3B9nCwwtJW9T0gVbCz8+fBngnn4zf1Dr3IK8PHQQHq0lDQw==
dependencies:
"@babel/types" "^7.25.4"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab"
@ -112,7 +122,20 @@
"@babel/traverse" "^7.25.0"
semver "^6.3.1"
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0":
"@babel/helper-create-class-features-plugin@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14"
integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-member-expression-to-functions" "^7.24.8"
"@babel/helper-optimise-call-expression" "^7.24.7"
"@babel/helper-replace-supers" "^7.25.0"
"@babel/helper-skip-transparent-expression-wrappers" "^7.24.7"
"@babel/traverse" "^7.25.4"
semver "^6.3.1"
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0", "@babel/helper-create-regexp-features-plugin@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz#24c75974ed74183797ffd5f134169316cd1808d9"
integrity sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==
@ -121,7 +144,7 @@
regexpu-core "^5.3.1"
semver "^6.3.1"
"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2":
"@babel/helper-define-polyfill-provider@^0.6.2":
version "0.6.2"
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d"
integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==
@ -253,6 +276,13 @@
dependencies:
"@babel/types" "^7.25.2"
"@babel/parser@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a"
integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==
dependencies:
"@babel/types" "^7.25.4"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3":
version "7.25.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz#dca427b45a6c0f5c095a1c639dfe2476a3daba7f"
@ -519,15 +549,15 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-async-generator-functions@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz#b785cf35d73437f6276b1e30439a57a50747bddf"
integrity sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==
"@babel/plugin-transform-async-generator-functions@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz#2afd4e639e2d055776c9f091b6c0c180ed8cf083"
integrity sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==
dependencies:
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-remap-async-to-generator" "^7.25.0"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/traverse" "^7.25.0"
"@babel/traverse" "^7.25.4"
"@babel/plugin-transform-async-to-generator@^7.24.7":
version "7.24.7"
@ -552,13 +582,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-transform-class-properties@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834"
integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==
"@babel/plugin-transform-class-properties@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz#bae7dbfcdcc2e8667355cd1fb5eda298f05189fd"
integrity sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.24.7"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/helper-create-class-features-plugin" "^7.25.4"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-transform-class-static-block@^7.24.7":
version "7.24.7"
@ -569,16 +599,16 @@
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-syntax-class-static-block" "^7.14.5"
"@babel/plugin-transform-classes@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz#63122366527d88e0ef61b612554fe3f8c793991e"
integrity sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==
"@babel/plugin-transform-classes@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz#d29dbb6a72d79f359952ad0b66d88518d65ef89a"
integrity sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-compilation-targets" "^7.24.8"
"@babel/helper-compilation-targets" "^7.25.2"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-replace-supers" "^7.25.0"
"@babel/traverse" "^7.25.0"
"@babel/traverse" "^7.25.4"
globals "^11.1.0"
"@babel/plugin-transform-computed-properties@^7.24.7":
@ -806,13 +836,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-private-methods@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz#e6318746b2ae70a59d023d5cc1344a2ba7a75f5e"
integrity sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==
"@babel/plugin-transform-private-methods@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz#9bbefbe3649f470d681997e0b64a4b254d877242"
integrity sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.24.7"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/helper-create-class-features-plugin" "^7.25.4"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-transform-private-property-in-object@^7.24.7":
version "7.24.7"
@ -975,20 +1005,20 @@
"@babel/helper-create-regexp-features-plugin" "^7.24.7"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-unicode-sets-regex@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz#d40705d67523803a576e29c63cef6e516b858ed9"
integrity sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==
"@babel/plugin-transform-unicode-sets-regex@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz#be664c2a0697ffacd3423595d5edef6049e8946c"
integrity sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==
dependencies:
"@babel/helper-create-regexp-features-plugin" "^7.24.7"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/helper-create-regexp-features-plugin" "^7.25.2"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/preset-env@^7.16.4", "@babel/preset-env@^7.24.3":
version "7.25.3"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.3.tgz#0bf4769d84ac51d1073ab4a86f00f30a3a83c67c"
integrity sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==
"@babel/preset-env@^7.16.4", "@babel/preset-env@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.4.tgz#be23043d43a34a2721cd0f676c7ba6f1481f6af6"
integrity sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==
dependencies:
"@babel/compat-data" "^7.25.2"
"@babel/compat-data" "^7.25.4"
"@babel/helper-compilation-targets" "^7.25.2"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-validator-option" "^7.24.8"
@ -1017,13 +1047,13 @@
"@babel/plugin-syntax-top-level-await" "^7.14.5"
"@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
"@babel/plugin-transform-arrow-functions" "^7.24.7"
"@babel/plugin-transform-async-generator-functions" "^7.25.0"
"@babel/plugin-transform-async-generator-functions" "^7.25.4"
"@babel/plugin-transform-async-to-generator" "^7.24.7"
"@babel/plugin-transform-block-scoped-functions" "^7.24.7"
"@babel/plugin-transform-block-scoping" "^7.25.0"
"@babel/plugin-transform-class-properties" "^7.24.7"
"@babel/plugin-transform-class-properties" "^7.25.4"
"@babel/plugin-transform-class-static-block" "^7.24.7"
"@babel/plugin-transform-classes" "^7.25.0"
"@babel/plugin-transform-classes" "^7.25.4"
"@babel/plugin-transform-computed-properties" "^7.24.7"
"@babel/plugin-transform-destructuring" "^7.24.8"
"@babel/plugin-transform-dotall-regex" "^7.24.7"
@ -1051,7 +1081,7 @@
"@babel/plugin-transform-optional-catch-binding" "^7.24.7"
"@babel/plugin-transform-optional-chaining" "^7.24.8"
"@babel/plugin-transform-parameters" "^7.24.7"
"@babel/plugin-transform-private-methods" "^7.24.7"
"@babel/plugin-transform-private-methods" "^7.25.4"
"@babel/plugin-transform-private-property-in-object" "^7.24.7"
"@babel/plugin-transform-property-literals" "^7.24.7"
"@babel/plugin-transform-regenerator" "^7.24.7"
@ -1064,10 +1094,10 @@
"@babel/plugin-transform-unicode-escapes" "^7.24.7"
"@babel/plugin-transform-unicode-property-regex" "^7.24.7"
"@babel/plugin-transform-unicode-regex" "^7.24.7"
"@babel/plugin-transform-unicode-sets-regex" "^7.24.7"
"@babel/plugin-transform-unicode-sets-regex" "^7.25.4"
"@babel/preset-modules" "0.1.6-no-external-plugins"
babel-plugin-polyfill-corejs2 "^0.4.10"
babel-plugin-polyfill-corejs3 "^0.10.4"
babel-plugin-polyfill-corejs3 "^0.10.6"
babel-plugin-polyfill-regenerator "^0.6.1"
core-js-compat "^3.37.1"
semver "^6.3.1"
@ -1138,6 +1168,19 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e"
integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==
dependencies:
"@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.25.4"
"@babel/parser" "^7.25.4"
"@babel/template" "^7.25.0"
"@babel/types" "^7.25.4"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.4", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.4.4":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125"
@ -1147,6 +1190,15 @@
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.25.4":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f"
integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==
dependencies:
"@babel/helper-string-parser" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.17.0":
version "6.17.0"
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.17.0.tgz#24ff5fc37fd91f6439df6f4ff9c8e910cde1b053"
@ -1971,10 +2023,10 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@kittycad/lib@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-2.0.0.tgz#c7808f47546f5b85371899b0b82989a4049d258d"
integrity sha512-iJCifdt7C+gHCH7xomNhynUmhy4rvM+J3Skxm3vxJR+zMWludm1bSCd+LgUpgk6kZOvkHJ755Y0gfMDYz1IVxw==
"@kittycad/lib@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-2.0.1.tgz#d3f1c80d9903452b0b9df378c72ed1e83b19a73d"
integrity sha512-VYunezWS+cNZbdKfVkB3zg2YbDCQEb/AjzER85+yyDAlTU5PL4paQDpNlEI6icSglDGRUIR4Er/bRFj68r3UQg==
dependencies:
openapi-types "^12.0.0"
ts-node "^10.9.1"
@ -3220,13 +3272,13 @@ babel-plugin-polyfill-corejs2@^0.4.10:
"@babel/helper-define-polyfill-provider" "^0.6.2"
semver "^6.3.1"
babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4:
version "0.10.4"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77"
integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==
babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.6:
version "0.10.6"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7"
integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==
dependencies:
"@babel/helper-define-polyfill-provider" "^0.6.1"
core-js-compat "^3.36.1"
"@babel/helper-define-polyfill-provider" "^0.6.2"
core-js-compat "^3.38.0"
babel-plugin-polyfill-regenerator@^0.6.1:
version "0.6.2"
@ -3380,6 +3432,16 @@ browserslist@^4.23.0, browserslist@^4.23.1:
node-releases "^2.0.14"
update-browserslist-db "^1.1.0"
browserslist@^4.23.3:
version "4.23.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800"
integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
dependencies:
caniuse-lite "^1.0.30001646"
electron-to-chromium "^1.5.4"
node-releases "^2.0.18"
update-browserslist-db "^1.1.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
@ -3476,6 +3538,11 @@ caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001640:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz#d472f2882259ba032dd73ee069ff01bfd059b25d"
integrity sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==
caniuse-lite@^1.0.30001646:
version "1.0.30001651"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138"
integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==
chai@^4.3.10:
version "4.5.0"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8"
@ -3754,12 +3821,12 @@ cookie@0.6.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
core-js-compat@^3.36.1, core-js-compat@^3.37.1:
version "3.37.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee"
integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==
core-js-compat@^3.37.1, core-js-compat@^3.38.0:
version "3.38.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.38.1.tgz#2bc7a298746ca5a7bcb9c164bcb120f2ebc09a09"
integrity sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==
dependencies:
browserslist "^4.23.0"
browserslist "^4.23.3"
core-util-is@~1.0.0:
version "1.0.3"
@ -4183,6 +4250,11 @@ electron-to-chromium@^1.4.820:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz#cd477c830dd6fca41fbd5465c1ff6ce08ac22343"
integrity sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==
electron-to-chromium@^1.5.4:
version "1.5.13"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6"
integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==
electron-winstaller@^5.3.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/electron-winstaller/-/electron-winstaller-5.4.0.tgz#f0660d476d5c4f579fdf7edd2f0cf01d54c4d0b2"
@ -6731,7 +6803,7 @@ node-gyp@^9.0.0:
tar "^6.1.2"
which "^2.0.2"
node-releases@^2.0.14:
node-releases@^2.0.14, node-releases@^2.0.18:
version "2.0.18"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==