Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
12b3717eb5 | |||
0bc685b0c4 | |||
9ee032771a | |||
c307ddd1b1 | |||
a30818ff2b | |||
53e763d938 | |||
8f74cd1d0c | |||
c271942897 | |||
a03d09b41d | |||
2971b7752b | |||
70e99eb00b | |||
5c66af59d2 | |||
6dda6daeef | |||
b5387f1220 | |||
fd5921b366 | |||
716ad938fc | |||
40136eb392 | |||
8d2b89fcd1 | |||
ad9fba3390 | |||
911c43af50 | |||
ab4e04f6c2 | |||
94aef05f74 | |||
d820cf2446 | |||
0c724c4971 | |||
b54ac4a694 | |||
27227092b1 | |||
04e1b92a5b | |||
0553cd4621 | |||
61a0c88af4 | |||
d5b0544437 | |||
6cc8af5c23 | |||
888104080e | |||
b6769889e3 | |||
a32258dac4 | |||
18dbbad244 | |||
b67c16cc9d | |||
ad482641ef | |||
9ee24845a1 | |||
e69d263252 | |||
111738f38e | |||
e34501cc5a | |||
c767c1c3a6 | |||
e399a8f938 | |||
59d5f2524a | |||
b47ebd14d2 | |||
e74bcd0695 | |||
22161ec386 | |||
ada46c4317 | |||
6675fa8d1e | |||
075d2debce | |||
488e41ac0e | |||
8147f5f1eb | |||
bc7e9d9789 | |||
8d493d6517 | |||
9fa98d6f3f | |||
24a31c94e7 | |||
76e3207251 | |||
e2237fa9f6 | |||
ae4aa82129 | |||
14b287a746 | |||
dd1b7631fa | |||
f98f782b40 | |||
01f5ecdc36 | |||
5297d3e142 | |||
f71f44968b | |||
7b79998c40 | |||
4632d407c1 | |||
58d7e59ca4 | |||
f592d8db84 | |||
31eca3728e | |||
c5d8779af4 | |||
cf686bdeb0 | |||
ae7143a94f | |||
f2b24849b3 | |||
35d6530406 | |||
01208221c7 | |||
fbbed3fbfb | |||
ce51f26701 |
@ -1,6 +1,6 @@
|
|||||||
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
||||||
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
VITE_KC_SENTRY_DSN=
|
VITE_KC_SENTRY_DSN=
|
||||||
|
2
.github/workflows/cargo-build.yml
vendored
2
.github/workflows/cargo-build.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
4
.github/workflows/cargo-clippy.yml
vendored
4
.github/workflows/cargo-clippy.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@ -54,4 +54,4 @@ jobs:
|
|||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo clippy --all --tests -- -D warnings
|
cargo clippy --all --tests --benches -- -D warnings
|
||||||
|
37
.github/workflows/cargo-criterion.yml
vendored
Normal file
37
.github/workflows/cargo-criterion.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- '**.rs'
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
- '**/rust-toolchain.toml'
|
||||||
|
- .github/workflows/cargo-criterion.yml
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**.rs'
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
- '**/rust-toolchain.toml'
|
||||||
|
- .github/workflows/cargo-criterion.yml
|
||||||
|
workflow_dispatch:
|
||||||
|
permissions: read-all
|
||||||
|
name: cargo criterion
|
||||||
|
jobs:
|
||||||
|
cargocriterion:
|
||||||
|
name: cargo criterion
|
||||||
|
runs-on: ubuntu-latest-8-cores
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cargo install cargo-criterion
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2.6.1
|
||||||
|
- name: Benchmark kcl library
|
||||||
|
shell: bash
|
||||||
|
run: |-
|
||||||
|
cd src/wasm-lib/kcl; cargo criterion
|
||||||
|
|
2
.github/workflows/cargo-fmt.yml
vendored
2
.github/workflows/cargo-fmt.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib', 'src-tauri']
|
dir: ['src/wasm-lib', 'src-tauri']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
5
.github/workflows/cargo-test.yml
vendored
5
.github/workflows/cargo-test.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@ -55,7 +55,8 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo test --all
|
cargo nextest run --workspace --no-fail-fast -P ci
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
|
RUST_MIN_STACK: 10485760000
|
||||||
|
|
||||||
|
91
.github/workflows/ci.yml
vendored
91
.github/workflows/ci.yml
vendored
@ -4,16 +4,15 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
check-format:
|
check-format:
|
||||||
runs-on: 'ubuntu-20.04'
|
runs-on: 'ubuntu-20.04'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
@ -25,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
@ -33,19 +32,17 @@ jobs:
|
|||||||
- run: yarn install
|
- run: yarn install
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "./src/wasm-lib"
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
- run: yarn tsc
|
- run: yarn tsc
|
||||||
|
|
||||||
|
|
||||||
build-test-web:
|
build-test-web:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.export_version.outputs.version }}
|
version: ${{ steps.export_version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
@ -56,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "./src/wasm-lib"
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
|
||||||
@ -69,7 +66,6 @@ jobs:
|
|||||||
- id: export_version
|
- id: export_version
|
||||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
|
||||||
build-apps:
|
build-apps:
|
||||||
needs: [check-format, build-test-web, check-types]
|
needs: [check-format, build-test-web, check-types]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@ -77,8 +73,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-20.04, windows-latest]
|
os: [macos-latest, ubuntu-20.04, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: install ubuntu system dependencies
|
- name: install ubuntu system dependencies
|
||||||
if: matrix.os == 'ubuntu-20.04'
|
if: matrix.os == 'ubuntu-20.04'
|
||||||
@ -104,7 +99,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "./src/wasm-lib"
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- name: wasm prep
|
- name: wasm prep
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -114,18 +109,6 @@ jobs:
|
|||||||
cd ../../
|
cd ../../
|
||||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
|
|
||||||
- name: macos sed
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i '' 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
|
|
||||||
|
|
||||||
- name: ubuntu and windows sed
|
|
||||||
if: matrix.os != 'macos-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
|
|
||||||
|
|
||||||
- name: Fix format
|
- name: Fix format
|
||||||
run: yarn fmt
|
run: yarn fmt
|
||||||
|
|
||||||
@ -134,38 +117,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
rustup target add aarch64-apple-darwin
|
rustup target add aarch64-apple-darwin
|
||||||
|
|
||||||
- name: Build the app for the current platform (no upload)
|
- name: Prepare Windows certificate and variables
|
||||||
uses: tauri-apps/tauri-action@v0
|
if: matrix.os == 'windows-latest'
|
||||||
env:
|
|
||||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
|
||||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
|
||||||
with:
|
|
||||||
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
|
|
||||||
|
|
||||||
|
|
||||||
sign-windows-msi:
|
|
||||||
runs-on: windows-latest
|
|
||||||
if: github.event_name == 'release'
|
|
||||||
needs: [build-test-web, build-apps]
|
|
||||||
env:
|
|
||||||
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
|
|
||||||
- name: Setup Certificate
|
|
||||||
run: |
|
run: |
|
||||||
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||||
cat /d/Certificate_pkcs12.p12
|
cat /d/Certificate_pkcs12.p12
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set variables
|
|
||||||
id: variables
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
||||||
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
||||||
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
||||||
@ -176,7 +132,8 @@ jobs:
|
|||||||
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Setup SSM KSP on windows latest
|
- name: Setup Windows certicate with SSM KSP
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||||
msiexec /i smtools-windows-x64.msi /quiet /qn
|
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||||
@ -186,28 +143,32 @@ jobs:
|
|||||||
smksp_cert_sync.exe
|
smksp_cert_sync.exe
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- name: Signing using Signtool
|
- name: Build and sign the app for the current platform
|
||||||
run: |
|
uses: tauri-apps/tauri-action@v0
|
||||||
signtool.exe sign /sha1 ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "artifact\msi\*.msi"
|
env:
|
||||||
signtool.exe verify /v /pa "artifact\msi\*.msi"
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
# TODO: for the updater, investigate if we need to also replace what's in the .zip, and what to do about the .sig file
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
|
with:
|
||||||
|
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: artifact/*
|
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
|
||||||
|
|
||||||
|
|
||||||
publish-apps-release:
|
publish-apps-release:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
needs: [build-test-web, build-apps, sign-windows-msi]
|
needs: [build-test-web, build-apps]
|
||||||
env:
|
env:
|
||||||
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
|
||||||
PUB_DATE: ${{ github.event.release.created_at }}
|
PUB_DATE: ${{ github.event.release.created_at }}
|
||||||
NOTES: ${{ github.event.release.body }}
|
NOTES: ${{ github.event.release.body }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
|
|
||||||
- name: Generate the update static endpoint
|
- name: Generate the update static endpoint
|
||||||
|
2
.github/workflows/update-dev-branch.yml
vendored
2
.github/workflows/update-dev-branch.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.0
|
- uses: actions/checkout@v4
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
# checkout our branch
|
# checkout our branch
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -22,6 +22,11 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
src/wasm-lib/.idea
|
||||||
|
src/wasm-lib/.vscode
|
||||||
|
|
||||||
# rust
|
# rust
|
||||||
src/wasm-lib/target
|
src/wasm-lib/target
|
||||||
src/wasm-lib/bindings
|
src/wasm-lib/bindings
|
||||||
|
15
README.md
15
README.md
@ -123,13 +123,24 @@ Before you submit a contribution PR to this repo, please ensure that:
|
|||||||
|
|
||||||
## Release a new version
|
## Release a new version
|
||||||
|
|
||||||
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
|
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VERSION=x.y.z yarn run bump-jsons
|
VERSION=x.y.z yarn run bump-jsons
|
||||||
```
|
```
|
||||||
|
|
||||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(
|
||||||
|
'- ' +
|
||||||
|
Array.from(
|
||||||
|
document.querySelectorAll('[data-hovercard-type="pull_request"]')
|
||||||
|
).map((a) => `[${a.innerText}](${a.href})`).join(`
|
||||||
|
- `)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
grab the md list and delete any that are older than the last bump
|
||||||
|
|
||||||
2. Merge the PR
|
2. Merge the PR
|
||||||
|
|
||||||
|
@ -1,4 +1,60 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"name": "abs",
|
||||||
|
"summary": "Computes the absolute value of a number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "acos",
|
||||||
|
"summary": "Computes the arccosine of a number (in radians).",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "angleToMatchLengthX",
|
"name": "angleToMatchLengthX",
|
||||||
"summary": "Returns the angle to match the given length for x.",
|
"summary": "Returns the angle to match the given length for x.",
|
||||||
@ -7473,6 +7529,62 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "asin",
|
||||||
|
"summary": "Computes the arcsine of a number (in radians).",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atan",
|
||||||
|
"summary": "Computes the arctangent of a number (in radians).",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "bezierCurve",
|
"name": "bezierCurve",
|
||||||
"summary": "Draw a bezier curve.",
|
"summary": "Draw a bezier curve.",
|
||||||
@ -8446,6 +8558,34 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ceil",
|
||||||
|
"summary": "Computes the smallest integer greater than or equal to a number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "close",
|
"name": "close",
|
||||||
"summary": "Close the current sketch.",
|
"summary": "Close the current sketch.",
|
||||||
@ -9350,6 +9490,24 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "e",
|
||||||
|
"summary": "Return the value of Euler’s number `e`.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "extrude",
|
"name": "extrude",
|
||||||
"summary": "Extrudes by a given amount.",
|
"summary": "Extrudes by a given amount.",
|
||||||
@ -9944,6 +10102,34 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "floor",
|
||||||
|
"summary": "Computes the largest integer less than or equal to a number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getExtrudeWallTransform",
|
"name": "getExtrudeWallTransform",
|
||||||
"summary": "Returns the extrude wall transform.",
|
"summary": "Returns the extrude wall transform.",
|
||||||
@ -13015,9 +13201,161 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ln",
|
||||||
|
"summary": "Computes the natural logarithm of the number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "log",
|
||||||
|
"summary": "Computes the logarithm of the number with respect to an arbitrary base.",
|
||||||
|
"description": "The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "log10",
|
||||||
|
"summary": "Computes the base 10 logarithm of the number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "log2",
|
||||||
|
"summary": "Computes the base 2 logarithm of the number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max",
|
||||||
|
"summary": "Computes the maximum of the given arguments.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "args",
|
||||||
|
"type": "[number]",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "min",
|
"name": "min",
|
||||||
"summary": "Returns the minimum of the given arguments.",
|
"summary": "Computes the minimum of the given arguments.",
|
||||||
"description": "",
|
"description": "",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [
|
"args": [
|
||||||
@ -13048,7 +13386,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pi",
|
"name": "pi",
|
||||||
"summary": "Return the value of `pi`.",
|
"summary": "Return the value of `pi`. Archimedes’ constant (π).",
|
||||||
"description": "",
|
"description": "",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [],
|
"args": [],
|
||||||
@ -13064,6 +13402,43 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pow",
|
||||||
|
"summary": "Computes the number to a power.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pow",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "segAng",
|
"name": "segAng",
|
||||||
"summary": "Returns the angle of the segment.",
|
"summary": "Returns the angle of the segment.",
|
||||||
@ -15376,6 +15751,34 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sqrt",
|
||||||
|
"summary": "Computes the square root of a number.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "num",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "startSketchAt",
|
"name": "startSketchAt",
|
||||||
"summary": "Start a sketch at a given point.",
|
"summary": "Start a sketch at a given point.",
|
||||||
@ -15891,6 +16294,24 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false
|
"deprecated": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tau",
|
||||||
|
"summary": "Return the value of `tau`. The full circle constant (τ). Equal to 2π.",
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"args": [],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "xLine",
|
"name": "xLine",
|
||||||
"summary": "Draw a line on the x-axis.",
|
"summary": "Draw a line on the x-axis.",
|
||||||
|
319
docs/kcl/std.md
319
docs/kcl/std.md
@ -5,6 +5,8 @@
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
* [Functions](#functions)
|
* [Functions](#functions)
|
||||||
|
* [`abs`](#abs)
|
||||||
|
* [`acos`](#acos)
|
||||||
* [`angleToMatchLengthX`](#angleToMatchLengthX)
|
* [`angleToMatchLengthX`](#angleToMatchLengthX)
|
||||||
* [`angleToMatchLengthY`](#angleToMatchLengthY)
|
* [`angleToMatchLengthY`](#angleToMatchLengthY)
|
||||||
* [`angledLine`](#angledLine)
|
* [`angledLine`](#angledLine)
|
||||||
@ -14,10 +16,15 @@
|
|||||||
* [`angledLineToX`](#angledLineToX)
|
* [`angledLineToX`](#angledLineToX)
|
||||||
* [`angledLineToY`](#angledLineToY)
|
* [`angledLineToY`](#angledLineToY)
|
||||||
* [`arc`](#arc)
|
* [`arc`](#arc)
|
||||||
|
* [`asin`](#asin)
|
||||||
|
* [`atan`](#atan)
|
||||||
* [`bezierCurve`](#bezierCurve)
|
* [`bezierCurve`](#bezierCurve)
|
||||||
|
* [`ceil`](#ceil)
|
||||||
* [`close`](#close)
|
* [`close`](#close)
|
||||||
* [`cos`](#cos)
|
* [`cos`](#cos)
|
||||||
|
* [`e`](#e)
|
||||||
* [`extrude`](#extrude)
|
* [`extrude`](#extrude)
|
||||||
|
* [`floor`](#floor)
|
||||||
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
|
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
|
||||||
* [`lastSegX`](#lastSegX)
|
* [`lastSegX`](#lastSegX)
|
||||||
* [`lastSegY`](#lastSegY)
|
* [`lastSegY`](#lastSegY)
|
||||||
@ -26,16 +33,24 @@
|
|||||||
* [`legLen`](#legLen)
|
* [`legLen`](#legLen)
|
||||||
* [`line`](#line)
|
* [`line`](#line)
|
||||||
* [`lineTo`](#lineTo)
|
* [`lineTo`](#lineTo)
|
||||||
|
* [`ln`](#ln)
|
||||||
|
* [`log`](#log)
|
||||||
|
* [`log10`](#log10)
|
||||||
|
* [`log2`](#log2)
|
||||||
|
* [`max`](#max)
|
||||||
* [`min`](#min)
|
* [`min`](#min)
|
||||||
* [`pi`](#pi)
|
* [`pi`](#pi)
|
||||||
|
* [`pow`](#pow)
|
||||||
* [`segAng`](#segAng)
|
* [`segAng`](#segAng)
|
||||||
* [`segEndX`](#segEndX)
|
* [`segEndX`](#segEndX)
|
||||||
* [`segEndY`](#segEndY)
|
* [`segEndY`](#segEndY)
|
||||||
* [`segLen`](#segLen)
|
* [`segLen`](#segLen)
|
||||||
* [`show`](#show)
|
* [`show`](#show)
|
||||||
* [`sin`](#sin)
|
* [`sin`](#sin)
|
||||||
|
* [`sqrt`](#sqrt)
|
||||||
* [`startSketchAt`](#startSketchAt)
|
* [`startSketchAt`](#startSketchAt)
|
||||||
* [`tan`](#tan)
|
* [`tan`](#tan)
|
||||||
|
* [`tau`](#tau)
|
||||||
* [`xLine`](#xLine)
|
* [`xLine`](#xLine)
|
||||||
* [`xLineTo`](#xLineTo)
|
* [`xLineTo`](#xLineTo)
|
||||||
* [`yLine`](#yLine)
|
* [`yLine`](#yLine)
|
||||||
@ -44,6 +59,46 @@
|
|||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
|
### abs
|
||||||
|
|
||||||
|
Computes the absolute value of a number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
abs(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### acos
|
||||||
|
|
||||||
|
Computes the arccosine of a number (in radians).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
acos(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### angleToMatchLengthX
|
### angleToMatchLengthX
|
||||||
|
|
||||||
Returns the angle to match the given length for x.
|
Returns the angle to match the given length for x.
|
||||||
@ -1332,6 +1387,46 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### asin
|
||||||
|
|
||||||
|
Computes the arcsine of a number (in radians).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
asin(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### atan
|
||||||
|
|
||||||
|
Computes the arctangent of a number (in radians).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
atan(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### bezierCurve
|
### bezierCurve
|
||||||
|
|
||||||
Draw a bezier curve.
|
Draw a bezier curve.
|
||||||
@ -1497,6 +1592,26 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ceil
|
||||||
|
|
||||||
|
Computes the smallest integer greater than or equal to a number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
ceil(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### close
|
### close
|
||||||
|
|
||||||
Close the current sketch.
|
Close the current sketch.
|
||||||
@ -1661,6 +1776,25 @@ cos(num: number) -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### e
|
||||||
|
|
||||||
|
Return the value of Euler’s number `e`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
e() -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### extrude
|
### extrude
|
||||||
|
|
||||||
Extrudes by a given amount.
|
Extrudes by a given amount.
|
||||||
@ -1770,6 +1904,26 @@ extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### floor
|
||||||
|
|
||||||
|
Computes the largest integer less than or equal to a number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
floor(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### getExtrudeWallTransform
|
### getExtrudeWallTransform
|
||||||
|
|
||||||
Returns the extrude wall transform.
|
Returns the extrude wall transform.
|
||||||
@ -2358,9 +2512,110 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ln
|
||||||
|
|
||||||
|
Computes the natural logarithm of the number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
ln(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### log
|
||||||
|
|
||||||
|
Computes the logarithm of the number with respect to an arbitrary base.
|
||||||
|
|
||||||
|
The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.
|
||||||
|
|
||||||
|
```
|
||||||
|
log(num: number, base: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
* `base`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### log10
|
||||||
|
|
||||||
|
Computes the base 10 logarithm of the number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
log10(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### log2
|
||||||
|
|
||||||
|
Computes the base 2 logarithm of the number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
log2(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### max
|
||||||
|
|
||||||
|
Computes the maximum of the given arguments.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
max(args: [number]) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `args`: `[number]`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### min
|
### min
|
||||||
|
|
||||||
Returns the minimum of the given arguments.
|
Computes the minimum of the given arguments.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -2380,7 +2635,7 @@ min(args: [number]) -> number
|
|||||||
|
|
||||||
### pi
|
### pi
|
||||||
|
|
||||||
Return the value of `pi`.
|
Return the value of `pi`. Archimedes’ constant (π).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -2397,6 +2652,27 @@ pi() -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### pow
|
||||||
|
|
||||||
|
Computes the number to a power.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
pow(num: number, pow: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
* `pow`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### segAng
|
### segAng
|
||||||
|
|
||||||
Returns the angle of the segment.
|
Returns the angle of the segment.
|
||||||
@ -2827,6 +3103,26 @@ sin(num: number) -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### sqrt
|
||||||
|
|
||||||
|
Computes the square root of a number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
sqrt(num: number) -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `num`: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### startSketchAt
|
### startSketchAt
|
||||||
|
|
||||||
Start a sketch at a given point.
|
Start a sketch at a given point.
|
||||||
@ -2938,6 +3234,25 @@ tan(num: number) -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### tau
|
||||||
|
|
||||||
|
Return the value of `tau`. The full circle constant (τ). Equal to 2π.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
tau() -> number
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `number`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### xLine
|
### xLine
|
||||||
|
|
||||||
Draw a line on the x-axis.
|
Draw a line on the x-axis.
|
||||||
|
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.7.1",
|
"version": "0.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.9.0",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.37",
|
"@kittycad/lib": "^0.0.39",
|
||||||
"@lezer/javascript": "^1.4.7",
|
"@lezer/javascript": "^1.4.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",
|
||||||
@ -27,6 +27,7 @@
|
|||||||
"@uiw/react-codemirror": "^4.21.13",
|
"@uiw/react-codemirror": "^4.21.13",
|
||||||
"@xstate/react": "^3.2.2",
|
"@xstate/react": "^3.2.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
"debounce-promise": "^3.1.2",
|
||||||
"formik": "^2.4.3",
|
"formik": "^2.4.3",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
@ -62,15 +63,17 @@
|
|||||||
"build:local": "vite build",
|
"build:local": "vite build",
|
||||||
"build:both": "vite build",
|
"build:both": "vite build",
|
||||||
"build:both:local": "yarn build:wasm && vite build",
|
"build:both:local": "yarn build:wasm && vite build",
|
||||||
|
"pretest": "yarn remove-importmeta",
|
||||||
"test": "vitest --mode development",
|
"test": "vitest --mode development",
|
||||||
"test:nowatch": "vitest run --mode development",
|
"test:nowatch": "vitest run --mode development",
|
||||||
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)",
|
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
|
||||||
"test:cov": "vitest run --coverage --mode development",
|
"test:cov": "vitest run --coverage --mode development",
|
||||||
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
|
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||||
"simpleserver": "http-server ./public --cors -p 3000",
|
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||||
"fmt": "prettier --write ./src",
|
"fmt": "prettier --write ./src",
|
||||||
"fmt-check": "prettier --check ./src",
|
"fmt-check": "prettier --check ./src",
|
||||||
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
|
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||||
|
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src",
|
"lint": "eslint --fix src",
|
||||||
@ -99,7 +102,7 @@
|
|||||||
"@babel/preset-env": "^7.22.9",
|
"@babel/preset-env": "^7.22.9",
|
||||||
"@tauri-apps/cli": "^1.3.1",
|
"@tauri-apps/cli": "^1.3.1",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/debounce": "^1.2.1",
|
"@types/debounce-promise": "^3.1.6",
|
||||||
"@types/isomorphic-fetch": "^0.0.36",
|
"@types/isomorphic-fetch": "^0.0.36",
|
||||||
"@types/react-modal": "^3.16.0",
|
"@types/react-modal": "^3.16.0",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
|
46
public/kcma-logomark-dark.svg
Normal file
46
public/kcma-logomark-dark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
46
public/kcma-logomark.svg
Normal file
46
public/kcma-logomark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
BIN
public/onboarding-bracket-dark.png
Normal file
BIN
public/onboarding-bracket-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
BIN
public/onboarding-bracket.png
Normal file
BIN
public/onboarding-bracket.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
108
src-tauri/Cargo.lock
generated
108
src-tauri/Cargo.lock
generated
@ -155,6 +155,20 @@ version = "0.21.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bigdecimal"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "454bca3db10617b88b566f205ed190aedb0e0e6dd4cad61d3988a72e8c5594cb"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"libm",
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@ -1630,13 +1644,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.25"
|
version = "0.2.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
|
checksum = "e2623ee601ce203476229df3f9d3a14664cb43e3f7455e9ac8ed91aacaa6163d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
|
"bigdecimal",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
@ -1688,6 +1703,12 @@ version = "0.2.148"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "line-wrap"
|
name = "line-wrap"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -1959,6 +1980,17 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
@ -1982,9 +2014,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
@ -2182,6 +2214,17 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_info"
|
||||||
|
version = "3.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -2399,9 +2442,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phonenumber"
|
name = "phonenumber"
|
||||||
version = "0.3.2+8.13.9"
|
version = "0.3.3+8.13.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34749f64ea9d76f10cdc8a859588b57775f59177c7dd91f744d620bd62982d6f"
|
checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"either",
|
"either",
|
||||||
@ -2414,6 +2457,7 @@ dependencies = [
|
|||||||
"regex-cache",
|
"regex-cache",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
"strum",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3010,10 +3054,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.13"
|
version = "0.8.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
|
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bigdecimal",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
@ -3026,9 +3071,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars_derive"
|
name = "schemars_derive"
|
||||||
version = "0.8.13"
|
version = "0.8.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
|
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3152,9 +3197,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.106"
|
version = "1.0.107"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"ryu",
|
"ryu",
|
||||||
@ -3447,6 +3492,28 @@ dependencies = [
|
|||||||
"syn 2.0.33",
|
"syn 2.0.33",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.4.1",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
@ -3469,6 +3536,19 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sys-locale"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@ -3604,6 +3684,7 @@ dependencies = [
|
|||||||
"objc",
|
"objc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open",
|
"open",
|
||||||
|
"os_info",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@ -3616,6 +3697,7 @@ dependencies = [
|
|||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
"state",
|
"state",
|
||||||
|
"sys-locale",
|
||||||
"tar",
|
"tar",
|
||||||
"tauri-macros",
|
"tauri-macros",
|
||||||
"tauri-runtime",
|
"tauri-runtime",
|
||||||
@ -3693,7 +3775,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs-extra"
|
name = "tauri-plugin-fs-extra"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#5b814f56e6368fdec46c4ddb04a07e0923ff995a"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#b04bde3461066c709d6801cf9ca305cf889a8394"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -16,11 +16,11 @@ tauri-build = { version = "1.4.0", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kittycad = "0.2.25"
|
kittycad = "0.2.26"
|
||||||
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"
|
||||||
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
tauri = { version = "1.4.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tokio = { version = "1.32.0", features = ["time"] }
|
tokio = { version = "1.32.0", features = ["time"] }
|
||||||
toml = "0.8.0"
|
toml = "0.8.0"
|
||||||
|
@ -6,6 +6,7 @@ use std::io::Read;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use oauth2::TokenResponse;
|
use oauth2::TokenResponse;
|
||||||
use tauri::{InvokeError, Manager};
|
use tauri::{InvokeError, Manager};
|
||||||
|
const DEFAULT_HOST: &str = "https://api.kittycad.io";
|
||||||
|
|
||||||
/// This command returns the a json string parse from a toml file at the path.
|
/// This command returns the a json string parse from a toml file at the path.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -88,11 +89,34 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
|||||||
///This command returns the KittyCAD user info given a token.
|
///This command returns the KittyCAD user info given a token.
|
||||||
/// The string returned from this method is the user info as a json string.
|
/// The string returned from this method is the user info as a json string.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_user(token: Option<String>) -> Result<kittycad::types::User, InvokeError> {
|
async fn get_user(
|
||||||
|
token: Option<String>,
|
||||||
|
hostname: &str,
|
||||||
|
) -> Result<kittycad::types::User, InvokeError> {
|
||||||
|
// Use the host passed in if it's set.
|
||||||
|
// Otherwise, use the default host.
|
||||||
|
let host = if hostname.is_empty() {
|
||||||
|
DEFAULT_HOST.to_string()
|
||||||
|
} else {
|
||||||
|
hostname.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change the baseURL to the one we want.
|
||||||
|
let mut baseurl = host.to_string();
|
||||||
|
if !host.starts_with("http://") && !host.starts_with("https://") {
|
||||||
|
baseurl = format!("https://{host}");
|
||||||
|
if host.starts_with("localhost") {
|
||||||
|
baseurl = format!("http://{host}")
|
||||||
|
}
|
||||||
|
}
|
||||||
println!("Getting user info...");
|
println!("Getting user info...");
|
||||||
|
|
||||||
// use kittycad library to fetch the user info from /user/me
|
// use kittycad library to fetch the user info from /user/me
|
||||||
let client = kittycad::Client::new(token.unwrap());
|
let mut client = kittycad::Client::new(token.unwrap());
|
||||||
|
|
||||||
|
if baseurl != DEFAULT_HOST {
|
||||||
|
client.set_base_url(&baseurl);
|
||||||
|
}
|
||||||
|
|
||||||
let user_info: kittycad::types::User = client
|
let user_info: kittycad::types::User = client
|
||||||
.users()
|
.users()
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "kittycad-modeling",
|
"productName": "kittycad-modeling",
|
||||||
"version": "0.7.1"
|
"version": "0.9.2"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@ -36,6 +36,9 @@
|
|||||||
"https://api.dev.kittycad.io/*"
|
"https://api.dev.kittycad.io/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"os": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"open": true
|
"open": true
|
||||||
},
|
},
|
||||||
@ -71,9 +74,9 @@
|
|||||||
"shortDescription": "",
|
"shortDescription": "",
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"windows": {
|
"windows": {
|
||||||
"certificateThumbprint": null,
|
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": ""
|
"timestampUrl": "http://timestamp.digicert.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
|
40
src/App.tsx
40
src/App.tsx
@ -1,7 +1,6 @@
|
|||||||
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
||||||
import { DebugPanel } from './components/DebugPanel'
|
import { DebugPanel } from './components/DebugPanel'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { _executor } from './lang/executor'
|
|
||||||
import { PaneType, useStore } from './useStore'
|
import { PaneType, useStore } from './useStore'
|
||||||
import { Logs, KCLErrors } from './components/Logs'
|
import { Logs, KCLErrors } from './components/Logs'
|
||||||
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
||||||
@ -32,6 +31,7 @@ import { TextEditor } from 'components/TextEditor'
|
|||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||||
@ -40,7 +40,6 @@ export function App() {
|
|||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const {
|
const {
|
||||||
setCode,
|
setCode,
|
||||||
engineCommandManager,
|
|
||||||
buttonDownInStream,
|
buttonDownInStream,
|
||||||
openPanes,
|
openPanes,
|
||||||
setOpenPanes,
|
setOpenPanes,
|
||||||
@ -48,16 +47,17 @@ export function App() {
|
|||||||
streamDimensions,
|
streamDimensions,
|
||||||
guiMode,
|
guiMode,
|
||||||
setGuiMode,
|
setGuiMode,
|
||||||
|
executeAst,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
setCode: s.setCode,
|
setCode: s.setCode,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
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,
|
||||||
|
executeAst: s.executeAst,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -88,14 +88,25 @@ export function App() {
|
|||||||
if (guiMode.mode === 'sketch') {
|
if (guiMode.mode === 'sketch') {
|
||||||
if (guiMode.sketchMode === 'selectFace') return
|
if (guiMode.sketchMode === 'selectFace') return
|
||||||
if (guiMode.sketchMode === 'sketchEdit') {
|
if (guiMode.sketchMode === 'sketchEdit') {
|
||||||
engineCommandManager?.sendSceneCommand({
|
// TODO: share this with Toolbar's "Exit sketch" button
|
||||||
|
// exiting sketch should be done consistently across all exits
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: { type: 'edit_mode_exit' },
|
cmd: { type: 'edit_mode_exit' },
|
||||||
})
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
})
|
||||||
setGuiMode({ mode: 'default' })
|
setGuiMode({ mode: 'default' })
|
||||||
|
// this is necessary to get the UI back into a consistent
|
||||||
|
// state right now, hopefully won't need to rerender
|
||||||
|
// when exiting sketch mode in the future
|
||||||
|
executeAst()
|
||||||
} else {
|
} else {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -109,6 +120,7 @@ export function App() {
|
|||||||
rotation: guiMode.rotation,
|
rotation: guiMode.rotation,
|
||||||
position: guiMode.position,
|
position: guiMode.position,
|
||||||
pathToNode: guiMode.pathToNode,
|
pathToNode: guiMode.pathToNode,
|
||||||
|
pathId: guiMode.pathId,
|
||||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -117,12 +129,13 @@ export function App() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const paneOpacity =
|
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||||
onboardingStatus === onboardingPaths.CAMERA
|
(p) => p === onboardingStatus
|
||||||
? 'opacity-20'
|
)
|
||||||
: didDragInStream
|
? 'opacity-20'
|
||||||
? 'opacity-40'
|
: didDragInStream
|
||||||
: ''
|
? 'opacity-40'
|
||||||
|
: ''
|
||||||
|
|
||||||
// Use file code loaded from disk
|
// Use file code loaded from disk
|
||||||
// on mount, and overwrite any locally-stored code
|
// on mount, and overwrite any locally-stored code
|
||||||
@ -142,7 +155,7 @@ export function App() {
|
|||||||
useEngineConnectionSubscriptions()
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
engineCommandManager?.sendSceneCommand(message)
|
engineCommandManager.sendSceneCommand(message)
|
||||||
}, 16)
|
}, 16)
|
||||||
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
e.nativeEvent.preventDefault()
|
e.nativeEvent.preventDefault()
|
||||||
@ -202,7 +215,6 @@ export function App() {
|
|||||||
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
} else {
|
} else {
|
||||||
console.log('none')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +267,7 @@ export function App() {
|
|||||||
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="h-full flex flex-col justify-between">
|
<div id="code-pane" className="h-full flex flex-col justify-between">
|
||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
title="Code"
|
title="Code"
|
||||||
icon={faCode}
|
icon={faCode}
|
||||||
|
@ -6,9 +6,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
|
|||||||
const {
|
const {
|
||||||
auth: { state },
|
auth: { state },
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
const isLoggedIn = state.matches('checkIfLoggedIn')
|
const isLoggingIn = state.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggedIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>Loading KittyCAD Modeling App...</Loading>
|
<Loading>Loading KittyCAD Modeling App...</Loading>
|
||||||
) : (
|
) : (
|
||||||
<>{children}</>
|
<>{children}</>
|
||||||
|
@ -130,6 +130,7 @@ const router = createBrowserRouter(
|
|||||||
path: paths.INDEX,
|
path: paths.INDEX,
|
||||||
loader: () =>
|
loader: () =>
|
||||||
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
|
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: paths.FILE + '/:id',
|
path: paths.FILE + '/:id',
|
||||||
@ -140,7 +141,6 @@ const router = createBrowserRouter(
|
|||||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||||
</Auth>
|
</Auth>
|
||||||
),
|
),
|
||||||
errorElement: <ErrorPage />,
|
|
||||||
id: paths.FILE,
|
id: paths.FILE,
|
||||||
loader: async ({
|
loader: async ({
|
||||||
request,
|
request,
|
||||||
|
@ -47,15 +47,52 @@
|
|||||||
@apply hover:bg-cool-20;
|
@apply hover:bg-cool-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
.smallScrollbar::-webkit-scrollbar {
|
.toolbarButtons::-webkit-scrollbar {
|
||||||
@apply h-0.5;
|
@apply h-0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.smallScrollbar {
|
.toolbarButtons {
|
||||||
@apply overflow-x-auto;
|
@apply flex items-center overflow-x-auto;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbarButtons button {
|
||||||
|
@apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
@apply gap-1.5 p-0.5 pr-1;
|
||||||
|
@apply rounded-sm;
|
||||||
|
}
|
||||||
|
:global(.dark) .toolbarButtons button {
|
||||||
|
@apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
|
||||||
|
}
|
||||||
|
.toolbarButtons button:hover {
|
||||||
|
@apply text-cool-90 bg-cool-10;
|
||||||
|
}
|
||||||
|
:global(.sketch) .toolbarButtons button:hover {
|
||||||
|
@apply text-fern-90 bg-fern-10;
|
||||||
|
}
|
||||||
|
.toolbarButtons button:disabled {
|
||||||
|
@apply text-chalkboard-70 bg-chalkboard-30;
|
||||||
|
}
|
||||||
|
.toolbarButtons button:disabled:hover {
|
||||||
|
@apply !bg-inherit !text-inherit cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .toolbarButtons button {
|
||||||
|
@apply text-chalkboard-20 border-chalkboard-50;
|
||||||
|
}
|
||||||
|
:global(.dark) .toolbarButtons button:hover {
|
||||||
|
@apply text-cool-10 border-chalkboard-50 bg-cool-90;
|
||||||
|
}
|
||||||
|
:global(.dark .sketch) .toolbarButtons button:hover {
|
||||||
|
@apply text-fern-10 border-chalkboard-50 bg-fern-90;
|
||||||
|
}
|
||||||
|
:global(.dark) .toolbarButtons button:disabled {
|
||||||
|
@apply text-chalkboard-40 bg-chalkboard-80;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.dark) .popoverToggle {
|
:global(.dark) .popoverToggle {
|
||||||
@apply hover:bg-cool-90;
|
@apply hover:bg-cool-90;
|
||||||
}
|
}
|
||||||
|
110
src/Toolbar.tsx
110
src/Toolbar.tsx
@ -1,4 +1,4 @@
|
|||||||
import { useStore, toolTips, Selections } from './useStore'
|
import { useStore, toolTips, ToolTip } from './useStore'
|
||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/queryAst'
|
import { getNodePathFromSourceRange } from './lang/queryAst'
|
||||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
import { HorzVert } from './components/Toolbar/HorzVert'
|
||||||
@ -17,6 +17,31 @@ import { Popover, Transition } from '@headlessui/react'
|
|||||||
import styles from './Toolbar.module.css'
|
import styles from './Toolbar.module.css'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useAppMode } from 'hooks/useAppMode'
|
import { useAppMode } from 'hooks/useAppMode'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
|
||||||
|
export const sketchButtonClassnames = {
|
||||||
|
background:
|
||||||
|
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
|
||||||
|
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
|
||||||
|
sketch_line: 'Line',
|
||||||
|
line: 'Line',
|
||||||
|
move: 'Move',
|
||||||
|
angledLine: 'Angled Line',
|
||||||
|
angledLineThatIntersects: 'Angled Line That Intersects',
|
||||||
|
angledLineOfXLength: 'Angled Line Of X Length',
|
||||||
|
angledLineOfYLength: 'Angled Line Of Y Length',
|
||||||
|
angledLineToX: 'Angled Line To X',
|
||||||
|
angledLineToY: 'Angled Line To Y',
|
||||||
|
lineTo: 'Line to Point',
|
||||||
|
xLine: 'Horizontal Line',
|
||||||
|
yLine: 'Vertical Line',
|
||||||
|
xLineTo: 'Horizontal Line to Point',
|
||||||
|
yLineTo: 'Vertical Line to Point',
|
||||||
|
}
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -26,7 +51,6 @@ export const Toolbar = () => {
|
|||||||
ast,
|
ast,
|
||||||
updateAst,
|
updateAst,
|
||||||
programMemory,
|
programMemory,
|
||||||
engineCommandManager,
|
|
||||||
executeAst,
|
executeAst,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
@ -35,18 +59,13 @@ export const Toolbar = () => {
|
|||||||
ast: s.ast,
|
ast: s.ast,
|
||||||
updateAst: s.updateAst,
|
updateAst: s.updateAst,
|
||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
executeAst: s.executeAst,
|
executeAst: s.executeAst,
|
||||||
}))
|
}))
|
||||||
useAppMode()
|
useAppMode()
|
||||||
|
|
||||||
useEffect(() => {
|
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||||
console.log('guiMode', guiMode)
|
|
||||||
}, [guiMode])
|
|
||||||
|
|
||||||
function ToolbarButtons() {
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.smallScrollbar}>
|
<span className={styles.toolbarButtons + ' ' + className}>
|
||||||
{guiMode.mode === 'default' && (
|
{guiMode.mode === 'default' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -55,7 +74,9 @@ export const Toolbar = () => {
|
|||||||
sketchMode: 'selectFace',
|
sketchMode: 'selectFace',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
Start Sketch
|
Start Sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -74,31 +95,31 @@ export const Toolbar = () => {
|
|||||||
)
|
)
|
||||||
updateAst(modifiedAst, true)
|
updateAst(modifiedAst, true)
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
SketchOnFace
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
|
Sketch on Face
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{guiMode.mode === 'canEditSketch' && (
|
{guiMode.mode === 'canEditSketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log('guiMode.pathId', guiMode.pathId)
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
engineCommandManager?.sendSceneCommand({
|
ast,
|
||||||
type: 'modeling_cmd_req',
|
selectionRanges.codeBasedSelections[0].range
|
||||||
cmd_id: uuidv4(),
|
)
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: guiMode.pathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'enterSketchEdit',
|
||||||
pathToNode: guiMode.pathToNode,
|
pathToNode: pathToNode,
|
||||||
rotation: guiMode.rotation,
|
rotation: [0, 0, 0, 1],
|
||||||
position: guiMode.position,
|
position: [0, 0, 0],
|
||||||
|
pathId: guiMode.pathId,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
Edit Sketch
|
Edit Sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -117,8 +138,10 @@ export const Toolbar = () => {
|
|||||||
)
|
)
|
||||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
ExtrudeSketch
|
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||||
|
Extrude
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -134,8 +157,10 @@ export const Toolbar = () => {
|
|||||||
)
|
)
|
||||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
ExtrudeSketch (w/o pipe)
|
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||||
|
Extrude as new
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -143,12 +168,12 @@ export const Toolbar = () => {
|
|||||||
{guiMode.mode === 'sketch' && (
|
{guiMode.mode === 'sketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: { type: 'edit_mode_exit' },
|
cmd: { type: 'edit_mode_exit' },
|
||||||
})
|
})
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
@ -157,7 +182,15 @@ export const Toolbar = () => {
|
|||||||
setGuiMode({ mode: 'default' })
|
setGuiMode({ mode: 'default' })
|
||||||
executeAst()
|
executeAst()
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon="exit"
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
Exit sketch
|
Exit sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -176,7 +209,7 @@ export const Toolbar = () => {
|
|||||||
<button
|
<button
|
||||||
key={sketchFnName}
|
key={sketchFnName}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -198,12 +231,25 @@ export const Toolbar = () => {
|
|||||||
sketchMode: sketchFnName,
|
sketchMode: sketchFnName,
|
||||||
waitingFirstClick: true,
|
waitingFirstClick: true,
|
||||||
isTooltip: true,
|
isTooltip: true,
|
||||||
|
pathId: guiMode.pathId,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
className={
|
||||||
|
'group ' +
|
||||||
|
(guiMode.sketchMode === sketchFnName
|
||||||
|
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{sketchFnName}
|
<ActionIcon
|
||||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
icon={sketchFnName.includes('line') ? 'line' : 'move'}
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
{sketchFnLabels[sketchFnName]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -234,7 +280,7 @@ export const Toolbar = () => {
|
|||||||
<span className={styles.toolbarCap + ' ' + styles.label}>
|
<span className={styles.toolbarCap + ' ' + styles.label}>
|
||||||
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
||||||
</span>
|
</span>
|
||||||
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||||
<ToolbarButtons />
|
<ToolbarButtons />
|
||||||
</menu>
|
</menu>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
@ -275,7 +321,7 @@ export const Toolbar = () => {
|
|||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<ToolbarButtons />
|
<ToolbarButtons className="flex-wrap" />
|
||||||
</section>
|
</section>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
|
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { CustomIcon, CustomIconName } from './CustomIcon'
|
||||||
|
|
||||||
const iconSizes = {
|
const iconSizes = {
|
||||||
sm: 12,
|
sm: 12,
|
||||||
@ -13,7 +14,7 @@ const iconSizes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionIconProps extends React.PropsWithChildren {
|
export interface ActionIconProps extends React.PropsWithChildren {
|
||||||
icon?: SolidIconDefinition | BrandIconDefinition
|
icon?: SolidIconDefinition | BrandIconDefinition | CustomIconName
|
||||||
className?: string
|
className?: string
|
||||||
bgClassName?: string
|
bgClassName?: string
|
||||||
iconClassName?: string
|
iconClassName?: string
|
||||||
@ -28,25 +29,39 @@ export const ActionIcon = ({
|
|||||||
size = 'md',
|
size = 'md',
|
||||||
children,
|
children,
|
||||||
}: ActionIconProps) => {
|
}: ActionIconProps) => {
|
||||||
|
// By default, we reverse the icon color and background color in dark mode
|
||||||
|
const computedIconClassName =
|
||||||
|
iconClassName ||
|
||||||
|
`text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50`
|
||||||
|
|
||||||
|
const computedBgClassName =
|
||||||
|
bgClassName ||
|
||||||
|
`bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10 group-disabled:bg-chalkboard-80 dark:group-disabled:bg-chalkboard-80`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
`p-${
|
`p-${
|
||||||
size === 'xl' ? '2' : '1'
|
size === 'xl' ? '2' : '1'
|
||||||
} w-fit inline-grid place-content-center ${className} ` +
|
} w-fit inline-grid place-content-center ${className} ` +
|
||||||
(bgClassName ||
|
computedBgClassName
|
||||||
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10')
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{children || (
|
{children ? (
|
||||||
|
children
|
||||||
|
) : typeof icon === 'string' ? (
|
||||||
|
<CustomIcon
|
||||||
|
name={icon}
|
||||||
|
width={iconSizes[size]}
|
||||||
|
height={iconSizes[size]}
|
||||||
|
className={computedIconClassName}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
width={iconSizes[size]}
|
width={iconSizes[size]}
|
||||||
height={iconSizes[size]}
|
height={iconSizes[size]}
|
||||||
className={
|
className={computedIconClassName}
|
||||||
iconClassName ||
|
|
||||||
'text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-liquid-100 dark:group-hover:text-liquid-100 dark:hover:text-liquid-100'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@ export const AppHeader = ({
|
|||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={
|
className={
|
||||||
(showToolbar ? 'grid ' : 'flex justify-between ') +
|
(showToolbar ? 'w-full grid ' : 'flex justify-between ') +
|
||||||
styles.header +
|
styles.header +
|
||||||
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
||||||
className
|
className
|
||||||
@ -38,7 +38,7 @@ export const AppHeader = ({
|
|||||||
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
|
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
|
||||||
{/* Toolbar if the context deems it */}
|
{/* Toolbar if the context deems it */}
|
||||||
{showToolbar && (
|
{showToolbar && (
|
||||||
<div className="max-w-4xl">
|
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
} from '../lang/modifyAst'
|
} from '../lang/modifyAst'
|
||||||
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export const AvailableVars = ({
|
export const AvailableVars = ({
|
||||||
onVarClick,
|
onVarClick,
|
||||||
@ -92,14 +93,11 @@ export function useCalc({
|
|||||||
newVariableInsertIndex: number
|
newVariableInsertIndex: number
|
||||||
setNewVariableName: (a: string) => void
|
setNewVariableName: (a: string) => void
|
||||||
} {
|
} {
|
||||||
const { ast, programMemory, selectionRange, engineCommandManager } = useStore(
|
const { ast, programMemory, selectionRange } = useStore((s) => ({
|
||||||
(s) => ({
|
ast: s.ast,
|
||||||
ast: s.ast,
|
programMemory: s.programMemory,
|
||||||
programMemory: s.programMemory,
|
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
|
||||||
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
|
}))
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const [availableVarInfo, setAvailableVarInfo] = useState<
|
const [availableVarInfo, setAvailableVarInfo] = useState<
|
||||||
ReturnType<typeof findAllPreviousVariables>
|
ReturnType<typeof findAllPreviousVariables>
|
||||||
@ -140,7 +138,6 @@ export function useCalc({
|
|||||||
}, [ast, programMemory, selectionRange])
|
}, [ast, programMemory, selectionRange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!engineCommandManager) return
|
|
||||||
try {
|
try {
|
||||||
const code = `const __result__ = ${value}\nshow(__result__)`
|
const code = `const __result__ = ${value}\nshow(__result__)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parser_wasm(code)
|
||||||
|
@ -23,7 +23,8 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
|||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (e.eventPhase === 3) {
|
const target = e.target as HTMLElement
|
||||||
|
if (e.eventPhase === 3 && target.closest('a') === null) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ export const CommandBarProvider = ({
|
|||||||
|
|
||||||
const CommandBar = () => {
|
const CommandBar = () => {
|
||||||
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
|
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
|
||||||
useHotkeys('meta+k', () => {
|
useHotkeys(['meta+k', 'meta+/'], () => {
|
||||||
if (commands.length === 0) return
|
if (commands.length === 0) return
|
||||||
setCommandBarOpen(!commandBarOpen)
|
setCommandBarOpen(!commandBarOpen)
|
||||||
})
|
})
|
||||||
@ -221,10 +221,10 @@ const CommandBar = () => {
|
|||||||
<Combobox
|
<Combobox
|
||||||
value={selectedCommand}
|
value={selectedCommand}
|
||||||
onChange={handleCommandSelection}
|
onChange={handleCommandSelection}
|
||||||
className="rounded relative mx-auto p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg"
|
className="relative w-full max-w-xl p-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
|
||||||
as="div"
|
as="div"
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
|
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
|
||||||
<div>
|
<div>
|
||||||
{inSubCommand && (
|
{inSubCommand && (
|
||||||
@ -235,7 +235,7 @@ const CommandBar = () => {
|
|||||||
)}
|
)}
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
className="bg-transparent focus:outline-none w-full"
|
className="w-full bg-transparent focus:outline-none"
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.metaKey && event.key === 'k')
|
if (event.metaKey && event.key === 'k')
|
||||||
setCommandBarOpen(false)
|
setCommandBarOpen(false)
|
||||||
@ -264,12 +264,12 @@ const CommandBar = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Combobox.Options static className="max-h-96 overflow-y-auto">
|
<Combobox.Options static className="overflow-y-auto max-h-96">
|
||||||
{filteredCommands?.map((commandResult) => (
|
{filteredCommands?.map((commandResult) => (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={commandResult.item.name}
|
key={commandResult.item.name}
|
||||||
value={commandResult}
|
value={commandResult}
|
||||||
className="my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90 py-1 px-2"
|
className="px-2 py-1 my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90"
|
||||||
>
|
>
|
||||||
<p>{commandResult.item.name}</p>
|
<p>{commandResult.item.name}</p>
|
||||||
{(commandResult.item as SubCommand).description && (
|
{(commandResult.item as SubCommand).description && (
|
||||||
|
161
src/components/CustomIcon.tsx
Normal file
161
src/components/CustomIcon.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
export type CustomIconName =
|
||||||
|
| 'equal'
|
||||||
|
| 'exit'
|
||||||
|
| 'extrude'
|
||||||
|
| 'horizontal'
|
||||||
|
| 'line'
|
||||||
|
| 'move'
|
||||||
|
| 'parallel'
|
||||||
|
| 'sketch'
|
||||||
|
| 'vertical'
|
||||||
|
|
||||||
|
export const CustomIcon = ({
|
||||||
|
name,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
name: CustomIconName
|
||||||
|
} & React.SVGProps<SVGSVGElement>) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'equal':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'exit':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M17 10L3 10M3 10L6.5 6.5M3 10L6.5 13.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'extrude':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10 3L10.3536 3.35355L12.3536 5.35355L11.6465 6.06066L10.5 4.91421V11.5854C11.0826 11.7913 11.5 12.3469 11.5 13C11.5 13.8284 10.8284 14.5 10 14.5C9.17157 14.5 8.5 13.8284 8.5 13C8.5 12.3469 8.91741 11.7913 9.5 11.5854V4.91421L8.35356 6.06066L7.64645 5.35355L9.64645 3.35355L10 3ZM1.95887 12.3282L8 8.63644V9.80838L2.91773 12.9142L10 17.2423L17.0823 12.9142L12 9.80838V8.63644L18.0411 12.3282L19 12.9142L19 14.9683H18V13.5253L10.5 18.1087V19.9683H9.5V18.1087L2 13.5253V14.9683H1L1 12.9142L1.95887 12.3282Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'horizontal':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M4 9.5H16V11.5H4V9.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'line':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.5 6C16.3284 6 17 5.32843 17 4.5C17 3.67157 16.3284 3 15.5 3C14.6716 3 14 3.67157 14 4.5C14 4.73107 14.0522 4.94993 14.1456 5.14543L5.14543 14.1456C4.94993 14.0522 4.73107 14 4.5 14C3.67157 14 3 14.6716 3 15.5C3 16.3284 3.67157 17 4.5 17C5.32843 17 6 16.3284 6 15.5C6 15.2679 5.94729 15.0482 5.8532 14.852L14.852 5.8532C15.0482 5.94729 15.2679 6 15.5 6Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'move':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10 2.29289L10.3536 2.64645L12.3536 4.64645L11.6465 5.35355L10.5 4.20711V8V9.50001H12L15.7929 9.50001L14.6465 8.35356L15.3536 7.64645L17.3536 9.64645L17.7071 10L17.3536 10.3536L15.3536 12.3536L14.6465 11.6465L15.7929 10.5H12H10.5V12V15.7929L11.6465 14.6464L12.3536 15.3536L10.3536 17.3536L10 17.7071L9.64645 17.3536L7.64645 15.3536L8.35356 14.6464L9.50001 15.7929V12V10.5H8.00001H4.20712L5.35357 11.6465L4.64646 12.3536L2.64646 10.3536L2.29291 10L2.64646 9.64645L4.64646 7.64645L5.35357 8.35356L4.20712 9.50001H8.00001H9.50001V8V4.20711L8.35356 5.35355L7.64645 4.64645L9.64645 2.64645L10 2.29289Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'parallel':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M8 16V4H6V16H8ZM14 16V4H12V16H14Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'sketch':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M14.8037 13.4035L15.5509 14.1635L16.3682 16.8386L13.5521 16.1346L12.8186 15.3885L14.8037 13.4035ZM14.1025 12.6903L12.1175 14.6754L3.48609 5.89624C2.94588 5.34678 2.94963 4.46456 3.49448 3.91971C4.04591 3.36828 4.94112 3.37208 5.48786 3.92817L14.1025 12.6903ZM6.20094 3.22709L16.4357 13.6371L17.5003 17.1216L17.8412 18.2376L16.7091 17.9546L13.0364 17.0364L2.77301 6.59732C1.84793 5.6564 1.85434 4.14564 2.78737 3.2126C3.73167 2.2683 5.26468 2.27481 6.20094 3.22709Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'vertical':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M11 4V16H9V4H11Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { EngineCommand } from '../lang/std/engineConnection'
|
import { EngineCommand } from '../lang/std/engineConnection'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@ -7,6 +6,7 @@ import { ActionButton } from '../components/ActionButton'
|
|||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { isReducedMotion } from 'lang/util'
|
import { isReducedMotion } from 'lang/util'
|
||||||
import { AstExplorer } from './AstExplorer'
|
import { AstExplorer } from './AstExplorer'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
type SketchModeCmd = Extract<
|
type SketchModeCmd = Extract<
|
||||||
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
||||||
@ -14,9 +14,6 @@ type SketchModeCmd = Extract<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
||||||
const { engineCommandManager } = useStore((s) => ({
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
}))
|
|
||||||
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
|
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
|
||||||
type: 'default_camera_enable_sketch_mode',
|
type: 'default_camera_enable_sketch_mode',
|
||||||
origin: { x: 0, y: 0, z: 0 },
|
origin: { x: 0, y: 0, z: 0 },
|
||||||
@ -30,7 +27,11 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
return (
|
return (
|
||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
{...props}
|
{...props}
|
||||||
className={'!absolute !h-auto bottom-5 right-5 ' + className}
|
className={
|
||||||
|
'!absolute overflow-hidden !h-auto bottom-5 right-5 ' + className
|
||||||
|
}
|
||||||
|
// header height, top-5, and bottom-5
|
||||||
|
style={{ maxHeight: 'calc(100% - 3rem - 1.25rem - 1.25rem)' }}
|
||||||
>
|
>
|
||||||
<section className="p-4 flex flex-col gap-4">
|
<section className="p-4 flex flex-col gap-4">
|
||||||
<Xyz
|
<Xyz
|
||||||
@ -66,19 +67,18 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
className="w-16"
|
className="w-16"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={sketchModeCmd.ortho}
|
checked={sketchModeCmd.ortho}
|
||||||
onChange={(a) => {
|
onChange={(a) =>
|
||||||
console.log(a, (a as any).checked)
|
|
||||||
setSketchModeCmd({
|
setSketchModeCmd({
|
||||||
...sketchModeCmd,
|
...sketchModeCmd,
|
||||||
ortho: a.target.checked,
|
ortho: a.target.checked,
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: sketchModeCmd,
|
cmd: sketchModeCmd,
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
|
@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
|
|||||||
</code>
|
</code>
|
||||||
, and isn't backed up anywhere! Visit{' '}
|
, and isn't backed up anywhere! Visit{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/KittyCAD/modeling-app/releases"
|
href="https://kittycad.io/modeling-app/download"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
||||||
>
|
>
|
||||||
our GitHub repository
|
our website
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to download the app for the best experience.
|
to download the app for the best experience.
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,8 +1,62 @@
|
|||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { useRouteError, isRouteErrorResponse } from 'react-router-dom'
|
||||||
|
import { ActionButton } from './ActionButton'
|
||||||
|
import {
|
||||||
|
faBug,
|
||||||
|
faHome,
|
||||||
|
faRefresh,
|
||||||
|
faTrash,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
export const ErrorPage = () => {
|
export const ErrorPage = () => {
|
||||||
|
let error = useRouteError()
|
||||||
|
|
||||||
|
console.error('error', error)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-screen">
|
<div className="flex flex-col items-center justify-center h-screen">
|
||||||
<h1 className="text-4xl font-bold">404</h1>
|
<section className="max-w-full xl:max-w-4xl mx-auto">
|
||||||
<p className="text-2xl font-bold">Page not found</p>
|
<h1 className="text-4xl mb-8 font-bold">
|
||||||
|
An unexpected error occurred
|
||||||
|
</h1>
|
||||||
|
{isRouteErrorResponse(error) && (
|
||||||
|
<p className="mb-8">
|
||||||
|
{error.status}: {error.data}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between gap-2 mt-6">
|
||||||
|
{isTauri() && (
|
||||||
|
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
|
||||||
|
Go Home
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
icon={{ icon: faRefresh }}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Reload
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
icon={{ icon: faTrash }}
|
||||||
|
onClick={() => {
|
||||||
|
window.localStorage.clear()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear storage
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="link"
|
||||||
|
icon={{ icon: faBug }}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
to="https://discord.com/channels/915388055236509727/1138967922614743060"
|
||||||
|
>
|
||||||
|
Report Bug
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Modal from 'react-modal'
|
import Modal from 'react-modal'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
|
|
||||||
@ -18,10 +18,6 @@ interface ExportButtonProps extends React.PropsWithChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
||||||
const { engineCommandManager } = useStore((s) => ({
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const [modalIsOpen, setIsOpen] = React.useState(false)
|
const [modalIsOpen, setIsOpen] = React.useState(false)
|
||||||
|
|
||||||
const defaultType = 'gltf'
|
const defaultType = 'gltf'
|
||||||
@ -66,7 +62,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'export',
|
type: 'export',
|
||||||
|
@ -24,7 +24,11 @@ export const MemoryPanel = ({
|
|||||||
<CollapsiblePanel {...props}>
|
<CollapsiblePanel {...props}>
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<div className="absolute inset-0 flex flex-col items-start">
|
<div className="absolute inset-0 flex flex-col items-start">
|
||||||
<div className=" h-full console-tile w-full">
|
<div
|
||||||
|
className="overflow-y-auto h-full console-tile w-full"
|
||||||
|
style={{ marginBottom: 36 }}
|
||||||
|
>
|
||||||
|
{/* 36px is the height of PanelHeader */}
|
||||||
<ReactJson
|
<ReactJson
|
||||||
src={ProcessedMemory}
|
src={ProcessedMemory}
|
||||||
collapsed={1}
|
collapsed={1}
|
||||||
@ -46,7 +50,7 @@ export const MemoryPanel = ({
|
|||||||
|
|
||||||
export const processMemory = (programMemory: ProgramMemory) => {
|
export const processMemory = (programMemory: ProgramMemory) => {
|
||||||
const processedMemory: any = {}
|
const processedMemory: any = {}
|
||||||
Object.keys(programMemory.root).forEach((key) => {
|
Object.keys(programMemory?.root || {}).forEach((key) => {
|
||||||
const val = programMemory.root[key]
|
const val = programMemory.root[key]
|
||||||
if (typeof val.value !== 'function') {
|
if (typeof val.value !== 'function') {
|
||||||
if (val.type === 'SketchGroup') {
|
if (val.type === 'SketchGroup') {
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/tauri'
|
|
||||||
import { open } from '@tauri-apps/api/dialog'
|
|
||||||
import { useStore } from '../useStore'
|
|
||||||
|
|
||||||
export const OpenFileButton = () => {
|
|
||||||
const { setCode } = useStore((s) => ({
|
|
||||||
setCode: s.setCode,
|
|
||||||
}))
|
|
||||||
const handleClick = async () => {
|
|
||||||
const selected = await open({
|
|
||||||
multiple: false,
|
|
||||||
directory: false,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: 'CAD',
|
|
||||||
extensions: ['toml'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (Array.isArray(selected)) {
|
|
||||||
// User selected multiple files
|
|
||||||
// We should not get here, since multiple is false.
|
|
||||||
} else if (selected === null) {
|
|
||||||
// User cancelled the selection
|
|
||||||
// Do nothing.
|
|
||||||
} else {
|
|
||||||
// User selected a single file
|
|
||||||
// We want to invoke our command to read the file.
|
|
||||||
const json: string = await invoke('read_toml', { path: selected })
|
|
||||||
const packageDetails = JSON.parse(json).package
|
|
||||||
if (packageDetails.main) {
|
|
||||||
const absPath = [
|
|
||||||
...selected.split('/').slice(0, -1),
|
|
||||||
packageDetails.main,
|
|
||||||
].join('/')
|
|
||||||
const file: string = await invoke('read_txt_file', { path: absPath })
|
|
||||||
setCode(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <button onClick={() => handleClick()}>Open File</button>
|
|
||||||
}
|
|
@ -16,8 +16,8 @@ const ProjectSidebarMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return renderAsLink ? (
|
return renderAsLink ? (
|
||||||
<Link
|
<Link
|
||||||
to={'../'}
|
to={paths.HOME}
|
||||||
className="flex items-center gap-4 my-2"
|
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
||||||
data-testid="project-sidebar-link"
|
data-testid="project-sidebar-link"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@ -26,7 +26,7 @@ const ProjectSidebarMenu = ({
|
|||||||
className="h-9 w-auto"
|
className="h-9 w-auto"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max"
|
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
|
||||||
data-testid="project-sidebar-link-name"
|
data-testid="project-sidebar-link-name"
|
||||||
>
|
>
|
||||||
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||||
@ -35,15 +35,15 @@ const ProjectSidebarMenu = ({
|
|||||||
) : (
|
) : (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className="border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
||||||
data-testid="project-sidebar-toggle"
|
data-testid="project-sidebar-toggle"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/kitt-8bit-winking.svg"
|
src="/kitt-8bit-winking.svg"
|
||||||
alt="KittyCAD App"
|
alt="KittyCAD App"
|
||||||
className="h-9 w-auto"
|
className="h-full w-auto"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max">
|
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
|
||||||
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
|
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||||
</span>
|
</span>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
|
@ -21,6 +21,11 @@ import {
|
|||||||
} from 'lang/std/sketch'
|
} from 'lang/std/sketch'
|
||||||
import { getNodeFromPath } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
||||||
|
import { modify_ast_for_sketch } from '../wasm-lib/pkg/wasm_lib'
|
||||||
|
import { KCLError } from 'lang/errors'
|
||||||
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
|
import { rangeTypeFix } from 'lang/abstractSyntaxTree'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }) => {
|
export const Stream = ({ className = '' }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -28,7 +33,6 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const {
|
const {
|
||||||
mediaStream,
|
mediaStream,
|
||||||
engineCommandManager,
|
|
||||||
setButtonDownInStream,
|
setButtonDownInStream,
|
||||||
didDragInStream,
|
didDragInStream,
|
||||||
setDidDragInStream,
|
setDidDragInStream,
|
||||||
@ -41,7 +45,6 @@ export const Stream = ({ className = '' }) => {
|
|||||||
programMemory,
|
programMemory,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
mediaStream: s.mediaStream,
|
mediaStream: s.mediaStream,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
setButtonDownInStream: s.setButtonDownInStream,
|
setButtonDownInStream: s.setButtonDownInStream,
|
||||||
fileId: s.fileId,
|
fileId: s.fileId,
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
@ -69,7 +72,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
if (!mediaStream) return
|
if (!mediaStream) return
|
||||||
videoRef.current.srcObject = mediaStream
|
videoRef.current.srcObject = mediaStream
|
||||||
}, [mediaStream, engineCommandManager])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
|
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
@ -103,7 +106,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'handle_mouse_drag_start',
|
type: 'handle_mouse_drag_start',
|
||||||
@ -117,7 +120,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
guiMode.sketchMode === ('sketch_line' as any)
|
guiMode.sketchMode === ('sketch_line' as any)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'camera_drag_start',
|
type: 'camera_drag_start',
|
||||||
@ -135,7 +138,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
||||||
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
||||||
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_zoom',
|
type: 'default_camera_zoom',
|
||||||
@ -173,7 +176,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!didDragInStream) {
|
if (!didDragInStream) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'select_with_point',
|
type: 'select_with_point',
|
||||||
@ -210,15 +213,10 @@ export const Stream = ({ className = '' }) => {
|
|||||||
window: { x, y },
|
window: { x, y },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
|
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
|
||||||
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
if (!(guiMode.mode === 'sketch')) return
|
||||||
if (
|
|
||||||
!(
|
if (guiMode.sketchMode === 'selectFace') return
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === ('sketch_line' as any as 'line')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
// Check if the sketch group already exists.
|
// Check if the sketch group already exists.
|
||||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
@ -230,13 +228,60 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const sketchGroup = programMemory.root[variableName]
|
const sketchGroup = programMemory.root[variableName]
|
||||||
const isEditingExistingSketch =
|
const isEditingExistingSketch =
|
||||||
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
||||||
|
let sketchGroupId = ''
|
||||||
|
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
|
||||||
|
sketchGroupId = sketchGroup.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
guiMode.sketchMode === ('move' as any as 'line') &&
|
||||||
|
command.cmd.type === 'handle_mouse_drag_end'
|
||||||
|
) {
|
||||||
|
// Let's get the updated ast.
|
||||||
|
if (sketchGroupId === '') return
|
||||||
|
// We have a problem if we do not have an id for the sketch group.
|
||||||
|
if (
|
||||||
|
guiMode.pathId === undefined ||
|
||||||
|
guiMode.pathId === null ||
|
||||||
|
guiMode.pathId === ''
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
let engineId = guiMode.pathId
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedAst: Program = await modify_ast_for_sketch(
|
||||||
|
engineCommandManager,
|
||||||
|
JSON.stringify(ast),
|
||||||
|
variableName,
|
||||||
|
engineId
|
||||||
|
)
|
||||||
|
|
||||||
|
updateAst(updatedAst, false)
|
||||||
|
} catch (e: any) {
|
||||||
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
|
const kclError = new KCLError(
|
||||||
|
parsed.kind,
|
||||||
|
parsed.msg,
|
||||||
|
rangeTypeFix(parsed.sourceRanges)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(kclError)
|
||||||
|
throw kclError
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
||||||
|
|
||||||
|
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
resp?.data?.data?.entities_modified?.length &&
|
resp?.data?.data?.entities_modified?.length &&
|
||||||
guiMode.waitingFirstClick &&
|
guiMode.waitingFirstClick &&
|
||||||
!isEditingExistingSketch
|
!isEditingExistingSketch
|
||||||
) {
|
) {
|
||||||
const curve = await engineCommandManager?.sendSceneCommand({
|
const curve = await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -257,6 +302,16 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const _modifiedAst = _addStartSketch.modifiedAst
|
const _modifiedAst = _addStartSketch.modifiedAst
|
||||||
const _pathToNode = _addStartSketch.pathToNode
|
const _pathToNode = _addStartSketch.pathToNode
|
||||||
|
|
||||||
|
// We need to update the guiMode with the right pathId so that we can
|
||||||
|
// move lines later and send the right sketch id to the engine.
|
||||||
|
for (const [id, artifact] of Object.entries(
|
||||||
|
engineCommandManager.artifactMap
|
||||||
|
)) {
|
||||||
|
if (artifact.commandType === 'start_path') {
|
||||||
|
guiMode.pathId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
...guiMode,
|
...guiMode,
|
||||||
pathToNode: _pathToNode,
|
pathToNode: _pathToNode,
|
||||||
@ -267,7 +322,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
resp?.data?.data?.entities_modified?.length &&
|
resp?.data?.data?.entities_modified?.length &&
|
||||||
(!guiMode.waitingFirstClick || isEditingExistingSketch)
|
(!guiMode.waitingFirstClick || isEditingExistingSketch)
|
||||||
) {
|
) {
|
||||||
const curve = await engineCommandManager?.sendSceneCommand({
|
const curve = await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -312,12 +367,15 @@ export const Stream = ({ className = '' }) => {
|
|||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'default',
|
mode: 'default',
|
||||||
})
|
})
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: { type: 'edit_mode_exit' },
|
||||||
type: 'sketch_mode_disable',
|
})
|
||||||
},
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
})
|
})
|
||||||
updateAst(_modifiedAst, true)
|
updateAst(_modifiedAst, true)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import { isOverlap, roundOff } from 'lib/utils'
|
|||||||
import { kclErrToDiagnostic } from 'lang/errors'
|
import { kclErrToDiagnostic } from 'lang/errors'
|
||||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||||
import interact from '@replit/codemirror-interact'
|
import interact from '@replit/codemirror-interact'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -52,7 +53,6 @@ export const TextEditor = ({
|
|||||||
code,
|
code,
|
||||||
deferredSetCode,
|
deferredSetCode,
|
||||||
editorView,
|
editorView,
|
||||||
engineCommandManager,
|
|
||||||
formatCode,
|
formatCode,
|
||||||
isLSPServerReady,
|
isLSPServerReady,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -64,7 +64,6 @@ export const TextEditor = ({
|
|||||||
code: s.code,
|
code: s.code,
|
||||||
deferredSetCode: s.deferredSetCode,
|
deferredSetCode: s.deferredSetCode,
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
formatCode: s.formatCode,
|
formatCode: s.formatCode,
|
||||||
isLSPServerReady: s.isLSPServerReady,
|
isLSPServerReady: s.isLSPServerReady,
|
||||||
selectionRanges: s.selectionRanges,
|
selectionRanges: s.selectionRanges,
|
||||||
@ -173,7 +172,7 @@ export const TextEditor = ({
|
|||||||
const idBasedSelections = codeBasedSelections
|
const idBasedSelections = codeBasedSelections
|
||||||
.map(({ type, range }) => {
|
.map(({ type, range }) => {
|
||||||
const hasOverlap = Object.entries(
|
const hasOverlap = Object.entries(
|
||||||
engineCommandManager?.sourceRangeMap || {}
|
engineCommandManager.sourceRangeMap || {}
|
||||||
).filter(([_, sourceRange]) => {
|
).filter(([_, sourceRange]) => {
|
||||||
return isOverlap(sourceRange, range)
|
return isOverlap(sourceRange, range)
|
||||||
})
|
})
|
||||||
@ -186,7 +185,7 @@ export const TextEditor = ({
|
|||||||
})
|
})
|
||||||
.filter(Boolean) as any
|
.filter(Boolean) as any
|
||||||
|
|
||||||
engineCommandManager?.cusorsSelected({
|
engineCommandManager.cusorsSelected({
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
idBasedSelections,
|
idBasedSelections,
|
||||||
})
|
})
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
export const EqualAngle = () => {
|
export const EqualAngle = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
@ -87,9 +89,17 @@ export const EqualAngle = () => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableEqual}
|
disabled={!enableEqual}
|
||||||
title="yo dawg"
|
title="Parallel (or equal angle)"
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
parallel
|
<ActionIcon
|
||||||
|
icon="parallel"
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
Parallel
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
export const EqualLength = () => {
|
export const EqualLength = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
@ -87,9 +89,17 @@ export const EqualLength = () => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableEqual}
|
disabled={!enableEqual}
|
||||||
title="yo dawg"
|
className="group"
|
||||||
|
title="Equal Length"
|
||||||
>
|
>
|
||||||
EqualLength
|
<ActionIcon
|
||||||
|
icon="equal"
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
Equal Length
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
export const HorzVert = ({
|
export const HorzVert = ({
|
||||||
horOrVert,
|
horOrVert,
|
||||||
@ -66,9 +68,17 @@ export const HorzVert = ({
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableHorz}
|
disabled={!enableHorz}
|
||||||
title="yo dawg"
|
className="group"
|
||||||
|
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||||
>
|
>
|
||||||
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'}
|
<ActionIcon
|
||||||
|
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -188,8 +188,9 @@ export const Intersect = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enable}
|
disabled={!enable}
|
||||||
|
title="Set Perpendicular Distance"
|
||||||
>
|
>
|
||||||
perpendicularDistance
|
Set Perpendicular Distance
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -70,9 +70,9 @@ export const RemoveConstrainingValues = () => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableHorz}
|
disabled={!enableHorz}
|
||||||
title="yo dawg"
|
title="Remove Constraining Values"
|
||||||
>
|
>
|
||||||
RemoveConstrainingValues
|
Remove Constraining Values
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,16 @@ import { updateCursors } from '../../lang/util'
|
|||||||
|
|
||||||
const getModalInfo = create(SetAngleLengthModal as any)
|
const getModalInfo = create(SetAngleLengthModal as any)
|
||||||
|
|
||||||
export const SetAbsDistance = ({
|
type ButtonType = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
|
||||||
buttonType,
|
|
||||||
}: {
|
const buttonLabels: Record<ButtonType, string> = {
|
||||||
buttonType: 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
|
xAbs: 'Set distance from X Axis',
|
||||||
}) => {
|
yAbs: 'Set distance from Y Axis',
|
||||||
|
snapToYAxis: 'Snap To Y Axis',
|
||||||
|
snapToXAxis: 'Snap To X Axis',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
useStore((s) => ({
|
useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
@ -128,12 +133,13 @@ export const SetAbsDistance = ({
|
|||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enableAngLen}
|
disabled={!enableAngLen}
|
||||||
|
title={buttonLabels[buttonType]}
|
||||||
>
|
>
|
||||||
{buttonType}
|
{buttonLabels[buttonType]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -147,8 +147,9 @@ export const SetAngleBetween = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enable}
|
disabled={!enable}
|
||||||
|
title="Set Angle Between"
|
||||||
>
|
>
|
||||||
angleBetween
|
Set Angle Between
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -21,17 +21,28 @@ import { GetInfoModal } from '../SetHorVertDistanceModal'
|
|||||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
const getModalInfo = create(GetInfoModal as any)
|
const getModalInfo = create(GetInfoModal as any)
|
||||||
|
|
||||||
|
type ButtonType =
|
||||||
|
| 'setHorzDistance'
|
||||||
|
| 'setVertDistance'
|
||||||
|
| 'alignEndsHorizontally'
|
||||||
|
| 'alignEndsVertically'
|
||||||
|
|
||||||
|
const buttonLabels: Record<ButtonType, string> = {
|
||||||
|
setHorzDistance: 'Set Horizontal Distance',
|
||||||
|
setVertDistance: 'Set Vertical Distance',
|
||||||
|
alignEndsHorizontally: 'Align Ends Horizontally',
|
||||||
|
alignEndsVertically: 'Align Ends Vertically',
|
||||||
|
}
|
||||||
|
|
||||||
export const SetHorzVertDistance = ({
|
export const SetHorzVertDistance = ({
|
||||||
buttonType,
|
buttonType,
|
||||||
}: {
|
}: {
|
||||||
buttonType:
|
buttonType: ButtonType
|
||||||
| 'setHorzDistance'
|
|
||||||
| 'setVertDistance'
|
|
||||||
| 'alignEndsHorizontally'
|
|
||||||
| 'alignEndsVertically'
|
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
useStore((s) => ({
|
useStore((s) => ({
|
||||||
@ -169,8 +180,9 @@ export const SetHorzVertDistance = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enable}
|
disabled={!enable}
|
||||||
|
title={buttonLabels[buttonType]}
|
||||||
>
|
>
|
||||||
{buttonType}
|
{buttonLabels[buttonType]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,17 @@ import { updateCursors } from '../../lang/util'
|
|||||||
|
|
||||||
const getModalInfo = create(SetAngleLengthModal as any)
|
const getModalInfo = create(SetAngleLengthModal as any)
|
||||||
|
|
||||||
|
type ButtonType = 'setAngle' | 'setLength'
|
||||||
|
|
||||||
|
const buttonLabels: Record<ButtonType, string> = {
|
||||||
|
setAngle: 'Set Angle',
|
||||||
|
setLength: 'Set Length',
|
||||||
|
}
|
||||||
|
|
||||||
export const SetAngleLength = ({
|
export const SetAngleLength = ({
|
||||||
angleOrLength,
|
angleOrLength,
|
||||||
}: {
|
}: {
|
||||||
angleOrLength: 'setAngle' | 'setLength'
|
angleOrLength: ButtonType
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
useStore((s) => ({
|
useStore((s) => ({
|
||||||
@ -140,12 +147,13 @@ export const SetAngleLength = ({
|
|||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e', e)
|
console.log('erorr', e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enableAngLen}
|
disabled={!enableAngLen}
|
||||||
|
title={buttonLabels[angleOrLength]}
|
||||||
>
|
>
|
||||||
{angleOrLength}
|
{buttonLabels[angleOrLength]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Popover, Transition } from '@headlessui/react'
|
|||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { paths } from '../Router'
|
import { paths } from '../Router'
|
||||||
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
|
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
|
||||||
@ -12,6 +12,7 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
|||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||||
|
const location = useLocation()
|
||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -38,7 +39,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
{user?.image && !imageLoadFailed ? (
|
{user?.image && !imageLoadFailed ? (
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className="border-0 rounded-full w-fit p-0 focus:outline-none group"
|
className="border-0 rounded-full w-fit min-w-max p-0 focus:outline-none group"
|
||||||
data-testid="user-sidebar-toggle"
|
data-testid="user-sidebar-toggle"
|
||||||
>
|
>
|
||||||
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
|
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
|
||||||
@ -126,7 +127,11 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
// since /settings is a nested route the sidebar doesn't close
|
// since /settings is a nested route the sidebar doesn't close
|
||||||
// automatically when navigating to it
|
// automatically when navigating to it
|
||||||
close()
|
close()
|
||||||
navigate(makeUrlPathRelative(paths.SETTINGS))
|
navigate(
|
||||||
|
(location.pathname.endsWith('/')
|
||||||
|
? location.pathname.slice(0, -1)
|
||||||
|
: location.pathname) + paths.SETTINGS
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
|
@ -109,7 +109,6 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
messageString += message
|
messageString += message
|
||||||
// console.log(messageString)
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
CompletionItemKind,
|
CompletionItemKind,
|
||||||
CompletionTriggerKind,
|
CompletionTriggerKind,
|
||||||
} from 'vscode-languageserver-protocol'
|
} from 'vscode-languageserver-protocol'
|
||||||
|
import debounce from 'debounce-promise'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Completion,
|
Completion,
|
||||||
@ -53,14 +54,11 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
private languageId: string
|
private languageId: string
|
||||||
private documentVersion: number
|
private documentVersion: number
|
||||||
|
|
||||||
private changesTimeout: number
|
|
||||||
|
|
||||||
constructor(private view: EditorView, private allowHTMLContent: boolean) {
|
constructor(private view: EditorView, private allowHTMLContent: boolean) {
|
||||||
this.client = this.view.state.facet(client)
|
this.client = this.view.state.facet(client)
|
||||||
this.documentUri = this.view.state.facet(documentUri)
|
this.documentUri = this.view.state.facet(documentUri)
|
||||||
this.languageId = this.view.state.facet(languageId)
|
this.languageId = this.view.state.facet(languageId)
|
||||||
this.documentVersion = 0
|
this.documentVersion = 0
|
||||||
this.changesTimeout = 0
|
|
||||||
|
|
||||||
this.client.attachPlugin(this)
|
this.client.attachPlugin(this)
|
||||||
|
|
||||||
@ -71,12 +69,10 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
update({ docChanged }: ViewUpdate) {
|
update({ docChanged }: ViewUpdate) {
|
||||||
if (!docChanged) return
|
if (!docChanged) return
|
||||||
if (this.changesTimeout) clearTimeout(this.changesTimeout)
|
|
||||||
this.changesTimeout = window.setTimeout(() => {
|
this.sendChange({
|
||||||
this.sendChange({
|
documentText: this.view.state.doc.toString(),
|
||||||
documentText: this.view.state.doc.toString(),
|
})
|
||||||
})
|
|
||||||
}, changesDelay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -99,14 +95,32 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
async sendChange({ documentText }: { documentText: string }) {
|
async sendChange({ documentText }: { documentText: string }) {
|
||||||
if (!this.client.ready) return
|
if (!this.client.ready) return
|
||||||
|
|
||||||
|
if (documentText.length > 5000) {
|
||||||
|
// Clear out the text it thinks we have, large documents will throw a stack error.
|
||||||
|
// This is obviously not a good fix but it works for now til we figure
|
||||||
|
// out the stack limits in wasm and also rewrite the parser.
|
||||||
|
// Since this is only for hover and completions it will be fine,
|
||||||
|
// completions will still work for stdlib but hover will not.
|
||||||
|
// That seems like a fine trade-off for a working editor for the time
|
||||||
|
// being.
|
||||||
|
documentText = ''
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.textDocumentDidChange({
|
debounce(
|
||||||
textDocument: {
|
() => {
|
||||||
uri: this.documentUri,
|
return this.client.textDocumentDidChange({
|
||||||
version: this.documentVersion++,
|
textDocument: {
|
||||||
|
uri: this.documentUri,
|
||||||
|
version: this.documentVersion++,
|
||||||
|
},
|
||||||
|
contentChanges: [{ text: documentText }],
|
||||||
|
})
|
||||||
},
|
},
|
||||||
contentChanges: [{ text: documentText }],
|
changesDelay,
|
||||||
})
|
{ leading: true }
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import init, {
|
|||||||
} from '../../wasm-lib/pkg/wasm_lib'
|
} from '../../wasm-lib/pkg/wasm_lib'
|
||||||
import { FromServer, IntoServer } from './codec'
|
import { FromServer, IntoServer } from './codec'
|
||||||
|
|
||||||
let server: null | Server
|
|
||||||
|
|
||||||
export default class Server {
|
export default class Server {
|
||||||
readonly initOutput: InitOutput
|
readonly initOutput: InitOutput
|
||||||
readonly #intoServer: IntoServer
|
readonly #intoServer: IntoServer
|
||||||
@ -26,12 +24,8 @@ export default class Server {
|
|||||||
intoServer: IntoServer,
|
intoServer: IntoServer,
|
||||||
fromServer: FromServer
|
fromServer: FromServer
|
||||||
): Promise<Server> {
|
): Promise<Server> {
|
||||||
if (null == server) {
|
const initOutput = await init()
|
||||||
const initOutput = await init()
|
const server = new Server(initOutput, intoServer, fromServer)
|
||||||
server = new Server(initOutput, intoServer, fromServer)
|
|
||||||
} else {
|
|
||||||
console.warn('Server already initialized; ignoring')
|
|
||||||
}
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
|
|||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import { isReducedMotion } from 'lang/util'
|
import { isReducedMotion } from 'lang/util'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
interface DefaultPlanes {
|
interface DefaultPlanes {
|
||||||
xy: string
|
xy: string
|
||||||
@ -17,19 +18,13 @@ interface DefaultPlanes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useAppMode() {
|
export function useAppMode() {
|
||||||
const {
|
const { guiMode, setGuiMode, selectionRanges, selectionRangeTypeMap } =
|
||||||
guiMode,
|
useStore((s) => ({
|
||||||
setGuiMode,
|
guiMode: s.guiMode,
|
||||||
selectionRanges,
|
setGuiMode: s.setGuiMode,
|
||||||
engineCommandManager,
|
selectionRanges: s.selectionRanges,
|
||||||
selectionRangeTypeMap,
|
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
||||||
} = useStore((s) => ({
|
}))
|
||||||
guiMode: s.guiMode,
|
|
||||||
setGuiMode: s.setGuiMode,
|
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
|
||||||
}))
|
|
||||||
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
|
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -37,27 +32,62 @@ export function useAppMode() {
|
|||||||
guiMode.sketchMode === 'selectFace' &&
|
guiMode.sketchMode === 'selectFace' &&
|
||||||
engineCommandManager
|
engineCommandManager
|
||||||
) {
|
) {
|
||||||
if (!defaultPlanes) {
|
const createAndShowPlanes = async () => {
|
||||||
const xy = createPlane(engineCommandManager, {
|
let localDefaultPlanes: DefaultPlanes
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
if (!defaultPlanes) {
|
||||||
y_axis: { x: 0, y: 1, z: 0 },
|
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
setDefaultPlanes(localDefaultPlanes)
|
||||||
})
|
} else {
|
||||||
// TODO re-enable
|
localDefaultPlanes = defaultPlanes
|
||||||
// const yz = createPlane(engineCommandManager, {
|
}
|
||||||
// x_axis: { x: 0, y: 1, z: 0 },
|
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
|
||||||
// y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
|
||||||
// })
|
|
||||||
// const xz = createPlane(engineCommandManager, {
|
|
||||||
// x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
// y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
|
||||||
// })
|
|
||||||
setDefaultPlanes({ xy })
|
|
||||||
} else {
|
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false)
|
|
||||||
}
|
}
|
||||||
|
createAndShowPlanes()
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === 'enterSketchEdit' &&
|
||||||
|
engineCommandManager
|
||||||
|
) {
|
||||||
|
const enableSketchMode = async () => {
|
||||||
|
let localDefaultPlanes: DefaultPlanes
|
||||||
|
if (!defaultPlanes) {
|
||||||
|
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||||
|
setDefaultPlanes(localDefaultPlanes)
|
||||||
|
} else {
|
||||||
|
localDefaultPlanes = defaultPlanes
|
||||||
|
}
|
||||||
|
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
|
||||||
|
// TODO figure out the plane to use based on the sketch
|
||||||
|
// maybe it's easier to make a new plane than rely on the defaults
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'sketch_mode_enable',
|
||||||
|
plane_id: localDefaultPlanes.xy,
|
||||||
|
ortho: true,
|
||||||
|
animated: !isReducedMotion(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const proms: any[] = []
|
||||||
|
proms.push(
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: guiMode.pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await Promise.all(proms)
|
||||||
|
}
|
||||||
|
enableSketchMode()
|
||||||
|
setGuiMode({
|
||||||
|
...guiMode,
|
||||||
|
sketchMode: 'sketchEdit',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||||
@ -100,7 +130,7 @@ export function useAppMode() {
|
|||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unSub = engineCommandManager?.subscribeTo({
|
const unSub = engineCommandManager.subscribeTo({
|
||||||
event: 'select_with_point',
|
event: 'select_with_point',
|
||||||
callback: async ({ data }) => {
|
callback: async ({ data }) => {
|
||||||
if (!data.entity_id) return
|
if (!data.entity_id) return
|
||||||
@ -109,18 +139,16 @@ export function useAppMode() {
|
|||||||
// user clicked something else in the scene
|
// user clicked something else in the scene
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
|
const sketchModeResponse = await engineCommandManager.sendSceneCommand({
|
||||||
{
|
type: 'modeling_cmd_req',
|
||||||
type: 'modeling_cmd_req',
|
cmd_id: uuidv4(),
|
||||||
cmd_id: uuidv4(),
|
cmd: {
|
||||||
cmd: {
|
type: 'sketch_mode_enable',
|
||||||
type: 'sketch_mode_enable',
|
plane_id: data.entity_id,
|
||||||
plane_id: data.entity_id,
|
ortho: true,
|
||||||
ortho: true,
|
animated: !isReducedMotion(),
|
||||||
animated: !isReducedMotion(),
|
},
|
||||||
},
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||||
const sketchUuid = uuidv4()
|
const sketchUuid = uuidv4()
|
||||||
const proms: any[] = []
|
const proms: any[] = []
|
||||||
@ -143,14 +171,14 @@ export function useAppMode() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const res = await Promise.all(proms)
|
await Promise.all(proms)
|
||||||
console.log('res', res)
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
pathToNode: [],
|
pathToNode: [],
|
||||||
|
pathId: sketchUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('sketchModeResponse', sketchModeResponse)
|
console.log('sketchModeResponse', sketchModeResponse)
|
||||||
@ -160,7 +188,7 @@ export function useAppMode() {
|
|||||||
}, [engineCommandManager, defaultPlanes])
|
}, [engineCommandManager, defaultPlanes])
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPlane(
|
async function createPlane(
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
{
|
{
|
||||||
x_axis,
|
x_axis,
|
||||||
@ -173,7 +201,7 @@ function createPlane(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const planeId = uuidv4()
|
const planeId = uuidv4()
|
||||||
engineCommandManager?.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'make_plane',
|
type: 'make_plane',
|
||||||
@ -185,7 +213,7 @@ function createPlane(
|
|||||||
},
|
},
|
||||||
cmd_id: planeId,
|
cmd_id: planeId,
|
||||||
})
|
})
|
||||||
engineCommandManager?.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'plane_set_color',
|
type: 'plane_set_color',
|
||||||
@ -198,12 +226,12 @@ function createPlane(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setDefaultPlanesHidden(
|
function setDefaultPlanesHidden(
|
||||||
engineCommandManager: EngineCommandManager | undefined,
|
engineCommandManager: EngineCommandManager,
|
||||||
defaultPlanes: DefaultPlanes,
|
defaultPlanes: DefaultPlanes,
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
) {
|
) {
|
||||||
Object.values(defaultPlanes).forEach((planeId) => {
|
Object.values(defaultPlanes).forEach((planeId) => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -215,6 +243,28 @@ function setDefaultPlanesHidden(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function initDefaultPlanes(
|
||||||
|
engineCommandManager: EngineCommandManager
|
||||||
|
): Promise<DefaultPlanes> {
|
||||||
|
const xy = await createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||||
|
})
|
||||||
|
// TODO re-enable
|
||||||
|
// const yz = createPlane(engineCommandManager, {
|
||||||
|
// x_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
// y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||||
|
// })
|
||||||
|
// const xz = createPlane(engineCommandManager, {
|
||||||
|
// x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
// y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||||
|
// })
|
||||||
|
return { xy }
|
||||||
|
}
|
||||||
|
|
||||||
function isCursorInSketchCommandRange(
|
function isCursorInSketchCommandRange(
|
||||||
artifactMap: ArtifactMap,
|
artifactMap: ArtifactMap,
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
|
13
src/hooks/useDotDotSlash.ts
Normal file
13
src/hooks/useDotDotSlash.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
export function useDotDotSlash(): (count?: number) => string {
|
||||||
|
const location = useLocation()
|
||||||
|
const dotDotSlash = (count = 1): string => {
|
||||||
|
// since we can't use relative paths (../) for windows
|
||||||
|
if (location.pathname === '/') return ''
|
||||||
|
const path = location.pathname.slice(0, location.pathname.lastIndexOf('/'))
|
||||||
|
if (count <= 1) return path
|
||||||
|
return dotDotSlash(count - 1)
|
||||||
|
}
|
||||||
|
return dotDotSlash
|
||||||
|
}
|
@ -1,14 +1,9 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export function useEngineConnectionSubscriptions() {
|
export function useEngineConnectionSubscriptions() {
|
||||||
const {
|
const { setCursor2, setHighlightRange, highlightRange } = useStore((s) => ({
|
||||||
engineCommandManager,
|
|
||||||
setCursor2,
|
|
||||||
setHighlightRange,
|
|
||||||
highlightRange,
|
|
||||||
} = useStore((s) => ({
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
setCursor2: s.setCursor2,
|
setCursor2: s.setCursor2,
|
||||||
setHighlightRange: s.setHighlightRange,
|
setHighlightRange: s.setHighlightRange,
|
||||||
highlightRange: s.highlightRange,
|
highlightRange: s.highlightRange,
|
||||||
|
@ -1,53 +1,90 @@
|
|||||||
import { useLayoutEffect } from 'react'
|
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||||
import { _executor } from '../lang/executor'
|
import { _executor } from '../lang/executor'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { EngineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { deferExecution } from 'lib/utils'
|
||||||
|
|
||||||
export function useSetupEngineManager(
|
export function useSetupEngineManager(
|
||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
token?: string
|
token?: string
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
setEngineCommandManager,
|
|
||||||
setMediaStream,
|
setMediaStream,
|
||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
setStreamDimensions,
|
setStreamDimensions,
|
||||||
executeCode,
|
executeCode,
|
||||||
|
streamDimensions,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
setEngineCommandManager: s.setEngineCommandManager,
|
|
||||||
setMediaStream: s.setMediaStream,
|
setMediaStream: s.setMediaStream,
|
||||||
setIsStreamReady: s.setIsStreamReady,
|
setIsStreamReady: s.setIsStreamReady,
|
||||||
setStreamDimensions: s.setStreamDimensions,
|
setStreamDimensions: s.setStreamDimensions,
|
||||||
executeCode: s.executeCode,
|
executeCode: s.executeCode,
|
||||||
|
streamDimensions: s.streamDimensions,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const streamWidth = streamRef?.current?.offsetWidth
|
const streamWidth = streamRef?.current?.offsetWidth
|
||||||
const streamHeight = streamRef?.current?.offsetHeight
|
const streamHeight = streamRef?.current?.offsetHeight
|
||||||
|
|
||||||
|
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
// Load the engine command manager once with the initial width and height,
|
||||||
|
// then we do not want to reload it.
|
||||||
|
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||||
|
streamWidth,
|
||||||
|
streamHeight
|
||||||
|
)
|
||||||
|
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
||||||
|
engineCommandManager.start({
|
||||||
|
setMediaStream,
|
||||||
|
setIsStreamReady,
|
||||||
|
width: quadWidth,
|
||||||
|
height: quadHeight,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
engineCommandManager.waitForReady.then(() => {
|
||||||
|
executeCode()
|
||||||
|
})
|
||||||
|
setStreamDimensions({
|
||||||
|
streamWidth: quadWidth,
|
||||||
|
streamHeight: quadHeight,
|
||||||
|
})
|
||||||
|
hasSetNonZeroDimensions.current = true
|
||||||
|
}
|
||||||
|
}, [streamRef?.current?.offsetWidth, streamRef?.current?.offsetHeight])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = deferExecution(() => {
|
||||||
|
const { width, height } = getDimensions(
|
||||||
|
streamRef?.current?.offsetWidth,
|
||||||
|
streamRef?.current?.offsetHeight
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
streamDimensions.streamWidth !== width ||
|
||||||
|
streamDimensions.streamHeight !== height
|
||||||
|
) {
|
||||||
|
engineCommandManager.handleResize({
|
||||||
|
streamWidth: width,
|
||||||
|
streamHeight: height,
|
||||||
|
})
|
||||||
|
setStreamDimensions({
|
||||||
|
streamWidth: width,
|
||||||
|
streamHeight: height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
||||||
const width = streamWidth ? streamWidth : 0
|
const width = streamWidth ? streamWidth : 0
|
||||||
const quadWidth = Math.round(width / 4) * 4
|
const quadWidth = Math.round(width / 4) * 4
|
||||||
const height = streamHeight ? streamHeight : 0
|
const height = streamHeight ? streamHeight : 0
|
||||||
const quadHeight = Math.round(height / 4) * 4
|
const quadHeight = Math.round(height / 4) * 4
|
||||||
|
return { width: quadWidth, height: quadHeight }
|
||||||
useLayoutEffect(() => {
|
|
||||||
setStreamDimensions({
|
|
||||||
streamWidth: quadWidth,
|
|
||||||
streamHeight: quadHeight,
|
|
||||||
})
|
|
||||||
if (!width || !height) return
|
|
||||||
const eng = new EngineCommandManager({
|
|
||||||
setMediaStream,
|
|
||||||
setIsStreamReady,
|
|
||||||
width: quadWidth,
|
|
||||||
height: quadHeight,
|
|
||||||
token,
|
|
||||||
})
|
|
||||||
setEngineCommandManager(eng)
|
|
||||||
eng.waitForReady.then(() => {
|
|
||||||
executeCode()
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
eng?.tearDown()
|
|
||||||
}
|
|
||||||
}, [quadWidth, quadHeight])
|
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export function useConvertToVariable() {
|
|||||||
|
|
||||||
updateAst(_modifiedAst, true)
|
updateAst(_modifiedAst, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,14 @@ body.dark {
|
|||||||
@apply text-chalkboard-10;
|
@apply text-chalkboard-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
@apply bg-chalkboard-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark select {
|
||||||
|
@apply bg-chalkboard-90;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@apply w-2 h-2 rounded-sm;
|
@apply w-2 h-2 rounded-sm;
|
||||||
@apply bg-chalkboard-20;
|
@apply bg-chalkboard-20;
|
||||||
|
@ -139,54 +139,6 @@ const newVar = myVar + 1
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('using std function "log"', () => {
|
|
||||||
const code = `log(5, "hello", aIdentifier)`
|
|
||||||
const { body } = parser_wasm(code)
|
|
||||||
expect(body).toEqual([
|
|
||||||
{
|
|
||||||
type: 'ExpressionStatement',
|
|
||||||
start: 0,
|
|
||||||
end: 28,
|
|
||||||
expression: {
|
|
||||||
type: 'CallExpression',
|
|
||||||
start: 0,
|
|
||||||
end: 28,
|
|
||||||
callee: {
|
|
||||||
type: 'Identifier',
|
|
||||||
start: 0,
|
|
||||||
end: 3,
|
|
||||||
name: 'log',
|
|
||||||
},
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
type: 'Literal',
|
|
||||||
start: 4,
|
|
||||||
end: 5,
|
|
||||||
value: 5,
|
|
||||||
raw: '5',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Literal',
|
|
||||||
start: 7,
|
|
||||||
end: 14,
|
|
||||||
value: 'hello',
|
|
||||||
raw: '"hello"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Identifier',
|
|
||||||
start: 16,
|
|
||||||
end: 27,
|
|
||||||
name: 'aIdentifier',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
function: {
|
|
||||||
type: 'InMemory',
|
|
||||||
},
|
|
||||||
optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing function declaration', () => {
|
describe('testing function declaration', () => {
|
||||||
@ -1560,7 +1512,7 @@ const yo = { a: { b: { c: '123' } } }
|
|||||||
// this is a comment
|
// this is a comment
|
||||||
const key = 'c'`
|
const key = 'c'`
|
||||||
const nonCodeMetaInstance = {
|
const nonCodeMetaInstance = {
|
||||||
type: 'NoneCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: code.indexOf('\n// this is a comment'),
|
start: code.indexOf('\n// this is a comment'),
|
||||||
end: code.indexOf('const key'),
|
end: code.indexOf('const key'),
|
||||||
value: {
|
value: {
|
||||||
@ -1569,17 +1521,17 @@ const key = 'c'`
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const { nonCodeMeta } = parser_wasm(code)
|
const { nonCodeMeta } = parser_wasm(code)
|
||||||
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance)
|
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
|
||||||
|
|
||||||
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
||||||
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
||||||
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
|
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
|
||||||
codeWithExtraStartWhitespace
|
codeWithExtraStartWhitespace
|
||||||
)
|
)
|
||||||
expect(nonCodeMeta2.noneCodeNodes[0].value).toStrictEqual(
|
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
|
||||||
nonCodeMetaInstance.value
|
nonCodeMetaInstance.value
|
||||||
)
|
)
|
||||||
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe(
|
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
|
||||||
nonCodeMetaInstance.start
|
nonCodeMetaInstance.start
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1596,9 +1548,9 @@ const key = 'c'`
|
|||||||
const { body } = parser_wasm(code)
|
const { body } = parser_wasm(code)
|
||||||
const indexOfSecondLineToExpression = 2
|
const indexOfSecondLineToExpression = 2
|
||||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||||
.noneCodeNodes
|
.nonCodeNodes
|
||||||
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
||||||
type: 'NoneCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: 106,
|
start: 106,
|
||||||
end: 166,
|
end: 166,
|
||||||
value: {
|
value: {
|
||||||
@ -1619,9 +1571,9 @@ const key = 'c'`
|
|||||||
|
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parser_wasm(code)
|
||||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||||
.noneCodeNodes
|
.nonCodeNodes
|
||||||
expect(sketchNonCodeMeta[3]).toEqual({
|
expect(sketchNonCodeMeta[3]).toEqual({
|
||||||
type: 'NoneCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: 125,
|
start: 125,
|
||||||
end: 141,
|
end: 141,
|
||||||
value: {
|
value: {
|
||||||
@ -1739,7 +1691,6 @@ describe('parsing errors', () => {
|
|||||||
let _theError
|
let _theError
|
||||||
try {
|
try {
|
||||||
const result = expect(parser_wasm(code))
|
const result = expect(parser_wasm(code))
|
||||||
console.log('result', result)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_theError = e
|
_theError = e
|
||||||
}
|
}
|
||||||
|
@ -33,5 +33,5 @@ export type SyntaxType =
|
|||||||
| 'PipeExpression'
|
| 'PipeExpression'
|
||||||
| 'PipeSubstitution'
|
| 'PipeSubstitution'
|
||||||
| 'Literal'
|
| 'Literal'
|
||||||
| 'NoneCodeNode'
|
| 'NonCodeNode'
|
||||||
| 'UnaryExpression'
|
| 'UnaryExpression'
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parser_wasm } from './abstractSyntaxTree'
|
||||||
import { initPromise } from './rust'
|
import { initPromise } from './rust'
|
||||||
|
import { Identifier } from './abstractSyntaxTreeTypes'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -27,4 +28,81 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
expect([node.start, node.end]).toEqual(sourceRange)
|
expect([node.start, node.end]).toEqual(sourceRange)
|
||||||
expect(node.type).toBe('CallExpression')
|
expect(node.type).toBe('CallExpression')
|
||||||
})
|
})
|
||||||
|
it('gets path right for function definition params', () => {
|
||||||
|
const code = `fn cube = (pos, scale) => {
|
||||||
|
const sg = startSketchAt(pos)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const b1 = cube([0,0], 10)`
|
||||||
|
const subStr = 'pos, scale'
|
||||||
|
const subStrIndex = code.indexOf(subStr)
|
||||||
|
const sourceRange: [number, number] = [
|
||||||
|
subStrIndex,
|
||||||
|
subStrIndex + 'pos'.length,
|
||||||
|
]
|
||||||
|
|
||||||
|
const ast = parser_wasm(code)
|
||||||
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
|
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
||||||
|
|
||||||
|
expect(nodePath).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['params', 'FunctionExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
])
|
||||||
|
expect(node.type).toBe('Identifier')
|
||||||
|
expect(node.name).toBe('pos')
|
||||||
|
})
|
||||||
|
it('gets path right for deep within function definition body', () => {
|
||||||
|
const code = `fn cube = (pos, scale) => {
|
||||||
|
const sg = startSketchAt(pos)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const b1 = cube([0,0], 10)`
|
||||||
|
const subStr = 'scale, 0'
|
||||||
|
const subStrIndex = code.indexOf(subStr)
|
||||||
|
const sourceRange: [number, number] = [
|
||||||
|
subStrIndex,
|
||||||
|
subStrIndex + 'scale'.length,
|
||||||
|
]
|
||||||
|
|
||||||
|
const ast = parser_wasm(code)
|
||||||
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
|
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
||||||
|
expect(nodePath).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['body', 'FunctionExpression'],
|
||||||
|
['body', 'FunctionExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['body', 'PipeExpression'],
|
||||||
|
[2, 'index'],
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['elements', 'ArrayExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
])
|
||||||
|
expect(node.type).toBe('Identifier')
|
||||||
|
expect(node.name).toBe('scale')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -106,7 +106,7 @@ describe('Testing addSketchTo', () => {
|
|||||||
body: [],
|
body: [],
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
nonCodeMeta: { noneCodeNodes: {}, start: null },
|
nonCodeMeta: { nonCodeNodes: {}, start: null },
|
||||||
},
|
},
|
||||||
'yz'
|
'yz'
|
||||||
)
|
)
|
||||||
|
@ -321,7 +321,7 @@ export function extrudeSketch(
|
|||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
modifiedAst: addToShow(_node, name),
|
modifiedAst: node,
|
||||||
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
|
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
|
||||||
pathToExtrudeArg,
|
pathToExtrudeArg,
|
||||||
}
|
}
|
||||||
@ -537,7 +537,7 @@ export function createPipeExpression(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
body,
|
body,
|
||||||
nonCodeMeta: { noneCodeNodes: {}, start: null },
|
nonCodeMeta: { nonCodeNodes: {}, start: null },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +239,29 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
console.error('not implemented')
|
if (_node.type === 'FunctionExpression' && isInRange) {
|
||||||
|
for (let i = 0; i < _node.params.length; i++) {
|
||||||
|
const param = _node.params[i]
|
||||||
|
if (param.start <= start && param.end >= end) {
|
||||||
|
path.push(['params', 'FunctionExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(param, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_node.body.start <= start && _node.body.end >= end) {
|
||||||
|
path.push(['body', 'FunctionExpression'])
|
||||||
|
const fnBody = _node.body.body
|
||||||
|
for (let i = 0; i < fnBody.length; i++) {
|
||||||
|
const statement = fnBody[i]
|
||||||
|
if (statement.start <= start && statement.end >= end) {
|
||||||
|
path.push(['body', 'FunctionExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error('not implemented: ' + node.type)
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ export const recast = (ast: Program): string => {
|
|||||||
return s
|
return s
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: do something real with the error.
|
// TODO: do something real with the error.
|
||||||
console.log('recast', e)
|
console.log('recast error', e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,17 @@ interface CommandInfo {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
parentId?: string
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
interface ResultCommand extends CommandInfo {
|
interface ResultCommand extends CommandInfo {
|
||||||
type: 'result'
|
type: 'result'
|
||||||
data: any
|
data: any
|
||||||
|
raw: WebSocketResponse
|
||||||
|
}
|
||||||
|
interface FailedCommand extends CommandInfo {
|
||||||
|
type: 'failed'
|
||||||
|
errors: Models['FailureWebSocketResponse_type']['errors']
|
||||||
}
|
}
|
||||||
interface PendingCommand extends CommandInfo {
|
interface PendingCommand extends CommandInfo {
|
||||||
type: 'pending'
|
type: 'pending'
|
||||||
@ -26,7 +34,7 @@ interface PendingCommand extends CommandInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ArtifactMap {
|
export interface ArtifactMap {
|
||||||
[key: string]: ResultCommand | PendingCommand
|
[key: string]: ResultCommand | PendingCommand | FailedCommand
|
||||||
}
|
}
|
||||||
export interface SourceRangeMap {
|
export interface SourceRangeMap {
|
||||||
[key: string]: SourceRange
|
[key: string]: SourceRange
|
||||||
@ -37,7 +45,10 @@ interface NewTrackArgs {
|
|||||||
mediaStream: MediaStream
|
mediaStream: MediaStream
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
// This looks funny, I know. This is needed because node and the browser
|
||||||
|
// disagree as to the type. In a browser it's a number, but in node it's a
|
||||||
|
// "Timeout".
|
||||||
|
type Timeout = ReturnType<typeof setTimeout>
|
||||||
|
|
||||||
type ClientMetrics = Models['ClientMetrics_type']
|
type ClientMetrics = Models['ClientMetrics_type']
|
||||||
|
|
||||||
@ -50,6 +61,9 @@ export class EngineConnection {
|
|||||||
unreliableDataChannel?: RTCDataChannel
|
unreliableDataChannel?: RTCDataChannel
|
||||||
|
|
||||||
private ready: boolean
|
private ready: boolean
|
||||||
|
private connecting: boolean
|
||||||
|
private dead: boolean
|
||||||
|
private failedConnTimeout: Timeout | null
|
||||||
|
|
||||||
readonly url: string
|
readonly url: string
|
||||||
private readonly token?: string
|
private readonly token?: string
|
||||||
@ -85,6 +99,9 @@ export class EngineConnection {
|
|||||||
this.url = url
|
this.url = url
|
||||||
this.token = token
|
this.token = token
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
this.connecting = false
|
||||||
|
this.dead = false
|
||||||
|
this.failedConnTimeout = null
|
||||||
this.onWebsocketOpen = onWebsocketOpen
|
this.onWebsocketOpen = onWebsocketOpen
|
||||||
this.onDataChannelOpen = onDataChannelOpen
|
this.onDataChannelOpen = onDataChannelOpen
|
||||||
this.onEngineConnectionOpen = onEngineConnectionOpen
|
this.onEngineConnectionOpen = onEngineConnectionOpen
|
||||||
@ -95,7 +112,10 @@ export class EngineConnection {
|
|||||||
// TODO(paultag): This ought to be tweakable.
|
// TODO(paultag): This ought to be tweakable.
|
||||||
const pingIntervalMs = 10000
|
const pingIntervalMs = 10000
|
||||||
|
|
||||||
setInterval(() => {
|
let pingInterval = setInterval(() => {
|
||||||
|
if (this.dead) {
|
||||||
|
clearInterval(pingInterval)
|
||||||
|
}
|
||||||
if (this.isReady()) {
|
if (this.isReady()) {
|
||||||
// When we're online, every 10 seconds, we'll attempt to put a 'ping'
|
// When we're online, every 10 seconds, we'll attempt to put a 'ping'
|
||||||
// command through the WebSocket connection. This will help both ends
|
// command through the WebSocket connection. This will help both ends
|
||||||
@ -104,6 +124,24 @@ export class EngineConnection {
|
|||||||
this.send({ type: 'ping' })
|
this.send({ type: 'ping' })
|
||||||
}
|
}
|
||||||
}, pingIntervalMs)
|
}, pingIntervalMs)
|
||||||
|
|
||||||
|
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
|
let connectInterval = setInterval(() => {
|
||||||
|
if (this.dead) {
|
||||||
|
clearInterval(connectInterval)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('connecting via retry')
|
||||||
|
this.connect()
|
||||||
|
}, connectionTimeoutMs)
|
||||||
|
}
|
||||||
|
// isConnecting will return true when connect has been called, but the full
|
||||||
|
// WebRTC is not online.
|
||||||
|
isConnecting() {
|
||||||
|
return this.connecting
|
||||||
}
|
}
|
||||||
// isReady will return true only when the WebRTC *and* WebSocket connection
|
// isReady will return true only when the WebRTC *and* WebSocket connection
|
||||||
// are connected. During setup, the WebSocket connection comes online first,
|
// are connected. During setup, the WebSocket connection comes online first,
|
||||||
@ -112,6 +150,10 @@ export class EngineConnection {
|
|||||||
isReady() {
|
isReady() {
|
||||||
return this.ready
|
return this.ready
|
||||||
}
|
}
|
||||||
|
tearDown() {
|
||||||
|
this.dead = true
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
// shouldTrace will return true when Sentry should be used to instrument
|
// shouldTrace will return true when Sentry should be used to instrument
|
||||||
// the Engine.
|
// the Engine.
|
||||||
shouldTrace() {
|
shouldTrace() {
|
||||||
@ -123,8 +165,10 @@ export class EngineConnection {
|
|||||||
// This will attempt the full handshake, and retry if the connection
|
// This will attempt the full handshake, and retry if the connection
|
||||||
// did not establish.
|
// did not establish.
|
||||||
connect() {
|
connect() {
|
||||||
// TODO(paultag): make this safe to call multiple times, and figure out
|
console.log('connect was called')
|
||||||
// when a connection is in progress (state: connecting or something).
|
if (this.isConnecting() || this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Information on the connect transaction
|
// Information on the connect transaction
|
||||||
|
|
||||||
@ -356,20 +400,6 @@ export class EngineConnection {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(paultag): This ought to be both controllable, as well as something
|
|
||||||
// like exponential backoff to have some grace on the backend, as well as
|
|
||||||
// fix responsiveness for clients that had a weird network hiccup.
|
|
||||||
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.isReady()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log('engine connection timeout on connection, retrying')
|
|
||||||
this.close()
|
|
||||||
this.connect()
|
|
||||||
}, connectionTimeoutMs)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pc.addEventListener('track', (event) => {
|
this.pc.addEventListener('track', (event) => {
|
||||||
@ -459,6 +489,7 @@ export class EngineConnection {
|
|||||||
|
|
||||||
this.onEngineConnectionOpen(this)
|
this.onEngineConnectionOpen(this)
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
this.connecting = false
|
||||||
})
|
})
|
||||||
|
|
||||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||||
@ -472,8 +503,31 @@ export class EngineConnection {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
|
|
||||||
|
if (this.failedConnTimeout) {
|
||||||
|
console.log('clearing timeout before set')
|
||||||
|
clearTimeout(this.failedConnTimeout)
|
||||||
|
this.failedConnTimeout = null
|
||||||
|
}
|
||||||
|
console.log('timeout set')
|
||||||
|
this.failedConnTimeout = setTimeout(() => {
|
||||||
|
if (this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('engine connection timeout on connection, closing')
|
||||||
|
this.close()
|
||||||
|
}, connectionTimeoutMs)
|
||||||
|
|
||||||
this.onConnectionStarted(this)
|
this.onConnectionStarted(this)
|
||||||
}
|
}
|
||||||
|
unreliableSend(message: object | string) {
|
||||||
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
|
// take actions if needed?
|
||||||
|
this.unreliableDataChannel?.send(
|
||||||
|
typeof message === 'string' ? message : JSON.stringify(message)
|
||||||
|
)
|
||||||
|
}
|
||||||
send(message: object | string) {
|
send(message: object | string) {
|
||||||
// TODO(paultag): Add in logic to determine the connection state and
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
// take actions if needed?
|
// take actions if needed?
|
||||||
@ -489,9 +543,15 @@ export class EngineConnection {
|
|||||||
this.pc = undefined
|
this.pc = undefined
|
||||||
this.unreliableDataChannel = undefined
|
this.unreliableDataChannel = undefined
|
||||||
this.webrtcStatsCollector = undefined
|
this.webrtcStatsCollector = undefined
|
||||||
|
if (this.failedConnTimeout) {
|
||||||
|
console.log('closed timeout in close')
|
||||||
|
clearTimeout(this.failedConnTimeout)
|
||||||
|
this.failedConnTimeout = null
|
||||||
|
}
|
||||||
|
|
||||||
this.onClose(this)
|
this.onClose(this)
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
this.connecting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +595,12 @@ export class EngineCommandManager {
|
|||||||
[localUnsubscribeId: string]: (a: any) => void
|
[localUnsubscribeId: string]: (a: any) => void
|
||||||
}
|
}
|
||||||
} = {} as any
|
} = {} as any
|
||||||
constructor({
|
|
||||||
|
constructor() {
|
||||||
|
this.engineConnection = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
start({
|
||||||
setMediaStream,
|
setMediaStream,
|
||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
width,
|
width,
|
||||||
@ -548,6 +613,16 @@ export class EngineCommandManager {
|
|||||||
height: number
|
height: number
|
||||||
token?: string
|
token?: string
|
||||||
}) {
|
}) {
|
||||||
|
if (width === 0 || height === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have an engine connection, just need to resize the stream.
|
||||||
|
if (this.engineConnection) {
|
||||||
|
this.handleResize({ streamWidth: width, streamHeight: height })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.waitForReady = new Promise((resolve) => {
|
this.waitForReady = new Promise((resolve) => {
|
||||||
this.resolveReady = resolve
|
this.resolveReady = resolve
|
||||||
})
|
})
|
||||||
@ -608,6 +683,8 @@ export class EngineCommandManager {
|
|||||||
message.request_id
|
message.request_id
|
||||||
) {
|
) {
|
||||||
this.handleModelingCommand(message.resp, message.request_id)
|
this.handleModelingCommand(message.resp, message.request_id)
|
||||||
|
} else if (!message.success && message.request_id) {
|
||||||
|
this.handleFailedModelingCommand(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -627,7 +704,35 @@ export class EngineCommandManager {
|
|||||||
|
|
||||||
this.engineConnection?.connect()
|
this.engineConnection?.connect()
|
||||||
}
|
}
|
||||||
|
handleResize({
|
||||||
|
streamWidth,
|
||||||
|
streamHeight,
|
||||||
|
}: {
|
||||||
|
streamWidth: number
|
||||||
|
streamHeight: number
|
||||||
|
}) {
|
||||||
|
console.log('handleResize', streamWidth, streamHeight)
|
||||||
|
if (!this.engineConnection?.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeCmd: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'reconfigure_stream',
|
||||||
|
width: streamWidth,
|
||||||
|
height: streamHeight,
|
||||||
|
fps: 60,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.engineConnection?.send(resizeCmd)
|
||||||
|
}
|
||||||
handleModelingCommand(message: WebSocketResponse, id: string) {
|
handleModelingCommand(message: WebSocketResponse, id: string) {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type !== 'modeling') {
|
if (message.type !== 'modeling') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -645,12 +750,14 @@ 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,
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
id,
|
id,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
range: command.range,
|
range: command.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
@ -658,11 +765,44 @@ export class EngineCommandManager {
|
|||||||
commandType: command?.commandType,
|
commandType: command?.commandType,
|
||||||
range: command?.range,
|
range: command?.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleFailedModelingCommand({
|
||||||
|
request_id,
|
||||||
|
errors,
|
||||||
|
}: Models['FailureWebSocketResponse_type']) {
|
||||||
|
const id = request_id
|
||||||
|
if (!id) return
|
||||||
|
const command = this.artifactMap[id]
|
||||||
|
if (command && command.type === 'pending') {
|
||||||
|
const resolve = command.resolve
|
||||||
|
this.artifactMap[id] = {
|
||||||
|
type: 'failed',
|
||||||
|
range: command.range,
|
||||||
|
commandType: command.commandType,
|
||||||
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
id,
|
||||||
|
commandType: command.commandType,
|
||||||
|
range: command.range,
|
||||||
|
errors,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.artifactMap[id] = {
|
||||||
|
type: 'failed',
|
||||||
|
range: command.range,
|
||||||
|
commandType: command.commandType,
|
||||||
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
|
errors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tearDown() {
|
tearDown() {
|
||||||
this.engineConnection?.close()
|
this.engineConnection?.tearDown()
|
||||||
}
|
}
|
||||||
startNewSession() {
|
startNewSession() {
|
||||||
this.artifactMap = {}
|
this.artifactMap = {}
|
||||||
@ -757,6 +897,9 @@ export class EngineCommandManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
sendSceneCommand(command: EngineCommand): Promise<any> {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
command.type === 'modeling_cmd_req' &&
|
command.type === 'modeling_cmd_req' &&
|
||||||
command.cmd.type !== lastMessage
|
command.cmd.type !== lastMessage
|
||||||
@ -765,7 +908,6 @@ export class EngineCommandManager {
|
|||||||
lastMessage = command.cmd.type
|
lastMessage = command.cmd.type
|
||||||
}
|
}
|
||||||
if (!this.engineConnection?.isReady()) {
|
if (!this.engineConnection?.isReady()) {
|
||||||
console.log('socket not ready')
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||||
@ -777,9 +919,7 @@ export class EngineCommandManager {
|
|||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableDataChannel?.send(
|
this.engineConnection?.unreliableSend(command)
|
||||||
JSON.stringify(command)
|
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} else if (
|
} else if (
|
||||||
cmd.type === 'highlight_set_entity' &&
|
cmd.type === 'highlight_set_entity' &&
|
||||||
@ -787,9 +927,7 @@ export class EngineCommandManager {
|
|||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableDataChannel?.send(
|
this.engineConnection?.unreliableSend(command)
|
||||||
JSON.stringify(command)
|
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} else if (
|
} else if (
|
||||||
cmd.type === 'mouse_move' &&
|
cmd.type === 'mouse_move' &&
|
||||||
@ -797,9 +935,7 @@ export class EngineCommandManager {
|
|||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableDataChannel?.send(
|
this.engineConnection?.unreliableSend(command)
|
||||||
JSON.stringify(command)
|
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||||
@ -815,10 +951,12 @@ export class EngineCommandManager {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
command: EngineCommand | string
|
command: EngineCommand | string
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
this.sourceRangeMap[id] = range
|
this.sourceRangeMap[id] = range
|
||||||
|
|
||||||
if (!this.engineConnection?.isReady()) {
|
if (!this.engineConnection?.isReady()) {
|
||||||
console.log('socket not ready')
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
this.engineConnection?.send(command)
|
this.engineConnection?.send(command)
|
||||||
@ -861,6 +999,9 @@ export class EngineCommandManager {
|
|||||||
rangeStr: string,
|
rangeStr: string,
|
||||||
commandStr: string
|
commandStr: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
throw new Error('id is undefined')
|
throw new Error('id is undefined')
|
||||||
}
|
}
|
||||||
@ -872,7 +1013,10 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
const range: SourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
|
|
||||||
return this.sendModelingCommand({ id, range, command: commandStr })
|
// We only care about the modeling command response.
|
||||||
|
return this.sendModelingCommand({ id, range, command: commandStr }).then(
|
||||||
|
({ raw }) => JSON.stringify(raw)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
commandResult(id: string): Promise<any> {
|
commandResult(id: string): Promise<any> {
|
||||||
const command = this.artifactMap[id]
|
const command = this.artifactMap[id]
|
||||||
@ -881,6 +1025,8 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
if (command.type === 'result') {
|
if (command.type === 'result') {
|
||||||
return command.data
|
return command.data
|
||||||
|
} else if (command.type === 'failed') {
|
||||||
|
return Promise.resolve(command.errors)
|
||||||
}
|
}
|
||||||
return command.promise
|
return command.promise
|
||||||
}
|
}
|
||||||
@ -906,6 +1052,9 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
|
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
/* This is a temporary solution since the cmd_ids that are sent through when
|
/* This is a temporary solution since the cmd_ids that are sent through when
|
||||||
sending 'extend_path' ids are not used as the segment ids.
|
sending 'extend_path' ids are not used as the segment ids.
|
||||||
|
|
||||||
@ -942,7 +1091,6 @@ export class EngineCommandManager {
|
|||||||
pathInfos.forEach(({ originalId, segments }) => {
|
pathInfos.forEach(({ originalId, segments }) => {
|
||||||
const originalArtifact = this.artifactMap[originalId]
|
const originalArtifact = this.artifactMap[originalId]
|
||||||
if (!originalArtifact || originalArtifact.type === 'pending') {
|
if (!originalArtifact || originalArtifact.type === 'pending') {
|
||||||
console.log('problem')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pipeExpPath = getNodePathFromSourceRange(
|
const pipeExpPath = getNodePathFromSourceRange(
|
||||||
@ -955,23 +1103,20 @@ export class EngineCommandManager {
|
|||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
).node
|
).node
|
||||||
if (pipeExp.type !== 'VariableDeclarator') {
|
if (pipeExp.type !== 'VariableDeclarator') {
|
||||||
console.log('problem', pipeExp, pipeExpPath, ast)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const variableName = pipeExp.id.name
|
const variableName = pipeExp.id.name
|
||||||
const memoryItem = programMemory.root[variableName]
|
const memoryItem = programMemory.root[variableName]
|
||||||
if (!memoryItem) {
|
if (!memoryItem) {
|
||||||
console.log('problem', variableName, programMemory)
|
|
||||||
return
|
return
|
||||||
} else if (memoryItem.type !== 'SketchGroup') {
|
} else if (memoryItem.type !== 'SketchGroup') {
|
||||||
console.log('problem', memoryItem, programMemory)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const relevantSegments = segments.filter(
|
const relevantSegments = segments.filter(
|
||||||
({ command_id }: { command_id: string | null }) => command_id
|
({ command_id }: { command_id: string | null }) => command_id
|
||||||
)
|
)
|
||||||
if (memoryItem.value.length !== relevantSegments.length) {
|
if (memoryItem.value.length !== relevantSegments.length) {
|
||||||
console.log('problem', memoryItem.value, relevantSegments)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (let i = 0; i < relevantSegments.length; i++) {
|
for (let i = 0; i < relevantSegments.length; i++) {
|
||||||
@ -981,9 +1126,13 @@ export class EngineCommandManager {
|
|||||||
const artifact = this.artifactMap[oldId]
|
const artifact = this.artifactMap[oldId]
|
||||||
delete this.artifactMap[oldId]
|
delete this.artifactMap[oldId]
|
||||||
delete this.sourceRangeMap[oldId]
|
delete this.sourceRangeMap[oldId]
|
||||||
this.artifactMap[engineSegment.command_id] = artifact
|
if (artifact) {
|
||||||
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
this.artifactMap[engineSegment.command_id] = artifact
|
||||||
|
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
@ -117,6 +117,7 @@ show(mySketch001)
|
|||||||
{
|
{
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
|
pathId: '',
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
pathToNode: [
|
pathToNode: [
|
||||||
|
@ -1279,7 +1279,7 @@ export function getTransformInfos(
|
|||||||
}) as TransformInfo[]
|
}) as TransformInfo[]
|
||||||
return theTransforms
|
return theTransforms
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log('error', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ export async function asyncLexer(str: string): Promise<Token[]> {
|
|||||||
return tokens
|
return tokens
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: do something real with the error.
|
// TODO: do something real with the error.
|
||||||
console.log('lexer', e)
|
console.log('lexer error', e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ export function lexer(str: string): Token[] {
|
|||||||
return tokens
|
return tokens
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: do something real with the error.
|
// TODO: do something real with the error.
|
||||||
console.log('lexer', e)
|
console.log('lexer error', e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
src/lib/exampleKcl.ts
Normal file
21
src/lib/exampleKcl.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const bracket = `// Material: 6061-T6 Aluminum
|
||||||
|
const sigmaAllow = 35000 // psi
|
||||||
|
const width = 9 // inch
|
||||||
|
const p = 150 // Force on shelf - lbs
|
||||||
|
const distance = 6 // inches
|
||||||
|
const FOS = 2
|
||||||
|
|
||||||
|
const leg1 = 5 // inches
|
||||||
|
const leg2 = 8 // inches
|
||||||
|
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
|
||||||
|
const bracket = startSketchAt([0, 0])
|
||||||
|
|> line([0, leg1], %)
|
||||||
|
|> line([leg2, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-leg2 + thickness, 0], %)
|
||||||
|
|> line([0, -leg1 + thickness], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(width, %)
|
||||||
|
|
||||||
|
show(bracket)
|
||||||
|
`
|
@ -39,6 +39,6 @@ export async function exportSave(data: ArrayBuffer) {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: do something real with the error.
|
// TODO: do something real with the error.
|
||||||
console.log('export', e)
|
console.log('export error', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ export async function initializeProjectDirectory(directory: string) {
|
|||||||
try {
|
try {
|
||||||
docDirectory = await documentDir()
|
docDirectory = await documentDir()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log('error', e)
|
||||||
docDirectory = await homeDir() // seems to work better on Linux
|
docDirectory = await homeDir() // seems to work better on Linux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@ import {
|
|||||||
EngineCommand,
|
EngineCommand,
|
||||||
} from '../lang/std/engineConnection'
|
} from '../lang/std/engineConnection'
|
||||||
import { SourceRange } from 'lang/executor'
|
import { SourceRange } from 'lang/executor'
|
||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
|
||||||
|
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
class MockEngineCommandManager {
|
class MockEngineCommandManager {
|
||||||
constructor(mockParams: {
|
constructor(mockParams: {
|
||||||
@ -23,7 +26,13 @@ class MockEngineCommandManager {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
command: EngineCommand
|
command: EngineCommand
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
return Promise.resolve()
|
const response: WebSocketResponse = {
|
||||||
|
type: 'modeling',
|
||||||
|
data: {
|
||||||
|
modeling_response: { type: 'empty' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return Promise.resolve(JSON.stringify(response))
|
||||||
}
|
}
|
||||||
sendModelingCommandFromWasm(
|
sendModelingCommandFromWasm(
|
||||||
id: string,
|
id: string,
|
||||||
@ -66,11 +75,12 @@ export async function executor(
|
|||||||
ast: Program,
|
ast: Program,
|
||||||
pm: ProgramMemory = { root: {}, return: null }
|
pm: ProgramMemory = { root: {}, return: null }
|
||||||
): Promise<ProgramMemory> {
|
): Promise<ProgramMemory> {
|
||||||
const engineCommandManager = new EngineCommandManager({
|
const engineCommandManager = new EngineCommandManager()
|
||||||
|
engineCommandManager.start({
|
||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
setMediaStream: () => {},
|
setMediaStream: () => {},
|
||||||
width: 100,
|
width: 0,
|
||||||
height: 100,
|
height: 0,
|
||||||
})
|
})
|
||||||
await engineCommandManager.waitForReady
|
await engineCommandManager.waitForReady
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
|
@ -4,6 +4,7 @@ import withBaseURL from '../lib/withBaseURL'
|
|||||||
import { CommandBarMeta } from '../lib/commands'
|
import { CommandBarMeta } from '../lib/commands'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
|
import { VITE_KC_API_BASE_URL } from 'env'
|
||||||
|
|
||||||
const SKIP_AUTH =
|
const SKIP_AUTH =
|
||||||
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
|
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
|
||||||
@ -132,6 +133,7 @@ async function getUser(context: UserContext) {
|
|||||||
.catch((err) => console.error('error from Browser getUser', err))
|
.catch((err) => console.error('error from Browser getUser', err))
|
||||||
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
|
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
|
||||||
token: context.token,
|
token: context.token,
|
||||||
|
hostname: VITE_KC_API_BASE_URL,
|
||||||
}).catch((err) => console.error('error from Tauri getUser', err))
|
}).catch((err) => console.error('error from Tauri getUser', err))
|
||||||
|
|
||||||
const user = await userPromise
|
const user = await userPromise
|
||||||
|
@ -2,48 +2,78 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
|
import { SettingsSection } from 'routes/Settings'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import {
|
||||||
|
CameraSystem,
|
||||||
|
cameraMouseDragGuards,
|
||||||
|
cameraSystems,
|
||||||
|
} from 'lib/cameraControls'
|
||||||
|
|
||||||
export default function Units() {
|
export default function Units() {
|
||||||
const { buttonDownInStream } = useStore((s) => ({
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
}))
|
}))
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.SKETCHING)
|
const next = useNextClick(onboardingPaths.STREAMING)
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
send,
|
||||||
|
state: {
|
||||||
|
context: { cameraControls },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<h1 className="text-2xl font-bold">Camera</h1>
|
<SettingsSection
|
||||||
<p className="mt-6">
|
title="Camera Controls"
|
||||||
Moving the camera is easy! The controls are as you might expect:
|
description="How you want to control the camera in the 3D view. Try them out above and choose the one that feels most comfortable to you."
|
||||||
</p>
|
className="my-4 last-of-type:mb-12"
|
||||||
<ul className="list-disc list-outside ms-8 mb-4">
|
>
|
||||||
<li>Click and drag anywhere in the scene to rotate the camera</li>
|
<select
|
||||||
<li>
|
id="camera-controls"
|
||||||
Hold down the <kbd>Shift</kbd> key while clicking and dragging to
|
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||||
pan the camera
|
value={cameraControls}
|
||||||
</li>
|
onChange={(e) => {
|
||||||
<li>
|
send({
|
||||||
Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can
|
type: 'Set Camera Controls',
|
||||||
also use the scroll wheel to zoom in and out.
|
data: { cameraControls: e.target.value as CameraSystem },
|
||||||
</li>
|
})
|
||||||
</ul>
|
}}
|
||||||
<p>
|
>
|
||||||
What you're seeing here is just a video, and your interactions are
|
{cameraSystems.map((program) => (
|
||||||
being sent to our Geometry Engine API, which sends back video frames
|
<option key={program} value={program}>
|
||||||
in real time. How cool is that? It means that you can use KittyCAD
|
{program}
|
||||||
Modeling App (or whatever you want to build) on any device, even a
|
</option>
|
||||||
cheap laptop with no graphics card!
|
))}
|
||||||
</p>
|
</select>
|
||||||
<div className="flex justify-between mt-6">
|
<ul className="mx-4 my-2 text-sm leading-relaxed">
|
||||||
|
<li>
|
||||||
|
<strong>Pan:</strong>{' '}
|
||||||
|
{cameraMouseDragGuards[cameraControls].pan.description}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Zoom:</strong>{' '}
|
||||||
|
{cameraMouseDragGuards[cameraControls].zoom.description}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Rotate:</strong>{' '}
|
||||||
|
{cameraMouseDragGuards[cameraControls].rotate.description}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</SettingsSection>
|
||||||
|
<div className="flex justify-between">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../../')}
|
onClick={dismiss}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
@ -59,7 +89,7 @@ export default function Units() {
|
|||||||
onClick={next}
|
onClick={next}
|
||||||
icon={{ icon: faArrowRight }}
|
icon={{ icon: faArrowRight }}
|
||||||
>
|
>
|
||||||
Next: Sketching
|
Next: Streaming
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
85
src/routes/Onboarding/CmdK.tsx
Normal file
85
src/routes/Onboarding/CmdK.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { Platform, platform } from '@tauri-apps/api/os'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function CmdK() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.USER_MENU)
|
||||||
|
const [platformName, setPlatformName] = useState<Platform | ''>('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getPlatform() {
|
||||||
|
setPlatformName(await platform())
|
||||||
|
}
|
||||||
|
getPlatform()
|
||||||
|
}, [setPlatformName])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-full xl:max-w-4xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2 className="text-2xl">Command Bar</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Press{' '}
|
||||||
|
{platformName === 'win32' ? (
|
||||||
|
<>
|
||||||
|
<kbd>Win</kbd> + <kbd>/</kbd>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<kbd>OS</kbd> + <kbd>K</kbd>
|
||||||
|
</>
|
||||||
|
)}{' '}
|
||||||
|
to open the command bar. Try changing your theme with it.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
We are working on a command bar that will allow you to quickly see and
|
||||||
|
search for any available commands. We are building KittyCAD Modeling
|
||||||
|
App's state management system on top of{' '}
|
||||||
|
<a
|
||||||
|
href="https://xstate.js.org/"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
XState
|
||||||
|
</a>
|
||||||
|
. Currently you can only control settings, authentication, and file
|
||||||
|
management from the command bar, but we will be powering modeling
|
||||||
|
commands with it soon.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: User Menu
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
85
src/routes/Onboarding/CodeEditor.tsx
Normal file
85
src/routes/Onboarding/CodeEditor.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
|
|
||||||
|
export default function CodeEditor() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||||
|
style={{ clipPath: useBackdropHighlight('code-pane') }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">
|
||||||
|
Editing code with <code>kcl</code>
|
||||||
|
</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
The left pane is where you write your code. It's a code editor with
|
||||||
|
syntax highlighting and autocompletion. We've decided to take the
|
||||||
|
difficult route of writing our own language—called <code>kcl</code>
|
||||||
|
—for describing geometry, because don't want to inherit all the
|
||||||
|
other functionality from existing languages. We have a lot of ideas
|
||||||
|
about how <code>kcl</code> will evolve, and we want to hear your
|
||||||
|
thoughts on it.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
We've built a language server for <code>kcl</code> that provides
|
||||||
|
documentation and autocompletion automatically generated from our
|
||||||
|
compiler code. You can try it out by hovering over some of the
|
||||||
|
function names in the pane now. If you like using VSCode, you can
|
||||||
|
try out our{' '}
|
||||||
|
<a
|
||||||
|
href="https://marketplace.visualstudio.com/items?itemName=KittyCAD.kcl-language-server"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
VSCode extension
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
You can resize the pane by dragging the handle on the right, and you
|
||||||
|
can collapse it by clicking the title bar or pressing{' '}
|
||||||
|
<kbd>Shift</kbd> + <kbd>C</kbd>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Parametric Modeling
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
65
src/routes/Onboarding/Export.tsx
Normal file
65
src/routes/Onboarding/Export.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function Export() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.SKETCHING)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">Export</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Try opening the project menu and clicking "Export Model".
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
KittyCAD Modeling App uses our open-source extension proposal for
|
||||||
|
the GLTF file format.{' '}
|
||||||
|
<a
|
||||||
|
href="https://kittycad.io/docs/api/convert-cad-file"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Our conversion API
|
||||||
|
</a>{' '}
|
||||||
|
can convert to and from most common CAD file formats, allowing
|
||||||
|
export to almost any CAD software.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Sketching
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
59
src/routes/Onboarding/FutureWork.tsx
Normal file
59
src/routes/Onboarding/FutureWork.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { useDismiss } from '.'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
|
export default function FutureWork() {
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const { deferredSetCode } = useStore((s) => ({
|
||||||
|
deferredSetCode: s.deferredSetCode,
|
||||||
|
}))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
deferredSetCode(bracket)
|
||||||
|
}, [deferredSetCode])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
||||||
|
<div className="max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
|
<h1 className="text-2xl font-bold">Future Work</h1>
|
||||||
|
<p className="my-4">
|
||||||
|
We have curves, cuts, and many more CAD features coming soon. We want
|
||||||
|
your feedback on this user interface, and we want to know what
|
||||||
|
features you want to see next. Please message us in the Discord server
|
||||||
|
and open issues on GitHub.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
If you make anything with the app we'd love to see it! Thank you for
|
||||||
|
taking time to try out KittyCAD Modeling App, and build the future of
|
||||||
|
hardware design with us 💚.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">— The KittyCAD Team</p>
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
125
src/routes/Onboarding/InteractiveNumbers.tsx
Normal file
125
src/routes/Onboarding/InteractiveNumbers.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
|
|
||||||
|
export default function InteractiveNumbers() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.COMMAND_K)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||||
|
style={{ clipPath: useBackdropHighlight('code-pane') }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1 overflow-y-auto mb-6">
|
||||||
|
<h2 className="text-2xl">Interactive Numbers</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Let's do a little bit of hybrid editing to this part.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Try changing the value of <code>width</code> on line 3 by holding
|
||||||
|
the <kbd>Alt</kbd> (or <kbd>Option</kbd>) key and dragging the
|
||||||
|
number left and right. You can hold down different modifier keys to
|
||||||
|
change the value by different increments:
|
||||||
|
</p>
|
||||||
|
<table className="border-collapse text-sm mx-auto my-4">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt + Shift + Cmd/Win</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
0.01
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt + Cmd/Win</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
0.1
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt + Shift</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
10
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p className="my-4">
|
||||||
|
Our code editor is built with{' '}
|
||||||
|
<a
|
||||||
|
href="https://codemirror.net/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopeneer"
|
||||||
|
>
|
||||||
|
CodeMirror
|
||||||
|
</a>
|
||||||
|
, a great open-source project with extensions that make it even more
|
||||||
|
dynamic and interactive, including{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/replit/codemirror-interact/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopeneer"
|
||||||
|
>
|
||||||
|
one by the Replit team
|
||||||
|
</a>{' '}
|
||||||
|
lets you interact with numbers in your code by dragging them around.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Editing code should feel as interactive as point-and-click when you
|
||||||
|
want it to be, so that you can work in the way that feels most
|
||||||
|
natural to you. We're going to keep extending the text editor, and
|
||||||
|
we'd love to hear your ideas for how to make it better.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Command Bar
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,29 +1,183 @@
|
|||||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import {
|
||||||
|
createNewProject,
|
||||||
|
getNextProjectIndex,
|
||||||
|
getProjectsInDir,
|
||||||
|
interpolateProjectNameWithIndex,
|
||||||
|
} from 'lib/tauriFS'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { paths } from 'Router'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export default function Introduction() {
|
function OnboardingWithNewFile() {
|
||||||
|
const navigate = useNavigate()
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.UNITS)
|
const next = useNextClick(onboardingPaths.INDEX)
|
||||||
|
const { deferredSetCode } = useStore((s) => ({
|
||||||
|
deferredSetCode: s.deferredSetCode,
|
||||||
|
}))
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
context: { defaultDirectory, defaultProjectName },
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
|
async function createAndOpenNewProject() {
|
||||||
|
const projects = await getProjectsInDir(defaultDirectory)
|
||||||
|
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
|
||||||
|
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
|
||||||
|
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
||||||
|
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||||
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
<h1 className="text-2xl font-bold">
|
{!isTauri() ? (
|
||||||
Welcome to the KittyCAD Modeling App
|
<>
|
||||||
|
<h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
|
||||||
|
Replaying onboarding resets your code
|
||||||
|
</h1>
|
||||||
|
<p className="my-4">
|
||||||
|
We see you have some of your own code written in this project.
|
||||||
|
Please save it somewhere else before continuing the onboarding.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => {
|
||||||
|
deferredSetCode(bracket)
|
||||||
|
next()
|
||||||
|
}}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Overwrite code and continue
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
||||||
|
Would you like to create a new project?
|
||||||
|
</h1>
|
||||||
|
<section className="my-12">
|
||||||
|
<p className="my-4">
|
||||||
|
You have some content in this project that we don't want to
|
||||||
|
overwrite. If you would like to create a new project, please
|
||||||
|
click the button below.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={createAndOpenNewProject}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Make a new project
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Introduction() {
|
||||||
|
const { deferredSetCode, code } = useStore((s) => ({
|
||||||
|
code: s.code,
|
||||||
|
deferredSetCode: s.deferredSetCode,
|
||||||
|
}))
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
state: {
|
||||||
|
context: { theme },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
const getLogoTheme = () =>
|
||||||
|
theme === Themes.Light ||
|
||||||
|
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
||||||
|
? '-dark'
|
||||||
|
: ''
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.CAMERA)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (code === '') deferredSetCode(bracket)
|
||||||
|
}, [code, deferredSetCode])
|
||||||
|
|
||||||
|
return !(code !== '' && code !== bracket) ? (
|
||||||
|
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||||
|
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
|
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
||||||
|
<img
|
||||||
|
src={`/kcma-logomark${getLogoTheme()}.svg`}
|
||||||
|
alt="KittyCAD Modeling App"
|
||||||
|
className="max-w-full h-20"
|
||||||
|
/>
|
||||||
|
<span className="bg-energy-10 text-energy-80 px-3 py-1 rounded-full text-base">
|
||||||
|
Alpha
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="my-2">
|
<section className="my-12">
|
||||||
A browser-first, GPU-streaming hardware design tool that lets you edit
|
<p className="my-4">
|
||||||
visually, with code, or both.
|
Welcome to KittyCAD Modeling App! This is a hardware design tool
|
||||||
</p>
|
that lets you edit visually, with code, or both. It's powered by the
|
||||||
<p className="my-2">
|
first API created for anyone to build hardware design tools. The 3D
|
||||||
Powered by the first API created for anyone to build hardware design
|
view is not running on your computer, but is instead being streamed
|
||||||
tools.
|
to you from a remote GPU as video.
|
||||||
</p>
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
This is an alpha release, so you will encounter bugs and missing
|
||||||
|
features. You can read our{' '}
|
||||||
|
<a
|
||||||
|
href="https://gist.github.com/jgomez720/5cd53fb7e8e54079f6dc0d2625de5393"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
expectations for alpha users here
|
||||||
|
</a>
|
||||||
|
. Please give us feedback on your experience! We are trying to
|
||||||
|
release as early as possible to get feedback from users like you.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
<div className="flex justify-between mt-6">
|
<div className="flex justify-between mt-6">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../')}
|
onClick={dismiss}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
@ -44,5 +198,7 @@ export default function Introduction() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<OnboardingWithNewFile />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
85
src/routes/Onboarding/ParametricModeling.tsx
Normal file
85
src/routes/Onboarding/ParametricModeling.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
|
export default function ParametricModeling() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
context: { theme },
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
const getImageTheme = () =>
|
||||||
|
theme === Themes.Light ||
|
||||||
|
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
||||||
|
? '-dark'
|
||||||
|
: ''
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||||
|
style={{ clipPath: useBackdropHighlight('code-pane') }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1 overflow-y-auto mb-6">
|
||||||
|
<h2 className="text-2xl">Towards true parametric modeling</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
This example script shows how having access to the code
|
||||||
|
representation of a part can allow us to do things that are tedious
|
||||||
|
or impossible in traditional CAD software. Here we are building a
|
||||||
|
simplified shelf bracket out of aluminum:
|
||||||
|
</p>
|
||||||
|
<figure className="my-4 w-3/4 mx-auto">
|
||||||
|
<img
|
||||||
|
src={`/onboarding-bracket${getImageTheme()}.png`}
|
||||||
|
alt="Bracket"
|
||||||
|
/>
|
||||||
|
<figcaption className="text-small italic text-center">
|
||||||
|
A simplified shelf bracket
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<p className="my-4">
|
||||||
|
We are able to easily calculate the thickness of the material based
|
||||||
|
on the width of the bracket to meet a set safety factor on line 6.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Interactive Numbers
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
56
src/routes/Onboarding/ProjectMenu.tsx
Normal file
56
src/routes/Onboarding/ProjectMenu.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
|
||||||
|
export default function ProjectMenu() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.EXPORT)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">Project Menu</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Click on Kitt in the upper left to open the project menu. You can
|
||||||
|
only {isTauri() && 'go home or '}export your model—which we'll talk
|
||||||
|
about next—for now. We'll add more options here soon, especially as
|
||||||
|
we add support for multi-file assemblies.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Export
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,21 +1,44 @@
|
|||||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { useDismiss } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export default function Sketching() {
|
export default function Sketching() {
|
||||||
|
const { deferredSetCode, buttonDownInStream } = useStore((s) => ({
|
||||||
|
deferredSetCode: s.deferredSetCode,
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
deferredSetCode('')
|
||||||
|
}, [deferredSetCode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50">
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
<div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
<h1 className="text-2xl font-bold">Sketching</h1>
|
<h1 className="text-2xl font-bold">Sketching</h1>
|
||||||
<p className="mt-6">
|
<p className="my-4">
|
||||||
We still have to implement this step, and the rest of the tutorial!
|
Our 3D modeling tools are still very much a work in progress, but we
|
||||||
|
want to show you some early features. Try creating a sketch by
|
||||||
|
clicking Create Sketch in the top toolbar, then clicking the Line
|
||||||
|
tool, and clicking in the 3D view.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Watch the code pane as you click. Point-and-click interactions are
|
||||||
|
always just modifying and generating code in KittyCAD Modeling App.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-between mt-6">
|
<div className="flex justify-between mt-6">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../../')}
|
onClick={dismiss}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
@ -28,10 +51,10 @@ export default function Sketching() {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../../')}
|
onClick={next}
|
||||||
icon={{ icon: faArrowRight }}
|
icon={{ icon: faArrowRight }}
|
||||||
>
|
>
|
||||||
Finish
|
Next: Future Work
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
66
src/routes/Onboarding/Streaming.tsx
Normal file
66
src/routes/Onboarding/Streaming.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function Streaming() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.EDITOR)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">Streaming Video</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
The 3D view is not running on your computer. Instead, our
|
||||||
|
infrastructure spins up the KittyCAD Geometry Engine on a remote
|
||||||
|
GPU, KittyCAD Modeling App sends it a series of commands via
|
||||||
|
Websockets and WebRTC, and the Geometry Engine sends back a video
|
||||||
|
stream of the 3D view.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
This means that you could run KittyCAD Modeling App on a Chromebook,
|
||||||
|
a tablet, or even a phone, as long as you have a good internet
|
||||||
|
connection.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
It also means that whatever tools you build on top of the KittyCAD
|
||||||
|
Geometry Engine will be able to run on any device with a browser,
|
||||||
|
and you won't have to worry about the performance of the device.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Code Editing
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -66,7 +66,7 @@ export default function Units() {
|
|||||||
<div className="flex justify-between mt-6">
|
<div className="flex justify-between mt-6">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../../')}
|
onClick={dismiss}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
|
53
src/routes/Onboarding/UserMenu.tsx
Normal file
53
src/routes/Onboarding/UserMenu.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function UserMenu() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">User Menu</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Click your avatar on the upper right to open the user menu. You can
|
||||||
|
change your settings, sign out, or request a feature.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={dismiss}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Project Menu
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,18 +1,36 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Outlet, useNavigate } from 'react-router-dom'
|
import { Outlet, useRouteLoaderData, useNavigate } from 'react-router-dom'
|
||||||
import Introduction from './Introduction'
|
import Introduction from './Introduction'
|
||||||
import Units from './Units'
|
|
||||||
import Camera from './Camera'
|
import Camera from './Camera'
|
||||||
import Sketching from './Sketching'
|
import Sketching from './Sketching'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import Streaming from './Streaming'
|
||||||
|
import CodeEditor from './CodeEditor'
|
||||||
|
import ParametricModeling from './ParametricModeling'
|
||||||
|
import InteractiveNumbers from './InteractiveNumbers'
|
||||||
|
import CmdK from './CmdK'
|
||||||
|
import UserMenu from './UserMenu'
|
||||||
|
import ProjectMenu from './ProjectMenu'
|
||||||
|
import Export from './Export'
|
||||||
|
import FutureWork from './FutureWork'
|
||||||
|
import { IndexLoaderData, paths } from 'Router'
|
||||||
|
|
||||||
export const onboardingPaths = {
|
export const onboardingPaths = {
|
||||||
INDEX: '/',
|
INDEX: '/',
|
||||||
UNITS: '/units',
|
|
||||||
CAMERA: '/camera',
|
CAMERA: '/camera',
|
||||||
|
STREAMING: '/streaming',
|
||||||
|
EDITOR: '/editor',
|
||||||
|
PARAMETRIC_MODELING: '/parametric-modeling',
|
||||||
|
INTERACTIVE_NUMBERS: '/interactive-numbers',
|
||||||
|
COMMAND_K: '/command-k',
|
||||||
|
USER_MENU: '/user-menu',
|
||||||
|
PROJECT_MENU: '/project-menu',
|
||||||
|
EXPORT: '/export',
|
||||||
|
MOVE: '/move',
|
||||||
SKETCHING: '/sketching',
|
SKETCHING: '/sketching',
|
||||||
|
FUTURE_WORK: '/future-work',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onboardingRoutes = [
|
export const onboardingRoutes = [
|
||||||
@ -20,18 +38,51 @@ export const onboardingRoutes = [
|
|||||||
index: true,
|
index: true,
|
||||||
element: <Introduction />,
|
element: <Introduction />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: makeUrlPathRelative(onboardingPaths.UNITS),
|
|
||||||
element: <Units />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: makeUrlPathRelative(onboardingPaths.CAMERA),
|
path: makeUrlPathRelative(onboardingPaths.CAMERA),
|
||||||
element: <Camera />,
|
element: <Camera />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.STREAMING),
|
||||||
|
element: <Streaming />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.EDITOR),
|
||||||
|
element: <CodeEditor />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.PARAMETRIC_MODELING),
|
||||||
|
element: <ParametricModeling />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.INTERACTIVE_NUMBERS),
|
||||||
|
element: <InteractiveNumbers />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.COMMAND_K),
|
||||||
|
element: <CmdK />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.USER_MENU),
|
||||||
|
element: <UserMenu />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.PROJECT_MENU),
|
||||||
|
element: <ProjectMenu />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.EXPORT),
|
||||||
|
element: <Export />,
|
||||||
|
},
|
||||||
|
// Export / conversion API
|
||||||
{
|
{
|
||||||
path: makeUrlPathRelative(onboardingPaths.SKETCHING),
|
path: makeUrlPathRelative(onboardingPaths.SKETCHING),
|
||||||
element: <Sketching />,
|
element: <Sketching />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.FUTURE_WORK),
|
||||||
|
element: <FutureWork />,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export function useNextClick(newStatus: string) {
|
export function useNextClick(newStatus: string) {
|
||||||
@ -39,37 +90,44 @@ export function useNextClick(newStatus: string) {
|
|||||||
settings: { send },
|
settings: { send },
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
send({
|
send({
|
||||||
type: 'Set Onboarding Status',
|
type: 'Set Onboarding Status',
|
||||||
data: { onboardingStatus: newStatus },
|
data: { onboardingStatus: newStatus },
|
||||||
})
|
})
|
||||||
navigate((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus)
|
navigate(
|
||||||
}, [newStatus, send, navigate])
|
paths.FILE +
|
||||||
|
'/' +
|
||||||
|
encodeURIComponent(project?.path || 'new') +
|
||||||
|
paths.ONBOARDING.INDEX.slice(0, -1) +
|
||||||
|
newStatus
|
||||||
|
)
|
||||||
|
}, [project, newStatus, send, navigate])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDismiss() {
|
export function useDismiss() {
|
||||||
|
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
const {
|
const {
|
||||||
settings: { send },
|
settings: { send },
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(() => {
|
||||||
(path: string) => {
|
send({
|
||||||
send({
|
type: 'Set Onboarding Status',
|
||||||
type: 'Set Onboarding Status',
|
data: { onboardingStatus: 'dismissed' },
|
||||||
data: { onboardingStatus: 'dismissed' },
|
})
|
||||||
})
|
navigate(
|
||||||
navigate(path)
|
paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
|
||||||
},
|
)
|
||||||
[send, navigate]
|
}, [send, navigate, routeData])
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Onboarding = () => {
|
const Onboarding = () => {
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
useHotkeys('esc', () => dismiss('../'))
|
useHotkeys('esc', dismiss)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -23,12 +23,14 @@ import {
|
|||||||
cameraMouseDragGuards,
|
cameraMouseDragGuards,
|
||||||
} from 'lib/cameraControls'
|
} from 'lib/cameraControls'
|
||||||
import { UnitSystem } from 'machines/settingsMachine'
|
import { UnitSystem } from 'machines/settingsMachine'
|
||||||
|
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
useHotkeys('esc', () => navigate('../'))
|
const dotDotSlash = useDotDotSlash()
|
||||||
|
useHotkeys('esc', () => navigate(dotDotSlash()))
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
send,
|
send,
|
||||||
@ -62,11 +64,11 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="body-bg fixed inset-0 z-40 overflow-auto">
|
<div className="fixed inset-0 z-40 overflow-auto body-bg">
|
||||||
<AppHeader showToolbar={false} project={loaderData?.project}>
|
<AppHeader showToolbar={false} project={loaderData?.project}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="link"
|
||||||
to={'../'}
|
to={location.pathname.replace(paths.SETTINGS, '')}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
@ -78,9 +80,9 @@ export const Settings = () => {
|
|||||||
Close
|
Close
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
<div className="my-24 max-w-5xl mx-auto">
|
<div className="max-w-5xl mx-auto my-24">
|
||||||
<h1 className="text-4xl font-bold">User Settings</h1>
|
<h1 className="text-4xl font-bold">User Settings</h1>
|
||||||
<p className="mt-6 max-w-2xl">
|
<p className="max-w-2xl mt-6">
|
||||||
Don't see the feature you want? Check to see if it's on{' '}
|
Don't see the feature you want? Check to see if it's on{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/KittyCAD/modeling-app/discussions"
|
href="https://github.com/KittyCAD/modeling-app/discussions"
|
||||||
@ -98,7 +100,7 @@ export const Settings = () => {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id="camera-controls"
|
id="camera-controls"
|
||||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||||
value={cameraControls}
|
value={cameraControls}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
send({
|
send({
|
||||||
@ -113,7 +115,7 @@ export const Settings = () => {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<ul className="text-sm my-2 mx-4 leading-relaxed">
|
<ul className="mx-4 my-2 text-sm leading-relaxed">
|
||||||
<li>
|
<li>
|
||||||
<strong>Pan:</strong>{' '}
|
<strong>Pan:</strong>{' '}
|
||||||
{cameraMouseDragGuards[cameraControls].pan.description}
|
{cameraMouseDragGuards[cameraControls].pan.description}
|
||||||
@ -134,7 +136,7 @@ export const Settings = () => {
|
|||||||
title="Default Directory"
|
title="Default Directory"
|
||||||
description="Where newly-created projects are saved on your local computer"
|
description="Where newly-created projects are saved on your local computer"
|
||||||
>
|
>
|
||||||
<div className="w-full flex gap-4 p-1 rounded border border-chalkboard-30">
|
<div className="flex w-full gap-4 p-1 border rounded border-chalkboard-30">
|
||||||
<input
|
<input
|
||||||
className="flex-1 px-2 bg-transparent"
|
className="flex-1 px-2 bg-transparent"
|
||||||
value={defaultDirectory}
|
value={defaultDirectory}
|
||||||
@ -161,7 +163,7 @@ export const Settings = () => {
|
|||||||
description="Name template for new projects. Use $n to include an incrementing index"
|
description="Name template for new projects. Use $n to include an incrementing index"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||||
defaultValue={defaultProjectName}
|
defaultValue={defaultProjectName}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME
|
const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME
|
||||||
@ -205,7 +207,7 @@ export const Settings = () => {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id="base-unit"
|
id="base-unit"
|
||||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||||
value={baseUnit}
|
value={baseUnit}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
send({
|
send({
|
||||||
@ -239,7 +241,7 @@ export const Settings = () => {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id="settings-theme"
|
id="settings-theme"
|
||||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||||
value={theme}
|
value={theme}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
send({
|
send({
|
||||||
@ -267,7 +269,7 @@ export const Settings = () => {
|
|||||||
type: 'Set Onboarding Status',
|
type: 'Set Onboarding Status',
|
||||||
data: { onboardingStatus: '' },
|
data: { onboardingStatus: '' },
|
||||||
})
|
})
|
||||||
navigate('..' + paths.ONBOARDING.INDEX)
|
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
|
||||||
}}
|
}}
|
||||||
icon={{ icon: faArrowRotateBack }}
|
icon={{ icon: faArrowRotateBack }}
|
||||||
>
|
>
|
||||||
@ -283,15 +285,22 @@ export const Settings = () => {
|
|||||||
interface SettingsSectionProps extends React.PropsWithChildren {
|
interface SettingsSectionProps extends React.PropsWithChildren {
|
||||||
title: string
|
title: string
|
||||||
description?: string
|
description?: string
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSection({
|
export function SettingsSection({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
className,
|
||||||
children,
|
children,
|
||||||
}: SettingsSectionProps) {
|
}: SettingsSectionProps) {
|
||||||
return (
|
return (
|
||||||
<section className="my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start">
|
<section
|
||||||
|
className={
|
||||||
|
'my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start ' +
|
||||||
|
className
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="w-80">
|
<div className="w-80">
|
||||||
<h2 className="text-2xl">{title}</h2>
|
<h2 className="text-2xl">{title}</h2>
|
||||||
<p className="mt-2 text-sm">{description}</p>
|
<p className="mt-2 text-sm">{description}</p>
|
||||||
|
@ -18,6 +18,8 @@ import { EngineCommandManager } from './lang/std/engineConnection'
|
|||||||
import { KCLError } from './lang/errors'
|
import { KCLError } from './lang/errors'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { deferExecution } from 'lib/utils'
|
||||||
import { _executor } from './lang/executor'
|
import { _executor } from './lang/executor'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
|
||||||
export type Selection = {
|
export type Selection = {
|
||||||
type: 'default' | 'line-end' | 'line-mid'
|
type: 'default' | 'line-end' | 'line-mid'
|
||||||
@ -70,7 +72,7 @@ export type GuiModes =
|
|||||||
waitingFirstClick: boolean
|
waitingFirstClick: boolean
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
id?: string
|
pathId: string
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@ -79,6 +81,15 @@ export type GuiModes =
|
|||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
|
pathId: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
mode: 'sketch'
|
||||||
|
sketchMode: 'enterSketchEdit'
|
||||||
|
rotation: Rotation
|
||||||
|
position: Position
|
||||||
|
pathToNode: PathToNode
|
||||||
|
pathId: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
mode: 'sketch'
|
mode: 'sketch'
|
||||||
@ -152,8 +163,6 @@ export interface StoreState {
|
|||||||
setProgramMemory: (programMemory: ProgramMemory) => void
|
setProgramMemory: (programMemory: ProgramMemory) => void
|
||||||
isShiftDown: boolean
|
isShiftDown: boolean
|
||||||
setIsShiftDown: (isShiftDown: boolean) => void
|
setIsShiftDown: (isShiftDown: boolean) => void
|
||||||
engineCommandManager?: EngineCommandManager
|
|
||||||
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
|
|
||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
setMediaStream: (mediaStream: MediaStream) => void
|
setMediaStream: (mediaStream: MediaStream) => void
|
||||||
isStreamReady: boolean
|
isStreamReady: boolean
|
||||||
@ -216,7 +225,7 @@ export const useStore = create<StoreState>()(
|
|||||||
const result = await executeCode({
|
const result = await executeCode({
|
||||||
code: code || get().code,
|
code: code || get().code,
|
||||||
lastAst: get().ast,
|
lastAst: get().ast,
|
||||||
engineCommandManager: get().engineCommandManager,
|
engineCommandManager: engineCommandManager,
|
||||||
})
|
})
|
||||||
if (!result.isChange) {
|
if (!result.isChange) {
|
||||||
return
|
return
|
||||||
@ -312,7 +321,7 @@ export const useStore = create<StoreState>()(
|
|||||||
end: 0,
|
end: 0,
|
||||||
body: [],
|
body: [],
|
||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
noneCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
start: null,
|
start: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -322,8 +331,6 @@ export const useStore = create<StoreState>()(
|
|||||||
executeAst: async (ast) => {
|
executeAst: async (ast) => {
|
||||||
const _ast = ast || get().ast
|
const _ast = ast || get().ast
|
||||||
if (!get().isStreamReady) return
|
if (!get().isStreamReady) return
|
||||||
const engineCommandManager = get().engineCommandManager!
|
|
||||||
if (!engineCommandManager) return
|
|
||||||
|
|
||||||
set({ isExecuting: true })
|
set({ isExecuting: true })
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
@ -340,8 +347,6 @@ export const useStore = create<StoreState>()(
|
|||||||
executeAstMock: async (ast) => {
|
executeAstMock: async (ast) => {
|
||||||
const _ast = ast || get().ast
|
const _ast = ast || get().ast
|
||||||
if (!get().isStreamReady) return
|
if (!get().isStreamReady) return
|
||||||
const engineCommandManager = get().engineCommandManager!
|
|
||||||
if (!engineCommandManager) return
|
|
||||||
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
@ -409,7 +414,7 @@ export const useStore = create<StoreState>()(
|
|||||||
}, 100) as unknown as number
|
}, 100) as unknown as number
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
code: '',
|
code: bracket,
|
||||||
setCode: (code) => set({ code }),
|
setCode: (code) => set({ code }),
|
||||||
deferredSetCode: (code) => {
|
deferredSetCode: (code) => {
|
||||||
set({ code })
|
set({ code })
|
||||||
@ -425,8 +430,6 @@ export const useStore = create<StoreState>()(
|
|||||||
setProgramMemory: (programMemory) => set({ programMemory }),
|
setProgramMemory: (programMemory) => set({ programMemory }),
|
||||||
isShiftDown: false,
|
isShiftDown: false,
|
||||||
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
||||||
setEngineCommandManager: (engineCommandManager) =>
|
|
||||||
set({ engineCommandManager }),
|
|
||||||
setMediaStream: (mediaStream) => set({ mediaStream }),
|
setMediaStream: (mediaStream) => set({ mediaStream }),
|
||||||
isStreamReady: false,
|
isStreamReady: false,
|
||||||
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
|
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
|
||||||
@ -444,7 +447,9 @@ export const useStore = create<StoreState>()(
|
|||||||
fileId: '',
|
fileId: '',
|
||||||
setFileId: (fileId) => set({ fileId }),
|
setFileId: (fileId) => set({ fileId }),
|
||||||
streamDimensions: { streamWidth: 1280, streamHeight: 720 },
|
streamDimensions: { streamWidth: 1280, streamHeight: 720 },
|
||||||
setStreamDimensions: (streamDimensions) => set({ streamDimensions }),
|
setStreamDimensions: (streamDimensions) => {
|
||||||
|
set({ streamDimensions })
|
||||||
|
},
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
setIsExecuting: (isExecuting) => set({ isExecuting }),
|
setIsExecuting: (isExecuting) => set({ isExecuting }),
|
||||||
|
|
||||||
@ -509,7 +514,7 @@ async function executeCode({
|
|||||||
}: {
|
}: {
|
||||||
code: string
|
code: string
|
||||||
lastAst: Program
|
lastAst: Program
|
||||||
engineCommandManager?: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
}): Promise<
|
}): Promise<
|
||||||
| {
|
| {
|
||||||
logs: string[]
|
logs: string[]
|
||||||
@ -529,7 +534,7 @@ async function executeCode({
|
|||||||
if (e instanceof KCLError) {
|
if (e instanceof KCLError) {
|
||||||
errors = [e]
|
errors = [e]
|
||||||
logs = []
|
logs = []
|
||||||
if (e.msg === 'file is empty') engineCommandManager?.endSession()
|
if (e.msg === 'file is empty') engineCommandManager.endSession()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isChange: true,
|
isChange: true,
|
||||||
@ -544,7 +549,7 @@ async function executeCode({
|
|||||||
end: 0,
|
end: 0,
|
||||||
body: [],
|
body: [],
|
||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
noneCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
start: null,
|
start: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -552,7 +557,7 @@ async function executeCode({
|
|||||||
}
|
}
|
||||||
// Check if the ast we have is equal to the ast in the storage.
|
// Check if the ast we have is equal to the ast in the storage.
|
||||||
// If it is, we don't need to update the ast.
|
// If it is, we don't need to update the ast.
|
||||||
if (!engineCommandManager || JSON.stringify(ast) === JSON.stringify(lastAst))
|
if (JSON.stringify(ast) === JSON.stringify(lastAst))
|
||||||
return { isChange: false }
|
return { isChange: false }
|
||||||
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
|
22
src/wasm-lib/.config/nextest.toml
Normal file
22
src/wasm-lib/.config/nextest.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Each test can have at most 4 threads, but if its name contains "serial_test_", then it
|
||||||
|
# also requires 4 threads.
|
||||||
|
# This means such tests run one at a time, with 4 threads.
|
||||||
|
|
||||||
|
[test-groups]
|
||||||
|
serial-integration = { max-threads = 4 }
|
||||||
|
|
||||||
|
[profile.default]
|
||||||
|
slow-timeout = { period = "10s", terminate-after = 1 }
|
||||||
|
|
||||||
|
[profile.ci]
|
||||||
|
slow-timeout = { period = "60s", terminate-after = 10 }
|
||||||
|
|
||||||
|
[[profile.default.overrides]]
|
||||||
|
filter = "test(serial_test_)"
|
||||||
|
test-group = "serial-integration"
|
||||||
|
threads-required = 4
|
||||||
|
|
||||||
|
[[profile.ci.overrides]]
|
||||||
|
filter = "test(serial_test_)"
|
||||||
|
test-group = "serial-integration"
|
||||||
|
threads-required = 4
|
470
src/wasm-lib/Cargo.lock
generated
470
src/wasm-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,8 @@ bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
|||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
||||||
serde_json = "1.0.106"
|
serde_json = "1.0.107"
|
||||||
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.87"
|
wasm-bindgen = "0.2.87"
|
||||||
wasm-bindgen-futures = "0.4.37"
|
wasm-bindgen-futures = "0.4.37"
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ wasm-bindgen-futures = "0.4.37"
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
image = "0.24.7"
|
image = "0.24.7"
|
||||||
kittycad = "0.2.25"
|
kittycad = "0.2.25"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
reqwest = { version = "0.11.20", default-features = false }
|
reqwest = { version = "0.11.20", default-features = false }
|
||||||
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
twenty-twenty = "0.6.1"
|
twenty-twenty = "0.6.1"
|
||||||
@ -50,3 +52,11 @@ members = [
|
|||||||
"derive-docs",
|
"derive-docs",
|
||||||
"kcl",
|
"kcl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "executor"
|
||||||
|
path = "tests/executor/main.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "modify"
|
||||||
|
path = "tests/modify/main.rs"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ proc-macro2 = "1"
|
|||||||
quote = "1"
|
quote = "1"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.33", features = ["full"] }
|
syn = { version = "2.0.37", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expectorate = "1.0.7"
|
expectorate = "1.0.7"
|
||||||
|
@ -79,13 +79,6 @@ fn do_stdlib_inner(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ast.sig.asyncness.is_some() {
|
|
||||||
errors.push(Error::new_spanned(
|
|
||||||
&ast.sig.fn_token,
|
|
||||||
"stdlib functions must not be async",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ast.sig.unsafety.is_some() {
|
if ast.sig.unsafety.is_some() {
|
||||||
errors.push(Error::new_spanned(
|
errors.push(Error::new_spanned(
|
||||||
&ast.sig.unsafety,
|
&ast.sig.unsafety,
|
||||||
@ -118,6 +111,7 @@ fn do_stdlib_inner(
|
|||||||
let fn_name = &ast.sig.ident;
|
let fn_name = &ast.sig.ident;
|
||||||
let fn_name_str = fn_name.to_string().replace("inner_", "");
|
let fn_name_str = fn_name.to_string().replace("inner_", "");
|
||||||
let fn_name_ident = format_ident!("{}", fn_name_str);
|
let fn_name_ident = format_ident!("{}", fn_name_str);
|
||||||
|
let boxed_fn_name_ident = format_ident!("boxed_{}", fn_name_str);
|
||||||
let _visibility = &ast.vis;
|
let _visibility = &ast.vis;
|
||||||
|
|
||||||
let (summary_text, description_text) = extract_doc_from_attrs(&ast.attrs);
|
let (summary_text, description_text) = extract_doc_from_attrs(&ast.attrs);
|
||||||
@ -204,7 +198,10 @@ fn do_stdlib_inner(
|
|||||||
syn::FnArg::Typed(pat) => pat.ty.as_ref().into_token_stream(),
|
syn::FnArg::Typed(pat) => pat.ty.as_ref().into_token_stream(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ty_string = ty.to_string().replace('&', "").replace("mut", "").replace(' ', "");
|
let mut ty_string = ty.to_string().replace('&', "").replace("mut", "").replace(' ', "");
|
||||||
|
if ty_string.starts_with("Args") {
|
||||||
|
ty_string = "Args".to_string();
|
||||||
|
}
|
||||||
let ty_string = ty_string.trim().to_string();
|
let ty_string = ty_string.trim().to_string();
|
||||||
let ty_ident = if ty_string.starts_with("Vec<") {
|
let ty_ident = if ty_string.starts_with("Vec<") {
|
||||||
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
|
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
|
||||||
@ -212,6 +209,12 @@ fn do_stdlib_inner(
|
|||||||
quote! {
|
quote! {
|
||||||
Vec<#ty_ident>
|
Vec<#ty_ident>
|
||||||
}
|
}
|
||||||
|
} else if ty_string.starts_with("Box<") {
|
||||||
|
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
|
||||||
|
let ty_ident = format_ident!("{}", ty_string);
|
||||||
|
quote! {
|
||||||
|
#ty_ident
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let ty_ident = format_ident!("{}", ty_string);
|
let ty_ident = format_ident!("{}", ty_string);
|
||||||
quote! {
|
quote! {
|
||||||
@ -250,7 +253,15 @@ fn do_stdlib_inner(
|
|||||||
.replace("Result < ", "")
|
.replace("Result < ", "")
|
||||||
.replace(", KclError >", "");
|
.replace(", KclError >", "");
|
||||||
let return_type = if !ret_ty_string.is_empty() {
|
let return_type = if !ret_ty_string.is_empty() {
|
||||||
let ret_ty_string = ret_ty_string.trim().to_string();
|
let ret_ty_string = if ret_ty_string.starts_with("Box <") {
|
||||||
|
ret_ty_string
|
||||||
|
.trim_start_matches("Box <")
|
||||||
|
.trim_end_matches('>')
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
ret_ty_string.trim().to_string()
|
||||||
|
};
|
||||||
let ret_ty_ident = format_ident!("{}", ret_ty_string);
|
let ret_ty_ident = format_ident!("{}", ret_ty_string);
|
||||||
let ret_ty_string = clean_type(&ret_ty_string);
|
let ret_ty_string = clean_type(&ret_ty_string);
|
||||||
quote! {
|
quote! {
|
||||||
@ -291,6 +302,14 @@ fn do_stdlib_inner(
|
|||||||
#description_doc_comment
|
#description_doc_comment
|
||||||
#const_struct
|
#const_struct
|
||||||
|
|
||||||
|
fn #boxed_fn_name_ident(
|
||||||
|
args: crate::std::Args,
|
||||||
|
) -> std::pin::Pin<
|
||||||
|
Box<dyn std::future::Future<Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>>>,
|
||||||
|
> {
|
||||||
|
Box::pin(#fn_name_ident(args))
|
||||||
|
}
|
||||||
|
|
||||||
impl #docs_crate::StdLibFn for #name_ident
|
impl #docs_crate::StdLibFn for #name_ident
|
||||||
{
|
{
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
@ -334,7 +353,7 @@ fn do_stdlib_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn std_lib_fn(&self) -> crate::std::StdFn {
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||||
#fn_name_ident
|
#boxed_fn_name_ident
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_box(&self) -> Box<dyn #docs_crate::StdLibFn> {
|
fn clone_box(&self) -> Box<dyn #docs_crate::StdLibFn> {
|
||||||
@ -475,6 +494,9 @@ fn clean_type(t: &str) -> String {
|
|||||||
if t.starts_with("Vec<") {
|
if t.starts_with("Vec<") {
|
||||||
t = t.replace("Vec<", "[").replace('>', "]");
|
t = t.replace("Vec<", "[").replace('>', "]");
|
||||||
}
|
}
|
||||||
|
if t.starts_with("Box<") {
|
||||||
|
t = t.replace("Box<", "").replace('>', "");
|
||||||
|
}
|
||||||
|
|
||||||
if t == "f64" {
|
if t == "f64" {
|
||||||
return "number".to_string();
|
return "number".to_string();
|
||||||
@ -564,4 +586,26 @@ mod tests {
|
|||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
expectorate::assert_contents("tests/show.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
expectorate::assert_contents("tests/show.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdlib_box() {
|
||||||
|
let (item, errors) = do_stdlib(
|
||||||
|
quote! {
|
||||||
|
name = "show",
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
fn inner_show(
|
||||||
|
/// The args to do shit to.
|
||||||
|
args: Box<f64>
|
||||||
|
) -> Box<f64> {
|
||||||
|
args
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let _expected = quote! {};
|
||||||
|
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
82
src/wasm-lib/derive-docs/tests/box.gen
Normal file
82
src/wasm-lib/derive-docs/tests/box.gen
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
|
#[doc = "Std lib function: show"]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub(crate) struct Show {}
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals, missing_docs)]
|
||||||
|
#[doc = "Std lib function: show"]
|
||||||
|
pub(crate) const Show: Show = Show {};
|
||||||
|
fn boxed_show(
|
||||||
|
args: crate::std::Args,
|
||||||
|
) -> std::pin::Pin<
|
||||||
|
Box<
|
||||||
|
dyn std::future::Future<
|
||||||
|
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
Box::pin(show(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::docs::StdLibFn for Show {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"show".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
vec![crate::docs::StdLibFnArg {
|
||||||
|
name: "args".to_string(),
|
||||||
|
type_: "number".to_string(),
|
||||||
|
schema: f64::json_schema(&mut generator),
|
||||||
|
required: true,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
Some(crate::docs::StdLibFnArg {
|
||||||
|
name: "".to_string(),
|
||||||
|
type_: "number".to_string(),
|
||||||
|
schema: f64::json_schema(&mut generator),
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublished(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deprecated(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||||
|
boxed_show
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_show(#[doc = r" The args to do shit to."] args: Box<f64>) -> Box<f64> {
|
||||||
|
args
|
||||||
|
}
|
@ -7,6 +7,18 @@ pub(crate) struct LineTo {}
|
|||||||
#[allow(non_upper_case_globals, missing_docs)]
|
#[allow(non_upper_case_globals, missing_docs)]
|
||||||
#[doc = "Std lib function: lineTo"]
|
#[doc = "Std lib function: lineTo"]
|
||||||
pub(crate) const LineTo: LineTo = LineTo {};
|
pub(crate) const LineTo: LineTo = LineTo {};
|
||||||
|
fn boxed_line_to(
|
||||||
|
args: crate::std::Args,
|
||||||
|
) -> std::pin::Pin<
|
||||||
|
Box<
|
||||||
|
dyn std::future::Future<
|
||||||
|
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
Box::pin(line_to(args))
|
||||||
|
}
|
||||||
|
|
||||||
impl crate::docs::StdLibFn for LineTo {
|
impl crate::docs::StdLibFn for LineTo {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"lineTo".to_string()
|
"lineTo".to_string()
|
||||||
@ -65,7 +77,7 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn std_lib_fn(&self) -> crate::std::StdFn {
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||||
line_to
|
boxed_line_to
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||||
|
@ -7,6 +7,18 @@ pub(crate) struct Min {}
|
|||||||
#[allow(non_upper_case_globals, missing_docs)]
|
#[allow(non_upper_case_globals, missing_docs)]
|
||||||
#[doc = "Std lib function: min"]
|
#[doc = "Std lib function: min"]
|
||||||
pub(crate) const Min: Min = Min {};
|
pub(crate) const Min: Min = Min {};
|
||||||
|
fn boxed_min(
|
||||||
|
args: crate::std::Args,
|
||||||
|
) -> std::pin::Pin<
|
||||||
|
Box<
|
||||||
|
dyn std::future::Future<
|
||||||
|
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
Box::pin(min(args))
|
||||||
|
}
|
||||||
|
|
||||||
impl crate::docs::StdLibFn for Min {
|
impl crate::docs::StdLibFn for Min {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"min".to_string()
|
"min".to_string()
|
||||||
@ -57,7 +69,7 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn std_lib_fn(&self) -> crate::std::StdFn {
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||||
min
|
boxed_min
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user