Compare commits

...

230 Commits

Author SHA1 Message Date
a73e3ae1b7 Add ? token 2023-11-17 15:26:55 -06:00
fc38fff327 Macro for parsing KCL at Rust compile-time
This adds a new `parse_kcl!` macro which takes a string as input. It parses the string into KCL source code _at compile-time_. Invalid KCL will make your Rust project fail to compile.
2023-11-09 17:30:53 -06:00
1672c1fd1f Impl databake for all AST node types (#1047)
Databake doesn't have any derive for HashMap, so for NonCodeMeta I decided to skip serializing the non_code_nodes. This should be OK for now.

See <https://github.com/unicode-org/icu4x/issues/4266> for the Databake hashmap issue.
2023-11-09 16:39:12 -06:00
6ec5881985 top level expressions fix (#1046)
#1033 top level expressions fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-11-09 13:08:11 -08:00
7272cc9fbd Describe Rust version for devs (#1044)
Fixes #1042
2023-11-09 17:01:52 +00:00
b925ed9b65 KCL stdlib and circle function (#1029)
Allows stdlib functions to be written as KCL, not as Rust. 

Rust stdlib functions will hereafter be referred to as "core" not "std".

Right now the only stdlib function I implemented is a circle function (it's a wrapper around the core arc function which sets the arc's start/end to 0 and 360 respectively). I know I want to change this function as soon as KCL has enums, which is my next task. So, I don't want users to start using this right away. To that end, I've named this function "unstable_stdlib_circle" not "circle". Once the function is ready to be stabilized, I can rename it to just "circle".

Note that this PR modifies the existing "sketch and extrude a cylinder" KCL test so that instead of using a user-defined circle function, it uses the unstable_stdlib_circle function now. And the twenty-twenty tests pass, so we know my stdlib is working.

https://github.com/KittyCAD/modeling-app/issues/922
2023-11-09 09:58:20 -06:00
0db5db2181 Bump js-sys from 0.3.64 to 0.3.65 in /src/wasm-lib (#980)
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.64 to 0.3.65.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 23:41:53 -06:00
898e3db9d1 AST function nodes no longer have stdlib function members (#1031)
* AST function nodes no longer have stdlib function members

IMO the AST should not need an actual pointer to a stdlib function -- that is a completely separate concern from the AST.

Instead, the AST nodes can just store function names, and the executor will have a stdlib which it can look up those names in.

* Fix tests

* Update snapshot tests
2023-11-08 20:23:59 -06:00
d337ac2546 Test with a circle function (#1030) 2023-11-08 15:06:41 -06:00
371d8e08f7 Refactor the call_fn fn to be more readable (#1028) 2023-11-08 13:44:31 -06:00
338c43a29d Fix auto-version in nightly builds (#1026)
* WIP Fix auto-version in nightly builds
Fixes #1015

* WIP

* Revert "WIP"

This reverts commit 7838bf1298.

* Need to update src-tauri/tauri.release.conf.json after CI changes

* Fixes just to test

* Clean up after tests
2023-11-08 13:58:37 +00:00
52bb5a2657 Bump @sentry/react from 7.65.0 to 7.77.0 (#977)
Bumps [@sentry/react](https://github.com/getsentry/sentry-javascript) from 7.65.0 to 7.77.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.65.0...7.77.0)

---
updated-dependencies:
- dependency-name: "@sentry/react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 10:39:40 +00:00
1b6a06d266 Bump vscode-languageserver-protocol from 3.17.3 to 3.17.5 (#978)
Bumps [vscode-languageserver-protocol](https://github.com/Microsoft/vscode-languageserver-node/tree/HEAD/protocol) from 3.17.3 to 3.17.5.
- [Commits](https://github.com/Microsoft/vscode-languageserver-node/commits/release/types/3.17.5/protocol)

---
updated-dependencies:
- dependency-name: vscode-languageserver-protocol
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 05:28:24 -05:00
c68d4778a5 Bump @tauri-apps/api from 1.5.0 to 1.5.1 (#979)
Bumps [@tauri-apps/api](https://github.com/tauri-apps/tauri) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/api-v1.5...@tauri-apps/api-v1.5.1)

---
updated-dependencies:
- dependency-name: "@tauri-apps/api"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 05:28:01 -05:00
a8abea4fb5 Bump eslint from 8.46.0 to 8.53.0 (#1001)
Bumps [eslint](https://github.com/eslint/eslint) from 8.46.0 to 8.53.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.46.0...v8.53.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 05:27:13 -05:00
a0678d22a8 fix epsilon bug (#1025) 2023-11-08 09:27:53 +00:00
acbfae2e65 Bump wasm-bindgen from 0.2.87 to 0.2.88 in /src/wasm-lib (#981)
Bumps [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) from 0.2.87 to 0.2.88.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.87...0.2.88)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 23:56:23 -06:00
1e1bec6a8a Bump winnow (#1024) 2023-11-07 23:55:45 -06:00
06462b5a65 Bump syn from 2.0.38 to 2.0.39 in /src/wasm-lib (#1000)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.38 to 2.0.39.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.38...2.0.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 22:45:58 -06:00
2f292fb1be Bump tauri-plugin-fs-extra from 11048fd to 6865299 in /src-tauri (#1023)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `11048fd` to `6865299`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](11048fd997...6865299149)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 22:45:20 -06:00
8184e7b376 Bump serde from 1.0.190 to 1.0.192 in /src-tauri (#1009)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.190 to 1.0.192.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.190...v1.0.192)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 22:45:03 -06:00
b1084cbf80 Bump serde from 1.0.190 to 1.0.192 in /src/wasm-lib (#1012)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.190 to 1.0.192.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.190...v1.0.192)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 22:44:55 -06:00
548b45905e Cut release v0.11.3 (#1020)
cut release v0.11.3
2023-11-08 12:57:00 +11:00
141fd2f3f1 fix variables panel and others (#1021) 2023-11-08 01:27:43 +00:00
604d931962 selections fix follow up (#1019) 2023-11-07 23:10:30 +00:00
b1668410f8 Neaten up stdlib code (#1017) 2023-11-07 12:12:18 -06:00
13176cec38 Cut release v0.11.2 (#1005)
* Cut release v0.11.2

* Replace 'release' with '*' for artifact upload

* New logic to prevent top-level artifact.zip changes

* Fix upload

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2023-11-07 05:20:26 -05:00
3a59ae13b6 fix selections (#1013) 2023-11-07 07:21:32 +00:00
57c2481943 unused imports (#1011) 2023-11-07 03:29:50 +00:00
a1c555c51e clear errors on good parse (#1008) 2023-11-07 02:02:44 +00:00
4d520541be Build release & sign only on merge / release / release PR (#991)
* Add env variable for release || schedule || Cut out v PR

* Skip Windows, Apple, and Tauri Updater signing

* Add tauri args to bypass updater on debug

* Trying to address includeRelease and includeDebug issues

* WIP

* Clean up, fix bool eval

* -c to --config

* Remove src-tauri

* inline config

* Cleanup

* Remove concurrency block

* Test release

* Escape backslash

* Clean up

* Add back concurrency and BUILD_RELEASE eval

* Back to build:wasm (no speed impr noticed)

* Adam's suggestions

Co-authored-by: Adam Chalmers <adam.chalmers@kittycad.io>

* New logic to prevent top-level artifact.zip changes

---------

Co-authored-by: Adam Chalmers <adam.chalmers@kittycad.io>
2023-11-06 19:51:32 -05:00
82586f002b Test Jess's KCL error (#1004) 2023-11-06 14:01:51 -06:00
4bd08f7444 safe parse (#996)
* safe parse

* use safe parse on mouse up, stream
2023-11-06 06:47:45 +00:00
6b2603b1c4 don't overwrite current file on onboarding-replay (#1003)
don't over write file on onboarding
2023-11-06 04:03:21 +00:00
af49bebde3 compare formated asts before execute (#1002) 2023-11-06 03:43:01 +00:00
ca056996fd stop double execute on project open (#997) 2023-11-06 14:33:19 +11:00
34163da361 start of fixing changing files and cleaning up after execute (#897)
* start of fixing changing files and cleaning up after execute

* stop constraints from leaving artifacts

* don't write file on initial load
2023-11-06 11:49:13 +11:00
7c22bac638 Remove unwraps from binary expression algorithm (#992) 2023-11-03 13:30:19 -05:00
37a65b166b Fix silly mistakes in previous CI.yml PRs (#988)
* Fix silly mistakes in previous CI.yml PRs

This is what happens when I code before my morning matcha

Part of #987
2023-11-02 12:34:38 -05:00
1189f272ba Use debug builds for wasm-pack on PRs (#986) 2023-11-02 09:55:58 -05:00
ca5bc880dc Build tauri app in debug mode on PRs, release mode on releases (#985)
Build tauri app in debug mode on PRs, release mode on merges.

Release builds are very slow and should only be used for actual releases. Fast debug builds should be used for CI on PRs.
2023-11-02 09:55:36 -05:00
828daba304 Tweak Codespell CI (#975)
Ensure codespell CI job actually catches typos
2023-11-01 23:35:41 +00:00
0b9ba55bb4 Nicer error messages for unknown tokens (#974) 2023-11-01 23:08:22 +00:00
2d2a85ae7d remove view change from debug panel (#866)
remove view change from debug
2023-11-01 23:03:58 +00:00
cc57a302cc Update twenty-twenty (#973)
Due to engine PR #1566
2023-11-01 22:35:17 +00:00
fdbfd0c4b6 Fix typos (#972)
* Update twenty-twenty

Due to engine PR #1566

* Fix typos
2023-11-01 22:34:54 +00:00
2e419907e6 Tokenizing fallibility (#883)
Tokenization tracks invalid tokens and produces a nice error about them

---------

Co-authored-by: Adam Chalmers <adam.chalmers@kittycad.io>
2023-11-01 17:20:49 -05:00
3d0c5c10b0 KCL literals are typed, not JSON values (#971)
We now control the KCL type system instead of reusing JSON's type system.
2023-11-01 13:52:50 -05:00
4d47c067b7 Snapshot testing for parser (#969)
See https://docs.rs/insta for more.
2023-11-01 12:40:40 -05:00
3b3b5371eb Bump wasm-streams from 0.3.0 to 0.4.0 in /src/wasm-lib (#961)
Bumps [wasm-streams](https://github.com/MattiasBuelens/wasm-streams) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/MattiasBuelens/wasm-streams/releases)
- [Changelog](https://github.com/MattiasBuelens/wasm-streams/blob/main/CHANGELOG.md)
- [Commits](https://github.com/MattiasBuelens/wasm-streams/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: wasm-streams
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 08:43:18 -05:00
3ea77f8e1e Bump zustand from 4.4.0 to 4.4.5 (#963)
Bumps [zustand](https://github.com/pmndrs/zustand) from 4.4.0 to 4.4.5.
- [Release notes](https://github.com/pmndrs/zustand/releases)
- [Commits](https://github.com/pmndrs/zustand/compare/v4.4.0...v4.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-11-01 09:14:57 -04:00
4fa7c07e54 Bump @uiw/react-codemirror from 4.21.13 to 4.21.20 (#967)
Bumps [@uiw/react-codemirror](https://github.com/uiwjs/react-codemirror) from 4.21.13 to 4.21.20.
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.21.13...v4.21.20)

---
updated-dependencies:
- dependency-name: "@uiw/react-codemirror"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-11-01 09:14:04 -04:00
c66a96a333 Bump @testing-library/react from 13.4.0 to 14.0.0 (#965)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 13.4.0 to 14.0.0.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v13.4.0...v14.0.0)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 07:42:24 -04:00
4196ff91ac Bump vite from 4.4.9 to 4.5.0 (#966)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.9 to 4.5.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 07:41:58 -04:00
cf66b93963 Fix most Github Actions 'annotations' in PRs (#417)
* Fix Github Actions 'annotations' in PRs
Fixes #383

* Fix the React Hooks complains, and _app for rust

* Revert "Fix the React Hooks complains, and _app for rust"

This reverts commit 4a2ff925e5.

* Add back prettier fix and rust _app

* Fmt

* More annotation fixes

* More non-hooks fixes

* Rollback with eslint rule

* Add APPLE_TEAM_ID secret

* Revert "Add APPLE_TEAM_ID secret"

This reverts commit 346aaff5f4.

* More fixes
2023-11-01 07:39:31 -04:00
0b0219b810 Add repository field to Cargo.toml (#960) 2023-10-31 18:24:19 -05:00
36c7fcf6d7 Add a basic tauri e2e test on Linux (#923)
* WIP e2e test

* Working test on Linux

* Clean up

* Add to CI

* Fix

* Install tauri-driver

* Migrate to ubuntu-latest

* Add button check and click

* Update name

* Separate job for e2e test

* Fix path

* Fix perms

* Fix perms

* Single build-test-apps job

* Lint wdio file
2023-10-31 18:30:24 -04:00
023c3cbb90 New math parser (#956)
* New math parser

* Remove old parser

* Comments

* Move tests into parser_impl, remove dead code

* Backport some math tests
2023-10-31 14:16:18 -05:00
387f7e0912 New benchmark for parsing binary expressions (#957) 2023-10-31 18:04:24 +00:00
9b55b1fd12 Bump serde_json from 1.0.107 to 1.0.108 in /src-tauri (#954)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.107 to 1.0.108.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.107...v1.0.108)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-10-31 09:58:27 -05:00
4b6662169c Bump tauri-plugin-fs-extra from 68d77f9 to 11048fd in /src-tauri (#955)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `68d77f9` to `11048fd`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](68d77f999c...11048fd997)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:50:50 -04:00
d36abfcb3d Bump serde_json from 1.0.107 to 1.0.108 in /src/wasm-lib (#953)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.107 to 1.0.108.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.107...v1.0.108)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:50:07 -04:00
9002ae9efb Bump winnow from 0.5.17 to 0.5.18 in /src/wasm-lib (#952)
Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.17 to 0.5.18.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.17...v0.5.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:49:51 -04:00
4deea25394 Bump @codemirror/autocomplete from 6.9.0 to 6.10.2 (#951)
Bumps [@codemirror/autocomplete](https://github.com/codemirror/autocomplete) from 6.9.0 to 6.10.2.
- [Changelog](https://github.com/codemirror/autocomplete/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/autocomplete/compare/6.9.0...6.10.2)

---
updated-dependencies:
- dependency-name: "@codemirror/autocomplete"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:49:15 -04:00
b5940d2cb7 Bump @tauri-apps/cli from 1.5.2 to 1.5.6 (#950)
Bumps [@tauri-apps/cli](https://github.com/tauri-apps/tauri) from 1.5.2 to 1.5.6.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/cli-v1.5.2...@tauri-apps/cli-v1.5.6)

---
updated-dependencies:
- dependency-name: "@tauri-apps/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:48:51 -04:00
932b467c1e Bump @testing-library/user-event from 13.5.0 to 14.5.1 (#949)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.5.0 to 14.5.1.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.5.0...v14.5.1)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:48:34 -04:00
7c7f5c81c4 Bump web-vitals from 2.1.4 to 3.5.0 (#948)
Bumps [web-vitals](https://github.com/GoogleChrome/web-vitals) from 2.1.4 to 3.5.0.
- [Changelog](https://github.com/GoogleChrome/web-vitals/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GoogleChrome/web-vitals/compare/v2.1.4...v3.5.0)

---
updated-dependencies:
- dependency-name: web-vitals
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:48:05 -04:00
066b4f3e06 Bump typescript from 4.9.5 to 5.2.2 (#778)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.5 to 5.2.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.5...v5.2.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 06:47:34 -04:00
c6067bfc7a Bump kittycad from 0.2.38 to 0.2.41 in /src-tauri (#935)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.38 to 0.2.41.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.38...v0.2.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:22:05 -05:00
2018f0d517 Bump actions/setup-node from 3 to 4 (#924)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:21:57 -05:00
74aae3d15f Bump openapitor from 57b4d8b to 1b2562f in /src/wasm-lib (#938)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `57b4d8b` to `1b2562f`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](57b4d8b168...1b2562f4b3)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:21:35 -05:00
812f419e75 Bump serde from 1.0.189 to 1.0.190 in /src/wasm-lib (#941)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.189 to 1.0.190.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.189...v1.0.190)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:21:23 -05:00
5ec8cc69db Bump tauri-plugin-fs-extra from 9b20f28 to 68d77f9 in /src-tauri (#942)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9b20f28` to `68d77f9`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9b20f28d74...68d77f999c)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 22:21:14 -05:00
a5302b6e0e Nitpick my Winnow code (#946) 2023-10-30 22:20:37 -05:00
2114cc0d94 Bump @babel/traverse from 7.22.8 to 7.23.2 (#879)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.8 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:45:36 -04:00
2471ce1aba Bump swr from 2.2.0 to 2.2.2 (#332)
Bumps [swr](https://github.com/vercel/swr) from 2.2.0 to 2.2.2.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.2.0...v2.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-10-30 20:45:18 -04:00
35772475b9 Bump uuid and @types/uuid (#617)
Bumps [uuid](https://github.com/uuidjs/uuid) and [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid). These dependencies needed to be updated together.

Updates `uuid` from 9.0.0 to 9.0.1
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

Updates `@types/uuid` from 9.0.2 to 9.0.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/uuid"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:44:51 -04:00
86c592c0f6 Bump vite-tsconfig-paths from 4.2.0 to 4.2.1 (#750)
Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.2.0...v4.2.1)

---
updated-dependencies:
- dependency-name: vite-tsconfig-paths
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:44:33 -04:00
0e98973cfa Bump @types/debounce-promise from 3.1.6 to 3.1.8 (#899)
Bumps [@types/debounce-promise](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/debounce-promise) from 3.1.6 to 3.1.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/debounce-promise)

---
updated-dependencies:
- dependency-name: "@types/debounce-promise"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:43:46 -04:00
7dd16fe6de Bump clap from 4.4.6 to 4.4.7 in /src/wasm-lib (#939)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.6 to 4.4.7.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.6...v4.4.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:42:58 -04:00
478b636049 Bump crypto-js from 4.1.1 to 4.2.0 (#940)
Bumps [crypto-js](https://github.com/brix/crypto-js) from 4.1.1 to 4.2.0.
- [Commits](https://github.com/brix/crypto-js/compare/4.1.1...4.2.0)

---
updated-dependencies:
- dependency-name: crypto-js
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:42:43 -04:00
c779311a56 Bump serde from 1.0.189 to 1.0.190 in /src-tauri (#943)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.189 to 1.0.190.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.189...v1.0.190)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:42:01 -04:00
ca02ec1151 Bump futures from 0.3.28 to 0.3.29 in /src/wasm-lib (#944)
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.28 to 0.3.29.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.28...0.3.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 20:41:37 -04:00
b271d5060e Fix typos and unnecessary import paths (#945) 2023-10-27 18:42:35 +00:00
19f11fe55a Cut release v0.11.1 (#936) 2023-10-26 07:27:29 -04:00
f6f1574982 Use kittycad::Angle instead of our own (#934) 2023-10-24 16:32:41 -07:00
6dc4fbc808 Use KittyCAD rust library from workspace (#932)
* Use KittyCAD rust library from workspace

* Clippy
2023-10-24 20:49:06 +00:00
8843d02380 Cargo update (#929) 2023-10-24 11:24:59 -07:00
3578ec07e6 Bump uuid from 1.4.1 to 1.5.0 in /src/wasm-lib (#926)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.4.1...1.5.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 10:42:07 -07:00
db35f73e41 Bump thiserror from 1.0.49 to 1.0.50 in /src/wasm-lib (#927)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.49 to 1.0.50.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.49...1.0.50)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 10:41:47 -07:00
5cfc2b7941 DRY the code (#921) 2023-10-24 17:30:14 +00:00
318e4a0cc7 Update kittycad.rs (#919) 2023-10-23 14:03:38 -07:00
1e23be8f08 Update rustix (#920) 2023-10-23 14:03:28 -07:00
ef547e7db8 bump kc lib 45 (#913) 2023-10-20 05:05:42 +00:00
71b48bbd89 Fix nightly last_update link (#901) 2023-10-18 21:01:44 -04:00
c825eac27e Cut release v0.11.0 (#826)
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-17 18:15:46 -04:00
82e8a491c4 honour sketch mode after execute (#885)
* enable stay in sketch mode after execute

* clean up
2023-10-17 17:03:02 -04:00
93e806fc99 don't query program memory for add sketch line (#895)
* don't query program memory for add sketch line

* add issue TODO

* fmt

* fix test tsc
2023-10-18 07:30:03 +11:00
f1a14f1e3d Guard all the vaules in the Metrics for undefined. (#891)
We've seen a few cases where the WebRTC metrics report contains
undefined values when the stream hasn't started yet. JavaScript, when we
send to the backend, drops `undefined` members of the object, for
example:

```
> JSON.stringify({rtc_frames_dropped: undefined})
< '{}'
```

This will fail the backend validation logic and cause an error to get
emitted to the front-end reporting the missing key. I don't think this
does anything to the session, but it's an error we can avoid by guarding
more of the statistics with a || 0.

Some of the values had this already, this just adds a few more.

Signed-off-by: Paul R. Tagliamonte <paul@kittycad.io>
2023-10-17 16:55:45 +00:00
57c01ec3a2 Use platform-agnostic file separators (#890)
* Use platform-agnostic path separators

* Fix file settings by fixing absolute file path

* Fix missing home link in AppHeader

* Found so many more instances of raw "/" characters

* Tiny Settings style fix

* Clean up onboarding behavior for XState and multi-file
2023-10-17 12:31:14 -04:00
ce951d7c12 Add https://tauri.localhost for Windows (#886)
* Add https://tauri.localhost for Windows
Fixes #878

* Comments and info logs
2023-10-17 09:37:16 -04:00
0aa2a6cee7 more clean up (#889) 2023-10-17 09:08:17 +00:00
ba8f5d9785 unused vars (#887) 2023-10-17 05:34:35 +00:00
50a133b2fa add wasm error banner (#882)
add wasm error banner
2023-10-17 01:07:48 +00:00
3b15bc12f7 Franknoirot/multi file (#844)
* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Add icons

* Add Tooltip component

* Rough-in of sidebar styling and add initial File Tree

* Polish basic styling

* Show nested files and directories

* Add tests

* use camelCase for entrypointMetadata

* Add ability to switch files via links

* Revert "Improve Prop Typings for Modals. Remove instances of `any`. (… (#813)

Revert "Improve Prop Typings for Modals. Remove instances of `any`. (#792)"

This reverts commit 629f326f4c.

* ffmpeg instructions (#814)

* Formatting

* Remove folder names from display in app header

* Highlight current file, open folders it's within

* Navigate on double click, delete on Cmd + Esc
+ highlight focused folders

* Migrate to an XState machine, add create new file

* Add ability to create folders (with naive names)
+ remove command bar stuff for now

* Use route loader data to instantiate the kcl code

* Clean up some unused things

* Add ability to rename files

* Add ability to rename folders

* Add keyboard shortcuts for creating files/folders

* Tooltip style tweaks

* Polish + re-execute when switching files with a connection

* Reset code before navigating via file tree

* Don't invoke `readProject` if you're in a browser

* Show files and folders for projects on home page

* Don't highlight folders further down the file tree

* @jgomez720 and @jessfraz feedback:
+ indentation markers
+ proper file icon
+ bump down font size
+ touch up colors

* Tune down spacing, allow scroll overflow

* Fix formatting

* Update src/lib/tauriFS.ts

* Add a confirmation dialog when deleting

Signed-off-by: Frank Noirot <frank@kittycad.io>

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2023-10-16 13:28:41 -04:00
8eedee328b Add nightly releases (#736)
* WIP nightly release
Will fix #708

* semver compatible date

* shell: bash

* Update

* WIP

* WIP

* Revert and move to artifact for data sharing

* Update date

* Clean up

* Testing publish-apps-nightly

* Add config override

* Missing checkout

* Remove v

* Unified publish-apps-release step

* Add comment and commit sha

* Remove the remove-if-safe guards

* Final touches, cron to 4am UTC

* Add checks back
2023-10-16 11:49:23 -04:00
49b321feb5 Tauri Upgrade 1.5.0 (#823)
* Tauri Upgrade 1.5.0

* Add APPLE_TEAM_ID secret
2023-10-16 11:04:06 -04:00
35b5ad7d9b refactor selections (#876)
* migrate selection types

* extract selection event into selections.ts

* move code-mirror selection functions into selections.ts

* move more selection logit out of code mirror and engine connection

* add selection functions pure

* tidy up naming

* write a novel about how selections work

* final comments
2023-10-16 10:20:05 +00:00
8fad9ef3c2 fix vertex selection (#869) 2023-10-16 15:29:02 +11:00
b257b202c3 Add modal typing back in, and clean up old constraints code (#865)
* Revert "Revert "Improve Prop Typings for Modals. Remove instances of `any`. (… (#813)"

This reverts commit 9822576077.

* tsc

* refactor all buttons

* add parallel constraint

* typegen?

* add constraint removal constraint

* add perpendicular distance constraint

* state diagram layout

* fmt

* improve modal typing for setAngleLength
2023-10-15 21:54:38 +00:00
c6af62797d holes (#848)
* start of holes

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

* update docs

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

* updates

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

* updates

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

* updates;

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

* close it

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

* Fix holes in jess's branch (#857)

tweak

* holes

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

* bump version

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

* fix image

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: mlfarrell <michael@kittycad.io>
2023-10-13 12:02:46 -07:00
16a9acad56 Bump kittycad from 0.2.32 to 0.2.33 in /src-tauri (#851)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.32 to 0.2.33.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.32...v0.2.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 10:26:36 -07:00
8a80a88ad3 update images (#856)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-13 10:26:22 -07:00
71d1bb70ef Fix move only working first time (#850)
* nuke fixIdMappings

* update readme

* remove sourceRangeMap as it was redundant

repeated state already covered by the artifactMap

* bug fix, name conflict

* bug fix

* update artifact map when sketch is first created

* update artifact map for line generation too

* fmt

* update move state to allow selections

* allow for selection of vertices

some what hacky, but better than nothing

* unnecessary react hook dependency

* generic react linting

* move working for non-execute case

* block partial contrained things too for now
2023-10-13 09:47:46 -07:00
4853872614 Bump serde from 1.0.188 to 1.0.189 in /src-tauri (#852)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.188 to 1.0.189.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.188...v1.0.189)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 09:47:29 -07:00
1ca5204a1a Bump kittycad from 0.2.32 to 0.2.33 in /src/wasm-lib (#853)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.32 to 0.2.33.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.32...v0.2.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 09:46:43 -07:00
7baed0b5bd Bump serde from 1.0.188 to 1.0.189 in /src/wasm-lib (#854)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.188 to 1.0.189.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.188...v1.0.189)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 09:46:23 -07:00
e4969857bd Bump openapitor from c122a9b to 7e087ec in /src/wasm-lib (#855)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `c122a9b` to `7e087ec`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](c122a9b1d6...7e087ecaee)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 09:46:10 -07:00
9b7cc7afa4 save path id on path create (#849) 2023-10-13 10:27:25 +11:00
714917429e Release kcl-lib 0.1.34 (#843) 2023-10-12 12:27:01 -05:00
5af9c6b22d Typo: tangental should be tangential (#842)
* Typo: tangental should be tangential

* Run executor tests in serial

* Fix typo in image file names
2023-10-12 11:50:54 -05:00
396a994fe6 Add unit test (#841) 2023-10-12 10:56:20 -05:00
872da51da5 Bump tauri from 1.5.1 to 1.5.2 in /src-tauri (#836)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.5.1...tauri-v1.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 08:50:44 -07:00
05cd8cfec9 Bump kittycad from 0.2.31 to 0.2.32 in /src-tauri (#837)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.31 to 0.2.32.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.31...v0.2.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 08:50:35 -07:00
2a02f6e039 New parser built in Winnow (#731)
* New parser built with Winnow

This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.

## Differences

I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.

It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace. 

Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:

- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)

so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.

If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.

## Performance

| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes  | 922 ms  | 42 ms | 21x |
| Kitt SVG  | 148 ms  | 7 ms | 21x |

There's definitely still room to improve performance:

- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840

## Winnow

Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:

- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344

The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.

Closes #716 
Closes #815 
Closes #599
2023-10-12 09:42:37 -05:00
5b90686e5e fix for culling (#835)
* start of fix for culling;

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

* update lock

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

* updates

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-11 18:30:04 -07:00
298269d117 Bump tokio from 1.32.0 to 1.33.0 in /src-tauri (#822)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 17:47:35 -07:00
b379f6518f Add Language Data's Comment Tokens to Support Toggle Commenting Shortcuts in the Editor (#809)
Add Language Data's Comment Tokens to Support Toggle Comment Shortcuts

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-11 16:59:54 -07:00
6b22c8789d Bump tokio from 1.32.0 to 1.33.0 in /src/wasm-lib (#820)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 16:57:33 -07:00
cb4683e70b Bump proc-macro2 from 1.0.67 to 1.0.69 in /src/wasm-lib (#810)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.67 to 1.0.69.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.67...1.0.69)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-11 16:57:18 -07:00
0a020d9959 Bump tauri-plugin-fs-extra from 9f27e6e to 9b20f28 in /src-tauri (#828)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9f27e6e` to `9b20f28`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9f27e6e441...9b20f28d74)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 16:57:08 -07:00
7aae3dccdc Use route loader data to instantiate the kcl code (#832) 2023-10-11 16:29:22 -04:00
818bf96d0b update rendered images (#831)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-11 11:49:55 -07:00
03bc2eaf22 Docs: std.md now displays correct number of members of an array (#825)
* First draft up

* removed print statements

* running cargo fmt

---------

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-11 11:12:21 -07:00
8ad1476c13 wait to execute code until planes are ready (#830)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-11 13:40:54 -04:00
6c15a743a2 remove useAppMode hook file (#829) 2023-10-11 04:12:29 +00:00
d0930477ad xstate migration (#713)
* Add basic Popover functionality

* Fix up light mode of basic bar

* Add support for 2D and 3D mode styling

* Turn toolbar buttons back on

* Remove ActionButton until after tool logic refactor

* Add transitions

* Add initial modeling machine
This is not a full description of how the modelingMachine should work,
but begins to replicate all of the features of our useStore in XState
instead of zustand.

* Add fillet tool flow

* Refactor: break out engine manager setup into hook
Preparing for making a wrapper component around the App
that will manage the engine manager at the same level as
the modelingMachine.

* Create modeling provider, move engine management to it

* Refactor: move other engine-related useEffect into hook

* Add TS schema, selection actions to modelingMachine

* Add barebones modeling machine to app
Only implementing adding to code-based selections in the text editor so far

* Update moved useEffect hook after merge

* give myself reminder TODO

* fix engineCommandManager waitForReady Promise

* enable devtools

* make utility class for handling default planes

* progresson startNewSketch and EditSketch

* add provider to tests

* too large of a commit
put all of the lang state into another singleton, but did lots of work on xstate too

* fix edit sketch ast issue

* re-execute on sketch exit

* prettierignore xstate typegen file

* add move tool button back in

* handle mouse commands with xState states

* fix move

* remove old imports

* big useStore delete

* fix some destructuring bugs

* start of constraint actions

* add horizontal/vertical distance constraints

* fix more destructuring errors

* fix

* add angle constaints

* add align vertically/horizontally constraints

* add length and equal length constraints

* rename modal states to be more cmd bar friendly

* add doesPipeHave query

* add another query

* add extrude states

* state machine clean up

* xstate layout tweak

* make xstate types happy

* Revamp cursor logic and place curors after ast mod

* Xstate merge (#796)

* turning back on all planes (#720)

* updates

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

tests

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

fix more tests

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

fixes

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

fix stdlib

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

fix tests

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

fixes

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

updates

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

updates

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

compile

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

update sample code

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

re-enable the planes

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

updates

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

fix all tests

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

updates

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

boilerplate

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

Cut release v0.9.2 (#714)

rust make default planes

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

updates

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

use the planes from engine

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

fixups

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

updates

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

fixes

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

updates

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

fixes

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

fixes

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

negative args

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

diable camera

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

hide planes

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

updates

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

updates

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

updatress

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

fmt

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

Update src/hooks/useAppMode.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

Update src/hooks/useAppMode.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

cleanups

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

Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump tauri-plugin-fs-extra from `b04bde3` to `6c7a4c0` in /src-tauri (#725)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)

Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump openapitor from `61a1605` to `d3e98c4` in /src/wasm-lib (#723)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)

Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

---
updated-dependencies:
- dependency-name: expectorate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

refactor cleanup

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

updates

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

updates

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

type improvements

* use new sketchmode no camera

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

* js working better

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

* start of negative planes

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

* tests and neg

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

* updates

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

* images

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

* norma;s

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

* better initial load of planes

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

* ts

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

* updates

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

* fixes

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

* updates

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

* fixes

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

* fix tsc

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

* fix edit sketch

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

* add regression test for 2d solid issue

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

* updates

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

* show planes

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

* fix clippy

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

* fix tests

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

* canecel in progress

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

* fix ci as well

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

* updates

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

---------

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

* stopping point

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

* updates

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

* fixes

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

* refactor

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

* updates

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

* it works

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

* updates

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

* Hide planes (#797)

* hide planes in one go

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

* update hide;

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

* fixes

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

---------

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

* make tsc happy

* Make "Replay Onboarding" button available on home settings page (#804)

* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Use ONBOARDING_PROJECT_NAME in both places

* Fix formatting

* Cut release v0.10.0 (#803)

Co-authored-by: Frank Noirot <frank@kittycad.io>

* Bump kittycad from 0.2.28 to 0.2.31 in /src-tauri (#798)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.28 to 0.2.31.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.28...v0.2.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump openapitor from `fa0345c` to `c122a9b` in /src/wasm-lib (#800)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `fa0345c` to `c122a9b`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](fa0345c514...c122a9b1d6)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump syn from 2.0.37 to 2.0.38 in /src/wasm-lib (#801)

Bumps [syn](https://github.com/dtolnay/syn) from 2.0.37 to 2.0.38.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.37...2.0.38)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump winnow from 0.5.15 to 0.5.16 in /src/wasm-lib (#799)

Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.15 to 0.5.16.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.15...v0.5.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump tauri-plugin-fs-extra from `fa32d1a` to `9f27e6e` in /src-tauri (#802)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `fa32d1a` to `9f27e6e`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](fa32d1afa9...9f27e6e441)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* better plane selection

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

* use the sketch plane id

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

* add todo w bug

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

* Improve Prop Typings for Modals. Remove instances of `any`. (#792)

* Update typings for modals. Remove instances of `any`

* Fix generic type for creating modals

* cleanup other stuffs

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

* make plane id available when selecting default plane

* few clean up things

* change enter sketch action order to make sure plane id is available to 'enter edit mode'

* Revert "Improve Prop Typings for Modals. Remove instances of `any`. (… (#813)

Revert "Improve Prop Typings for Modals. Remove instances of `any`. (#792)"

This reverts commit 629f326f4c.

* ffmpeg instructions (#814)

* fix some tsc stuff

* small tweak

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Rametta <rametta@outlook.com>

* clean up

* fix test and tsc

* remove one more thing from useStore

* tweak state digrame layout

* fmt

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Frank Johnson <frankjohnson1993@gmail.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Rametta <rametta@outlook.com>
2023-10-11 13:36:54 +11:00
e5e30d231b ffmpeg instructions (#814) 2023-10-10 06:54:50 +11:00
9822576077 Revert "Improve Prop Typings for Modals. Remove instances of any. (… (#813)
Revert "Improve Prop Typings for Modals. Remove instances of `any`. (#792)"

This reverts commit 629f326f4c.
2023-10-10 06:43:25 +11:00
629f326f4c Improve Prop Typings for Modals. Remove instances of any. (#792)
* Update typings for modals. Remove instances of `any`

* Fix generic type for creating modals
2023-10-06 16:34:21 -04:00
89b880d9ae Bump tauri-plugin-fs-extra from fa32d1a to 9f27e6e in /src-tauri (#802)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `fa32d1a` to `9f27e6e`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](fa32d1afa9...9f27e6e441)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:48:54 -07:00
f6de0de1bf Bump winnow from 0.5.15 to 0.5.16 in /src/wasm-lib (#799)
Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.15 to 0.5.16.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.15...v0.5.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:48:42 -07:00
65ebb86b67 Bump syn from 2.0.37 to 2.0.38 in /src/wasm-lib (#801)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.37 to 2.0.38.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.37...2.0.38)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:48:27 -07:00
cce8274902 Bump openapitor from fa0345c to c122a9b in /src/wasm-lib (#800)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `fa0345c` to `c122a9b`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](fa0345c514...c122a9b1d6)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:48:15 -07:00
c515bef8e4 Bump kittycad from 0.2.28 to 0.2.31 in /src-tauri (#798)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.28 to 0.2.31.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.28...v0.2.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:47:58 -07:00
b17e61d963 Cut release v0.10.0 (#803)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-10-06 11:09:54 -04:00
d31d07d9c8 Make "Replay Onboarding" button available on home settings page (#804)
* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Use ONBOARDING_PROJECT_NAME in both places

* Fix formatting
2023-10-06 10:00:35 -04:00
7aa2d63c21 Hide planes (#797)
* hide planes in one go

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

* update hide;

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-05 19:54:31 -07:00
e1081b0ee6 turning back on all planes (#720)
* updates

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

tests

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

fix more tests

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

fixes

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

fix stdlib

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

fix tests

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

fixes

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

updates

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

updates

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

compile

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

update sample code

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

re-enable the planes

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

updates

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

fix all tests

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

updates

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

boilerplate

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

Cut release v0.9.2 (#714)

rust make default planes

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

updates

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

use the planes from engine

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

fixups

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

updates

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

fixes

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

updates

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

fixes

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

fixes

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

negative args

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

diable camera

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

hide planes

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

updates

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

updates

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

updatress

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

fmt

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

Update src/hooks/useAppMode.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

Update src/hooks/useAppMode.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

cleanups

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

Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump tauri-plugin-fs-extra from `b04bde3` to `6c7a4c0` in /src-tauri (#725)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)

Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump openapitor from `61a1605` to `d3e98c4` in /src/wasm-lib (#723)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)

Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

---
updated-dependencies:
- dependency-name: expectorate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

refactor cleanup

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

updates

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

updates

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

type improvements

* use new sketchmode no camera

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

* js working better

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

* start of negative planes

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

* tests and neg

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

* updates

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

* images

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

* norma;s

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

* better initial load of planes

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

* ts

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

* updates

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

* fixes

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

* updates

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

* fixes

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

* fix tsc

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

* fix edit sketch

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

* add regression test for 2d solid issue

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

* updates

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

* show planes

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

* fix clippy

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

* fix tests

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

* canecel in progress

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

* fix ci as well

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-05 14:27:48 -07:00
59223279b7 Type Error: Type error engineConnection.ts (#786)
* TypeError Fix

* removed not needed console log

* took into account adamchalmers feedback and fixed cl type error

* pretty up

* Irev-dev feedback fixes issue
2023-10-05 16:18:50 +11:00
8a4e717565 Use absolute URLs to settings to avoid relative URL edge cases (#781)
* Create useAbsoluteFilePath hook

* Fix "report bug" link on Error page

* Replace relative URL to settings with absolute URL

* Replace other absolute file URLs to use common hook

* Use named const for default browser file name

* Fix UI tests that now rely on useRouteLoaderData()

Signed-off-by: Frank Noirot <frank@kittycad.io>

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-10-04 18:00:55 -04:00
80b542ca18 Allow Vertical Scroll Wheel to affect Horizontal Scroll for Toolbar (#780) 2023-10-04 09:35:50 -07:00
e4bfc863ea Bump re-resizable from 6.9.9 to 6.9.11 (#777)
Bumps [re-resizable](https://github.com/bokuweb/react-resizable-box) from 6.9.9 to 6.9.11.
- [Release notes](https://github.com/bokuweb/react-resizable-box/releases)
- [Changelog](https://github.com/bokuweb/re-resizable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bokuweb/react-resizable-box/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 10:08:10 -05:00
77ef255de4 Bump tauri from 1.5.0 to 1.5.1 in /src-tauri (#761)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.5...tauri-v1.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 09:21:58 -05:00
64c3841079 Bump tauri-plugin-fs-extra from 9b96996 to fa32d1a in /src-tauri (#776)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9b96996` to `fa32d1a`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9b96996b5a...fa32d1afa9)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 23:23:43 -05:00
c7bb6bc845 Bump reqwest from 0.11.21 to 0.11.22 in /src/wasm-lib (#775)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.21 to 0.11.22.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.21...v0.11.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 23:23:18 -05:00
1af8a8c64f Cut release v0.9.5 (#772) 2023-10-03 17:35:16 -07:00
eb4776826b disable pip for firefox (#770)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:11:44 -07:00
f3dd0469d5 re-execute when we remount the app (#769)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:07:09 -07:00
deea74754d save actual file contents (#768)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:06:45 -07:00
3fd798c704 Bump @adobe/css-tools from 4.2.0 to 4.3.1 (#353)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.2.0 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-10-03 14:12:58 -05:00
cc9eaf2991 Bump get-func-name from 2.0.0 to 2.0.2 (#730)
Bumps [get-func-name](https://github.com/chaijs/get-func-name) from 2.0.0 to 2.0.2.
- [Release notes](https://github.com/chaijs/get-func-name/releases)
- [Commits](https://github.com/chaijs/get-func-name/commits/v2.0.2)

---
updated-dependencies:
- dependency-name: get-func-name
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 14:12:46 -05:00
6f24031220 add mit (#765)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 11:59:57 -07:00
672bcd297f Bump toml from 0.8.1 to 0.8.2 in /src-tauri (#760)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.1 to 0.8.2.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.1...toml-v0.8.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 07:20:21 -07:00
3bc182fe16 Bump reqwest from 0.11.20 to 0.11.21 in /src/wasm-lib (#762)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.20 to 0.11.21.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.20...v0.11.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 07:20:08 -07:00
589cd39eec Cut release v0.9.4 (#759)
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-02 20:09:53 -04:00
63feebef5c add report a bug button (#758)
* add report a bug button

* format
2023-10-02 16:33:33 -07:00
65037abd9a new sample code w tangental arc (#756)
* new sample code w tangental arc

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

* line 6

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

* make sure sample code works in test

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-02 16:14:09 -07:00
97bc339a62 Bump tauri-plugin-fs-extra from 9af4c37 to 9b96996 in /src-tauri (#751)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9af4c37` to `9b96996`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9af4c3727c...9b96996b5a)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 12:27:24 -07:00
4e9a6375a5 Bump tauri-build from 1.4.1 to 1.5.0 in /src-tauri (#752)
Bumps [tauri-build](https://github.com/tauri-apps/tauri) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-build-v1.4.1...tauri-build-v1.5)

---
updated-dependencies:
- dependency-name: tauri-build
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 12:27:16 -07:00
3d19dfb800 Cut release v0.9.3 (#753) 2023-10-02 14:14:00 -04:00
d2a7b84292 Request a feature should open externally (#737)
Fixes #659
2023-10-02 13:26:23 -04:00
9e02bab155 Explicitly close the Engine Connection when ICE fails (#732)
This will avoid letting the stack think its ready until it times out,
and will hopefully help with faster retry logic in the future

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
2023-10-02 09:50:49 -04:00
7352de5a70 Bump vitest from 0.34.1 to 0.34.6 (#747)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 0.34.1 to 0.34.6.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v0.34.6/packages/vitest)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-30 12:24:39 +10:00
9797d0cb81 Fix pipe expression start (#746) 2023-09-29 22:19:20 -04:00
83907fa9db adds tangentalArc and tangentalArcTo to the stdlib (#748)
* start of tangental arc

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

* updates

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

* update docs

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

* fixes

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-29 14:41:14 -07:00
a367be4e2b add gizmo (#738)
* add gizmo

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

* updates

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

* fixups

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

* fix force ast execution

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-29 12:41:58 -07:00
056fa00adc Bump tauri from 1.4.1 to 1.5.0 in /src-tauri (#742)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.4.1...tauri-v1.5.0)

---
updated-dependencies:
- dependency-name: tauri
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 12:03:35 -07:00
4759fb2e6f Bump postcss from 8.4.27 to 8.4.31 (#743)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 11:12:08 -07:00
45f497d9cd Bump tauri-build from 1.4.0 to 1.4.1 in /src-tauri (#741)
Bumps [tauri-build](https://github.com/tauri-apps/tauri) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-build-v1.4...tauri-build-v1.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 11:11:57 -07:00
dc61bdebdf Bump tauri-plugin-fs-extra from 3d27909 to 9af4c37 in /src-tauri (#740)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `3d27909` to `9af4c37`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](3d279094d4...9af4c3727c)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 11:11:42 -07:00
61943055e5 refactor a bit before more rust port (#739)
* partially there

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

* cleanup

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

* fixes

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

* fixups

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-29 11:11:01 -07:00
416fe0f644 Bump clap from 4.4.5 to 4.4.6 in /src/wasm-lib (#744)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.5 to 4.4.6.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.5...v4.4.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 12:41:22 -04:00
708465d818 Bump kittycad from 0.2.27 to 0.2.28 in /src-tauri (#734)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.27 to 0.2.28.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.27...v0.2.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 18:09:56 -07:00
e706fb02d6 Bump openapitor from d3e98c4 to fa0345c in /src/wasm-lib (#733)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `d3e98c4` to `fa0345c`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](d3e98c4ec0...fa0345c514)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 18:09:46 -07:00
1bf7daa474 Bump tauri-plugin-fs-extra from 6c7a4c0 to 3d27909 in /src-tauri (#735)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `6c7a4c0` to `3d27909`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](6c7a4c0984...3d279094d4)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 18:09:31 -07:00
ffc47f8f40 actually use the units the user sets (#727)
* actually use the units the user sets

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

* update kittycad lib

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

* next

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

* fix tests

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

* updates

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

* fix bug

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

* fix types

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-27 15:42:16 -07:00
768aaa84f6 Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 09:10:07 -07:00
f3a700eec8 Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)
Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

---
updated-dependencies:
- dependency-name: expectorate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:39:16 -07:00
c853637a9a Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:39:03 -07:00
9af30d9ef6 Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:38:49 -07:00
6164714a6b Bump openapitor from 61a1605 to d3e98c4 in /src/wasm-lib (#723)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:38:37 -07:00
64ceb98eba Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:38:25 -07:00
2cbf260900 Bump tauri-plugin-fs-extra from b04bde3 to 6c7a4c0 in /src-tauri (#725)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:38:14 -07:00
cfaaedf602 Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:37:54 -07:00
12b3717eb5 Cut release v0.9.2 (#714) 2023-09-26 20:39:05 -04:00
0bc685b0c4 Bump tungstenite from 0.20.0 to 0.20.1 in /src/wasm-lib/kcl/fuzz (#709)
Bumps [tungstenite](https://github.com/snapview/tungstenite-rs) from 0.20.0 to 0.20.1.
- [Changelog](https://github.com/snapview/tungstenite-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/snapview/tungstenite-rs/compare/v0.20.0...v0.20.1)

---
updated-dependencies:
- dependency-name: tungstenite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 20:49:16 -07:00
9ee032771a unused dep (#710) 2023-09-26 03:22:05 +00:00
c307ddd1b1 resize (#706)
* start of resize

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

* refactor

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

* check if 0

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

* updates

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

* updates

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

* will work w new lib

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

* new types

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

* handle resize effect

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2023-09-25 19:49:53 -07:00
a30818ff2b fixes negative args in function (#707)
* updates

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

* fixes

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

* updates

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

* fixes

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-25 15:25:58 -07:00
53e763d938 fix close arc (#704)
* fix close arc

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

* much bigger radius

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-25 12:14:41 -07:00
8f74cd1d0c Bump tauri-plugin-fs-extra from 0190f68 to b04bde3 in /src-tauri (#702)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `0190f68` to `b04bde3`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](0190f68f1d...b04bde3461)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 09:33:53 -07:00
c271942897 remove errors (#703) 2023-09-25 07:28:03 +00:00
a03d09b41d Restructure tokenizer module (#700)
* Remove duplicated tests

These tests already were copied to tokeniser2.rs, so removing them doesn't affect code coverage.

* Move tokeniser to its own module

Now there's a module for tokens, and the tokenizer/lexer implementation is private within the token module.
2023-09-24 20:01:17 -05:00
2971b7752b Bump rust websocket libraries (#701)
Changelog: https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md#0201
2023-09-24 21:34:31 +00:00
70e99eb00b Refactor is_code_token into a method (#699)
* Refactor is_code_token into a method

* Fix typos, use Parser as it was imported
2023-09-24 21:11:36 +00:00
5c66af59d2 New tokenizer based on winnow (#697)
* New tokenizer, using Winnow instead of regexes.

Between 1.3x and 4.4x speedup on lexer benchmarks :)

* Use dispatch instead of alt

Most of the time, if you know the first character of a token, you can narrow down its potential possible token types, instead of just trying each token type until one succeeds.

This further speeds up the lexer. Compared to main, this branch is now between 3x and 12x faster than main.
2023-09-22 21:57:39 -05:00
6dda6daeef Use separate benchmarks for lexing and parsing (#698) 2023-09-23 02:01:18 +00:00
b5387f1220 Cut release v0.9.1 (#693)
* bump to ️v0.9.1️

* update bump instructions

* readme update

* read me again

* change pr convention
2023-09-22 10:38:17 +10:00
fd5921b366 Convert the lexer to be iterative not recursive (#691)
This is often more memory-efficient (does not create a bunch of stack
frames)
2023-09-21 19:19:08 -05:00
716ad938fc stop gap for large files making editor slow (#690)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-21 16:13:22 -07:00
40136eb392 Bump kittycad from 0.2.25 to 0.2.26 in /src-tauri (#680)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.25 to 0.2.26.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.25...v0.2.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 14:05:55 -07:00
8d2b89fcd1 Bump openapitor from 0d121f6 to 61a1605 in /src/wasm-lib (#679)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `0d121f6` to `61a1605`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](0d121f6881...61a16059b3)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:22:04 -05:00
ad9fba3390 Bump tauri-plugin-fs-extra from 76832e6 to 0190f68 in /src-tauri (#681)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `76832e6` to `0190f68`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](76832e60bf...0190f68f1d)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:21:08 -05:00
911c43af50 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src-tauri (#687)
Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

---
updated-dependencies:
- dependency-name: phonenumber
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:37 -05:00
ab4e04f6c2 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src/wasm-lib (#685)
Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

---
updated-dependencies:
- dependency-name: phonenumber
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:23 -05:00
94aef05f74 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src/wasm-lib/kcl/fuzz (#686)
Bump phonenumber in /src/wasm-lib/kcl/fuzz

Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

---
updated-dependencies:
- dependency-name: phonenumber
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:10 -05:00
d820cf2446 Tokenizer is accidentally quadratic (#689)
* Add comments and rename a function

* Typo: paran -> paren

* Use bytes, not string, for the tokenizer

* Fix typo
2023-09-21 14:18:42 -05:00
0c724c4971 Start to restructure the Engine's connection to the backend (#674)
* Start to restructure the Engine's connectio to the backend

1) Add in a tearDown stub for when the Engine is torn down. This is now
   distinct from a 'close', which will not stop connect from trying
   again. Running tearDown will mark the connection to not be retried
   and close active connections.

2) Move the retry logic out of connect and into the constructor. It will
   attempt to reconnect at the same rate as we had previously.

3) The timeout will now only close the connection, not restart it.

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>

* Don't continue on dead conn & setTimeout on init only

* Clean up extra setTimeout

* Keep track of connection timeouts and clear on close

* Fix tsc by defining Timeout

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>

* appease the format gods

---------

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2023-09-21 12:07:47 -04:00
b54ac4a694 improve getNodePathFromSourceRange and therefore the ast explorer aswell (#683)
improve getNodePathFromSourceRange and therefore the ast explorer as well
2023-09-21 05:40:41 +00:00
27227092b1 app stuck on blur when engine errors (#682)
* temp fix for when engine returns error

* don't add extrued to show function
2023-09-21 04:32:47 +00:00
04e1b92a5b Add a benchmark for parsing pipes-on-pipes (#678) 2023-09-21 03:13:07 +00:00
0553cd4621 tests for big files (#675)
* shit;

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

* updates

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

* cleanup

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

* updates

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

* u[dates;

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

* updates

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

* still ignore the big one

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

* Add big kitt SVG to benchmarks

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Adam Chalmers <adam.s.chalmers@gmail.com>
2023-09-20 19:35:37 -07:00
61a0c88af4 Add IDE dirs to .gitignore (#676) 2023-09-21 02:03:09 +00:00
d5b0544437 Bump tauri-plugin-fs-extra from 5b814f5 to 76832e6 in /src-tauri (#657)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `5b814f5` to `76832e6`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](5b814f56e6...76832e60bf)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 18:47:38 -07:00
6cc8af5c23 make stdlib functions async (#672)
* wip

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

updates

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

updates

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

fixes

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

closer

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

fixes

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

* fixes

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

* closer

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

* closer

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

* compiles

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

* connection

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

* fixes

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

* fix wasm

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

* timeout

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

* remove the drop

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

* drop handle

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

* updates

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

* fixes

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

* fix

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-20 18:27:08 -07:00
888104080e bump v0.9.0 (#673) 2023-09-21 10:38:40 +10:00
b6769889e3 Handle relative paths at kcl level (#506)
* handle relative paths at kcl level

* fmt

* update kittycad

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

* updates

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

* bump

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

* fix tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-09-21 10:36:26 +10:00
a32258dac4 Engine manager can be cloned (#671) 2023-09-20 16:22:47 -07:00
18dbbad244 Use an actor to manage the Tokio engine connection (#669)
* Use an actor to manage the Tokio engine connection

This means EngineManager trait's methods take &self not &mut self, and the tokio implementation can be cloned.

* Clean up code
2023-09-20 16:59:03 -05:00
270 changed files with 30361 additions and 11892 deletions

3
.codespellrc Normal file
View File

@ -0,0 +1,3 @@
[codespell]
ignore-words-list: crate,everytime
skip: **/target,node_modules,build

View File

@ -15,6 +15,9 @@ on:
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo build
jobs:
cargobuild:

View File

@ -15,6 +15,9 @@ on:
- '**/rust-toolchain.toml'
- '**.rs'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo clippy
jobs:
cargoclippy:

View File

@ -17,6 +17,9 @@ on:
- .github/workflows/cargo-criterion.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo criterion
jobs:
cargocriterion:

View File

@ -18,6 +18,9 @@ on:
permissions:
packages: read
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo fmt
jobs:
cargofmt:

View File

@ -17,6 +17,9 @@ on:
- .github/workflows/cargo-test.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo test
jobs:
cargotest:
@ -58,4 +61,5 @@ jobs:
cargo nextest run --workspace --no-fail-fast -P ci
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

View File

@ -7,25 +7,37 @@ on:
- main
release:
types: [published]
schedule:
- cron: '0 4 * * *'
# Daily at 04:00 AM UTC
# Will checkout the last commit from the default branch (main as of 2023-10-04)
env:
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && contains(github.event.pull_request.title, 'Cut release v') }}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check-format:
runs-on: 'ubuntu-20.04'
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- run: yarn fmt-check
check-types:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
@ -37,14 +49,27 @@ jobs:
- run: yarn build:wasm
- run: yarn tsc
check-typos:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
- name: Install codespell
run: |
python -m pip install codespell
- name: Run codespell
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
build-test-web:
runs-on: ubuntu-20.04
outputs:
version: ${{ steps.export_version.outputs.version }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
@ -63,36 +88,81 @@ jobs:
- run: yarn test:cov
- id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-apps:
needs: [check-format, build-test-web, check-types]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-20.04, windows-latest]
prepare-json-files:
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs:
version: ${{ steps.export_version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: install ubuntu system dependencies
if: matrix.os == 'ubuntu-20.04'
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Set nightly version
if: github.event_name == 'schedule'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
echo "$(jq --arg url 'https://dl.kittycad.io/releases/modeling-app/nightly/last_update.json' \
'.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
- uses: actions/upload-artifact@v3
if: github.event_name == 'schedule'
with:
path: |
package.json
src-tauri/tauri.conf.json
src-tauri/tauri.release.conf.json
- id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-test-apps:
needs: [prepare-json-files]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
- name: Copy updated .json files
if: github.event_name == 'schedule'
run: |
ls -l artifact
cp artifact/package.json package.json
cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
- name: Install ubuntu system dependencies
if: matrix.os == 'ubuntu-latest'
run: >
sudo apt-get update &&
sudo apt-get install -y
libgtk-3-dev
libgtksourceview-3.0-dev
webkit2gtk-4.0
libappindicator3-dev
webkit2gtk-driver
xvfb
- name: Sync node version and setup cache
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn' # Set this to npm, yarn or pnpm.
- run: yarn install
- name: Rust setup
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
- name: Setup Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
@ -101,24 +171,27 @@ jobs:
with:
workspaces: './src/wasm-lib'
- name: wasm prep
- name: Run build:wasm manually
shell: bash
env:
MODE: ${{ env.BUILD_RELEASE == 'true' && '--release' || '--debug' }}
run: |
mkdir src/wasm-lib/pkg; cd src/wasm-lib
npx wasm-pack build --target web --out-dir pkg
echo "building with ${{ env.MODE }}"
npx wasm-pack build --target web --out-dir pkg ${{ env.MODE }}
cd ../../
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
- name: Fix format
run: yarn fmt
- name: install apple silicon target mac
- name: Install Universal target (MacOS only)
if: matrix.os == 'macos-latest'
run: |
rustup target add aarch64-apple-darwin
- name: Prepare Windows certificate and variables
if: matrix.os == 'windows-latest'
- name: Prepare certificate and variables (Windows only)
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12
@ -132,8 +205,8 @@ jobs:
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
shell: bash
- name: Setup Windows certicate with SSM KSP
if: matrix.os == 'windows-latest'
- name: Setup certicate with SSM KSP (Windows only)
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn
@ -143,8 +216,17 @@ jobs:
smksp_cert_sync.exe
shell: cmd
- name: Build and sign the app for the current platform
- name: Build the app (debug)
uses: tauri-apps/tauri-action@v0
if: ${{ env.BUILD_RELEASE == 'false' }}
with:
includeRelease: false
includeDebug: true
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
- name: Build the app (release) and sign
uses: tauri-apps/tauri-action@v0
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
@ -153,21 +235,42 @@ jobs:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
with:
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
args: "${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
- uses: actions/upload-artifact@v3
env:
PREFIX: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
with:
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
- name: Install tauri-driver for e2e tests (linux only)
if: matrix.os == 'ubuntu-latest'
uses: actions-rs/cargo@v1
with:
command: install
args: tauri-driver
- name: Run e2e tests (linux only)
if: matrix.os == 'ubuntu-latest'
run: xvfb-run yarn test:e2e
env:
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
publish-apps-release:
runs-on: ubuntu-20.04
if: github.event_name == 'release'
needs: [build-test-web, build-apps]
runs-on: ubuntu-latest
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
needs: [check-format, check-types, check-typos, build-test-web, prepare-json-files, build-test-apps]
env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
PUB_DATE: ${{ github.event.release.created_at }}
NOTES: ${{ github.event.release.body }}
VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }}
VERSION: ${{ github.event_name == 'release' && format('v{0}', needs.prepare-json-files.outputs.version) || needs.prepare-json-files.outputs.version }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
steps:
- uses: actions/download-artifact@v3
@ -177,9 +280,9 @@ jobs:
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
RELEASE_DIR=https://${BUCKET_DIR}/${VERSION}
jq --null-input \
--arg version "v${VERSION_NO_V}" \
--arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_sig "$DARWIN_SIG" \
@ -215,9 +318,9 @@ jobs:
- name: Generate the download static endpoint
run: |
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
RELEASE_DIR=https://${BUCKET_DIR}/${VERSION}
jq --null-input \
--arg version "v${VERSION_NO_V}" \
--arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
@ -257,21 +360,22 @@ jobs:
path: artifact
glob: '*/*itty*'
parent: false
destination: dl.kittycad.io/releases/modeling-app/v${{ env.VERSION_NO_V }}
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
- name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v1.0.3
with:
path: last_update.json
destination: dl.kittycad.io/releases/modeling-app
destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v1.0.3
with:
path: last_download.json
destination: dl.kittycad.io/releases/modeling-app
destination: ${{ env.BUCKET_DIR }}
- name: Upload release files to Github
if: ${{ github.event_name == 'release' }}
uses: softprops/action-gh-release@v1
with:
files: artifact/*/*itty*

3
.gitignore vendored
View File

@ -22,7 +22,10 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
.vscode
src/wasm-lib/.idea
src/wasm-lib/.vscode
# rust
src/wasm-lib/target

View File

@ -7,3 +7,6 @@ coverage
target
src/wasm-lib/pkg
src/wasm-lib/kcl/bindings
# XState generated files
src/machines/modelingMachine.typegen.ts

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 The KittyCAD Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -29,6 +29,7 @@ The 3D view in KittyCAD Modeling App is just a video stream from our hosted geom
- [React](https://react.dev/)
- [Headless UI](https://headlessui.com/)
- [TailwindCSS](https://tailwindcss.com/)
- [XState](https://xstate.js.org/)
- Networking
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
- Code Editor
@ -47,7 +48,7 @@ We recommend downloading the latest application binary from [our Releases page](
## Running a development build
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. Then, run:
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. We always use the latest stable version of Rust, so you may need to run `rustup update stable`. Then, run:
```
yarn install
@ -56,7 +57,7 @@ yarn install
followed by:
```
yarn build:wasm
yarn build:wasm-dev
```
That will build the WASM binary and put in the `public` dir (though gitignored)
@ -88,15 +89,22 @@ yarn test
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.
For running the rust (not tauri rust though) only, you can
```bash
cd src/wasm-lib
cargo test
```
but you will need to have install ffmpeg prior to.
## Tauri
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
To spin up up tauri dev, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then
```
yarn tauri dev
```
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writting they can conflict.
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writing they can conflict.
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
@ -123,13 +131,24 @@ Before you submit a contribution PR to this repo, please ensure that:
## Release a new version
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
```bash
VERSION=x.y.z yarn run bump-jsons
```
The PR may serve as a place to discuss the human-readable changelog and extra QA.
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
```typescript
console.log(
'- ' +
Array.from(
document.querySelectorAll('[data-hovercard-type="pull_request"]')
).map((a) => `[${a.innerText}](${a.href})`).join(`
- `)
)
```
grab the md list and delete any that are older than the last bump
2. Merge the PR

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
describe('Modeling App', () => {
it('open the sign in page', async () => {
const button = await $('#signin')
expect(button).toHaveText('Sign in')
// Workaround for .click(), see https://github.com/tauri-apps/tauri/issues/6541
await button.waitForClickable()
await browser.execute('arguments[0].click();', button)
// TODO: handle auth
})
})

View File

@ -1,37 +1,39 @@
{
"name": "untitled-app",
"version": "0.8.2",
"version": "0.11.3",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.9.0",
"@codemirror/autocomplete": "^6.10.2",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.13",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.37",
"@kittycad/lib": "^0.0.45",
"@lezer/javascript": "^1.4.7",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0",
"@sentry/react": "^7.65.0",
"@tauri-apps/api": "^1.3.0",
"@sentry/react": "^7.77.0",
"@tauri-apps/api": "^1.5.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@ts-stack/markdown": "^1.5.0",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@uiw/react-codemirror": "^4.21.13",
"@uiw/react-codemirror": "^4.21.20",
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2",
"crypto-js": "^4.1.1",
"crypto-js": "^4.2.0",
"debounce-promise": "^3.1.2",
"formik": "^2.4.3",
"fuse.js": "^6.6.2",
"http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0",
"re-resizable": "^6.9.9",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
@ -41,20 +43,20 @@
"react-modal-promise": "^1.0.2",
"react-router-dom": "^6.14.2",
"sketch-helpers": "^0.0.4",
"swr": "^2.0.4",
"swr": "^2.2.2",
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
"toml": "^3.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.4.2",
"uuid": "^9.0.0",
"vitest": "^0.34.1",
"typescript": "^5.2.2",
"uuid": "^9.0.1",
"vitest": "^0.34.6",
"vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.3",
"vscode-languageserver-protocol": "^3.17.5",
"wasm-pack": "^0.12.1",
"web-vitals": "^2.1.0",
"web-vitals": "^3.5.0",
"ws": "^8.13.0",
"xstate": "^4.38.2",
"zustand": "^4.1.4"
"zustand": "^4.4.5"
},
"scripts": {
"start": "vite",
@ -67,10 +69,12 @@
"test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"test:cov": "vitest run --coverage --mode development",
"test:e2e": "wdio run wdio.conf.js",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./src",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
"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\"",
@ -99,29 +103,33 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.22.9",
"@tauri-apps/cli": "^1.3.1",
"@tauri-apps/cli": "^1.5.6",
"@types/crypto-js": "^4.1.1",
"@types/debounce": "^1.2.1",
"@types/debounce-promise": "^3.1.8",
"@types/isomorphic-fetch": "^0.0.36",
"@types/react-modal": "^3.16.0",
"@types/uuid": "^9.0.1",
"@types/uuid": "^9.0.4",
"@types/wicg-file-system-access": "^2020.9.6",
"@types/ws": "^8.5.5",
"@vitejs/plugin-react": "^4.0.3",
"@vitest/coverage-istanbul": "^0.34.1",
"autoprefixer": "^10.4.13",
"eslint": "^8.44.0",
"eslint": "^8.53.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.11.0",
"happy-dom": "^10.8.0",
"husky": "^8.0.3",
"postcss": "^8.4.19",
"postcss": "^8.4.31",
"prettier": "^2.8.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.2.4",
"vite": "^4.4.3",
"vite": "^4.5.0",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.0",
"yarn": "^1.22.19"
"vite-tsconfig-paths": "^4.2.1",
"yarn": "^1.22.19",
"@wdio/cli": "^7.7.3",
"@wdio/local-runner": "^7.7.3",
"@wdio/mocha-framework": "^7.7.3",
"@wdio/spec-reporter": "^7.7.3"
}
}

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13 12V14H11V15H13V17H14V15H16V14H14V12H13Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 475 B

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 3.5H4H7H7.16667L7.3 3.6L9.16667 5H16H16.5V5.5V7.5V10.3773C16.1992 10.1362 15.8628 9.93763 15.5 9.79076V8H4.5V15.5H10.5351C10.7529 15.8764 11.0302 16.2141 11.3542 16.5H4H3.5V16V7.5V4V3.5ZM4.5 4.5V7H15.5V6H9H8.83333L8.7 5.9L6.83333 4.5H4.5ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 3.5H4.5V16.5H15.5V8.00001M11 3.5L15.5 8.00001M11 3.5V8.00001H15.5" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 200 B

3
public/kcl-icon.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H0V40H40V0ZM7.34715 27.2143V15.6577L2.976 15.987V36.7949H7.34715V32.0645L8.00582 31.5256C8.24533 31.326 8.47487 31.1264 8.69442 30.9268L12.1075 36.7949H17.0475C16.1893 35.3978 15.311 33.9906 14.4128 32.5735C13.5346 31.1563 12.6664 29.7392 11.8081 28.3221L15.8499 24.9389C15.4308 24.4399 15.0017 23.931 14.5625 23.412L13.3051 21.8552L7.34715 27.2143ZM22.2581 26.6754C22.8769 25.9169 23.6753 25.5377 24.6533 25.5377C25.272 25.5377 25.8309 25.6175 26.3299 25.7772C26.8289 25.9169 27.4177 26.1465 28.0963 26.4658L29.3238 23.3521C28.5853 22.7933 27.7371 22.4041 26.779 22.1845C25.8409 21.9649 25.0625 21.8552 24.4437 21.8552C22.0885 21.8552 20.2223 22.5537 18.845 23.9509C17.4878 25.3281 16.8092 27.1944 16.8092 29.5496C16.8092 31.9048 17.4878 33.7611 18.845 35.1183C20.2223 36.4756 22.0885 37.1542 24.4437 37.1542C25.0625 37.1542 25.8509 37.0444 26.8089 36.8249C27.767 36.6053 28.6053 36.2161 29.3238 35.6572L28.0963 32.5435C27.4177 32.8629 26.8289 33.0924 26.3299 33.2321C25.8309 33.3718 25.272 33.4417 24.6533 33.4417C23.6753 33.4417 22.8769 33.0924 22.2581 32.3938C21.6594 31.6753 21.36 30.7272 21.36 29.5496C21.36 28.372 21.6594 27.4139 22.2581 26.6754ZM36.2796 36.7949V15.6577L31.9085 15.987V36.7949H36.2796Z" fill="#D0FF00"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

261
src-tauri/Cargo.lock generated
View File

@ -84,7 +84,7 @@ dependencies = [
"tauri-build",
"tauri-plugin-fs-extra",
"tokio",
"toml 0.8.0",
"toml 0.8.2",
]
[[package]]
@ -122,6 +122,12 @@ dependencies = [
"system-deps 6.1.0",
]
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -155,6 +161,20 @@ version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "bigdecimal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "454bca3db10617b88b566f205ed190aedb0e0e6dd4cad61d3988a72e8c5594cb"
dependencies = [
"autocfg",
"libm",
"num-bigint",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "bincode"
version = "1.3.3"
@ -1295,7 +1315,21 @@ checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
dependencies = [
"log",
"mac",
"markup5ever",
"markup5ever 0.10.1",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "html5ever"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [
"log",
"mac",
"markup5ever 0.11.0",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -1539,7 +1573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi 0.3.1",
"rustix 0.38.13",
"rustix 0.38.21",
"windows-sys 0.48.0",
]
@ -1630,13 +1664,14 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.25"
version = "0.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
checksum = "874914cd40bfd43674406683bb3f0924d41780698a4ade96f2e180a73678bdd1"
dependencies = [
"anyhow",
"async-trait",
"base64 0.21.2",
"bigdecimal",
"bytes",
"chrono",
"data-encoding",
@ -1671,7 +1706,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
dependencies = [
"cssparser",
"html5ever",
"html5ever 0.25.2",
"matches",
"selectors",
]
[[package]]
name = "kuchikiki"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
dependencies = [
"cssparser",
"html5ever 0.26.0",
"indexmap 1.9.3",
"matches",
"selectors",
]
@ -1688,6 +1736,12 @@ version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "libm"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
[[package]]
name = "line-wrap"
version = "0.1.1"
@ -1711,9 +1765,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.7"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "lock_api"
@ -1727,9 +1781,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.18"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
dependencies = [
"serde",
]
@ -1781,7 +1835,21 @@ checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
"log",
"phf 0.8.0",
"phf_codegen",
"phf_codegen 0.8.0",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
dependencies = [
"log",
"phf 0.10.1",
"phf_codegen 0.10.0",
"string_cache",
"string_cache_codegen",
"tendril",
@ -1959,6 +2027,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -1982,9 +2061,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
@ -2342,6 +2421,16 @@ dependencies = [
"phf_shared 0.8.0",
]
[[package]]
name = "phf_codegen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
@ -2410,9 +2499,9 @@ dependencies = [
[[package]]
name = "phonenumber"
version = "0.3.2+8.13.9"
version = "0.3.3+8.13.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34749f64ea9d76f10cdc8a859588b57775f59177c7dd91f744d620bd62982d6f"
checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06"
dependencies = [
"bincode",
"either",
@ -2425,6 +2514,7 @@ dependencies = [
"regex-cache",
"serde",
"serde_derive",
"strum",
"thiserror",
]
@ -2749,9 +2839,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "reqwest"
version = "0.11.20"
version = "0.11.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
dependencies = [
"base64 0.21.2",
"bytes",
@ -2778,6 +2868,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-rustls",
@ -2927,9 +3018,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.19"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
@ -2941,14 +3032,14 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.13"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys 0.4.7",
"linux-raw-sys 0.4.10",
"windows-sys 0.48.0",
]
@ -3021,10 +3112,11 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.13"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
dependencies = [
"bigdecimal",
"bytes",
"chrono",
"dyn-clone",
@ -3037,9 +3129,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.13"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [
"proc-macro2",
"quote",
@ -3105,7 +3197,7 @@ dependencies = [
"log",
"matches",
"phf 0.8.0",
"phf_codegen",
"phf_codegen 0.8.0",
"precomputed-hash",
"servo_arc",
"smallvec",
@ -3123,9 +3215,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.188"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
dependencies = [
"serde_derive",
]
@ -3141,9 +3233,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.188"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [
"proc-macro2",
"quote",
@ -3163,9 +3255,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.107"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa 1.0.6",
"ryu",
@ -3458,6 +3550,28 @@ dependencies = [
"syn 2.0.33",
]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -3493,6 +3607,27 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "system-deps"
version = "5.0.0"
@ -3605,9 +3740,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "1.4.1"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e"
checksum = "9bfe673cf125ef364d6f56b15e8ce7537d9ca7e4dae1cf6fbbdeed2e024db3d9"
dependencies = [
"anyhow",
"base64 0.21.2",
@ -3661,12 +3796,13 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d2edd6a259b5591c8efdeb9d5702cb53515b82a6affebd55c7fd6d3a27b7d1b"
checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c"
dependencies = [
"anyhow",
"cargo_toml",
"dirs-next",
"heck 0.4.1",
"json-patch",
"semver",
@ -3674,13 +3810,14 @@ dependencies = [
"serde_json",
"tauri-utils",
"tauri-winres",
"walkdir",
]
[[package]]
name = "tauri-codegen"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a"
checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb"
dependencies = [
"base64 0.21.2",
"brotli",
@ -3704,9 +3841,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7"
checksum = "613740228de92d9196b795ac455091d3a5fbdac2654abb8bb07d010b62ab43af"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@ -3719,7 +3856,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#5b814f56e6368fdec46c4ddb04a07e0923ff995a"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6865299149ffd183365e3ff291acf3edac78ca61"
dependencies = [
"log",
"serde",
@ -3730,9 +3867,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769"
checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43"
dependencies = [
"gtk",
"http",
@ -3751,9 +3888,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b"
checksum = "8141d72b6b65f2008911e9ef5b98a68d1e3413b7a1464e8f85eb3673bb19a895"
dependencies = [
"cocoa",
"gtk",
@ -3771,19 +3908,20 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84"
checksum = "34d55e185904a84a419308d523c2c6891d5e2dbcee740c4997eb42e75a7b0f46"
dependencies = [
"brotli",
"ctor",
"dunce",
"glob",
"heck 0.4.1",
"html5ever",
"html5ever 0.26.0",
"infer",
"json-patch",
"kuchiki",
"kuchikiki",
"log",
"memchr",
"phf 0.10.1",
"proc-macro2",
@ -3817,7 +3955,7 @@ dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix 0.37.19",
"rustix 0.37.27",
"windows-sys 0.45.0",
]
@ -3897,9 +4035,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.32.0"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
dependencies = [
"backtrace",
"bytes",
@ -3968,14 +4106,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e"
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.0",
"toml_edit 0.20.2",
]
[[package]]
@ -4002,9 +4140,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.20.0"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
"indexmap 2.0.0",
"serde",
@ -4182,6 +4320,7 @@ version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [
"atomic",
"getrandom 0.2.9",
"serde",
]
@ -4799,9 +4938,9 @@ dependencies = [
[[package]]
name = "wry"
version = "0.24.3"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33748f35413c8a98d45f7a08832d848c0c5915501803d1faade5a4ebcd258cea"
checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e"
dependencies = [
"base64 0.13.1",
"block",
@ -4813,7 +4952,7 @@ dependencies = [
"gio",
"glib",
"gtk",
"html5ever",
"html5ever 0.25.2",
"http",
"kuchiki",
"libc",

View File

@ -4,7 +4,7 @@ version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
repository = "https://github.com/KittyCAD/modeling-app"
default-run = "app"
edition = "2021"
rust-version = "1.60"
@ -12,18 +12,18 @@ rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.4.0", features = [] }
tauri-build = { version = "1.5.0", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.25"
kittycad = "0.2.41"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.4.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tauri = { version = "1.5.2", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tokio = { version = "1.32.0", features = ["time"] }
toml = "0.8.0"
tokio = { version = "1.33.0", features = ["time"] }
toml = "0.8.2"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@ -68,7 +68,7 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
};
// Open the system browser with the auth_uri.
// We do this in the browser and not a seperate window because we want 1password and
// We do this in the browser and not a separate window because we want 1password and
// other crap to work well.
tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None)
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
@ -129,10 +129,10 @@ async fn get_user(
fn main() {
tauri::Builder::default()
.setup(|app| {
.setup(|_app| {
#[cfg(debug_assertions)] // only include this code on debug builds
{
let window = app.get_window("main").unwrap();
let window = _app.get_window("main").unwrap();
// comment out the below if you don't devtools to open everytime.
// it's useful because otherwise devtools shuts everytime rust code changes.
window.open_devtools();

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "kittycad-modeling",
"version": "0.8.2"
"version": "0.11.3"
},
"tauri": {
"allowlist": {
@ -72,23 +72,13 @@
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
"targets": "all"
},
"security": {
"csp": null
},
"updater": {
"active": true,
"endpoints": [
"https://dl.kittycad.io/releases/modeling-app/last_update.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
"active": false
},
"windows": [
{

View File

@ -1,7 +1,6 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": {
"productName": "KittyCAD Modeling"
}
}
}

View File

@ -0,0 +1,21 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"updater": {
"active": true,
"endpoints": [
"https://dl.kittycad.io/releases/modeling-app/last_update.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
},
"bundle": {
"identifier": "io.kittycad.modeling-app",
"windows": {
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
}
}
}

View File

@ -1,7 +1,6 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": {
"productName": "KittyCAD Modeling"
}
}
}

View File

@ -1,9 +1,16 @@
import { render, screen } from '@testing-library/react'
import { App } from './App'
import { describe, test, vi } from 'vitest'
import { BrowserRouter } from 'react-router-dom'
import {
Route,
RouterProvider,
createMemoryRouter,
createRoutesFromElements,
} from 'react-router-dom'
import { GlobalStateProvider } from './components/GlobalStateProvider'
import CommandBarProvider from 'components/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { BROWSER_FILE_NAME } from 'Router'
let listener: ((rect: any) => void) | undefined = undefined
;(global as any).ResizeObserver = class ResizeObserver {
@ -24,7 +31,7 @@ describe('App tests', () => {
>
return {
...actual,
useParams: () => ({ id: 'new' }),
useParams: () => ({ id: BROWSER_FILE_NAME }),
useLoaderData: () => ({ code: null }),
}
})
@ -41,12 +48,26 @@ describe('App tests', () => {
})
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
// We have to use a memory router in the testing environment,
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
const router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/file/:id"
element={
<CommandBarProvider>
<GlobalStateProvider>
<ModelingMachineProvider>{children}</ModelingMachineProvider>
</GlobalStateProvider>
</CommandBarProvider>
}
/>
),
{
initialEntries: ['/file/new'],
initialIndex: 0,
}
)
return <RouterProvider router={router} />
}

View File

@ -1,4 +1,4 @@
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
import { useCallback, MouseEventHandler } from 'react'
import { DebugPanel } from './components/DebugPanel'
import { v4 as uuidv4 } from 'uuid'
import { PaneType, useStore } from './useStore'
@ -19,7 +19,6 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { useHotkeys } from 'react-hotkeys-hook'
import { getNormalisedCoordinates } from './lib/utils'
import { isTauri } from './lib/isTauri'
import { useLoaderData } from 'react-router-dom'
import { IndexLoaderData } from './Router'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
@ -29,46 +28,32 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { engineCommandManager } from './lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
const { project, file } = useLoaderData() as IndexLoaderData
const streamRef = useRef<HTMLDivElement>(null)
useHotKeyListener()
const {
setCode,
engineCommandManager,
buttonDownInStream,
openPanes,
setOpenPanes,
didDragInStream,
streamDimensions,
guiMode,
setGuiMode,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
setCode: s.setCode,
engineCommandManager: s.engineCommandManager,
buttonDownInStream: s.buttonDownInStream,
openPanes: s.openPanes,
setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream,
streamDimensions: s.streamDimensions,
executeAst: s.executeAst,
}))
const {
auth: {
context: { token },
},
settings: {
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
},
} = useGlobalStateContext()
const { settings } = useGlobalStateContext()
const { showDebugPanel, onboardingStatus, cameraControls, theme } =
settings?.context || {}
const { state, send } = useModelingContext()
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
@ -85,50 +70,7 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => {
if (guiMode.mode === 'sketch') {
if (guiMode.sketchMode === 'selectFace') return
if (guiMode.sketchMode === 'sketchEdit') {
// TODO: share this with Toolbar's "Exit sketch" button
// exiting sketch should be done consistently across all exits
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
// this is necessary to get the UI back into a consistent
// state right now, hopefully won't need to rerender
// when exiting sketch mode in the future
executeAst()
} else {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
})
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: guiMode.rotation,
position: guiMode.position,
pathToNode: guiMode.pathToNode,
pathId: guiMode.pathId,
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
})
}
} else {
setGuiMode({ mode: 'default' })
}
})
useHotkeys('esc', () => send('Cancel'))
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus
@ -138,25 +80,10 @@ export function App() {
? 'opacity-40'
: ''
// Use file code loaded from disk
// on mount, and overwrite any locally-stored code
useEffect(() => {
if (isTauri() && loadedCode !== null) {
setCode(loadedCode)
}
return () => {
// Clear code on unmount if in desktop app
if (isTauri()) {
setCode('')
}
}
}, [loadedCode, setCode])
useSetupEngineManager(streamRef, token)
useEngineConnectionSubscriptions()
const debounceSocketSend = throttle<EngineCommand>((message) => {
engineCommandManager?.sendSceneCommand(message)
engineCommandManager.sendSceneCommand(message)
}, 16)
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
e.nativeEvent.preventDefault()
@ -170,10 +97,7 @@ export function App() {
const newCmdId = uuidv4()
if (buttonDownInStream === undefined) {
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
) {
if (state.matches('Sketch.Line Tool')) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
@ -193,7 +117,7 @@ export function App() {
})
}
} else {
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
if (state.matches('Sketch.Move Tool')) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
@ -216,7 +140,6 @@ export function App() {
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
interaction = 'zoom'
} else {
console.log('none')
return
}
@ -234,9 +157,8 @@ export function App() {
return (
<div
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
className="relative h-full flex flex-col"
onMouseMove={handleMouseMove}
ref={streamRef}
>
<AppHeader
className={
@ -244,7 +166,7 @@ export function App() {
paneOpacity +
(buttonDownInStream ? ' pointer-events-none' : '')
}
project={project}
project={{ project, file }}
enableMenu={true}
/>
<ModalContainer />
@ -304,7 +226,7 @@ export function App() {
<Stream className="absolute inset-0 z-0" />
{showDebugPanel && (
<DebugPanel
title="Debug"
title="Debug (AST Explorer)"
className={
'transition-opacity transition-duration-75 ' +
paneOpacity +

View File

@ -3,10 +3,8 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
// Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => {
const {
auth: { state },
} = useGlobalStateContext()
const isLoggingIn = state.matches('checkIfLoggedIn')
const { auth } = useGlobalStateContext()
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
return isLoggingIn ? (
<Loading>Loading KittyCAD Modeling App...</Loading>

View File

@ -31,6 +31,7 @@ import {
} from './lib/tauriFS'
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
import DownloadAppBanner from './components/DownloadAppBanner'
import { WasmErrBanner } from './components/WasmErrBanner'
import { GlobalStateProvider } from './components/GlobalStateProvider'
import {
SETTINGS_PERSIST_KEY,
@ -40,6 +41,10 @@ import { ContextFrom } from 'xstate'
import CommandBarProvider from 'components/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider, kclManager } from 'lang/KclSinglton'
import FileMachineProvider from 'components/FileMachineProvider'
import { sep } from '@tauri-apps/api/path'
if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({
@ -94,13 +99,16 @@ export const paths = {
) as typeof onboardingPaths,
}
export const BROWSER_FILE_NAME = 'new'
export type IndexLoaderData = {
code: string | null
project?: ProjectWithEntryPointMetadata
file?: FileEntry
}
export type ProjectWithEntryPointMetadata = FileEntry & {
entrypoint_metadata: Metadata
entrypointMetadata: Metadata
}
export type HomeLoaderData = {
projects: ProjectWithEntryPointMetadata[]
@ -129,15 +137,24 @@ const router = createBrowserRouter(
{
path: paths.INDEX,
loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
isTauri()
? redirect(paths.HOME)
: redirect(paths.FILE + '/' + BROWSER_FILE_NAME),
errorElement: <ErrorPage />,
},
{
path: paths.FILE + '/:id',
element: (
<Auth>
<Outlet />
<App />
<FileMachineProvider>
<KclContextProvider>
<ModelingMachineProvider>
<Outlet />
<App />
</ModelingMachineProvider>
<WasmErrBanner />
</KclContextProvider>
</FileMachineProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
),
@ -167,21 +184,42 @@ const router = createBrowserRouter(
)
}
if (params.id && params.id !== 'new') {
const defaultDir = persistedSettings.defaultDirectory || ''
if (params.id && params.id !== BROWSER_FILE_NAME) {
const decodedId = decodeURIComponent(params.id)
const projectAndFile = decodedId.replace(defaultDir + sep, '')
const firstSlashIndex = projectAndFile.indexOf(sep)
const projectName = projectAndFile.slice(0, firstSlashIndex)
const projectPath = defaultDir + sep + projectName
const currentFileName = projectAndFile.slice(firstSlashIndex + 1)
if (firstSlashIndex === -1 || !currentFileName)
return redirect(
`${paths.FILE}/${encodeURIComponent(
`${params.id}${sep}${PROJECT_ENTRYPOINT}`
)}`
)
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
const entrypoint_metadata = await metadata(
params.id + '/' + PROJECT_ENTRYPOINT
const code = await readTextFile(decodedId)
const entrypointMetadata = await metadata(
projectPath + sep + PROJECT_ENTRYPOINT
)
const children = await readDir(params.id)
const children = await readDir(projectPath, { recursive: true })
kclManager.setCodeAndExecute(code, false)
return {
code,
project: {
name: params.id.slice(params.id.lastIndexOf('/') + 1),
path: params.id,
name: projectName,
path: projectPath,
children,
entrypoint_metadata,
entrypointMetadata,
},
file: {
name: currentFileName,
path: params.id,
},
}
}
@ -212,7 +250,7 @@ const router = createBrowserRouter(
),
loader: async () => {
if (!isTauri()) {
return redirect(paths.FILE + '/new')
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
}
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<
@ -234,9 +272,9 @@ const router = createBrowserRouter(
isProjectDirectory
)
const projects = await Promise.all(
projectsNoMeta.map(async (p) => ({
entrypoint_metadata: await metadata(
p.path + '/' + PROJECT_ENTRYPOINT
projectsNoMeta.map(async (p: FileEntry) => ({
entrypointMetadata: await metadata(
p.path + sep + PROJECT_ENTRYPOINT
),
...p,
}))

View File

@ -1,23 +1,12 @@
import { useStore, toolTips, ToolTip } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert'
import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues'
import { EqualLength } from './components/Toolbar/EqualLength'
import { EqualAngle } from './components/Toolbar/EqualAngle'
import { Intersect } from './components/Toolbar/Intersect'
import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
import { SetAngleLength } from './components/Toolbar/setAngleLength'
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
import { Fragment, useEffect } from 'react'
import { Fragment, WheelEvent, useRef, useMemo } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode'
import { isCursorInSketchCommandRange } from 'lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { engineCommandManager } from './lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
export const sketchButtonClassnames = {
background:
@ -25,265 +14,152 @@ export const sketchButtonClassnames = {
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
}
const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
sketch_line: 'Line',
line: 'Line',
move: 'Move',
angledLine: 'Angled Line',
angledLineThatIntersects: 'Angled Line That Intersects',
angledLineOfXLength: 'Angled Line Of X Length',
angledLineOfYLength: 'Angled Line Of Y Length',
angledLineToX: 'Angled Line To X',
angledLineToY: 'Angled Line To Y',
lineTo: 'Line to Point',
xLine: 'Horizontal Line',
yLine: 'Vertical Line',
xLineTo: 'Horizontal Line to Point',
yLineTo: 'Vertical Line to Point',
}
export const Toolbar = () => {
const {
setGuiMode,
guiMode,
selectionRanges,
ast,
updateAst,
programMemory,
engineCommandManager,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
ast: s.ast,
updateAst: s.updateAst,
programMemory: s.programMemory,
engineCommandManager: s.engineCommandManager,
executeAst: s.executeAst,
}))
useAppMode()
const { state, send, context } = useModelingContext()
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
const pathId = useMemo(
() =>
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
context.selectionRanges
),
[engineCommandManager.artifactMap, context.selectionRanges]
)
useEffect(() => {
console.log('guiMode', guiMode)
}, [guiMode])
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current
if (!span) {
return
}
span.scrollLeft = span.scrollLeft += ev.deltaY
}
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
return (
<span className={styles.toolbarButtons + ' ' + className}>
{guiMode.mode === 'default' && (
<span
ref={toolbarButtonsRef}
onWheel={handleToolbarButtonsWheelEvent}
className={styles.toolbarButtons + ' ' + className}
>
{state.nextEvents.includes('Enter sketch') && (
<button
onClick={() => {
setGuiMode({
mode: 'sketch',
sketchMode: 'selectFace',
})
}}
onClick={() => send({ type: 'Enter sketch' })}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Start Sketch
</button>
)}
{guiMode.mode === 'canEditExtrude' && (
{state.nextEvents.includes('Enter sketch') && pathId && (
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst } = sketchOnExtrudedFace(
ast,
pathToNode,
programMemory
)
updateAst(modifiedAst, true)
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Sketch on Face
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<button
onClick={() => {
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
setGuiMode({
mode: 'sketch',
sketchMode: 'enterSketchEdit',
pathToNode: pathToNode,
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathId: guiMode.pathId,
})
}}
onClick={() => send({ type: 'Enter sketch' })}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Edit Sketch
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<>
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
pathToNode
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
pathToNode,
false
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude as new
</button>
</>
)}
{guiMode.mode === 'sketch' && (
<button
onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
executeAst()
}}
className="group"
>
<ActionIcon
icon="exit"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Exit sketch
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
<button onClick={() => send({ type: 'Cancel' })} className="group">
<ActionIcon icon="exit" className="!p-0.5" size="md" />
Exit Sketch
</button>
)}
{toolTips
.filter(
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
(sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
)
.map((sketchFnName) => {
if (
guiMode.mode !== 'sketch' ||
!('isTooltip' in guiMode || guiMode.sketchMode === 'sketchEdit')
{state.matches('Sketch') && !state.matches('idle') && (
<button
onClick={() =>
state.matches('Sketch.Line Tool')
? send('CancelSketch')
: send('Equip tool')
}
className={
'group ' +
(state.matches('Sketch.Line Tool')
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
<ActionIcon icon="line" className="!p-0.5" size="md" />
Line
</button>
)}
{state.matches('Sketch') && (
<button
onClick={() =>
state.matches('Sketch.Move Tool')
? send('CancelSketch')
: send('Equip move tool')
}
className={
'group ' +
(state.matches('Sketch.Move Tool')
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
<ActionIcon icon="move" className="!p-0.5" size="md" />
Move
</button>
)}
{state.matches('Sketch.SketchIdle') &&
state.nextEvents
.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
)
return null
return (
.map((eventName) => (
<button
key={sketchFnName}
onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool:
guiMode.sketchMode === sketchFnName
? 'select'
: (sketchFnName as any),
},
})
setGuiMode({
...guiMode,
...(guiMode.sketchMode === sketchFnName
? {
sketchMode: 'sketchEdit',
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
}
: {
sketchMode: sketchFnName,
waitingFirstClick: true,
isTooltip: true,
pathId: guiMode.pathId,
}),
})
}}
className={
'group ' +
(guiMode.sketchMode === sketchFnName
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
key={eventName}
onClick={() => send(eventName)}
className="group"
disabled={
!state.nextEvents
.filter((event) => state.can(event as any))
.includes(eventName)
}
title={eventName}
>
<ActionIcon
icon={sketchFnName.includes('line') ? 'line' : 'move'}
className="!p-0.5"
icon={'line'} // TODO
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{sketchFnLabels[sketchFnName]}
{eventName
.replace('Make segment ', '')
.replace('Constrain ', '')}
</button>
)
})}
<HorzVert horOrVert="horizontal" />
<HorzVert horOrVert="vertical" />
<EqualLength />
<EqualAngle />
<SetHorzVertDistance buttonType="alignEndsVertically" />
<SetHorzVertDistance buttonType="setHorzDistance" />
<SetAbsDistance buttonType="snapToYAxis" />
<SetAbsDistance buttonType="xAbs" />
<SetHorzVertDistance buttonType="alignEndsHorizontally" />
<SetAbsDistance buttonType="snapToXAxis" />
<SetHorzVertDistance buttonType="setVertDistance" />
<SetAbsDistance buttonType="yAbs" />
<SetAngleLength angleOrLength="setAngle" />
<SetAngleLength angleOrLength="setLength" />
<Intersect />
<RemoveConstrainingValues />
<SetAngleBetween />
))}
{state.matches('idle') && (
<button
onClick={() => send('extrude intent')}
disabled={!state.can('extrude intent')}
className="group"
title={
state.can('extrude intent')
? 'extrude'
: 'sketches need to be closed, or not already extruded'
}
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
)}
</span>
)
}
return (
<Popover className={styles.toolbarWrapper + ' ' + guiMode.mode}>
<Popover
className={
styles.toolbarWrapper + state.matches('Sketch') ? ' sketch' : ''
}
>
<div className={styles.toolbar}>
<span className={styles.toolbarCap + ' ' + styles.label}>
{guiMode.mode === 'sketch' ? '2D' : '3D'}
{state.matches('Sketch') ? '2D' : '3D'}
</span>
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<ToolbarButtons />
@ -319,7 +195,7 @@ export const Toolbar = () => {
<p
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
>
You're in {guiMode.mode === 'sketch' ? '2D' : '3D'}
You're in {state.matches('Sketch') ? '2D' : '3D'}
</p>
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
<FontAwesomeIcon icon={faX} className="w-4 h-4" />

View File

@ -23,10 +23,7 @@ type ActionButtonAsLink = BaseActionButtonProps &
}
type ActionButtonAsExternal = BaseActionButtonProps &
Omit<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
keyof BaseActionButtonProps
> & {
Omit<LinkProps, keyof BaseActionButtonProps> & {
Element: 'externalLink'
}
@ -69,12 +66,17 @@ export const ActionButton = (props: ActionButtonProps) => {
)
}
case 'externalLink': {
const { Element, icon, children, className, ...rest } = props
const { Element, to, icon, children, className, ...rest } = props
return (
<a className={classNames} {...rest}>
<Link
to={to || paths.INDEX}
className={classNames}
{...rest}
target="_blank"
>
{icon && <ActionIcon {...icon} />}
{children}
</a>
</Link>
)
}
default: {

View File

@ -1,6 +1,6 @@
import { Toolbar } from '../Toolbar'
import UserSidebarMenu from './UserSidebarMenu'
import { ProjectWithEntryPointMetadata } from '../Router'
import { IndexLoaderData } from '../Router'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import styles from './AppHeader.module.css'
@ -8,7 +8,7 @@ import { NetworkHealthIndicator } from './NetworkHealthIndicator'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
project?: ProjectWithEntryPointMetadata
project?: Omit<IndexLoaderData, 'code'>
className?: string
enableMenu?: boolean
}
@ -20,11 +20,8 @@ export const AppHeader = ({
className = '',
enableMenu = false,
}: AppHeaderProps) => {
const {
auth: {
context: { user },
},
} = useGlobalStateContext()
const { auth } = useGlobalStateContext()
const user = auth?.context?.user
return (
<header
@ -35,7 +32,11 @@ export const AppHeader = ({
className
}
>
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
<ProjectSidebarMenu
renderAsLink={!enableMenu}
project={project?.project}
file={project?.file}
/>
{/* Toolbar if the context deems it */}
{showToolbar && (
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
@ -44,7 +45,7 @@ export const AppHeader = ({
)}
{/* If there are children, show them, otherwise show User menu */}
{children || (
<div className="ml-auto flex items-center gap-1">
<div className="flex items-center gap-1 ml-auto">
<NetworkHealthIndicator />
<UserSidebarMenu user={user} />
</div>

View File

@ -1,18 +1,18 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager } from 'lang/KclSinglton'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore'
export function AstExplorer() {
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
ast: s.ast,
setHighlightRange: s.setHighlightRange,
selectionRanges: s.selectionRanges,
}))
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { context } = useModelingContext()
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections?.[0]?.range
// TODO maybe need to have callback to make sure it stays in sync
kclManager.ast,
context.selectionRanges.codeBasedSelections?.[0]?.range
)
const node = getNodeFromPath(ast, pathToNode).node
const node = getNodeFromPath(kclManager.ast, pathToNode).node
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
return (
@ -46,7 +46,11 @@ export function AstExplorer() {
}}
>
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
<DisplayObj
obj={kclManager.ast}
filterKeys={filterKeys}
node={node}
/>
</pre>
</div>
</div>
@ -84,10 +88,8 @@ function DisplayObj({
filterKeys: string[]
node: any
}) {
const { setHighlightRange, setCursor2 } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
setCursor2: s.setCursor2,
}))
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { send } = useModelingContext()
const ref = useRef<HTMLPreElement>(null)
const [hasCursor, setHasCursor] = useState(false)
const [isCollapsed, setIsCollapsed] = useState(false)
@ -118,7 +120,16 @@ function DisplayObj({
setHighlightRange([obj?.start || 0, obj.end])
}}
onClick={(e) => {
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
send({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
type: 'default',
range: [obj?.start || 0, obj.end || 0],
},
},
})
e.stopPropagation()
}}
>
@ -173,6 +184,7 @@ function DisplayObj({
</li>
)
}
return null
})}
</ul>
</span>

View File

@ -1,7 +1,5 @@
import { useEffect, useState, useRef } from 'react'
import { parser_wasm } from '../lang/abstractSyntaxTree'
import { BinaryPart, Value } from '../lang/abstractSyntaxTreeTypes'
import { executor } from '../lang/executor'
import { parse, BinaryPart, Value } from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -9,7 +7,10 @@ import {
findUniqueName,
} from '../lang/modifyAst'
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext'
import { executeAst } from 'useStore'
export const AvailableVars = ({
onVarClick,
@ -92,14 +93,9 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { ast, programMemory, selectionRange, engineCommandManager } = useStore(
(s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
engineCommandManager: s.engineCommandManager,
})
)
const { programMemory } = useKclContext()
const { context } = useModelingContext()
const selectionRange = context.selectionRanges.codeBasedSelections[0].range
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -119,9 +115,7 @@ export function useCalc({
inputRef.current &&
inputRef.current.setSelectionRange(0, String(value).length)
}, 100)
if (ast) {
setNewVariableName(findUniqueName(ast, valueName))
}
setNewVariableName(findUniqueName(kclManager.ast, valueName))
}, [])
useEffect(() => {
@ -134,21 +128,32 @@ export function useCalc({
}, [newVariableName])
useEffect(() => {
if (!ast || !programMemory || !selectionRange) return
const varInfo = findAllPreviousVariables(ast, programMemory, selectionRange)
if (!programMemory || !selectionRange) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
kclManager.programMemory,
selectionRange
)
setAvailableVarInfo(varInfo)
}, [ast, programMemory, selectionRange])
}, [kclManager.ast, kclManager.programMemory, selectionRange])
useEffect(() => {
if (!engineCommandManager) return
try {
const code = `const __result__ = ${value}\nshow(__result__)`
const ast = parser_wasm(code)
const code = `const __result__ = ${value}`
const ast = parse(code)
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
executor(ast, _programMem, engineCommandManager).then((programMemory) => {
executeAst({
ast,
engineCommandManager,
defaultPlanes: kclManager.defaultPlanes,
useFakeExecutor: true,
programMemoryOverride: JSON.parse(
JSON.stringify(kclManager.programMemory)
),
}).then(({ programMemory }) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
@ -165,7 +170,7 @@ export function useCalc({
setCalcResult('NAN')
setValueNode(null)
}
}, [value])
}, [value, availableVarInfo])
return {
valueNode,
@ -210,7 +215,10 @@ export const CreateNewVariable = ({
}) => {
return (
<>
<label htmlFor="create-new-variable" className="block mt-3 font-mono">
<label
htmlFor="create-new-variable"
className="block mt-3 font-mono text-gray-900"
>
Create new variable
</label>
<div className="mt-1 flex gap-2 items-center">
@ -221,6 +229,7 @@ export const CreateNewVariable = ({
onChange={(e) => {
setShouldCreateVariable(e.target.checked)
}}
className="bg-white text-gray-900"
/>
)}
<input

View File

@ -5,16 +5,13 @@ import {
faEllipsis,
} from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from './ActionIcon'
import { useStore } from 'useStore'
import styles from './CodeMenu.module.css'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { editorShortcutMeta } from './TextEditor'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { kclManager } from 'lang/KclSinglton'
export const CodeMenu = ({ children }: PropsWithChildren) => {
const { formatCode } = useStore((s) => ({
formatCode: s.formatCode,
}))
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
useConvertToVariable()
@ -41,7 +38,10 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
</Menu.Button>
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
<Menu.Item>
<button onClick={() => formatCode()} className={styles.button}>
<button
onClick={() => kclManager.format()}
className={styles.button}
>
<span>Format code</span>
<small>{editorShortcutMeta.formatCode.display}</small>
</button>

View File

@ -1,7 +1,10 @@
export type CustomIconName =
| 'createFile'
| 'createFolder'
| 'equal'
| 'exit'
| 'extrude'
| 'file'
| 'horizontal'
| 'line'
| 'move'
@ -16,6 +19,38 @@ export const CustomIcon = ({
name: CustomIconName
} & React.SVGProps<SVGSVGElement>) => {
switch (name) {
case 'createFile':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13 12V14H11V15H13V17H14V15H16V14H14V12H13Z"
fill="currentColor"
/>
</svg>
)
case 'createFolder':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.5 3.5H4H7H7.16667L7.3 3.6L9.16667 5H16H16.5V5.5V7.5V10.3773C16.1992 10.1362 15.8628 9.93763 15.5 9.79076V8H4.5V15.5H10.5351C10.7529 15.8764 11.0302 16.2141 11.3542 16.5H4H3.5V16V7.5V4V3.5ZM4.5 4.5V7H15.5V6H9H8.83333L8.7 5.9L6.83333 4.5H4.5ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z"
fill="currentColor"
/>
</svg>
)
case 'equal':
return (
<svg
@ -61,6 +96,20 @@ export const CustomIcon = ({
/>
</svg>
)
case 'file':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 3.5H4.5V16.5H15.5V8.00001M11 3.5L15.5 8.00001M11 3.5V8.00001H15.5"
stroke="currentColor"
/>
</svg>
)
case 'horizontal':
return (
<svg

View File

@ -1,32 +1,7 @@
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { v4 as uuidv4 } from 'uuid'
import { EngineCommand } from '../lang/std/engineConnection'
import { useState } from 'react'
import { ActionButton } from '../components/ActionButton'
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { isReducedMotion } from 'lang/util'
import { AstExplorer } from './AstExplorer'
type SketchModeCmd = Extract<
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
{ type: 'default_camera_enable_sketch_mode' }
>
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
const { engineCommandManager } = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
}))
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
type: 'default_camera_enable_sketch_mode',
origin: { x: 0, y: 0, z: 0 },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
distance_to_plane: 100,
ortho: true,
animated: !isReducedMotion(),
})
if (!sketchModeCmd) return null
return (
<CollapsiblePanel
{...props}
@ -37,68 +12,6 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
style={{ maxHeight: 'calc(100% - 3rem - 1.25rem - 1.25rem)' }}
>
<section className="p-4 flex flex-col gap-4">
<Xyz
onChange={setSketchModeCmd}
pointKey="origin"
data={sketchModeCmd}
/>
<Xyz
onChange={setSketchModeCmd}
pointKey="x_axis"
data={sketchModeCmd}
/>
<Xyz
onChange={setSketchModeCmd}
pointKey="y_axis"
data={sketchModeCmd}
/>
<div className="flex">
<div className="pr-4">distance_to_plane</div>
<input
className="w-16 dark:bg-chalkboard-90"
type="number"
value={sketchModeCmd.distance_to_plane}
onChange={({ target }) => {
setSketchModeCmd({
...sketchModeCmd,
distance_to_plane: Number(target.value),
})
}}
/>
<div className="pr-4">ortho</div>
<input
className="w-16"
type="checkbox"
checked={sketchModeCmd.ortho}
onChange={(a) => {
console.log(a, (a as any).checked)
setSketchModeCmd({
...sketchModeCmd,
ortho: a.target.checked,
})
}}
/>
</div>
<ActionButton
Element="button"
onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: sketchModeCmd,
cmd_id: uuidv4(),
})
}}
className="hover:border-succeed-50"
icon={{
icon: faCheck,
bgClassName:
'bg-succeed-80 group-hover:bg-succeed-70 hover:bg-succeed-70',
iconClassName:
'text-succeed-20 group-hover:text-succeed-10 hover:text-succeed-10',
}}
>
Send sketch mode command
</ActionButton>
<div style={{ height: '400px' }} className="overflow-y-auto">
<AstExplorer />
</div>
@ -106,41 +19,3 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
</CollapsiblePanel>
)
}
const Xyz = ({
pointKey,
data,
onChange,
}: {
pointKey: 'origin' | 'y_axis' | 'x_axis'
data: SketchModeCmd
onChange: (a: SketchModeCmd) => void
}) => {
if (!data) return null
return (
<div className="flex">
<div className="pr-4">{pointKey}</div>
{Object.entries(data[pointKey]).map(([axis, val]) => {
return (
<div key={axis} className="flex">
<div className="w-4">{axis}</div>
<input
className="w-16 dark:bg-chalkboard-90"
type="number"
value={val}
onChange={({ target }) => {
onChange({
...data,
[pointKey]: {
...data[pointKey],
[axis]: Number(target.value),
},
})
}}
/>
</div>
)
})}
</div>
)
}

View File

@ -47,11 +47,9 @@ export const ErrorPage = () => {
Clear storage
</ActionButton>
<ActionButton
Element="link"
Element="externalLink"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
to="https://github.com/KittyCAD/modeling-app/issues/new"
>
Report Bug
</ActionButton>

View File

@ -1,31 +1,40 @@
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from './ActionButton'
import Modal from 'react-modal'
import React from 'react'
import { useFormik } from 'formik'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type']
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
type StorageUnion = ExtractStorageTypes<OutputFormat>
interface ExportButtonProps extends React.PropsWithChildren {
className?: {
button?: string
// If we wanted more classname configuration of sub-elements,
// put them here
icon?: string
bg?: string
}
}
export const ExportButton = ({ children, className }: ExportButtonProps) => {
const { engineCommandManager } = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
}))
const [modalIsOpen, setIsOpen] = React.useState(false)
const {
settings: {
state: {
context: { baseUnit },
},
},
} = useGlobalStateContext()
const defaultType = 'gltf'
const [type, setType] = React.useState(defaultType)
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
const defaultStorage = 'embedded'
const [storage, setStorage] = React.useState<StorageUnion>(defaultStorage)
function openModal() {
setIsOpen(true)
@ -38,7 +47,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
// Default to gltf and embedded.
const initialValues: OutputFormat = {
type: defaultType,
storage: 'embedded',
storage: defaultStorage,
presentation: 'pretty',
}
const formik = useFormik({
@ -66,7 +75,18 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
},
}
}
engineCommandManager?.sendSceneCommand({
if (values.type === 'obj' || values.type === 'stl') {
values.units = baseUnit
}
if (
values.type === 'ply' ||
values.type === 'stl' ||
values.type === 'gltf'
) {
// Set the storage type.
values.storage = storage
}
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'export',
@ -75,6 +95,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
// in the scene to export. In that case, you'd pass the IDs thru here.
entity_ids: [],
format: values,
source_unit: baseUnit,
},
cmd_id: uuidv4(),
})
@ -88,7 +109,11 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
<ActionButton
onClick={openModal}
Element="button"
icon={{ icon: faFileExport }}
icon={{
icon: faFileExport,
iconClassName: className?.icon,
bgClassName: className?.bg,
}}
className={className?.button}
>
{children || 'Export'}
@ -109,7 +134,17 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
id="type"
name="type"
onChange={(e) => {
setType(e.target.value)
setType(e.target.value as OutputTypeKey)
if (e.target.value === 'gltf') {
// Set default to embedded.
setStorage('embedded')
} else if (e.target.value === 'ply') {
// Set default to ascii.
setStorage('ascii')
} else if (e.target.value === 'stl') {
// Set default to ascii.
setStorage('ascii')
}
formik.handleChange(e)
}}
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
@ -127,10 +162,10 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
<select
id="storage"
name="storage"
onChange={formik.handleChange}
value={
'storage' in formik.values ? formik.values.storage : ''
}
onChange={(e) => {
setStorage(e.target.value as StorageUnion)
formik.handleChange(e)
}}
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
>
{type === 'gltf' && (

View File

@ -0,0 +1,158 @@
import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { IndexLoaderData, paths } from '../Router'
import React, { createContext } from 'react'
import { toast } from 'react-hot-toast'
import {
AnyStateMachine,
ContextFrom,
EventFrom,
InterpreterFrom,
Prop,
StateFrom,
} from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { DEFAULT_FILE_NAME, fileMachine } from 'machines/fileMachine'
import {
createDir,
removeDir,
removeFile,
renameFile,
writeFile,
} from '@tauri-apps/api/fs'
import { FILE_EXT, readProject } from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri'
import { sep } from '@tauri-apps/api/path'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<InterpreterFrom<T>, 'send'>
}
export const FileContext = createContext(
{} as MachineContext<typeof fileMachine>
)
export const FileMachineProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const navigate = useNavigate()
const { setCommandBarOpen } = useCommandsContext()
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
const [state, send] = useMachine(fileMachine, {
context: {
project,
selectedDirectory: project,
},
actions: {
navigateToFile: (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine>
) => {
if (event.data && 'name' in event.data) {
setCommandBarOpen(false)
navigate(
`${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + sep + event.data.name
)}`
)
}
},
toastSuccess: (_, event) =>
event.data && toast.success((event.data || '') + ''),
toastError: (_, event) => toast.error((event.data || '') + ''),
},
services: {
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
const newFiles = isTauri()
? await readProject(context.project.path)
: []
return {
...context.project,
children: newFiles,
}
},
createFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Create file'>
) => {
let name = event.data.name.trim() || DEFAULT_FILE_NAME
if (event.data.makeDir) {
await createDir(context.selectedDirectory.path + sep + name)
} else {
await writeFile(
context.selectedDirectory.path +
sep +
name +
(name.endsWith(FILE_EXT) ? '' : FILE_EXT),
''
)
}
return `Successfully created "${name}"`
},
renameFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Rename file'>
) => {
const { oldName, newName, isDir } = event.data
let name = newName ? newName : DEFAULT_FILE_NAME
await renameFile(
context.selectedDirectory.path + sep + oldName,
context.selectedDirectory.path +
sep +
name +
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
)
return (
oldName !== name && `Successfully renamed "${oldName}" to "${name}"`
)
},
deleteFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Delete file'>
) => {
const isDir = !!event.data.children
if (isDir) {
await removeDir(event.data.path, {
recursive: true,
}).catch((e) => console.error('Error deleting directory', e))
} else {
await removeFile(event.data.path).catch((e) =>
console.error('Error deleting file', e)
)
}
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${
event.data.name
}"`
},
},
guards: {
'Has at least 1 file': (_, event: EventFrom<typeof fileMachine>) => {
if (event.type !== 'done.invoke.read-files') return false
return !!event?.data?.children && event.data.children.length > 0
},
},
})
return (
<FileContext.Provider
value={{
send,
state,
context: state.context, // just a convenience, can remove if we need to save on memory
}}
>
{children}
</FileContext.Provider>
)
}
export default FileMachineProvider

View File

@ -0,0 +1,16 @@
.folder {
position: relative;
}
.folder::after {
content: '';
width: 1px;
z-index: -1;
@apply absolute top-0 bottom-0;
left: calc(var(--indent-line-left, 1rem) + 0.25rem);
@apply bg-chalkboard-30;
}
:global(.dark) .folder::after {
@apply bg-chalkboard-80;
}

399
src/components/FileTree.tsx Normal file
View File

@ -0,0 +1,399 @@
import { IndexLoaderData, paths } from 'Router'
import { ActionButton } from './ActionButton'
import Tooltip from './Tooltip'
import { FileEntry } from '@tauri-apps/api/fs'
import { Dispatch, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Dialog, Disclosure } from '@headlessui/react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { useFileContext } from 'hooks/useFileContext'
import { useHotkeys } from 'react-hotkeys-hook'
import { kclManager } from 'lang/KclSinglton'
import styles from './FileTree.module.css'
import { sortProject } from 'lib/tauriFS'
function getIndentationCSS(level: number) {
return `calc(1rem * ${level + 1})`
}
function RenameForm({
fileOrDir,
setIsRenaming,
level = 0,
}: {
fileOrDir: FileEntry
setIsRenaming: Dispatch<React.SetStateAction<boolean>>
level?: number
}) {
const { send } = useFileContext()
const inputRef = useRef<HTMLInputElement>(null)
function handleRenameSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
setIsRenaming(false)
send({
type: 'Rename file',
data: {
oldName: fileOrDir.name || '',
newName: inputRef.current?.value || fileOrDir.name || '',
isDir: fileOrDir.children !== undefined,
},
})
}
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Escape') {
e.stopPropagation()
setIsRenaming(false)
}
}
return (
<form onSubmit={handleRenameSubmit}>
<label>
<span className="sr-only">Rename file</span>
<input
ref={inputRef}
type="text"
autoFocus
placeholder={fileOrDir.name}
className="w-full py-1 bg-transparent text-chalkboard-100 placeholder:text-chalkboard-70 dark:text-chalkboard-10 dark:placeholder:text-chalkboard-50 focus:outline-none focus:ring-0"
onKeyDown={handleKeyDown}
onBlur={() => setIsRenaming(false)}
style={{ paddingInlineStart: getIndentationCSS(level) }}
/>
</label>
<button className="sr-only" type="submit">
Submit
</button>
</form>
)
}
function DeleteConfirmationDialog({
fileOrDir,
setIsOpen,
}: {
fileOrDir: FileEntry
setIsOpen: Dispatch<React.SetStateAction<boolean>>
}) {
const { send } = useFileContext()
return (
<Dialog
open={true}
onClose={() => setIsOpen(false)}
className="relative z-50"
>
<div className="fixed inset-0 bg-chalkboard-110/80 grid place-content-center">
<Dialog.Panel className="rounded p-4 bg-chalkboard-10 dark:bg-chalkboard-100 border border-destroy-80 max-w-2xl">
<Dialog.Title as="h2" className="text-2xl font-bold mb-4">
Delete {fileOrDir.children !== undefined ? 'Folder' : 'File'}
</Dialog.Title>
<Dialog.Description className="my-6">
This will permanently delete "{fileOrDir.name || 'this file'}"
{fileOrDir.children !== undefined
? ' and all of its contents. '
: '. '}
This action cannot be undone.
</Dialog.Description>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={async () => {
send({ type: 'Delete file', data: fileOrDir })
setIsOpen(false)
}}
icon={{
icon: faTrashAlt,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10 dark:text-destroy-20 dark:group-hover:text-destroy-10 dark:hover:text-destroy-10',
}}
className="hover:border-destroy-40 dark:hover:border-destroy-40"
>
Delete
</ActionButton>
<ActionButton Element="button" onClick={() => setIsOpen(false)}>
Cancel
</ActionButton>
</div>
</Dialog.Panel>
</div>
</Dialog>
)
}
const FileTreeItem = ({
project,
currentFile,
fileOrDir,
closePanel,
level = 0,
}: {
project?: IndexLoaderData['project']
currentFile?: IndexLoaderData['file']
fileOrDir: FileEntry
closePanel: (
focusableElement?:
| HTMLElement
| React.MutableRefObject<HTMLElement | null>
| undefined
) => void
level?: number
}) => {
const { send, context } = useFileContext()
const navigate = useNavigate()
const [isRenaming, setIsRenaming] = useState(false)
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
const isCurrentFile = fileOrDir.path === currentFile?.path
function handleKeyUp(e: React.KeyboardEvent<HTMLButtonElement>) {
if (e.metaKey && e.key === 'Backspace') {
// Open confirmation dialog
setIsConfirmingDelete(true)
} else if (e.key === 'Enter') {
// Show the renaming form
setIsRenaming(true)
} else if (e.code === 'Space') {
openFile()
}
}
function openFile() {
if (fileOrDir.children !== undefined) return // Don't open directories
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
closePanel()
}
return (
<>
{fileOrDir.children === undefined ? (
<li
className={
'group m-0 p-0 border-solid border-0 text-energy-100 hover:text-energy-70 hover:bg-energy-10/50 dark:text-energy-30 dark:hover:!text-energy-20 dark:hover:bg-energy-90/50 focus-within:bg-energy-10/80 dark:focus-within:bg-energy-80/50 hover:focus-within:bg-energy-10/80 dark:hover:focus-within:bg-energy-80/50 ' +
(isCurrentFile ? 'bg-energy-10/50 dark:bg-energy-90/50' : '')
}
>
{!isRenaming ? (
<button
className="flex gap-1 items-center py-0.5 rounded-none border-none p-0 m-0 text-sm w-full hover:!bg-transparent text-left !text-inherit"
style={{ paddingInlineStart: getIndentationCSS(level) }}
onDoubleClick={openFile}
onClick={(e) => e.currentTarget.focus()}
onKeyUp={handleKeyUp}
>
<KclIcon
className={
'inline-block w-3 ' +
(isCurrentFile
? 'text-energy-90 dark:text-energy-10'
: 'text-energy-50 dark:text-energy-50')
}
/>
{fileOrDir.name}
</button>
) : (
<RenameForm
fileOrDir={fileOrDir}
setIsRenaming={setIsRenaming}
level={level}
/>
)}
</li>
) : (
<Disclosure defaultOpen={currentFile?.path.includes(fileOrDir.path)}>
{({ open }) => (
<div className="group">
{!isRenaming ? (
<Disclosure.Button
className={
' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 text-chalkboard-70 dark:text-chalkboard-30 hover:bg-energy-10/50 dark:hover:bg-energy-90/50' +
(context.selectedDirectory.path.includes(fileOrDir.path)
? ' group-focus-within:bg-chalkboard-20/50 dark:group-focus-within:bg-chalkboard-80/20 hover:group-focus-within:bg-chalkboard-20 dark:hover:group-focus-within:bg-chalkboard-80/20 group-active:bg-chalkboard-20/50 dark:group-active:bg-chalkboard-80/20 hover:group-active:bg-chalkboard-20/50 dark:hover:group-active:bg-chalkboard-80/20'
: '')
}
style={{ paddingInlineStart: getIndentationCSS(level) }}
onClick={(e) => e.currentTarget.focus()}
onClickCapture={(e) =>
send({ type: 'Set selected directory', data: fileOrDir })
}
onFocusCapture={(e) =>
send({ type: 'Set selected directory', data: fileOrDir })
}
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
onKeyUp={handleKeyUp}
>
<FontAwesomeIcon
icon={faChevronRight}
className={
'inline-block mr-2 m-0 p-0 w-2 h-2 ' +
(open ? 'transform rotate-90' : '')
}
/>
{fileOrDir.name}
</Disclosure.Button>
) : (
<div
className="flex items-center"
style={{ paddingInlineStart: getIndentationCSS(level) }}
>
<FontAwesomeIcon
icon={faChevronRight}
className={
'inline-block mr-2 m-0 p-0 w-2 h-2 ' +
(open ? 'transform rotate-90' : '')
}
/>
<RenameForm
fileOrDir={fileOrDir}
setIsRenaming={setIsRenaming}
level={-1}
/>
</div>
)}
<Disclosure.Panel
className={styles.folder}
style={
{
'--indent-line-left': getIndentationCSS(level),
} as React.CSSProperties
}
>
<ul
className="m-0 p-0"
onClickCapture={(e) => {
send({ type: 'Set selected directory', data: fileOrDir })
}}
onFocusCapture={(e) =>
send({ type: 'Set selected directory', data: fileOrDir })
}
>
{fileOrDir.children?.map((child) => (
<FileTreeItem
fileOrDir={child}
project={project}
currentFile={currentFile}
closePanel={closePanel}
level={level + 1}
key={level + '-' + child.path}
/>
))}
</ul>
</Disclosure.Panel>
</div>
)}
</Disclosure>
)}
{isConfirmingDelete && (
<DeleteConfirmationDialog
fileOrDir={fileOrDir}
setIsOpen={setIsConfirmingDelete}
/>
)}
</>
)
}
interface FileTreeProps {
className?: string
file?: IndexLoaderData['file']
closePanel: (
focusableElement?:
| HTMLElement
| React.MutableRefObject<HTMLElement | null>
| undefined
) => void
}
export const FileTree = ({
className = '',
file,
closePanel,
}: FileTreeProps) => {
const { send, context } = useFileContext()
useHotkeys('meta + n', createFile)
useHotkeys('meta + shift + n', createFolder)
async function createFile() {
send({ type: 'Create file', data: { name: '', makeDir: false } })
}
async function createFolder() {
send({ type: 'Create file', data: { name: '', makeDir: true } })
}
return (
<div className={className}>
<div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-30/50 dark:bg-chalkboard-70/50">
<h2 className="flex-1 m-0 p-0 text-sm mono">Files</h2>
<ActionButton
Element="button"
icon={{
icon: 'createFile',
iconClassName: '!text-energy-80 dark:!text-energy-20',
bgClassName: 'hover:bg-energy-10/50 dark:hover:bg-transparent',
}}
className="!p-0 border-none bg-transparent !outline-none"
onClick={createFile}
>
<Tooltip position="inlineStart" delay={750}>
Create File
</Tooltip>
</ActionButton>
<ActionButton
Element="button"
icon={{
icon: 'createFolder',
iconClassName: '!text-energy-80 dark:!text-energy-20',
bgClassName: 'hover:bg-energy-10/50 dark:hover:bg-transparent',
}}
className="!p-0 border-none bg-transparent !outline-none"
onClick={createFolder}
>
<Tooltip position="inlineStart" delay={750}>
Create Folder
</Tooltip>
</ActionButton>
</div>
<div className="overflow-auto max-h-full pb-12">
<ul
className="m-0 p-0 text-sm"
onClickCapture={(e) => {
send({ type: 'Set selected directory', data: context.project })
}}
>
{sortProject(context.project.children || []).map((fileOrDir) => (
<FileTreeItem
project={context.project}
currentFile={file}
fileOrDir={fileOrDir}
closePanel={closePanel}
key={fileOrDir.path}
/>
))}
</ul>
</div>
</div>
)
}
function KclIcon({ className = '' }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M40 0H0V40H40V0ZM7.34715 27.2143V15.6577L2.976 15.987V36.7949H7.34715V32.0645L8.00582 31.5256C8.24533 31.326 8.47487 31.1264 8.69442 30.9268L12.1075 36.7949H17.0475C16.1893 35.3978 15.311 33.9906 14.4128 32.5735C13.5346 31.1563 12.6664 29.7392 11.8081 28.3221L15.8499 24.9389C15.4308 24.4399 15.0017 23.931 14.5625 23.412L13.3051 21.8552L7.34715 27.2143ZM22.2581 26.6754C22.8769 25.9169 23.6753 25.5377 24.6533 25.5377C25.272 25.5377 25.8309 25.6175 26.3299 25.7772C26.8289 25.9169 27.4177 26.1465 28.0963 26.4658L29.3238 23.3521C28.5853 22.7933 27.7371 22.4041 26.779 22.1845C25.8409 21.9649 25.0625 21.8552 24.4437 21.8552C22.0885 21.8552 20.2223 22.5537 18.845 23.9509C17.4878 25.3281 16.8092 27.1944 16.8092 29.5496C16.8092 31.9048 17.4878 33.7611 18.845 35.1183C20.2223 36.4756 22.0885 37.1542 24.4437 37.1542C25.0625 37.1542 25.8509 37.0444 26.8089 36.8249C27.767 36.6053 28.6053 36.2161 29.3238 35.6572L28.0963 32.5435C27.4177 32.8629 26.8289 33.0924 26.3299 33.2321C25.8309 33.3718 25.272 33.4417 24.6533 33.4417C23.6753 33.4417 22.8769 33.0924 22.2581 32.3938C21.6594 31.6753 21.36 30.7272 21.36 29.5496C21.36 28.372 21.6594 27.4139 22.2581 26.6754ZM36.2796 36.7949V15.6577L31.9085 15.987V36.7949H36.2796Z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -24,9 +24,7 @@ import {
StateFrom,
} from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { invoke } from '@tauri-apps/api'
import { isTauri } from 'lib/isTauri'
import { VITE_KC_API_BASE_URL } from 'env'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>

View File

@ -1,8 +1,8 @@
import ReactJson from 'react-json-view'
import { useEffect } from 'react'
import { useStore } from '../useStore'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
const ReactJsonTypeHack = ReactJson as any
@ -11,9 +11,7 @@ interface LogPanelProps extends CollapsiblePanelProps {
}
export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => {
const { logs } = useStore(({ logs }) => ({
logs,
}))
const { logs } = useKclContext()
useEffect(() => {
const element = document.querySelector('.console-tile')
if (element) {
@ -47,21 +45,19 @@ export const KCLErrors = ({
theme = Themes.Light,
...props
}: LogPanelProps) => {
const { kclErrors } = useStore(({ kclErrors }) => ({
kclErrors,
}))
const { errors } = useKclContext()
useEffect(() => {
const element = document.querySelector('.console-tile')
if (element) {
element.scrollTop = element.scrollHeight - element.clientHeight
}
}, [kclErrors])
}, [errors])
return (
<CollapsiblePanel {...props}>
<div className="h-full relative">
<div className="absolute inset-0 flex flex-col">
<ReactJsonTypeHack
src={kclErrors}
src={errors}
collapsed={1}
collapseStringsAfterLength={60}
enableClipboard={false}

View File

@ -1,7 +1,6 @@
import { processMemory } from './MemoryPanel'
import { parser_wasm } from '../lang/abstractSyntaxTree'
import { enginelessExecutor } from '../lib/testHelpers'
import { initPromise } from '../lang/rust'
import { initPromise, parse } from '../lang/wasm'
beforeAll(() => initPromise)
@ -15,18 +14,20 @@ describe('processMemory', () => {
}
const otherVar = myFn(5)
const theExtrude = startSketchAt([0, 0])
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.4, myVar], %)
|> lineTo([-0.76, otherVar], %)
|> extrude(4, %)
const theSketch = startSketchAt([0, 0])
const theSketch = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-3.35, 0.17], %)
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)
show(theExtrude, theSketch)`
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast, {
root: {},
return: null,

View File

@ -1,9 +1,9 @@
import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { useMemo } from 'react'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
interface MemoryPanelProps extends CollapsiblePanelProps {
theme?: Exclude<Themes, Themes.System>
@ -13,9 +13,7 @@ export const MemoryPanel = ({
theme = Themes.Light,
...props
}: MemoryPanelProps) => {
const { programMemory } = useStore((s) => ({
programMemory: s.programMemory,
}))
const { programMemory } = useKclContext()
const ProcessedMemory = useMemo(
() => processMemory(programMemory),
[programMemory]
@ -50,7 +48,7 @@ export const MemoryPanel = ({
export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {}
Object.keys(programMemory.root).forEach((key) => {
Object.keys(programMemory?.root || {}).forEach((key) => {
const val = programMemory.root[key]
if (typeof val.value !== 'function') {
if (val.type === 'SketchGroup') {

View File

@ -0,0 +1,458 @@
import { useMachine } from '@xstate/react'
import React, { createContext, useEffect, useRef } from 'react'
import {
AnyStateMachine,
ContextFrom,
InterpreterFrom,
Prop,
StateFrom,
assign,
} from 'xstate'
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager } from 'lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { addStartSketch } from 'lang/modifyAst'
import { roundOff } from 'lib/utils'
import {
recast,
parse,
Program,
PipeExpression,
CallExpression,
} from 'lang/wasm'
import { getNodeFromPath } from 'lang/queryAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { kclManager } from 'lang/KclSinglton'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import { applyConstraintAngleBetween } from './Toolbar/SetAngleBetween'
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import { toast } from 'react-hot-toast'
import { pathMapToSelections } from 'lang/util'
import { useStore } from 'useStore'
import { handleSelectionBatch, handleSelectionWithShift } from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<InterpreterFrom<T>, 'send'>
}
export const ModelingMachineContext = createContext(
{} as MachineContext<typeof modelingMachine>
)
export const ModelingMachineProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const { auth } = useGlobalStateContext()
const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token)
const { isShiftDown, editorView } = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
}))
// const { commands } = useCommandsContext()
// Settings machine setup
// const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
// )
// What should we persist from modeling state? Nothing?
// const persistedSettings = Object.assign(
// settingsMachine.initialState.context,
// JSON.parse(retrievedSettings.current) as Partial<
// (typeof settingsMachine)['context']
// >
// )
const [modelingState, modelingSend] = useMachine(modelingMachine, {
// context: persistedSettings,
actions: {
'Modify AST': () => {},
'Update code selection cursors': () => {},
'show default planes': () => {
kclManager.showPlanes()
},
'create path': assign({
sketchEnginePathId: () => {
const sketchUuid = uuidv4()
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
})
return sketchUuid
},
}),
'AST start new sketch': assign(
({ sketchEnginePathId }, { data: { coords, axis, segmentId } }) => {
if (!axis) {
// Something really weird must have happened for this to happen.
console.error('axis is undefined for starting a new sketch')
return {}
}
if (!segmentId) {
// Something really weird must have happened for this to happen.
console.error('segmentId is undefined for starting a new sketch')
return {}
}
const _addStartSketch = addStartSketch(
kclManager.ast,
axis,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
const newCode = recast(_modifiedAst)
const astWithUpdatedSource = parse(newCode)
const updatedPipeNode = getNodeFromPath<PipeExpression>(
astWithUpdatedSource,
_pathToNode
).node
const startProfileAtCallExp = updatedPipeNode.body.find(
(exp) =>
exp.type === 'CallExpression' &&
exp.callee.name === 'startProfileAt'
)
if (startProfileAtCallExp)
engineCommandManager.artifactMap[sketchEnginePathId] = {
type: 'result',
range: [startProfileAtCallExp.start, startProfileAtCallExp.end],
commandType: 'start_path',
data: null,
raw: {} as any,
}
const lineCallExp = updatedPipeNode.body.find(
(exp) => exp.type === 'CallExpression' && exp.callee.name === 'line'
)
if (lineCallExp)
engineCommandManager.artifactMap[segmentId] = {
type: 'result',
range: [lineCallExp.start, lineCallExp.end],
commandType: 'extend_path',
parentId: sketchEnginePathId,
data: null,
raw: {} as any,
}
kclManager.executeAstMock(astWithUpdatedSource, true)
return {
sketchPathToNode: _pathToNode,
}
}
),
'AST add line segment': async (
{ sketchPathToNode, sketchEnginePathId },
{ data: { coords, segmentId } }
) => {
if (!sketchPathToNode) return
const lastCoord = coords[coords.length - 1]
const pathInfo = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'path_get_info',
path_id: sketchEnginePathId,
},
})
const firstSegment = pathInfo?.data?.data?.segments.find(
(seg: any) => seg.command === 'line_to'
)
const firstSegCoords = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: firstSegment.command_id,
},
})
const startPathCoord = firstSegCoords?.data?.data?.control_points[0]
const isClose = compareVec2Epsilon(
[startPathCoord.x, startPathCoord.y],
[lastCoord.x, lastCoord.y]
)
let _modifiedAst: Program
if (!isClose) {
const newSketchLn = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [lastCoord.x, lastCoord.y],
from: [coords[0].x, coords[0].y],
fnName: 'line',
pathToNode: sketchPathToNode,
})
const _modifiedAst = newSketchLn.modifiedAst
kclManager.executeAstMock(_modifiedAst, true).then(() => {
const lineCallExp = getNodeFromPath<CallExpression>(
kclManager.ast,
newSketchLn.pathToNode
).node
if (segmentId)
engineCommandManager.artifactMap[segmentId] = {
type: 'result',
range: [lineCallExp.start, lineCallExp.end],
commandType: 'extend_path',
parentId: sketchEnginePathId,
data: null,
raw: {} as any,
}
})
} else {
_modifiedAst = addCloseToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
pathToNode: sketchPathToNode,
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
kclManager.executeAstMock(_modifiedAst, true)
// updateAst(_modifiedAst, true)
}
},
'sketch exit execute': () => {
kclManager.executeAst()
},
'set tool': () => {}, // TODO
'toast extrude failed': () => {
toast.error(
'Extrude failed, sketches need to be closed, or not already extruded'
)
},
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
return { selectionRanges: setSelections.selection }
else if (setSelections.selectionType === 'otherSelection')
return {
selectionRanges: {
...selectionRanges,
otherSelections: [setSelections.selection],
},
}
else if (!editorView) return {}
else if (setSelections.selectionType === 'singleCodeCursor') {
// This DOES NOT set the `selectionRanges` in xstate context
// instead it updates/dispatches to the editor, which in turn updates the xstate context
// I've found this the best way to deal with the editor without causing an infinite loop
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
// because we want to respect the user manually placing the cursor too.
// for more details on how selections see `src/lib/selections.ts`.
const { codeMirrorSelection, selectionRangeTypeMap } =
handleSelectionWithShift({
codeSelection: setSelections.selection,
currestSelections: selectionRanges,
isShiftDown,
})
if (codeMirrorSelection) {
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
}
return { selectionRangeTypeMap }
}
// This DOES NOT set the `selectionRanges` in xstate context
// same as comment above
const { codeMirrorSelection, selectionRangeTypeMap } =
handleSelectionBatch({
selections: setSelections.selection,
})
if (codeMirrorSelection) {
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
}
return { selectionRangeTypeMap }
}),
},
guards: {
'Selection contains axis': () => true,
'Selection contains edge': () => true,
'Selection contains face': () => true,
'Selection contains line': () => true,
'Selection contains point': () => true,
'Selection is not empty': () => true,
'Selection is one face': ({ selectionRanges }) => {
return !!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
},
},
services: {
'Get horizontal info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setHorzDistance',
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get vertical info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setVertDistance',
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get angle info': async ({ selectionRanges }): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleBetween({
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get length info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } = await applyConstraintAngleLength(
{ selectionRanges }
)
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get perpendicular distance info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect({
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
},
devTools: true,
})
useEffect(() => {
engineCommandManager.onPlaneSelected((plane_id: string) => {
if (modelingState.nextEvents.includes('Select default plane')) {
modelingSend({
type: 'Select default plane',
data: { planeId: plane_id },
})
}
})
}, [modelingSend, modelingState.nextEvents])
useEffect(() => {
kclManager.registerExecuteCallback(() => {
modelingSend({ type: 'Re-execute' })
})
}, [modelingSend])
// useStateMachineCommands({
// state: settingsState,
// send: settingsSend,
// commands,
// owner: 'settings',
// commandBarMeta: settingsCommandBarMeta,
// })
return (
<ModelingMachineContext.Provider
value={{
state: modelingState,
context: modelingState.context,
send: modelingSend,
}}
>
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
since realistically it won't ever have generic children that isn't app.tsx */}
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
{children}
</div>
</ModelingMachineContext.Provider>
)
}
export default ModelingMachineProvider

View File

@ -1,4 +1,4 @@
import { FormEvent, useState } from 'react'
import { FormEvent, useEffect, useState } from 'react'
import { type ProjectWithEntryPointMetadata, paths } from '../Router'
import { Link } from 'react-router-dom'
import { ActionButton } from './ActionButton'
@ -8,7 +8,7 @@ import {
faTrashAlt,
faX,
} from '@fortawesome/free-solid-svg-icons'
import { FILE_EXT } from '../lib/tauriFS'
import { FILE_EXT, getPartsCount, readProject } from '../lib/tauriFS'
import { Dialog } from '@headlessui/react'
import { useHotkeys } from 'react-hotkeys-hook'
@ -28,6 +28,8 @@ function ProjectCard({
useHotkeys('esc', () => setIsEditing(false))
const [isEditing, setIsEditing] = useState(false)
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
const [numberOfParts, setNumberOfParts] = useState(1)
const [numberOfFolders, setNumberOfFolders] = useState(0)
function handleSave(e: FormEvent<HTMLFormElement>) {
e.preventDefault()
@ -42,6 +44,17 @@ function ProjectCard({
: date.toLocaleTimeString()
}
useEffect(() => {
async function getNumberOfParts() {
const { kclFileCount, kclDirCount } = getPartsCount(
await readProject(project.path)
)
setNumberOfParts(kclFileCount)
setNumberOfFolders(kclDirCount)
}
getNumberOfParts()
}, [project.path])
return (
<li
{...props}
@ -76,7 +89,7 @@ function ProjectCard({
</form>
) : (
<>
<div className="p-1 flex flex-col gap-2">
<div className="p-1 flex flex-col h-full gap-2">
<Link
to={`${paths.FILE}/${encodeURIComponent(project.path)}`}
className="flex-1 text-liquid-100"
@ -84,7 +97,14 @@ function ProjectCard({
{project.name?.replace(FILE_EXT, '')}
</Link>
<span className="text-chalkboard-60 text-xs">
Edited {getDisplayedTime(project.entrypoint_metadata.modifiedAt)}
{numberOfParts} part{numberOfParts === 1 ? '' : 's'}{' '}
{numberOfFolders > 0 &&
`/ ${numberOfFolders} folder${
numberOfFolders === 1 ? '' : 's'
}`}
</span>
<span className="text-chalkboard-60 text-xs">
Edited {getDisplayedTime(project.entrypointMetadata.modifiedAt)}
</span>
<div className="absolute bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
<ActionButton

View File

@ -2,6 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { ProjectWithEntryPointMetadata } from '../Router'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar'
const now = new Date()
const projectWellFormed = {
@ -13,7 +15,7 @@ const projectWellFormed = {
path: '/some/path/Simple Box/main.kcl',
},
],
entrypoint_metadata: {
entrypointMetadata: {
accessedAt: now,
blksize: 32,
blocks: 32,
@ -38,7 +40,11 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders the project name', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu project={projectWellFormed} />
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu project={projectWellFormed} />
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
@ -55,7 +61,11 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders app name if given no project', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu />
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu />
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
@ -69,7 +79,14 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders as a link if set to do so', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu project={projectWellFormed} renderAsLink={true} />
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu
project={projectWellFormed}
renderAsLink={true}
/>
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)

View File

@ -1,18 +1,22 @@
import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faHome } from '@fortawesome/free-solid-svg-icons'
import { ProjectWithEntryPointMetadata, paths } from '../Router'
import { IndexLoaderData, paths } from '../Router'
import { isTauri } from '../lib/isTauri'
import { Link } from 'react-router-dom'
import { ExportButton } from './ExportButton'
import { Fragment } from 'react'
import { FileTree } from './FileTree'
import { sep } from '@tauri-apps/api/path'
const ProjectSidebarMenu = ({
project,
file,
renderAsLink = false,
}: {
renderAsLink?: boolean
project?: Partial<ProjectWithEntryPointMetadata>
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}) => {
return renderAsLink ? (
<Link
@ -23,10 +27,10 @@ const ProjectSidebarMenu = ({
<img
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="h-9 w-auto"
className="w-auto h-9"
/>
<span
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
data-testid="project-sidebar-link-name"
>
{project?.name ? project.name : 'KittyCAD Modeling App'}
@ -41,11 +45,20 @@ const ProjectSidebarMenu = ({
<img
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="h-full w-auto"
className="w-auto h-full"
/>
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
</span>
<div className="flex flex-col items-start py-0.5">
<span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
{isTauri() && file?.name
? file.name.slice(file.name.lastIndexOf(sep) + 1)
: 'KittyCAD Modeling App'}
</span>
{isTauri() && project?.name && (
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
{project.name}
</span>
)}
</div>
</Popover.Button>
<Transition
enter="duration-200 ease-out"
@ -56,7 +69,7 @@ const ProjectSidebarMenu = ({
leaveTo="opacity-0"
as={Fragment}
>
<Popover.Overlay className="fixed z-20 inset-0 bg-chalkboard-110/50" />
<Popover.Overlay className="fixed inset-0 z-20 bg-chalkboard-110/50" />
</Transition>
<Transition
@ -68,54 +81,74 @@ const ProjectSidebarMenu = ({
leaveTo="opacity-0 -translate-x-4"
as={Fragment}
>
<Popover.Panel className="fixed inset-0 right-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-energy-100 dark:border-energy-100/50 shadow-md rounded-r-lg overflow-hidden">
<div className="flex items-center gap-4 px-4 py-3 bg-energy-100">
<img
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="h-9 w-auto"
/>
<Popover.Panel
className="fixed inset-0 right-auto z-30 grid w-64 h-screen max-h-screen grid-cols-1 border rounded-r-lg shadow-md bg-chalkboard-10 dark:bg-chalkboard-100 border-energy-100 dark:border-energy-100/50"
style={{ gridTemplateRows: 'auto 1fr auto' }}
>
{({ close }) => (
<>
<div className="flex items-center gap-4 px-4 py-3 bg-energy-10/25 dark:bg-energy-110">
<img
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="w-auto h-9"
/>
<div>
<p
className="m-0 text-energy-10 text-mono"
data-testid="projectName"
>
{project?.name ? project.name : 'KittyCAD Modeling App'}
</p>
{project?.entrypoint_metadata && (
<p
className="m-0 text-energy-40 text-xs"
data-testid="createdAt"
>
Created{' '}
{project?.entrypoint_metadata.createdAt.toLocaleDateString()}
</p>
<div>
<p
className="m-0 text-chalkboard-100 dark:text-energy-10 text-mono"
data-testid="projectName"
>
{project?.name ? project.name : 'KittyCAD Modeling App'}
</p>
{project?.entrypointMetadata && (
<p
className="m-0 text-xs text-chalkboard-100 dark:text-energy-40"
data-testid="createdAt"
>
Created{' '}
{project.entrypointMetadata.createdAt.toLocaleDateString()}
</p>
)}
</div>
</div>
{isTauri() ? (
<FileTree
file={file}
className="overflow-hidden border-0 border-y border-energy-40 dark:border-energy-70"
closePanel={close}
/>
) : (
<div className="flex-1 overflow-hidden" />
)}
</div>
</div>
<div className="p-4 flex flex-col gap-2">
<ExportButton
className={{
button:
'border-transparent dark:border-transparent dark:hover:border-energy-60',
}}
>
Export Model
</ExportButton>
{isTauri() && (
<ActionButton
Element="link"
to={paths.HOME}
icon={{
icon: faHome,
}}
className="border-transparent dark:border-transparent dark:hover:border-energy-60"
>
Go to Home
</ActionButton>
)}
</div>
<div className="flex flex-col gap-2 p-4 bg-energy-10/25 dark:bg-energy-110">
<ExportButton
className={{
button:
'border-transparent dark:border-transparent hover:border-energy-60',
icon: 'text-energy-10 dark:text-energy-120',
bg: 'bg-energy-120 dark:bg-energy-10',
}}
>
Export Model
</ExportButton>
{isTauri() && (
<ActionButton
Element="link"
to={paths.HOME}
icon={{
icon: faHome,
iconClassName: 'text-energy-10 dark:text-energy-120',
bgClassName: 'bg-energy-120 dark:bg-energy-10',
}}
className="border-transparent dark:border-transparent hover:border-energy-60"
>
Go to Home
</ActionButton>
)}
</div>
</>
)}
</Popover.Panel>
</Transition>
</Popover>

View File

@ -1,6 +1,7 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
import { Value } from '../lang/abstractSyntaxTreeTypes'
import { type InstanceProps, create } from 'react-modal-promise'
import { Value } from '../lang/wasm'
import {
AvailableVars,
addToInputHelper,
@ -9,6 +10,28 @@ import {
CreateNewVariable,
} from './AvailableVarsHelpers'
type ModalResolve = {
value: string
sign: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
}
type ModalReject = boolean
type SetAngleLengthModalProps = InstanceProps<ModalResolve, ModalReject> & {
value: number
valueName: string
shouldCreateVariable?: boolean
}
export const createSetAngleLengthModal = create<
SetAngleLengthModalProps,
ModalResolve,
ModalReject
>
export const SetAngleLengthModal = ({
isOpen,
onResolve,
@ -16,20 +39,7 @@ export const SetAngleLengthModal = ({
value: initialValue,
valueName,
shouldCreateVariable: initialShouldCreateVariable = false,
}: {
isOpen: boolean
onResolve: (a: {
value: string
sign: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
}) => void
onReject: (a: any) => void
value: number
valueName: string
shouldCreateVariable: boolean
}) => {
}: SetAngleLengthModalProps) => {
const [sign, setSign] = useState(Math.sign(Number(initialValue)))
const [value, setValue] = useState(String(initialValue * sign))
const [shouldCreateVariable, setShouldCreateVariable] = useState(
@ -98,7 +108,7 @@ export const SetAngleLengthModal = ({
</label>
<div className="mt-1 flex">
<button
className="border border-gray-300 px-2"
className="border border-gray-300 px-2 text-gray-900"
onClick={() => setSign(-sign)}
>
{sign > 0 ? '+' : '-'}
@ -108,7 +118,7 @@ export const SetAngleLengthModal = ({
type="text"
name="val"
id="val"
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1"
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1 text-gray-900"
value={value}
onChange={(e) => {
setValue(e.target.value)

View File

@ -1,6 +1,7 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
import { Value } from '../lang/abstractSyntaxTreeTypes'
import { type InstanceProps, create } from 'react-modal-promise'
import { Value } from '../lang/wasm'
import {
AvailableVars,
addToInputHelper,
@ -9,6 +10,30 @@ import {
CreateNewVariable,
} from './AvailableVarsHelpers'
type ModalResolve = {
value: string
segName: string
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
}
type ModalReject = boolean
type GetInfoModalProps = InstanceProps<ModalResolve, ModalReject> & {
segName: string
isSegNameEditable: boolean
value?: number
initialVariableName: string
}
export const createInfoModal = create<
GetInfoModalProps,
ModalResolve,
ModalReject
>
export const GetInfoModal = ({
isOpen,
onResolve,
@ -17,25 +42,12 @@ export const GetInfoModal = ({
isSegNameEditable,
value: initialValue,
initialVariableName,
}: {
isOpen: boolean
onResolve: (a: {
value: string
segName: string
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
}) => void
onReject: (a: any) => void
segName: string
isSegNameEditable: boolean
value: number
initialVariableName: string
}) => {
}: GetInfoModalProps) => {
const [sign, setSign] = useState(Math.sign(Number(initialValue)))
const [segName, setSegName] = useState(initialSegName)
const [value, setValue] = useState(String(Math.abs(initialValue)))
const [value, setValue] = useState(
initialValue === undefined ? '' : String(Math.abs(initialValue))
)
const [shouldCreateVariable, setShouldCreateVariable] = useState(false)
const {
@ -75,7 +87,7 @@ export const GetInfoModal = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white/90 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
@ -97,7 +109,7 @@ export const GetInfoModal = ({
</label>
<div className="mt-1 flex">
<button
className="border border-gray-300 px-2 mr-1"
className="border border-gray-400 px-2 mr-1 text-gray-900"
onClick={() => setSign(-sign)}
>
{sign > 0 ? '+' : '-'}
@ -107,7 +119,7 @@ export const GetInfoModal = ({
name="val"
id="val"
ref={inputRef}
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono"
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm text-gray-900 border-gray-300 rounded-md font-mono"
value={value}
onChange={(e) => {
setValue(e.target.value)
@ -127,7 +139,7 @@ export const GetInfoModal = ({
name="segName"
id="segName"
disabled={!isSegNameEditable}
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono"
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm text-gray-900 border-gray-300 rounded-md font-mono"
value={segName}
onChange={(e) => {
setSegName(e.target.value)

View File

@ -4,19 +4,26 @@ import { useCalc, CreateNewVariable } from './AvailableVarsHelpers'
import { ActionButton } from './ActionButton'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { toast } from 'react-hot-toast'
import { type InstanceProps, create } from 'react-modal-promise'
type ModalResolve = { variableName: string }
type ModalReject = boolean
type SetVarNameModalProps = InstanceProps<ModalResolve, ModalReject> & {
valueName: string
}
export const createSetVarNameModal = create<
SetVarNameModalProps,
ModalResolve,
ModalReject
>
export const SetVarNameModal = ({
isOpen,
onResolve,
onReject,
valueName,
}: {
isOpen: boolean
onResolve: (a: { variableName?: string }) => void
onReject: (a: any) => void
value: number
valueName: string
}) => {
}: SetVarNameModalProps) => {
const { isNewVariableNameUnique, newVariableName, setNewVariableName } =
useCalc({ value: '', initialVariableName: valueName })

View File

@ -7,24 +7,18 @@ import {
} from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { getNormalisedCoordinates, roundOff } from '../lib/utils'
import { getNormalisedCoordinates } from '../lib/utils'
import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
import { Models } from '@kittycad/lib'
import { addStartSketch } from 'lang/modifyAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { getNodeFromPath } from 'lang/queryAst'
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
import { modify_ast_for_sketch } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from 'lang/errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from 'lang/abstractSyntaxTree'
import { VariableDeclarator, recast, CallExpression } from 'lang/wasm'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { changeSketchArguments } from 'lang/std/sketch'
export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true)
@ -32,37 +26,21 @@ export const Stream = ({ className = '' }) => {
const videoRef = useRef<HTMLVideoElement>(null)
const {
mediaStream,
engineCommandManager,
setButtonDownInStream,
didDragInStream,
setDidDragInStream,
streamDimensions,
isExecuting,
guiMode,
ast,
updateAst,
setGuiMode,
programMemory,
} = useStore((s) => ({
mediaStream: s.mediaStream,
engineCommandManager: s.engineCommandManager,
setButtonDownInStream: s.setButtonDownInStream,
fileId: s.fileId,
didDragInStream: s.didDragInStream,
setDidDragInStream: s.setDidDragInStream,
streamDimensions: s.streamDimensions,
isExecuting: s.isExecuting,
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
setGuiMode: s.setGuiMode,
programMemory: s.programMemory,
}))
const {
settings: {
context: { cameraControls },
},
} = useGlobalStateContext()
const { settings } = useGlobalStateContext()
const cameraControls = settings?.context?.cameraControls
const { send, state, context } = useModelingContext()
const { isExecuting } = useKclContext()
useEffect(() => {
if (
@ -73,7 +51,7 @@ export const Stream = ({ className = '' }) => {
if (!videoRef.current) return
if (!mediaStream) return
videoRef.current.srcObject = mediaStream
}, [mediaStream, engineCommandManager])
}, [mediaStream])
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
if (!videoRef.current) return
@ -106,8 +84,14 @@ export const Stream = ({ className = '' }) => {
interaction = 'zoom'
}
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
engineCommandManager?.sendSceneCommand({
if (state.matches('Sketch.Move Tool')) {
if (
state.matches('Sketch.Move Tool.No move') ||
state.matches('Sketch.Move Tool.Move with execute')
) {
return
}
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'handle_mouse_drag_start',
@ -115,13 +99,8 @@ export const Stream = ({ className = '' }) => {
},
cmd_id: newId,
})
} else if (
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
)
) {
engineCommandManager?.sendSceneCommand({
} else if (!state.matches('Sketch.Line Tool')) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
@ -139,7 +118,7 @@ export const Stream = ({ className = '' }) => {
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
@ -176,215 +155,209 @@ export const Stream = ({ className = '' }) => {
cmd_id: newCmdId,
}
if (!didDragInStream) {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
},
cmd_id: uuidv4(),
})
}
if (!didDragInStream && guiMode.mode === 'default') {
if (!didDragInStream && state.matches('Sketch no face')) {
command.cmd = {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
}
} else if (
(!didDragInStream &&
guiMode.mode === 'sketch' &&
['move', 'select'].includes(guiMode.sketchMode)) ||
(guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any))
) {
engineCommandManager.sendSceneCommand(command)
} else if (!didDragInStream && state.matches('Sketch.Line Tool')) {
command.cmd = {
type: 'mouse_click',
window: { x, y },
}
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
const entities_modified = resp?.data?.data?.entities_modified
if (!entities_modified) return
if (state.matches('Sketch.Line Tool.No Points')) {
send('Add point')
} else if (state.matches('Sketch.Line Tool.Point Added')) {
const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
// We need the normal for the plane we are on.
const plane = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'get_sketch_mode_plane',
},
})
const z_axis = plane.data.data.z_axis
// Get the current axis.
let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null =
null
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
if (z_axis.z === -1) {
currentAxis = '-xy'
} else {
currentAxis = 'xy'
}
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
if (z_axis.x === -1) {
currentAxis = '-yz'
} else {
currentAxis = 'yz'
}
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
if (z_axis.y === -1) {
currentAxis = '-xz'
} else {
currentAxis = 'xz'
}
}
send({
type: 'Add point',
data: {
coords,
axis: currentAxis,
segmentId: entities_modified[0],
},
})
} else if (state.matches('Sketch.Line Tool.Segment Added')) {
const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
send({
type: 'Add point',
data: { coords, axis: null, segmentId: entities_modified[0] },
})
}
})
} else if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('move' as any)
!didDragInStream &&
(state.matches('Sketch.SketchIdle') ||
state.matches('idle') ||
state.matches('awaiting selection'))
) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (!didDragInStream && state.matches('Sketch.Move Tool')) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (didDragInStream && state.matches('Sketch.Move Tool')) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
}
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
if (!(guiMode.mode === 'sketch')) return
if (guiMode.sketchMode === 'selectFace') return
// Check if the sketch group already exists.
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
'VariableDeclarator'
).node
const variableName = varDec?.id?.name
const sketchGroup = programMemory.root[variableName]
const isEditingExistingSketch =
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
let sketchGroupId = ''
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
sketchGroupId = sketchGroup.id
}
if (
guiMode.sketchMode === ('move' as any as 'line') &&
command.cmd.type === 'handle_mouse_drag_end'
) {
// Let's get the updated ast.
if (sketchGroupId === '') return
console.log('guiMode.pathId', guiMode.pathId)
// We have a problem if we do not have an id for the sketch group.
if (
guiMode.pathId === undefined ||
guiMode.pathId === null ||
guiMode.pathId === ''
)
return
let engineId = guiMode.pathId
try {
const updatedAst: Program = await modify_ast_for_sketch(
engineCommandManager,
JSON.stringify(ast),
variableName,
engineId
)
updateAst(updatedAst, false)
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
return
}
if (command?.cmd?.type !== 'mouse_click' || !ast) return
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
if (
resp?.data?.data?.entities_modified?.length &&
guiMode.waitingFirstClick &&
!isEditingExistingSketch
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: resp?.data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const _addStartSketch = addStartSketch(
ast,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
// We need to update the guiMode with the right pathId so that we can
// move lines later and send the right sketch id to the engine.
for (const [id, artifact] of Object.entries(
engineCommandManager.artifactMap
)) {
if (artifact.commandType === 'start_path') {
guiMode.pathId = id
}
}
setGuiMode({
...guiMode,
pathToNode: _pathToNode,
waitingFirstClick: false,
})
updateAst(_modifiedAst, false)
} else if (
resp?.data?.data?.entities_modified?.length &&
(!guiMode.waitingFirstClick || isEditingExistingSketch)
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: resp?.data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
engineCommandManager.sendSceneCommand(command).then(async () => {
if (!context.sketchPathToNode) return
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
context.sketchPathToNode,
'VariableDeclarator'
)
const variableName = varDec.id.name
const sketchGroup = programMemory.root[variableName]
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
const initialCoords = sketchGroup.value[0].from
const isClose = compareVec2Epsilon(initialCoords, [
coords[1].x,
coords[1].y,
])
let _modifiedAst: Program
if (!isClose) {
_modifiedAst = addNewSketchLn({
node: ast,
programMemory,
to: [coords[1].x, coords[1].y],
fnName: 'line',
pathToNode: guiMode.pathToNode,
}).modifiedAst
updateAst(_modifiedAst, false)
} else {
_modifiedAst = addCloseToPipe({
node: ast,
programMemory,
pathToNode: guiMode.pathToNode,
})
setGuiMode({
mode: 'default',
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
updateAst(_modifiedAst, true)
// Get the current plane string for plane we are on.
let currentPlaneString = ''
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
currentPlaneString = 'XY'
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
currentPlaneString = 'YZ'
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
currentPlaneString = 'XZ'
}
}
})
// Do not supporting editing/moving lines on a non-default plane.
// Eventually we can support this but for now we will just throw an
// error.
if (currentPlaneString === '') return
const pathInfo = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'path_get_info',
path_id: context.sketchEnginePathId,
},
})
const segmentsWithMappings = (
pathInfo?.data?.data?.segments as { command_id: string }[]
)
.filter(({ command_id }) => {
return command_id && engineCommandManager.artifactMap[command_id]
})
.map(({ command_id }) => command_id)
const segment2dInfo = await Promise.all(
segmentsWithMappings.map(async (segmentId) => {
const response = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: segmentId,
},
})
const controlPoints: [
{ x: number; y: number },
{ x: number; y: number }
] = response.data.data.control_points
return {
controlPoints,
segmentId,
}
})
)
let modifiedAst = { ...kclManager.ast }
let code = kclManager.code
for (const controlPoint of segment2dInfo) {
const range =
engineCommandManager.artifactMap[controlPoint.segmentId].range
if (!range) continue
const from = controlPoint.controlPoints[0]
const to = controlPoint.controlPoints[1]
const modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
range,
[to.x, to.y],
[from.x, from.y]
)
modifiedAst = modded.modifiedAst
// update artifact map ranges now that we have updated the ast.
code = recast(modded.modifiedAst)
const astWithCurrentRanges = kclManager.safeParse(code)
if (!astWithCurrentRanges) return
const updateNode = getNodeFromPath<CallExpression>(
astWithCurrentRanges,
modded.pathToNode
).node
engineCommandManager.artifactMap[controlPoint.segmentId].range = [
updateNode.start,
updateNode.end,
]
}
kclManager.executeAstMock(modifiedAst, true)
})
}
setDidDragInStream(false)
setClickCoords(undefined)
}
@ -415,7 +388,8 @@ export const Stream = ({ className = '' }) => {
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full h-full ${isExecuting && 'blur-md'}`}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
disablePictureInPicture
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/>
{isLoading && (

View File

@ -13,23 +13,18 @@ import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes } from 'lib/theme'
import { useMemo } from 'react'
import { linter, lintGutter } from '@codemirror/lint'
import { Selections, useStore } from 'useStore'
import { useStore } from 'useStore'
import { processCodeMirrorRanges } from 'lib/selections'
import { LanguageServerClient } from 'editor/lsp'
import kclLanguage from 'editor/lsp/language'
import { isTauri } from 'lib/isTauri'
import { useParams } from 'react-router-dom'
import { writeTextFile } from '@tauri-apps/api/fs'
import { PROJECT_ENTRYPOINT } from 'lib/tauriFS'
import { toast } from 'react-hot-toast'
import {
EditorView,
addLineHighlight,
lineHighlightField,
} from 'editor/highlightextension'
import { isOverlap, roundOff } from 'lib/utils'
import { EditorView, lineHighlightField } from 'editor/highlightextension'
import { roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
export const editorShortcutMeta = {
formatCode: {
@ -47,38 +42,22 @@ export const TextEditor = ({
}: {
theme: Themes.Light | Themes.Dark
}) => {
const pathParams = useParams()
const {
code,
deferredSetCode,
editorView,
engineCommandManager,
formatCode,
isLSPServerReady,
selectionRanges,
selectionRangeTypeMap,
setEditorView,
setIsLSPServerReady,
setSelectionRanges,
} = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
editorView: s.editorView,
engineCommandManager: s.engineCommandManager,
formatCode: s.formatCode,
isLSPServerReady: s.isLSPServerReady,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges,
}))
const { editorView, isLSPServerReady, setEditorView, setIsLSPServerReady } =
useStore((s) => ({
editorView: s.editorView,
isLSPServerReady: s.isLSPServerReady,
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
}))
const { code, errors } = useKclContext()
const {
settings: {
context: { textWrapping },
},
} = useGlobalStateContext()
context: { selectionRanges, selectionRangeTypeMap },
send,
} = useModelingContext()
const { settings: { context: { textWrapping } = {} } = {} } =
useGlobalStateContext()
const { setCommandBarOpen } = useCommandsContext()
const { enable: convertEnabled, handleClick: convertCallback } =
useConvertToVariable()
@ -104,7 +83,7 @@ export const TextEditor = ({
// Here we initialize the plugin which will start the client.
// When we have multi-file support the name of the file will be a dep of
// this use memo, as well as the directory structure, which I think is
// a good setup becuase it will restart the client but not the server :)
// a good setup because it will restart the client but not the server :)
// We do not want to restart the server, its just wasteful.
const kclLSP = useMemo(() => {
let plugin = null
@ -123,78 +102,24 @@ export const TextEditor = ({
}, [lspClient, isLSPServerReady])
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
deferredSetCode(value)
if (isTauri() && pathParams.id) {
// Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, value).catch(
(err) => {
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')
}
)
}
if (editorView) {
editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) })
}
const onChange = (newCode: string) => {
kclManager.setCodeAndExecute(newCode)
} //, []);
const onUpdate = (viewUpdate: ViewUpdate) => {
if (!editorView) {
setEditorView(viewUpdate.view)
}
const ranges = viewUpdate.state.selection.ranges
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges,
selectionRangeTypeMap,
})
if (!eventInfo) return
const isChange =
ranges.length !== selectionRanges.codeBasedSelections.length ||
ranges.some(({ from, to }, i) => {
return (
from !== selectionRanges.codeBasedSelections[i].range[0] ||
to !== selectionRanges.codeBasedSelections[i].range[1]
)
})
if (!isChange) return
const codeBasedSelections: Selections['codeBasedSelections'] = ranges.map(
({ from, to }) => {
if (selectionRangeTypeMap[to]) {
return {
type: selectionRangeTypeMap[to],
range: [from, to],
}
}
return {
type: 'default',
range: [from, to],
}
}
send(eventInfo.modelingEvent)
eventInfo.engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
const idBasedSelections = codeBasedSelections
.map(({ type, range }) => {
const hasOverlap = Object.entries(
engineCommandManager?.sourceRangeMap || {}
).filter(([_, sourceRange]) => {
return isOverlap(sourceRange, range)
})
if (hasOverlap.length) {
return {
type,
id: hasOverlap[0][0],
}
}
})
.filter(Boolean) as any
engineCommandManager?.cusorsSelected({
otherSelections: [],
idBasedSelections,
})
setSelectionRanges({
otherSelections: [],
codeBasedSelections,
})
}
const editorExtensions = useMemo(() => {
@ -211,7 +136,7 @@ export const TextEditor = ({
{
key: editorShortcutMeta.formatCode.codeMirror,
run: () => {
formatCode()
kclManager.format()
return true
},
},
@ -235,7 +160,7 @@ export const TextEditor = ({
extensions.push(
lintGutter(),
linter((_view) => {
return kclErrToDiagnostic(useStore.getState().kclErrors)
return kclErrToDiagnostic(errors)
}),
interact({
rules: [
@ -274,7 +199,7 @@ export const TextEditor = ({
}
return extensions
}, [kclLSP, textWrapping])
}, [kclLSP, textWrapping, convertCallback])
return (
<div

View File

@ -1,105 +1,79 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
export const EqualAngle = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enableEqual, setEnableEqual] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'equalAngle'
)
setTransformInfos(theTransforms)
const _enableEqual =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnableEqual(_enableEqual)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
const { modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
programMemory,
})
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableEqual}
title="Parallel (or equal angle)"
className="group"
>
<ActionIcon
icon="parallel"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Parallel
</button>
export function equalAngleInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'equalAngle'
)
const enabled =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
transforms.every(Boolean)
return { enabled, transforms }
}
export function applyConstraintEqualAngle({
selectionRanges,
}: {
selectionRanges: Selections
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const { transforms } = equalAngleInfo({ selectionRanges })
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
})
return { modifiedAst, pathToNodeMap }
}

View File

@ -1,105 +1,83 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
export const EqualLength = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enableEqual, setEnableEqual] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'equalLength'
)
setTransformInfos(theTransforms)
const _enableEqual =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnableEqual(_enableEqual)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={() => {
if (!(transformInfos && ast)) return
const { modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
programMemory,
})
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableEqual}
className="group"
title="Equal Length"
>
<ActionIcon
icon="equal"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Equal Length
</button>
export function setEqualLengthInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'equalLength'
)
const enabled =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
transforms.every(Boolean)
return { enabled, transforms }
}
export function applyConstraintEqualLength({
selectionRanges,
}: {
selectionRanges: Selections
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const { transforms } = setEqualLengthInfo({ selectionRanges })
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
})
return { modifiedAst, pathToNodeMap }
// kclManager.updateAst(modifiedAst, true, {
// // callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,84 +1,57 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { Program, ProgramMemory, Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import {
TransformInfo,
PathToNodeMap,
getTransformInfos,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
export const HorzVert = ({
horOrVert,
}: {
export function horzVertInfo(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal'
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enableHorz, setEnableHorz] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, horOrVert)
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={() => {
if (!transformInfos || !ast) return
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableHorz}
className="group"
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
>
<ActionIcon
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
</button>
) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
selectionRanges,
kclManager.ast,
horOrVert
)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
return { enabled: _enableHorz, transforms: theTransforms }
}
export function applyConstraintHorzVert(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal',
ast: Program,
programMemory: ProgramMemory
): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = horzVertInfo(selectionRanges, horOrVert).transforms
return transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
}

View File

@ -1,11 +1,6 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import {
BinaryPart,
Value,
VariableDeclarator,
} from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -13,184 +8,170 @@ import {
} from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(GetInfoModal as any)
const getModalInfo = createInfoModal(GetInfoModal)
export const Intersect = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
const [forecdSelectionRanges, setForcedSelectionRanges] =
useState<typeof selectionRanges>()
useEffect(() => {
if (!ast) return
if (selectionRanges.codeBasedSelections.length < 2) {
setEnable(false)
setForcedSelectionRanges({ ...selectionRanges })
return
export function intersectInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
if (selectionRanges.codeBasedSelections.length < 2) {
return {
enabled: false,
transforms: [],
forcedSelectionRanges: { ...selectionRanges },
}
}
const previousSegment =
selectionRanges.codeBasedSelections.length > 1 &&
isLinesParallelAndConstrained(
ast,
programMemory,
selectionRanges.codeBasedSelections[0],
selectionRanges.codeBasedSelections[1]
)
const shouldUsePreviousSegment =
selectionRanges.codeBasedSelections?.[1]?.type !== 'line-end' &&
previousSegment &&
previousSegment.isParallelAndConstrained
const previousSegment =
selectionRanges.codeBasedSelections.length > 1 &&
isLinesParallelAndConstrained(
kclManager.ast,
kclManager.programMemory,
selectionRanges.codeBasedSelections[0],
selectionRanges.codeBasedSelections[1]
)
const shouldUsePreviousSegment =
selectionRanges.codeBasedSelections?.[1]?.type !== 'line-end' &&
previousSegment &&
previousSegment.isParallelAndConstrained
const _forcedSelectionRanges: typeof selectionRanges = {
...selectionRanges,
codeBasedSelections: [
selectionRanges.codeBasedSelections?.[0],
shouldUsePreviousSegment
? {
range: previousSegment.sourceRange,
type: 'line-end',
}
: selectionRanges.codeBasedSelections?.[1],
],
}
setForcedSelectionRanges(_forcedSelectionRanges)
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections:
_forcedSelectionRanges.codeBasedSelections.slice(1),
},
ast,
'intersect'
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean) &&
_forcedSelectionRanges?.codeBasedSelections?.[1]?.type === 'line-end'
setEnable(_enableEqual)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={async () => {
if (!(transformInfos && ast && forecdSelectionRanges)) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges: forecdSelectionRanges,
transformInfos,
programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: 'offset',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
// transform again but forcing certain values
const finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
selectionRanges: forecdSelectionRanges,
transformInfos,
programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
const _forcedSelectionRanges: typeof selectionRanges = {
...selectionRanges,
codeBasedSelections: [
selectionRanges.codeBasedSelections?.[0],
shouldUsePreviousSegment
? {
range: previousSegment.sourceRange,
type: 'line-end',
}
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title="Set Perpendicular Distance"
>
Set Perpendicular Distance
</button>
: selectionRanges.codeBasedSelections?.[1],
],
}
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: _forcedSelectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'intersect'
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean) &&
_forcedSelectionRanges?.codeBasedSelections?.[1]?.type === 'line-end'
return {
enabled: _enableEqual,
transforms: theTransforms,
forcedSelectionRanges: _forcedSelectionRanges,
}
}
export async function applyConstraintIntersect({
selectionRanges,
}: {
selectionRanges: Selections
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const { transforms, forcedSelectionRanges } = intersectInfo({
selectionRanges,
})
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: forcedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: 'offset',
})
if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
}
// transform again but forcing certain values
const finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges: forcedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap: _pathToNodeMap,
}
}

View File

@ -1,78 +1,63 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { Program, Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import {
TransformInfo,
PathToNodeMap,
getRemoveConstraintsTransforms,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
export const RemoveConstrainingValues = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enableHorz, setEnableHorz] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
try {
const theTransforms = getRemoveConstraintsTransforms(
selectionRanges,
ast,
'removeConstrainingValues'
)
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz)
} catch (e) {
console.error(e)
}
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={() => {
if (!transformInfos || !ast) return
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableHorz}
title="Remove Constraining Values"
>
Remove Constraining Values
</button>
export function removeConstrainingValuesInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
try {
const transforms = getRemoveConstraintsTransforms(
selectionRanges,
kclManager.ast,
'removeConstrainingValues'
)
const enabled = isAllTooltips && transforms.every(Boolean)
return { enabled, transforms }
} catch (e) {
console.error(e)
return { enabled: false, transforms: [] }
}
}
export function applyRemoveConstrainingValues({
selectionRanges,
}: {
selectionRanges: Selections
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const { transforms } = removeConstrainingValuesInfo({ selectionRanges })
return transformAstSketchLines({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
}

View File

@ -1,145 +1,152 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { BinaryPart, Program, Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import {
TransformInfo,
getTransformInfos,
transformAstSketchLines,
ConstraintType,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { SetAngleLengthModal } from '../SetAngleLengthModal'
import {
SetAngleLengthModal,
createSetAngleLengthModal,
} from '../SetAngleLengthModal'
import {
createIdentifier,
createVariableDeclaration,
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(SetAngleLengthModal as any)
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
type ButtonType = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
type Constraint = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
const buttonLabels: Record<ButtonType, string> = {
xAbs: 'Set distance from X Axis',
yAbs: 'Set distance from Y Axis',
snapToYAxis: 'Snap To Y Axis',
snapToXAxis: 'Snap To X Axis',
}
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const disType: ConstraintType =
buttonType === 'xAbs' || buttonType === 'yAbs'
? buttonType
: buttonType === 'snapToYAxis'
export function absDistanceInfo({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: Constraint
}) {
const disType =
constraint === 'xAbs' || constraint === 'yAbs'
? constraint
: constraint === 'snapToYAxis'
? 'xAbs'
: 'yAbs'
const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, disType)
setTransformInfos(theTransforms)
const enableY =
disType === 'yAbs' &&
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis' // select the x axis to set the distance from it i.e. y
const enableX =
disType === 'xAbs' &&
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis' // select the y axis to set the distance from it i.e. x
const _enableHorz =
isAllTooltips &&
theTransforms.every(Boolean) &&
selectionRanges.codeBasedSelections.length === 1 &&
(enableX || enableY)
setEnableAngLen(_enableHorz)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
const isAlign = buttonType === 'snapToYAxis' || buttonType === 'snapToXAxis'
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
try {
let forceVal = valueUsedInTransform || 0
const { valueNode, variableName, newVariableInsertIndex, sign } =
await (!isAlign &&
getModalInfo({
value: forceVal,
valueName: disType === 'yAbs' ? 'yDis' : 'xDis',
} as any))
let finalValue = isAlign
? createIdentifier('_0')
: removeDoubleNegatives(valueNode, sign, variableName)
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
console.log('e', e)
}
}}
disabled={!enableAngLen}
title={buttonLabels[buttonType]}
>
{buttonLabels[buttonType]}
</button>
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(selectionRanges, kclManager.ast, disType)
const enableY =
disType === 'yAbs' &&
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis' // select the x axis to set the distance from it i.e. y
const enableX =
disType === 'xAbs' &&
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis' // select the y axis to set the distance from it i.e. x
const enabled =
isAllTooltips &&
transforms.every(Boolean) &&
selectionRanges.codeBasedSelections.length === 1 &&
(enableX || enableY)
return { enabled, transforms }
}
export async function applyConstraintAbsDistance({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'xAbs' | 'yAbs'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = absDistanceInfo({
selectionRanges,
constraint,
}).transforms
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
let forceVal = valueUsedInTransform || 0
const { valueNode, variableName, newVariableInsertIndex, sign } =
await getModalInfo({
value: forceVal,
valueName: constraint === 'yAbs' ? 'yDis' : 'xDis',
})
let finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return { modifiedAst: _modifiedAst, pathToNodeMap }
}
export function applyConstraintAxisAlign({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'snapToYAxis' | 'snapToXAxis'
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = absDistanceInfo({
selectionRanges,
constraint,
}).transforms
let finalValue = createIdentifier('_0')
return transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
}

View File

@ -1,155 +1,135 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import {
BinaryPart,
Value,
VariableDeclarator,
} from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(GetInfoModal as any)
const getModalInfo = createInfoModal(GetInfoModal)
export const SetAngleBetween = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'setAngleBetween'
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges,
transformInfos,
programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
const finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title="Set Angle Between"
>
Set Angle Between
</button>
export function angleBetweenInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'setAngleBetween'
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
return { enabled: _enableEqual, transforms: theTransforms }
}
export async function applyConstraintAngleBetween({
selectionRanges,
}: // constraint,
{
selectionRanges: Selections
// constraint: 'setHorzDistance' | 'setVertDistance'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = angleBetweenInfo({ selectionRanges }).transforms
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
}
const finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap: _pathToNodeMap,
}
}

View File

@ -1,188 +1,170 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import {
BinaryPart,
Value,
VariableDeclarator,
} from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
ConstraintType,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
import { Selections } from 'lib/selections'
const getModalInfo = create(GetInfoModal as any)
const getModalInfo = createInfoModal(GetInfoModal)
type ButtonType =
| 'setHorzDistance'
| 'setVertDistance'
| 'alignEndsHorizontally'
| 'alignEndsVertically'
const buttonLabels: Record<ButtonType, string> = {
setHorzDistance: 'Set Horizontal Distance',
setVertDistance: 'Set Vertical Distance',
alignEndsHorizontally: 'Align Ends Horizontally',
alignEndsVertically: 'Align Ends Vertically',
}
export const SetHorzVertDistance = ({
buttonType,
export function horzVertDistanceInfo({
selectionRanges,
constraint,
}: {
buttonType: ButtonType
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const constraint: ConstraintType =
buttonType === 'setHorzDistance' || buttonType === 'setVertDistance'
? buttonType
: buttonType === 'alignEndsHorizontally'
? 'setVertDistance'
: 'setHorzDistance'
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
constraint
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
const isAlign =
buttonType === 'alignEndsHorizontally' ||
buttonType === 'alignEndsVertically'
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges,
transformInfos,
programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await (!isAlign &&
getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName:
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
let finalValue = isAlign
? createLiteral(0)
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title={buttonLabels[buttonType]}
>
{buttonLabels[buttonType]}
</button>
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
constraint
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
return { enabled: _enableEqual, transforms: theTransforms }
}
export async function applyConstraintHorzVertDistance({
selectionRanges,
constraint,
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
isAlign = false,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
isAlign?: false
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = horzVertDistanceInfo({
selectionRanges,
constraint,
}).transforms
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any)
if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
} else {
let finalValue = isAlign
? createLiteral(0)
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
}
}
export function applyConstraintHorzVertAlign({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = horzVertDistanceInfo({
selectionRanges,
constraint,
}).transforms
let finalValue = createLiteral(0)
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceValueUsedInTransform: finalValue,
})
return {
modifiedAst: modifiedAst,
pathToNodeMap,
}
}

View File

@ -1,17 +1,19 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { toolTips } from '../../useStore'
import { Selections } from 'lib/selections'
import { BinaryPart, Program, Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import {
TransformInfo,
PathToNodeMap,
getTransformInfos,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { SetAngleLengthModal } from '../SetAngleLengthModal'
import {
SetAngleLengthModal,
createSetAngleLengthModal,
} from '../SetAngleLengthModal'
import {
createBinaryExpressionWithUnary,
createIdentifier,
@ -19,141 +21,123 @@ import {
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(SetAngleLengthModal as any)
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
type ButtonType = 'setAngle' | 'setLength'
const buttonLabels: Record<ButtonType, string> = {
setAngle: 'Set Angle',
setLength: 'Set Length',
}
export const SetAngleLength = ({
angleOrLength,
export function setAngleLengthInfo({
selectionRanges,
angleOrLength = 'setLength',
}: {
angleOrLength: ButtonType
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, angleOrLength)
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableAngLen(_enableHorz)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
try {
const isReferencingYAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis'
const isReferencingYAxisAngle =
isReferencingYAxis && angleOrLength === 'setAngle'
const isReferencingXAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis'
const isReferencingXAxisAngle =
isReferencingXAxis && angleOrLength === 'setAngle'
let forceVal = valueUsedInTransform || 0
let calcIdentifier = createIdentifier('_0')
if (isReferencingYAxisAngle) {
calcIdentifier = createIdentifier(forceVal < 0 ? '_270' : '_90')
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
} else if (isReferencingXAxisAngle) {
calcIdentifier = createIdentifier(
Math.abs(forceVal) > 90 ? '_180' : '_0'
)
forceVal =
Math.abs(forceVal) > 90
? normaliseAngle(forceVal - 180)
: forceVal
}
const { valueNode, variableName, newVariableInsertIndex, sign } =
await getModalInfo({
value: forceVal,
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true,
} as any)
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
if (
isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name !== '_0')
) {
finalValue = createBinaryExpressionWithUnary([
calcIdentifier,
finalValue,
])
}
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
console.log('e', e)
}
}}
disabled={!enableAngLen}
title={buttonLabels[angleOrLength]}
>
{buttonLabels[angleOrLength]}
</button>
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
selectionRanges,
kclManager.ast,
angleOrLength
)
const enabled = isAllTooltips && transforms.every(Boolean)
return { enabled, transforms }
}
export async function applyConstraintAngleLength({
selectionRanges,
angleOrLength = 'setLength',
}: {
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const { transforms } = setAngleLengthInfo({ selectionRanges, angleOrLength })
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
try {
const isReferencingYAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis'
const isReferencingYAxisAngle =
isReferencingYAxis && angleOrLength === 'setAngle'
const isReferencingXAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis'
const isReferencingXAxisAngle =
isReferencingXAxis && angleOrLength === 'setAngle'
let forceVal = valueUsedInTransform || 0
let calcIdentifier = createIdentifier('_0')
if (isReferencingYAxisAngle) {
calcIdentifier = createIdentifier(forceVal < 0 ? '_270' : '_90')
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
} else if (isReferencingXAxisAngle) {
calcIdentifier = createIdentifier(Math.abs(forceVal) > 90 ? '_180' : '_0')
forceVal =
Math.abs(forceVal) > 90 ? normaliseAngle(forceVal - 180) : forceVal
}
const { valueNode, variableName, newVariableInsertIndex, sign } =
await getModalInfo({
value: forceVal,
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true,
})
let finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
if (
isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name !== '_0')
) {
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
}
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
} catch (e) {
console.log('erorr', e)
throw e
}
}

View File

@ -0,0 +1,229 @@
/* Adapted from https://github.com/argyleink/gui-challenges/blob/main/tooltips/tool-tip.css */
.tooltip {
/* internal CSS vars */
--_delay: 200ms;
--_p-inline: 1ch;
--_p-block: 4px;
--_triangle-size: 7px;
/* --_bg: hsl(0 0% 20%); */
--_bg: var(--chalkboard-10);
--_shadow-alpha: 20%;
/* Used to power spacing and layout for RTL languages */
--isRTL: -1;
/* Using conic gradients to get a clear tip triangle */
--_bottom-tip: conic-gradient(
from -30deg at bottom,
#0000,
#000 1deg 60deg,
#0000 61deg
)
bottom / 100% 50% no-repeat;
--_top-tip: conic-gradient(
from 150deg at top,
#0000,
#000 1deg 60deg,
#0000 61deg
)
top / 100% 50% no-repeat;
--_right-tip: conic-gradient(
from -120deg at right,
#0000,
#000 1deg 60deg,
#0000 61deg
)
right / 50% 100% no-repeat;
--_left-tip: conic-gradient(
from 60deg at left,
#0000,
#000 1deg 60deg,
#0000 61deg
)
left / 50% 100% no-repeat;
pointer-events: none;
user-select: none;
/* The parts that will be transitioned */
opacity: 0;
transform: translate(var(--_x, 0), var(--_y, 0));
transition: transform 0.15s ease-out, opacity 0.11s ease-out;
position: absolute;
z-index: 1;
inline-size: max-content;
max-inline-size: 25ch;
text-align: start;
font-family: var(--mono-font-family);
text-transform: none;
font-size: 0.9rem;
font-weight: normal;
line-height: initial;
letter-spacing: 0;
padding: var(--_p-block) var(--_p-inline);
margin: 0;
border-radius: 3px;
background: var(--_bg);
@apply text-chalkboard-110;
will-change: filter;
filter: drop-shadow(0 1px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
drop-shadow(0 6px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}
:global(.dark) .tooltip {
--_bg: var(--chalkboard-110);
@apply text-chalkboard-10;
}
/* TODO we don't support a light theme yet */
/* @media (prefers-color-scheme: light) {
.tooltip {
--_bg: white;
--_shadow-alpha: 15%;
}
} */
.tooltip:dir(rtl) {
--isRTL: 1;
}
/* :has and :is are pretty fresh CSS pseudo-selectors, may not see full support */
:has(> .tooltip) {
position: relative;
}
:is(:hover, :focus-visible, :active) > .tooltip {
opacity: 1;
transition-delay: var(--_delay);
}
:is(:focus, :focus-visible, :focus-within) > .tooltip {
--_delay: 0 !important;
}
/* prepend some prose for screen readers only */
.tooltip::before {
content: '; Has tooltip: ';
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* tooltip shape is a pseudo element so we can cast a shadow */
.tooltip::after {
content: '';
background: var(--_bg);
position: absolute;
z-index: -1;
inset: 0;
mask: var(--_tip);
}
.tooltip.top,
.tooltip.blockStart,
.tooltip.bottom,
.tooltip.blockEnd {
text-align: center;
}
/* TOP || BLOCK-START */
.tooltip.top,
.tooltip.blockStart {
inset-inline-start: 50%;
inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
.tooltip.top::after,
.tooltip.tooltip.blockStart::after {
--_tip: var(--_bottom-tip);
inset-block-end: calc(var(--_triangle-size) * -1);
border-block-end: var(--_triangle-size) solid transparent;
}
/* RIGHT || INLINE-END */
.tooltip.right,
.tooltip.inlineEnd {
inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
.tooltip.right::after,
.tooltip.tooltip.inlineEnd::after {
--_tip: var(--_left-tip);
inset-inline-start: calc(var(--_triangle-size) * -1);
border-inline-start: var(--_triangle-size) solid transparent;
}
.tooltip.right:dir(rtl)::after,
.tooltip.inlineEnd:dir(rtl)::after {
--_tip: var(--_right-tip);
}
/* BOTTOM || BLOCK-END */
.tooltip.bottom,
.tooltip.blockEnd {
inset-inline-start: 50%;
inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
.tooltip.bottom::after,
.tooltip.tooltip.blockEnd::after {
--_tip: var(--_top-tip);
inset-block-start: calc(var(--_triangle-size) * -1);
border-block-start: var(--_triangle-size) solid transparent;
}
/* LEFT || INLINE-START */
.tooltip.left,
.tooltip.inlineStart {
inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
.tooltip.left::after,
.tooltip.tooltip.inlineStart::after {
--_tip: var(--_right-tip);
inset-inline-end: calc(var(--_triangle-size) * -1);
border-inline-end: var(--_triangle-size) solid transparent;
}
.tooltip.left:dir(rtl)::after,
.tooltip.inlineStart:dir(rtl)::after {
--_tip: var(--_left-tip);
}
@media (prefers-reduced-motion: no-preference) {
/* TOP || BLOCK-START */
:has(> :is(.tooltip.top, .tooltip.blockStart)):not(:hover, :active) .tooltip {
--_y: 3px;
}
/* RIGHT || INLINE-END */
:has(> :is(.tooltip.right, .tooltip.inlineEnd)):not(:hover, :active)
.tooltip {
--_x: calc(var(--isRTL) * -3px * -1);
}
/* BOTTOM || BLOCK-END */
:has(> :is(.tooltip.bottom, .tooltip.blockEnd)):not(:hover, :active)
.tooltip {
--_y: -3px;
}
/* BOTTOM || BLOCK-END */
:has(> :is(.tooltip.left, .tooltip.inlineStart)):not(:hover, :active)
.tooltip {
--_x: calc(var(--isRTL) * 3px * -1);
}
}

View File

@ -0,0 +1,37 @@
// We do use all the classes in this file currently, but we
// index into them with styles[position], which CSS Modules doesn't pick up.
// eslint-disable-next-line css-modules/no-unused-class
import styles from './Tooltip.module.css'
interface TooltipProps extends React.PropsWithChildren {
position?:
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'blockStart'
| 'blockEnd'
| 'inlineStart'
| 'inlineEnd'
className?: string
delay?: number
}
export default function Tooltip({
children,
position = 'top',
className,
delay = 200,
}: TooltipProps) {
return (
<div
// @ts-ignore while awaiting merge of this PR for support of "inert" https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60822
inert="true"
role="tooltip"
className={styles.tooltip + ' ' + styles[position] + ' ' + className}
style={{ '--_delay': delay + 'ms' } as React.CSSProperties}
>
{children}
</div>
)
}

View File

@ -1,6 +1,11 @@
import { fireEvent, render, screen } from '@testing-library/react'
import UserSidebarMenu from './UserSidebarMenu'
import { BrowserRouter } from 'react-router-dom'
import {
Route,
RouterProvider,
createMemoryRouter,
createRoutesFromElements,
} from 'react-router-dom'
import { Models } from '@kittycad/lib'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar'
@ -93,11 +98,24 @@ describe('UserSidebarMenu tests', () => {
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
// We have to use a memory router in the testing environment,
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
const router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/file/:id"
element={
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
}
/>
),
{
initialEntries: ['/file/new'],
initialIndex: 0,
}
)
return <RouterProvider router={router} />
}

View File

@ -1,24 +1,28 @@
import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import {
faBars,
faBug,
faGear,
faSignOutAlt,
} from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useState } from 'react'
import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
import { Models } from '@kittycad/lib'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => {
const location = useLocation()
const filePath = useAbsoluteFilePath()
const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate()
const {
auth: { send },
} = useGlobalStateContext()
const send = useGlobalStateContext()?.auth?.send
// Fallback logic for displaying user's "name":
// 1. user.name
@ -127,23 +131,30 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
// since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it
close()
navigate(
(location.pathname.endsWith('/')
? location.pathname.slice(0, -1)
: location.pathname) + paths.SETTINGS
)
const targetPath = location.pathname.includes(paths.FILE)
? filePath + paths.SETTINGS
: paths.HOME + paths.SETTINGS
navigate(targetPath)
}}
>
Settings
</ActionButton>
<ActionButton
Element="link"
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/discussions"
icon={{ icon: faGithub }}
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
>
Request a feature
</ActionButton>
<ActionButton
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/issues/new"
icon={{ icon: faBug }}
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
>
Report a bug
</ActionButton>
<ActionButton
Element="button"
onClick={() => send('Log out')}

View File

@ -0,0 +1,63 @@
import { Dialog } from '@headlessui/react'
import { useState } from 'react'
import { ActionButton } from './ActionButton'
import { faX } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclSinglton'
export function WasmErrBanner() {
const [isBannerDismissed, setBannerDismissed] = useState(false)
const { wasmInitFailed } = useKclContext()
if (!wasmInitFailed) return null
return (
<Dialog
className="fixed inset-0 top-auto z-50 bg-warn-20 text-warn-80 px-8 py-4"
open={!isBannerDismissed}
onClose={() => ({})}
>
<Dialog.Panel className="max-w-3xl mx-auto">
<div className="flex gap-2 justify-between items-start">
<h2 className="text-xl font-bold mb-4">
Problem with our WASM blob :(
</h2>
<ActionButton
Element="button"
onClick={() => setBannerDismissed(true)}
icon={{
icon: faX,
bgClassName:
'bg-warn-70 hover:bg-warn-80 dark:bg-warn-70 dark:hover:bg-warn-80',
iconClassName:
'text-warn-10 group-hover:text-warn-10 dark:text-warn-10 dark:group-hover:text-warn-10',
}}
className="!p-0 !bg-transparent !border-transparent"
/>
</div>
<p>
<a
href="https://webassembly.org/"
rel="noopener noreferrer"
target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
>
WASM or web assembly
</a>{' '}
is core part of how our app works. It might because you OS is not
up-to-date. If you're able to update your OS to a later version, try
that. If not create an issue on{' '}
<a
href="https://github.com/KittyCAD/modeling-app"
rel="noopener noreferrer"
target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
>
our Github
</a>
.
</p>
</Dialog.Panel>
</Dialog>
)
}

View File

@ -108,8 +108,8 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
break
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
messageString += message
// console.log(messageString)
return
})

View File

@ -26,7 +26,7 @@ export class Codec {
}
}
// FIXME: tracing effiency
// FIXME: tracing efficiency
export class IntoServer
extends Queue<Uint8Array>
implements AsyncGenerator<Uint8Array, never, void>

View File

@ -9,6 +9,7 @@ import { LanguageServerClient } from '.'
import { kclPlugin } from './plugin'
import type * as LSP from 'vscode-languageserver-protocol'
import { parser as jsParser } from '@lezer/javascript'
import { EditorState } from '@uiw/react-codemirror'
const data = defineLanguageFacet({})
@ -22,7 +23,25 @@ export default function kclLanguage(options: LanguageOptions): LanguageSupport {
// For now let's use the javascript parser.
// It works really well and has good syntax highlighting.
// We can use our lsp for the rest.
const lang = new Language(data, jsParser, [], 'kcl')
const lang = new Language(
data,
jsParser,
[
EditorState.languageData.of(() => [
{
// https://codemirror.net/docs/ref/#commands.CommentTokens
commentTokens: {
line: '//',
block: {
open: '/*',
close: '*/',
},
},
},
]),
],
'kcl'
)
// Create our supporting extension.
const kclLsp = kclPlugin({

View File

@ -13,6 +13,7 @@ import {
CompletionItemKind,
CompletionTriggerKind,
} from 'vscode-languageserver-protocol'
import debounce from 'debounce-promise'
import type {
Completion,
@ -53,14 +54,11 @@ export class LanguageServerPlugin implements PluginValue {
private languageId: string
private documentVersion: number
private changesTimeout: number
constructor(private view: EditorView, private allowHTMLContent: boolean) {
this.client = this.view.state.facet(client)
this.documentUri = this.view.state.facet(documentUri)
this.languageId = this.view.state.facet(languageId)
this.documentVersion = 0
this.changesTimeout = 0
this.client.attachPlugin(this)
@ -71,12 +69,10 @@ export class LanguageServerPlugin implements PluginValue {
update({ docChanged }: ViewUpdate) {
if (!docChanged) return
if (this.changesTimeout) clearTimeout(this.changesTimeout)
this.changesTimeout = window.setTimeout(() => {
this.sendChange({
documentText: this.view.state.doc.toString(),
})
}, changesDelay)
this.sendChange({
documentText: this.view.state.doc.toString(),
})
}
destroy() {
@ -99,14 +95,32 @@ export class LanguageServerPlugin implements PluginValue {
async sendChange({ documentText }: { documentText: string }) {
if (!this.client.ready) return
if (documentText.length > 5000) {
// Clear out the text it thinks we have, large documents will throw a stack error.
// This is obviously not a good fix but it works for now til we figure
// out the stack limits in wasm and also rewrite the parser.
// Since this is only for hover and completions it will be fine,
// completions will still work for stdlib but hover will not.
// That seems like a fine trade-off for a working editor for the time
// being.
documentText = ''
}
try {
await this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
debounce(
() => {
return this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
},
contentChanges: [{ text: documentText }],
})
},
contentChanges: [{ text: documentText }],
})
changesDelay,
{ leading: true }
)
} catch (e) {
console.error(e)
}

View File

@ -1,8 +1,5 @@
// all web app environment variables are defined here, jest doesn't like import.meta.env so centralising them here
// allows us to mock them in one place, see src/setupTests.ts, it pulls the variable names and valuse from .env.development
// note the exported variable name must match the env var name for the jest mocks to work
// i.e. const VITE_MY_VAR = import.meta.env.VITE_MY_VAR
// Maybe this file should be generated in a GHA from .env.development?
// env vars were centralised so they could be mocked in jest
// but isn't needed anymore with vite, so is now just a convention
export const VITE_KC_API_WS_MODELING_URL = import.meta.env
.VITE_KC_API_WS_MODELING_URL
@ -12,3 +9,4 @@ export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
.VITE_KC_CONNECTION_TIMEOUT_MS
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
export const TEST = import.meta.env.TEST
export const DEV = import.meta.env.DEV

View File

@ -0,0 +1,12 @@
import { BROWSER_FILE_NAME, IndexLoaderData, paths } from 'Router'
import { useRouteLoaderData } from 'react-router-dom'
export function useAbsoluteFilePath() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
return (
paths.FILE +
'/' +
encodeURIComponent(routeData?.file?.path || BROWSER_FILE_NAME)
)
}

View File

@ -1,297 +0,0 @@
// needed somewhere to dump this logic,
// Once we have xState this should be removed
import { useStore, Selections } from 'useStore'
import { useEffect, useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
import { Models } from '@kittycad/lib/dist/types/src'
import { isReducedMotion } from 'lang/util'
import { isOverlap } from 'lib/utils'
interface DefaultPlanes {
xy: string
// TODO re-enable
// yz: string
// xz: string
}
export function useAppMode() {
const {
guiMode,
setGuiMode,
selectionRanges,
engineCommandManager,
selectionRangeTypeMap,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
engineCommandManager: s.engineCommandManager,
selectionRangeTypeMap: s.selectionRangeTypeMap,
}))
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
useEffect(() => {
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'selectFace' &&
engineCommandManager
) {
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
}
createAndShowPlanes()
}
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'enterSketchEdit' &&
engineCommandManager
) {
const enableSketchMode = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
// TODO figure out the plane to use based on the sketch
// maybe it's easier to make a new plane than rely on the defaults
await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: localDefaultPlanes.xy,
ortho: true,
animated: !isReducedMotion(),
},
})
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
)
await Promise.all(proms)
}
enableSketchMode()
setGuiMode({
...guiMode,
sketchMode: 'sketchEdit',
})
}
if (guiMode.mode !== 'sketch' && defaultPlanes) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
}
if (guiMode.mode === 'default') {
const pathId =
engineCommandManager &&
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
if (pathId) {
setGuiMode({
mode: 'canEditSketch',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId,
})
}
} else if (guiMode.mode === 'canEditSketch') {
if (
!engineCommandManager ||
!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
) {
setGuiMode({
mode: 'default',
})
}
}
}, [
guiMode,
guiMode.mode,
engineCommandManager,
selectionRanges,
selectionRangeTypeMap,
])
useEffect(() => {
const unSub = engineCommandManager?.subscribeTo({
event: 'select_with_point',
callback: async ({ data }) => {
if (!data.entity_id) return
if (!defaultPlanes) return
if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
// user clicked something else in the scene
return
}
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
{
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: data.entity_id,
ortho: true,
animated: !isReducedMotion(),
},
}
)
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
const sketchUuid = uuidv4()
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
})
)
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
})
)
const res = await Promise.all(proms)
console.log('res', res)
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId: sketchUuid,
})
console.log('sketchModeResponse', sketchModeResponse)
},
})
return unSub
}, [engineCommandManager, defaultPlanes])
}
async function createPlane(
engineCommandManager: EngineCommandManager,
{
x_axis,
y_axis,
color,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
}
) {
const planeId = uuidv4()
await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
size: 60,
origin: { x: 0, y: 0, z: 0 },
x_axis,
y_axis,
clobber: false,
},
cmd_id: planeId,
})
await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
plane_id: planeId,
color,
},
cmd_id: uuidv4(),
})
return planeId
}
function setDefaultPlanesHidden(
engineCommandManager: EngineCommandManager | undefined,
defaultPlanes: DefaultPlanes,
hidden: boolean
) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: hidden,
},
})
})
}
async function initDefaultPlanes(
engineCommandManager: EngineCommandManager
): Promise<DefaultPlanes> {
const xy = await createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
})
// TODO re-enable
// const yz = createPlane(engineCommandManager, {
// x_axis: { x: 0, y: 1, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
// })
// const xz = createPlane(engineCommandManager, {
// x_axis: { x: 1, y: 0, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
// })
return { xy }
}
function isCursorInSketchCommandRange(
artifactMap: ArtifactMap,
selectionRanges: Selections
): string | false {
const overlapingEntries = Object.entries(artifactMap || {}).filter(
([id, artifact]) =>
selectionRanges.codeBasedSelections.some(
(selection) =>
Array.isArray(selection?.range) &&
Array.isArray(artifact?.range) &&
isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' ||
artifact.commandType === 'close_path')
)
)
return overlapingEntries.length && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId
: overlapingEntries.find(
([, artifact]) => artifact.commandType === 'start_path'
)?.[0] || false
}

View File

@ -1,18 +1,16 @@
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'
export function useEngineConnectionSubscriptions() {
const {
engineCommandManager,
setCursor2,
setHighlightRange,
highlightRange,
} = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
setCursor2: s.setCursor2,
const { setHighlightRange, highlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
const { send, context } = useModelingContext()
useEffect(() => {
if (!engineCommandManager) return
@ -21,7 +19,7 @@ export function useEngineConnectionSubscriptions() {
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange =
engineCommandManager.sourceRangeMap[data.entity_id]
engineCommandManager.artifactMap?.[data.entity_id]?.range
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
@ -33,18 +31,21 @@ export function useEngineConnectionSubscriptions() {
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
callback: async (engineEvent) => {
const event = await getEventForSelectWithPoint(engineEvent, {
sketchEnginePathId: context.sketchEnginePathId,
})
event && send(event)
},
})
return () => {
unSubHover()
unSubClick()
}
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
}, [
engineCommandManager,
setHighlightRange,
highlightRange,
context.sketchEnginePathId,
])
}

View File

@ -0,0 +1,6 @@
import { FileContext } from 'components/FileMachineProvider'
import { useContext } from 'react'
export const useFileContext = () => {
return useContext(FileContext)
}

View File

@ -0,0 +1,6 @@
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
import { useContext } from 'react'
export const useModelingContext = () => {
return useContext(ModelingMachineContext)
}

View File

@ -1,53 +1,90 @@
import { useLayoutEffect } from 'react'
import { _executor } from '../lang/executor'
import { useLayoutEffect, useEffect, useRef } from 'react'
import { parse } from '../lang/wasm'
import { useStore } from '../useStore'
import { EngineCommandManager } from '../lang/std/engineConnection'
import { engineCommandManager } from '../lang/std/engineConnection'
import { deferExecution } from 'lib/utils'
import { kclManager } from 'lang/KclSinglton'
export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
token?: string
) {
const {
setEngineCommandManager,
setMediaStream,
setIsStreamReady,
setStreamDimensions,
executeCode,
streamDimensions,
} = useStore((s) => ({
setEngineCommandManager: s.setEngineCommandManager,
setMediaStream: s.setMediaStream,
setIsStreamReady: s.setIsStreamReady,
setStreamDimensions: s.setStreamDimensions,
executeCode: s.executeCode,
streamDimensions: s.streamDimensions,
}))
const streamWidth = streamRef?.current?.offsetWidth
const streamHeight = streamRef?.current?.offsetHeight
const hasSetNonZeroDimensions = useRef<boolean>(false)
useLayoutEffect(() => {
// Load the engine command manager once with the initial width and height,
// then we do not want to reload it.
const { width: quadWidth, height: quadHeight } = getDimensions(
streamWidth,
streamHeight
)
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
engineCommandManager.start({
setMediaStream,
setIsStreamReady,
width: quadWidth,
height: quadHeight,
executeCode: (code?: string) => {
const _ast = parse(code || kclManager.code)
return kclManager.executeAst(_ast, true)
},
token,
})
setStreamDimensions({
streamWidth: quadWidth,
streamHeight: quadHeight,
})
hasSetNonZeroDimensions.current = true
}
}, [streamRef?.current?.offsetWidth, streamRef?.current?.offsetHeight])
useEffect(() => {
const handleResize = deferExecution(() => {
const { width, height } = getDimensions(
streamRef?.current?.offsetWidth,
streamRef?.current?.offsetHeight
)
if (
streamDimensions.streamWidth !== width ||
streamDimensions.streamHeight !== height
) {
engineCommandManager.handleResize({
streamWidth: width,
streamHeight: height,
})
setStreamDimensions({
streamWidth: width,
streamHeight: height,
})
}
}, 500)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
}
function getDimensions(streamWidth?: number, streamHeight?: number) {
const width = streamWidth ? streamWidth : 0
const quadWidth = Math.round(width / 4) * 4
const height = streamHeight ? streamHeight : 0
const quadHeight = Math.round(height / 4) * 4
useLayoutEffect(() => {
setStreamDimensions({
streamWidth: quadWidth,
streamHeight: quadHeight,
})
if (!width || !height) return
const eng = new EngineCommandManager({
setMediaStream,
setIsStreamReady,
width: quadWidth,
height: quadHeight,
token,
})
setEngineCommandManager(eng)
eng.waitForReady.then(() => {
executeCode()
})
return () => {
eng?.tearDown()
}
}, [quadWidth, quadHeight])
return { width: quadWidth, height: quadHeight }
}

View File

@ -1,54 +1,47 @@
import { SetVarNameModal } from 'components/SetVarNameModal'
import {
SetVarNameModal,
createSetVarNameModal,
} from 'components/SetVarNameModal'
import { kclManager } from 'lang/KclSinglton'
import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react'
import { create } from 'react-modal-promise'
import { useStore } from 'useStore'
import { useModelingContext } from './useModelingContext'
const getModalInfo = create(SetVarNameModal as any)
const getModalInfo = createSetVarNameModal(SetVarNameModal)
export function useConvertToVariable() {
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
(s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
})
)
const { context } = useModelingContext()
const [enable, setEnabled] = useState(false)
useEffect(() => {
if (!ast) return
const { isSafe, value } = isNodeSafeToReplace(
ast,
selectionRanges.codeBasedSelections?.[0]?.range || []
kclManager.ast,
context.selectionRanges.codeBasedSelections?.[0]?.range || []
)
const canReplace = isSafe && value.type !== 'Identifier'
const isOnlyOneSelection = selectionRanges.codeBasedSelections.length === 1
const isOnlyOneSelection =
context.selectionRanges.codeBasedSelections.length === 1
const _enableHorz = canReplace && isOnlyOneSelection
setEnabled(_enableHorz)
}, [guiMode, selectionRanges])
}, [context.selectionRanges])
const handleClick = async () => {
if (!ast) return
try {
const { variableName } = await getModalInfo({
valueName: 'var',
} as any)
})
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
selectionRanges.codeBasedSelections[0].range,
kclManager.ast,
kclManager.programMemory,
context.selectionRanges.codeBasedSelections[0].range,
variableName
)
updateAst(_modifiedAst, true)
kclManager.updateAst(_modifiedAst, true)
} catch (e) {
console.log('e', e)
console.log('error', e)
}
}

View File

@ -4,6 +4,13 @@ import reportWebVitals from './reportWebVitals'
import { Toaster } from 'react-hot-toast'
import { Router } from './Router'
import { HotkeysProvider } from 'react-hotkeys-hook'
import { inspect } from '@xstate/inspect'
import { DEV } from 'env'
if (DEV)
inspect({
iframe: false,
})
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

450
src/lang/KclSinglton.tsx Normal file
View File

@ -0,0 +1,450 @@
import { executeAst, executeCode } from 'useStore'
import { Selections } from 'lib/selections'
import { KCLError } from './errors'
import {
EngineCommandManager,
engineCommandManager,
} from './std/engineConnection'
import { deferExecution } from 'lib/utils'
import {
initPromise,
parse,
PathToNode,
Program,
ProgramMemory,
recast,
} from 'lang/wasm'
import { bracket } from 'lib/exampleKcl'
import { createContext, useContext, useEffect, useState } from 'react'
import { getNodeFromPath } from './queryAst'
import { IndexLoaderData } from 'Router'
import { Params, useLoaderData } from 'react-router-dom'
import { isTauri } from 'lib/isTauri'
import { writeTextFile } from '@tauri-apps/api/fs'
import { toast } from 'react-hot-toast'
import { useParams } from 'react-router-dom'
const PERSIST_CODE_TOKEN = 'persistCode'
class KclManager {
private _code = bracket
private _ast: Program = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: [],
},
}
private _programMemory: ProgramMemory = {
root: {},
return: null,
}
private _logs: string[] = []
private _kclErrors: KCLError[] = []
private _isExecuting = false
private _wasmInitFailed = true
private _params: Params<string> = {}
engineCommandManager: EngineCommandManager
private _defferer = deferExecution((code: string) => {
const ast = this.safeParse(code)
if (!ast) return
try {
const fmtAndStringify = (ast: Program) =>
JSON.stringify(parse(recast(ast)))
const isAstTheSame = fmtAndStringify(ast) === fmtAndStringify(this._ast)
if (isAstTheSame) return
} catch (e) {
console.error(e)
}
this.executeAst(ast)
}, 600)
private _isExecutingCallback: (arg: boolean) => void = () => {}
private _codeCallBack: (arg: string) => void = () => {}
private _astCallBack: (arg: Program) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
private _executeCallback: () => void = () => {}
get ast() {
return this._ast
}
set ast(ast) {
this._ast = ast
this._astCallBack(ast)
}
get code() {
return this._code
}
set code(code) {
this._code = code
this._codeCallBack(code)
if (isTauri()) {
setTimeout(() => {
// Wait one event loop to give a chance for params to be set
// Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
this._params.id &&
writeTextFile(this._params.id, code).catch((err) => {
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')
})
})
} else {
localStorage.setItem(PERSIST_CODE_TOKEN, code)
}
}
get programMemory() {
return this._programMemory
}
set programMemory(programMemory) {
this._programMemory = programMemory
this._programMemoryCallBack(programMemory)
}
get defaultPlanes() {
return this?.engineCommandManager?.defaultPlanes
}
get logs() {
return this._logs
}
set logs(logs) {
this._logs = logs
this._logsCallBack(logs)
}
get kclErrors() {
return this._kclErrors
}
set kclErrors(kclErrors) {
this._kclErrors = kclErrors
this._kclErrorsCallBack(kclErrors)
}
get isExecuting() {
return this._isExecuting
}
set isExecuting(isExecuting) {
this._isExecuting = isExecuting
this._isExecutingCallback(isExecuting)
}
get wasmInitFailed() {
return this._wasmInitFailed
}
set wasmInitFailed(wasmInitFailed) {
this._wasmInitFailed = wasmInitFailed
this._wasmInitFailedCallback(wasmInitFailed)
}
setParams(params: Params<string>) {
this._params = params
}
constructor(engineCommandManager: EngineCommandManager) {
this.engineCommandManager = engineCommandManager
if (isTauri()) {
this.code = ''
return
}
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
// TODO #819 remove zustand persistence logic in a few months
// short term migration, shouldn't make a difference for tauri app users
// anyway since that's filesystem based.
const zustandStore = JSON.parse(localStorage.getItem('store') || '{}')
if (storedCode === null && zustandStore?.state?.code) {
this.code = zustandStore.state.code
localStorage.setItem(PERSIST_CODE_TOKEN, this._code)
zustandStore.state.code = ''
localStorage.setItem('store', JSON.stringify(zustandStore))
} else if (storedCode === null) {
console.log('stored brack thing')
this.code = bracket
} else {
this.code = storedCode
}
}
registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
setKclErrors,
setIsExecuting,
setWasmInitFailed,
}: {
setCode: (arg: string) => void
setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Program) => void
setLogs: (arg: string[]) => void
setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) {
this._codeCallBack = setCode
this._programMemoryCallBack = setProgramMemory
this._astCallBack = setAst
this._logsCallBack = setLogs
this._kclErrorsCallBack = setKclErrors
this._isExecutingCallback = setIsExecuting
this._wasmInitFailedCallback = setWasmInitFailed
}
registerExecuteCallback(callback: () => void) {
this._executeCallback = callback
}
safeParse(code: string): Program | null {
try {
const ast = parse(code)
this.kclErrors = []
return ast
} catch (e) {
console.error('error parsing code', e)
if (e instanceof KCLError) {
this.kclErrors = [e]
if (e.msg === 'file is empty') engineCommandManager.endSession()
}
return null
}
}
async ensureWasmInit() {
try {
await initPromise
if (this.wasmInitFailed) {
this.wasmInitFailed = false
}
} catch (e) {
this.wasmInitFailed = true
}
}
async executeAst(ast: Program = this._ast, updateCode = false) {
await this.ensureWasmInit()
this.isExecuting = true
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
})
this.isExecuting = false
this.logs = logs
this.kclErrors = errors
this.programMemory = programMemory
this.ast = { ...ast }
if (updateCode) {
this.code = recast(ast)
}
this._executeCallback()
}
async executeAstMock(ast: Program = this._ast, updateCode = false) {
await this.ensureWasmInit()
const newCode = recast(ast)
const newAst = this.safeParse(newCode)
if (!newAst) return
await this?.engineCommandManager?.waitForReady
if (updateCode) {
this.setCode(recast(ast))
}
this._ast = { ...newAst }
const { logs, errors, programMemory } = await executeAst({
ast: newAst,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
useFakeExecutor: true,
})
this._logs = logs
this._kclErrors = errors
this._programMemory = programMemory
}
async executeCode(code?: string) {
await this.ensureWasmInit()
await this?.engineCommandManager?.waitForReady
if (!this?.engineCommandManager?.planesInitialized()) return
const result = await executeCode({
engineCommandManager,
code: code || this._code,
lastAst: this._ast,
defaultPlanes: this.defaultPlanes,
force: false,
})
if (!result.isChange) return
const { logs, errors, programMemory, ast } = result
this.logs = logs
this.kclErrors = errors
this.programMemory = programMemory
this.ast = ast
if (code) this.code = code
}
setCode(code: string, shouldWriteFile = true) {
if (shouldWriteFile) {
// use the normal code setter
this.code = code
return
}
this._code = code
this._codeCallBack(code)
}
setCodeAndExecute(code: string, shouldWriteFile = true) {
this.setCode(code, shouldWriteFile)
if (code.trim()) {
this._defferer(code)
return
}
this._ast = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: [],
},
}
this._programMemory = {
root: {},
return: null,
}
this.engineCommandManager.endSession()
}
format() {
const ast = this.safeParse(this.code)
if (!ast) return
this.code = recast(ast)
}
// There's overlapping responsibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier.
// but should probably have think about which of the function to keep
async updateAst(
ast: Program,
execute: boolean,
optionalParams?: {
focusPath?: PathToNode
}
): Promise<Selections | null> {
const newCode = recast(ast)
const astWithUpdatedSource = this.safeParse(newCode)
if (!astWithUpdatedSource) return null
let returnVal: Selections | null = null
if (optionalParams?.focusPath) {
const { node } = getNodeFromPath<any>(
astWithUpdatedSource,
optionalParams?.focusPath
)
const { start, end } = node
if (!start || !end) return null
returnVal = {
codeBasedSelections: [
{
type: 'default',
range: [start, end],
},
],
otherSelections: [],
}
}
if (execute) {
// Call execute on the set ast.
await this.executeAst(astWithUpdatedSource, true)
} else {
// When we don't re-execute, we still want to update the program
// memory with the new ast. So we will hit the mock executor
// instead.
await this.executeAstMock(astWithUpdatedSource, true)
}
return returnVal
}
getPlaneId(axis: 'xy' | 'xz' | 'yz'): string {
return this.defaultPlanes[axis]
}
showPlanes() {
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false)
}
hidePlanes() {
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
}
export const kclManager = new KclManager(engineCommandManager)
const KclContext = createContext({
code: kclManager.code,
programMemory: kclManager.programMemory,
ast: kclManager.ast,
isExecuting: kclManager.isExecuting,
errors: kclManager.kclErrors,
logs: kclManager.logs,
wasmInitFailed: kclManager.wasmInitFailed,
})
export function useKclContext() {
return useContext(KclContext)
}
export function KclContextProvider({
children,
}: {
children: React.ReactNode
}) {
// If we try to use this component anywhere but under the paths.FILE route it will fail
// Because useLoaderData assumes we are on within it's context.
const { code: loadedCode } = useLoaderData() as IndexLoaderData
const [code, setCode] = useState(loadedCode || kclManager.code)
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false)
const [errors, setErrors] = useState<KCLError[]>([])
const [logs, setLogs] = useState<string[]>([])
const [wasmInitFailed, setWasmInitFailed] = useState(false)
useEffect(() => {
kclManager.registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
setKclErrors: setErrors,
setIsExecuting,
setWasmInitFailed,
})
}, [])
const params = useParams()
useEffect(() => {
kclManager.setParams(params)
}, [params])
return (
<KclContext.Provider
value={{
code,
programMemory,
ast,
isExecuting,
errors,
logs,
wasmInitFailed,
}}
>
{children}
</KclContext.Provider>
)
}

View File

@ -1,12 +1,11 @@
import { parser_wasm } from './abstractSyntaxTree'
import { KCLError } from './errors'
import { initPromise } from './rust'
import { initPromise, parse } from './wasm'
beforeAll(() => initPromise)
describe('testing AST', () => {
test('5 + 6', () => {
const result = parser_wasm('5 +6')
const result = parse('5 +6')
delete (result as any).nonCodeMeta
expect(result.body).toEqual([
{
@ -37,7 +36,7 @@ describe('testing AST', () => {
])
})
test('const myVar = 5', () => {
const { body } = parser_wasm('const myVar = 5')
const { body } = parse('const myVar = 5')
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -71,7 +70,7 @@ describe('testing AST', () => {
const code = `const myVar = 5
const newVar = myVar + 1
`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -142,44 +141,8 @@ const newVar = myVar + 1
})
describe('testing function declaration', () => {
test('fn funcN = () => {}', () => {
const { body } = parser_wasm('fn funcN = () => {}')
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 19,
kind: 'fn',
declarations: [
{
type: 'VariableDeclarator',
start: 3,
end: 19,
id: {
type: 'Identifier',
start: 3,
end: 8,
name: 'funcN',
},
init: {
type: 'FunctionExpression',
start: 11,
end: 19,
params: [],
body: {
start: 17,
end: 19,
body: [],
},
},
},
],
},
])
})
test('fn funcN = (a, b) => {return a + b}', () => {
const { body } = parser_wasm(
const { body } = parse(
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
@ -256,7 +219,7 @@ describe('testing function declaration', () => {
test('call expression assignment', () => {
const code = `fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)`
const { body } = parser_wasm(code)
const { body } = parse(code)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
expect(body).toEqual([
{
@ -368,9 +331,6 @@ const myVar = funcN(1, 2)`
raw: '2',
},
],
function: {
type: 'InMemory',
},
optional: false,
},
},
@ -388,7 +348,7 @@ describe('testing pipe operator special', () => {
|> lineTo([1, 1], %)
|> rx(45, %)
`
const { body } = parser_wasm(code)
const { body } = parse(code)
delete (body[0] as any).declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -404,7 +364,7 @@ describe('testing pipe operator special', () => {
id: { type: 'Identifier', start: 6, end: 14, name: 'mySketch' },
init: {
type: 'PipeExpression',
start: 15,
start: 17,
end: 145,
body: [
{
@ -440,7 +400,6 @@ describe('testing pipe operator special', () => {
],
},
],
function: expect.any(Object),
optional: false,
},
{
@ -477,7 +436,6 @@ describe('testing pipe operator special', () => {
},
{ type: 'PipeSubstitution', start: 59, end: 60 },
],
function: expect.any(Object),
optional: false,
},
{
@ -550,7 +508,6 @@ describe('testing pipe operator special', () => {
},
{ type: 'PipeSubstitution', start: 105, end: 106 },
],
function: expect.any(Object),
optional: false,
},
{
@ -587,7 +544,6 @@ describe('testing pipe operator special', () => {
},
{ type: 'PipeSubstitution', start: 128, end: 129 },
],
function: expect.any(Object),
optional: false,
},
{
@ -610,9 +566,6 @@ describe('testing pipe operator special', () => {
},
{ type: 'PipeSubstitution', start: 143, end: 144 },
],
function: {
type: 'InMemory',
},
optional: false,
},
],
@ -624,7 +577,7 @@ describe('testing pipe operator special', () => {
})
test('pipe operator with binary expression', () => {
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
const { body } = parser_wasm(code)
const { body } = parse(code)
delete (body as any)[0].declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -645,7 +598,7 @@ describe('testing pipe operator special', () => {
},
init: {
type: 'PipeExpression',
start: 12,
start: 14,
end: 36,
body: [
{
@ -692,9 +645,6 @@ describe('testing pipe operator special', () => {
end: 35,
},
],
function: {
type: 'InMemory',
},
optional: false,
},
],
@ -706,7 +656,7 @@ describe('testing pipe operator special', () => {
})
test('array expression', () => {
let code = `const yo = [1, '2', three, 4 + 5]`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -781,7 +731,7 @@ describe('testing pipe operator special', () => {
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -925,7 +875,7 @@ describe('testing pipe operator special', () => {
const code = `const yo = {key: {
key2: 'value'
}}`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -993,7 +943,7 @@ describe('testing pipe operator special', () => {
})
test('object expression with array ast', () => {
const code = `const yo = {key: [1, '2']}`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1057,7 +1007,7 @@ describe('testing pipe operator special', () => {
})
test('object memberExpression simple', () => {
const code = `const prop = yo.one.two`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1112,7 +1062,7 @@ describe('testing pipe operator special', () => {
})
test('object memberExpression with square braces', () => {
const code = `const prop = yo.one["two"]`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1168,7 +1118,7 @@ describe('testing pipe operator special', () => {
})
test('object memberExpression with two square braces literal and identifier', () => {
const code = `const prop = yo["one"][two]`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1227,7 +1177,7 @@ describe('testing pipe operator special', () => {
describe('nests binary expressions correctly', () => {
it('works with the simple case', () => {
const code = `const yo = 1 + 2`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1271,7 +1221,7 @@ describe('nests binary expressions correctly', () => {
it('should nest according to precedence with multiply first', () => {
// should be binExp { binExp { lit-1 * lit-2 } + lit}
const code = `const yo = 1 * 2 + 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1328,7 +1278,7 @@ describe('nests binary expressions correctly', () => {
it('should nest according to precedence with sum first', () => {
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
const code = `const yo = 1 + 2 * 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1382,9 +1332,9 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest properly with two opperators of equal precedence', () => {
it('should nest properly with two operators of equal precedence', () => {
const code = `const yo = 1 + 2 - 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1419,9 +1369,9 @@ describe('nests binary expressions correctly', () => {
},
})
})
it('should nest properly with two opperators of equal (but higher) precedence', () => {
it('should nest properly with two operators of equal (but higher) precedence', () => {
const code = `const yo = 1 * 2 / 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1458,7 +1408,7 @@ describe('nests binary expressions correctly', () => {
})
it('should nest properly with longer example', () => {
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
const { body } = parser_wasm(code)
const { body } = parse(code)
const init = (body[0] as any).declarations[0].init
expect(init).toEqual({
type: 'BinaryExpression',
@ -1480,7 +1430,7 @@ describe('nests binary expressions correctly', () => {
type: 'BinaryExpression',
operator: '*',
start: 15,
end: 26,
end: 25,
left: { type: 'Literal', value: 2, raw: '2', start: 15, end: 16 },
right: {
type: 'BinaryExpression',
@ -1514,24 +1464,23 @@ const key = 'c'`
const nonCodeMetaInstance = {
type: 'NonCodeNode',
start: code.indexOf('\n// this is a comment'),
end: code.indexOf('const key'),
end: code.indexOf('const key') - 1,
value: {
type: 'blockComment',
style: 'line',
value: 'this is a comment',
},
}
const { nonCodeMeta } = parser_wasm(code)
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
const { nonCodeMeta } = parse(code)
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
const codeWithExtraStartWhitespace = '\n\n\n' + code
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
codeWithExtraStartWhitespace
)
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
nonCodeMetaInstance.value
)
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
expect(nonCodeMeta2.nonCodeNodes[0][0].start).not.toBe(
nonCodeMetaInstance.start
)
})
@ -1545,16 +1494,17 @@ const key = 'c'`
|> close(%)
`
const { body } = parser_wasm(code)
const { body } = parse(code)
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.nonCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
expect(sketchNonCodeMeta[indexOfSecondLineToExpression][0]).toEqual({
type: 'NonCodeNode',
start: 106,
end: 166,
end: 163,
value: {
type: 'blockComment',
type: 'inlineComment',
style: 'block',
value: 'this is\n a comment\n spanning a few lines',
},
})
@ -1569,16 +1519,17 @@ const key = 'c'`
' |> rx(90, %)',
].join('\n')
const { body } = parser_wasm(code)
const { body } = parse(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.nonCodeNodes
expect(sketchNonCodeMeta[3]).toEqual({
.nonCodeNodes[3][0]
expect(sketchNonCodeMeta).toEqual({
type: 'NonCodeNode',
start: 125,
end: 141,
end: 138,
value: {
type: 'blockComment',
value: 'a comment',
style: 'line',
},
})
})
@ -1587,7 +1538,7 @@ const key = 'c'`
describe('test UnaryExpression', () => {
it('should parse a unary expression in simple var dec situation', () => {
const code = `const myVar = -min(4, 100)`
const { body } = parser_wasm(code)
const { body } = parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'UnaryExpression',
@ -1603,7 +1554,6 @@ describe('test UnaryExpression', () => {
{ type: 'Literal', start: 19, end: 20, value: 4, raw: '4' },
{ type: 'Literal', start: 22, end: 25, value: 100, raw: '100' },
],
function: expect.any(Object),
optional: false,
},
})
@ -1613,7 +1563,7 @@ describe('test UnaryExpression', () => {
describe('testing nested call expressions', () => {
it('callExp in a binExp in a callExp', () => {
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
const { body } = parser_wasm(code)
const { body } = parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'CallExpression',
@ -1637,12 +1587,10 @@ describe('testing nested call expressions', () => {
{ type: 'Literal', start: 34, end: 35, value: 5, raw: '5' },
{ type: 'Literal', start: 37, end: 38, value: 3, raw: '3' },
],
function: expect.any(Object),
optional: false,
},
},
],
function: expect.any(Object),
optional: false,
})
})
@ -1651,7 +1599,7 @@ describe('testing nested call expressions', () => {
describe('should recognise callExpresions in binaryExpressions', () => {
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
it('should recognise the callExp', () => {
const { body } = parser_wasm(code)
const { body } = parse(code)
const callExpArgs = (body?.[0] as any).expression?.arguments
expect(callExpArgs).toEqual([
{
@ -1674,7 +1622,6 @@ describe('should recognise callExpresions in binaryExpressions', () => {
},
{ type: 'PipeSubstitution', start: 25, end: 26 },
],
function: expect.any(Object),
optional: false,
},
right: { type: 'Literal', value: 1, raw: '1', start: 30, end: 31 },
@ -1690,18 +1637,13 @@ describe('parsing errors', () => {
let _theError
try {
const result = expect(parser_wasm(code))
console.log('result', result)
const result = expect(parse(code))
} catch (e) {
_theError = e
}
const theError = _theError as any
expect(theError).toEqual(
new KCLError(
'unexpected',
'Unexpected token Token { token_type: Brace, start: 29, end: 30, value: "}" }',
[[29, 30]]
)
new KCLError('syntax', 'Unexpected token', [[27, 28]])
)
})
})

View File

@ -1,48 +0,0 @@
import { Program } from './abstractSyntaxTreeTypes'
import { parse_js } from '../wasm-lib/pkg/wasm_lib'
import { initPromise } from './rust'
import { Token } from './tokeniser'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
ranges.map(([start, end]) => [start, end])
export const parser_wasm = (code: string): Program => {
try {
const program: Program = parse_js(code)
return program
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
}
export async function asyncParser(code: string): Promise<Program> {
await initPromise
try {
const program: Program = parse_js(code)
return program
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
}
export function rangeOfToken(token: Token | undefined): [number, number][] {
return token === undefined ? [] : [[token.start, token.end]]
}

View File

@ -1,37 +0,0 @@
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value'
export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression'
export type { MemberExpression } from '../wasm-lib/kcl/bindings/MemberExpression'
export type { PipeExpression } from '../wasm-lib/kcl/bindings/PipeExpression'
export type { VariableDeclaration } from '../wasm-lib/kcl/bindings/VariableDeclaration'
export type { PipeSubstitution } from '../wasm-lib/kcl/bindings/PipeSubstitution'
export type { Identifier } from '../wasm-lib/kcl/bindings/Identifier'
export type { UnaryExpression } from '../wasm-lib/kcl/bindings/UnaryExpression'
export type { BinaryExpression } from '../wasm-lib/kcl/bindings/BinaryExpression'
export type { ReturnStatement } from '../wasm-lib/kcl/bindings/ReturnStatement'
export type { ExpressionStatement } from '../wasm-lib/kcl/bindings/ExpressionStatement'
export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
export type SyntaxType =
| 'Program'
| 'ExpressionStatement'
| 'BinaryExpression'
| 'CallExpression'
| 'Identifier'
| 'ReturnStatement'
| 'VariableDeclaration'
| 'VariableDeclarator'
| 'MemberExpression'
| 'ArrayExpression'
| 'ObjectExpression'
| 'ObjectProperty'
| 'FunctionExpression'
| 'PipeExpression'
| 'PipeSubstitution'
| 'Literal'
| 'NonCodeNode'
| 'UnaryExpression'

View File

@ -1,5 +1,4 @@
import { parser_wasm } from './abstractSyntaxTree'
import { initPromise } from './rust'
import { parse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(() => initPromise)
@ -8,12 +7,13 @@ describe('testing artifacts', () => {
// Enable rotations #152
test('sketch artifacts', async () => {
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
show(mySketch001)`
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const shown = programMemory?.return?.map(
// @ts-ignore
@ -28,7 +28,7 @@ show(mySketch001)`
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [21, 42],
sourceRange: [46, 71],
},
},
value: [
@ -38,7 +38,7 @@ show(mySketch001)`
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [48, 73],
sourceRange: [77, 102],
id: expect.any(String),
},
},
@ -48,7 +48,7 @@ show(mySketch001)`
from: [-1.59, -1.54],
name: '',
__geoMeta: {
sourceRange: [79, 103],
sourceRange: [108, 132],
id: expect.any(String),
},
},
@ -56,20 +56,22 @@ show(mySketch001)`
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: expect.any(String),
__meta: [{ sourceRange: [21, 42] }],
planeId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
},
])
})
test('extrude artifacts', async () => {
// Enable rotations #152
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)
show(mySketch001)`
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const shown = programMemory?.return?.map(
// @ts-ignore
@ -83,7 +85,7 @@ show(mySketch001)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [21, 42] }],
__meta: [{ sourceRange: [46, 71] }],
},
])
})
@ -91,7 +93,8 @@ show(mySketch001)`
// Enable rotations #152
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
const code = `
const sk1 = startSketchAt([0, 0])
const sk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 10], tag: "p" }, %)
|> lineTo([2.5, 0], %)
@ -100,7 +103,8 @@ const sk1 = startSketchAt([0, 0])
// |> ry(5, %)
const theExtrude = extrude(2, sk1)
// const theTransf = getExtrudeWallTransform('p', theExtrude)
const sk2 = startSketchAt([0, 0])
const sk2 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 3], tag: "p" }, %)
|> lineTo([2.5, 0], %)
@ -109,7 +113,7 @@ const sk2 = startSketchAt([0, 0])
show(theExtrude, sk2)`
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const geos = programMemory?.return?.map(
// @ts-ignore
@ -123,7 +127,7 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [13, 34] }],
__meta: [{ sourceRange: [38, 63] }],
},
{
type: 'ExtrudeGroup',
@ -132,7 +136,7 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [302, 323] }],
__meta: [{ sourceRange: [356, 381] }],
},
])
})

View File

@ -18,6 +18,20 @@ export class KCLError {
}
}
export class KCLLexicalError extends KCLError {
constructor(msg: string, sourceRanges: [number, number][]) {
super('lexical', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLInternalError extends KCLError {
constructor(msg: string, sourceRanges: [number, number][]) {
super('internal', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLSyntaxError extends KCLError {
constructor(msg: string, sourceRanges: [number, number][]) {
super('syntax', msg, sourceRanges)
@ -72,7 +86,7 @@ export class KCLUndefinedValueError extends KCLError {
* Currently the diagnostics are all errors, but in the future they could include lints.
* */
export function kclErrToDiagnostic(errors: KCLError[]): Diagnostic[] {
return errors.flatMap((err) => {
return errors?.flatMap((err) => {
return err.sourceRanges.map(([from, to]) => {
return { from, to, message: err.msg, severity: 'error' }
})

View File

@ -1,10 +1,7 @@
import fs from 'node:fs'
import { parser_wasm } from './abstractSyntaxTree'
import { ProgramMemory, SketchGroup } from './executor'
import { initPromise } from './rust'
import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { vi } from 'vitest'
import { KCLError } from './errors'
beforeAll(() => initPromise)
@ -44,7 +41,8 @@ const newVar = myVar + 1`
expect(root.magicNum.value).toBe(69)
})
it('sketch declaration', async () => {
let code = `const mySketch = startSketchAt([0,0])
let code = `const mySketch = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> lineTo({to: [0,2], tag: "myPath"}, %)
|> lineTo([2,3], %)
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
@ -60,7 +58,7 @@ show(mySketch)
to: [0, 2],
from: [0, 0],
__geoMeta: {
sourceRange: [43, 80],
sourceRange: [72, 109],
id: expect.any(String),
},
name: 'myPath',
@ -71,7 +69,7 @@ show(mySketch)
from: [0, 2],
name: '',
__geoMeta: {
sourceRange: [86, 102],
sourceRange: [115, 131],
id: expect.any(String),
},
},
@ -80,7 +78,7 @@ show(mySketch)
to: [5, -1],
from: [2, 3],
__geoMeta: {
sourceRange: [108, 151],
sourceRange: [137, 180],
id: expect.any(String),
},
name: 'rightPath',
@ -90,8 +88,8 @@ show(mySketch)
expect(_return).toEqual([
{
type: 'Identifier',
start: 174,
end: 182,
start: 203,
end: 211,
name: 'mySketch',
},
])
@ -135,7 +133,8 @@ show(mySketch)
it('execute pipe sketch into call expression', async () => {
// Enable rotations #152
const code = [
'const mySk1 = startSketchAt([0,0])',
"const mySk1 = startSketchOn('XY')",
' |> startProfileAt([0,0], %)',
' |> lineTo([1,1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([1,1], %)',
@ -150,7 +149,7 @@ show(mySketch)
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [14, 34],
sourceRange: [39, 63],
},
},
value: [
@ -160,7 +159,7 @@ show(mySketch)
from: [0, 0],
name: '',
__geoMeta: {
sourceRange: [40, 56],
sourceRange: [69, 85],
id: expect.any(String),
},
},
@ -169,7 +168,7 @@ show(mySketch)
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [62, 100],
sourceRange: [91, 129],
id: expect.any(String),
},
name: 'myPath',
@ -180,7 +179,7 @@ show(mySketch)
from: [0, 1],
name: '',
__geoMeta: {
sourceRange: [106, 122],
sourceRange: [135, 151],
id: expect.any(String),
},
},
@ -188,7 +187,8 @@ show(mySketch)
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: expect.any(String),
__meta: [{ sourceRange: [14, 34] }],
planeId: expect.any(String),
__meta: [{ sourceRange: [39, 63] }],
})
})
it('execute array expression', async () => {
@ -332,7 +332,8 @@ describe('testing math operators', () => {
})
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
const code = [
'const part001 = startSketchAt([0, 0])',
"const part001 = startSketchOn('XY')",
' |> startProfileAt([0, 0], %)',
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { root } = await exe(code)
@ -344,7 +345,8 @@ describe('testing math operators', () => {
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
const code = [
`const myVar = 3`,
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
` |> line([`,
` min(segLen('seg01', %), myVar),`,
@ -380,7 +382,8 @@ describe('testing math operators', () => {
describe('Testing Errors', () => {
it('should throw an error when a variable is not defined', async () => {
const code = `const myVar = 5
const theExtrude = startSketchAt([0, 0])
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.4, 5], %)
|> line([-0.76], myVarZ, %)
|> line([5,5], %)
@ -391,7 +394,7 @@ show(theExtrude)`
new KCLError(
'undefined_value',
'memory item key `myVarZ` is not defined',
[[100, 106]]
[[129, 135]]
)
)
})
@ -403,7 +406,7 @@ async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, return: null }
) {
const ast = parser_wasm(code)
const ast = parse(code)
const result = await enginelessExecutor(ast, programMemory)
return result

View File

@ -1,81 +0,0 @@
import { Program } from './abstractSyntaxTreeTypes'
import {
EngineCommandManager,
ArtifactMap,
SourceRangeMap,
} from './std/engineConnection'
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from './abstractSyntaxTree'
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Position } from '../wasm-lib/kcl/bindings/Position'
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: MemoryItem
}
export interface ProgramMemory {
root: Memory
return: ProgramReturn | null
}
export const executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
tempMapCallback: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void = () => {}
): Promise<ProgramMemory> => {
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
programMemory,
engineCommandManager
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands(node, _programMemory)
tempMapCallback({ artifactMap, sourceRangeMap })
engineCommandManager.endSession()
return _programMemory
}
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager
): Promise<ProgramMemory> => {
try {
const memory: ProgramMemory = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory),
engineCommandManager
)
return memory
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
}

View File

@ -1,6 +1,5 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { parser_wasm } from './abstractSyntaxTree'
import { initPromise } from './rust'
import { Identifier, parse, initPromise } from './wasm'
beforeAll(() => initPromise)
@ -20,11 +19,88 @@ const sk3 = startSketchAt([0, 0])
lineToSubstringIndex + subStr.length,
]
const ast = parser_wasm(code)
const ast = parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const { node } = getNodeFromPath<any>(ast, nodePath)
expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const b1 = cube([0,0], 10)`
const subStr = 'pos, scale'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'pos'.length,
]
const ast = parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['params', 'FunctionExpression'],
[0, 'index'],
])
expect(node.type).toBe('Identifier')
expect(node.name).toBe('pos')
})
it('gets path right for deep within function definition body', () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const b1 = cube([0,0], 10)`
const subStr = 'scale, 0'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'scale'.length,
]
const ast = parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'FunctionExpression'],
['body', 'FunctionExpression'],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
['elements', 'ArrayExpression'],
[0, 'index'],
])
expect(node.type).toBe('Identifier')
expect(node.name).toBe('scale')
})
})

View File

@ -1,4 +1,4 @@
import { parser_wasm } from './abstractSyntaxTree'
import { parse, recast, initPromise } from './wasm'
import {
createLiteral,
createIdentifier,
@ -13,8 +13,6 @@ import {
giveSketchFnCallTag,
moveValueIntoNewVariable,
} from './modifyAst'
import { recast } from './recast'
import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(() => initPromise)
@ -106,13 +104,13 @@ describe('Testing addSketchTo', () => {
body: [],
start: 0,
end: 0,
nonCodeMeta: { nonCodeNodes: {}, start: null },
nonCodeMeta: { nonCodeNodes: {}, start: [] },
},
'yz'
)
const str = recast(result.modifiedAst)
expect(str).toBe(`const part001 = startSketchAt('default')
|> ry(90, %)
expect(str).toBe(`const part001 = startSketchOn('YZ')
|> startProfileAt('default', %)
|> line('default', %)
show(part001)
`)
@ -126,7 +124,7 @@ function giveSketchFnCallTagTestHelper(
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
// this wrapper changes the input and output to code
// making it more of an integration test, but easier to read the test intention is the goal
const ast = parser_wasm(code)
const ast = parse(code)
const start = code.indexOf(searchStr)
const range: [number, number] = [start, start + searchStr.length]
const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(ast, range)
@ -135,7 +133,8 @@ function giveSketchFnCallTagTestHelper(
}
describe('Testing giveSketchFnCallTag', () => {
const code = `const part001 = startSketchAt([0, 0])
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.57, -0.13], %)
|> line([0, 0.83], %)
|> line([0.82, 0.34], %)
@ -187,7 +186,8 @@ fn ghi = (x) => {
const abc = 3
const identifierGuy = 5
const yo = 5 + 6
const part001 = startSketchAt([-1.2, 4.83])
const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
@ -197,7 +197,7 @@ const part001 = startSketchAt([-1.2, 4.83])
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('should move a binary expression into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
@ -211,7 +211,7 @@ show(part001)`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
@ -225,7 +225,7 @@ show(part001)`
expect(newCode).toContain(`line([newVar, 0], %)`)
})
it('should move a callExpression into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
@ -239,7 +239,7 @@ show(part001)`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a binary expression with call expression into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
@ -253,7 +253,7 @@ show(part001)`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a identifier into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(

View File

@ -1,4 +1,5 @@
import { Selection, ToolTip } from '../useStore'
import { ToolTip } from '../useStore'
import { Selection } from 'lib/selections'
import {
Program,
CallExpression,
@ -14,14 +15,15 @@ import {
ObjectExpression,
UnaryExpression,
BinaryExpression,
} from './abstractSyntaxTreeTypes'
PathToNode,
ProgramMemory,
} from './wasm'
import {
findAllPreviousVariables,
getNodeFromPath,
getNodePathFromSourceRange,
isNodeSafeToReplace,
} from './queryAst'
import { PathToNode, ProgramMemory } from './executor'
import {
addTagForSketchOnFace,
getFirstArg,
@ -31,21 +33,26 @@ import { isLiteralArrayOrStatic } from './std/sketchcombos'
export function addStartSketch(
node: Program,
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz',
start: [number, number],
end: [number, number]
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node }
const _name = findUniqueName(node, 'part')
const startSketchAt = createCallExpression('startSketchAt', [
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis.toUpperCase()),
])
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
createPipeSubstitution(),
])
const initialLineTo = createCallExpression('line', [
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
createPipeSubstitution(),
])
const pipeBody = [startSketchAt, initialLineTo]
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,
@ -78,11 +85,11 @@ export function addSketchTo(
const _node = { ...node }
const _name = name || findUniqueName(node, 'part')
const startSketchAt = createCallExpressionStdLib('startSketchAt', [
createLiteral('default'),
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis.toUpperCase()),
])
const rotate = createCallExpression(axis === 'xz' ? 'rx' : 'ry', [
createLiteral(90),
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createLiteral('default'),
createPipeSubstitution(),
])
const initialLineTo = createCallExpressionStdLib('line', [
@ -90,10 +97,7 @@ export function addSketchTo(
createPipeSubstitution(),
])
const pipeBody =
axis !== 'xy'
? [startSketchAt, rotate, initialLineTo]
: [startSketchAt, initialLineTo]
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,
@ -306,7 +310,7 @@ export function extrudeSketch(
const name = findUniqueName(node, 'part')
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
let showCallIndex = getShowIndex(_node)
if (showCallIndex == -1) {
if (showCallIndex === -1) {
// We didn't find a show, so let's just append everything
showCallIndex = _node.body.length
}
@ -321,7 +325,7 @@ export function extrudeSketch(
[0, 'index'],
]
return {
modifiedAst: addToShow(_node, name),
modifiedAst: node,
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
pathToExtrudeArg,
}
@ -476,21 +480,6 @@ export function createCallExpressionStdLib(
end: 0,
name,
},
function: {
type: 'StdLib',
func: {
// We only need the name here to map it back when it serializes
// to rust, don't worry about the rest.
name,
summary: '',
description: '',
tags: [],
returnValue: { type: '', required: false, name: '', schema: {} },
args: [],
unpublished: false,
deprecated: false,
},
},
optional: false,
arguments: args,
}
@ -510,9 +499,6 @@ export function createCallExpression(
end: 0,
name,
},
function: {
type: 'InMemory',
},
optional: false,
arguments: args,
}
@ -537,7 +523,7 @@ export function createPipeExpression(
start: 0,
end: 0,
body,
nonCodeMeta: { nonCodeNodes: {}, start: null },
nonCodeMeta: { nonCodeNodes: {}, start: [] },
}
}

View File

@ -1,11 +1,12 @@
import { parser_wasm } from './abstractSyntaxTree'
import { parse, recast, initPromise } from './wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
isTypeInValue,
getNodePathFromSourceRange,
doesPipeHaveCallExp,
hasExtrudeSketchGroup,
} from './queryAst'
import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers'
import {
createArrayExpression,
@ -13,7 +14,6 @@ import {
createLiteral,
createPipeSubstitution,
} from './modifyAst'
import { recast } from './recast'
beforeAll(() => initPromise)
@ -28,7 +28,8 @@ const halfArmAngle = armAngle / 2
const arrExpShouldNotBeIncluded = [1, 2, 3]
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> yLineTo(1, %)
|> xLine(3.84, %) // selection-range-7ish-before-this
@ -36,7 +37,7 @@ const variableBelowShouldNotBeIncluded = 3
show(part001)`
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
@ -59,7 +60,8 @@ show(part001)`
})
describe('testing argIsNotIdentifier', () => {
const code = `const part001 = startSketchAt([-1.2, 4.83])
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
@ -70,7 +72,7 @@ const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('find a safe binaryExpression', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('100 + 100') + 2
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -84,7 +86,7 @@ show(part001)`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe Identifier', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('abc')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -92,7 +94,7 @@ show(part001)`
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
})
it('find a safe CallExpression', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('def')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -106,7 +108,7 @@ show(part001)`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('ghi')
const range: [number, number] = [rangeStart, rangeStart]
const result = isNodeSafeToReplace(ast, range)
@ -115,7 +117,7 @@ show(part001)`
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
})
it('find an UNsafe Identifier, as it is a callee', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('ine([2.8,')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(false)
@ -125,7 +127,7 @@ show(part001)`
)
})
it("find a safe BinaryExpression that's assigned to a variable", () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('5 + 6') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -139,7 +141,7 @@ show(part001)`
expect(outCode).toContain(`const yo = replaceName`)
})
it('find a safe BinaryExpression that has a CallExpression within', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('jkl') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -155,7 +157,7 @@ show(part001)`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe BinaryExpression within a CallExpression', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('identifierGuy') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -196,14 +198,15 @@ show(part001)`
})
describe('testing getNodePathFromSourceRange', () => {
const code = `const part001 = startSketchAt([0.39, -0.05])
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([0.39, -0.05], %)
|> line([0.94, 2.61], %)
|> line([-0.21, -1.4], %)
show(part001)`
it('finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = parser_wasm(code)
const ast = parse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
@ -212,13 +215,13 @@ show(part001)`
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[1, 'index'],
[2, 'index'],
])
})
it('finds the last line when cursor is put at the end', () => {
const searchLn = `line([-0.21, -1.4], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = parser_wasm(code)
const ast = parse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const expected = [
['body', ''],
@ -227,7 +230,7 @@ show(part001)`
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
[3, 'index'],
]
expect(result).toEqual(expected)
// expect similar result for start of line
@ -245,3 +248,114 @@ show(part001)`
expect(selectWholeThing).toEqual(expected)
})
})
describe('testing doesPipeHave', () => {
it('finds close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> close(%)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(true)
})
it('finds extrude', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> close(%)
|> extrude(1, %)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'extrude',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(true)
})
it('does NOT find close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(false)
})
it('returns false if not a pipe', () => {
const exampleCode = `const length001 = 2`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [9, 10] },
})
expect(result).toEqual(false)
})
})
describe('testing hasExtrudeSketchGroup', () => {
it('find sketch group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [100, 101] },
programMemory,
})
expect(result).toEqual(true)
})
it('find extrude group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> extrude(1, %)`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [100, 101] },
programMemory,
})
expect(result).toEqual(true)
})
it('finds nothing', async () => {
const exampleCode = `const length001 = 2`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [10, 11] },
programMemory,
})
expect(result).toEqual(false)
})
})

View File

@ -1,5 +1,5 @@
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
import { Selection, ToolTip } from '../useStore'
import { ToolTip } from '../useStore'
import { Selection } from 'lib/selections'
import {
BinaryExpression,
Program,
@ -10,7 +10,12 @@ import {
VariableDeclaration,
ReturnStatement,
ArrayExpression,
} from './abstractSyntaxTreeTypes'
PathToNode,
ProgramMemory,
SketchGroup,
SourceRange,
PipeExpression,
} from './wasm'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
import { getAngle } from '../lib/utils'
@ -239,7 +244,29 @@ function moreNodePathFromSourceRange(
}
return path
}
console.error('not implemented')
if (_node.type === 'FunctionExpression' && isInRange) {
for (let i = 0; i < _node.params.length; i++) {
const param = _node.params[i]
if (param.start <= start && param.end >= end) {
path.push(['params', 'FunctionExpression'])
path.push([i, 'index'])
return moreNodePathFromSourceRange(param, sourceRange, path)
}
}
if (_node.body.start <= start && _node.body.end >= end) {
path.push(['body', 'FunctionExpression'])
const fnBody = _node.body.body
for (let i = 0; i < fnBody.length; i++) {
const statement = fnBody[i]
if (statement.start <= start && statement.end >= end) {
path.push(['body', 'FunctionExpression'])
path.push([i, 'index'])
return moreNodePathFromSourceRange(statement, sourceRange, path)
}
}
}
}
console.error('not implemented: ' + node.type)
return path
}
@ -486,3 +513,47 @@ export function isLinesParallelAndConstrained(
}
}
}
export function doesPipeHaveCallExp({
ast,
selection,
calleeName,
}: {
calleeName: string
ast: Program
selection: Selection
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const pipeExpression = getNodeFromPath<PipeExpression>(
ast,
pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return false
return pipeExpression.body.some(
(expression) =>
expression.type === 'CallExpression' &&
expression.callee.name === calleeName
)
}
export function hasExtrudeSketchGroup({
ast,
selection,
programMemory,
}: {
ast: Program
selection: Selection
programMemory: ProgramMemory
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const varDec = getNodeFromPath<VariableDeclaration>(
ast,
pathToNode,
'VariableDeclaration'
).node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declarations[0].id.name
const varValue = programMemory?.root[varName]
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
}

View File

@ -1,8 +1,5 @@
import { recast } from './recast'
import { parser_wasm } from './abstractSyntaxTree'
import { Program } from './abstractSyntaxTreeTypes'
import { parse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs'
import { initPromise } from './rust'
beforeAll(() => initPromise)
@ -275,21 +272,20 @@ const mySk1 = startSketchAt([0, 0])
`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(`// comment at start
expect(recasted).toBe(`/* comment at start */
const mySk1 = startSketchAt([0, 0])
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|> lineTo([1, 1], %)
/* and
here
a comment between pipe expression statements */
|> lineTo([1, 1], %) /* and
here */
// a comment between pipe expression statements
|> rx(90, %)
// and another with just white space between others below
|> ry(45, %)
|> rx(45, %)
// one more for good measure
/* one more for good measure */
`)
})
})
@ -366,6 +362,6 @@ describe('it recasts binary expression using brackets where needed', () => {
// helpers
function code2ast(code: string): { ast: Program } {
const ast = parser_wasm(code)
const ast = parse(code)
return { ast }
}

View File

@ -1,13 +0,0 @@
import { Program } from './abstractSyntaxTreeTypes'
import { recast_wasm } from '../wasm-lib/pkg/wasm_lib'
export const recast = (ast: Program): string => {
try {
const s: string = recast_wasm(JSON.stringify(ast))
return s
} catch (e) {
// TODO: do something real with the error.
console.log('recast', e)
throw e
}
}

View File

@ -1,20 +0,0 @@
import init from '../wasm-lib/pkg/wasm_lib'
const initialise = async () => {
const baseUrl =
typeof window === 'undefined'
? 'http://127.0.0.1:3000'
: window.location.origin.includes('tauri://localhost')
? 'tauri://localhost'
: window.location.origin.includes('localhost')
? 'http://localhost:3000'
: window.location.origin && window.location.origin !== 'null'
? window.location.origin
: 'http://localhost:3000'
const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer()
return init(buffer)
}
export const initPromise = initialise()

View File

@ -2,5 +2,5 @@ The std is as expected, tools that are provided with the language.
For this language that means functions.
However because programatically changing the source code is a first class citizen in this lang, there needs to be helpes for adding and modifying these function calls,
However because programmatically changing the source code is a first class citizen in this lang, there needs to be helpers for adding and modifying these function calls,
So it makes sense to group some of these together.

Some files were not shown because too many files have changed in this diff Show More