Compare commits

...

30 Commits

Author SHA1 Message Date
a947e23ef9 Merge branch 'main' into achalmers/extrude-in-batch 2024-04-11 15:30:55 -07:00
76de64780c Bump yarn from 1.22.19 to 1.22.22 (#2083)
Bumps [yarn](https://github.com/yarnpkg/yarn) from 1.22.19 to 1.22.22.
- [Release notes](https://github.com/yarnpkg/yarn/releases)
- [Changelog](https://github.com/yarnpkg/yarn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yarnpkg/yarn/compare/v1.22.19...v1.22.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 15:27:53 -07:00
2d804dee2b Bump three and @types/three (#2081)
Bumps [three](https://github.com/mrdoob/three.js) and [@types/three](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/three). These dependencies needed to be updated together.

Updates `three` from 0.160.0 to 0.163.0
- [Release notes](https://github.com/mrdoob/three.js/releases)
- [Commits](https://github.com/mrdoob/three.js/commits)

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

---
updated-dependencies:
- dependency-name: three
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/three"
  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>
2024-04-11 15:27:44 -07:00
c094d0ced1 Screenshot for core dump (#2066)
* start of screenshot, need uploader

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

* cleanup

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

* some cleanup

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

* most things working

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

* bump the world

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>

* mime type

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-11 20:15:49 +00:00
a90fe3c066 add asssignees (#2079)
* add asssignee

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

* add asssignee

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-11 18:43:33 +00:00
f3ea7fd0e2 fixing onboarding bracket with fillet changes (#2069)
* fixing fillet changes

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

* Trigger CI with empty commit

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

* Rerun CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
2024-04-11 14:15:48 -04:00
704ff0df62 Bump tar from 6.1.15 to 6.2.1 (#2048)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.1.15 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.1.15...v6.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 10:51:27 -07:00
eba17e92b7 Bump @types/node from 18.19.26 to 18.19.31 (#2055)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.19.26 to 18.19.31.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 10:48:56 -07:00
d9d700624e Bump @playwright/test from 1.39.0 to 1.43.0 (#2058)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.39.0 to 1.43.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.39.0...v1.43.0)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  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>
2024-04-11 10:48:32 -07:00
1e547aeef0 Bump swr from 2.2.2 to 2.2.5 (#2057)
Bumps [swr](https://github.com/vercel/swr) from 2.2.2 to 2.2.5.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.2.2...v2.2.5)

---
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>
2024-04-11 10:48:18 -07:00
22899c9e69 Bump quote from 1.0.35 to 1.0.36 in /src/wasm-lib (#2074)
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.35 to 1.0.36.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.35...1.0.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 10:47:54 -07:00
25702f454c Bump anyhow from 1.0.81 to 1.0.82 in /src/wasm-lib (#2071)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.81 to 1.0.82.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.81...1.0.82)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 10:47:44 -07:00
11faf86983 Bump kittycad-modeling-cmds from 0.2.10 to 0.2.17 in /src/wasm-lib (#2073)
Bumps [kittycad-modeling-cmds](https://github.com/KittyCAD/modeling-api) from 0.2.10 to 0.2.17.
- [Commits](https://github.com/KittyCAD/modeling-api/compare/kittycad-modeling-cmds-0.2.10...kittycad-modeling-cmds-0.2.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 10:47:23 -07:00
67d73382b1 Bump kittycad from 0.2.63 to 0.2.66 in /src-tauri (#2076)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.63 to 0.2.66.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.63...v0.2.66)

---
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>
2024-04-11 10:39:44 -07:00
15b9f43f2c Revert Playwright tests to use addInitScript to adjust storage state (#2077)
* Revert Playwright tests to use addInitScript to adjust storage state

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

* Fix tsc

* Rerun CI

* Rerun CI

* Only use page.addInitScript within tests
because technically adding multiple init scripts to the context has an indeterminate run order, per the [Playwright docs](https://playwright.dev/docs/api/class-page#page-add-init-script)

* Rerun CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-11 13:37:49 -04:00
d28555a070 fix source range error when end of file (#2070)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-11 01:51:09 +00:00
7bf116629f clean up very old ast mod (#2060)
* clean up very old ast mod

* typo
2024-04-11 10:35:23 +10:00
fe45b5b54d Bump anyhow from 1.0.81 to 1.0.82 in /src-tauri (#2059)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.81 to 1.0.82.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.81...1.0.82)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 09:45:11 -05:00
bcbd3f5bfd playwright snapshot stability (#2053)
stability attempt
2024-04-10 04:55:29 +00:00
959433e357 start of coredump (#2050)
* start of coredump

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

* add local

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

* more coredummp

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

* add arch

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

* os info

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

* fix app version

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

* more webrtc stats

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

* webrtc data

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

* webrtc stats

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

* add wasm function

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

* add coredump things

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

* add hotkey

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>

* fixes

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>

* 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>

* fixes

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

* updates

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

* clippy

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-09 18:05:36 -07:00
b3695c060d Unit test which draws 3 cubes 2024-04-09 16:46:39 -05:00
09374081ea Batch the Extrude, ObjectBringToFront and Solid3DGetExtrusionFaceInfo calls into one batch
This means sketching a cube takes 2 batches, not 1.
2024-04-09 15:19:10 -05:00
3d40650cab Debugging prints 2024-04-09 15:19:09 -05:00
d18e35b7ea Bump derive-docs (#2047) 2024-04-09 15:02:04 -05:00
596c9a0ee6 Bump @fortawesome/free-brands-svg-icons from 6.4.2 to 6.5.2 (#2012)
Bumps [@fortawesome/free-brands-svg-icons](https://github.com/FortAwesome/Font-Awesome) from 6.4.2 to 6.5.2.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.4.2...6.5.2)

---
updated-dependencies:
- dependency-name: "@fortawesome/free-brands-svg-icons"
  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>
2024-04-09 19:07:21 +00:00
9106a81c77 Bump formik from 2.4.3 to 2.4.5 (#2013)
Bumps [formik](https://github.com/jaredpalmer/formik) from 2.4.3 to 2.4.5.
- [Release notes](https://github.com/jaredpalmer/formik/releases)
- [Commits](https://github.com/jaredpalmer/formik/compare/formik@2.4.3...formik@2.4.5)

---
updated-dependencies:
- dependency-name: formik
  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>
2024-04-09 19:05:46 +00:00
8b5ebe67b2 fix js string undefined (#2045)
* fix js string undefined

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

* fixes for tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-09 18:51:41 +00:00
a7f539eca6 Bump ws and @types/ws (#2010)
Bumps [ws](https://github.com/websockets/ws) and [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws). These dependencies needed to be updated together.

Updates `ws` from 8.13.0 to 8.16.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.16.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:55:07 -07:00
f4c87c994c Bump @fortawesome/fontawesome-svg-core from 6.4.2 to 6.5.2 (#2014)
Bumps [@fortawesome/fontawesome-svg-core](https://github.com/FortAwesome/Font-Awesome) from 6.4.2 to 6.5.2.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.4.2...6.5.2)

---
updated-dependencies:
- dependency-name: "@fortawesome/fontawesome-svg-core"
  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>
2024-04-09 10:54:57 -07:00
3d4ae05145 Bump @types/react from 18.2.73 to 18.2.75 (#2042)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.73 to 18.2.75.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:53:30 -07:00
59 changed files with 1337 additions and 437 deletions

View File

@ -9,15 +9,27 @@ updates:
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- franknoirot
- irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values - package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests directory: '/src/wasm-lib/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src-tauri/' # Location of package manifests directory: '/src-tauri/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- adamchalmers
- jessfraz

View File

@ -15,7 +15,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
generate-website-docs: generate-website-docs:
name: generate-website-docs name: generate-website-docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -56,6 +56,9 @@ jobs:
gh pr create --title "Update KCL docs" \ gh pr create --title "Update KCL docs" \
--body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \ --body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \
--head "$NEW_BRANCH" \ --head "$NEW_BRANCH" \
--reviewer jessfraz \
--reviewer irev-dev \
--reviewer franknoirot \
--base main || true --base main || true
env: env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -34,7 +34,7 @@ const part = startSketchOn('XY')
{ {
// The arc angle (in degrees) to place the repetitions. Must be greater than 0. // The arc angle (in degrees) to place the repetitions. Must be greater than 0.
arcDegrees: number, arcDegrees: number,
// The center about which to make th pattern. This is a 2D vector. // The center about which to make the pattern. This is a 2D vector.
center: [number, number], center: [number, number],
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. // The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
repetitions: number, repetitions: number,

View File

@ -42,7 +42,7 @@ const part = startSketchOn('XY')
arcDegrees: number, arcDegrees: number,
// The axis around which to make the pattern. This is a 3D vector. // The axis around which to make the pattern. This is a 3D vector.
axis: [number, number, number], axis: [number, number, number],
// The center about which to make th pattern. This is a 3D vector. // The center about which to make the pattern. This is a 3D vector.
center: [number, number, number], center: [number, number, number],
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. // The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
repetitions: number, repetitions: number,

View File

@ -42165,7 +42165,7 @@
"format": "double" "format": "double"
}, },
"center": { "center": {
"description": "The center about which to make th pattern. This is a 2D vector.", "description": "The center about which to make the pattern. This is a 2D vector.",
"type": "array", "type": "array",
"items": { "items": {
"type": "number", "type": "number",
@ -44168,7 +44168,7 @@
"minItems": 3 "minItems": 3
}, },
"center": { "center": {
"description": "The center about which to make th pattern. This is a 3D vector.", "description": "The center about which to make the pattern. This is a 3D vector.",
"type": "array", "type": "array",
"items": { "items": {
"type": "number", "type": "number",

View File

@ -2,10 +2,15 @@ import { test, expect } from '@playwright/test'
import { getUtils } from './test-utils' import { getUtils } from './test-utils'
import waitOn from 'wait-on' import waitOn from 'wait-on'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { basicStorageState } from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes' import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { Themes } from 'lib/theme' import { secrets } from './secrets'
import {
TEST_SETTINGS,
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS_ONBOARDING,
} from './storageStates'
/* /*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@ -32,13 +37,25 @@ test.beforeEach(async ({ context, page }) => {
timeout: 5000, timeout: 5000,
}) })
await context.addInitScript(
async ({ token, settingsKey, settings }) => {
localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings)
},
{
token: secrets.token,
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS }),
}
)
// kill animations, speeds up tests and reduced flakiness // kill animations, speeds up tests and reduced flakiness
await page.emulateMedia({ reducedMotion: 'reduce' }) await page.emulateMedia({ reducedMotion: 'reduce' })
}) })
test.setTimeout(60000) test.setTimeout(60000)
test('Basic sketch', async ({ page, context }) => { test('Basic sketch', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -308,9 +325,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
}) })
test('executes on load', async ({ page, context }) => { test('executes on load', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await context.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const part001 = startSketchOn('-XZ')
@ -340,9 +357,9 @@ test('executes on load', async ({ page, context }) => {
).toBeVisible() ).toBeVisible()
}) })
test('re-executes', async ({ page, context }) => { test('re-executes', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await context.addInitScript(async (token) => { await page.addInitScript(async () => {
localStorage.setItem('persistCode', `const myVar = 5`) localStorage.setItem('persistCode', `const myVar = 5`)
}) })
await page.setViewportSize({ width: 1000, height: 500 }) await page.setViewportSize({ width: 1000, height: 500 })
@ -512,134 +529,131 @@ test('Auto complete works', async ({ page }) => {
|> xLine(5, %) // lin`) |> xLine(5, %) // lin`)
}) })
// Stored settings validation test test('Stored settings are validated and fall back to defaults', async ({
test.describe('Settings persistence and validation tests', () => { page,
// Override test setup }) => {
// Override beforeEach test setup
// with corrupted settings // with corrupted settings
const storageState = structuredClone(basicStorageState) await page.addInitScript(
const s = TOML.parse(storageState.origins[0].localStorage[2].value) as { async ({ settingsKey, settings }) => {
settings: SaveSettingsPayload localStorage.setItem(settingsKey, settings)
} },
s.settings.app.theme = Themes.Dark {
s.settings.app.projectDirectory = 123 as any settingsKey: TEST_SETTINGS_KEY,
s.settings.modeling.defaultUnit = 'invalid' as any settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
s.settings.modeling.mouseControls = `() => alert('hack the planet')` as any }
s.settings.projects.defaultProjectName = false as any )
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
test.use({ storageState }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
test('Stored settings are validated and fall back to defaults', async ({ // Check the settings were reset
page, const storedSettings = TOML.parse(
}) => { await page.evaluate(
const u = getUtils(page) ({ settingsKey }) => localStorage.getItem(settingsKey) || '{}',
await page.setViewportSize({ width: 1200, height: 500 }) { settingsKey: TEST_SETTINGS_KEY }
await page.goto('/') )
await u.waitForAuthSkipAppStart() ) as { settings: SaveSettingsPayload }
// Check the settings were reset expect(storedSettings.settings.app?.theme).toBe('dark')
const storedSettings = TOML.parse(
await page.evaluate(() => localStorage.getItem('/user.toml') || '{}')
) as { settings: SaveSettingsPayload }
expect(storedSettings.settings.app?.theme).toBe('dark') // Check that the invalid settings were removed
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
// Check that the invalid settings were removed expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined) expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined) expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
})
test('Project settings can be set and override user settings', async ({
page,
}) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('Meta+Shift+,')
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
await page
.locator('select[name="app-theme"]')
.selectOption({ value: 'light' })
// Verify the toast appeared
await expect(
page.getByText(`Set theme to "light" for this project`)
).toBeVisible()
// Check that the theme changed
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
// Check that the user setting was not changed
await page.getByRole('radio', { name: 'User' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
// Roll back to default "system" theme
await page
.getByText(
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
)
.hover()
await page
.getByRole('button', {
name: 'Roll back theme ; Has tooltip: Roll back to match default',
})
.click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
// Check that the project setting did not change
await page.getByRole('radio', { name: 'Project' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
})
}) })
// Onboarding tests test('Project settings can be set and override user settings', async ({
test.describe('Onboarding tests', () => { page,
// Override test setup }) => {
const storageState = structuredClone(basicStorageState) await page.setViewportSize({ width: 1200, height: 500 })
const s = TOML.parse(storageState.origins[0].localStorage[2].value) as { await page.goto('/', { waitUntil: 'domcontentloaded' })
settings: SaveSettingsPayload await page
} .getByRole('button', { name: 'Start Sketch' })
s.settings.app.onboardingStatus = '/export' .waitFor({ state: 'visible' })
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
test.use({ storageState })
test('Onboarding redirects and code updating', async ({ page, context }) => { // Open the settings modal with the browser keyboard shortcut
const u = getUtils(page) await page.keyboard.press('Meta+Shift+,')
await page.setViewportSize({ width: 1200, height: 500 }) await expect(
await page.goto('/') page.getByRole('heading', { name: 'Settings', exact: true })
await u.waitForAuthSkipAppStart() ).toBeVisible()
await page
.locator('select[name="app-theme"]')
.selectOption({ value: 'light' })
// Test that the redirect happened // Verify the toast appeared
await expect(page.url().split(':3000').slice(-1)[0]).toBe( await expect(
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export` page.getByText(`Set theme to "light" for this project`)
).toBeVisible()
// Check that the theme changed
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
// Check that the user setting was not changed
await page.getByRole('radio', { name: 'User' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
// Roll back to default "system" theme
await page
.getByText(
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
) )
.hover()
await page
.getByRole('button', {
name: 'Roll back theme ; Has tooltip: Roll back to match default',
})
.click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
// Test that you come back to this page when you refresh // Check that the project setting did not change
await page.reload() await page.getByRole('radio', { name: 'Project' }).click()
await expect(page.url().split(':3000').slice(-1)[0]).toBe( await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export` })
)
// Test that the onboarding pane loaded test('Onboarding redirects and code updating', async ({ page }) => {
const title = page.locator('[data-testid="onboarding-content"]') const u = getUtils(page)
await expect(title).toBeAttached()
// Test that the code changes when you advance to the next step // Override beforeEach test setup
await page.locator('[data-testid="onboarding-next"]').click() await page.addInitScript(
await expect(page.locator('.cm-content')).toHaveText('') async ({ settingsKey, settings }) => {
// Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
}
)
// Test that the code is not empty when you click on the next step await page.setViewportSize({ width: 1200, height: 500 })
await page.locator('[data-testid="onboarding-next"]').click() await page.goto('/')
await expect(page.locator('.cm-content')).toHaveText(/.+/) await u.waitForAuthSkipAppStart()
})
// Test that the redirect happened
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
)
// Test that you come back to this page when you refresh
await page.reload()
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
)
// Test that the onboarding pane loaded
const title = page.locator('[data-testid="onboarding-content"]')
await expect(title).toBeAttached()
// Test that the code changes when you advance to the next step
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText('')
// Test that the code is not empty when you click on the next step
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/)
}) })
test('Selections work on fresh and edited sketch', async ({ page }) => { test('Selections work on fresh and edited sketch', async ({ page }) => {
@ -851,19 +865,22 @@ test.describe('Command bar tests', () => {
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
}) })
// Override test setup code test('Can extrude from the command bar', async ({ page }) => {
const storageState = structuredClone(basicStorageState) await page.addInitScript(async () => {
storageState.origins[0].localStorage[1].value = `const distance = sqrt(20) localStorage.setItem(
const part001 = startSketchOn('-XZ') 'persistCode',
|> startProfileAt([-6.95, 4.98], %) `
|> line([25.1, 0.41], %) const distance = sqrt(20)
|> line([0.73, -14.93], %) const part001 = startSketchOn('-XZ')
|> line([-23.44, 0.52], %) |> startProfileAt([-6.95, 4.98], %)
|> close(%) |> line([25.1, 0.41], %)
` |> line([0.73, -14.93], %)
test.use({ storageState }) |> line([-23.44, 0.52], %)
|> close(%)
`
)
})
test('Can extrude from the command bar', async ({ page, context }) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
@ -1055,9 +1072,9 @@ const part002 = startSketchOn('XY')
) )
}) })
test('ProgramMemory can be serialised', async ({ page, context }) => { test('ProgramMemory can be serialised', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await context.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part = startSketchOn('XY') `const part = startSketchOn('XY')
@ -1096,7 +1113,6 @@ test('ProgramMemory can be serialised', async ({ page, context }) => {
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({ test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
page, page,
context,
}) => { }) => {
const u = getUtils(page) const u = getUtils(page)
const selectionsSnippets = { const selectionsSnippets = {
@ -1105,7 +1121,7 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)', extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)',
editOnly: '|> startProfileAt([15.79, -14.6], %)', editOnly: '|> startProfileAt([15.79, -14.6], %)',
} }
await context.addInitScript( await page.addInitScript(
async ({ async ({
extrudeAndEditBlocked, extrudeAndEditBlocked,
extrudeAndEditBlockedInFunction, extrudeAndEditBlockedInFunction,
@ -1265,12 +1281,9 @@ test('Deselecting line tool should mean nothing happens on click', async ({
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
}) })
test('Can edit segments by dragging their handles', async ({ test('Can edit segments by dragging their handles', async ({ page }) => {
page,
context,
}) => {
const u = getUtils(page) const u = getUtils(page)
await context.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const part001 = startSketchOn('-XZ')
@ -1422,9 +1435,9 @@ test('Snap to close works (at any scale)', async ({ page }) => {
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate()) await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
}) })
test('Sketch on face', async ({ page, context }) => { test('Sketch on face', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await context.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const part001 = startSketchOn('-XZ')

View File

@ -7,16 +7,26 @@ import { spawn } from 'child_process'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import JSZip from 'jszip' import JSZip from 'jszip'
import path from 'path' import path from 'path'
import { basicSettings, basicStorageState } from './storageStates' import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// reducedMotion kills animations, which speeds up tests and reduces flakiness // reducedMotion kills animations, which speeds up tests and reduces flakiness
await page.emulateMedia({ reducedMotion: 'reduce' }) await page.emulateMedia({ reducedMotion: 'reduce' })
})
test.use({ // set the default settings
storageState: structuredClone(basicStorageState), await page.addInitScript(
async ({ token, settingsKey, settings }) => {
localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings)
},
{
token: secrets.token,
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS }),
}
)
}) })
test.setTimeout(60_000) test.setTimeout(60_000)
@ -364,13 +374,15 @@ test('extrude on each default plane should be stable', async ({
await u.removeCurrentCode() await u.removeCurrentCode()
// add makeCode('XZ') // add makeCode('XZ')
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await page.locator('.cm-content').fill(makeCode(plane)) await u.doAndWaitForImageDiff(
() => page.locator('.cm-content').fill(makeCode(plane)),
200
)
// wait for execution done // wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await page.getByText('Code').click() await page.getByText('Code').click()
await page.waitForTimeout(150)
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
@ -445,105 +457,108 @@ test('Draft segments should look right', async ({ page, context }) => {
}) })
}) })
test('Client side scene scale should match engine scale - Inch', async ({ test.describe('Client side scene scale should match engine scale', () => {
page, test('Inch', async ({ page }) => {
}) => { const u = getUtils(page)
const u = getUtils(page) await page.setViewportSize({ width: 1200, height: 500 })
await page.setViewportSize({ width: 1200, height: 500 }) const PUR = 400 / 37.5 //pixeltoUnitRatio
const PUR = 400 / 37.5 //pixeltoUnitRatio await page.goto('/')
await page.goto('/') await u.waitForAuthSkipAppStart()
await u.waitForAuthSkipAppStart() await u.openDebugPanel()
await u.openDebugPanel()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button // click on "Start Sketch" button
await u.clearCommandLogs() await u.clearCommandLogs()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(), () => page.getByRole('button', { name: 'Start Sketch' }).click(),
200 200
) )
// select a plane // select a plane
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')` `const part001 = startSketchOn('-XZ')`
) )
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ') .toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)`) |> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.closeDebugPanel() await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ') .toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %) |> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`) |> line([9.14, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click() await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ') .toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %) |> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %) |> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`) |> tangentialArcTo([27.34, -3.08], %)`)
// click tangential arc tool again to unequip it // click tangential arc tool again to unequip it
await page.getByRole('button', { name: 'Tangential Arc' }).click() await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// screen shot should show the sketch // screen shot should show the sketch
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
// exit sketch // exit sketch
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done // wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200) await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same. // second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
})
test.describe('Client side scene scale should match engine scale - Millimeters', () => {
const storageState = structuredClone(basicStorageState)
storageState.origins[0].localStorage[2].value = TOML.stringify({
settings: {
...basicSettings,
modeling: {
...basicSettings.modeling,
defaultUnit: 'mm',
},
},
})
test.use({
storageState,
}) })
test('Millimeters', async ({ page }) => { test('Millimeters', async ({ page }) => {
await page.addInitScript(
async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({
settings: {
...TEST_SETTINGS,
modeling: {
...TEST_SETTINGS.modeling,
defaultUnit: 'mm',
},
},
}),
}
)
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,9 +1,8 @@
import { SaveSettingsPayload } from 'lib/settings/settingsTypes' import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { secrets } from './secrets'
import * as TOML from '@iarna/toml'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
export const basicSettings = { export const TEST_SETTINGS_KEY = '/user.toml'
export const TEST_SETTINGS = {
app: { app: {
theme: Themes.Dark, theme: Themes.Dark,
onboardingStatus: 'dismissed', onboardingStatus: 'dismissed',
@ -22,19 +21,26 @@ export const basicSettings = {
}, },
} satisfies Partial<SaveSettingsPayload> } satisfies Partial<SaveSettingsPayload>
export const basicStorageState = { export const TEST_SETTINGS_ONBOARDING = {
cookies: [], ...TEST_SETTINGS,
origins: [ app: { ...TEST_SETTINGS.app, onboardingStatus: '/export ' },
{ } satisfies Partial<SaveSettingsPayload>
origin: 'http://localhost:3000',
localStorage: [ export const TEST_SETTINGS_CORRUPTED = {
{ name: 'TOKEN_PERSIST_KEY', value: secrets.token }, app: {
{ name: 'persistCode', value: '' }, theme: Themes.Dark,
{ onboardingStatus: 'dismissed',
name: '/user.toml', projectDirectory: 123 as any,
value: TOML.stringify({ settings: basicSettings }), },
}, modeling: {
], defaultUnit: 'invalid' as any,
}, mouseControls: `() => alert('hack the planet')` as any,
], showDebugPanel: true,
} },
projects: {
defaultProjectName: false as any,
},
textEditor: {
textWrapping: true,
},
} satisfies Partial<SaveSettingsPayload>

View File

@ -4,8 +4,8 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.15.0", "@codemirror/autocomplete": "^6.15.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2", "@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.18", "@headlessui/react": "^1.7.18",
@ -16,7 +16,7 @@
"@open-rpc/client-js": "^1.8.1", "@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6", "@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0", "@replit/codemirror-interact": "^6.3.0",
"@tauri-apps/api": "^2.0.0-beta.7", "@tauri-apps/api": "2.0.0-beta.7",
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2", "@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
"@tauri-apps/plugin-fs": "^2.0.0-beta.2", "@tauri-apps/plugin-fs": "^2.0.0-beta.2",
"@tauri-apps/plugin-http": "^2.0.0-beta.2", "@tauri-apps/plugin-http": "^2.0.0-beta.2",
@ -27,8 +27,8 @@
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@ts-stack/markdown": "^1.5.0", "@ts-stack/markdown": "^1.5.0",
"@tweenjs/tween.js": "^23.1.1", "@tweenjs/tween.js": "^23.1.1",
"@types/node": "^18.19.26", "@types/node": "^18.19.31",
"@types/react": "^18.2.73", "@types/react": "^18.2.75",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"@uiw/react-codemirror": "^4.21.25", "@uiw/react-codemirror": "^4.21.25",
"@xstate/inspect": "^0.8.0", "@xstate/inspect": "^0.8.0",
@ -36,8 +36,9 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"debounce-promise": "^3.1.2", "debounce-promise": "^3.1.2",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"formik": "^2.4.3", "formik": "^2.4.5",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"html2canvas-pro": "^1.4.3",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0", "json-rpc-2.0": "^1.6.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
@ -52,18 +53,19 @@
"react-modal-promise": "^1.0.2", "react-modal-promise": "^1.0.2",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"sketch-helpers": "^0.0.4", "sketch-helpers": "^0.0.4",
"swr": "^2.2.2", "swr": "^2.2.5",
"three": "^0.160.0", "three": "^0.163.0",
"toml": "^3.0.0", "toml": "^3.0.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.3", "typescript": "^5.4.3",
"ua-parser-js": "^1.0.37",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vitest": "^1.4.0", "vitest": "^1.4.0",
"vscode-jsonrpc": "^8.1.0", "vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-protocol": "^3.17.5",
"wasm-pack": "^0.12.1", "wasm-pack": "^0.12.1",
"web-vitals": "^3.5.2", "web-vitals": "^3.5.2",
"ws": "^8.13.0", "ws": "^8.16.0",
"xstate": "^4.38.2", "xstate": "^4.38.2",
"zustand": "^4.5.2" "zustand": "^4.5.2"
}, },
@ -115,18 +117,19 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.24.3", "@babel/preset-env": "^7.24.3",
"@playwright/test": "^1.39.0", "@playwright/test": "^1.43.0",
"@tauri-apps/cli": "^2.0.0-beta.12", "@tauri-apps/cli": "^2.0.0-beta.12",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/debounce-promise": "^3.1.9", "@types/debounce-promise": "^3.1.9",
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@types/three": "^0.160.0", "@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4", "@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.5", "@types/ws": "^8.5.10",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"@wdio/cli": "^8.24.3", "@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.24.3", "@wdio/globals": "^8.24.3",
@ -153,6 +156,6 @@
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest-webgl-canvas-mock": "^1.1.0", "vitest-webgl-canvas-mock": "^1.1.0",
"wait-on": "^7.2.0", "wait-on": "^7.2.0",
"yarn": "^1.22.19" "yarn": "^1.22.22"
} }
} }

View File

@ -1,5 +1,4 @@
import { defineConfig, devices } from '@playwright/test' import { defineConfig, devices } from '@playwright/test'
import { basicStorageState } from './e2e/playwright/storageStates'
/** /**
* Read environment variables from file. * Read environment variables from file.
@ -29,9 +28,6 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: 'on-first-retry',
/* Use a common shared localStorage */
storageState: basicStorageState,
}, },
/* Configure projects for major browsers */ /* Configure projects for major browsers */

10
src-tauri/Cargo.lock generated
View File

@ -69,9 +69,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.81" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]] [[package]]
name = "app" name = "app"
@ -2231,9 +2231,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad" name = "kittycad"
version = "0.2.63" version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a332250e08fd715ad3d5826e04d36da1c5bb42d0c1b1ff1f0598278b9ebf3c" checksum = "9e2897244f4600f863115561a0fd1cd7c87fca20253ffecfebc53ef642d0aceb"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -2333,7 +2333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.48.0", "windows-targets 0.52.4",
] ]
[[package]] [[package]]

View File

@ -16,7 +16,7 @@ tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
kittycad = "0.2.63" kittycad = "0.2.66"
oauth2 = "4.4.2" oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -1,4 +1,4 @@
import { useCallback, MouseEventHandler, useEffect } from 'react' import { useCallback, MouseEventHandler, useEffect, useRef } from 'react'
import { DebugPanel } from './components/DebugPanel' import { DebugPanel } from './components/DebugPanel'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { PaneType, useStore } from './useStore' import { PaneType, useStore } from './useStore'
@ -41,6 +41,9 @@ export function App() {
const navigate = useNavigate() const navigate = useNavigate()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const { onProjectOpen } = useLspContext() const { onProjectOpen } = useLspContext()
// We need the ref for the outermost div so we can screenshot the app for
// the coredump.
const ref = useRef<HTMLDivElement>(null)
const projectName = project?.name || null const projectName = project?.name || null
const projectPath = project?.path || null const projectPath = project?.path || null
@ -55,14 +58,20 @@ export function App() {
setOpenPanes, setOpenPanes,
didDragInStream, didDragInStream,
streamDimensions, streamDimensions,
setHtmlRef,
} = useStore((s) => ({ } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream, buttonDownInStream: s.buttonDownInStream,
openPanes: s.openPanes, openPanes: s.openPanes,
setOpenPanes: s.setOpenPanes, setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream, didDragInStream: s.didDragInStream,
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
setHtmlRef: s.setHtmlRef,
})) }))
useEffect(() => {
setHtmlRef(ref)
}, [ref])
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { const {
modeling: { showDebugPanel }, modeling: { showDebugPanel },
@ -140,6 +149,7 @@ export function App() {
<div <div
className="relative h-full flex flex-col" className="relative h-full flex flex-col"
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
ref={ref}
> >
<AppHeader <AppHeader
className={ className={

View File

@ -38,7 +38,7 @@ import {
getSketchQuaternion, getSketchQuaternion,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst' import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
import { Program, parse } from 'lang/wasm' import { Program, coreDump, parse } from 'lang/wasm'
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst' import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
import { TEST } from 'env' import { TEST } from 'env'
import { exportFromEngine } from 'lib/exportFromEngine' import { exportFromEngine } from 'lib/exportFromEngine'
@ -47,6 +47,8 @@ import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror' import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three' import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers' import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { CoreDumpManager } from 'lib/coredump'
import { useHotkeys } from 'react-hotkeys-hook'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -76,6 +78,15 @@ export const ModelingMachineProvider = ({
const token = auth?.context?.token const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null) const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token, theme.current) useSetupEngineManager(streamRef, token, theme.current)
const { htmlRef } = useStore((s) => ({
htmlRef: s.htmlRef,
}))
const coreDumpManager = new CoreDumpManager(
engineCommandManager,
htmlRef,
token
)
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
const { const {
isShiftDown, isShiftDown,

View File

@ -241,7 +241,6 @@ export function extrudeSketch(
pathToExtrudeArg: PathToNode pathToExtrudeArg: PathToNode
} { } {
const _node = { ...node } const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const { node: sketchExpression } = getNodeFromPath( const { node: sketchExpression } = getNodeFromPath(
_node, _node,
pathToNode, pathToNode,
@ -256,18 +255,14 @@ export function extrudeSketch(
) )
const isInPipeExpression = pipeExpression.type === 'PipeExpression' const isInPipeExpression = pipeExpression.type === 'PipeExpression'
const { node: variableDeclorator, shallowPath: pathToDecleration } = const { node: variableDeclarator, shallowPath: pathToDecleration } =
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator') getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
const extrudeCall = createCallExpressionStdLib('extrude', [ const extrudeCall = createCallExpressionStdLib('extrude', [
distance, distance,
shouldPipe shouldPipe
? createPipeSubstitution() ? createPipeSubstitution()
: { : createIdentifier(variableDeclarator.id.name),
type: 'Identifier',
...dumbyStartend,
name: variableDeclorator.id.name,
},
]) ])
if (shouldPipe) { if (shouldPipe) {
@ -277,7 +272,7 @@ export function extrudeSketch(
: [sketchExpression as any, extrudeCall] : [sketchExpression as any, extrudeCall]
) )
variableDeclorator.init = pipeChain variableDeclarator.init = pipeChain
const pathToExtrudeArg: PathToNode = [ const pathToExtrudeArg: PathToNode = [
...pathToDecleration, ...pathToDecleration,
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],

View File

@ -25,7 +25,8 @@ interface CommandInfo {
} }
} }
type WebSocketResponse = Models['OkWebSocketResponseData_type'] type WebSocketResponse = Models['WebSocketResponse_type']
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
interface ResultCommand extends CommandInfo { interface ResultCommand extends CommandInfo {
type: 'result' type: 'result'
@ -37,10 +38,19 @@ interface FailedCommand extends CommandInfo {
type: 'failed' type: 'failed'
errors: Models['FailureWebSocketResponse_type']['errors'] errors: Models['FailureWebSocketResponse_type']['errors']
} }
interface ResolveCommand {
id: string
commandType: CommandTypes
range: SourceRange
// We ALWAYS need the raw response because we pass it back to the rust side.
raw: WebSocketResponse
data?: Models['OkModelingCmdResponse_type']
errors?: Models['FailureWebSocketResponse_type']['errors']
}
interface PendingCommand extends CommandInfo { interface PendingCommand extends CommandInfo {
type: 'pending' type: 'pending'
promise: Promise<any> promise: Promise<any>
resolve: (val: any) => void resolve: (val: ResolveCommand) => void
} }
export interface ArtifactMap { export interface ArtifactMap {
@ -59,6 +69,15 @@ type Timeout = ReturnType<typeof setTimeout>
type ClientMetrics = Models['ClientMetrics_type'] type ClientMetrics = Models['ClientMetrics_type']
interface WebRTCClientMetrics extends ClientMetrics {
rtc_frame_height: number
rtc_frame_width: number
rtc_packets_lost: number
rtc_pli_count: number
rtc_pause_count: number
rtc_total_pauses_duration_sec: number
}
type Value<T, U> = U extends undefined type Value<T, U> = U extends undefined
? { type: T; value: U } ? { type: T; value: U }
: U extends void : U extends void
@ -224,7 +243,7 @@ class EngineConnection {
private onNewTrack: (track: NewTrackArgs) => void private onNewTrack: (track: NewTrackArgs) => void
// TODO: actual type is ClientMetrics // TODO: actual type is ClientMetrics
private webrtcStatsCollector?: () => Promise<ClientMetrics> public webrtcStatsCollector?: () => Promise<WebRTCClientMetrics>
private engineCommandManager: EngineCommandManager private engineCommandManager: EngineCommandManager
constructor({ constructor({
@ -396,7 +415,7 @@ class EngineConnection {
}, },
} }
this.webrtcStatsCollector = (): Promise<ClientMetrics> => { this.webrtcStatsCollector = (): Promise<WebRTCClientMetrics> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (mediaStream.getVideoTracks().length !== 1) { if (mediaStream.getVideoTracks().length !== 1) {
reject(new Error('too many video tracks to report')) reject(new Error('too many video tracks to report'))
@ -405,7 +424,7 @@ class EngineConnection {
let videoTrack = mediaStream.getVideoTracks()[0] let videoTrack = mediaStream.getVideoTracks()[0]
void this.pc?.getStats(videoTrack).then((videoTrackStats) => { void this.pc?.getStats(videoTrack).then((videoTrackStats) => {
let client_metrics: ClientMetrics = { let client_metrics: WebRTCClientMetrics = {
rtc_frames_decoded: 0, rtc_frames_decoded: 0,
rtc_frames_dropped: 0, rtc_frames_dropped: 0,
rtc_frames_received: 0, rtc_frames_received: 0,
@ -414,6 +433,12 @@ class EngineConnection {
rtc_jitter_sec: 0.0, rtc_jitter_sec: 0.0,
rtc_keyframes_decoded: 0, rtc_keyframes_decoded: 0,
rtc_total_freezes_duration_sec: 0.0, rtc_total_freezes_duration_sec: 0.0,
rtc_frame_height: 0,
rtc_frame_width: 0,
rtc_packets_lost: 0,
rtc_pli_count: 0,
rtc_pause_count: 0,
rtc_total_pauses_duration_sec: 0.0,
} }
// TODO(paultag): Since we can technically have multiple WebRTC // TODO(paultag): Since we can technically have multiple WebRTC
@ -439,6 +464,13 @@ class EngineConnection {
videoTrackReport.keyFramesDecoded || 0 videoTrackReport.keyFramesDecoded || 0
client_metrics.rtc_total_freezes_duration_sec = client_metrics.rtc_total_freezes_duration_sec =
videoTrackReport.totalFreezesDuration || 0 videoTrackReport.totalFreezesDuration || 0
client_metrics.rtc_frame_height =
videoTrackReport.frameHeight || 0
client_metrics.rtc_frame_width =
videoTrackReport.frameWidth || 0
client_metrics.rtc_packets_lost =
videoTrackReport.packetsLost || 0
client_metrics.rtc_pli_count = videoTrackReport.pliCount || 0
} else if (videoTrackReport.type === 'transport') { } else if (videoTrackReport.type === 'transport') {
// videoTrackReport.bytesReceived, // videoTrackReport.bytesReceived,
// videoTrackReport.bytesSent, // videoTrackReport.bytesSent,
@ -827,7 +859,7 @@ export type CommandLog =
} }
| { | {
type: 'receive-reliable' type: 'receive-reliable'
data: WebSocketResponse data: OkWebSocketResponseData
id: string id: string
cmd_type?: string cmd_type?: string
} }
@ -1020,7 +1052,11 @@ export class EngineCommandManager {
message.resp.type === 'modeling' && message.resp.type === 'modeling' &&
message.request_id message.request_id
) { ) {
this.handleModelingCommand(message.resp, message.request_id) this.handleModelingCommand(
message.resp,
message.request_id,
message
)
} else if ( } else if (
!message.success && !message.success &&
message.request_id && message.request_id &&
@ -1069,7 +1105,11 @@ export class EngineCommandManager {
} }
this.engineConnection?.send(resizeCmd) this.engineConnection?.send(resizeCmd)
} }
handleModelingCommand(message: WebSocketResponse, id: string) { handleModelingCommand(
message: OkWebSocketResponseData,
id: string,
raw: WebSocketResponse
) {
if (message.type !== 'modeling') { if (message.type !== 'modeling') {
return return
} }
@ -1081,7 +1121,7 @@ export class EngineCommandManager {
command?.additionalData?.type === 'batch-ids' command?.additionalData?.type === 'batch-ids'
) { ) {
command.additionalData.ids.forEach((id) => { command.additionalData.ids.forEach((id) => {
this.handleModelingCommand(message, id) this.handleModelingCommand(message, id, raw)
}) })
// batch artifact is just a container, we don't need to keep it // batch artifact is just a container, we don't need to keep it
// once we process all the commands inside it // once we process all the commands inside it
@ -1092,7 +1132,7 @@ export class EngineCommandManager {
commandType: command.commandType, commandType: command.commandType,
range: command.range, range: command.range,
data: modelingResponse, data: modelingResponse,
raw: message, raw,
}) })
return return
} }
@ -1116,7 +1156,7 @@ export class EngineCommandManager {
commandType: command.commandType, commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined, parentId: command.parentId ? command.parentId : undefined,
data: modelingResponse, data: modelingResponse,
raw: message, raw,
} as const } as const
this.artifactMap[id] = artifact this.artifactMap[id] = artifact
if ( if (
@ -1161,7 +1201,7 @@ export class EngineCommandManager {
commandType: command.commandType, commandType: command.commandType,
range: command.range, range: command.range,
data: modelingResponse, data: modelingResponse,
raw: message, raw,
}) })
} else if (sceneCommand && sceneCommand.type === 'pending') { } else if (sceneCommand && sceneCommand.type === 'pending') {
const resolve = sceneCommand.resolve const resolve = sceneCommand.resolve
@ -1172,7 +1212,7 @@ export class EngineCommandManager {
commandType: sceneCommand.commandType, commandType: sceneCommand.commandType,
parentId: sceneCommand.parentId ? sceneCommand.parentId : undefined, parentId: sceneCommand.parentId ? sceneCommand.parentId : undefined,
data: modelingResponse, data: modelingResponse,
raw: message, raw,
} as const } as const
this.sceneCommandArtifacts[id] = artifact this.sceneCommandArtifacts[id] = artifact
resolve({ resolve({
@ -1180,6 +1220,7 @@ export class EngineCommandManager {
commandType: sceneCommand.commandType, commandType: sceneCommand.commandType,
range: sceneCommand.range, range: sceneCommand.range,
data: modelingResponse, data: modelingResponse,
raw,
}) })
} else if (command) { } else if (command) {
this.artifactMap[id] = { this.artifactMap[id] = {
@ -1188,7 +1229,7 @@ export class EngineCommandManager {
range: command?.range, range: command?.range,
pathToNode: command?.pathToNode, pathToNode: command?.pathToNode,
data: modelingResponse, data: modelingResponse,
raw: message, raw,
} }
} else { } else {
this.sceneCommandArtifacts[id] = { this.sceneCommandArtifacts[id] = {
@ -1197,15 +1238,14 @@ export class EngineCommandManager {
range: sceneCommand?.range, range: sceneCommand?.range,
pathToNode: sceneCommand?.pathToNode, pathToNode: sceneCommand?.pathToNode,
data: modelingResponse, data: modelingResponse,
raw: message, raw,
} }
} }
} }
handleFailedModelingCommand({ handleFailedModelingCommand(raw: WebSocketResponse) {
request_id, const id = raw.request_id
errors, const failed = raw as Models['FailureWebSocketResponse_type']
}: Models['FailureWebSocketResponse_type']) { const errors = failed.errors
const id = request_id
if (!id) return if (!id) return
const command = this.artifactMap[id] const command = this.artifactMap[id]
if (command && command.type === 'pending') { if (command && command.type === 'pending') {
@ -1223,6 +1263,7 @@ export class EngineCommandManager {
commandType: command.commandType, commandType: command.commandType,
range: command.range, range: command.range,
errors, errors,
raw,
}) })
} else { } else {
this.artifactMap[id] = { this.artifactMap[id] = {
@ -1573,7 +1614,14 @@ export class EngineCommandManager {
command: commandStr, command: commandStr,
ast: this.getAst(), ast: this.getAst(),
idToRangeMap, idToRangeMap,
}).then(({ raw }) => JSON.stringify(raw)) }).then(({ raw }: { raw: WebSocketResponse | undefined | null }) => {
if (raw === undefined || raw === null) {
throw new Error(
'returning modeling cmd response to the rust side is undefined or null'
)
}
return JSON.stringify(raw)
})
} }
commandResult(id: string): Promise<any> { commandResult(id: string): Promise<any> {
const command = this.artifactMap[id] const command = this.artifactMap[id]

View File

@ -10,6 +10,7 @@ import init, {
ServerConfig, ServerConfig,
copilot_lsp_run, copilot_lsp_run,
kcl_lsp_run, kcl_lsp_run,
coredump,
} from '../wasm-lib/pkg/wasm_lib' } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
@ -21,6 +22,9 @@ import type { Token } from '../wasm-lib/kcl/bindings/Token'
import { Coords2d } from './std/sketch' import { Coords2d } from './std/sketch'
import { fileSystemManager } from 'lang/std/fileSystemManager' import { fileSystemManager } from 'lang/std/fileSystemManager'
import { DEV } from 'env' import { DEV } from 'env'
import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
export type { Program } from '../wasm-lib/kcl/bindings/Program' export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value' export type { Value } from '../wasm-lib/kcl/bindings/Value'
@ -311,3 +315,18 @@ export async function kclLspRun(config: ServerConfig, token: string) {
// We can't restart here because a moved value, we should do this another way. // We can't restart here because a moved value, we should do this another way.
} }
} }
export async function coreDump(
coreDumpManager: CoreDumpManager,
openGithubIssue: boolean = false
): Promise<AppInfo> {
try {
const dump: AppInfo = await coredump(coreDumpManager)
if (openGithubIssue && dump.github_issue_url) {
openWindow(dump.github_issue_url)
}
return dump
} catch (e: any) {
throw new Error(`Error getting core dump: ${e}`)
}
}

150
src/lib/coredump.ts Normal file
View File

@ -0,0 +1,150 @@
import { EngineCommandManager } from 'lang/std/engineConnection'
import { WebrtcStats } from 'wasm-lib/kcl/bindings/WebrtcStats'
import { OsInfo } from 'wasm-lib/kcl/bindings/OsInfo'
import { isTauri } from 'lib/isTauri'
import {
platform as tauriPlatform,
arch as tauriArch,
version as tauriKernelVersion,
} from '@tauri-apps/plugin-os'
import { APP_VERSION } from 'routes/Settings'
import { UAParser } from 'ua-parser-js'
import screenshot from 'lib/screenshot'
import React from 'react'
import { VITE_KC_API_BASE_URL } from 'env'
// This is a class for getting all the values from the JS world to pass to the Rust world
// for a core dump.
export class CoreDumpManager {
engineCommandManager: EngineCommandManager
htmlRef: React.RefObject<HTMLDivElement> | null
token: string | undefined
baseUrl: string = VITE_KC_API_BASE_URL
constructor(
engineCommandManager: EngineCommandManager,
htmlRef: React.RefObject<HTMLDivElement> | null,
token: string | undefined
) {
this.engineCommandManager = engineCommandManager
this.htmlRef = htmlRef
this.token = token
}
// Get the token.
authToken(): string {
if (!this.token) {
throw new Error('Token not set')
}
return this.token
}
// Get the base url.
baseApiUrl(): string {
return this.baseUrl
}
// Get the version of the app from the package.json.
version(): string {
return APP_VERSION
}
// Get the os information.
getOsInfo(): Promise<string> {
if (this.isTauri()) {
return tauriArch()
.catch((error: any) => {
throw new Error(`Error getting arch: ${error}`)
})
.then((arch: string) => {
return tauriPlatform()
.catch((error: any) => {
throw new Error(`Error getting platform: ${error}`)
})
.then((platform: string) => {
return tauriKernelVersion()
.catch((error: any) => {
throw new Error(`Error getting kernel version: ${error}`)
})
.then((kernelVersion: string) => {
const osinfo: OsInfo = {
platform,
arch,
version: kernelVersion,
}
return JSON.stringify(osinfo)
})
})
})
}
const userAgent = window.navigator.userAgent || 'unknown browser'
if (userAgent === 'unknown browser') {
const osinfo: OsInfo = {
platform: userAgent,
arch: userAgent,
version: userAgent,
}
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
}
const parser = new UAParser(userAgent)
const parserResults = parser.getResult()
const osinfo: OsInfo = {
platform: parserResults.os.name,
arch: parserResults.cpu.architecture,
version: parserResults.os.version,
browser: userAgent,
}
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
}
isTauri(): boolean {
return isTauri()
}
getWebrtcStats(): Promise<string> {
if (!this.engineCommandManager.engineConnection) {
throw new Error('Engine connection not initialized')
}
if (!this.engineCommandManager.engineConnection.webrtcStatsCollector) {
throw new Error('Engine webrtcStatsCollector not initialized')
}
return this.engineCommandManager.engineConnection
.webrtcStatsCollector()
.catch((error: any) => {
throw new Error(`Error getting webrtc stats: ${error}`)
})
.then((stats: any) => {
const webrtcStats: WebrtcStats = {
packets_lost: stats.rtc_packets_lost,
frames_received: stats.rtc_frames_received,
frame_width: stats.rtc_frame_width,
frame_height: stats.rtc_frame_height,
frame_rate: stats.rtc_frames_per_second,
key_frames_decoded: stats.rtc_keyframes_decoded,
frames_dropped: stats.rtc_frames_dropped,
pause_count: stats.rtc_pause_count,
total_pauses_duration: stats.rtc_total_pauses_duration_sec,
freeze_count: stats.rtc_freeze_count,
total_freezes_duration: stats.rtc_total_freezes_duration_sec,
pli_count: stats.rtc_pli_count,
jitter: stats.rtc_jitter_sec,
}
return JSON.stringify(webrtcStats)
})
}
// Return a data URL (png format) of the screenshot of the current page.
screenshot(): Promise<string> {
return screenshot(this.htmlRef)
.then((screenshot: string) => {
return screenshot
})
.catch((error: any) => {
throw new Error(`Error getting screenshot: ${error}`)
})
}
}

View File

@ -29,11 +29,11 @@ const bracket = startSketchOn('XY')
|> extrude(width, %) |> extrude(width, %)
|> fillet({ |> fillet({
radius: filletR, radius: filletR,
tags: [getNextAdjacentEdge('innerEdge', %)] tags: [getPreviousAdjacentEdge('innerEdge', %)]
}, %) }, %)
|> fillet({ |> fillet({
radius: filletR + thickness, radius: filletR + thickness,
tags: [getNextAdjacentEdge('outerEdge', %)] tags: [getPreviousAdjacentEdge('outerEdge', %)]
}, %)` }, %)`
function findLineInExampleCode({ function findLineInExampleCode({

11
src/lib/openWindow.ts Normal file
View File

@ -0,0 +1,11 @@
import { isTauri } from 'lib/isTauri'
import { open as tauriOpen } from '@tauri-apps/plugin-shell'
// Open a new browser window tauri style or browser style.
export default async function openWindow(url: string) {
if (isTauri()) {
await tauriOpen(url)
} else {
window.open(url, '_blank')
}
}

21
src/lib/screenshot.ts Normal file
View File

@ -0,0 +1,21 @@
import React from 'react'
import html2canvas from 'html2canvas-pro'
// Return a data URL (png format) of the screenshot of the current page.
export default async function screenshot(
htmlRef: React.RefObject<HTMLDivElement> | null
): Promise<string> {
if (htmlRef === null) {
throw new Error('htmlRef is null')
}
if (htmlRef.current === null) {
throw new Error('htmlRef is null')
}
return html2canvas(htmlRef.current)
.then((canvas) => {
return canvas.toDataURL()
})
.catch((error) => {
throw error
})
}

View File

@ -6,7 +6,7 @@ import {
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { Themes } from './theme' import { Themes } from './theme'
type WebSocketResponse = Models['OkWebSocketResponseData_type'] type WebSocketResponse = Models['WebSocketResponse_type']
class MockEngineCommandManager { class MockEngineCommandManager {
// eslint-disable-next-line @typescript-eslint/no-useless-constructor // eslint-disable-next-line @typescript-eslint/no-useless-constructor
@ -27,9 +27,12 @@ class MockEngineCommandManager {
command: EngineCommand command: EngineCommand
}): Promise<any> { }): Promise<any> {
const response: WebSocketResponse = { const response: WebSocketResponse = {
type: 'modeling', success: true,
data: { resp: {
modeling_response: { type: 'empty' }, type: 'modeling',
data: {
modeling_response: { type: 'empty' },
},
}, },
} }
return Promise.resolve(JSON.stringify(response)) return Promise.resolve(JSON.stringify(response))

View File

@ -37,8 +37,9 @@ import {
shouldShowSettingInput, shouldShowSettingInput,
} from 'lib/settings/settingsUtils' } from 'lib/settings/settingsUtils'
export const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
export const Settings = () => { export const Settings = () => {
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
const navigate = useNavigate() const navigate = useNavigate()
const close = () => navigate(location.pathname.replace(paths.SETTINGS, '')) const close = () => navigate(location.pathname.replace(paths.SETTINGS, ''))
const location = useLocation() const location = useLocation()

View File

@ -81,6 +81,8 @@ export interface StoreState {
streamWidth: number streamWidth: number
streamHeight: number streamHeight: number
}) => void }) => void
setHtmlRef: (ref: React.RefObject<HTMLDivElement>) => void
htmlRef: React.RefObject<HTMLDivElement> | null
showHomeMenu: boolean showHomeMenu: boolean
setHomeShowMenu: (showMenu: boolean) => void setHomeShowMenu: (showMenu: boolean) => void
@ -132,6 +134,10 @@ export const useStore = create<StoreState>()(
setButtonDownInStream: (buttonDownInStream) => { setButtonDownInStream: (buttonDownInStream) => {
set({ buttonDownInStream }) set({ buttonDownInStream })
}, },
setHtmlRef: (htmlRef) => {
set({ htmlRef })
},
htmlRef: null,
didDragInStream: false, didDragInStream: false,
setDidDragInStream: (didDragInStream) => { setDidDragInStream: (didDragInStream) => {
set({ didDragInStream }) set({ didDragInStream })

View File

@ -155,9 +155,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.81" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -1373,6 +1373,12 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "git_rev"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60884563ea313b5037683cd5d44f1e14e9cb07b08543756242a65887f9cff48e"
[[package]] [[package]]
name = "gloo-utils" name = "gloo-utils"
version = "0.2.0" version = "0.2.0"
@ -1864,7 +1870,9 @@ dependencies = [
"databake", "databake",
"derive-docs", "derive-docs",
"expectorate", "expectorate",
"form_urlencoded",
"futures", "futures",
"git_rev",
"gltf-json", "gltf-json",
"iai", "iai",
"image", "image",
@ -1912,9 +1920,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad" name = "kittycad"
version = "0.2.63" version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a332250e08fd715ad3d5826e04d36da1c5bb42d0c1b1ff1f0598278b9ebf3c" checksum = "9e2897244f4600f863115561a0fd1cd7c87fca20253ffecfebc53ef642d0aceb"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1950,9 +1958,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan" name = "kittycad-execution-plan"
version = "0.1.3" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e913f8e5f3ef7928cddca2e7b53c6582d7be6a8f900d18ce6c31c04083056270" checksum = "acf8ffb148bd09de8889a8a2b3075a23ee86446c3a6e1c6dcf66b40fdc778158"
dependencies = [ dependencies = [
"bytes", "bytes",
"gltf-json", "gltf-json",
@ -1995,9 +2003,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.10" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41beb7d9b776df93fd449604de5c447e33c7bd3326fd590002dc18cf5f08166" checksum = "e94b75afb2ab9fe824bb483d2475d419bb15fbe850466c45962999b54456173a"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -2035,9 +2043,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-session" name = "kittycad-modeling-session"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ee3a24232a086ec12ae4cfee443485c22e6c6959936d861006fa13bebef0904" checksum = "bae9bc47fcc3cc30727b35e738c35666b97e1e5f48f3f4c60ddaeccb69b66559"
dependencies = [ dependencies = [
"futures", "futures",
"kittycad", "kittycad",
@ -2815,9 +2823,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]

View File

@ -59,12 +59,12 @@ members = [
] ]
[workspace.dependencies] [workspace.dependencies]
kittycad = { version = "0.2.63", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.2.66", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = "0.1.3" kittycad-execution-plan = "0.1.4"
kittycad-execution-plan-macros = "0.1.9" kittycad-execution-plan-macros = "0.1.9"
kittycad-execution-plan-traits = "0.1.14" kittycad-execution-plan-traits = "0.1.14"
kittycad-modeling-cmds = "0.2.10" kittycad-modeling-cmds = "0.2.17"
kittycad-modeling-session = "0.1.2" kittycad-modeling-session = "0.1.3"
[[test]] [[test]]
name = "executor" name = "executor"

View File

@ -23,7 +23,7 @@ serde_tokenstream = "0.2"
syn = { version = "2.0.58", features = ["full"] } syn = { version = "2.0.58", features = ["full"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.81" anyhow = "1.0.82"
expectorate = "1.1.0" expectorate = "1.1.0"
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
rustfmt-wrapper = "0.2.1" rustfmt-wrapper = "0.2.1"

View File

@ -777,7 +777,7 @@ fn generate_code_block_test(
} }
let ws = client let ws = client
.modeling() .modeling()
.commands_ws(None, None, None, None, None, Some(false)) .commands_ws(None, None, None, None, None,None, Some(false))
.await.unwrap(); .await.unwrap();
let tokens = crate::token::lexer(#code_block); let tokens = crate::token::lexer(#code_block);

View File

@ -11,15 +11,18 @@ keywords = ["kcl", "KittyCAD", "CAD"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = { version = "1.0.81", features = ["backtrace"] } anyhow = { version = "1.0.82", features = ["backtrace"] }
async-recursion = "1.1.0" async-recursion = "1.1.0"
async-trait = "0.1.79" async-trait = "0.1.79"
base64 = "0.22.0"
chrono = "0.4.37" chrono = "0.4.37"
clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true } clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3" dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] } databake = { version = "0.1.7", features = ["derive"] }
derive-docs = { version = "0.1.12", path = "../derive-docs" } derive-docs = { version = "0.1.13", path = "../derive-docs" }
form_urlencoded = "1.2.1"
futures = { version = "0.3.30" } futures = { version = "0.3.30" }
git_rev = "0.1.0"
gltf-json = "1.4.0" gltf-json = "1.4.0"
kittycad = { workspace = true } kittycad = { workspace = true }
kittycad-execution-plan-macros = { workspace = true } kittycad-execution-plan-macros = { workspace = true }

View File

@ -0,0 +1,58 @@
//! Functions for getting core dump information via local rust.
use anyhow::Result;
use crate::coredump::CoreDump;
#[derive(Debug, Clone)]
pub struct CoreDumper {}
impl CoreDumper {
pub fn new() -> Self {
CoreDumper {}
}
}
impl Default for CoreDumper {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait(?Send)]
impl CoreDump for CoreDumper {
fn token(&self) -> Result<String> {
Ok(std::env::var("KITTYCAD_API_TOKEN").unwrap_or_default())
}
fn base_api_url(&self) -> Result<String> {
Ok("https://api.zoo.dev".to_string())
}
fn version(&self) -> Result<String> {
Ok(env!("CARGO_PKG_VERSION").to_string())
}
async fn os(&self) -> Result<crate::coredump::OsInfo> {
Ok(crate::coredump::OsInfo {
platform: Some(std::env::consts::OS.to_string()),
arch: Some(std::env::consts::ARCH.to_string()),
version: None,
browser: None,
})
}
fn is_tauri(&self) -> Result<bool> {
Ok(false)
}
async fn get_webrtc_stats(&self) -> Result<crate::coredump::WebrtcStats> {
// TODO: we could actually implement this.
Ok(crate::coredump::WebrtcStats::default())
}
async fn screenshot(&self) -> Result<String> {
// Take a screenshot of the engine.
todo!()
}
}

View File

@ -0,0 +1,193 @@
//! Core dump related structures and functions.
#[cfg(not(target_arch = "wasm32"))]
pub mod local;
#[cfg(target_arch = "wasm32")]
pub mod wasm;
use anyhow::Result;
use base64::Engine;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[async_trait::async_trait(?Send)]
pub trait CoreDump: Clone {
/// Return the authentication token.
fn token(&self) -> Result<String>;
fn base_api_url(&self) -> Result<String>;
fn version(&self) -> Result<String>;
async fn os(&self) -> Result<OsInfo>;
fn is_tauri(&self) -> Result<bool>;
async fn get_webrtc_stats(&self) -> Result<WebrtcStats>;
/// Return a screenshot of the app.
async fn screenshot(&self) -> Result<String>;
/// Get a screenshot of the app and upload it to public cloud storage.
async fn upload_screenshot(&self) -> Result<String> {
let screenshot = self.screenshot().await?;
let cleaned = screenshot.trim_start_matches("data:image/png;base64,");
// Create the zoo client.
let mut zoo = kittycad::Client::new(self.token()?);
zoo.set_base_url(&self.base_api_url()?);
// Base64 decode the screenshot.
let data = base64::engine::general_purpose::STANDARD.decode(cleaned)?;
// Upload the screenshot.
let links = zoo
.meta()
.create_debug_uploads(vec![kittycad::types::multipart::Attachment {
name: "".to_string(),
filename: Some("modeling-app/core-dump-screenshot.png".to_string()),
content_type: Some("image/png".to_string()),
data,
}])
.await
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
if links.is_empty() {
anyhow::bail!("Failed to upload screenshot");
}
Ok(links[0].clone())
}
/// Dump the app info.
async fn dump(&self) -> Result<AppInfo> {
let webrtc_stats = self.get_webrtc_stats().await?;
let os = self.os().await?;
let screenshot_url = self.upload_screenshot().await?;
let mut app_info = AppInfo {
version: self.version()?,
git_rev: git_rev::try_revision_string!().map_or_else(|| "unknown".to_string(), |s| s.to_string()),
timestamp: chrono::Utc::now(),
tauri: self.is_tauri()?,
os,
webrtc_stats,
github_issue_url: None,
};
app_info.set_github_issue_url(&screenshot_url)?;
Ok(app_info)
}
}
/// The app info structure.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct AppInfo {
/// The version of the app.
pub version: String,
/// The git revision of the app.
pub git_rev: String,
/// A timestamp of the core dump.
#[ts(type = "string")]
pub timestamp: chrono::DateTime<chrono::Utc>,
/// If the app is running in tauri or the browser.
pub tauri: bool,
/// The os info.
pub os: OsInfo,
/// The webrtc stats.
pub webrtc_stats: WebrtcStats,
/// A GitHub issue url to report the core dump.
/// This gets prepoulated with all the core dump info.
#[serde(skip_serializing_if = "Option::is_none")]
pub github_issue_url: Option<String>,
}
impl AppInfo {
/// Set the github issue url.
pub fn set_github_issue_url(&mut self, screenshot_url: &str) -> Result<()> {
let tauri_or_browser_label = if self.tauri { "tauri" } else { "browser" };
let labels = ["coredump", "bug", tauri_or_browser_label];
let body = format!(
r#"[Insert a description of the issue here]
![Screenshot]({})
<details>
<summary><b>Core Dump</b></summary>
```json
{}
```
</details>
"#,
screenshot_url,
serde_json::to_string_pretty(&self)?
);
let urlencoded: String = form_urlencoded::byte_serialize(body.as_bytes()).collect();
self.github_issue_url = Some(format!(
r#"https://github.com/{}/{}/issues/new?body={}&labels={}"#,
"KittyCAD",
"modeling-app",
urlencoded,
labels.join(",")
));
Ok(())
}
}
/// The os info structure.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct OsInfo {
/// The platform the app is running on.
#[serde(skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
/// The architecture the app is running on.
#[serde(skip_serializing_if = "Option::is_none")]
pub arch: Option<String>,
/// The kernel version.
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
/// Information about the browser.
#[serde(skip_serializing_if = "Option::is_none")]
pub browser: Option<String>,
}
/// The webrtc stats structure.
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct WebrtcStats {
/// The packets lost.
pub packets_lost: u32,
/// The frames received.
pub frames_received: u32,
/// The frame width.
pub frame_width: f32,
/// The frame height.
pub frame_height: f32,
/// The frame rate.
pub frame_rate: f32,
/// The number of key frames decoded.
pub key_frames_decoded: u32,
/// The number of frames dropped.
pub frames_dropped: u32,
/// The pause count.
pub pause_count: u32,
/// The total pauses duration.
pub total_pauses_duration: f32,
/// The freeze count.
pub freeze_count: u32,
/// The total freezes duration.
pub total_freezes_duration: f32,
/// The pli count.
pub pli_count: u32,
/// Packet jitter for this synchronizing source, measured in seconds.
pub jitter: f32,
}

View File

@ -0,0 +1,134 @@
//! Functions for getting core dump information via wasm.
use anyhow::Result;
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{coredump::CoreDump, wasm::JsFuture};
#[wasm_bindgen(module = "/../../lib/coredump.ts")]
extern "C" {
#[derive(Debug, Clone)]
pub type CoreDumpManager;
#[wasm_bindgen(method, js_name = authToken, catch)]
fn auth_token(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
#[wasm_bindgen(method, js_name = baseApiUrl, catch)]
fn baseApiUrl(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
#[wasm_bindgen(method, js_name = version, catch)]
fn version(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
#[wasm_bindgen(method, js_name = getOsInfo, catch)]
fn get_os_info(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = isTauri, catch)]
fn is_tauri(this: &CoreDumpManager) -> Result<bool, js_sys::Error>;
#[wasm_bindgen(method, js_name = getWebrtcStats, catch)]
fn get_webrtc_stats(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = screenshot, catch)]
fn screenshot(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
}
#[derive(Debug, Clone)]
pub struct CoreDumper {
manager: CoreDumpManager,
}
impl CoreDumper {
pub fn new(manager: CoreDumpManager) -> Self {
CoreDumper { manager }
}
}
unsafe impl Send for CoreDumper {}
unsafe impl Sync for CoreDumper {}
#[async_trait::async_trait(?Send)]
impl CoreDump for CoreDumper {
fn token(&self) -> Result<String> {
self.manager
.auth_token()
.map_err(|e| anyhow::anyhow!("Failed to get response from token: {:?}", e))
}
fn base_api_url(&self) -> Result<String> {
self.manager
.baseApiUrl()
.map_err(|e| anyhow::anyhow!("Failed to get response from base api url: {:?}", e))
}
fn version(&self) -> Result<String> {
self.manager
.version()
.map_err(|e| anyhow::anyhow!("Failed to get response from version: {:?}", e))
}
async fn os(&self) -> Result<crate::coredump::OsInfo> {
let promise = self
.manager
.get_os_info()
.map_err(|e| anyhow::anyhow!("Failed to get promise from get os info: {:?}", e))?;
let value = JsFuture::from(promise)
.await
.map_err(|e| anyhow::anyhow!("Failed to get response from os info: {:?}", e))?;
// Parse the value as a string.
let s = value
.as_string()
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from os info: `{:?}`", value))?;
let os: crate::coredump::OsInfo =
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse os info: {:?}", e))?;
Ok(os)
}
fn is_tauri(&self) -> Result<bool> {
self.manager
.is_tauri()
.map_err(|e| anyhow::anyhow!("Failed to get response from is tauri: {:?}", e))
}
async fn get_webrtc_stats(&self) -> Result<crate::coredump::WebrtcStats> {
let promise = self
.manager
.get_webrtc_stats()
.map_err(|e| anyhow::anyhow!("Failed to get promise from get webrtc stats: {:?}", e))?;
let value = JsFuture::from(promise)
.await
.map_err(|e| anyhow::anyhow!("Failed to get response from webrtc stats: {:?}", e))?;
// Parse the value as a string.
let s = value
.as_string()
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from webrtc stats: `{:?}`", value))?;
let stats: crate::coredump::WebrtcStats =
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse webrtc stats: {:?}", e))?;
Ok(stats)
}
async fn screenshot(&self) -> Result<String> {
let promise = self
.manager
.screenshot()
.map_err(|e| anyhow::anyhow!("Failed to get promise from get screenshot: {:?}", e))?;
let value = JsFuture::from(promise)
.await
.map_err(|e| anyhow::anyhow!("Failed to get response from screenshot: {:?}", e))?;
// Parse the value as a string.
let s = value
.as_string()
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from screenshot: `{:?}`", value))?;
Ok(s)
}
}

View File

@ -99,13 +99,25 @@ impl crate::engine::EngineManager for EngineConnection {
}) })
})?; })?;
let modeling_result: kittycad::types::OkWebSocketResponseData = serde_json::from_str(&s).map_err(|e| { let ws_result: kittycad::types::WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails { KclError::Engine(KclErrorDetails {
message: format!("Failed to deserialize response from engine: {:?}", e), message: format!("Failed to deserialize response from engine: {:?}", e),
source_ranges: vec![source_range], source_ranges: vec![source_range],
}) })
})?; })?;
Ok(modeling_result) if let Some(data) = &ws_result.resp {
Ok(data.clone())
} else if let Some(errors) = &ws_result.errors {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", errors),
source_ranges: vec![source_range],
}))
} else {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", ws_result),
source_ranges: vec![source_range],
}))
}
} }
} }

View File

@ -28,6 +28,16 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>, id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>; ) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
fn push_to_batch(
&self,
cmd_id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
) {
let req = WebSocketRequest::ModelingCmdReq { cmd, cmd_id };
self.batch().lock().unwrap().push((req, source_range))
}
async fn send_modeling_cmd( async fn send_modeling_cmd(
&self, &self,
id: uuid::Uuid, id: uuid::Uuid,
@ -85,6 +95,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
let batched_requests = WebSocketRequest::ModelingCmdBatchReq { let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
requests, requests,
batch_id: uuid::Uuid::new_v4(), batch_id: uuid::Uuid::new_v4(),
responses: Some(false),
}; };
let final_req = if self.batch().lock().unwrap().len() == 1 { let final_req = if self.batch().lock().unwrap().len() == 1 {
@ -93,7 +104,8 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
} else { } else {
batched_requests batched_requests
}; };
// println!("Running batch: {final_req:#?}"); // TODO: Uncomment this.
debug_batch(&final_req);
// Create the map of original command IDs to source range. // Create the map of original command IDs to source range.
// This is for the wasm side, kurt needs it for selections. // This is for the wasm side, kurt needs it for selections.
@ -117,7 +129,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// We pop off the responses to cleanup our mappings. // We pop off the responses to cleanup our mappings.
let id_final = match final_req { let id_final = match final_req {
WebSocketRequest::ModelingCmdBatchReq { requests: _, batch_id } => batch_id, WebSocketRequest::ModelingCmdBatchReq {
requests: _,
batch_id,
responses: _,
} => batch_id,
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => cmd_id, WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => cmd_id,
_ => { _ => {
return Err(KclError::Engine(KclErrorDetails { return Err(KclError::Engine(KclErrorDetails {
@ -186,3 +202,18 @@ pub fn is_cmd_with_return_values(cmd: &kittycad::types::ModelingCmd) -> bool {
true true
} }
#[allow(dead_code)] // Only used in debugging.
fn debug_batch(msg: &WebSocketRequest) {
match msg {
WebSocketRequest::ModelingCmdReq { cmd, .. } => {
println!("[ {:?} ]", cmd);
}
WebSocketRequest::ModelingCmdBatchReq { requests, .. } => {
let names: Vec<_> = requests.iter().map(|req| format!("{:?}", req.cmd)).collect();
println!("[ {} ]", names.join(", "))
}
other => panic!("this isn't a modeling command or batch: {other:?}"),
}
}

View File

@ -1226,7 +1226,7 @@ pub(crate) async fn execute(
} }
// Flush the batch queue. // Flush the batch queue.
ctx.engine.flush_batch(SourceRange::default()).await?; ctx.engine.flush_batch(SourceRange([program.end, program.end])).await?;
Ok(memory.clone()) Ok(memory.clone())
} }

View File

@ -8,6 +8,7 @@ pub use local::FileManager;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]
pub mod wasm; pub mod wasm;
use anyhow::Result; use anyhow::Result;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]

View File

@ -5,6 +5,7 @@
#![recursion_limit = "1024"] #![recursion_limit = "1024"]
pub mod ast; pub mod ast;
pub mod coredump;
pub mod docs; pub mod docs;
pub mod engine; pub mod engine;
pub mod errors; pub mod errors;

View File

@ -38,15 +38,14 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
let id = uuid::Uuid::new_v4(); let id = uuid::Uuid::new_v4();
// Extrude the element. // Extrude the element.
args.send_modeling_cmd( args.push_to_batch(
id, id,
kittycad::types::ModelingCmd::Extrude { kittycad::types::ModelingCmd::Extrude {
target: sketch_group.id, target: sketch_group.id,
distance: length, distance: length,
cap: true, cap: true,
}, },
) );
.await?;
do_post_extrude(sketch_group, length, id, args).await do_post_extrude(sketch_group, length, id, args).await
} }
@ -66,13 +65,12 @@ pub(crate) async fn do_post_extrude(
// Bring the object to the front of the scene. // Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806 // See: https://github.com/KittyCAD/modeling-app/issues/806
args.send_modeling_cmd( args.push_to_batch(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::ObjectBringToFront { kittycad::types::ModelingCmd::ObjectBringToFront {
object_id: sketch_group.id, object_id: sketch_group.id,
}, },
) );
.await?;
if sketch_group.value.is_empty() { if sketch_group.value.is_empty() {
return Err(KclError::Type(KclErrorDetails { return Err(KclError::Type(KclErrorDetails {

View File

@ -214,6 +214,10 @@ impl Args {
self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await
} }
pub fn push_to_batch(&self, id: uuid::Uuid, cmd: kittycad::types::ModelingCmd) {
self.ctx.engine.push_to_batch(id, self.source_range, cmd)
}
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> { fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::UserVal(crate::executor::UserVal { Ok(MemoryItem::UserVal(crate::executor::UserVal {
value: j, value: j,

View File

@ -235,7 +235,7 @@ pub struct CircularPattern2dData {
/// This excludes the original entity. For example, if `repetitions` is 1, /// This excludes the original entity. For example, if `repetitions` is 1,
/// the original entity will be copied once. /// the original entity will be copied once.
pub repetitions: u32, pub repetitions: u32,
/// The center about which to make th pattern. This is a 2D vector. /// The center about which to make the pattern. This is a 2D vector.
pub center: [f64; 2], pub center: [f64; 2],
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0. /// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
pub arc_degrees: f64, pub arc_degrees: f64,
@ -254,7 +254,7 @@ pub struct CircularPattern3dData {
pub repetitions: u32, pub repetitions: u32,
/// The axis around which to make the pattern. This is a 3D vector. /// The axis around which to make the pattern. This is a 3D vector.
pub axis: [f64; 3], pub axis: [f64; 3],
/// The center about which to make th pattern. This is a 3D vector. /// The center about which to make the pattern. This is a 3D vector.
pub center: [f64; 3], pub center: [f64; 3],
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0. /// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
pub arc_degrees: f64, pub arc_degrees: f64,

View File

@ -224,7 +224,7 @@ async fn inner_revolve(
match data.axis { match data.axis {
RevolveAxis::Axis(axis) => { RevolveAxis::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?; let (axis, origin) = axis.axis_and_origin()?;
args.send_modeling_cmd( args.push_to_batch(
id, id,
ModelingCmd::Revolve { ModelingCmd::Revolve {
angle, angle,
@ -234,8 +234,7 @@ async fn inner_revolve(
tolerance: DEFAULT_TOLERANCE, tolerance: DEFAULT_TOLERANCE,
axis_is_2d: true, axis_is_2d: true,
}, },
) );
.await?;
} }
RevolveAxis::Edge(edge) => { RevolveAxis::Edge(edge) => {
let edge_id = match edge { let edge_id = match edge {
@ -256,7 +255,7 @@ async fn inner_revolve(
.id .id
} }
}; };
args.send_modeling_cmd( args.push_to_batch(
id, id,
ModelingCmd::RevolveAboutEdge { ModelingCmd::RevolveAboutEdge {
angle, angle,
@ -264,8 +263,7 @@ async fn inner_revolve(
edge_id, edge_id,
tolerance: DEFAULT_TOLERANCE, tolerance: DEFAULT_TOLERANCE,
}, },
) );
.await?;
} }
} }

View File

@ -7,7 +7,7 @@ use std::{
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
use gloo_utils::format::JsValueSerdeExt; use gloo_utils::format::JsValueSerdeExt;
use kcl_lib::engine::EngineManager; use kcl_lib::{coredump::CoreDump, engine::EngineManager};
use tower_lsp::{LspService, Server}; use tower_lsp::{LspService, Server};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -379,3 +379,16 @@ pub fn program_memory_init() -> Result<JsValue, String> {
// gloo-serialize crate instead. // gloo-serialize crate instead.
JsValue::from_serde(&memory).map_err(|e| e.to_string()) JsValue::from_serde(&memory).map_err(|e| e.to_string())
} }
/// Get a coredump.
#[wasm_bindgen]
pub async fn coredump(core_dump_manager: kcl_lib::coredump::wasm::CoreDumpManager) -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let core_dumper = kcl_lib::coredump::wasm::CoreDumper::new(core_dump_manager);
let dump = core_dumper.dump().await.map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead.
JsValue::from_serde(&dump).map_err(|e| e.to_string())
}

View File

@ -0,0 +1,18 @@
const width = 20
fn cube = (cx, cy) => {
let d = width/2
startSketchAt([cx-d, cy-d])
|> lineTo([cx+d, cy-d], %)
|> lineTo([cx+d, cy+d], %)
|> lineTo([cx-d, cy+d], %)
|> lineTo([cx-d, cy-d], %)
|> close(%)
|> extrude(width, %)
}
let interval = 30
cube(interval,interval)
cube(0,0)
cube(-interval,interval)

View File

@ -29,7 +29,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
let ws = client let ws = client
.modeling() .modeling()
.commands_ws(None, None, None, None, None, Some(false)) .commands_ws(None, None, None, None, None, None, Some(false))
.await?; .await?;
// Create a temporary file to write the output to. // Create a temporary file to write the output to.
@ -110,6 +110,15 @@ const part002 = startSketchOn(part001, "here")
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999); twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999);
} }
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_three_cubes() {
let code = include_str!("inputs/three_cubes.kcl");
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/three_cubes.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn serial_test_riddle_small() { async fn serial_test_riddle_small() {
let code = include_str!("inputs/riddle_small.kcl"); let code = include_str!("inputs/riddle_small.kcl");
@ -1959,3 +1968,52 @@ capScrew([0, 0.5, 0], 50, 37.5, 50, 25)
.unwrap(); .unwrap();
twenty_twenty::assert_image("tests/executor/outputs/member_expression_in_params.png", &result, 1.0); twenty_twenty::assert_image("tests/executor/outputs/member_expression_in_params.png", &result, 1.0);
} }
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_bracket_with_fillets_ensure_fail_on_flush_source_ranges() {
let code = r#"// Shelf Bracket
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
const sigmaAllow = 35000 // psi
const width = 6 // inch
const p = 300 // Force on shelf - lbs
const distance = 12 // inches
const M = 12 * 300 / 2 // Moment experienced at fixed end of bracket
const FOS = 2 // Factor of safety of 2
const shelfMountL = 8 // The length of the bracket holding up the shelf is 6 inches
const wallMountL = 8 // the length of the bracket
// Calculate the thickness off the allowable bending stress and factor of safety
const thickness = sqrt(6 * M * FOS / (width * sigmaAllow))
// 0.25 inch fillet radius
const filletR = 0.25
// Sketch the bracket and extrude with fillets
const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, wallMountL], %, 'outerEdge')
|> line([-shelfMountL, 0], %)
|> line([0, -thickness], %)
|> line([shelfMountL - thickness, 0], %, 'innerEdge')
|> line([0, -wallMountL + thickness], %)
|> close(%)
|> extrude(width, %)
|> fillet({
radius: filletR,
tags: [getNextAdjacentEdge('innerEdge', %)]
}, %)
|> fillet({
radius: filletR + thickness,
tags: [getNextAdjacentEdge('outerEdge', %)]
}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm).await;
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1443, 1443])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"Fillet failed\" }])" }"#
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -33,7 +33,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
let ws = client let ws = client
.modeling() .modeling()
.commands_ws(None, None, None, None, None, Some(false)) .commands_ws(None, None, None, None, None, None, Some(false))
.await?; .await?;
let tokens = kcl_lib::token::lexer(code); let tokens = kcl_lib::token::lexer(code);

211
yarn.lock
View File

@ -1630,19 +1630,24 @@
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5"
integrity sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA== integrity sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==
"@fortawesome/fontawesome-svg-core@^6.4.2": "@fortawesome/fontawesome-common-types@6.5.2":
version "6.4.2" version "6.5.2"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz#37f4507d5ec645c8b50df6db14eced32a6f9be09" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz#eaf2f5699f73cef198454ebc0c414e3688898179"
integrity sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg== integrity sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.2"
"@fortawesome/free-brands-svg-icons@^6.4.2": "@fortawesome/fontawesome-svg-core@^6.5.2":
version "6.4.2" version "6.5.2"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz#9b8e78066ea6dd563da5dfa686615791d0f7cc71" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz#4b42de71e196039b0d5ccf88559b8044e3296c21"
integrity sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg== integrity sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==
dependencies: dependencies:
"@fortawesome/fontawesome-common-types" "6.4.2" "@fortawesome/fontawesome-common-types" "6.5.2"
"@fortawesome/free-brands-svg-icons@^6.5.2":
version "6.5.2"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz#bfca0cebd2c4713dc93244e1fa8b384f1f023587"
integrity sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==
dependencies:
"@fortawesome/fontawesome-common-types" "6.5.2"
"@fortawesome/free-solid-svg-icons@^6.4.2": "@fortawesome/free-solid-svg-icons@^6.4.2":
version "6.4.2" version "6.4.2"
@ -1913,12 +1918,12 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@playwright/test@^1.39.0": "@playwright/test@^1.43.0":
version "1.39.0" version "1.43.0"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.39.0.tgz#d10ba8e38e44104499e25001945f07faa9fa91cd" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.0.tgz#5d90f247b26d404dd5d81c60f9c7c5e5159eb664"
integrity sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ== integrity sha512-Ebw0+MCqoYflop7wVKj711ccbNlrwTBCtjY5rlbiY9kHL2bCYxq+qltK6uPsVBGGAOb033H2VO0YobcQVxoW7Q==
dependencies: dependencies:
playwright "1.39.0" playwright "1.43.0"
"@puppeteer/browsers@1.4.6": "@puppeteer/browsers@1.4.6":
version "1.4.6" version "1.4.6"
@ -2106,7 +2111,7 @@
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6"
integrity sha512-Nxtj28NYUo5iwYkpYslxmOPkdI2WkELU2e3UH9nbJm9Ydki2CQwJVGQxx4EANtdZcMNsEsUzRqaDTvEUYH1l6w== integrity sha512-Nxtj28NYUo5iwYkpYslxmOPkdI2WkELU2e3UH9nbJm9Ydki2CQwJVGQxx4EANtdZcMNsEsUzRqaDTvEUYH1l6w==
"@tauri-apps/api@^2.0.0-beta.7": "@tauri-apps/api@2.0.0-beta.7":
version "2.0.0-beta.7" version "2.0.0-beta.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.7.tgz#a80a3ffc24e6ec8dbdc8131dc3e68d8f4342293d" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.7.tgz#a80a3ffc24e6ec8dbdc8131dc3e68d8f4342293d"
integrity sha512-cM7SJQP4DBkLLMOdybLFYUURWn/tng2FEdAnXlu42f3NhFxKL4KVeeQTkuwlgC7ePwwwvDSqiXGiF+dKOadY7w== integrity sha512-cM7SJQP4DBkLLMOdybLFYUURWn/tng2FEdAnXlu42f3NhFxKL4KVeeQTkuwlgC7ePwwwvDSqiXGiF+dKOadY7w==
@ -2287,7 +2292,7 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@tweenjs/tween.js@^23.1.1": "@tweenjs/tween.js@^23.1.1", "@tweenjs/tween.js@~23.1.1":
version "23.1.1" version "23.1.1"
resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-23.1.1.tgz#0ae28ed9c635805557f78c2626464018d5f1b5e2" resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-23.1.1.tgz#0ae28ed9c635805557f78c2626464018d5f1b5e2"
integrity sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw== integrity sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw==
@ -2358,6 +2363,14 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/hoist-non-react-statics@^3.3.1":
version "3.3.5"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/http-cache-semantics@^4.0.2": "@types/http-cache-semantics@^4.0.2":
version "4.0.4" version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
@ -2412,10 +2425,10 @@
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
"@types/node@^18.19.26": "@types/node@^18.19.31":
version "18.19.26" version "18.19.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.26.tgz#18991279d0a0e53675285e8cf4a0823766349729" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.31.tgz#b7d4a00f7cb826b60a543cebdbda5d189aaecdcd"
integrity sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw== integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
@ -2462,10 +2475,10 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^18.2.73": "@types/react@*", "@types/react@^18.2.75":
version "18.2.73" version "18.2.75"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.73.tgz#0579548ad122660d99e00499d22e33b81e73ed94" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.75.tgz#45d18f384939306d35312def1bf532eb38a68562"
integrity sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA== integrity sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^3.0.2" csstype "^3.0.2"
@ -2492,16 +2505,22 @@
dependencies: dependencies:
"@types/jest" "*" "@types/jest" "*"
"@types/three@^0.160.0": "@types/three@^0.163.0":
version "0.160.0" version "0.163.0"
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.160.0.tgz#7915a97e0a14ccaa9ccbb9f190c5730b04a23075" resolved "https://registry.yarnpkg.com/@types/three/-/three-0.163.0.tgz#96f5440fcd39452d2c84dfe0c9b7a9cf0247b9e6"
integrity sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w== integrity sha512-uIdDhsXRpQiBUkflBS/i1l3JX14fW6Ot9csed60nfbZNXHDTRsnV2xnTVwXcgbvTiboAR4IW+t+lTL5f1rqIqA==
dependencies: dependencies:
"@tweenjs/tween.js" "~23.1.1"
"@types/stats.js" "*" "@types/stats.js" "*"
"@types/webxr" "*" "@types/webxr" "*"
fflate "~0.6.10" fflate "~0.8.2"
meshoptimizer "~0.18.1" meshoptimizer "~0.18.1"
"@types/ua-parser-js@^0.7.39":
version "0.7.39"
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz#832c58e460c9435e4e34bb866e85e9146e12cdbb"
integrity sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==
"@types/uuid@^9.0.8": "@types/uuid@^9.0.8":
version "9.0.8" version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
@ -2529,20 +2548,13 @@
resolved "https://registry.yarnpkg.com/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.5.tgz#14b3c25eb4d914b5734795bdea71da229f918b9d" resolved "https://registry.yarnpkg.com/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.5.tgz#14b3c25eb4d914b5734795bdea71da229f918b9d"
integrity sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA== integrity sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==
"@types/ws@^8.5.3": "@types/ws@^8.5.10", "@types/ws@^8.5.3":
version "8.5.10" version "8.5.10"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/ws@^8.5.5":
version "8.5.5"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==
dependencies:
"@types/node" "*"
"@types/yargs-parser@*": "@types/yargs-parser@*":
version "21.0.3" version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@ -3505,6 +3517,11 @@ base16@^1.0.0:
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
base64-arraybuffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
base64-js@^1.3.1: base64-js@^1.3.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@ -4022,6 +4039,13 @@ crypto-js@^4.2.0:
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
css-line-break@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
dependencies:
utrie "^1.0.2"
css-shorthand-properties@^1.1.1: css-shorthand-properties@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz#1c808e63553c283f289f2dd56fcee8f3337bd935" resolved "https://registry.yarnpkg.com/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz#1c808e63553c283f289f2dd56fcee8f3337bd935"
@ -4993,10 +5017,10 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4:
node-domexception "^1.0.0" node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3" web-streams-polyfill "^3.0.3"
fflate@~0.6.10: fflate@~0.8.2:
version "0.6.10" version "0.8.2"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg== integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
figures@^5.0.0: figures@^5.0.0:
version "5.0.0" version "5.0.0"
@ -5110,11 +5134,12 @@ formdata-polyfill@^4.0.10:
dependencies: dependencies:
fetch-blob "^3.1.2" fetch-blob "^3.1.2"
formik@^2.4.3: formik@^2.4.5:
version "2.4.3" version "2.4.5"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.3.tgz#6020e85eb3e3e8415b3b19d6f4f65793ab754b24" resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.5.tgz#f899b5b7a6f103a8fabb679823e8fafc7e0ee1b4"
integrity sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q== integrity sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==
dependencies: dependencies:
"@types/hoist-non-react-statics" "^3.3.1"
deepmerge "^2.1.1" deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.0"
lodash "^4.17.21" lodash "^4.17.21"
@ -5608,6 +5633,14 @@ html-encoding-sniffer@^3.0.0:
dependencies: dependencies:
whatwg-encoding "^2.0.0" whatwg-encoding "^2.0.0"
html2canvas-pro@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.4.3.tgz#100124e2d17d4de483700ce03176d7447e90d49f"
integrity sha512-RB36SrUGxT9PTjImC7BsGxTinaI3y8cEne76ACdw+E7nRmeJ0jgDntxUP15B9Q9AM2mvEPN6SZo6zmkzwk8HKg==
dependencies:
css-line-break "^2.1.0"
text-segmentation "^1.0.3"
http-cache-semantics@^4.1.1: http-cache-semantics@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
@ -7231,17 +7264,17 @@ pkg-types@^1.0.3:
mlly "^1.2.0" mlly "^1.2.0"
pathe "^1.1.0" pathe "^1.1.0"
playwright-core@1.39.0: playwright-core@1.43.0:
version "1.39.0" version "1.43.0"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.39.0.tgz#efeaea754af4fb170d11845b8da30b2323287c63" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.43.0.tgz#d8079acb653abebb0b63062e432479647a4e1271"
integrity sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw== integrity sha512-iWFjyBUH97+pUFiyTqSLd8cDMMOS0r2ZYz2qEsPjH8/bX++sbIJT35MSwKnp1r/OQBAqC5XO99xFbJ9XClhf4w==
playwright@1.39.0: playwright@1.43.0:
version "1.39.0" version "1.43.0"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.39.0.tgz#184c81cd6478f8da28bcd9e60e94fcebf566e077" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.43.0.tgz#2c2efd4ee2a25defd8c24c98ccb342bdd9d435f5"
integrity sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw== integrity sha512-SiOKHbVjTSf6wHuGCbqrEyzlm6qvXcv7mENP+OZon1I07brfZLGdfWV0l/efAzVx7TF3Z45ov1gPEkku9q25YQ==
dependencies: dependencies:
playwright-core "1.39.0" playwright-core "1.43.0"
optionalDependencies: optionalDependencies:
fsevents "2.3.2" fsevents "2.3.2"
@ -8327,10 +8360,10 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
swr@^2.2.2: swr@^2.2.5:
version "2.2.2" version "2.2.5"
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.2.tgz#abcb1f9c97e10527789884169d58b878472d4c98" resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b"
integrity sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ== integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==
dependencies: dependencies:
client-only "^0.0.1" client-only "^0.0.1"
use-sync-external-store "^1.2.0" use-sync-external-store "^1.2.0"
@ -8393,9 +8426,9 @@ tar-stream@^3.0.0, tar-stream@^3.1.5:
streamx "^2.15.0" streamx "^2.15.0"
tar@^6.1.11: tar@^6.1.11:
version "6.1.15" version "6.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
dependencies: dependencies:
chownr "^2.0.0" chownr "^2.0.0"
fs-minipass "^2.0.0" fs-minipass "^2.0.0"
@ -8404,6 +8437,13 @@ tar@^6.1.11:
mkdirp "^1.0.3" mkdirp "^1.0.3"
yallist "^4.0.0" yallist "^4.0.0"
text-segmentation@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
dependencies:
utrie "^1.0.2"
text-table@^0.2.0: text-table@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -8423,10 +8463,10 @@ thenify-all@^1.0.0:
dependencies: dependencies:
any-promise "^1.0.0" any-promise "^1.0.0"
three@^0.160.0: three@^0.163.0:
version "0.160.0" version "0.163.0"
resolved "https://registry.yarnpkg.com/three/-/three-0.160.0.tgz#cd1e4dbd01aee0719280a9086d75545db52b7a8f" resolved "https://registry.yarnpkg.com/three/-/three-0.163.0.tgz#cbfefbfd64a1353ab7cc8bf0fc396ddca1875a49"
integrity sha512-DLU8lc0zNIPkM7rH5/e1Ks1Z8tWCGRq6g8mPowdDJpw1CFBJMU7UoJjC6PefXW7z//SSl0b2+GCw14LB+uDhng== integrity sha512-HlMgCb2TF/dTLRtknBnjUTsR8FsDqBY43itYop2+Zg822I+Kd0Ua2vs8CvfBVefXkBdNDrLMoRTGCIIpfCuDew==
through@^2.3.8: through@^2.3.8:
version "2.3.8" version "2.3.8"
@ -8531,12 +8571,7 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0: tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@ -8644,6 +8679,11 @@ ua-parser-js@^1.0.35:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011"
integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA== integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==
ua-parser-js@^1.0.37:
version "1.0.37"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"
integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
ufo@^1.1.2: ufo@^1.1.2:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.2.0.tgz#28d127a087a46729133fdc89cb1358508b3f80ba" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.2.0.tgz#28d127a087a46729133fdc89cb1358508b3f80ba"
@ -8791,6 +8831,13 @@ util@^0.12.5:
is-typed-array "^1.1.3" is-typed-array "^1.1.3"
which-typed-array "^1.1.2" which-typed-array "^1.1.2"
utrie@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
dependencies:
base64-arraybuffer "^1.0.2"
uuid@^9.0.1: uuid@^9.0.1:
version "9.0.1" version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
@ -9193,7 +9240,7 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@8.13.0, ws@^8.13.0: ws@8.13.0:
version "8.13.0" version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
@ -9203,10 +9250,10 @@ ws@^7.0.0:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
ws@^8.8.0: ws@^8.16.0, ws@^8.8.0:
version "8.14.2" version "8.16.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
"xstate-beta@npm:xstate@beta": "xstate-beta@npm:xstate@beta":
version "5.0.0-beta.54" version "5.0.0-beta.54"
@ -9307,10 +9354,10 @@ yargs@17.7.2, yargs@^17.7.2:
y18n "^5.0.5" y18n "^5.0.5"
yargs-parser "^21.1.1" yargs-parser "^21.1.1"
yarn@^1.22.19: yarn@^1.22.22:
version "1.22.19" version "1.22.22"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.22.tgz#ac34549e6aa8e7ead463a7407e1c7390f61a6610"
integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ== integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==
yauzl@^2.10.0: yauzl@^2.10.0:
version "2.10.0" version "2.10.0"