Compare commits
28 Commits
kcl-0.2.7
...
kurt-does-
Author | SHA1 | Date | |
---|---|---|---|
d2a3bfdb5a | |||
dbdc7e5c8b | |||
f6bb10170d | |||
972dca8743 | |||
e9e933eecd | |||
2b1315423f | |||
bd4c24bc04 | |||
50cc88977c | |||
bea9a1c3ec | |||
f43411fdb4 | |||
c2e9d18f92 | |||
199722c505 | |||
f9699d174c | |||
590a6479e0 | |||
fbf0d3d953 | |||
3dd66bc8d2 | |||
a928b8fbd0 | |||
d2349bec2b | |||
6e10f75ff6 | |||
03e289af20 | |||
efc140abbf | |||
4dfad19b7e | |||
e56c634b35 | |||
00292abc98 | |||
483d6903d6 | |||
3780996374 | |||
2fde71228a | |||
5cd8ab3812 |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
||||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas,.yarn.lock,**/yarn.lock
|
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock
|
||||||
|
2
.github/workflows/cargo-check.yml
vendored
@ -37,4 +37,4 @@ jobs:
|
|||||||
# We specifically want to test the disable-println feature
|
# We specifically want to test the disable-println feature
|
||||||
# Since it is not enabled by default, we need to specify it
|
# Since it is not enabled by default, we need to specify it
|
||||||
# This is used in kcl-lsp
|
# This is used in kcl-lsp
|
||||||
cargo check --all --features disable-println --features pyo3
|
cargo check --all --features disable-println --features pyo3 --features cli
|
||||||
|
5
.github/workflows/cargo-test.yml
vendored
@ -38,11 +38,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
- name: install dependencies
|
|
||||||
if: matrix.dir == 'src-tauri'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
|
||||||
- name: Install vector
|
- name: Install vector
|
||||||
run: |
|
run: |
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||||
|
5
.gitignore
vendored
@ -54,7 +54,6 @@ e2e/playwright/export-snapshots/*
|
|||||||
|
|
||||||
## generated files
|
## generated files
|
||||||
src/**/*.typegen.ts
|
src/**/*.typegen.ts
|
||||||
src-tauri/gen
|
|
||||||
|
|
||||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||||
Mac_App_Distribution.provisionprofile
|
Mac_App_Distribution.provisionprofile
|
||||||
@ -66,7 +65,3 @@ venv
|
|||||||
|
|
||||||
# electron
|
# electron
|
||||||
out/
|
out/
|
||||||
|
|
||||||
src-tauri/target
|
|
||||||
electron-test-projects-dir
|
|
||||||
electron-test-projects-dir-2
|
|
||||||
|
344
Info.plist
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.kcl</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>KCL</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Owner</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.toml</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>TOML</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.gltf</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>glTF</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.glb</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>glb</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.step</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>STEP</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.fbx</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>FBX</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.sldprt</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Solidworks Part</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.geometry-definition-format</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>OBJ</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.polygon-file-format</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>PLY</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.standard-tesselated-geometry-format</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>STL</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.folder</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Folders</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.kcl</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://zoo.dev/docs/kcl</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.source-code</string>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
<string>public.script</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>KCL (KittyCAD Language) document</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>kcl</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/vnd.zoo.kcl</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.gltf</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
<string>public.json</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Graphics Library Transmission Format (glTF)</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>gltf</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/gltf+json</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.glb</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Graphics Library Transmission Format (glTF) binary</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>glb</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/gltf-binary</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.step</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://www.loc.gov/preservation/digital/formats/fdd/fdd000448.shtml</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>STEP-file, ISO 10303-21</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>step</string>
|
||||||
|
<string>stp</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/step</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.sldprt</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://docs.fileformat.com/cad/sldprt/</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Solidworks Part</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>sldprt</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/vnd.solidworks.sldprt</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.fbx</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://en.wikipedia.org/wiki/FBX</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Autodesk Filmbox (FBX) format</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>fbx</string>
|
||||||
|
<string>fbxb</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/vnd.autodesk.fbx</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.toml</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://toml.io/en/</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Tom's Obvious Minimal Language</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>kcl</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/toml</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
30
README.md
@ -189,12 +189,22 @@ For more information on fuzzing you can check out
|
|||||||
|
|
||||||
### Playwright tests
|
### Playwright tests
|
||||||
|
|
||||||
|
You will need a `./e2e/playwright/playwright-secrets.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ touch ./e2e/playwright/playwright-secrets.env
|
||||||
|
$ cat ./e2e/playwright/playwright-secrets.env
|
||||||
|
token=<dev.zoo.dev/account/api-tokens>
|
||||||
|
snapshottoken=<your-snapshot-token>
|
||||||
|
```
|
||||||
|
|
||||||
For a portable way to run Playwright you'll need Docker.
|
For a portable way to run Playwright you'll need Docker.
|
||||||
|
|
||||||
|
#### Generic example
|
||||||
After that, open a terminal and run:
|
After that, open a terminal and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --network host --rm --init -it playwright/chrome:playwright-1.43.1
|
docker run --network host --rm --init -it playwright/chrome:playwright-x.xx.x
|
||||||
```
|
```
|
||||||
|
|
||||||
and in another terminal, run:
|
and in another terminal, run:
|
||||||
@ -203,21 +213,27 @@ and in another terminal, run:
|
|||||||
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
||||||
```
|
```
|
||||||
|
|
||||||
An example of a `<test suite>` is: `e2e/playwright/flow-tests.spec.ts`
|
|
||||||
|
|
||||||
YOU WILL NEED A PLAYWRIGHT-SECRETS.ENV FILE:
|
#### Specific example
|
||||||
|
|
||||||
|
open a terminal and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ./e2e/playwright/playwright-secrets.env
|
docker run --network host --rm --init -it playwright/chrome:playwright-1.46.0
|
||||||
token=<your-token>
|
```
|
||||||
snapshottoken=<your-snapshot-token>
|
|
||||||
|
and in another terminal, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" e2e/playwright/command-bar-tests.spec.ts
|
||||||
```
|
```
|
||||||
then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens
|
|
||||||
|
|
||||||
run a specific test change the test from `test('...` to `test.only('...`
|
run a specific test change the test from `test('...` to `test.only('...`
|
||||||
(note if you commit this, the tests will instantly fail without running any of the tests)
|
(note if you commit this, the tests will instantly fail without running any of the tests)
|
||||||
|
|
||||||
|
|
||||||
|
**Gotcha**: running the docker container with a mismatched image against your `./node_modules/playwright` will cause a failure. Make sure the versions are matched and up to date.
|
||||||
|
|
||||||
run headed
|
run headed
|
||||||
|
|
||||||
```
|
```
|
||||||
|
835
docs/kcl/hollow.md
Normal file
@ -44,6 +44,7 @@ layout: manual
|
|||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
* [`helix`](kcl/helix)
|
* [`helix`](kcl/helix)
|
||||||
* [`hole`](kcl/hole)
|
* [`hole`](kcl/hole)
|
||||||
|
* [`hollow`](kcl/hollow)
|
||||||
* [`import`](kcl/import)
|
* [`import`](kcl/import)
|
||||||
* [`inch`](kcl/inch)
|
* [`inch`](kcl/inch)
|
||||||
* [`int`](kcl/int)
|
* [`int`](kcl/int)
|
||||||
|
5173
docs/kcl/std.json
@ -6,7 +6,39 @@ test.afterEach(async ({ page }, testInfo) => {
|
|||||||
await tearDown(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(
|
test(
|
||||||
'User settings has correct shortcut',
|
'User settings has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
@ -1,6 +1,13 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
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 { bracket } from 'lib/exampleKcl'
|
||||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
@ -223,26 +230,24 @@ test(
|
|||||||
'Opening multiple panes persists when switching projects',
|
'Opening multiple panes persists when switching projects',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
// Setup multiple projects.
|
// Setup multiple projects.
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
|
const bracketDir = join(dir, 'bracket')
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
fsp.mkdir(bracketDir, { recursive: true }),
|
||||||
])
|
])
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
executorInputPath('router-template-slate.kcl'),
|
||||||
`${dir}/router-template-slate/main.kcl`
|
join(routerTemplateDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
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'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
@ -10,22 +16,19 @@ test(
|
|||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
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([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
executorInputPath('router-template-slate.kcl'),
|
||||||
`${dir}/bracket/other.kcl`
|
join(bracketDir, 'other.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -84,6 +84,63 @@ test.describe('Editor tests', () => {
|
|||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`const sketch_001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-info')
|
||||||
|
await expect(
|
||||||
|
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await page.locator('#code-pane button:first-child').click()
|
||||||
|
await page.locator('button:has-text("Format code")').click()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch_001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-info')
|
||||||
|
await expect(
|
||||||
|
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('fold gutters work', async ({ page }) => {
|
test('fold gutters work', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -241,6 +298,67 @@ test.describe('Editor tests', () => {
|
|||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch_001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
localStorage.setItem('disableAxis', 'true')
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-info')
|
||||||
|
await expect(
|
||||||
|
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
// Hit alt+shift+f to format the code
|
||||||
|
await page.keyboard.press('Alt+Shift+KeyF')
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch_001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-info')
|
||||||
|
await expect(
|
||||||
|
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
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'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
@ -10,17 +11,14 @@ test(
|
|||||||
'When machine-api server not found butt is disabled and shows the reason',
|
'When machine-api server not found butt is disabled and shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -58,17 +56,14 @@ test(
|
|||||||
'When machine-api server not found home screen & project status shows the reason',
|
'When machine-api server not found home screen & project status shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
|
import { join } from 'path'
|
||||||
import fsp from 'fs/promises'
|
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 { bracket } from 'lib/exampleKcl'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import {
|
import {
|
||||||
@ -347,17 +354,14 @@ test(
|
|||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
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(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
executorInputPath('router-template-slate.kcl'),
|
||||||
`${dir}/router-template-slate/main.kcl`
|
join(routerTemplateDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
import {
|
import {
|
||||||
doExport,
|
doExport,
|
||||||
|
executorInputPath,
|
||||||
getUtils,
|
getUtils,
|
||||||
isOutOfViewInScrollContainer,
|
isOutOfViewInScrollContainer,
|
||||||
Paths,
|
Paths,
|
||||||
@ -49,17 +50,11 @@ test(
|
|||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
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(
|
await fsp.copyFile(
|
||||||
join(
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
'src',
|
join(bracketDir, 'main.kcl')
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs',
|
|
||||||
'focusrite_scarlett_mounting_braket.kcl'
|
|
||||||
),
|
|
||||||
join(dir, 'bracket', 'main.kcl')
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -99,14 +94,7 @@ test(
|
|||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
|
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(
|
executorInputPath('broken-code-test.kcl'),
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs',
|
|
||||||
'broken-code-test.kcl'
|
|
||||||
),
|
|
||||||
join(dir, 'broken-code', 'main.kcl')
|
join(dir, 'broken-code', 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -143,17 +131,14 @@ test.describe('Can export from electron app', () => {
|
|||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -469,6 +454,7 @@ test(
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'File in the file pane should open with a single click',
|
'File in the file pane should open with a single click',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
@ -521,6 +507,69 @@ test(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Nested directories in project without main.kcl do not create main.kcl',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
let testDir: string | undefined
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, 'router-template-slate', 'nested'), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('router-template-slate.kcl'),
|
||||||
|
join(dir, 'router-template-slate', 'nested', 'slate.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
|
join(dir, 'router-template-slate', 'nested', 'bracket.kcl')
|
||||||
|
)
|
||||||
|
testDir = dir
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await test.step('Open the project', async () => {
|
||||||
|
await page.getByText('router-template-slate').click()
|
||||||
|
await expect(page.getByTestId('loading')).toBeAttached()
|
||||||
|
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||||
|
timeout: 20_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// It actually loads.
|
||||||
|
await expect(u.codeLocator).toContainText('mounting bracket')
|
||||||
|
await expect(u.codeLocator).toContainText('const radius =')
|
||||||
|
})
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
// Find the current file.
|
||||||
|
const filesPane = page.locator('#files-pane')
|
||||||
|
await expect(filesPane.getByText('bracket.kcl')).toBeVisible()
|
||||||
|
// But there's no main.kcl in the file tree browser.
|
||||||
|
await expect(filesPane.getByText('main.kcl')).not.toBeVisible()
|
||||||
|
// No main.kcl file is created on the filesystem.
|
||||||
|
expect(testDir).toBeDefined()
|
||||||
|
if (testDir !== undefined) {
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
await expect(
|
||||||
|
fsp.access(join(testDir, 'router-template-slate', 'main.kcl'))
|
||||||
|
).rejects.toThrow()
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
await expect(
|
||||||
|
fsp.access(join(testDir, 'router-template-slate', 'nested', 'main.kcl'))
|
||||||
|
).rejects.toThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
@ -605,6 +654,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(
|
test(
|
||||||
'Can sort projects on home page',
|
'Can sort projects on home page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
@ -1032,10 +1123,6 @@ test(
|
|||||||
'Search projects on desktop home',
|
'Search projects on desktop home',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ browserName: _ }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const projectData = [
|
const projectData = [
|
||||||
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
|
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
|
||||||
['basic-cube', 'basic_fillet_cube_end.kcl'],
|
['basic-cube', 'basic_fillet_cube_end.kcl'],
|
||||||
@ -1050,7 +1137,7 @@ test(
|
|||||||
for (const [name, file] of projectData) {
|
for (const [name, file] of projectData) {
|
||||||
await fsp.mkdir(join(dir, name), { recursive: true })
|
await fsp.mkdir(join(dir, name), { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', file),
|
executorInputPath(file),
|
||||||
join(dir, name, `main.kcl`)
|
join(dir, name, `main.kcl`)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1097,14 +1184,11 @@ test(
|
|||||||
'file pane is scrollable when there are many files',
|
'file pane is scrollable when there are many files',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
|
const testDir = join(dir, 'testProject')
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
const fileNames = [
|
const fileNames = [
|
||||||
'angled_line.kcl',
|
'angled_line.kcl',
|
||||||
'basic_fillet_cube_close_opposite.kcl',
|
'basic_fillet_cube_close_opposite.kcl',
|
||||||
@ -1168,8 +1252,8 @@ test(
|
|||||||
]
|
]
|
||||||
for (const fileName of fileNames) {
|
for (const fileName of fileNames) {
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
`src/wasm-lib/tests/executor/inputs/${fileName}`,
|
executorInputPath(fileName),
|
||||||
`${dir}/testProject/${fileName}`
|
join(testDir, fileName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1210,19 +1294,16 @@ test(
|
|||||||
'select all in code editor does not actually select all, just what is visible (regression)',
|
'select all in code editor does not actually select all, just what is visible (regression)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
|
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
|
||||||
const name = 'mike_stress_test'
|
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(
|
await fsp.copyFile(
|
||||||
`src/wasm-lib/tests/executor/inputs/${name}.kcl`,
|
executorInputPath(`${name}.kcl`),
|
||||||
`${dir}/${name}/main.kcl`
|
join(testDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -1320,27 +1401,16 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
'A file you have open',
|
'A file you have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
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({
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
const exampleDir = join(
|
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs'
|
|
||||||
)
|
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1425,27 +1495,16 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
'A file you do not have open',
|
'A file you do not have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
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({
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
const exampleDir = join(
|
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs'
|
|
||||||
)
|
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1527,10 +1586,6 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
`A folder you're not inside`,
|
`A folder you're not inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
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({
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
@ -1538,19 +1593,12 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
const exampleDir = join(
|
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs'
|
|
||||||
)
|
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1625,11 +1673,6 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
`A folder you are inside`,
|
`A folder you are inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
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({
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
@ -1638,11 +1681,11 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1738,21 +1781,18 @@ test.describe('Deleting files from the file pane', () => {
|
|||||||
`when main.kcl exists, navigate to main.kcl`,
|
`when main.kcl exists, navigate to main.kcl`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
|
const testDir = join(dir, 'testProject')
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
executorInputPath('cylinder.kcl'),
|
||||||
`${dir}/testProject/main.kcl`
|
join(testDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/basic_fillet_cube_end.kcl',
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
`${dir}/testProject/fileToDelete.kcl`
|
join(testDir, 'fileToDelete.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
|
import { join } from 'path'
|
||||||
import * as fsp from 'fs/promises'
|
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 { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
@ -425,17 +432,14 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
`Network health indicator only appears in modeling view`,
|
`Network health indicator only appears in modeling view`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
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({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@ -912,3 +912,7 @@ export async function createProjectAndRenameIt({
|
|||||||
|
|
||||||
await page.getByLabel('checkmark').last().click()
|
await page.getByLabel('checkmark').last().click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function executorInputPath(fileName: string): string {
|
||||||
|
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
||||||
|
}
|
||||||
|
@ -174,168 +174,166 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
}, [0, -85, -85])
|
}, [0, -85, -85])
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO fixme something is wrong with sketch here
|
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||||
test.fixme(
|
page,
|
||||||
'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
|
||||||
// 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 zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
// than again for sketching
|
||||||
// than again for sketching
|
|
||||||
|
|
||||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
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)
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||||
// 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({
|
|
||||||
timeout: 10_000,
|
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,
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import * as fsp from 'fs/promises'
|
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 { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
|
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
@ -115,6 +122,36 @@ test.describe('Testing settings', () => {
|
|||||||
).not.toBeChecked()
|
).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 }) => {
|
test('Project and user settings can be reset', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -203,10 +240,11 @@ test.describe('Testing settings', () => {
|
|||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -329,4 +367,130 @@ test.describe('Testing settings', () => {
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test('Changing modeling default unit', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
const userSettingsTab = page.getByRole('radio', { name: 'User' })
|
||||||
|
|
||||||
|
// Open the settings modal with lower-right button
|
||||||
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
const resetButton = page.getByRole('button', {
|
||||||
|
name: 'Restore default settings',
|
||||||
|
})
|
||||||
|
// Default unit should be mm
|
||||||
|
await resetButton.click()
|
||||||
|
|
||||||
|
await test.step('Change modeling default unit within project tab', async () => {
|
||||||
|
const changeUnitOfMeasureInProjectTab = async (unitOfMeasure: string) => {
|
||||||
|
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
||||||
|
await page
|
||||||
|
.getByTestId('modeling-defaultUnit')
|
||||||
|
.selectOption(`${unitOfMeasure}`)
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await changeUnitOfMeasureInProjectTab('in')
|
||||||
|
await changeUnitOfMeasureInProjectTab('ft')
|
||||||
|
await changeUnitOfMeasureInProjectTab('yd')
|
||||||
|
await changeUnitOfMeasureInProjectTab('mm')
|
||||||
|
await changeUnitOfMeasureInProjectTab('cm')
|
||||||
|
await changeUnitOfMeasureInProjectTab('m')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Go to the user tab
|
||||||
|
await userSettingsTab.click()
|
||||||
|
await test.step('Change modeling default unit within user tab', async () => {
|
||||||
|
const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => {
|
||||||
|
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
||||||
|
await page
|
||||||
|
.getByTestId('modeling-defaultUnit')
|
||||||
|
.selectOption(`${unitOfMeasure}`)
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" as a user default`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await changeUnitOfMeasureInUserTab('in')
|
||||||
|
await changeUnitOfMeasureInUserTab('ft')
|
||||||
|
await changeUnitOfMeasureInUserTab('yd')
|
||||||
|
await changeUnitOfMeasureInUserTab('mm')
|
||||||
|
await changeUnitOfMeasureInUserTab('cm')
|
||||||
|
await changeUnitOfMeasureInUserTab('m')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close settings
|
||||||
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
await settingsCloseButton.click()
|
||||||
|
|
||||||
|
await test.step('Change modeling default unit within command bar', async () => {
|
||||||
|
const commands = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => {
|
||||||
|
// Open command bar
|
||||||
|
await commands.click()
|
||||||
|
const settingsModelingDefaultUnitCommand = page.getByText(
|
||||||
|
'Settings · modeling · default unit'
|
||||||
|
)
|
||||||
|
await settingsModelingDefaultUnitCommand.click()
|
||||||
|
|
||||||
|
const commandOption = page.getByRole('option', {
|
||||||
|
name: unitOfMeasure,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
await commandOption.click()
|
||||||
|
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
}
|
||||||
|
await changeUnitOfMeasureInCommandBar('in')
|
||||||
|
await changeUnitOfMeasureInCommandBar('ft')
|
||||||
|
await changeUnitOfMeasureInCommandBar('yd')
|
||||||
|
await changeUnitOfMeasureInCommandBar('mm')
|
||||||
|
await changeUnitOfMeasureInCommandBar('cm')
|
||||||
|
await changeUnitOfMeasureInCommandBar('m')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Change modeling default unit within gizmo', async () => {
|
||||||
|
const changeUnitOfMeasureInGizmo = async (
|
||||||
|
unitOfMeasure: string,
|
||||||
|
copy: string
|
||||||
|
) => {
|
||||||
|
const gizmo = page.getByRole('button', {
|
||||||
|
name: 'Current units are: ',
|
||||||
|
})
|
||||||
|
await gizmo.click()
|
||||||
|
const button = page.getByRole('button', {
|
||||||
|
name: copy,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
await button.click()
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
await changeUnitOfMeasureInGizmo('in', 'Inches')
|
||||||
|
await changeUnitOfMeasureInGizmo('ft', 'Feet')
|
||||||
|
await changeUnitOfMeasureInGizmo('yd', 'Yards')
|
||||||
|
await changeUnitOfMeasureInGizmo('mm', 'Millimeters')
|
||||||
|
await changeUnitOfMeasureInGizmo('cm', 'Centimeters')
|
||||||
|
await changeUnitOfMeasureInGizmo('m', 'Meters')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import {
|
||||||
|
getUtils,
|
||||||
|
setup,
|
||||||
|
tearDown,
|
||||||
|
setupElectron,
|
||||||
|
createProjectAndRenameIt,
|
||||||
|
} from './test-utils'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
await setup(context, page)
|
await setup(context, page)
|
||||||
@ -683,3 +689,60 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Text-to-CAD functionality',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Create and navigate to the project
|
||||||
|
await createProjectAndRenameIt({ name: 'test-000', page })
|
||||||
|
await page.getByTestId('project-link').click()
|
||||||
|
|
||||||
|
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeEnabled({
|
||||||
|
timeout: 20_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Open the files pane
|
||||||
|
const filesPaneButton = page.getByTestId('files-pane-button')
|
||||||
|
await filesPaneButton.click()
|
||||||
|
|
||||||
|
await test.step(`Test file creation`, async () => {
|
||||||
|
await sendPromptFromCommandBar(page, 'lego 2x4')
|
||||||
|
// File is considered created if it shows up in the Project Files pane
|
||||||
|
const file = page.getByRole('button', { name: 'lego-2x4.kcl' })
|
||||||
|
await expect(file).toBeVisible({ timeout: 20_000 })
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Test file navigation`, async () => {
|
||||||
|
const file = page.getByRole('button', { name: 'lego-2x4.kcl' })
|
||||||
|
await file.click()
|
||||||
|
const kclComment = page.getByText('Lego 2x4 Brick')
|
||||||
|
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
|
||||||
|
await expect(kclComment).toBeVisible({ timeout: 20_000 })
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Test file deletion on rejection`, async () => {
|
||||||
|
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||||
|
// A file is created and can be navigated to while this prompt is still opened
|
||||||
|
// Click the "Reject" button within the prompt and it will delete the file.
|
||||||
|
await rejectButton.click()
|
||||||
|
|
||||||
|
const submittingToastMessage = page.getByText(
|
||||||
|
`Successfully deleted file "lego-2x4.kcl"`
|
||||||
|
)
|
||||||
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -4,10 +4,17 @@ import { MakerZIP } from '@electron-forge/maker-zip'
|
|||||||
import { MakerDeb } from '@electron-forge/maker-deb'
|
import { MakerDeb } from '@electron-forge/maker-deb'
|
||||||
import { MakerRpm } from '@electron-forge/maker-rpm'
|
import { MakerRpm } from '@electron-forge/maker-rpm'
|
||||||
import { VitePlugin } from '@electron-forge/plugin-vite'
|
import { VitePlugin } from '@electron-forge/plugin-vite'
|
||||||
|
import { MakerWix, MakerWixConfig } from '@electron-forge/maker-wix'
|
||||||
import { FusesPlugin } from '@electron-forge/plugin-fuses'
|
import { FusesPlugin } from '@electron-forge/plugin-fuses'
|
||||||
import { FuseV1Options, FuseVersion } from '@electron/fuses'
|
import { FuseV1Options, FuseVersion } from '@electron/fuses'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
interface ExtendedMakerWixConfig extends MakerWixConfig {
|
||||||
|
// see https://github.com/electron/forge/issues/3673
|
||||||
|
// this is an undocumented property of electron-wix-msi
|
||||||
|
associateExtensions?: string
|
||||||
|
}
|
||||||
|
|
||||||
const rootDir = process.cwd()
|
const rootDir = process.cwd()
|
||||||
|
|
||||||
const config: ForgeConfig = {
|
const config: ForgeConfig = {
|
||||||
@ -23,12 +30,23 @@ const config: ForgeConfig = {
|
|||||||
undefined,
|
undefined,
|
||||||
executableName: 'zoo-modeling-app',
|
executableName: 'zoo-modeling-app',
|
||||||
icon: path.resolve(rootDir, 'assets', 'icon'),
|
icon: path.resolve(rootDir, 'assets', 'icon'),
|
||||||
|
protocols: [
|
||||||
|
{
|
||||||
|
name: 'Zoo Studio',
|
||||||
|
schemes: ['zoo-studio'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extendInfo: 'Info.plist', // Information for file associations.
|
||||||
},
|
},
|
||||||
rebuildConfig: {},
|
rebuildConfig: {},
|
||||||
makers: [
|
makers: [
|
||||||
new MakerSquirrel({
|
new MakerSquirrel({
|
||||||
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||||
}),
|
}),
|
||||||
|
new MakerWix({
|
||||||
|
icon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||||
|
associateExtensions: 'kcl',
|
||||||
|
} as ExtendedMakerWixConfig),
|
||||||
new MakerZIP({}, ['darwin']),
|
new MakerZIP({}, ['darwin']),
|
||||||
new MakerRpm({
|
new MakerRpm({
|
||||||
options: {
|
options: {
|
||||||
|
1
interface.d.ts
vendored
@ -31,6 +31,7 @@ export interface IElectronAPI {
|
|||||||
sep: typeof path.sep
|
sep: typeof path.sep
|
||||||
rename: (prev: string, next: string) => typeof fs.rename
|
rename: (prev: string, next: string) => typeof fs.rename
|
||||||
setBaseUrl: (value: string) => void
|
setBaseUrl: (value: string) => void
|
||||||
|
loadProjectAtStartup: () => Promise<ProjectState | null>
|
||||||
packageJson: {
|
packageJson: {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
12
package.json
@ -26,7 +26,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^2.0.0",
|
"@kittycad/lib": "^2.0.1",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@lezer/lr": "^1.4.1",
|
"@lezer/lr": "^1.4.1",
|
||||||
"@react-hook/resize-observer": "^2.0.1",
|
"@react-hook/resize-observer": "^2.0.1",
|
||||||
@ -44,6 +44,7 @@
|
|||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
"openid-client": "^5.6.5",
|
"openid-client": "^5.6.5",
|
||||||
"re-resizable": "^6.9.11",
|
"re-resizable": "^6.9.11",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@ -88,8 +89,8 @@
|
|||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src e2e",
|
"lint": "eslint --fix src e2e",
|
||||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||||
"postinstall": "yarn xstate:typegen",
|
"postinstall": "yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||||
"make:dev": "make dev",
|
"make:dev": "make dev",
|
||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
@ -119,16 +120,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@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/cli": "^7.4.0",
|
||||||
"@electron-forge/maker-deb": "^7.4.0",
|
"@electron-forge/maker-deb": "^7.4.0",
|
||||||
"@electron-forge/maker-rpm": "^7.4.0",
|
"@electron-forge/maker-rpm": "^7.4.0",
|
||||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||||
|
"@electron-forge/maker-wix": "^7.4.0",
|
||||||
"@electron-forge/maker-zip": "^7.4.0",
|
"@electron-forge/maker-zip": "^7.4.0",
|
||||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||||
"@electron-forge/plugin-fuses": "^7.4.0",
|
"@electron-forge/plugin-fuses": "^7.4.0",
|
||||||
"@electron-forge/plugin-vite": "^7.4.0",
|
"@electron-forge/plugin-vite": "^7.4.0",
|
||||||
"@electron/fuses": "^1.8.0",
|
"@electron/fuses": "^1.8.0",
|
||||||
|
"@electron/rebuild": "^3.6.0",
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@lezer/generator": "^1.7.1",
|
"@lezer/generator": "^1.7.1",
|
||||||
"@playwright/test": "^1.46.1",
|
"@playwright/test": "^1.46.1",
|
||||||
@ -137,6 +140,7 @@
|
|||||||
"@types/d3-force": "^3.0.10",
|
"@types/d3-force": "^3.0.10",
|
||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/isomorphic-fetch": "^0.0.39",
|
"@types/isomorphic-fetch": "^0.0.39",
|
||||||
|
"@types/minimist": "^1.2.5",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.6",
|
||||||
"@types/node": "^22.5.0",
|
"@types/node": "^22.5.0",
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
|
@ -33,7 +33,6 @@ import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
|||||||
import LspProvider from 'components/LspProvider'
|
import LspProvider from 'components/LspProvider'
|
||||||
import { KclContextProvider } from 'lang/KclProvider'
|
import { KclContextProvider } from 'lang/KclProvider'
|
||||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
import { getState, setState } from 'lib/desktop'
|
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
@ -71,17 +70,13 @@ const router = createRouter([
|
|||||||
loader: async () => {
|
loader: async () => {
|
||||||
const onDesktop = isDesktop()
|
const onDesktop = isDesktop()
|
||||||
if (onDesktop) {
|
if (onDesktop) {
|
||||||
const appState = await getState()
|
const projectStartupFile =
|
||||||
|
await window.electron.loadProjectAtStartup()
|
||||||
if (appState) {
|
if (projectStartupFile !== null) {
|
||||||
// Reset the state.
|
|
||||||
// We do this so that we load the initial state from the cli but everything
|
|
||||||
// else we can ignore.
|
|
||||||
await setState(undefined)
|
|
||||||
// Redirect to the file if we have a file path.
|
// Redirect to the file if we have a file path.
|
||||||
if (appState.current_file) {
|
if (projectStartupFile.length > 0) {
|
||||||
return redirect(
|
return redirect(
|
||||||
PATHS.FILE + '/' + encodeURIComponent(appState.current_file)
|
PATHS.FILE + '/' + encodeURIComponent(projectStartupFile)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { MouseGuard } from 'lib/cameraControls'
|
import { cameraMouseDragGuards, MouseGuard } from 'lib/cameraControls'
|
||||||
import {
|
import {
|
||||||
Euler,
|
Euler,
|
||||||
MathUtils,
|
MathUtils,
|
||||||
@ -81,24 +81,7 @@ export class CameraControls {
|
|||||||
pendingZoom: number | null = null
|
pendingZoom: number | null = null
|
||||||
pendingRotation: Vector2 | null = null
|
pendingRotation: Vector2 | null = null
|
||||||
pendingPan: Vector2 | null = null
|
pendingPan: Vector2 | null = null
|
||||||
interactionGuards: MouseGuard = {
|
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
||||||
pan: {
|
|
||||||
description: 'Right click + Shift + drag or middle click + drag',
|
|
||||||
callback: (e) => !!(e.buttons & 4) && !e.ctrlKey,
|
|
||||||
},
|
|
||||||
zoom: {
|
|
||||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
|
||||||
dragCallback: (e) => e.button === 2 && e.ctrlKey,
|
|
||||||
scrollCallback: () => true,
|
|
||||||
},
|
|
||||||
rotate: {
|
|
||||||
description: 'Right click + drag',
|
|
||||||
callback: (e) => {
|
|
||||||
console.log('event', e)
|
|
||||||
return !!(e.buttons & 2)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
fovBeforeOrtho = 45
|
fovBeforeOrtho = 45
|
||||||
get isPerspective() {
|
get isPerspective() {
|
||||||
|
@ -4,4 +4,21 @@
|
|||||||
*/
|
*/
|
||||||
.header {
|
.header {
|
||||||
grid-template-columns: 1fr auto 1fr;
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
-webkit-app-region: drag; /* Make the header of the app draggable */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header button {
|
||||||
|
-webkit-app-region: no-drag; /* Make the button not draggable */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header a {
|
||||||
|
-webkit-app-region: no-drag; /* Make the link not draggable */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header textarea {
|
||||||
|
-webkit-app-region: no-drag; /* Make the textarea not draggable */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header input {
|
||||||
|
-webkit-app-region: no-drag; /* Make the input not draggable */
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
|||||||
import { CustomIcon } from 'components/CustomIcon'
|
import { CustomIcon } from 'components/CustomIcon'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
|
|
||||||
|
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
||||||
|
|
||||||
export const CommandBar = () => {
|
export const CommandBar = () => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||||
@ -24,7 +26,7 @@ export const CommandBar = () => {
|
|||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
// Hook up keyboard shortcuts
|
// Hook up keyboard shortcuts
|
||||||
useHotkeyWrapper(['mod+k'], () => {
|
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
||||||
if (commandBarState.context.commands.length === 0) return
|
if (commandBarState.context.commands.length === 0) return
|
||||||
if (commandBarState.matches('Closed')) {
|
if (commandBarState.matches('Closed')) {
|
||||||
commandBarSend({ type: 'Open' })
|
commandBarSend({ type: 'Open' })
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
|
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||||
|
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
|
||||||
|
|
||||||
export function CommandBarOpenButton() {
|
export function CommandBarOpenButton() {
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
@ -12,7 +14,7 @@ export function CommandBarOpenButton() {
|
|||||||
>
|
>
|
||||||
<span>Commands</span>
|
<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">
|
<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>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { FileEntry, IndexLoaderData } from 'lib/types'
|
import type { IndexLoaderData } from 'lib/types'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
@ -20,6 +20,7 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { DeleteConfirmationDialog } from './ProjectCard/DeleteProjectDialog'
|
import { DeleteConfirmationDialog } from './ProjectCard/DeleteProjectDialog'
|
||||||
import { ContextMenu, ContextMenuItem } from './ContextMenu'
|
import { ContextMenu, ContextMenuItem } from './ContextMenu'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
|
import { FileEntry } from 'lib/project'
|
||||||
|
|
||||||
function getIndentationCSS(level: number) {
|
function getIndentationCSS(level: number) {
|
||||||
return `calc(1rem * ${level + 1})`
|
return `calc(1rem * ${level + 1})`
|
||||||
|
@ -9,7 +9,7 @@ import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
|||||||
import { coreDump } from 'lang/wasm'
|
import { coreDump } from 'lang/wasm'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import openWindow from 'lib/openWindow'
|
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
||||||
|
|
||||||
export function LowerRightControls({
|
export function LowerRightControls({
|
||||||
@ -66,6 +66,9 @@ export function LowerRightControls({
|
|||||||
{children}
|
{children}
|
||||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||||
<a
|
<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}`}
|
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
@ -15,7 +15,7 @@ import { Extension } from '@codemirror/state'
|
|||||||
import { LanguageSupport } from '@codemirror/language'
|
import { LanguageSupport } from '@codemirror/language'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { FileEntry } from 'lib/types'
|
import { FileEntry } from 'lib/project'
|
||||||
import Worker from 'editor/plugins/lsp/worker.ts?worker'
|
import Worker from 'editor/plugins/lsp/worker.ts?worker'
|
||||||
import {
|
import {
|
||||||
KclWorkerOptions,
|
KclWorkerOptions,
|
||||||
|
@ -7,7 +7,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
|
|||||||
import Tooltip from '../Tooltip'
|
import Tooltip from '../Tooltip'
|
||||||
import { DeleteConfirmationDialog } from './DeleteProjectDialog'
|
import { DeleteConfirmationDialog } from './DeleteProjectDialog'
|
||||||
import { ProjectCardRenameForm } from './ProjectCardRenameForm'
|
import { ProjectCardRenameForm } from './ProjectCardRenameForm'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
function ProjectCard({
|
function ProjectCard({
|
||||||
project,
|
project,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
import { HTMLProps, forwardRef } from 'react'
|
import { HTMLProps, forwardRef } from 'react'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
interface ProjectCardRenameFormProps extends HTMLProps<HTMLFormElement> {
|
interface ProjectCardRenameFormProps extends HTMLProps<HTMLFormElement> {
|
||||||
project: Project
|
project: Project
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'lib/project'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
@ -3,7 +3,7 @@ import { BrowserRouter } from 'react-router-dom'
|
|||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const projectWellFormed = {
|
const projectWellFormed = {
|
||||||
|
@ -25,8 +25,17 @@ const ProjectSidebarMenu = ({
|
|||||||
project?: IndexLoaderData['project']
|
project?: IndexLoaderData['project']
|
||||||
file?: IndexLoaderData['file']
|
file?: IndexLoaderData['file']
|
||||||
}) => {
|
}) => {
|
||||||
|
// Make room for traffic lights on desktop left side.
|
||||||
|
// TODO: make sure this doesn't look like shit on Linux or Windows
|
||||||
|
const trafficLightsOffset =
|
||||||
|
isDesktop() && window.electron.os.isMac ? 'ml-20' : ''
|
||||||
return (
|
return (
|
||||||
<div className="!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2">
|
<div
|
||||||
|
className={
|
||||||
|
'!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2 ' +
|
||||||
|
trafficLightsOffset
|
||||||
|
}
|
||||||
|
>
|
||||||
<AppLogoLink project={project} file={file} />
|
<AppLogoLink project={project} file={file} />
|
||||||
{enableMenu ? (
|
{enableMenu ? (
|
||||||
<ProjectMenuPopover project={project} file={file} />
|
<ProjectMenuPopover project={project} file={file} />
|
||||||
|
@ -399,6 +399,9 @@ export class KclManager {
|
|||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
// Write back to the file system.
|
// Write back to the file system.
|
||||||
codeManager.writeToFile()
|
codeManager.writeToFile()
|
||||||
|
|
||||||
|
// execute the code.
|
||||||
|
this.executeCode()
|
||||||
}
|
}
|
||||||
// There's overlapping responsibility between updateAst and executeAst.
|
// There's overlapping responsibility between updateAst and executeAst.
|
||||||
// updateAst was added as it was used a lot before xState migration so makes the port easier.
|
// updateAst was added as it was used a lot before xState migration so makes the port easier.
|
||||||
|
@ -6,9 +6,13 @@ import {
|
|||||||
Expr,
|
Expr,
|
||||||
Program,
|
Program,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
|
makeDefaultPlanes,
|
||||||
|
PipeExpression,
|
||||||
|
VariableDeclaration,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
addFillet,
|
addFillet,
|
||||||
|
getPathToExtrudeForSegmentSelection,
|
||||||
hasValidFilletSelection,
|
hasValidFilletSelection,
|
||||||
isTagUsedInFillet,
|
isTagUsedInFillet,
|
||||||
} from './addFillet'
|
} from './addFillet'
|
||||||
@ -16,9 +20,204 @@ import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
|||||||
import { createLiteral } from 'lang/modifyAst'
|
import { createLiteral } from 'lang/modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
|
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise // Initialize the WASM environment before running tests
|
await initPromise
|
||||||
|
|
||||||
|
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
engineCommandManager.start({
|
||||||
|
token: VITE_KC_DEV_TOKEN,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
|
||||||
|
setMediaStream: () => {},
|
||||||
|
setIsStreamReady: () => {},
|
||||||
|
modifyGrid: async () => {},
|
||||||
|
callbackOnEngineLiteConnect: async () => {
|
||||||
|
resolve(true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, 20_000)
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
engineCommandManager.tearDown()
|
||||||
|
})
|
||||||
|
|
||||||
|
const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||||
|
code: string,
|
||||||
|
selectedSegmentSnippet: string,
|
||||||
|
expectedExtrudeSnippet: string
|
||||||
|
) => {
|
||||||
|
// helpers
|
||||||
|
function getExtrudeExpression(
|
||||||
|
ast: Program,
|
||||||
|
pathToExtrudeNode: PathToNode
|
||||||
|
): CallExpression | PipeExpression | undefined | Error {
|
||||||
|
if (pathToExtrudeNode.length === 0) return undefined // no extrude node
|
||||||
|
|
||||||
|
const extrudeNodeResult = getNodeFromPath(ast, pathToExtrudeNode)
|
||||||
|
if (err(extrudeNodeResult)) {
|
||||||
|
return extrudeNodeResult
|
||||||
|
}
|
||||||
|
return extrudeNodeResult.node as CallExpression | PipeExpression
|
||||||
|
}
|
||||||
|
function getExpectedExtrudeExpression(
|
||||||
|
ast: Program,
|
||||||
|
code: string,
|
||||||
|
expectedExtrudeSnippet: string
|
||||||
|
): CallExpression | PipeExpression | Error {
|
||||||
|
const extrudeRange: [number, number] = [
|
||||||
|
code.indexOf(expectedExtrudeSnippet),
|
||||||
|
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
||||||
|
]
|
||||||
|
const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
|
const expedtedExtrudeNodeResult = getNodeFromPath(ast, expedtedExtrudePath)
|
||||||
|
if (err(expedtedExtrudeNodeResult)) {
|
||||||
|
return expedtedExtrudeNodeResult
|
||||||
|
}
|
||||||
|
const expectedExtrudeNode =
|
||||||
|
expedtedExtrudeNodeResult.node as VariableDeclaration
|
||||||
|
return expectedExtrudeNode.declarations[0].init as
|
||||||
|
| CallExpression
|
||||||
|
| PipeExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
// ast
|
||||||
|
const astOrError = parse(code)
|
||||||
|
if (err(astOrError)) return new Error('AST not found')
|
||||||
|
const ast = astOrError as Program
|
||||||
|
|
||||||
|
// selection
|
||||||
|
const segmentRange: [number, number] = [
|
||||||
|
code.indexOf(selectedSegmentSnippet),
|
||||||
|
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||||
|
]
|
||||||
|
const selection: Selections = {
|
||||||
|
codeBasedSelections: [
|
||||||
|
{
|
||||||
|
range: segmentRange,
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
// programMemory and artifactGraph
|
||||||
|
await kclManager.executeAst({ ast })
|
||||||
|
const programMemory = kclManager.programMemory
|
||||||
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
|
||||||
|
// get extrude expression
|
||||||
|
const pathResult = getPathToExtrudeForSegmentSelection(
|
||||||
|
ast,
|
||||||
|
selection,
|
||||||
|
programMemory,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
|
if (err(pathResult)) return pathResult
|
||||||
|
const { pathToExtrudeNode } = pathResult
|
||||||
|
const extrudeExpression = getExtrudeExpression(ast, pathToExtrudeNode)
|
||||||
|
|
||||||
|
// test
|
||||||
|
if (expectedExtrudeSnippet) {
|
||||||
|
const expectedExtrudeExpression = getExpectedExtrudeExpression(
|
||||||
|
ast,
|
||||||
|
code,
|
||||||
|
expectedExtrudeSnippet
|
||||||
|
)
|
||||||
|
if (err(expectedExtrudeExpression)) return expectedExtrudeExpression
|
||||||
|
expect(extrudeExpression).toEqual(expectedExtrudeExpression)
|
||||||
|
} else {
|
||||||
|
expect(extrudeExpression).toBeUndefined()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe('Testing getPathToExtrudeForSegmentSelection', () => {
|
||||||
|
it('should return the correct paths for a valid selection and extrusion', async () => {
|
||||||
|
const code = `const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(-15, sketch001)`
|
||||||
|
const selectedSegmentSnippet = `line([20, 0], %)`
|
||||||
|
const expectedExtrudeSnippet = `const extrude001 = extrude(-15, sketch001)`
|
||||||
|
await runGetPathToExtrudeForSegmentSelectionTest(
|
||||||
|
code,
|
||||||
|
selectedSegmentSnippet,
|
||||||
|
expectedExtrudeSnippet
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
|
||||||
|
const code = `const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-30, 30], %)
|
||||||
|
|> line([15, 0], %)
|
||||||
|
|> line([0, -15], %)
|
||||||
|
|> line([-15, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch002 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([30, 30], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch003 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([30, -30], %)
|
||||||
|
|> line([25, 0], %)
|
||||||
|
|> line([0, -25], %)
|
||||||
|
|> line([-25, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(-15, sketch001)
|
||||||
|
const extrude002 = extrude(-15, sketch002)
|
||||||
|
const extrude003 = extrude(-15, sketch003)`
|
||||||
|
const selectedSegmentSnippet = `line([20, 0], %)`
|
||||||
|
const expectedExtrudeSnippet = `const extrude002 = extrude(-15, sketch002)`
|
||||||
|
await runGetPathToExtrudeForSegmentSelectionTest(
|
||||||
|
code,
|
||||||
|
selectedSegmentSnippet,
|
||||||
|
expectedExtrudeSnippet
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should not return any path for missing extrusion', async () => {
|
||||||
|
const code = `const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-30, 30], %)
|
||||||
|
|> line([15, 0], %)
|
||||||
|
|> line([0, -15], %)
|
||||||
|
|> line([-15, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch002 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([30, 30], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch003 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([30, -30], %)
|
||||||
|
|> line([25, 0], %)
|
||||||
|
|> line([0, -25], %)
|
||||||
|
|> line([-25, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(-15, sketch001)
|
||||||
|
const extrude003 = extrude(-15, sketch003)`
|
||||||
|
const selectedSegmentSnippet = `line([20, 0], %)`
|
||||||
|
const expectedExtrudeSnippet = ``
|
||||||
|
await runGetPathToExtrudeForSegmentSelectionTest(
|
||||||
|
code,
|
||||||
|
selectedSegmentSnippet,
|
||||||
|
expectedExtrudeSnippet
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const runFilletTest = async (
|
const runFilletTest = async (
|
||||||
@ -57,8 +256,6 @@ const runFilletTest = async (
|
|||||||
return new Error('Path to extrude node not found')
|
return new Error('Path to extrude node not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
// const radius = createLiteral(5) as Expr
|
|
||||||
|
|
||||||
const result = addFillet(ast, pathToSegmentNode, pathToExtrudeNode, radius)
|
const result = addFillet(ast, pathToSegmentNode, pathToExtrudeNode, radius)
|
||||||
if (err(result)) {
|
if (err(result)) {
|
||||||
return result
|
return result
|
||||||
@ -68,7 +265,6 @@ const runFilletTest = async (
|
|||||||
|
|
||||||
expect(newCode).toContain(expectedCode)
|
expect(newCode).toContain(expectedCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Testing addFillet', () => {
|
describe('Testing addFillet', () => {
|
||||||
/**
|
/**
|
||||||
* 1. Ideal Case
|
* 1. Ideal Case
|
||||||
|
@ -4,9 +4,11 @@ import {
|
|||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Program,
|
Program,
|
||||||
|
ProgramMemory,
|
||||||
Expr,
|
Expr,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
|
sketchGroupFromKclValue,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
createCallExpressionStdLib,
|
createCallExpressionStdLib,
|
||||||
@ -28,62 +30,210 @@ import {
|
|||||||
getTagFromCallExpression,
|
getTagFromCallExpression,
|
||||||
sketchLineHelperMap,
|
sketchLineHelperMap,
|
||||||
} from '../std/sketch'
|
} from '../std/sketch'
|
||||||
import { err } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { Selections, canFilletSelection } from 'lib/selections'
|
import { Selections, canFilletSelection } from 'lib/selections'
|
||||||
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
|
import {
|
||||||
|
ArtifactGraph,
|
||||||
|
getExtrusionFromSuspectedPath,
|
||||||
|
} from 'lang/std/artifactGraph'
|
||||||
|
import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Fillet To Selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function applyFilletToSelection(
|
||||||
|
selection: Selections,
|
||||||
|
radius: KclCommandValue
|
||||||
|
): void | Error {
|
||||||
|
// 1. get AST
|
||||||
|
let ast = kclManager.ast
|
||||||
|
const astResult = insertRadiusIntoAst(ast, radius)
|
||||||
|
if (err(astResult)) return astResult
|
||||||
|
|
||||||
|
// 2. get path
|
||||||
|
const programMemory = kclManager.programMemory
|
||||||
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
const getPathToExtrudeForSegmentSelectionResult =
|
||||||
|
getPathToExtrudeForSegmentSelection(
|
||||||
|
ast,
|
||||||
|
selection,
|
||||||
|
programMemory,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
|
if (err(getPathToExtrudeForSegmentSelectionResult))
|
||||||
|
return getPathToExtrudeForSegmentSelectionResult
|
||||||
|
const { pathToSegmentNode, pathToExtrudeNode } =
|
||||||
|
getPathToExtrudeForSegmentSelectionResult
|
||||||
|
|
||||||
|
// 3. add fillet
|
||||||
|
const addFilletResult = addFillet(
|
||||||
|
ast,
|
||||||
|
pathToSegmentNode,
|
||||||
|
pathToExtrudeNode,
|
||||||
|
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
|
||||||
|
)
|
||||||
|
if (trap(addFilletResult)) return addFilletResult
|
||||||
|
const { modifiedAst, pathToFilletNode } = addFilletResult
|
||||||
|
|
||||||
|
// 4. update ast
|
||||||
|
updateAstAndFocus(modifiedAst, pathToFilletNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertRadiusIntoAst(
|
||||||
|
ast: Program,
|
||||||
|
radius: KclCommandValue
|
||||||
|
): { ast: Program } | Error {
|
||||||
|
try {
|
||||||
|
// Validate and update AST
|
||||||
|
if (
|
||||||
|
'variableName' in radius &&
|
||||||
|
radius.variableName &&
|
||||||
|
radius.insertIndex !== undefined
|
||||||
|
) {
|
||||||
|
const newAst = structuredClone(ast)
|
||||||
|
newAst.body.splice(radius.insertIndex, 0, radius.variableDeclarationAst)
|
||||||
|
return { ast: newAst }
|
||||||
|
}
|
||||||
|
return { ast }
|
||||||
|
} catch (error) {
|
||||||
|
return new Error(`Failed to handle AST: ${(error as Error).message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPathToExtrudeForSegmentSelection(
|
||||||
|
ast: Program,
|
||||||
|
selection: Selections,
|
||||||
|
programMemory: ProgramMemory,
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
||||||
|
const pathToSegmentNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
selection.codeBasedSelections[0].range
|
||||||
|
)
|
||||||
|
|
||||||
|
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
||||||
|
ast,
|
||||||
|
pathToSegmentNode,
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (err(varDecNode)) return varDecNode
|
||||||
|
const sketchVar = varDecNode.node.declarations[0].id.name
|
||||||
|
|
||||||
|
const sketchGroup = sketchGroupFromKclValue(
|
||||||
|
kclManager.programMemory.get(sketchVar),
|
||||||
|
sketchVar
|
||||||
|
)
|
||||||
|
if (trap(sketchGroup)) return sketchGroup
|
||||||
|
|
||||||
|
const extrusion = getExtrusionFromSuspectedPath(sketchGroup.id, artifactGraph)
|
||||||
|
if (err(extrusion)) return extrusion
|
||||||
|
|
||||||
|
const pathToExtrudeNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
extrusion.codeRef.range
|
||||||
|
)
|
||||||
|
if (err(pathToExtrudeNode)) return pathToExtrudeNode
|
||||||
|
|
||||||
|
return { pathToSegmentNode, pathToExtrudeNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAstAndFocus(
|
||||||
|
modifiedAst: Program,
|
||||||
|
pathToFilletNode: PathToNode
|
||||||
|
) {
|
||||||
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
|
focusPath: pathToFilletNode,
|
||||||
|
})
|
||||||
|
if (updatedAst?.selections) {
|
||||||
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Fillet
|
||||||
|
*/
|
||||||
|
|
||||||
export function addFillet(
|
export function addFillet(
|
||||||
node: Program,
|
ast: Program,
|
||||||
pathToSegmentNode: PathToNode,
|
pathToSegmentNode: PathToNode,
|
||||||
pathToExtrudeNode: PathToNode,
|
pathToExtrudeNode: PathToNode,
|
||||||
radius = createLiteral(5) as Expr
|
radius: Expr = createLiteral(5)
|
||||||
// shouldPipe = false, // TODO: Implement this feature
|
|
||||||
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
|
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
|
||||||
// clone ast to make mutations safe
|
// Clone AST to ensure safe mutations
|
||||||
let _node = structuredClone(node)
|
const astClone = structuredClone(ast)
|
||||||
|
|
||||||
/**
|
// Modify AST clone : TAG the sketch segment and retrieve tag
|
||||||
* Add Tag to the Segment Expression
|
const segmentResult = mutateAstWithTagForSketchSegment(
|
||||||
*/
|
astClone,
|
||||||
|
pathToSegmentNode
|
||||||
|
)
|
||||||
|
if (err(segmentResult)) return segmentResult
|
||||||
|
const { tag } = segmentResult
|
||||||
|
|
||||||
// Find the specific sketch segment to tag with the new tag
|
// Modify AST clone : Insert FILLET node and retrieve path to fillet
|
||||||
const sketchSegmentChunk = getNodeFromPath(
|
const filletResult = mutateAstWithFilletNode(
|
||||||
_node,
|
astClone,
|
||||||
|
pathToExtrudeNode,
|
||||||
|
radius,
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
if (err(filletResult)) return filletResult
|
||||||
|
const { pathToFilletNode } = filletResult
|
||||||
|
|
||||||
|
return { modifiedAst: astClone, pathToFilletNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
function mutateAstWithTagForSketchSegment(
|
||||||
|
astClone: Program,
|
||||||
|
pathToSegmentNode: PathToNode
|
||||||
|
): { modifiedAst: Program; tag: string } | Error {
|
||||||
|
const segmentNode = getNodeFromPath<CallExpression>(
|
||||||
|
astClone,
|
||||||
pathToSegmentNode,
|
pathToSegmentNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
)
|
)
|
||||||
if (err(sketchSegmentChunk)) return sketchSegmentChunk
|
if (err(segmentNode)) return segmentNode
|
||||||
const { node: sketchSegmentNode } = sketchSegmentChunk as {
|
|
||||||
node: CallExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether selection is a valid segment from sketchLineHelpersMap
|
// Check whether selection is a valid segment
|
||||||
if (!(sketchSegmentNode.callee.name in sketchLineHelperMap)) {
|
if (!(segmentNode.node.callee.name in sketchLineHelperMap)) {
|
||||||
return new Error('Selection is not a sketch segment')
|
return new Error('Selection is not a sketch segment')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add tag to the sketch segment or use existing tag
|
// Add tag to the sketch segment or use existing tag
|
||||||
|
// a helper function that creates the updated node and applies the changes to the AST
|
||||||
const taggedSegment = addTagForSketchOnFace(
|
const taggedSegment = addTagForSketchOnFace(
|
||||||
{
|
{
|
||||||
// previousProgramMemory: programMemory,
|
|
||||||
pathToNode: pathToSegmentNode,
|
pathToNode: pathToSegmentNode,
|
||||||
node: _node,
|
node: astClone,
|
||||||
},
|
},
|
||||||
sketchSegmentNode.callee.name
|
segmentNode.node.callee.name
|
||||||
)
|
)
|
||||||
if (err(taggedSegment)) return taggedSegment
|
if (err(taggedSegment)) return taggedSegment
|
||||||
const { tag } = taggedSegment
|
const { tag } = taggedSegment
|
||||||
|
|
||||||
/**
|
return { modifiedAst: astClone, tag }
|
||||||
* Find Extrude Expression automatically
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// 1. Get the sketch name
|
function mutateAstWithFilletNode(
|
||||||
|
astClone: Program,
|
||||||
|
pathToExtrudeNode: PathToNode,
|
||||||
|
radius: Expr,
|
||||||
|
tag: string
|
||||||
|
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
|
||||||
|
// Locate the extrude call
|
||||||
|
const locatedExtrudeDeclarator = locateExtrudeDeclarator(
|
||||||
|
astClone,
|
||||||
|
pathToExtrudeNode
|
||||||
|
)
|
||||||
|
if (err(locatedExtrudeDeclarator)) return locatedExtrudeDeclarator
|
||||||
|
const { extrudeDeclarator } = locatedExtrudeDeclarator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add Fillet to the Extrude expression
|
* Prepare changes to the AST
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Create the fillet call expression in one line
|
|
||||||
const filletCall = createCallExpressionStdLib('fillet', [
|
const filletCall = createCallExpressionStdLib('fillet', [
|
||||||
createObjectExpression({
|
createObjectExpression({
|
||||||
radius: radius,
|
radius: radius,
|
||||||
@ -92,104 +242,36 @@ export function addFillet(
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
|
||||||
// Locate the extrude call
|
/**
|
||||||
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
* Mutate the AST
|
||||||
_node,
|
*/
|
||||||
pathToExtrudeNode,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (err(extrudeChunk)) return extrudeChunk
|
|
||||||
const { node: extrudeVarDecl } = extrudeChunk
|
|
||||||
|
|
||||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
|
||||||
const extrudeInit = extrudeDeclarator.init
|
|
||||||
|
|
||||||
if (
|
|
||||||
!extrudeDeclarator ||
|
|
||||||
(extrudeInit.type !== 'CallExpression' &&
|
|
||||||
extrudeInit.type !== 'PipeExpression')
|
|
||||||
) {
|
|
||||||
return new Error('Extrude PipeExpression / CallExpression not found.')
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine if extrude is in a PipeExpression or CallExpression
|
|
||||||
|
|
||||||
// CallExpression - no fillet
|
// CallExpression - no fillet
|
||||||
// PipeExpression - fillet exists
|
// PipeExpression - fillet exists
|
||||||
|
|
||||||
const getPathToNodeOfFilletLiteral = (
|
let pathToFilletNode: PathToNode = []
|
||||||
pathToExtrudeNode: PathToNode,
|
|
||||||
extrudeDeclarator: VariableDeclarator,
|
if (extrudeDeclarator.init.type === 'CallExpression') {
|
||||||
tag: string
|
// 1. case when no fillet exists
|
||||||
): PathToNode => {
|
|
||||||
let pathToFilletObj: any
|
// modify ast with new fillet call by mutating the extrude node
|
||||||
let inFillet = false
|
extrudeDeclarator.init = createPipeExpression([
|
||||||
traverse(extrudeDeclarator.init, {
|
extrudeDeclarator.init,
|
||||||
enter(node, path) {
|
filletCall,
|
||||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
])
|
||||||
inFillet = true
|
|
||||||
}
|
// get path to the fillet node
|
||||||
if (inFillet && node.type === 'ObjectExpression') {
|
pathToFilletNode = getPathToNodeOfFilletLiteral(
|
||||||
const hasTag = node.properties.some((prop) => {
|
pathToExtrudeNode,
|
||||||
const isTagProp = prop.key.name === 'tags'
|
extrudeDeclarator,
|
||||||
if (isTagProp && prop.value.type === 'ArrayExpression') {
|
tag
|
||||||
return prop.value.elements.some(
|
|
||||||
(element) =>
|
|
||||||
element.type === 'Identifier' && element.name === tag
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if (!hasTag) return false
|
|
||||||
pathToFilletObj = path
|
|
||||||
node.properties.forEach((prop, index) => {
|
|
||||||
if (prop.key.name === 'radius') {
|
|
||||||
pathToFilletObj.push(
|
|
||||||
['properties', 'ObjectExpression'],
|
|
||||||
[index, 'index'],
|
|
||||||
['value', 'Property']
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leave(node) {
|
|
||||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
|
||||||
inFillet = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
let indexOfPipeExpression = pathToExtrudeNode.findIndex(
|
|
||||||
(path) => path[1] === 'PipeExpression'
|
|
||||||
)
|
)
|
||||||
indexOfPipeExpression =
|
|
||||||
indexOfPipeExpression === -1
|
|
||||||
? pathToExtrudeNode.length
|
|
||||||
: indexOfPipeExpression
|
|
||||||
|
|
||||||
return [
|
return { modifiedAst: astClone, pathToFilletNode }
|
||||||
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
|
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
||||||
...pathToFilletObj,
|
// 2. case when fillet exists
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extrudeInit.type === 'CallExpression') {
|
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
|
||||||
// 1. no fillet case
|
|
||||||
extrudeDeclarator.init = createPipeExpression([extrudeInit, filletCall])
|
|
||||||
return {
|
|
||||||
modifiedAst: _node,
|
|
||||||
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
|
||||||
pathToExtrudeNode,
|
|
||||||
extrudeDeclarator,
|
|
||||||
tag
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else if (extrudeInit.type === 'PipeExpression') {
|
|
||||||
// 2. fillet case
|
|
||||||
|
|
||||||
// there are 2 options here:
|
|
||||||
|
|
||||||
const existingFilletCall = extrudeInit.body.find((node) => {
|
|
||||||
return node.type === 'CallExpression' && node.callee.name === 'fillet'
|
return node.type === 'CallExpression' && node.callee.name === 'fillet'
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -198,25 +280,13 @@ export function addFillet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if the existing fillet has the same tag as the new fillet
|
// check if the existing fillet has the same tag as the new fillet
|
||||||
let filletTag = null
|
const filletTag = getFilletTag(existingFilletCall)
|
||||||
if (existingFilletCall.arguments[0].type === 'ObjectExpression') {
|
|
||||||
const properties = (existingFilletCall.arguments[0] as ObjectExpression)
|
|
||||||
.properties
|
|
||||||
const tagsProperty = properties.find((prop) => prop.key.name === 'tags')
|
|
||||||
if (tagsProperty && tagsProperty.value.type === 'ArrayExpression') {
|
|
||||||
const elements = (tagsProperty.value as ArrayExpression).elements
|
|
||||||
if (elements.length > 0 && elements[0].type === 'Identifier') {
|
|
||||||
filletTag = elements[0].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Error('Expected an ObjectExpression node')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filletTag !== tag) {
|
if (filletTag !== tag) {
|
||||||
extrudeInit.body.push(filletCall)
|
// mutate the extrude node with the new fillet call
|
||||||
|
extrudeDeclarator.init.body.push(filletCall)
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: astClone,
|
||||||
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
||||||
pathToExtrudeNode,
|
pathToExtrudeNode,
|
||||||
extrudeDeclarator,
|
extrudeDeclarator,
|
||||||
@ -228,9 +298,124 @@ export function addFillet(
|
|||||||
return new Error('Unsupported extrude type.')
|
return new Error('Unsupported extrude type.')
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Error('Unsupported extrude type.')
|
return { modifiedAst: astClone, pathToFilletNode }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function locateExtrudeDeclarator(
|
||||||
|
node: Program,
|
||||||
|
pathToExtrudeNode: PathToNode
|
||||||
|
): { extrudeDeclarator: VariableDeclarator } | Error {
|
||||||
|
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
||||||
|
node,
|
||||||
|
pathToExtrudeNode,
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (err(extrudeChunk)) return extrudeChunk
|
||||||
|
|
||||||
|
const { node: extrudeVarDecl } = extrudeChunk
|
||||||
|
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||||
|
if (!extrudeDeclarator) {
|
||||||
|
return new Error('Extrude Declarator not found.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const extrudeInit = extrudeDeclarator?.init
|
||||||
|
if (!extrudeInit) {
|
||||||
|
return new Error('Extrude Init not found.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
extrudeInit.type !== 'CallExpression' &&
|
||||||
|
extrudeInit.type !== 'PipeExpression'
|
||||||
|
) {
|
||||||
|
return new Error('Extrude must be a PipeExpression or CallExpression')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { extrudeDeclarator }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathToNodeOfFilletLiteral(
|
||||||
|
pathToExtrudeNode: PathToNode,
|
||||||
|
extrudeDeclarator: VariableDeclarator,
|
||||||
|
tag: string
|
||||||
|
): PathToNode {
|
||||||
|
let pathToFilletObj: PathToNode = []
|
||||||
|
let inFillet = false
|
||||||
|
|
||||||
|
traverse(extrudeDeclarator.init, {
|
||||||
|
enter(node, path) {
|
||||||
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||||
|
inFillet = true
|
||||||
|
}
|
||||||
|
if (inFillet && node.type === 'ObjectExpression') {
|
||||||
|
if (!hasTag(node, tag)) return false
|
||||||
|
pathToFilletObj = getPathToRadiusLiteral(node, path)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leave(node) {
|
||||||
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||||
|
inFillet = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
let indexOfPipeExpression = pathToExtrudeNode.findIndex(
|
||||||
|
(path) => path[1] === 'PipeExpression'
|
||||||
|
)
|
||||||
|
|
||||||
|
indexOfPipeExpression =
|
||||||
|
indexOfPipeExpression === -1
|
||||||
|
? pathToExtrudeNode.length
|
||||||
|
: indexOfPipeExpression
|
||||||
|
|
||||||
|
return [
|
||||||
|
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
|
||||||
|
...pathToFilletObj,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTag(node: ObjectExpression, tag: string): boolean {
|
||||||
|
return node.properties.some((prop) => {
|
||||||
|
if (prop.key.name === 'tags' && prop.value.type === 'ArrayExpression') {
|
||||||
|
return prop.value.elements.some(
|
||||||
|
(element) => element.type === 'Identifier' && element.name === tag
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathToRadiusLiteral(node: ObjectExpression, path: any): PathToNode {
|
||||||
|
let pathToFilletObj = path
|
||||||
|
node.properties.forEach((prop, index) => {
|
||||||
|
if (prop.key.name === 'radius') {
|
||||||
|
pathToFilletObj.push(
|
||||||
|
['properties', 'ObjectExpression'],
|
||||||
|
[index, 'index'],
|
||||||
|
['value', 'Property']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return pathToFilletObj
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilletTag(existingFilletCall: CallExpression): string | null {
|
||||||
|
if (existingFilletCall.arguments[0].type === 'ObjectExpression') {
|
||||||
|
const properties = (existingFilletCall.arguments[0] as ObjectExpression)
|
||||||
|
.properties
|
||||||
|
const tagsProperty = properties.find((prop) => prop.key.name === 'tags')
|
||||||
|
if (tagsProperty && tagsProperty.value.type === 'ArrayExpression') {
|
||||||
|
const elements = (tagsProperty.value as ArrayExpression).elements
|
||||||
|
if (elements.length > 0 && elements[0].type === 'Identifier') {
|
||||||
|
return elements[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button states
|
||||||
|
*/
|
||||||
|
|
||||||
export const hasValidFilletSelection = ({
|
export const hasValidFilletSelection = ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
ast,
|
ast,
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { MouseControlType } from 'wasm-lib/kcl/bindings/MouseControlType'
|
import { MouseControlType } from 'wasm-lib/kcl/bindings/MouseControlType'
|
||||||
|
import { platform } from './utils'
|
||||||
|
|
||||||
|
const PLATFORM = platform()
|
||||||
|
const META =
|
||||||
|
PLATFORM === 'macos' ? 'Cmd' : PLATFORM === 'windows' ? 'Win' : 'Super'
|
||||||
|
const ALT = PLATFORM === 'macos' ? 'Option' : 'Alt'
|
||||||
|
|
||||||
const noModifiersPressed = (e: React.MouseEvent) =>
|
const noModifiersPressed = (e: React.MouseEvent) =>
|
||||||
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
||||||
@ -73,99 +79,99 @@ export const btnName = (e: React.MouseEvent) => ({
|
|||||||
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
KittyCAD: {
|
KittyCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Shift + drag or middle click + drag',
|
description: 'Shift + Right click drag or middle click drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(btnName(e).middle && noModifiersPressed(e)) ||
|
(btnName(e).middle && noModifiersPressed(e)) ||
|
||||||
(btnName(e).right && e.shiftKey),
|
(btnName(e).right && e.shiftKey),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
description: 'Scroll or Ctrl + Right click drag',
|
||||||
dragCallback: (e) => !!(e.buttons & 2) && e.ctrlKey,
|
dragCallback: (e) => !!(e.buttons & 2) && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Right click + drag',
|
description: 'Right click drag',
|
||||||
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OnShape: {
|
OnShape: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Ctrl + drag or middle click + drag',
|
description: 'Ctrl + Right click drag or middle click drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(btnName(e).right && e.ctrlKey) ||
|
(btnName(e).right && e.ctrlKey) ||
|
||||||
(btnName(e).middle && noModifiersPressed(e)),
|
(btnName(e).middle && noModifiersPressed(e)),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel',
|
description: 'Scroll',
|
||||||
dragCallback: () => false,
|
dragCallback: () => false,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Right click + drag',
|
description: 'Right click drag',
|
||||||
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
callback: (e) => btnName(e).right && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Trackpad Friendly': {
|
'Trackpad Friendly': {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Left click + Alt + Shift + drag or middle click + drag',
|
description: `${ALT} + Shift + Left click drag or middle click drag`,
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(btnName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
(btnName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
||||||
(btnName(e).middle && noModifiersPressed(e)),
|
(btnName(e).middle && noModifiersPressed(e)),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Left click + Alt + OS + drag',
|
description: `Scroll or ${ALT} + ${META} + Left click drag`,
|
||||||
dragCallback: (e) => btnName(e).left && e.altKey && e.metaKey,
|
dragCallback: (e) => btnName(e).left && e.altKey && e.metaKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Left click + Alt + drag',
|
description: `${ALT} + Left click drag`,
|
||||||
callback: (e) => btnName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
callback: (e) => btnName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
||||||
lenientDragStartButton: 0,
|
lenientDragStartButton: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Solidworks: {
|
Solidworks: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Ctrl + drag',
|
description: 'Ctrl + Right click drag',
|
||||||
callback: (e) => btnName(e).right && e.ctrlKey,
|
callback: (e) => btnName(e).right && e.ctrlKey,
|
||||||
lenientDragStartButton: 2,
|
lenientDragStartButton: 2,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Shift + drag',
|
description: 'Scroll or Shift + Middle click drag',
|
||||||
dragCallback: (e) => btnName(e).middle && e.shiftKey,
|
dragCallback: (e) => btnName(e).middle && e.shiftKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NX: {
|
NX: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Shift + Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && e.shiftKey,
|
callback: (e) => btnName(e).middle && e.shiftKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
description: 'Scroll or Ctrl + Middle click drag',
|
||||||
dragCallback: (e) => btnName(e).middle && e.ctrlKey,
|
dragCallback: (e) => btnName(e).middle && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Creo: {
|
Creo: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Left click + Ctrl + drag',
|
description: 'Ctrl + Left click drag',
|
||||||
callback: (e) => btnName(e).left && !btnName(e).right && e.ctrlKey,
|
callback: (e) => btnName(e).left && !btnName(e).right && e.ctrlKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
description: 'Scroll or Ctrl + Right click drag',
|
||||||
dragCallback: (e) => btnName(e).right && !btnName(e).left && e.ctrlKey,
|
dragCallback: (e) => btnName(e).right && !btnName(e).left && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle (or Left + Right) click + Ctrl + drag',
|
description: 'Ctrl + Middle (or Left + Right) click drag',
|
||||||
callback: (e) => {
|
callback: (e) => {
|
||||||
const b = btnName(e)
|
const b = btnName(e)
|
||||||
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
||||||
@ -174,16 +180,16 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
},
|
},
|
||||||
AutoCAD: {
|
AutoCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
callback: (e) => btnName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel',
|
description: 'Scroll',
|
||||||
dragCallback: () => false,
|
dragCallback: () => false,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Shift + Middle click drag',
|
||||||
callback: (e) => btnName(e).middle && e.shiftKey,
|
callback: (e) => btnName(e).middle && e.shiftKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project, FileEntry } from 'lib/project'
|
||||||
import { ProjectState } from 'wasm-lib/kcl/bindings/ProjectState'
|
|
||||||
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defaultAppSettings,
|
defaultAppSettings,
|
||||||
@ -477,18 +475,6 @@ export const writeAppSettingsFile = async (tomlStr: string) => {
|
|||||||
return window.electron.writeFile(appSettingsFilePath, tomlStr)
|
return window.electron.writeFile(appSettingsFilePath, tomlStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
let appStateStore: ProjectState | undefined = undefined
|
|
||||||
|
|
||||||
export const getState = async (): Promise<ProjectState | undefined> => {
|
|
||||||
return Promise.resolve(appStateStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setState = async (
|
|
||||||
state: ProjectState | undefined
|
|
||||||
): Promise<void> => {
|
|
||||||
appStateStore = state
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getUser = async (
|
export const getUser = async (
|
||||||
token: string,
|
token: string,
|
||||||
hostname: string
|
hostname: string
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import type { FileEntry } from 'lib/types'
|
import type { FileEntry } from 'lib/project'
|
||||||
import {
|
import {
|
||||||
FILE_EXT,
|
FILE_EXT,
|
||||||
INDEX_IDENTIFIER,
|
INDEX_IDENTIFIER,
|
||||||
|
98
src/lib/getCurrentProjectFile.test.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { promises as fs } from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import os from 'os'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import getCurrentProjectFile from './getCurrentProjectFile'
|
||||||
|
|
||||||
|
describe('getCurrentProjectFile', () => {
|
||||||
|
test('with explicit open file with space (URL encoded)', async () => {
|
||||||
|
const name = `kittycad-modeling-projects-${uuidv4()}`
|
||||||
|
const tmpProjectDir = path.join(os.tmpdir(), name)
|
||||||
|
|
||||||
|
await fs.mkdir(tmpProjectDir, { recursive: true })
|
||||||
|
await fs.writeFile(path.join(tmpProjectDir, 'i have a space.kcl'), '')
|
||||||
|
|
||||||
|
const state = await getCurrentProjectFile(
|
||||||
|
path.join(tmpProjectDir, 'i%20have%20a%20space.kcl')
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(state).toBe(path.join(tmpProjectDir, 'i have a space.kcl'))
|
||||||
|
|
||||||
|
await fs.rm(tmpProjectDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with explicit open file with space', async () => {
|
||||||
|
const name = `kittycad-modeling-projects-${uuidv4()}`
|
||||||
|
const tmpProjectDir = path.join(os.tmpdir(), name)
|
||||||
|
|
||||||
|
await fs.mkdir(tmpProjectDir, { recursive: true })
|
||||||
|
await fs.writeFile(path.join(tmpProjectDir, 'i have a space.kcl'), '')
|
||||||
|
|
||||||
|
const state = await getCurrentProjectFile(
|
||||||
|
path.join(tmpProjectDir, 'i have a space.kcl')
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(state).toBe(path.join(tmpProjectDir, 'i have a space.kcl'))
|
||||||
|
|
||||||
|
await fs.rm(tmpProjectDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with source path dot', async () => {
|
||||||
|
const name = `kittycad-modeling-projects-${uuidv4()}`
|
||||||
|
const tmpProjectDir = path.join(os.tmpdir(), name)
|
||||||
|
await fs.mkdir(tmpProjectDir, { recursive: true })
|
||||||
|
|
||||||
|
// Set the current directory to the temp project directory.
|
||||||
|
const originalCwd = process.cwd()
|
||||||
|
process.chdir(tmpProjectDir)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const state = await getCurrentProjectFile('.')
|
||||||
|
|
||||||
|
if (state instanceof Error) {
|
||||||
|
throw state
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(state.replace('/private', '')).toBe(
|
||||||
|
path.join(tmpProjectDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
await fs.rm(tmpProjectDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with main.kcl not existing', async () => {
|
||||||
|
const name = `kittycad-modeling-projects-${uuidv4()}`
|
||||||
|
const tmpProjectDir = path.join(os.tmpdir(), name)
|
||||||
|
await fs.mkdir(tmpProjectDir, { recursive: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
const state = await getCurrentProjectFile(tmpProjectDir)
|
||||||
|
|
||||||
|
expect(state).toBe(path.join(tmpProjectDir, 'main.kcl'))
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tmpProjectDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with directory, main.kcl not existing, other.kcl does', async () => {
|
||||||
|
const name = `kittycad-modeling-projects-${uuidv4()}`
|
||||||
|
const tmpProjectDir = path.join(os.tmpdir(), name)
|
||||||
|
await fs.mkdir(tmpProjectDir, { recursive: true })
|
||||||
|
await fs.writeFile(path.join(tmpProjectDir, 'other.kcl'), '')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const state = await getCurrentProjectFile(tmpProjectDir)
|
||||||
|
|
||||||
|
expect(state).toBe(path.join(tmpProjectDir, 'other.kcl'))
|
||||||
|
|
||||||
|
// make sure we didn't create a main.kcl file
|
||||||
|
await expect(
|
||||||
|
fs.access(path.join(tmpProjectDir, 'main.kcl'))
|
||||||
|
).rejects.toThrow()
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tmpProjectDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
116
src/lib/getCurrentProjectFile.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs/promises'
|
||||||
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
|
import { PROJECT_ENTRYPOINT } from './constants'
|
||||||
|
|
||||||
|
// Create a const object with the values
|
||||||
|
const FILE_IMPORT_FORMATS = {
|
||||||
|
fbx: 'fbx',
|
||||||
|
gltf: 'gltf',
|
||||||
|
obj: 'obj',
|
||||||
|
ply: 'ply',
|
||||||
|
sldprt: 'sldprt',
|
||||||
|
step: 'step',
|
||||||
|
stl: 'stl',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// Extract the values into an array
|
||||||
|
const fileImportFormats: Models['FileImportFormat_type'][] =
|
||||||
|
Object.values(FILE_IMPORT_FORMATS)
|
||||||
|
export const allFileImportFormats: string[] = [
|
||||||
|
...fileImportFormats,
|
||||||
|
'stp',
|
||||||
|
'fbxb',
|
||||||
|
'glb',
|
||||||
|
]
|
||||||
|
export const relevantExtensions = ['kcl', ...allFileImportFormats]
|
||||||
|
|
||||||
|
/// Get the current project file from the path.
|
||||||
|
/// This is used for double-clicking on a file in the file explorer,
|
||||||
|
/// or the command line args, or deep linking.
|
||||||
|
export default async function getCurrentProjectFile(
|
||||||
|
pathString: string
|
||||||
|
): Promise<string | Error> {
|
||||||
|
// Fix for "." path, which is the current directory.
|
||||||
|
let sourcePath = pathString === '.' ? process.cwd() : pathString
|
||||||
|
|
||||||
|
// URL decode the path.
|
||||||
|
sourcePath = decodeURIComponent(sourcePath)
|
||||||
|
|
||||||
|
// If the path does not start with a slash, it is a relative path.
|
||||||
|
// We need to convert it to an absolute path.
|
||||||
|
sourcePath = path.isAbsolute(sourcePath)
|
||||||
|
? sourcePath
|
||||||
|
: path.join(process.cwd(), sourcePath)
|
||||||
|
|
||||||
|
// If the path is a directory, let's assume it is a project directory.
|
||||||
|
const stats = await fs.stat(sourcePath)
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
// Walk the directory and look for a kcl file.
|
||||||
|
const files = await fs.readdir(sourcePath)
|
||||||
|
const kclFiles = files.filter((file) => path.extname(file) === '.kcl')
|
||||||
|
|
||||||
|
if (kclFiles.length === 0) {
|
||||||
|
let projectFile = path.join(sourcePath, PROJECT_ENTRYPOINT)
|
||||||
|
// Check if we have a main.kcl file in the project.
|
||||||
|
try {
|
||||||
|
await fs.access(projectFile)
|
||||||
|
} catch {
|
||||||
|
// Create the default file in the project.
|
||||||
|
await fs.writeFile(projectFile, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a project entrypoint file exists, use it.
|
||||||
|
// Otherwise, use the first kcl file in the project.
|
||||||
|
const gotMain = files.filter((file) => file === PROJECT_ENTRYPOINT)
|
||||||
|
if (gotMain.length === 0) {
|
||||||
|
return path.join(sourcePath, kclFiles[0])
|
||||||
|
}
|
||||||
|
return path.join(sourcePath, PROJECT_ENTRYPOINT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the extension on what we are trying to open is a relevant file type.
|
||||||
|
const extension = path.extname(sourcePath).slice(1)
|
||||||
|
|
||||||
|
if (!relevantExtensions.includes(extension) && extension !== 'toml') {
|
||||||
|
return new Error(
|
||||||
|
`File type (${extension}) cannot be opened with this app: '${sourcePath}', try opening one of the following file types: ${relevantExtensions.join(
|
||||||
|
', '
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were given a file path, not a directory.
|
||||||
|
// Let's get the parent directory of the file.
|
||||||
|
const parent = path.dirname(sourcePath)
|
||||||
|
|
||||||
|
// If we got an import model file, we need to check if we have a file in the project for
|
||||||
|
// this import model.
|
||||||
|
if (allFileImportFormats.includes(extension)) {
|
||||||
|
const importFileName = path.basename(sourcePath)
|
||||||
|
// Check if we have a file in the project for this import model.
|
||||||
|
const kclWrapperFilename = `${importFileName}.kcl`
|
||||||
|
const kclWrapperFilePath = path.join(parent, kclWrapperFilename)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.access(kclWrapperFilePath)
|
||||||
|
} catch {
|
||||||
|
// Create the file in the project with the default import content.
|
||||||
|
const content = `// This file was automatically generated by the application when you
|
||||||
|
// double-clicked on the model file.
|
||||||
|
// You can edit this file to add your own content.
|
||||||
|
// But we recommend you keep the import statement as it is.
|
||||||
|
// For more information on the import statement, see the documentation at:
|
||||||
|
// https://zoo.dev/docs/kcl/import
|
||||||
|
const model = import("${importFileName}")`
|
||||||
|
await fs.writeFile(kclWrapperFilePath, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kclWrapperFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourcePath
|
||||||
|
}
|
35
src/lib/hotkeyWrapper.test.ts
Normal 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'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,9 +1,10 @@
|
|||||||
import { Options, useHotkeys } from 'react-hotkeys-hook'
|
import { Options, useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { codeManager } from './singletons'
|
import { codeManager } from './singletons'
|
||||||
|
import { Platform } from './utils'
|
||||||
|
|
||||||
// Hotkey wrapper wraps hotkeys for the app (outside of the editor)
|
// 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
|
// 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
|
// conflicting hotkeys, or them only being implemented for the app but not
|
||||||
// inside the editor.
|
// inside the editor.
|
||||||
@ -37,3 +38,48 @@ function mapHotkeyToCodeMirrorHotkey(hotkey: string): string {
|
|||||||
.replaceAll('shift', 'Shift')
|
.replaceAll('shift', 'Shift')
|
||||||
.replaceAll('alt', 'Alt')
|
.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
|
||||||
|
}
|
||||||
|
46
src/lib/project.d.ts
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* The permissions of a file.
|
||||||
|
*/
|
||||||
|
export type FilePermission = 'read' | 'write' | 'execute'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of a file.
|
||||||
|
*/
|
||||||
|
export type FileType = 'file' | 'directory' | 'symlink'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about a file or directory.
|
||||||
|
*/
|
||||||
|
export type FileMetadata = {
|
||||||
|
accessed: string | null
|
||||||
|
created: string | null
|
||||||
|
type: FileType | null
|
||||||
|
size: number
|
||||||
|
modified: string | null
|
||||||
|
permission: FilePermission | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a file or directory.
|
||||||
|
*/
|
||||||
|
export type FileEntry = {
|
||||||
|
path: string
|
||||||
|
name: string
|
||||||
|
children: Array<FileEntry> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about project.
|
||||||
|
*/
|
||||||
|
export type Project = {
|
||||||
|
metadata: FileMetadata | null
|
||||||
|
kcl_file_count: number
|
||||||
|
directory_count: number
|
||||||
|
/**
|
||||||
|
* The default file to open on load.
|
||||||
|
*/
|
||||||
|
default_file: string
|
||||||
|
path: string
|
||||||
|
name: string
|
||||||
|
children: Array<FileEntry> | null
|
||||||
|
}
|
@ -61,7 +61,7 @@ export const interactionMap: Record<
|
|||||||
name: 'toggle-command-palette',
|
name: 'toggle-command-palette',
|
||||||
sequence: `${PRIMARY}+K`,
|
sequence: `${PRIMARY}+K`,
|
||||||
title: 'Toggle Command Palette',
|
title: 'Toggle Command Palette',
|
||||||
description: 'Always available. Use Ctrl+/ on Windows/Linux.',
|
description: 'Always available.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
Panes: [
|
Panes: [
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
const DESC = ':desc'
|
const DESC = ':desc'
|
||||||
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
import { Project, FileEntry } from 'lib/project'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
|
||||||
|
|
||||||
export type { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
|
||||||
|
|
||||||
export type IndexLoaderData = {
|
export type IndexLoaderData = {
|
||||||
code: string | null
|
code: string | null
|
||||||
|
@ -151,6 +151,32 @@ export function platform(): Platform {
|
|||||||
return ''
|
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) {
|
if (navigator.userAgent.indexOf('Mac') !== -1) {
|
||||||
return 'macos'
|
return 'macos'
|
||||||
} else if (navigator.userAgent.indexOf('Win') !== -1) {
|
} else if (navigator.userAgent.indexOf('Win') !== -1) {
|
||||||
@ -158,7 +184,12 @@ export function platform(): Platform {
|
|||||||
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
|
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
|
||||||
return 'linux'
|
return 'linux'
|
||||||
}
|
}
|
||||||
console.error('Unknown platform userAgent:', navigator.userAgent)
|
console.error(
|
||||||
|
'Unknown platform userAgent:',
|
||||||
|
navigator.platform,
|
||||||
|
userAgentDataPlatform,
|
||||||
|
navigator.userAgent
|
||||||
|
)
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import type { FileEntry } from 'lib/types'
|
import { Project, FileEntry } from 'lib/project'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
|
||||||
|
|
||||||
export const fileMachine = createMachine(
|
export const fileMachine = createMachine(
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import { HomeCommandSchema } from 'lib/commandBarConfigs/homeCommandConfig'
|
import { HomeCommandSchema } from 'lib/commandBarConfigs/homeCommandConfig'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
export const homeMachine = createMachine(
|
export const homeMachine = createMachine(
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
parse,
|
parse,
|
||||||
recast,
|
recast,
|
||||||
sketchGroupFromKclValue,
|
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { Axis, Selection, Selections, updateSelections } from 'lib/selections'
|
import { Axis, Selection, Selections, updateSelections } from 'lib/selections'
|
||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
@ -35,7 +34,7 @@ import {
|
|||||||
setEqualLengthInfo,
|
setEqualLengthInfo,
|
||||||
} from 'components/Toolbar/EqualLength'
|
} from 'components/Toolbar/EqualLength'
|
||||||
import { deleteFromSelection, extrudeSketch } from 'lang/modifyAst'
|
import { deleteFromSelection, extrudeSketch } from 'lang/modifyAst'
|
||||||
import { addFillet } from 'lang/modifyAst/addFillet'
|
import { applyFilletToSelection } from 'lang/modifyAst/addFillet'
|
||||||
import { getNodeFromPath } from '../lang/queryAst'
|
import { getNodeFromPath } from '../lang/queryAst'
|
||||||
import {
|
import {
|
||||||
applyConstraintEqualAngle,
|
applyConstraintEqualAngle,
|
||||||
@ -59,7 +58,6 @@ import { Coords2d } from 'lang/std/sketch'
|
|||||||
import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
|
import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
|
||||||
import { executeAst } from 'lang/langHelpers'
|
import { executeAst } from 'lang/langHelpers'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { getExtrusionFromSuspectedPath } from 'lang/std/artifactGraph'
|
|
||||||
|
|
||||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||||
|
|
||||||
@ -1163,65 +1161,16 @@ export const modelingMachine = createMachine(
|
|||||||
'AST fillet': async (_, event) => {
|
'AST fillet': async (_, event) => {
|
||||||
if (!event.data) return
|
if (!event.data) return
|
||||||
|
|
||||||
|
// Extract inputs
|
||||||
const { selection, radius } = event.data
|
const { selection, radius } = event.data
|
||||||
let ast = kclManager.ast
|
|
||||||
|
|
||||||
if (
|
// Apply fillet to selection
|
||||||
'variableName' in radius &&
|
const applyFilletToSelectionResult = applyFilletToSelection(
|
||||||
radius.variableName &&
|
selection,
|
||||||
radius.insertIndex !== undefined
|
radius
|
||||||
) {
|
|
||||||
const newBody = [...ast.body]
|
|
||||||
newBody.splice(radius.insertIndex, 0, radius.variableDeclarationAst)
|
|
||||||
ast.body = newBody
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathToSegmentNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selection.codeBasedSelections[0].range
|
|
||||||
)
|
)
|
||||||
|
if (err(applyFilletToSelectionResult))
|
||||||
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
return applyFilletToSelectionResult
|
||||||
ast,
|
|
||||||
pathToSegmentNode,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (err(varDecNode)) return
|
|
||||||
const sketchVar = varDecNode.node.declarations[0].id.name
|
|
||||||
const sketchGroup = sketchGroupFromKclValue(
|
|
||||||
kclManager.programMemory.get(sketchVar),
|
|
||||||
sketchVar
|
|
||||||
)
|
|
||||||
if (trap(sketchGroup)) return
|
|
||||||
const extrusion = getExtrusionFromSuspectedPath(
|
|
||||||
sketchGroup.id,
|
|
||||||
engineCommandManager.artifactGraph
|
|
||||||
)
|
|
||||||
const pathToExtrudeNode = err(extrusion)
|
|
||||||
? []
|
|
||||||
: getNodePathFromSourceRange(ast, extrusion.codeRef.range)
|
|
||||||
|
|
||||||
// we assume that there is only one body related to the sketch
|
|
||||||
// and apply the fillet to it
|
|
||||||
|
|
||||||
const addFilletResult = addFillet(
|
|
||||||
ast,
|
|
||||||
pathToSegmentNode,
|
|
||||||
pathToExtrudeNode,
|
|
||||||
'variableName' in radius
|
|
||||||
? radius.variableIdentifierAst
|
|
||||||
: radius.valueAst
|
|
||||||
)
|
|
||||||
|
|
||||||
if (trap(addFilletResult)) return
|
|
||||||
const { modifiedAst, pathToFilletNode } = addFilletResult
|
|
||||||
|
|
||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
|
||||||
focusPath: pathToFilletNode,
|
|
||||||
})
|
|
||||||
if (updatedAst?.selections) {
|
|
||||||
editorManager.selectRange(updatedAst?.selections)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'conditionally equip line tool': (_, { type }) => {
|
'conditionally equip line tool': (_, { type }) => {
|
||||||
if (type === 'done.invoke.animate-to-face') {
|
if (type === 'done.invoke.animate-to-face') {
|
||||||
|
144
src/main.ts
@ -8,6 +8,13 @@ import { Issuer } from 'openid-client'
|
|||||||
import { Bonjour, Service } from 'bonjour-service'
|
import { Bonjour, Service } from 'bonjour-service'
|
||||||
// @ts-ignore: TS1343
|
// @ts-ignore: TS1343
|
||||||
import * as kittycad from '@kittycad/lib/import'
|
import * as kittycad from '@kittycad/lib/import'
|
||||||
|
import minimist from 'minimist'
|
||||||
|
import getCurrentProjectFile from 'lib/getCurrentProjectFile'
|
||||||
|
|
||||||
|
let mainWindow: BrowserWindow | null = null
|
||||||
|
|
||||||
|
// Check the command line arguments for a project path
|
||||||
|
const args = parseCLIArgs()
|
||||||
|
|
||||||
// If it's not set, scream.
|
// If it's not set, scream.
|
||||||
const NODE_ENV = process.env.NODE_ENV || 'production'
|
const NODE_ENV = process.env.NODE_ENV || 'production'
|
||||||
@ -22,8 +29,25 @@ if (require('electron-squirrel-startup')) {
|
|||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
const createWindow = () => {
|
const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
|
/// Register our application to handle all "electron-fiddle://" protocols.
|
||||||
|
if (process.defaultApp) {
|
||||||
|
if (process.argv.length >= 2) {
|
||||||
|
app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL, process.execPath, [
|
||||||
|
path.resolve(process.argv[1]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global app listeners
|
||||||
|
// Must be done before ready event.
|
||||||
|
registerStartupListeners()
|
||||||
|
|
||||||
|
const createWindow = (): BrowserWindow => {
|
||||||
|
const newWindow = new BrowserWindow({
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
show: false,
|
show: false,
|
||||||
width: 1800,
|
width: 1800,
|
||||||
@ -35,13 +59,15 @@ const createWindow = () => {
|
|||||||
preload: path.join(__dirname, './preload.js'),
|
preload: path.join(__dirname, './preload.js'),
|
||||||
},
|
},
|
||||||
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
||||||
|
frame: false,
|
||||||
|
titleBarStyle: 'hiddenInset',
|
||||||
})
|
})
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
|
newWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(
|
newWindow.loadFile(
|
||||||
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
|
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -49,7 +75,9 @@ const createWindow = () => {
|
|||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
// mainWindow.webContents.openDevTools()
|
// mainWindow.webContents.openDevTools()
|
||||||
|
|
||||||
mainWindow.show()
|
newWindow.show()
|
||||||
|
|
||||||
|
return newWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
@ -64,7 +92,10 @@ app.on('window-all-closed', () => {
|
|||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
app.on('ready', createWindow)
|
app.on('ready', (event, data) => {
|
||||||
|
// Create the mainWindow
|
||||||
|
mainWindow = createWindow()
|
||||||
|
})
|
||||||
|
|
||||||
// For now there is no good reason to separate these out to another file(s)
|
// For now there is no good reason to separate these out to another file(s)
|
||||||
// There is just not enough code to warrant it and further abstracts everything
|
// There is just not enough code to warrant it and further abstracts everything
|
||||||
@ -159,3 +190,104 @@ ipcMain.handle('find_machine_api', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('loadProjectAtStartup', async () => {
|
||||||
|
// If we are in development mode, we don't want to load a project at
|
||||||
|
// startup.
|
||||||
|
// Since the args passed are always '.'
|
||||||
|
if (NODE_ENV !== 'production') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let projectPath: string | null = null
|
||||||
|
// macOS: open-file events that were received before the app is ready
|
||||||
|
const macOpenFiles: string[] = (global as any).macOpenFiles
|
||||||
|
if (macOpenFiles && macOpenFiles && macOpenFiles.length > 0) {
|
||||||
|
projectPath = macOpenFiles[0] // We only do one project at a time
|
||||||
|
}
|
||||||
|
// Reset this so we don't accidentally use it again.
|
||||||
|
const macOpenFilesEmpty: string[] = []
|
||||||
|
// @ts-ignore
|
||||||
|
global['macOpenFiles'] = macOpenFilesEmpty
|
||||||
|
|
||||||
|
// macOS: open-url events that were received before the app is ready
|
||||||
|
const getOpenUrls: string[] = (global as any).getOpenUrls
|
||||||
|
if (getOpenUrls && getOpenUrls.length > 0) {
|
||||||
|
projectPath = getOpenUrls[0] // We only do one project at a
|
||||||
|
}
|
||||||
|
// Reset this so we don't accidentally use it again.
|
||||||
|
// @ts-ignore
|
||||||
|
global['getOpenUrls'] = []
|
||||||
|
|
||||||
|
// Check if we have a project path in the command line arguments
|
||||||
|
// If we do, we will load the project at that path
|
||||||
|
if (args._.length > 1) {
|
||||||
|
if (args._[1].length > 0) {
|
||||||
|
projectPath = args._[1]
|
||||||
|
// Reset all this value so we don't accidentally use it again.
|
||||||
|
args._[1] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectPath) {
|
||||||
|
// We have a project path, load the project information.
|
||||||
|
console.log(`Loading project at startup: ${projectPath}`)
|
||||||
|
try {
|
||||||
|
const currentFile = await getCurrentProjectFile(projectPath)
|
||||||
|
console.log(`Project loaded: ${currentFile}`)
|
||||||
|
return currentFile
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
function parseCLIArgs(): minimist.ParsedArgs {
|
||||||
|
return minimist(process.argv, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerStartupListeners() {
|
||||||
|
/**
|
||||||
|
* macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
|
||||||
|
* the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
|
||||||
|
*/
|
||||||
|
const macOpenFiles: string[] = []
|
||||||
|
// @ts-ignore
|
||||||
|
global['macOpenFiles'] = macOpenFiles
|
||||||
|
app.on('open-file', function (event, path) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
macOpenFiles.push(path)
|
||||||
|
// If we have a mainWindow, lets open another window.
|
||||||
|
if (mainWindow) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* macOS: react to open-url requests.
|
||||||
|
*/
|
||||||
|
const openUrls: string[] = []
|
||||||
|
// @ts-ignore
|
||||||
|
global['openUrls'] = openUrls
|
||||||
|
const onOpenUrl = function (
|
||||||
|
event: { preventDefault: () => void },
|
||||||
|
url: string
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
openUrls.push(url)
|
||||||
|
// If we have a mainWindow, lets open another window.
|
||||||
|
if (mainWindow) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('will-finish-launching', function () {
|
||||||
|
app.on('open-url', onOpenUrl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -60,6 +60,9 @@ const listMachines = async (): Promise<MachinesListing> => {
|
|||||||
const getMachineApiIp = async (): Promise<String | null> =>
|
const getMachineApiIp = async (): Promise<String | null> =>
|
||||||
ipcRenderer.invoke('find_machine_api')
|
ipcRenderer.invoke('find_machine_api')
|
||||||
|
|
||||||
|
const loadProjectAtStartup = async (): Promise<string | null> =>
|
||||||
|
ipcRenderer.invoke('loadProjectAtStartup')
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
login,
|
login,
|
||||||
// Passing fs directly is not recommended since it gives a lot of power
|
// Passing fs directly is not recommended since it gives a lot of power
|
||||||
@ -93,6 +96,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
isWindows,
|
isWindows,
|
||||||
isLinux,
|
isLinux,
|
||||||
},
|
},
|
||||||
|
loadProjectAtStartup,
|
||||||
// IMPORTANT NOTE: kittycad.ts reads process.env.BASE_URL. But there is
|
// IMPORTANT NOTE: kittycad.ts reads process.env.BASE_URL. But there is
|
||||||
// no way to set it across the bridge boundary. We need to make it a command.
|
// no way to set it across the bridge boundary. We need to make it a command.
|
||||||
setBaseUrl: (value: string) => (process.env.BASE_URL = value),
|
setBaseUrl: (value: string) => (process.env.BASE_URL = value),
|
||||||
|
@ -31,13 +31,13 @@ import { kclManager } from 'lib/singletons'
|
|||||||
import { useLspContext } from 'components/LspProvider'
|
import { useLspContext } from 'components/LspProvider'
|
||||||
import { useRefreshSettings } from 'hooks/useRefreshSettings'
|
import { useRefreshSettings } from 'hooks/useRefreshSettings'
|
||||||
import { LowerRightControls } from 'components/LowerRightControls'
|
import { LowerRightControls } from 'components/LowerRightControls'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
|
||||||
import {
|
import {
|
||||||
createNewProjectDirectory,
|
createNewProjectDirectory,
|
||||||
listProjects,
|
listProjects,
|
||||||
renameProjectDirectory,
|
renameProjectDirectory,
|
||||||
} from 'lib/desktop'
|
} from 'lib/desktop'
|
||||||
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
|
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
|
||||||
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
// This route only opens in the desktop context for now,
|
// This route only opens in the desktop context for now,
|
||||||
// as defined in Router.tsx, so we can use the desktop APIs and types.
|
// as defined in Router.tsx, so we can use the desktop APIs and types.
|
||||||
|
@ -2,6 +2,8 @@ import usePlatform from 'hooks/usePlatform'
|
|||||||
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||||
|
import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar'
|
||||||
|
|
||||||
export default function CmdK() {
|
export default function CmdK() {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
@ -20,15 +22,9 @@ export default function CmdK() {
|
|||||||
<h2 className="text-2xl font-bold">Command Bar</h2>
|
<h2 className="text-2xl font-bold">Command Bar</h2>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
Press{' '}
|
Press{' '}
|
||||||
{platformName === 'macos' ? (
|
<kbd className={kbdClasses}>
|
||||||
<>
|
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platformName)}
|
||||||
<kbd className={kbdClasses}>⌘K</kbd>
|
</kbd>{' '}
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<kbd className={kbdClasses}>Ctrl + /</kbd>
|
|
||||||
</>
|
|
||||||
)}{' '}
|
|
||||||
to open the command bar. Try changing your theme with it.
|
to open the command bar. Try changing your theme with it.
|
||||||
</p>
|
</p>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
|
166
src/wasm-lib/Cargo.lock
generated
@ -70,54 +70,12 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.8"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
@ -169,7 +127,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -180,7 +138,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -191,7 +149,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -426,12 +384,8 @@ version = "4.5.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
|
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim 0.11.0",
|
|
||||||
"unicase",
|
|
||||||
"unicode-width",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -443,7 +397,7 @@ dependencies = [
|
|||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -452,12 +406,6 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@ -642,8 +590,8 @@ dependencies = [
|
|||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim 0.10.0",
|
"strsim",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -654,7 +602,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -709,7 +657,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -724,7 +672,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.24"
|
version = "0.1.25"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -738,7 +686,7 @@ dependencies = [
|
|||||||
"rustfmt-wrapper",
|
"rustfmt-wrapper",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_tokenstream",
|
"serde_tokenstream",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -749,7 +697,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -776,7 +724,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -948,7 +896,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1038,7 +986,7 @@ dependencies = [
|
|||||||
"inflections",
|
"inflections",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1397,7 +1345,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.7"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -1464,12 +1412,12 @@ dependencies = [
|
|||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper",
|
"hyper",
|
||||||
@ -1482,9 +1430,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.3.16"
|
version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a9621df809fa105ae28b01586c52ce46561e166702babb50e5bcc5097a8ce62"
|
checksum = "fbb7c076d64ad00a29ae900108707d1bbb583944d4b2d005e1eca9914a18c7c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1492,7 +1440,6 @@ dependencies = [
|
|||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"format_serde_error",
|
"format_serde_error",
|
||||||
"futures",
|
"futures",
|
||||||
@ -1852,7 +1799,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.8.3",
|
"regex-syntax 0.8.3",
|
||||||
"structmeta",
|
"structmeta",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1905,7 +1852,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2069,7 +2016,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"pyo3-macros-backend",
|
"pyo3-macros-backend",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2082,7 +2029,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"pyo3-build-config",
|
"pyo3-build-config",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2096,9 +2043,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.36"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@ -2544,7 +2491,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde_derive_internals",
|
"serde_derive_internals",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2594,9 +2541,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.208"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -2612,13 +2559,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.208"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2629,14 +2576,14 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.125"
|
version = "1.0.127"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
|
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.5",
|
"indexmap 2.2.5",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -2653,7 +2600,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2674,7 +2621,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2796,12 +2743,6 @@ version = "0.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "structmeta"
|
name = "structmeta"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -2811,7 +2752,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"structmeta-derive",
|
"structmeta-derive",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2822,7 +2763,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2866,9 +2807,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.75"
|
version = "2.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
|
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -2889,7 +2830,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2996,7 +2937,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3091,7 +3032,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3244,7 +3185,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3272,7 +3213,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3349,7 +3290,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3469,12 +3410,6 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@ -3513,7 +3448,7 @@ dependencies = [
|
|||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3574,7 +3509,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3609,7 +3544,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@ -3626,7 +3561,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bson",
|
"bson",
|
||||||
"clap",
|
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"futures",
|
"futures",
|
||||||
@ -3935,7 +3869,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.75",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -11,12 +11,11 @@ crate-type = ["cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bson = { version = "2.11.0", features = ["uuid-1", "chrono"] }
|
bson = { version = "2.11.0", features = ["uuid-1", "chrono"] }
|
||||||
clap = "4.5.16"
|
|
||||||
data-encoding = "2.6.0"
|
data-encoding = "2.6.0"
|
||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad.workspace = true
|
kittycad.workspace = true
|
||||||
serde_json = "1.0.125"
|
serde_json = "1.0.127"
|
||||||
tokio = { version = "1.39.3", features = ["sync"] }
|
tokio = { version = "1.39.3", features = ["sync"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||||
@ -71,7 +70,7 @@ members = [
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
http = "0.2.12"
|
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"
|
kittycad-modeling-session = "0.1.4"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.24"
|
version = "0.1.25"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -18,9 +18,9 @@ once_cell = "1.19.0"
|
|||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.75", features = ["full"] }
|
syn = { version = "2.0.76", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
@ -15,7 +15,7 @@ databake = "0.1.8"
|
|||||||
kcl-lib = { path = "../kcl" }
|
kcl-lib = { path = "../kcl" }
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2.0.75", features = ["full"] }
|
syn = { version = "2.0.76", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
description = "A test server for KCL"
|
description = "A test server for KCL"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
@ -10,6 +10,6 @@ anyhow = "1.0.86"
|
|||||||
hyper = { version = "0.14.29", features = ["server"] }
|
hyper = { version = "0.14.29", features = ["server"] }
|
||||||
kcl-lib = { version = "0.2", path = "../kcl" }
|
kcl-lib = { version = "0.2", path = "../kcl" }
|
||||||
pico-args = "0.5.0"
|
pico-args = "0.5.0"
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.125"
|
serde_json = "1.0.127"
|
||||||
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.7"
|
version = "0.2.11"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -16,7 +16,7 @@ async-recursion = "1.1.1"
|
|||||||
async-trait = "0.1.81"
|
async-trait = "0.1.81"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.16", default-features = false, optional = true }
|
clap = { version = "4.5.16", default-features = false, optional = true, features = ["std", "derive"] }
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
dashmap = "6.0.1"
|
dashmap = "6.0.1"
|
||||||
databake = { version = "0.1.8", features = ["derive"] }
|
databake = { version = "0.1.8", features = ["derive"] }
|
||||||
@ -27,7 +27,7 @@ git_rev = "0.1.0"
|
|||||||
gltf-json = "1.4.1"
|
gltf-json = "1.4.1"
|
||||||
http = { workspace = true }
|
http = { workspace = true }
|
||||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||||
kittycad = { workspace = true, features = ["clap"] }
|
kittycad = { workspace = true }
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
measurements = "0.11.0"
|
measurements = "0.11.0"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
@ -36,8 +36,8 @@ pyo3 = { version = "0.22.2", optional = true }
|
|||||||
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
|
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
|
||||||
ropey = "1.6.1"
|
ropey = "1.6.1"
|
||||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.125"
|
serde_json = "1.0.127"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
tabled = { version = "0.15.0", optional = true }
|
tabled = { version = "0.15.0", optional = true }
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
@ -66,7 +66,7 @@ tokio-tungstenite = { version = "0.23.1", features = ["rustls-tls-native-roots"]
|
|||||||
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["cli", "engine"]
|
default = ["engine"]
|
||||||
cli = ["dep:clap"]
|
cli = ["dep:clap"]
|
||||||
# For the lsp server, when run with stdout for rpc we want to disable println.
|
# For the lsp server, when run with stdout for rpc we want to disable println.
|
||||||
# This is used for editor extensions that use the lsp server.
|
# This is used for editor extensions that use the lsp server.
|
||||||
|
@ -18,3 +18,11 @@ We've built a lot of tooling to make contributing to KCL easier. If you are inte
|
|||||||
10. Run `TWENTY_TWENTY=overwrite cargo nextest run --workspace --no-fail-fast` to take snapshot tests of your example code running in the engine
|
10. Run `TWENTY_TWENTY=overwrite cargo nextest run --workspace --no-fail-fast` to take snapshot tests of your example code running in the engine
|
||||||
11. Run `EXPECTORATE=overwrite cargo test --all generate_stdlib -- --nocapture` to generate new Markdown documentation for your function that will be used [to generate docs on our website](https://zoo.dev/docs/kcl).
|
11. Run `EXPECTORATE=overwrite cargo test --all generate_stdlib -- --nocapture` to generate new Markdown documentation for your function that will be used [to generate docs on our website](https://zoo.dev/docs/kcl).
|
||||||
12. Create a PR in GitHub.
|
12. Create a PR in GitHub.
|
||||||
|
|
||||||
|
## Bumping the version
|
||||||
|
|
||||||
|
If you bump the version of kcl-lib and push it to crates, be sure to update the repos we own that use it as well. These are:
|
||||||
|
|
||||||
|
- [kcl.py](https://github.com/kittycad/kcl.py)
|
||||||
|
- [kcl-lsp](https://github.com/kittycad/kcl-lsp)
|
||||||
|
- [cli](https://github.com/kittycad/cli)
|
||||||
|
134
src/wasm-lib/kcl/fuzz/Cargo.lock
generated
@ -70,55 +70,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
@ -377,54 +328,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.5",
|
"windows-targets 0.52.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.5.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
"clap_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.5.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
"unicase",
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.5.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
|
||||||
dependencies = [
|
|
||||||
"heck 0.5.0",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.75",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@ -596,7 +499,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.24"
|
version = "0.1.25"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"convert_case",
|
"convert_case",
|
||||||
@ -906,12 +809,6 @@ version = "0.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -1090,12 +987,6 @@ version = "2.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@ -1140,7 +1031,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.6"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -1149,7 +1040,6 @@ dependencies = [
|
|||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bson",
|
"bson",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"dashmap 6.0.1",
|
"dashmap 6.0.1",
|
||||||
"databake",
|
"databake",
|
||||||
@ -1158,6 +1048,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"git_rev",
|
"git_rev",
|
||||||
"gltf-json",
|
"gltf-json",
|
||||||
|
"http 0.2.12",
|
||||||
"image",
|
"image",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
@ -1198,9 +1089,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.3.14"
|
version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce5e9c51976882cdf6777557fd8c3ee68b00bb53e9307fc1721acb397f2ece9a"
|
checksum = "fbb7c076d64ad00a29ae900108707d1bbb583944d4b2d005e1eca9914a18c7c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1208,7 +1099,6 @@ dependencies = [
|
|||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"format_serde_error",
|
"format_serde_error",
|
||||||
"futures",
|
"futures",
|
||||||
@ -2197,7 +2087,7 @@ version = "0.26.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.1",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
@ -2649,12 +2539,6 @@ version = "1.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-width"
|
|
||||||
version = "0.1.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -2685,12 +2569,6 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -9,7 +9,7 @@ use std::{
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use kittycad::types::{WebSocketRequest, WebSocketResponse};
|
use kittycad::types::{ModelingSessionData, WebSocketRequest, WebSocketResponse};
|
||||||
use tokio::sync::{mpsc, oneshot, RwLock};
|
use tokio::sync::{mpsc, oneshot, RwLock};
|
||||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||||
|
|
||||||
@ -27,10 +27,10 @@ enum SocketHealth {
|
|||||||
|
|
||||||
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
|
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(dead_code)] // for the TcpReadHandle
|
|
||||||
pub struct EngineConnection {
|
pub struct EngineConnection {
|
||||||
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
||||||
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
||||||
|
#[allow(dead_code)]
|
||||||
tcp_read_handle: Arc<TcpReadHandle>,
|
tcp_read_handle: Arc<TcpReadHandle>,
|
||||||
socket_health: Arc<Mutex<SocketHealth>>,
|
socket_health: Arc<Mutex<SocketHealth>>,
|
||||||
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
@ -38,6 +38,8 @@ pub struct EngineConnection {
|
|||||||
|
|
||||||
/// The default planes for the scene.
|
/// The default planes for the scene.
|
||||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
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 {
|
pub struct TcpRead {
|
||||||
@ -181,6 +183,8 @@ impl EngineConnection {
|
|||||||
|
|
||||||
let mut tcp_read = TcpRead { stream: tcp_read };
|
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: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
|
||||||
let responses_clone = responses.clone();
|
let responses_clone = responses.clone();
|
||||||
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
|
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
|
||||||
@ -192,35 +196,40 @@ impl EngineConnection {
|
|||||||
match tcp_read.read().await {
|
match tcp_read.read().await {
|
||||||
Ok(ws_resp) => {
|
Ok(ws_resp) => {
|
||||||
// If we got a batch response, add all the inner responses.
|
// If we got a batch response, add all the inner responses.
|
||||||
if let Some(kittycad::types::OkWebSocketResponseData::ModelingBatch { responses }) =
|
match &ws_resp.resp {
|
||||||
&ws_resp.resp
|
Some(kittycad::types::OkWebSocketResponseData::ModelingBatch { responses }) => {
|
||||||
{
|
for (resp_id, batch_response) in responses {
|
||||||
for (resp_id, batch_response) in responses {
|
let id: uuid::Uuid = resp_id.parse().unwrap();
|
||||||
let id: uuid::Uuid = resp_id.parse().unwrap();
|
if let Some(response) = &batch_response.response {
|
||||||
if let Some(response) = &batch_response.response {
|
responses_clone.insert(
|
||||||
responses_clone.insert(
|
id,
|
||||||
id,
|
kittycad::types::WebSocketResponse {
|
||||||
kittycad::types::WebSocketResponse {
|
request_id: Some(id),
|
||||||
request_id: Some(id),
|
resp: Some(kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
resp: Some(kittycad::types::OkWebSocketResponseData::Modeling {
|
modeling_response: response.clone(),
|
||||||
modeling_response: response.clone(),
|
}),
|
||||||
}),
|
errors: None,
|
||||||
errors: None,
|
success: Some(true),
|
||||||
success: Some(true),
|
},
|
||||||
},
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
responses_clone.insert(
|
||||||
responses_clone.insert(
|
id,
|
||||||
id,
|
kittycad::types::WebSocketResponse {
|
||||||
kittycad::types::WebSocketResponse {
|
request_id: Some(id),
|
||||||
request_id: Some(id),
|
resp: None,
|
||||||
resp: None,
|
errors: batch_response.errors.clone(),
|
||||||
errors: batch_response.errors.clone(),
|
success: Some(false),
|
||||||
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 {
|
if let Some(id) = ws_resp.request_id {
|
||||||
@ -249,6 +258,7 @@ impl EngineConnection {
|
|||||||
batch: Arc::new(Mutex::new(Vec::new())),
|
batch: Arc::new(Mutex::new(Vec::new())),
|
||||||
batch_end: Arc::new(Mutex::new(HashMap::new())),
|
batch_end: Arc::new(Mutex::new(HashMap::new())),
|
||||||
default_planes: Default::default(),
|
default_planes: Default::default(),
|
||||||
|
session_data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,4 +355,8 @@ impl EngineManager for EngineConnection {
|
|||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||||
|
self.session_data.lock().unwrap().clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|