Compare commits
59 Commits
nightly-v2
...
jtran/fix-
Author | SHA1 | Date | |
---|---|---|---|
0c2f63b399 | |||
363ae10658 | |||
ac4a6c84cf | |||
c6fad2e2dc | |||
013cb10961 | |||
6261083cb1 | |||
2b0ba37ed0 | |||
96174f3cf6 | |||
aed62ff912 | |||
9334d64608 | |||
4fa7d2d8c8 | |||
3e615dfdbc | |||
c9860af29f | |||
23a42f0195 | |||
a77fa639f3 | |||
0a5ad7c95b | |||
4a654523d2 | |||
73a7e2bfd6 | |||
eb0850fea9 | |||
029f76f273 | |||
28b5f7080c | |||
5b1dcfecd6 | |||
f89d191425 | |||
2f4e4b62a8 | |||
5ebd5c8dbb | |||
a9ceaf2678 | |||
c8afd3399b | |||
5dda4828c6 | |||
72acab752c | |||
81df38ad1c | |||
0576a2bef1 | |||
4b2f6b4647 | |||
69edaa4183 | |||
2eb7c382bf | |||
38913ecb98 | |||
debd06129f | |||
d38bd342a0 | |||
f026f10335 | |||
895d7ebc6d | |||
65edf17a44 | |||
0c2a0a8c07 | |||
97cef4d16c | |||
9358278f7b | |||
a174e084d4 | |||
df7246897a | |||
0c9f64dd7c | |||
d2b9d3a058 | |||
7e54f08778 | |||
d9c2dd376e | |||
275a2150e7 | |||
8b8feb8d68 | |||
e21ef3f122 | |||
66834931aa | |||
06c1bcaf2e | |||
fc7df7ecbe | |||
67cb7b33bb | |||
ea74b94fac | |||
529833c63f | |||
92da86391a |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
|
||||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts,./packages/codemirror-lang-kcl/test/all.test.ts
|
skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo
|
||||||
|
12
.eslintrc
@ -5,16 +5,24 @@
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"css-modules",
|
"css-modules",
|
||||||
|
"jest",
|
||||||
|
"react",
|
||||||
"suggest-no-throw",
|
"suggest-no-throw",
|
||||||
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
|
||||||
"react-app/jest",
|
|
||||||
"plugin:css-modules/recommended"
|
"plugin:css-modules/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
"@typescript-eslint/no-misused-promises": "error",
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
|
"no-restricted-globals": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"name": "isNaN",
|
||||||
|
"message": "Use Number.isNaN() instead."
|
||||||
|
}
|
||||||
|
],
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
|
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
retry=1
|
retry=1
|
||||||
max_retrys=4
|
max_retrys=5
|
||||||
|
|
||||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retrys ]]; do
|
while [[ $retry -le $max_retrys ]]; do
|
||||||
|
4
.github/dependabot.yml
vendored
@ -26,3 +26,7 @@ updates:
|
|||||||
reviewers:
|
reviewers:
|
||||||
- adamchalmers
|
- adamchalmers
|
||||||
- jessfraz
|
- jessfraz
|
||||||
|
groups:
|
||||||
|
serde-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "serde*"
|
||||||
|
6
.github/workflows/e2e-tests.yml
vendored
@ -127,9 +127,12 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: yarn tron:package
|
run: yarn tron:package
|
||||||
- name: Run ubuntu/chrome snapshots
|
- name: Run ubuntu/chrome snapshots
|
||||||
|
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
|
||||||
|
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
|
||||||
run: |
|
run: |
|
||||||
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
@ -150,6 +153,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: rm -r test-results
|
run: rm -r test-results
|
||||||
- name: check for changes
|
- name: check for changes
|
||||||
|
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||||
shell: bash
|
shell: bash
|
||||||
id: git-check
|
id: git-check
|
||||||
run: |
|
run: |
|
||||||
|
40
README.md
@ -337,13 +337,47 @@ For individual testing:
|
|||||||
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
||||||
```
|
```
|
||||||
|
|
||||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro) tests, in interactive mode by default.
|
||||||
|
|
||||||
### Rust tests
|
### Rust tests
|
||||||
|
|
||||||
```bash
|
**Dependencies**
|
||||||
|
|
||||||
|
- `KITTYCAD_API_TOKEN`
|
||||||
|
- `cargo-nextest`
|
||||||
|
- `just`
|
||||||
|
|
||||||
|
#### Setting KITTYCAD_API_TOKEN
|
||||||
|
Use the production zoo.dev token, set this environment variable before running the tests
|
||||||
|
|
||||||
|
#### Installing cargonextest
|
||||||
|
|
||||||
|
```
|
||||||
cd src/wasm-lib
|
cd src/wasm-lib
|
||||||
KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1
|
cargo search cargo-nextest
|
||||||
|
cargo install cargo-nextest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### just
|
||||||
|
install [`just`](https://github.com/casey/just?tab=readme-ov-file#pre-built-binaries)
|
||||||
|
|
||||||
|
#### Running the tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With just
|
||||||
|
# Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set
|
||||||
|
# Make sure you installed cargo-nextest
|
||||||
|
# Make sure you installed just
|
||||||
|
cd src/wasm-lib
|
||||||
|
just test
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Without just
|
||||||
|
# Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set
|
||||||
|
# Make sure you installed cargo-nextest
|
||||||
|
cd src/wasm-lib
|
||||||
|
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `XXX` is an API token from the production engine (NOT the dev environment).
|
Where `XXX` is an API token from the production engine (NOT the dev environment).
|
||||||
|
@ -24,3 +24,5 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
chamfer cases work currently.
|
chamfer cases work currently.
|
||||||
|
|
||||||
- **Appearance**: Changing the appearance on a loft does not work.
|
- **Appearance**: Changing the appearance on a loft does not work.
|
||||||
|
|
||||||
|
- **Helix**: Currently sweeping a helix does not work.
|
||||||
|
43
docs/kcl/helixRevolutions.md
Normal file
@ -48,6 +48,7 @@ layout: manual
|
|||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
* [`helix`](kcl/helix)
|
* [`helix`](kcl/helix)
|
||||||
|
* [`helixRevolutions`](kcl/helixRevolutions)
|
||||||
* [`hole`](kcl/hole)
|
* [`hole`](kcl/hole)
|
||||||
* [`hollow`](kcl/hollow)
|
* [`hollow`](kcl/hollow)
|
||||||
* [`import`](kcl/import)
|
* [`import`](kcl/import)
|
||||||
@ -81,6 +82,7 @@ layout: manual
|
|||||||
* [`pi`](kcl/pi)
|
* [`pi`](kcl/pi)
|
||||||
* [`polar`](kcl/polar)
|
* [`polar`](kcl/polar)
|
||||||
* [`polygon`](kcl/polygon)
|
* [`polygon`](kcl/polygon)
|
||||||
|
* [`pop`](kcl/pop)
|
||||||
* [`pow`](kcl/pow)
|
* [`pow`](kcl/pow)
|
||||||
* [`profileStart`](kcl/profileStart)
|
* [`profileStart`](kcl/profileStart)
|
||||||
* [`profileStartX`](kcl/profileStartX)
|
* [`profileStartX`](kcl/profileStartX)
|
||||||
|
39
docs/kcl/pop.md
Normal file
6781
docs/kcl/std.json
@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
title: "AxisOrEdgeReference"
|
title: "Axis2dOrEdgeReference"
|
||||||
excerpt: "Axis or tagged edge."
|
excerpt: "A 2D axis or tagged edge."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Axis or tagged edge.
|
A 2D axis or tagged edge.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
**This schema accepts any of the following:**
|
||||||
|
|
||||||
Axis and origin.
|
2D axis and origin.
|
||||||
|
|
||||||
[`AxisAndOrigin`](/docs/kcl/types/AxisAndOrigin)
|
[`AxisAndOrigin2d`](/docs/kcl/types/AxisAndOrigin2d)
|
||||||
|
|
||||||
|
|
||||||
|
|
42
docs/kcl/types/Axis3dOrEdgeReference.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: "Axis3dOrEdgeReference"
|
||||||
|
excerpt: "A 3D axis or tagged edge."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A 3D axis or tagged edge.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**This schema accepts any of the following:**
|
||||||
|
|
||||||
|
3D axis and origin.
|
||||||
|
|
||||||
|
[`AxisAndOrigin3d`](/docs/kcl/types/AxisAndOrigin3d)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Tagged edge.
|
||||||
|
|
||||||
|
[`EdgeReference`](/docs/kcl/types/EdgeReference)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "AxisAndOrigin"
|
title: "AxisAndOrigin2d"
|
||||||
excerpt: "Axis and origin."
|
excerpt: "A 2D axis and origin."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Axis and origin.
|
A 2D axis and origin.
|
||||||
|
|
||||||
|
|
||||||
|
|
105
docs/kcl/types/AxisAndOrigin3d.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
title: "AxisAndOrigin3d"
|
||||||
|
excerpt: "A 3D axis and origin."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A 3D axis and origin.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**This schema accepts exactly one of the following:**
|
||||||
|
|
||||||
|
X-axis.
|
||||||
|
|
||||||
|
**enum:** `X`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Y-axis.
|
||||||
|
|
||||||
|
**enum:** `Y`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Z-axis.
|
||||||
|
|
||||||
|
**enum:** `Z`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Flip the X-axis.
|
||||||
|
|
||||||
|
**enum:** `-X`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Flip the Y-axis.
|
||||||
|
|
||||||
|
**enum:** `-Y`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Flip the Z-axis.
|
||||||
|
|
||||||
|
**enum:** `-Z`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `custom` |`object`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
25
docs/kcl/types/Helix.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: "Helix"
|
||||||
|
excerpt: "A helix."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A helix.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `value` |`string`| The id of the helix. | No |
|
||||||
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "HelixData"
|
title: "HelixData"
|
||||||
excerpt: "Data for helices."
|
excerpt: "Data for a helix."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Data for helices.
|
Data for a helix.
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -19,6 +19,8 @@ Data for helices.
|
|||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||||
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |
|
| `length` |`number`| Length of the helix. | No |
|
||||||
|
| `radius` |`number`| Radius of the helix. | No |
|
||||||
|
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |
|
||||||
|
|
||||||
|
|
||||||
|
24
docs/kcl/types/HelixRevolutionsData.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: "HelixRevolutionsData"
|
||||||
|
excerpt: "Data for helix revolutions."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Data for helix revolutions.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||||
|
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |
|
||||||
|
|
||||||
|
|
25
docs/kcl/types/HelixValue.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: "HelixValue"
|
||||||
|
excerpt: "A helix."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A helix.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `value` |`string`| The id of the helix. | No |
|
||||||
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
@ -285,6 +285,27 @@ An solid is a collection of extrude surfaces.
|
|||||||
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
A helix.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
|
||||||
|
| `value` |`string`| The id of the helix. | No |
|
||||||
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
Data for an imported geometry.
|
Data for an imported geometry.
|
||||||
|
|
||||||
|
@ -16,6 +16,6 @@ Data for a mirror.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis to use as mirror. | No |
|
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis to use as mirror. | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ Data for revolution surfaces.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
|
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
|
||||||
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis of revolution. | No |
|
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis of revolution. | No |
|
||||||
| `tolerance` |`number`| Tolerance for the revolve operation. | No |
|
| `tolerance` |`number`| Tolerance for the revolve operation. | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Data for a sweep.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
| `path` |[`SweepPath`](/docs/kcl/types/SweepPath)| The path to sweep along. | No |
|
||||||
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||||
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
||||||
|
|
||||||
|
42
docs/kcl/types/SweepPath.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: "SweepPath"
|
||||||
|
excerpt: "A path to sweep along."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A path to sweep along.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**This schema accepts any of the following:**
|
||||||
|
|
||||||
|
A path to sweep along.
|
||||||
|
|
||||||
|
[`Sketch`](/docs/kcl/types/Sketch)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
A path to sweep along.
|
||||||
|
|
||||||
|
[`Helix`](/docs/kcl/types/Helix)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,7 +36,8 @@ type DragFromHandler = (
|
|||||||
|
|
||||||
export class SceneFixture {
|
export class SceneFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
public streamWrapper!: Locator
|
||||||
|
public loadingIndicator!: Locator
|
||||||
private exeIndicator!: Locator
|
private exeIndicator!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
@ -64,6 +65,8 @@ export class SceneFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
|
|
||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
|
this.streamWrapper = page.getByTestId('stream')
|
||||||
|
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
||||||
}
|
}
|
||||||
|
|
||||||
makeMouseHelpers = (
|
makeMouseHelpers = (
|
||||||
|
@ -14,6 +14,7 @@ export class ToolbarFixture {
|
|||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
loftButton!: Locator
|
loftButton!: Locator
|
||||||
|
sweepButton!: Locator
|
||||||
shellButton!: Locator
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
@ -40,6 +41,7 @@ export class ToolbarFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
|
this.sweepButton = page.getByTestId('sweep')
|
||||||
this.shellButton = page.getByTestId('shell')
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
|
@ -756,6 +756,17 @@ test(`Offset plane point-and-click`, async ({
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await test.step('Delete offset plane via feature tree selection', async () => {
|
||||||
|
await editor.closePane()
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
|
'Offset Plane',
|
||||||
|
0
|
||||||
|
)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const loftPointAndClickCases = [
|
const loftPointAndClickCases = [
|
||||||
@ -851,6 +862,173 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await test.step('Delete loft via feature tree selection', async () => {
|
||||||
|
await editor.closePane()
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: merge with above test. Right now we're not able to delete a loft
|
||||||
|
// right after creation via selection for some reason, so we go with a new instance
|
||||||
|
test('Loft and offset plane deletion via selection', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
|
plane001 = offsetPlane('XZ', 50)
|
||||||
|
sketch002 = startSketchOn(plane001)
|
||||||
|
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||||
|
loft001 = loft([sketch001, sketch002])
|
||||||
|
`
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 575, y: 200 }
|
||||||
|
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 80)
|
||||||
|
|
||||||
|
await test.step(`Delete loft`, async () => {
|
||||||
|
// Check for loft
|
||||||
|
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||||
|
await clickOnSketch1()
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||||
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
|
`)
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
// Check for sketch 1
|
||||||
|
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete sketch002', async () => {
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await clickOnSketch2()
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||||
|
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||||
|
`)
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
// Check for plane001
|
||||||
|
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete plane001', async () => {
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await clickOnSketch2()
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||||
|
plane001 = offsetPlane('XZ', 50)
|
||||||
|
`)
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
// Check for sketch 1
|
||||||
|
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Sweep point-and-click`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|
|> circle({
|
||||||
|
center = [0, 0],
|
||||||
|
radius = 500
|
||||||
|
}, %)
|
||||||
|
sketch002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> xLine(-500, %)
|
||||||
|
|> tangentialArcTo([-2000, 500], %)
|
||||||
|
`
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 700, y: 250 }
|
||||||
|
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
|
||||||
|
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
|
||||||
|
|
||||||
|
await test.step(`Look for sketch001`, async () => {
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
|
await toolbar.sweepButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
currentArgKey: 'profile',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Path: '',
|
||||||
|
Profile: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'profile',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await clickOnSketch1()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
currentArgKey: 'path',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Path: '',
|
||||||
|
Profile: '1 face',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'path',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await clickOnSketch2()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
headerArguments: {
|
||||||
|
Path: '1 face',
|
||||||
|
Profile: '1 face',
|
||||||
|
},
|
||||||
|
stage: 'review',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
||||||
|
await toolbar.openPane('code')
|
||||||
|
await editor.expectEditor.toContain(sweepDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [sweepDeclaration],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete sweep via feature tree selection', async () => {
|
||||||
|
await toolbar.openPane('feature-tree')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await toolbar.closePane('feature-tree')
|
||||||
|
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1030,4 +1208,104 @@ extrude001 = extrude(40, sketch001)
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await test.step('Delete shell via feature tree selection', async () => {
|
||||||
|
await editor.closePane()
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const shellSketchOnFacesCases = [
|
||||||
|
`sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 100 }, %)
|
||||||
|
|> extrude(100, %)
|
||||||
|
|
||||||
|
sketch002 = startSketchOn(sketch001, 'END')
|
||||||
|
|> circle({ center = [0, 0], radius = 50 }, %)
|
||||||
|
|> extrude(50, %)
|
||||||
|
`,
|
||||||
|
`sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 100 }, %)
|
||||||
|
extrude001 = extrude(100, sketch001)
|
||||||
|
|
||||||
|
sketch002 = startSketchOn(extrude001, 'END')
|
||||||
|
|> circle({ center = [0, 0], radius = 50 }, %)
|
||||||
|
extrude002 = extrude(50, sketch002)
|
||||||
|
`,
|
||||||
|
]
|
||||||
|
shellSketchOnFacesCases.forEach((initialCode, index) => {
|
||||||
|
const hasExtrudesInPipe = index === 0
|
||||||
|
test(`Shell point-and-click sketch on face (extrudes in pipes: ${hasExtrudesInPipe})`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 550, y: 295 }
|
||||||
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
|
||||||
|
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
||||||
|
})`
|
||||||
|
|
||||||
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor([128, 128, 128], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnCap()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await toolbar.openPane('code')
|
||||||
|
await editor.expectEditor.toContain(shellDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [shellDeclaration],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor([73, 73, 73], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -115,7 +115,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'yyyyyyyyy open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -199,7 +199,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'aaayyyyyyyy open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -276,7 +276,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'nooooooooooooo open a file in a project works and renders, open empty file, it should clear the scene',
|
'open a file in a project works and renders, open empty file, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -1885,3 +1885,48 @@ test.fixme(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'project name with foreign characters should open',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const bracketDir = path.join(dir, 'اَلْعَرَبِيَّةُ')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
|
path.join(bracketDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
|
||||||
|
await fsp.writeFile(path.join(bracketDir, 'empty.kcl'), '')
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
const pointOnModel = { x: 630, y: 280 }
|
||||||
|
|
||||||
|
await test.step('Opening the اَلْعَرَبِيَّةُ project should load the stream', async () => {
|
||||||
|
// expect to see the text bracket
|
||||||
|
await expect(page.getByText('اَلْعَرَبِيَّةُ')).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByText('اَلْعَرَبِيَّةُ').click()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeEnabled({
|
||||||
|
timeout: 20_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// gray at this pixel means the stream has loaded in the most
|
||||||
|
// user way we can verify it (pixel color)
|
||||||
|
await expect
|
||||||
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -614,6 +614,38 @@ extrude001 = extrude(50, sketch001)
|
|||||||
await expect(gizmo).toBeVisible()
|
await expect(gizmo).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Refreshing the app doesn't cause the stream to pause on long-executing files`, async ({
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
toolbar,
|
||||||
|
viewport,
|
||||||
|
}) => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const legoDir = path.join(dir, 'lego')
|
||||||
|
await fsp.mkdir(legoDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('lego.kcl'),
|
||||||
|
path.join(legoDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Test setup`, async () => {
|
||||||
|
await homePage.openProject('lego')
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
})
|
||||||
|
await test.step(`Waiting for the loading spinner to disappear`, async () => {
|
||||||
|
await scene.loadingIndicator.waitFor({ state: 'detached' })
|
||||||
|
})
|
||||||
|
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
[143, 143, 143],
|
||||||
|
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
|
||||||
|
15
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function clickExportButton(page: Page) {
|
async function clickExportButton(page: Page) {
|
||||||
|
@ -39,8 +39,8 @@ test.describe('Sketch tests', () => {
|
|||||||
${startProfileAt1}
|
${startProfileAt1}
|
||||||
|> arc({
|
|> arc({
|
||||||
radius = screwRadius,
|
radius = screwRadius,
|
||||||
angle_start = 0,
|
angleStart = 0,
|
||||||
angle_end = 360
|
angleEnd = 360
|
||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
part001 = startSketchOn('XY')
|
part001 = startSketchOn('XY')
|
||||||
@ -60,8 +60,8 @@ test.describe('Sketch tests', () => {
|
|||||||
|> yLine(wireOffset, %)
|
|> yLine(wireOffset, %)
|
||||||
|> arc({
|
|> arc({
|
||||||
radius = wireRadius,
|
radius = wireRadius,
|
||||||
angle_start = 0,
|
angleStart = 0,
|
||||||
angle_end = 180
|
angleEnd = 180
|
||||||
}, %)
|
}, %)
|
||||||
|> yLine(-wireOffset, %)
|
|> yLine(-wireOffset, %)
|
||||||
|> xLine(-width / 4, %)
|
|> xLine(-width / 4, %)
|
||||||
@ -1323,3 +1323,85 @@ test.describe(`Sketching with offset planes`, () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4891
|
||||||
|
test.describe(`Click based selection don't brick the app when clicked out of range after format using cache`, () => {
|
||||||
|
test(`Can select a line that reformmed after entering sketch mode`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
scene,
|
||||||
|
toolbar,
|
||||||
|
editor,
|
||||||
|
homePage,
|
||||||
|
}) => {
|
||||||
|
// We seed the scene with a single offset plane
|
||||||
|
await context.addInitScript(() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([3.14, 3.14], %)
|
||||||
|
|> arcTo({
|
||||||
|
end = [4, 2],
|
||||||
|
interior = [1, 2]
|
||||||
|
}, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
|
await test.step(`format the code`, async () => {
|
||||||
|
// doesn't contain condensed version
|
||||||
|
await editor.expectEditor.not.toContain(
|
||||||
|
`arcTo({ end = [4, 2], interior = [1, 2] }, %)`
|
||||||
|
)
|
||||||
|
// click the code to enter sketch mode
|
||||||
|
await page.getByText(`arcTo`).click()
|
||||||
|
// Format the code.
|
||||||
|
await page.locator('#code-pane button:first-child').click()
|
||||||
|
await page.locator('button:has-text("Format code")').click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Ensure the code reformatted`, async () => {
|
||||||
|
await editor.expectEditor.toContain(
|
||||||
|
`arcTo({ end = [4, 2], interior = [1, 2] }, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const [arcClick, arcHover] = scene.makeMouseHelpers(699, 337)
|
||||||
|
await test.step('Ensure we can hover the arc', async () => {
|
||||||
|
await arcHover()
|
||||||
|
|
||||||
|
// Check that the code is highlighted
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: ["sketch001=startSketchOn('XZ')"],
|
||||||
|
diagnostics: [],
|
||||||
|
highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('reset the selection', async () => {
|
||||||
|
// Move the mouse out of the way
|
||||||
|
await page.mouse.move(655, 337)
|
||||||
|
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: ["sketch001=startSketchOn('XZ')"],
|
||||||
|
diagnostics: [],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Ensure we can click the arc', async () => {
|
||||||
|
await arcClick()
|
||||||
|
|
||||||
|
// Check that the code is highlighted
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [],
|
||||||
|
diagnostics: [],
|
||||||
|
highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 38 KiB |
@ -389,25 +389,25 @@ test.describe('Testing selections', () => {
|
|||||||
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 0, y = -50, z = 0 },
|
origin = { x = 0, y = -50, z = 0 },
|
||||||
x_axis = { x = 1, y = 0, z = 0 },
|
xAxis = { x = 1, y = 0, z = 0 },
|
||||||
y_axis = { x = 0, y = 0, z = 1 },
|
yAxis = { x = 0, y = 0, z = 1 },
|
||||||
z_axis = { x = 0, y = -1, z = 0 }
|
zAxis = { x = 0, y = -1, z = 0 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||||
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
xAxis = { x = -0.81, y = 0, z = 0.58 },
|
||||||
y_axis = { x = 0, y = -1, z = 0 },
|
yAxis = { x = 0, y = -1, z = 0 },
|
||||||
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
zAxis = { x = 0.58, y = 0, z = 0.81 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||||
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
xAxis = { x = -0.66, y = 0, z = -0.75 },
|
||||||
y_axis = { x = 0, y = -1, z = 0 },
|
yAxis = { x = 0, y = -1, z = 0 },
|
||||||
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
zAxis = { x = -0.75, y = 0, z = 0.66 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
|
|
||||||
|
@ -156,13 +156,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
@ -224,13 +224,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||||
@ -314,13 +314,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||||
@ -392,13 +392,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
@ -604,7 +604,7 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
|
18
flake.lock
generated
@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721933792,
|
"lastModified": 1736320768,
|
||||||
"narHash": "sha256-zYVwABlQnxpbaHMfX6Wt9jhyQstFYwN2XjleOJV3VVg=",
|
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2122a9b35b35719ad9a395fe783eabb092df01b1",
|
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1718428119,
|
"lastModified": 1728538411,
|
||||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -43,11 +43,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721960387,
|
"lastModified": 1736476219,
|
||||||
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=",
|
"narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699",
|
"rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
8
interface.d.ts
vendored
@ -11,6 +11,13 @@ export interface IElectronAPI {
|
|||||||
open: typeof dialog.showOpenDialog
|
open: typeof dialog.showOpenDialog
|
||||||
save: typeof dialog.showSaveDialog
|
save: typeof dialog.showSaveDialog
|
||||||
openExternal: typeof shell.openExternal
|
openExternal: typeof shell.openExternal
|
||||||
|
takeElectronWindowScreenshot: ({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}) => Promise<string>
|
||||||
showInFolder: typeof shell.showItemInFolder
|
showInFolder: typeof shell.showItemInFolder
|
||||||
/** Require to be called first before {@link loginWithDeviceFlow} */
|
/** Require to be called first before {@link loginWithDeviceFlow} */
|
||||||
startDeviceFlow: (host: string) => Promise<string>
|
startDeviceFlow: (host: string) => Promise<string>
|
||||||
@ -86,5 +93,6 @@ export interface IElectronAPI {
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: IElectronAPI
|
electron: IElectronAPI
|
||||||
|
openExternalLink: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
package.json
@ -15,18 +15,18 @@
|
|||||||
"@codemirror/autocomplete": "^6.17.0",
|
"@codemirror/autocomplete": "^6.17.0",
|
||||||
"@codemirror/commands": "^6.6.0",
|
"@codemirror/commands": "^6.6.0",
|
||||||
"@codemirror/language": "^6.10.3",
|
"@codemirror/language": "^6.10.3",
|
||||||
"@codemirror/lint": "^6.8.1",
|
"@codemirror/lint": "^6.8.4",
|
||||||
"@codemirror/search": "^6.5.6",
|
"@codemirror/search": "^6.5.6",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@csstools/postcss-oklab-function": "^4.0.2",
|
"@csstools/postcss-oklab-function": "^4.0.7",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@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.7",
|
"@kittycad/lib": "2.0.13",
|
||||||
"@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",
|
||||||
@ -52,13 +52,13 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-hotkeys-hook": "^4.5.1",
|
"react-hotkeys-hook": "^4.6.1",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.3",
|
||||||
"react-modal-promise": "^1.0.2",
|
"react-modal-promise": "^1.0.2",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"sketch-helpers": "^0.0.4",
|
"sketch-helpers": "^0.0.4",
|
||||||
"three": "^0.166.1",
|
"three": "^0.172.0",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uuid": "^11.0.2",
|
"uuid": "^11.0.2",
|
||||||
"vscode-jsonrpc": "^8.2.1",
|
"vscode-jsonrpc": "^8.2.1",
|
||||||
@ -91,8 +91,8 @@
|
|||||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"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": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||||
"lint-fix": "eslint --fix src e2e packages/codemirror-lsp-client",
|
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||||
"lint": "eslint --max-warnings 0 src e2e packages/codemirror-lsp-client",
|
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||||
@ -166,13 +166,11 @@
|
|||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
"@types/three": "^0.163.0",
|
"@types/three": "^0.172.0",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@types/wicg-file-system-access": "^2023.10.5",
|
"@types/wicg-file-system-access": "^2023.10.5",
|
||||||
"@types/ws": "^8.5.13",
|
"@types/ws": "^8.5.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
|
||||||
"@vitejs/plugin-react": "^4.3.0",
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
"@vitest/web-worker": "^1.5.0",
|
"@vitest/web-worker": "^1.5.0",
|
||||||
"@xstate/cli": "^0.5.17",
|
"@xstate/cli": "^0.5.17",
|
||||||
@ -182,11 +180,12 @@
|
|||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
"electron-notarize": "1.2.2",
|
"electron-notarize": "1.2.2",
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.0.1",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-import": "^2.30.0",
|
"eslint-plugin-import": "^2.30.0",
|
||||||
|
"eslint-plugin-jest": "^28.10.0",
|
||||||
|
"eslint-plugin-react": "^7.37.3",
|
||||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^15.11.7",
|
"happy-dom": "^16.3.0",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"husky": "^9.1.5",
|
"husky": "^9.1.5",
|
||||||
"kill-port": "^2.0.1",
|
"kill-port": "^2.0.1",
|
||||||
@ -200,6 +199,7 @@
|
|||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
|
"typescript-eslint": "^8.19.1",
|
||||||
"vite": "^5.4.6",
|
"vite": "^5.4.6",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
statement[@isGroup=Statement] {
|
statement[@isGroup=Statement] {
|
||||||
ImportStatement { kw<"import"> ImportItems ImportFrom String } |
|
ImportStatement { kw<"import"> ImportItems ImportFrom String } |
|
||||||
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals ParamList Arrow Body } |
|
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } |
|
||||||
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||||
ReturnStatement { kw<"return"> expression } |
|
ReturnStatement { kw<"return"> expression } |
|
||||||
ExpressionStatement { expression }
|
ExpressionStatement { expression }
|
||||||
@ -57,7 +57,7 @@ expression[@isGroup=Expression] {
|
|||||||
|
|
||||||
UnaryOp { AddOp | BangOp }
|
UnaryOp { AddOp | BangOp }
|
||||||
|
|
||||||
ObjectProperty { PropertyName ":" expression }
|
ObjectProperty { PropertyName (":" | Equals) expression }
|
||||||
|
|
||||||
ArgumentList { "(" commaSep<expression> ")" }
|
ArgumentList { "(" commaSep<expression> ")" }
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
|
|||||||
@tokens {
|
@tokens {
|
||||||
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
||||||
|
|
||||||
Number { "." @digit+ | @digit+ ("." @digit*)? }
|
Number { "." @digit+ | @digit+ ("." @digit+)? }
|
||||||
@precedence { Number, "." }
|
@precedence { Number, "." }
|
||||||
|
|
||||||
AddOp { "+" | "-" }
|
AddOp { "+" | "-" }
|
||||||
|
60
packages/codemirror-lang-kcl/test/fn.txt
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# full
|
||||||
|
|
||||||
|
fn two = () => {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
Program(FunctionDeclaration(fn,
|
||||||
|
VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ParamList,
|
||||||
|
Arrow,
|
||||||
|
Body(ReturnStatement(return,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# = is optional
|
||||||
|
|
||||||
|
fn one () => {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
Program(FunctionDeclaration(fn,
|
||||||
|
VariableDefinition,
|
||||||
|
ParamList,
|
||||||
|
Arrow,
|
||||||
|
Body(ReturnStatement(return,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# => is optional
|
||||||
|
|
||||||
|
fn one = () {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
Program(FunctionDeclaration(fn,
|
||||||
|
VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ParamList,
|
||||||
|
Body(ReturnStatement(return,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# terse
|
||||||
|
|
||||||
|
fn two() {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
Program(FunctionDeclaration(fn,
|
||||||
|
VariableDefinition,
|
||||||
|
ParamList,
|
||||||
|
Body(ReturnStatement(return,
|
||||||
|
Number))))
|
||||||
|
|
20
packages/codemirror-lang-kcl/test/key.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# colon (deprecated)
|
||||||
|
|
||||||
|
x = { k: 123 }
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(VariableDeclaration(VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ObjectExpression(ObjectProperty(PropertyName,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# equal
|
||||||
|
|
||||||
|
x = { k = 123 }
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(VariableDeclaration(VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ObjectExpression(ObjectProperty(PropertyName,
|
||||||
|
Equals,
|
||||||
|
Number))))
|
43
packages/codemirror-lang-kcl/test/range.txt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# spaced
|
||||||
|
|
||||||
|
a = [0 .. 1]
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(VariableDeclaration(VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ArrayExpression(IntegerRange(Number,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# compact
|
||||||
|
|
||||||
|
a = [0..1]
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(VariableDeclaration(VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ArrayExpression(IntegerRange(Number,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# expr spaced
|
||||||
|
|
||||||
|
a = [start .. start + 10]
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(VariableDeclaration(VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ArrayExpression(IntegerRange(VariableName,
|
||||||
|
BinaryExpression(VariableName,
|
||||||
|
AddOp,
|
||||||
|
Number)))))
|
||||||
|
|
||||||
|
# expr compact
|
||||||
|
|
||||||
|
a = [start..start + 10]
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(VariableDeclaration(VariableDefinition,
|
||||||
|
Equals,
|
||||||
|
ArrayExpression(IntegerRange(VariableName,
|
||||||
|
BinaryExpression(VariableName,
|
||||||
|
AddOp,
|
||||||
|
Number)))))
|
@ -42,7 +42,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
|||||||
// try to parse the content-length from the headers
|
// try to parse the content-length from the headers
|
||||||
const length = parseInt(match[1])
|
const length = parseInt(match[1])
|
||||||
|
|
||||||
if (isNaN(length))
|
if (Number.isNaN(length))
|
||||||
return Promise.reject(new Error('invalid content length'))
|
return Promise.reject(new Error('invalid content length'))
|
||||||
|
|
||||||
// slice the headers since we now have the content length
|
// slice the headers since we now have the content length
|
||||||
|
@ -368,13 +368,20 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
sortText,
|
sortText,
|
||||||
filterText,
|
filterText,
|
||||||
}) => {
|
}) => {
|
||||||
|
const detailText = [
|
||||||
|
deprecated ? 'Deprecated' : undefined,
|
||||||
|
labelDetails ? labelDetails.detail : detail,
|
||||||
|
]
|
||||||
|
// Don't let undefined appear.
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
const completion: Completion & {
|
const completion: Completion & {
|
||||||
filterText: string
|
filterText: string
|
||||||
sortText?: string
|
sortText?: string
|
||||||
apply: string
|
apply: string
|
||||||
} = {
|
} = {
|
||||||
label,
|
label,
|
||||||
detail: labelDetails ? labelDetails.detail : detail,
|
detail: detailText,
|
||||||
apply: label,
|
apply: label,
|
||||||
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
||||||
sortText: sortText ?? label,
|
sortText: sortText ?? label,
|
||||||
@ -382,7 +389,11 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
if (documentation) {
|
if (documentation) {
|
||||||
completion.info = () => {
|
completion.info = () => {
|
||||||
const htmlString = formatMarkdownContents(documentation)
|
const deprecatedHtml = deprecated
|
||||||
|
? '<p><strong>Deprecated</strong></p>'
|
||||||
|
: ''
|
||||||
|
const htmlString =
|
||||||
|
deprecatedHtml + formatMarkdownContents(documentation)
|
||||||
const htmlNode = document.createElement('div')
|
const htmlNode = document.createElement('div')
|
||||||
htmlNode.style.display = 'contents'
|
htmlNode.style.display = 'contents'
|
||||||
htmlNode.innerHTML = htmlString
|
htmlNode.innerHTML = htmlString
|
||||||
|
@ -32,10 +32,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'Google Chrome',
|
name: 'chromium',
|
||||||
use: {
|
use: {
|
||||||
...devices['Desktop Chrome'],
|
...devices['Desktop Chrome'],
|
||||||
channel: 'chrome',
|
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
/* Chromium is the only one with these permission types */
|
/* Chromium is the only one with these permission types */
|
||||||
permissions: ['clipboard-write', 'clipboard-read'],
|
permissions: ['clipboard-write', 'clipboard-read'],
|
||||||
|
@ -1230,12 +1230,13 @@ export class SceneEntities {
|
|||||||
// lee: Well, it appears all our code in sceneEntities each act as their own
|
// lee: Well, it appears all our code in sceneEntities each act as their own
|
||||||
// kind of classes. In this case, I'll keep utility functions pertaining to
|
// kind of classes. In this case, I'll keep utility functions pertaining to
|
||||||
// circle3Point here. Feel free to extract as needed.
|
// circle3Point here. Feel free to extract as needed.
|
||||||
entryDraftCircle3Point = async (
|
entryDraftCircle3Point = (
|
||||||
|
done: () => void,
|
||||||
startSketchOnASTNodePath: PathToNode,
|
startSketchOnASTNodePath: PathToNode,
|
||||||
forward: Vector3,
|
forward: Vector3,
|
||||||
up: Vector3,
|
up: Vector3,
|
||||||
sketchOrigin: Vector3
|
sketchOrigin: Vector3
|
||||||
) => {
|
): (() => void) => {
|
||||||
// lee: Not a fan we need to re-iterate this dummy object all over the place
|
// lee: Not a fan we need to re-iterate this dummy object all over the place
|
||||||
// just to get the scale but okie dokie.
|
// just to get the scale but okie dokie.
|
||||||
const dummy = new Mesh()
|
const dummy = new Mesh()
|
||||||
@ -1374,13 +1375,13 @@ export class SceneEntities {
|
|||||||
groupOfDrafts.add(groupCircle)
|
groupOfDrafts.add(groupCircle)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
this.scene.remove(groupOfDrafts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The target of our dragging
|
// The target of our dragging
|
||||||
let target: Object3D | undefined = undefined
|
let target: Object3D | undefined = undefined
|
||||||
|
|
||||||
|
const cleanupFn = () => {
|
||||||
|
this.scene.remove(groupOfDrafts)
|
||||||
|
}
|
||||||
|
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
async onDrag(args) {
|
async onDrag(args) {
|
||||||
const draftPointsIntersected = args.intersects.filter(
|
const draftPointsIntersected = args.intersects.filter(
|
||||||
@ -1444,9 +1445,11 @@ export class SceneEntities {
|
|||||||
await kclManager.executeAstMock(astSnapshot)
|
await kclManager.executeAstMock(astSnapshot)
|
||||||
await codeManager.updateEditorWithAstAndWriteToFile(astSnapshot)
|
await codeManager.updateEditorWithAstAndWriteToFile(astSnapshot)
|
||||||
|
|
||||||
sceneInfra.modelingSend({ type: 'circle3PointsFinished', cleanup })
|
done()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return cleanupFn
|
||||||
}
|
}
|
||||||
setupDraftCircle = async (
|
setupDraftCircle = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
|
@ -75,6 +75,7 @@ function CommandBarTextareaInput({
|
|||||||
target.selectionStart = selectionStart + 1
|
target.selectionStart = selectionStart + 1
|
||||||
target.selectionEnd = selectionStart + 1
|
target.selectionEnd = selectionStart + 1
|
||||||
} else if (event.key === 'Enter') {
|
} else if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
formRef.current?.dispatchEvent(
|
formRef.current?.dispatchEvent(
|
||||||
new Event('submit', { bubbles: true })
|
new Event('submit', { bubbles: true })
|
||||||
)
|
)
|
||||||
|
@ -157,12 +157,10 @@ export const ModelingMachineProvider = ({
|
|||||||
'enable copilot': () => {
|
'enable copilot': () => {
|
||||||
editorManager.setCopilotEnabled(true)
|
editorManager.setCopilotEnabled(true)
|
||||||
},
|
},
|
||||||
// tsc reports this typing as perfectly fine, but eslint is complaining.
|
'sketch exit execute': ({ context: { store } }) => {
|
||||||
// It's actually nonsensical, so I'm quieting.
|
// TODO: Remove this async callback. For some reason eslint wouldn't
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// let me disable @typescript-eslint/no-misused-promises for the line.
|
||||||
'sketch exit execute': async ({
|
;(async () => {
|
||||||
context: { store },
|
|
||||||
}): Promise<void> => {
|
|
||||||
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
||||||
await engineCommandManager.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
@ -190,6 +188,7 @@ export const ModelingMachineProvider = ({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
|
})().catch(reportRejection)
|
||||||
},
|
},
|
||||||
'Set mouse state': assign(({ context, event }) => {
|
'Set mouse state': assign(({ context, event }) => {
|
||||||
if (event.type !== 'Set mouse state') return {}
|
if (event.type !== 'Set mouse state') return {}
|
||||||
@ -271,6 +270,7 @@ export const ModelingMachineProvider = ({
|
|||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_center_to_selection',
|
type: 'default_camera_center_to_selection',
|
||||||
|
camera_movement: 'vantage',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
|
@ -97,6 +97,7 @@ export const KclEditorPane = () => {
|
|||||||
if (!editorIsMounted || !lastSelectionEvent || !editorManager.editorView) {
|
if (!editorIsMounted || !lastSelectionEvent || !editorManager.editorView) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
editorManager.editorView.dispatch({
|
editorManager.editorView.dispatch({
|
||||||
selection: lastSelectionEvent.codeMirrorSelection,
|
selection: lastSelectionEvent.codeMirrorSelection,
|
||||||
annotations: [modelingMachineEvent, Transaction.addToHistory.of(false)],
|
annotations: [modelingMachineEvent, Transaction.addToHistory.of(false)],
|
||||||
|
@ -218,20 +218,6 @@ export const Stream = () => {
|
|||||||
}
|
}
|
||||||
}, [IDLE, streamState])
|
}, [IDLE, streamState])
|
||||||
|
|
||||||
/**
|
|
||||||
* Play the vid
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
if (!kclManager.isExecuting) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// execute in the next event loop
|
|
||||||
videoRef.current?.play().catch((e) => {
|
|
||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [kclManager.isExecuting])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
typeof window === 'undefined' ||
|
typeof window === 'undefined' ||
|
||||||
@ -243,9 +229,15 @@ export const Stream = () => {
|
|||||||
|
|
||||||
// The browser complains if we try to load a new stream without pausing first.
|
// The browser complains if we try to load a new stream without pausing first.
|
||||||
// Do not immediately play the stream!
|
// Do not immediately play the stream!
|
||||||
|
// we instead use a setTimeout to play the stream in the next event loop
|
||||||
try {
|
try {
|
||||||
videoRef.current.srcObject = mediaStream
|
videoRef.current.srcObject = mediaStream
|
||||||
videoRef.current.pause()
|
videoRef.current.pause()
|
||||||
|
setTimeout(() => {
|
||||||
|
videoRef.current?.play().catch((e) => {
|
||||||
|
console.warn('Video playing was prevented', e, videoRef.current)
|
||||||
|
})
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Attempted to pause stream while play was still loading', e)
|
console.warn('Attempted to pause stream while play was still loading', e)
|
||||||
}
|
}
|
||||||
|
@ -470,6 +470,17 @@ export function ToastPromptToEditCadSuccess({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
sendTelemetry(modelId, 'accepted', token).catch(reportRejection)
|
sendTelemetry(modelId, 'accepted', token).catch(reportRejection)
|
||||||
toast.dismiss(toastId)
|
toast.dismiss(toastId)
|
||||||
|
|
||||||
|
// Write new content to disk since they have accepted.
|
||||||
|
codeManager
|
||||||
|
.writeToFile()
|
||||||
|
.then(() => {
|
||||||
|
// no-op
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('Failed to save prompt-to-edit to disk')
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Accept
|
Accept
|
||||||
|
@ -150,4 +150,31 @@ describe('ToastUpdate tests', () => {
|
|||||||
expect(restartButton).toBeEnabled()
|
expect(restartButton).toBeEnabled()
|
||||||
expect(dismissButton).toBeEnabled()
|
expect(dismissButton).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Happy path: external links render correctly', () => {
|
||||||
|
const releaseNotesWithBreakingChanges = `
|
||||||
|
## Some markdown release notes
|
||||||
|
- [Zoo](https://zoo.dev/)
|
||||||
|
`
|
||||||
|
const onRestart = vi.fn()
|
||||||
|
const onDismiss = vi.fn()
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ToastUpdate
|
||||||
|
onRestart={onRestart}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
version={testData.version}
|
||||||
|
releaseNotes={releaseNotesWithBreakingChanges}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locators and other constants
|
||||||
|
const zooDev = screen.getByText('Zoo', {
|
||||||
|
selector: 'a',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(zooDev).toHaveAttribute('href', 'https://zoo.dev/')
|
||||||
|
expect(zooDev).toHaveAttribute('target', '_blank')
|
||||||
|
expect(zooDev).toHaveAttribute('onClick')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
import { Marked } from '@ts-stack/markdown'
|
import { escape, Marked, MarkedOptions, unescape } from '@ts-stack/markdown'
|
||||||
import { getReleaseUrl } from 'routes/Settings'
|
import { getReleaseUrl } from 'routes/Settings'
|
||||||
|
import { SafeRenderer } from 'lib/markdown'
|
||||||
|
|
||||||
export function ToastUpdate({
|
export function ToastUpdate({
|
||||||
version,
|
version,
|
||||||
@ -19,6 +20,14 @@ export function ToastUpdate({
|
|||||||
?.toLocaleLowerCase()
|
?.toLocaleLowerCase()
|
||||||
.includes('breaking')
|
.includes('breaking')
|
||||||
|
|
||||||
|
const markedOptions: MarkedOptions = {
|
||||||
|
gfm: true,
|
||||||
|
breaks: true,
|
||||||
|
sanitize: true,
|
||||||
|
unescape,
|
||||||
|
escape,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
||||||
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
@ -58,9 +67,8 @@ export function ToastUpdate({
|
|||||||
className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto"
|
className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: Marked.parse(releaseNotes, {
|
__html: Marked.parse(releaseNotes, {
|
||||||
gfm: true,
|
renderer: new SafeRenderer(markedOptions),
|
||||||
breaks: true,
|
...markedOptions,
|
||||||
sanitize: true,
|
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
|
||||||
import { vi } from 'vitest'
|
|
||||||
import { UpdaterModal } from './UpdaterModal'
|
|
||||||
|
|
||||||
describe('UpdaterModal tests', () => {
|
|
||||||
test('Renders the modal', () => {
|
|
||||||
const callback = vi.fn()
|
|
||||||
const data = {
|
|
||||||
version: '1.2.3',
|
|
||||||
date: '2021-22-23T21:22:23Z',
|
|
||||||
body: 'This is the body.',
|
|
||||||
}
|
|
||||||
|
|
||||||
render(
|
|
||||||
<UpdaterModal
|
|
||||||
isOpen={true}
|
|
||||||
onReject={() => {}}
|
|
||||||
onResolve={callback}
|
|
||||||
instanceId=""
|
|
||||||
open={false}
|
|
||||||
close={(res) => {}}
|
|
||||||
version={data.version}
|
|
||||||
date={data.date}
|
|
||||||
body={data.body}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(screen.getByTestId('update-version')).toHaveTextContent(data.version)
|
|
||||||
|
|
||||||
const updateButton = screen.getByTestId('update-button-update')
|
|
||||||
expect(updateButton).toBeEnabled()
|
|
||||||
fireEvent.click(updateButton)
|
|
||||||
expect(callback.mock.calls).toHaveLength(1)
|
|
||||||
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: true })
|
|
||||||
|
|
||||||
const cancelButton = screen.getByTestId('update-button-cancel')
|
|
||||||
expect(cancelButton).toBeEnabled()
|
|
||||||
fireEvent.click(cancelButton)
|
|
||||||
expect(callback.mock.calls).toHaveLength(2)
|
|
||||||
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: false })
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,87 +0,0 @@
|
|||||||
import { create, InstanceProps } from 'react-modal-promise'
|
|
||||||
import { ActionButton } from './ActionButton'
|
|
||||||
import { Logo } from './Logo'
|
|
||||||
import { Marked } from '@ts-stack/markdown'
|
|
||||||
|
|
||||||
type ModalResolve = {
|
|
||||||
wantUpdate: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModalReject = boolean
|
|
||||||
|
|
||||||
type UpdaterModalProps = InstanceProps<ModalResolve, ModalReject> & {
|
|
||||||
version: string
|
|
||||||
date?: string
|
|
||||||
body?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createUpdaterModal = create<
|
|
||||||
UpdaterModalProps,
|
|
||||||
ModalResolve,
|
|
||||||
ModalReject
|
|
||||||
>
|
|
||||||
|
|
||||||
export const UpdaterModal = ({
|
|
||||||
onResolve,
|
|
||||||
version,
|
|
||||||
date,
|
|
||||||
body,
|
|
||||||
}: UpdaterModalProps) => (
|
|
||||||
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
|
||||||
<div className="max-w-3xl min-w-[45rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<h1 className="flex-grow text-3xl font-bold">New version available!</h1>
|
|
||||||
<Logo className="h-9" />
|
|
||||||
</div>
|
|
||||||
<div className="my-4 flex items-baseline">
|
|
||||||
<span
|
|
||||||
className="px-3 py-1 text-xl rounded-full bg-energy-10 text-energy-80"
|
|
||||||
data-testid="update-version"
|
|
||||||
>
|
|
||||||
v{version}
|
|
||||||
</span>
|
|
||||||
<span className="ml-4 text-sm text-gray-400">Published on {date}</span>
|
|
||||||
</div>
|
|
||||||
{/* TODO: fix list bullets */}
|
|
||||||
{body && (
|
|
||||||
<div
|
|
||||||
className="my-4 max-h-60 overflow-y-auto"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: Marked.parse(body, {
|
|
||||||
gfm: true,
|
|
||||||
breaks: true,
|
|
||||||
sanitize: true,
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<ActionButton
|
|
||||||
Element="button"
|
|
||||||
onClick={() => onResolve({ wantUpdate: false })}
|
|
||||||
iconStart={{
|
|
||||||
icon: 'close',
|
|
||||||
bgClassName: 'bg-destroy-80',
|
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
|
||||||
}}
|
|
||||||
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
|
|
||||||
data-testid="update-button-cancel"
|
|
||||||
>
|
|
||||||
Not now
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
Element="button"
|
|
||||||
onClick={() => onResolve({ wantUpdate: true })}
|
|
||||||
iconStart={{
|
|
||||||
icon: 'arrowRight',
|
|
||||||
bgClassName: 'dark:bg-chalkboard-80',
|
|
||||||
}}
|
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
|
||||||
data-testid="update-button-update"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
@ -40,6 +40,7 @@ export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
|||||||
export default class EditorManager {
|
export default class EditorManager {
|
||||||
private _copilotEnabled: boolean = true
|
private _copilotEnabled: boolean = true
|
||||||
|
|
||||||
|
private _isAllTextSelected: boolean = false
|
||||||
private _isShiftDown: boolean = false
|
private _isShiftDown: boolean = false
|
||||||
private _selectionRanges: Selections = {
|
private _selectionRanges: Selections = {
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
@ -117,6 +118,10 @@ export default class EditorManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isAllTextSelected() {
|
||||||
|
return this._isAllTextSelected
|
||||||
|
}
|
||||||
|
|
||||||
get editorView(): EditorView | null {
|
get editorView(): EditorView | null {
|
||||||
return this._editorView
|
return this._editorView
|
||||||
}
|
}
|
||||||
@ -129,6 +134,21 @@ export default class EditorManager {
|
|||||||
this._isShiftDown = isShiftDown
|
this._isShiftDown = isShiftDown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private selectionsWithSafeEnds(
|
||||||
|
selection: Array<Selection['codeRef']['range']>
|
||||||
|
): Array<[number, number]> {
|
||||||
|
if (!this._editorView) {
|
||||||
|
return selection.map((s): [number, number] => {
|
||||||
|
return [s[0], s[1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection.map((s): [number, number] => {
|
||||||
|
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
||||||
|
return [s[0], safeEnd]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
set selectionRanges(selectionRanges: Selections) {
|
set selectionRanges(selectionRanges: Selections) {
|
||||||
this._selectionRanges = selectionRanges
|
this._selectionRanges = selectionRanges
|
||||||
}
|
}
|
||||||
@ -154,14 +174,9 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
||||||
this._highlightRange = range.map((s): [number, number] => {
|
const selectionsWithSafeEnds = this.selectionsWithSafeEnds(range)
|
||||||
return [s[0], s[1]]
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectionsWithSafeEnds = range.map((s): [number, number] => {
|
this._highlightRange = selectionsWithSafeEnds
|
||||||
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
|
||||||
return [s[0], safeEnd]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this._editorView) {
|
if (this._editorView) {
|
||||||
this._editorView.dispatch({
|
this._editorView.dispatch({
|
||||||
@ -302,20 +317,20 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
let codeBasedSelections = []
|
let codeBasedSelections = []
|
||||||
for (const selection of selections.graphSelections) {
|
for (const selection of selections.graphSelections) {
|
||||||
codeBasedSelections.push(
|
const safeEnd = Math.min(
|
||||||
EditorSelection.range(
|
selection.codeRef.range[1],
|
||||||
selection.codeRef.range[0],
|
this._editorView?.state.doc.length || selection.codeRef.range[1]
|
||||||
selection.codeRef.range[1]
|
|
||||||
)
|
)
|
||||||
|
codeBasedSelections.push(
|
||||||
|
EditorSelection.range(selection.codeRef.range[0], safeEnd)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
codeBasedSelections.push(
|
const end =
|
||||||
EditorSelection.cursor(
|
selections.graphSelections[selections.graphSelections.length - 1].codeRef
|
||||||
selections.graphSelections[selections.graphSelections.length - 1]
|
.range[1]
|
||||||
.codeRef.range[1]
|
const safeEnd = Math.min(end, this._editorView?.state.doc.length || end)
|
||||||
)
|
codeBasedSelections.push(EditorSelection.cursor(safeEnd))
|
||||||
)
|
|
||||||
|
|
||||||
if (!this._editorView) {
|
if (!this._editorView) {
|
||||||
return
|
return
|
||||||
@ -352,6 +367,16 @@ export default class EditorManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._isAllTextSelected = viewUpdate.state.selection.ranges.some(
|
||||||
|
(selection) => {
|
||||||
|
return (
|
||||||
|
// The user will need to select the empty new lines as well to be considered all of the text.
|
||||||
|
// CTRL+A is the best way to select all the text
|
||||||
|
selection.from === 0 && selection.to === viewUpdate.state.doc.length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const eventInfo = processCodeMirrorRanges({
|
const eventInfo = processCodeMirrorRanges({
|
||||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||||
selectionRanges: this._selectionRanges,
|
selectionRanges: this._selectionRanges,
|
||||||
|
@ -10,8 +10,11 @@ import { AppStreamProvider } from 'AppState'
|
|||||||
import { ToastUpdate } from 'components/ToastUpdate'
|
import { ToastUpdate } from 'components/ToastUpdate'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
|
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
|
||||||
|
import { initializeWindowExceptionHandler } from 'lib/exceptions'
|
||||||
|
|
||||||
markOnce('code/willAuth')
|
markOnce('code/willAuth')
|
||||||
|
initializeWindowExceptionHandler()
|
||||||
|
|
||||||
// uncomment for xstate inspector
|
// uncomment for xstate inspector
|
||||||
// import { DEV } from 'env'
|
// import { DEV } from 'env'
|
||||||
// import { inspect } from '@xstate/inspect'
|
// import { inspect } from '@xstate/inspect'
|
||||||
|
@ -376,7 +376,11 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
// updateArtifactGraph relies on updated executeState/programMemory
|
// updateArtifactGraph relies on updated executeState/programMemory
|
||||||
await this.engineCommandManager.updateArtifactGraph(this.ast)
|
await this.engineCommandManager.updateArtifactGraph(
|
||||||
|
this.ast,
|
||||||
|
execState.artifactCommands,
|
||||||
|
execState.artifacts
|
||||||
|
)
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
@ -390,6 +394,24 @@ export class KclManager {
|
|||||||
this._cancelTokens.delete(currentExecutionId)
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
markOnce('code/endExecuteAst')
|
markOnce('code/endExecuteAst')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This cleanup function is external and internal to the KclSingleton class.
|
||||||
|
* Since the WASM runtime can panic and the error cannot be caught in executeAst
|
||||||
|
* we need a global exception handler in exceptions.ts
|
||||||
|
* This file will interface with this cleanup as if it caught the original error
|
||||||
|
* to properly restore the TS application state.
|
||||||
|
*/
|
||||||
|
executeAstCleanUp() {
|
||||||
|
this.isExecuting = false
|
||||||
|
this.executeIsStale = null
|
||||||
|
this.engineCommandManager.addCommandLog({
|
||||||
|
type: 'execution-done',
|
||||||
|
data: null,
|
||||||
|
})
|
||||||
|
markOnce('code/endExecuteAst')
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: this always updates the code state and editor.
|
// NOTE: this always updates the code state and editor.
|
||||||
// DO NOT CALL THIS from codemirror ever.
|
// DO NOT CALL THIS from codemirror ever.
|
||||||
async executeAstMock(
|
async executeAstMock(
|
||||||
@ -464,13 +486,42 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
async executeCode(zoomToFit?: boolean): Promise<void> {
|
async executeCode(zoomToFit?: boolean): Promise<void> {
|
||||||
const ast = await this.safeParse(codeManager.code)
|
const ast = await this.safeParse(codeManager.code)
|
||||||
|
|
||||||
if (!ast) {
|
if (!ast) {
|
||||||
this.clearAst()
|
this.clearAst()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, zoomToFit)
|
||||||
|
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
return this.executeAst({ zoomToFit })
|
return this.executeAst({ zoomToFit })
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This will override the zoom to fit to zoom into the model if the previous AST was empty.
|
||||||
|
* Workflows this improves,
|
||||||
|
* When someone comments the entire file then uncomments the entire file it zooms to the model
|
||||||
|
* When someone CRTL+A and deletes the code then adds the code back it zooms to the model
|
||||||
|
* When someone CRTL+A and copies new code into the editor it zooms to the model
|
||||||
|
*/
|
||||||
|
tryToZoomToFitOnCodeUpdate(
|
||||||
|
ast: Node<Program>,
|
||||||
|
zoomToFit: boolean | undefined
|
||||||
|
) {
|
||||||
|
const isAstEmpty = this._isAstEmpty(this._ast)
|
||||||
|
const isRequestedAstEmpty = this._isAstEmpty(ast)
|
||||||
|
|
||||||
|
// If the AST went from empty to not empty or
|
||||||
|
// If the user has all of the content selected and they copy new code in
|
||||||
|
if (
|
||||||
|
(isAstEmpty && !isRequestedAstEmpty) ||
|
||||||
|
editorManager.isAllTextSelected
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return zoomToFit
|
||||||
|
}
|
||||||
async format() {
|
async format() {
|
||||||
const originalCode = codeManager.code
|
const originalCode = codeManager.code
|
||||||
const ast = await this.safeParse(originalCode)
|
const ast = await this.safeParse(originalCode)
|
||||||
|
@ -47,7 +47,7 @@ describe('parsing errors', () => {
|
|||||||
const result = parse(code)
|
const result = parse(code)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
const error = result.errors[0]
|
const error = result.errors[0]
|
||||||
expect(error.message).toBe('Unexpected token: (')
|
expect(error.message).toBe('Array is missing a closing bracket(`]`)')
|
||||||
expect(error.sourceRange).toEqual([27, 28, 0])
|
expect(error.sourceRange).toEqual([28, 29, 0])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|