Compare commits

..

1 Commits

Author SHA1 Message Date
a21c2c50b3 start of url scheme
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-25 14:38:03 -07:00
298 changed files with 2169 additions and 3394 deletions

View File

@ -3,4 +3,3 @@ VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"

View File

@ -27,7 +27,7 @@ jobs:
# Upload the WASM bundle as an artifact
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v2
with:
name: wasm-bundle
path: src/wasm-lib/pkg

View File

@ -13,7 +13,7 @@ on:
# Will checkout the last commit from the default branch (main as of 2023-10-04)
env:
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && contains(github.event.pull_request.title, 'Cut release v') }}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -130,9 +130,7 @@ jobs:
matrix:
os: [macos-14, ubuntu-latest, windows-latest]
env:
# Specific Apple Universal target for macos
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
# Only build executable on linux (no appimage or deb)
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
steps:
- uses: actions/checkout@v4
@ -239,96 +237,6 @@ jobs:
includeDebug: true
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
- name: Build for Mac TestFlight (nightly)
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
shell: bash
run: |
unset APPLE_SIGNING_IDENTITY
unset APPLE_CERTIFICATE
sign_app="3rd Party Mac Developer Application: KittyCAD Inc (${APPLE_TEAM_ID})"
sign_install="3rd Party Mac Developer Installer: KittyCAD Inc (${APPLE_TEAM_ID})"
profile="src-tauri/entitlements/Mac_App_Distribution.provisionprofile"
mkdir -p src-tauri/entitlements
echo -n "${APPLE_STORE_PROVISIONING_PROFILE}" | base64 --decode -o "${profile}"
echo -n "${APPLE_STORE_DISTRIBUTION_CERT}" | base64 --decode -o "dist.cer"
echo -n "${APPLE_STORE_INSTALLER_CERT}" | base64 --decode -o "installer.cer"
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD="password"
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import "dist.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
security import "installer.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
target="universal-apple-darwin"
# Turn off the default target
# We don't want to install the updater for the apple store build
sed -i.bu "s/default =/# default =/" src-tauri/Cargo.toml
rm src-tauri/Cargo.toml.bu
git diff src-tauri/Cargo.toml
yarn tauri build --target "${target}" --verbose --config src-tauri/tauri.app-store.conf.json
app_path="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app"
build_name="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.pkg"
cp_dir="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app/Contents/embedded.provisionprofile"
entitlements="src-tauri/entitlements/app-store.entitlements"
cp "${profile}" "${cp_dir}"
codesign --deep --force -s "${sign_app}" --entitlements "${entitlements}" "${app_path}"
productbuild --component "${app_path}" /Applications/ --sign "${sign_install}" "${build_name}"
# Undo the changes to the Cargo.toml
git checkout src-tauri/Cargo.toml
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_STORE_PROVISIONING_PROFILE: ${{ secrets.APPLE_STORE_PROVISIONING_PROFILE }}
APPLE_STORE_DISTRIBUTION_CERT: ${{ secrets.APPLE_STORE_DISTRIBUTION_CERT }}
APPLE_STORE_INSTALLER_CERT: ${{ secrets.APPLE_STORE_INSTALLER_CERT }}
APPLE_STORE_P12_PASSWORD: ${{ secrets.APPLE_STORE_P12_PASSWORD }}
- name: 'Upload to Mac TestFlight (nightly)'
uses: apple-actions/upload-testflight-build@v1
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
with:
app-path: 'src-tauri/target/universal-apple-darwin/release/bundle/macos/Zoo Modeling App.pkg'
issuer-id: ${{ secrets.APPLE_STORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPLE_STORE_API_KEY_ID }}
api-private-key: ${{ secrets.APPLE_STORE_API_PRIVATE_KEY }}
app-type: osx
- name: Clean up after Mac TestFlight (nightly)
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
shell: bash
run: |
git status
# remove our target builds because we want to make sure the later build
# includes the updater, and that anything we changed with the target
# does not persist
rm -rf src-tauri/target
# Lets get rid of the info.plist for the normal mac builds since its
# being sketchy.
rm src-tauri/Info.plist
# We do this after the apple store because the apple store build is
# specific and we want to overwrite it with the this new build after and
# not upload the apple store build to the public bucket
- name: Build the app (release) and sign
uses: tauri-apps/tauri-action@v0
if: ${{ env.BUILD_RELEASE == 'true' }}
@ -353,10 +261,11 @@ jobs:
with:
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
# TODO: re-enable linux e2e tests when possible
- name: Run e2e tests (linux only)
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
if: false
run: |
cargo install tauri-driver --force
cargo install tauri-driver
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
export VITE_KC_API_BASE_URL
xvfb-run yarn test:e2e:tauri

View File

@ -1,37 +0,0 @@
name: Create Release
on:
push:
branches:
- main
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
if: contains(github.event.head_commit.message, 'Cut release v')
steps:
- uses: actions/github-script@v7
name: Read Cut release PR info and create release
with:
script: |
const { owner, repo } = context.repo
const pulls = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: context.sha,
})
const { title, body } = pulls.data[0]
const version = title.split('Cut release ')[1]
const result = await github.rest.repos.createRelease({
owner,
repo,
body,
tag_name: version,
name: version,
draft: true,
})
console.log(result)

View File

@ -27,7 +27,7 @@ jobs:
- id: filter
name: Check for Rust changes
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@v2
with:
filters: |
rust:
@ -48,37 +48,38 @@ jobs:
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Download Wasm Cache
- name: download wasm
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v3
uses: actions/download-artifact@v2
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: src/wasm-lib/pkg
- name: copy wasm blob
if: needs.check-rust-changes.outputs.rust-changed == 'false'
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
continue-on-error: true
- name: Setup Rust
if: needs.check-rust-changes.outputs.rust-changed == 'true'
uses: dtolnay/rust-toolchain@stable
- name: Cache Wasm (because rust diff)
- name: Setup Rust
if: steps.download-wasm.outcome == 'failure'
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
if: needs.check-rust-changes.outputs.rust-changed == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: OR Cache Wasm (because wasm cache failed)
- name: Cache wasm
if: steps.download-wasm.outcome == 'failure'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Build Wasm (because rust diff)
- name: build wasm
if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm
- name: OR Build Wasm (because wasm cache failed)
- name: build wasm
if: steps.download-wasm.outcome == 'failure'
run: yarn build:wasm
- name: build web
@ -130,6 +131,11 @@ jobs:
name: playwright-report
path: playwright-report/
retention-days: 30
- uses: actions/upload-artifact@v4
if: github.ref == 'refs/heads/main'
with:
name: wasm-bundle
path: src/wasm-lib/pkg
playwright-macos:
timeout-minutes: 60
@ -145,37 +151,38 @@ jobs:
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Download Wasm Cache
- name: download wasm
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v3
continue-on-error: true
uses: actions/download-artifact@v4
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: src/wasm-lib/pkg
continue-on-error: true
- name: copy wasm blob
if: needs.check-rust-changes.outputs.rust-changed == 'false'
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
continue-on-error: true
- name: Setup Rust
if: needs.check-rust-changes.outputs.rust-changed == 'true'
uses: dtolnay/rust-toolchain@stable
- name: Cache Wasm (because rust diff)
- name: Setup Rust
if: steps.download-wasm.outcome == 'failure'
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
if: needs.check-rust-changes.outputs.rust-changed == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: OR Cache Wasm (because wasm cache failed)
- name: Cache wasm
if: steps.download-wasm.outcome == 'failure'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Build Wasm (because rust diff)
- name: build wasm
if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm
- name: OR Build Wasm (because wasm cache failed)
- name: build wasm
if: steps.download-wasm.outcome == 'failure'
run: yarn build:wasm
- name: build web
@ -193,3 +200,8 @@ jobs:
name: playwright-report
path: playwright-report/
retention-days: 30
- uses: actions/upload-artifact@v4
if: github.ref == 'refs/heads/main'
with:
name: wasm-bundle
path: src/wasm-lib/pkg

1
.gitignore vendored
View File

@ -54,4 +54,3 @@ src/**/*.typegen.ts
src-tauri/gen
src/wasm-lib/grackle/stdlib_cube_partial.json
Mac_App_Distribution.provisionprofile

View File

@ -59,10 +59,6 @@ followed by:
```
yarn build:wasm-dev
```
or if you have the gh cli installed
```
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
```
That will build the WASM binary and put in the `public` dir (though gitignored)
@ -72,13 +68,7 @@ finally, to run the web app only, run:
yarn start
```
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens ofcourse, then navigate to localhost:3000 again. Note that navigating to localhost:3000/signin removes your token so you will need to set the token again.
### Development environment variables
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `dev.zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
### Developing in Chrome
## Developing in Chrome
Chrome is in the process of rolling out a new default which
[blocks Third-Party Cookies](https://developer.chrome.com/en/docs/privacy-sandbox/third-party-cookie-phase-out/).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,6 +31,7 @@ layout: manual
* [`fillet`](kcl/fillet)
* [`floor`](kcl/floor)
* [`getEdge`](kcl/getEdge)
* [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform)
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
* [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28338,6 +28338,839 @@
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn('XY')\n |> startProfileAt([0, -10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
]
},
{
"name": "getExtrudeWallTransform",
"summary": "Returns the extrude wall transform.",
"description": "",
"tags": [],
"args": [
{
"name": "surface_name",
"type": "string",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "extrude_group",
"type": "ExtrudeGroup",
"schema": {
"description": "An extrude group is a collection of extrude surfaces.",
"type": "object",
"required": [
"__meta",
"height",
"id",
"position",
"rotation",
"sketchGroupValues",
"value",
"xAxis",
"yAxis",
"zAxis"
],
"properties": {
"__meta": {
"description": "Metadata.",
"type": "array",
"items": {
"description": "Metadata.",
"type": "object",
"required": [
"sourceRange"
],
"properties": {
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
}
},
"endCapId": {
"description": "The id of the extrusion end cap",
"type": "string",
"format": "uuid",
"nullable": true
},
"height": {
"description": "The height of the extrude group.",
"type": "number",
"format": "double"
},
"id": {
"description": "The id of the extrude group.",
"type": "string",
"format": "uuid"
},
"position": {
"description": "The position of the extrude group.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 3,
"minItems": 3
},
"rotation": {
"description": "The rotation of the extrude group.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 4,
"minItems": 4
},
"sketchGroupValues": {
"description": "The sketch group paths.",
"type": "array",
"items": {
"description": "A path.",
"oneOf": [
{
"description": "A path that goes to a point.",
"type": "object",
"required": [
"__geoMeta",
"from",
"name",
"to",
"type"
],
"properties": {
"__geoMeta": {
"description": "Metadata.",
"type": "object",
"required": [
"id",
"sourceRange"
],
"properties": {
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
},
"from": {
"description": "The from point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"name": {
"description": "The name of the path.",
"type": "string"
},
"to": {
"description": "The to point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"ToPoint"
]
}
}
},
{
"description": "A arc that is tangential to the last path segment that goes to a point",
"type": "object",
"required": [
"__geoMeta",
"ccw",
"center",
"from",
"name",
"to",
"type"
],
"properties": {
"__geoMeta": {
"description": "Metadata.",
"type": "object",
"required": [
"id",
"sourceRange"
],
"properties": {
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
},
"ccw": {
"description": "arc's direction",
"type": "boolean"
},
"center": {
"description": "the arc's center",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"from": {
"description": "The from point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"name": {
"description": "The name of the path.",
"type": "string"
},
"to": {
"description": "The to point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"TangentialArcTo"
]
}
}
},
{
"description": "A arc that is tangential to the last path segment",
"type": "object",
"required": [
"__geoMeta",
"from",
"name",
"to",
"type"
],
"properties": {
"__geoMeta": {
"description": "Metadata.",
"type": "object",
"required": [
"id",
"sourceRange"
],
"properties": {
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
},
"from": {
"description": "The from point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"name": {
"description": "The name of the path.",
"type": "string"
},
"to": {
"description": "The to point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"TangentialArc"
]
}
}
},
{
"description": "A path that is horizontal.",
"type": "object",
"required": [
"__geoMeta",
"from",
"name",
"to",
"type",
"x"
],
"properties": {
"__geoMeta": {
"description": "Metadata.",
"type": "object",
"required": [
"id",
"sourceRange"
],
"properties": {
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
},
"from": {
"description": "The from point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"name": {
"description": "The name of the path.",
"type": "string"
},
"to": {
"description": "The to point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"Horizontal"
]
},
"x": {
"description": "The x coordinate.",
"type": "number",
"format": "double"
}
}
},
{
"description": "An angled line to.",
"type": "object",
"required": [
"__geoMeta",
"from",
"name",
"to",
"type"
],
"properties": {
"__geoMeta": {
"description": "Metadata.",
"type": "object",
"required": [
"id",
"sourceRange"
],
"properties": {
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
},
"from": {
"description": "The from point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"name": {
"description": "The name of the path.",
"type": "string"
},
"to": {
"description": "The to point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"AngledLineTo"
]
},
"x": {
"description": "The x coordinate.",
"type": "number",
"format": "double",
"nullable": true
},
"y": {
"description": "The y coordinate.",
"type": "number",
"format": "double",
"nullable": true
}
}
},
{
"description": "A base path.",
"type": "object",
"required": [
"__geoMeta",
"from",
"name",
"to",
"type"
],
"properties": {
"__geoMeta": {
"description": "Metadata.",
"type": "object",
"required": [
"id",
"sourceRange"
],
"properties": {
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
},
"from": {
"description": "The from point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"name": {
"description": "The name of the path.",
"type": "string"
},
"to": {
"description": "The to point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"Base"
]
}
}
}
]
}
},
"startCapId": {
"description": "The id of the extrusion start cap",
"type": "string",
"format": "uuid",
"nullable": true
},
"value": {
"description": "The extrude surfaces.",
"type": "array",
"items": {
"description": "An extrude surface.",
"oneOf": [
{
"description": "An extrude plane.",
"type": "object",
"required": [
"faceId",
"id",
"name",
"position",
"rotation",
"sourceRange",
"type"
],
"properties": {
"faceId": {
"description": "The face id for the extrude plane.",
"type": "string",
"format": "uuid"
},
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"name": {
"description": "The name.",
"type": "string"
},
"position": {
"description": "The position.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 3,
"minItems": 3
},
"rotation": {
"description": "The rotation.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 4,
"minItems": 4
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"extrudePlane"
]
}
}
},
{
"description": "An extruded arc.",
"type": "object",
"required": [
"faceId",
"id",
"name",
"position",
"rotation",
"sourceRange",
"type"
],
"properties": {
"faceId": {
"description": "The face id for the extrude plane.",
"type": "string",
"format": "uuid"
},
"id": {
"description": "The id of the geometry.",
"type": "string",
"format": "uuid"
},
"name": {
"description": "The name.",
"type": "string"
},
"position": {
"description": "The position.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 3,
"minItems": 3
},
"rotation": {
"description": "The rotation.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 4,
"minItems": 4
},
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
},
"type": {
"type": "string",
"enum": [
"extrudeArc"
]
}
}
}
]
}
},
"xAxis": {
"description": "The x-axis of the extrude group base plane in the 3D space",
"type": "object",
"required": [
"x",
"y",
"z"
],
"properties": {
"x": {
"type": "number",
"format": "double"
},
"y": {
"type": "number",
"format": "double"
},
"z": {
"type": "number",
"format": "double"
}
}
},
"yAxis": {
"description": "The y-axis of the extrude group base plane in the 3D space",
"type": "object",
"required": [
"x",
"y",
"z"
],
"properties": {
"x": {
"type": "number",
"format": "double"
},
"y": {
"type": "number",
"format": "double"
},
"z": {
"type": "number",
"format": "double"
}
}
},
"zAxis": {
"description": "The z-axis of the extrude group base plane in the 3D space",
"type": "object",
"required": [
"x",
"y",
"z"
],
"properties": {
"x": {
"type": "number",
"format": "double"
},
"y": {
"type": "number",
"format": "double"
},
"z": {
"type": "number",
"format": "double"
}
}
}
}
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "ExtrudeTransform",
"schema": {
"type": "object",
"required": [
"__meta",
"position",
"rotation"
],
"properties": {
"__meta": {
"type": "array",
"items": {
"description": "Metadata.",
"type": "object",
"required": [
"sourceRange"
],
"properties": {
"sourceRange": {
"description": "The source range.",
"type": "array",
"items": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"maxItems": 2,
"minItems": 2
}
}
}
},
"position": {
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 3,
"minItems": 3
},
"rotation": {
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 4,
"minItems": 4
}
}
},
"required": true
},
"unpublished": false,
"deprecated": false,
"examples": [
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, \"surface\")\n |> close(%)\n |> extrude(5, %)\n\nconst transform = getExtrudeWallTransform('surface', box)"
]
},
{
"name": "getNextAdjacentEdge",
"summary": "Get the next adjacent edge to the edge given.",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { makeTemplate, getUtils } from './test-utils'
import { getUtils } from './test-utils'
import waitOn from 'wait-on'
import { roundOff } from 'lib/utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
@ -8,11 +8,9 @@ import {
TEST_SETTINGS,
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_START,
TEST_SETTINGS_ONBOARDING,
} from './storageStates'
import * as TOML from '@iarna/toml'
import { Coords2d } from 'lang/std/sketch'
/*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@ -131,7 +129,6 @@ test('Basic sketch', async ({ page }) => {
// selected two lines therefore there should be two cursors
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.getByRole('button', { name: 'Constrain' }).click()
await page.getByRole('button', { name: 'Equal Length' }).click()
await expect(page.locator('.cm-content'))
@ -265,88 +262,6 @@ test('Can moving camera', async ({ page, context }) => {
}, [1, -94, -94])
})
test('if you click the format button it formats your code', async ({
page,
}) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await page.click('.cm-content')
await page.keyboard.type(`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
await page.click('#code-pane button:first-child')
await page.click('button:has-text("Format code")')
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
})
test('if you use the format keyboard binding it formats your code', async ({
page,
}) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`
)
})
await page.setViewportSize({ width: 1000, height: 500 })
const lspStartPromise = page.waitForEvent('console', async (message) => {
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
// but that doesn't seem to make it to the console for macos/safari :(
if (message.text().includes('start kcl lsp')) {
await new Promise((resolve) => setTimeout(resolve, 200))
return true
}
return false
})
await page.goto('/')
await u.waitForAuthSkipAppStart()
await lspStartPromise
// check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// focus the editor
await page.click('.cm-line')
// Hit alt+shift+f to format the code
await page.keyboard.press('Alt+Shift+KeyF')
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
})
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 })
@ -363,7 +278,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
const bottomAng = 25
*/
await page.click('.cm-content')
await page.keyboard.type('$ error')
await page.keyboard.type('# error')
// press arrows to clear autocomplete
await page.keyboard.press('ArrowLeft')
@ -380,10 +295,10 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText("found unknown token '$'")).toBeVisible()
await expect(page.getByText("found unknown token '#'")).toBeVisible()
// select the line that's causing the error and delete it
await page.getByText('$ error').click()
await page.getByText('# error').click()
await page.keyboard.press('End')
await page.keyboard.down('Shift')
await page.keyboard.press('Home')
@ -765,45 +680,6 @@ test('Project settings can be set and override user settings', async ({
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
})
test('Click through each onboarding step', async ({ page }) => {
const u = getUtils(page)
// Override beforeEach test setup
await page.addInitScript(
async ({ settingsKey, settings }) => {
// Give no initial code, so that the onboarding start is shown immediately
localStorage.setItem('persistCode', '')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
}
)
await page.setViewportSize({ width: 1200, height: 1080 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
while ((await nextButton.innerText()) !== 'Finish') {
await expect(nextButton).toBeVisible()
await nextButton.click()
}
// Finish the onboarding
await expect(nextButton).toBeVisible()
await nextButton.click()
// Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect(page.url()).not.toContain('onboarding')
})
test('Onboarding redirects and code updating', async ({ page }) => {
const u = getUtils(page)
@ -816,7 +692,7 @@ test('Onboarding redirects and code updating', async ({ page }) => {
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
}
)
@ -941,14 +817,11 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick()
await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', { name: 'Constrain' })
const absYButton = page.getByRole('button', { name: 'ABS Y' })
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled()
@ -958,14 +831,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
@ -974,12 +845,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// check the same selection again by putting cursor in code first then selecting axis
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
@ -1041,8 +910,9 @@ test.describe('Command bar tests', () => {
let cmdSearchBar = page.getByPlaceholder('Search commands')
// First try opening the command bar and closing it
// It has a different label on mac and windows/linux, "Meta+K" and "Ctrl+/" respectively
await page
.getByRole('button', { name: 'Commands', exact: false })
.getByRole('button', { name: 'Ctrl+/' })
.or(page.getByRole('button', { name: '⌘K' }))
.click()
await expect(cmdSearchBar).toBeVisible()
@ -1087,13 +957,13 @@ test.describe('Command bar tests', () => {
localStorage.setItem(
'persistCode',
`const distance = sqrt(20)
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 10.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -20.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
`
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
`
)
})
@ -1110,6 +980,7 @@ test.describe('Command bar tests', () => {
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await u.clearCommandLogs()
await page.getByText('|> line([0.73, -14.93], %)').click()
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
let cmdSearchBar = page.getByPlaceholder('Search commands')
@ -1119,12 +990,6 @@ test.describe('Command bar tests', () => {
// Search for extrude command and choose it
await page.getByRole('option', { name: 'Extrude' }).click()
// Assert that we're on the selection step
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
// Select a face
await page.mouse.move(700, 200)
await page.mouse.click(700, 200)
// Assert that we're on the distance step
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
@ -1158,9 +1023,9 @@ test.describe('Command bar tests', () => {
`const distance = sqrt(20)
const distance001 = 5 + 7
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 10.98], %)
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -20.93], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
@ -1351,72 +1216,6 @@ test('ProgramMemory can be serialised', async ({ page }) => {
})
})
test('Hovering over 3d features highlights code', async ({ page }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
|> startProfileAt([20, 0], %)
|> line([7.13, 4 + 0], %)
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|> lineTo([20.14 + 0, -0.14 + 0], %)
|> xLineTo(29 + 0, %)
|> yLine(-3.14 + 0, %, 'a')
|> xLine(1.63, %)
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|> angledLineThatIntersects({
angle: 3.14,
intersectTag: 'a',
offset: 0
}, %)
|> tangentialArcTo([13.14 + 0, 13.14], %)
|> close(%)
|> extrude(5 + 7, %)
`
)
})
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
const extrusionTop: Coords2d = [800, 240]
const flatExtrusionFace: Coords2d = [960, 160]
const arc: Coords2d = [840, 160]
const close: Coords2d = [720, 200]
const nothing: Coords2d = [600, 200]
await page.mouse.move(nothing[0], nothing[1])
await page.mouse.click(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
await page.mouse.move(extrusionTop[0], extrusionTop[1])
await expect(page.getByTestId('hover-highlight')).toBeVisible()
await page.mouse.move(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(arc[0], arc[1])
await expect(page.getByTestId('hover-highlight')).toBeVisible()
await page.mouse.move(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(close[0], close[1])
await expect(page.getByTestId('hover-highlight')).toBeVisible()
await page.mouse.move(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
await page.mouse.move(nothing[0], nothing[1])
await page.waitForTimeout(100)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
})
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
page,
}) => {
@ -1551,7 +1350,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(600)
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
@ -1850,13 +1649,14 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
const result = makeTemplate`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${[2.28, 2.35]}, -${0.07}], %)
|> line([-3.05, -1.47], %)
|> close(%)`
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
}], %)
|> line([-3.05, -1.47], %)
|> close(%)`)
// exit sketch
await u.openAndClearDebugPanel()
@ -1875,9 +1675,15 @@ test('Sketch on face', async ({ page }) => {
await expect(page.getByText('Confirm Extrude')).toBeVisible()
await page.keyboard.press('Enter')
const result2 = result.genNext`
|> extrude(${[5, 5]} + 7, %)`
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
}], %)
|> line([-3.05, -1.47], %)
|> close(%)
|> extrude(5 + 7, %)`)
})
test('Can code mod a line length', async ({ page }) => {
@ -1913,7 +1719,6 @@ test('Can code mod a line length', async ({ page }) => {
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 102)
await page.getByRole('button', { name: 'Constrain', exact: true }).click()
await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByText('Add constraining value').click()

View File

@ -507,7 +507,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
await u.closeDebugPanel()
const startXPx = 600
@ -597,15 +597,12 @@ test.describe('Client side scene scale should match engine scale', () => {
// exit sketch
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
200
)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(300)
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
@ -699,15 +696,12 @@ test.describe('Client side scene scale should match engine scale', () => {
// exit sketch
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
200
)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(300)
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -22,16 +22,11 @@ export const TEST_SETTINGS = {
},
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_ONBOARDING_EXPORT = {
export const TEST_SETTINGS_ONBOARDING = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export' },
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_ONBOARDING_START = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: '' },
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_CORRUPTED = {
app: {
theme: Themes.Dark,

View File

@ -182,76 +182,3 @@ export function getUtils(page: Page) {
}),
}
}
type TemplateOptions = Array<number | Array<number>>
type makeTemplateReturn = {
regExp: RegExp
genNext: (
templateParts: TemplateStringsArray,
...options: TemplateOptions
) => makeTemplateReturn
}
const escapeRegExp = (string: string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
const _makeTemplate = (
templateParts: TemplateStringsArray,
...options: TemplateOptions
) => {
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
let reExpTemplate = ''
for (let i = 0; i < length; i++) {
const currentStr = templateParts.map((str, index) => {
const currentOptions = options[index]
return (
escapeRegExp(str) +
String(
Array.isArray(currentOptions)
? currentOptions[i]
: typeof currentOptions === 'number'
? currentOptions
: ''
)
)
})
reExpTemplate += '|' + currentStr.join('')
}
return new RegExp(reExpTemplate)
}
/**
* Tool for making templates to match code snippets in the editor with some fudge factor,
* as there's some level of non-determinism.
*
* Usage is as such:
* ```typescript
* const result = makeTemplate`const myVar = aFunc(${[1, 2, 3]})`
* await expect(page.locator('.cm-content')).toHaveText(result.regExp)
* ```
* Where the value `1`, `2` or `3` are all valid and should make the test pass.
*
* The function also has a `genNext` function that allows you to chain multiple templates
* together without having to repeat previous parts of the template.
* ```typescript
* const result2 = result.genNext`const myVar2 = aFunc(${[4, 5, 6]})`
* ```
*/
export const makeTemplate: (
templateParts: TemplateStringsArray,
...values: TemplateOptions
) => makeTemplateReturn = (templateParts, ...options) => {
return {
regExp: _makeTemplate(templateParts, ...options),
genNext: (
nextTemplateParts: TemplateStringsArray,
...nextOptions: TemplateOptions
) =>
makeTemplate(
[...templateParts, ...nextTemplateParts] as any as TemplateStringsArray,
[...options, ...nextOptions] as any
),
}
}

View File

@ -2,7 +2,7 @@ import { browser, $, expect } from '@wdio/globals'
import fs from 'fs/promises'
const documentsDir = `${process.env.HOME}/Documents`
const userSettingsDir = `${process.env.HOME}/.config/dev.zoo.modeling-app`
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
const newProjectDir = `${documentsDir}/a-different-directory`
const userCodeDir = '/tmp/kittycad_user_code'
@ -29,10 +29,8 @@ describe('ZMA (Tauri, Linux)', () => {
// Clean up filesystem from previous tests
await new Promise((resolve) => setTimeout(resolve, 100))
await fs.rm(defaultProjectDir, { force: true, recursive: true })
await fs.rm(newProjectDir, { force: true, recursive: true })
await fs.rm(userCodeDir, { force: true })
await fs.rm(userSettingsDir, { force: true, recursive: true })
await fs.mkdir(defaultProjectDir, { recursive: true })
await fs.rm(userSettingsFile, { force: true })
await fs.mkdir(newProjectDir, { recursive: true })
const signInButton = await $('[data-testid="sign-in-button"]')
@ -72,7 +70,6 @@ describe('ZMA (Tauri, Linux)', () => {
console.log(cr.status)
// Now should be signed in
await new Promise((resolve) => setTimeout(resolve, 10000))
const newFileButton = await $('[data-testid="home-new-file"]')
expect(await newFileButton.getText()).toEqual('New project')
})
@ -120,8 +117,8 @@ describe('ZMA (Tauri, Linux)', () => {
it('opens the new file and expects a loading stream', async () => {
const projectLink = await $('[data-testid="project-link"]')
await click(projectLink)
const errorText = await $('[data-testid="unexpected-error"]')
expect(await errorText.getText()).toContain('unexpected error')
const loadingText = await $('[data-testid="loading-stream"]')
expect(await loadingText.getText()).toContain('Loading stream...')
await browser.execute('window.location.href = "tauri://localhost/home"')
})

View File

@ -1,24 +0,0 @@
#!/bin/bash
# Set the repository owner and name
REPO_OWNER="KittyCAD"
REPO_NAME="modeling-app"
WORKFLOW_NAME="build-and-store-wasm.yml"
ARTIFACT_NAME="wasm-bundle"
# Fetch the latest completed workflow run ID for the specified workflow
# RUN_ID=$(gh api repos/$REPO_OWNER/$REPO_NAME/actions/workflows/$WORKFLOW_NAME/runs --paginate --jq '.workflow_runs[] | select(.status=="completed") | .id' | head -n 1)
RUN_ID=$(gh api repos/$REPO_OWNER/$REPO_NAME/actions/workflows/$WORKFLOW_NAME/runs --paginate --jq '.workflow_runs[] | select(.status=="completed" and .conclusion=="success") | .id' | head -n 1)
echo $RUN_ID
# Check if a valid RUN_ID was found
if [ -z "$RUN_ID" ]; then
echo "Failed to find a workflow run for $WORKFLOW_NAME."
exit 1
fi
gh run download $RUN_ID --repo $REPO_OWNER/$REPO_NAME --name $ARTIFACT_NAME --dir ./src/wasm-lib/pkg
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
echo "latest wasm copied to public folder"

View File

@ -15,7 +15,7 @@
<script
defer
data-domain="app.zoo.dev"
src="https://plausible.corp.zoo.dev/js/script.tagged-events.js"
src="https://plausible.corp.zoo.dev/js/script.js"
></script>
<title>Zoo Modeling App</title>
</head>

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.21.1",
"version": "0.19.3",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.16.0",
@ -10,7 +10,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.60",
"@kittycad/lib": "^0.0.58",
"@lezer/javascript": "^1.4.9",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
@ -86,7 +86,6 @@
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
@ -123,7 +122,6 @@
"@tauri-apps/cli": "^2.0.0-beta.13",
"@types/crypto-js": "^4.2.2",
"@types/debounce-promise": "^3.1.9",
"@types/mocha": "^10.0.6",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3",

View File

@ -27,7 +27,7 @@ export default defineConfig({
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'retain-on-failure',
trace: 'on-first-retry',
},
/* Configure projects for major browsers */

View File

@ -1,15 +0,0 @@
{
"applinks": {
"details": [
{
"appIDs": ["92H8YB3B95.dev.zoo.modeling-app"],
"components": [
{
"/": "/file/*",
"comment": "Matches any URL whose path starts with /file/"
}
]
}
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 KiB

After

Width:  |  Height:  |  Size: 142 KiB

342
src-tauri/Cargo.lock generated
View File

@ -38,17 +38,6 @@ dependencies = [
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.14",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.11"
@ -92,24 +81,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_log-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
[[package]]
name = "android_logger"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f"
dependencies = [
"android_log-sys",
"env_logger",
"log",
"once_cell",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -183,24 +154,20 @@ dependencies = [
"anyhow",
"kcl-lib",
"kittycad",
"log",
"oauth2",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-cli",
"tauri-plugin-deep-link",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-http",
"tauri-plugin-log",
"tauri-plugin-os",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-updater",
"tokio",
"toml 0.8.12",
"url",
]
[[package]]
@ -212,12 +179,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "ashpd"
version = "0.8.1"
@ -328,9 +289,9 @@ dependencies = [
[[package]]
name = "async-recursion"
version = "1.1.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
dependencies = [
"proc-macro2",
"quote",
@ -453,9 +414,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
[[package]]
name = "base64ct"
@ -544,30 +505,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "borsh"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77"
dependencies = [
"borsh-derive",
"cfg_aliases 0.1.1",
]
[[package]]
name = "borsh-derive"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d"
dependencies = [
"once_cell",
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn_derive",
]
[[package]]
name = "brotli"
version = "3.5.0"
@ -595,7 +532,7 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d43b38e074cc0de2957f10947e376a1d88b9c4dbab340b590800cc1b2e066b2"
dependencies = [
"ahash 0.8.11",
"ahash",
"base64 0.13.1",
"bitvec",
"chrono",
@ -617,39 +554,6 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byte-unit"
version = "5.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e"
dependencies = [
"rust_decimal",
"serde",
"utf8-width",
]
[[package]]
name = "bytecheck"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
dependencies = [
"bytecheck_derive",
"ptr_meta",
"simdutf8",
]
[[package]]
name = "bytecheck_derive"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bytemuck"
version = "1.15.0"
@ -1388,16 +1292,6 @@ dependencies = [
"syn 2.0.60",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"log",
"regex",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -1471,15 +1365,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "fern"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
dependencies = [
"log",
]
[[package]]
name = "field-offset"
version = "0.3.6"
@ -2077,9 +1962,6 @@ name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.8",
]
[[package]]
name = "hashbrown"
@ -2536,13 +2418,13 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.54"
version = "0.1.53"
dependencies = [
"anyhow",
"approx",
"async-recursion",
"async-trait",
"base64 0.22.1",
"base64 0.22.0",
"bson",
"chrono",
"clap",
@ -2595,9 +2477,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.3.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6e12eb45fd9a28c8e99dbdef54556246b39acee14e4aa6f0fc43636caa62d9"
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
dependencies = [
"anyhow",
"async-trait",
@ -2774,7 +2656,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
dependencies = [
"serde",
"value-bag",
]
[[package]]
@ -3087,15 +2968,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "oauth2"
version = "4.4.2"
@ -3745,26 +3617,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "quick-xml"
version = "0.31.0"
@ -3982,15 +3834,6 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "rend"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
dependencies = [
"bytecheck",
]
[[package]]
name = "reqwest"
version = "0.11.27"
@ -4041,7 +3884,7 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
dependencies = [
"base64 0.22.1",
"base64 0.22.0",
"bytes",
"encoding_rs",
"futures-core",
@ -4199,35 +4042,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rkyv"
version = "0.7.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0"
dependencies = [
"bitvec",
"bytecheck",
"bytes",
"hashbrown 0.12.3",
"ptr_meta",
"rend",
"rkyv_derive",
"seahash",
"tinyvec",
"uuid",
]
[[package]]
name = "rkyv_derive"
version = "0.7.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "ropey"
version = "1.6.1"
@ -4238,22 +4052,6 @@ dependencies = [
"str_indices",
]
[[package]]
name = "rust_decimal"
version = "1.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a"
dependencies = [
"arrayvec",
"borsh",
"bytes",
"num-traits",
"rand 0.8.5",
"rkyv",
"serde",
"serde_json",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -4336,7 +4134,7 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
dependencies = [
"base64 0.22.1",
"base64 0.22.0",
"rustls-pki-types",
]
@ -4399,9 +4197,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.17"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309"
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
dependencies = [
"bigdecimal",
"bytes",
@ -4417,14 +4215,14 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.17"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108"
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.60",
"syn 1.0.109",
]
[[package]]
@ -4449,12 +4247,6 @@ dependencies = [
"untrusted",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "2.10.0"
@ -4509,9 +4301,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.200"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [
"serde_derive",
]
@ -4527,9 +4319,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.200"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [
"proc-macro2",
"quote",
@ -4538,13 +4330,13 @@ dependencies = [
[[package]]
name = "serde_derive_internals"
version = "0.29.0"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 1.0.109",
]
[[package]]
@ -4743,12 +4535,6 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simdutf8"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
[[package]]
name = "siphasher"
version = "0.3.11"
@ -5006,18 +4792,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
@ -5162,9 +4936,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "2.0.0-beta.17"
version = "2.0.0-beta.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fedd5490eddf117253945f0baedafded43474c971cba546a818f527d5c26266"
checksum = "4d411ebb670bbe5cf948f6c24978632937329748b499de1619ab55ad31512652"
dependencies = [
"anyhow",
"bytes",
@ -5176,7 +4950,7 @@ dependencies = [
"getrandom 0.2.14",
"glob",
"gtk",
"heck 0.5.0",
"heck 0.4.1",
"http 1.1.0",
"jni",
"libc",
@ -5237,7 +5011,7 @@ version = "2.0.0-beta.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b383f341efb803852b0235a2f330ca90c4c113f422dd6d646b888685b372cace"
dependencies = [
"base64 0.22.1",
"base64 0.22.0",
"brotli",
"ico",
"json-patch",
@ -5304,21 +5078,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "tauri-plugin-deep-link"
version = "2.0.0-beta.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a1aee2af6aec05ace816d46da0b0c0bdb4fcd0c985c0f14634a50c860824435"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
"url",
]
[[package]]
name = "tauri-plugin-dialog"
version = "2.0.0-beta.6"
@ -5377,27 +5136,6 @@ dependencies = [
"urlpattern",
]
[[package]]
name = "tauri-plugin-log"
version = "2.0.0-beta.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97718db0d981b03b7b1257c22f699ff46639220c5acb4510ac9696437afc93f8"
dependencies = [
"android_logger",
"byte-unit",
"cocoa",
"fern",
"log",
"objc",
"serde",
"serde_json",
"serde_repr",
"swift-rs",
"tauri",
"tauri-plugin",
"time",
]
[[package]]
name = "tauri-plugin-os"
version = "2.0.0-beta.3"
@ -5452,7 +5190,7 @@ version = "2.0.0-beta.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f34be6851c7e84ca99b3bddd57e033d55d8bfca1dd153d6e8d18e9f1fb95469"
dependencies = [
"base64 0.22.1",
"base64 0.22.0",
"dirs-next",
"flate2",
"futures-util",
@ -5476,9 +5214,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.0.0-beta.14"
version = "2.0.0-beta.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "148b6e6aff8e63fe5d4ae1d50159d50cfc0b4309abdeca64833c887c6b5631ef"
checksum = "c7439729d0107c9797764919c39c4a4cc3af64306faaa48271da50d8eb4c0283"
dependencies = [
"dpi",
"gtk",
@ -5495,9 +5233,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.0.0-beta.14"
version = "2.0.0-beta.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "398d065c6e0fbf3c4304583759b6e153bc1e0daeb033bede6834ebe4df371fc3"
checksum = "0c38dcfa7f8c2b2e344c7401972e0ddaaec4fa655666788d94b1852d6c4a7fe8"
dependencies = [
"cocoa",
"gtk",
@ -5638,9 +5376,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa 1.0.11",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
@ -6210,12 +5946,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
[[package]]
name = "utf8parse"
version = "0.2.1"
@ -6270,12 +6000,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]]
name = "version-compare"
version = "0.2.0"
@ -6922,7 +6646,7 @@ version = "0.39.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e180ac2740d6cb4d5cec0abf63eacbea90f1b7e5e3803043b13c1c84c4b7884"
dependencies = [
"base64 0.22.1",
"base64 0.22.0",
"block",
"cocoa",
"core-graphics",

View File

@ -1,8 +1,8 @@
[package]
name = "app"
version = "0.1.0"
description = "The Zoo Modeling App"
authors = ["Zoo Engineers <eng@zoo.dev>"]
description = "A Tauri App"
authors = ["you"]
license = ""
repository = "https://github.com/KittyCAD/modeling-app"
default-run = "app"
@ -17,28 +17,22 @@ tauri-build = { version = "2.0.0-beta.13", features = [] }
anyhow = "1"
kcl-lib = { version = "0.1.53", path = "../src/wasm-lib/kcl" }
kittycad = "0.3.0"
log = "0.4.21"
oauth2 = "4.4.2"
serde_json = "1.0"
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
tauri-plugin-cli = { version = "2.0.0-beta.3" }
tauri-plugin-deep-link = { version = "2.0.0-beta.3" }
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
tauri-plugin-fs = { version = "2.0.0-beta.6" }
tauri-plugin-http = { version = "2.0.0-beta.6" }
tauri-plugin-log = { version = "2.0.0-beta.4" }
tauri-plugin-os = { version = "2.0.0-beta.2" }
tauri-plugin-process = { version = "2.0.0-beta.2" }
tauri-plugin-shell = { version = "2.0.0-beta.2" }
tauri-plugin-updater = { version = "2.0.0-beta.4" }
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
tokio = { version = "1.37.0", features = ["time", "fs"] }
toml = "0.8.2"
url = "2.5.0"
[features]
default = ["updater"]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
updater = []

View File

@ -1,376 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>NSDesktopFolderUsageDescription</key>
<string>Zoo Modeling App accesses the Desktop to load and save your project files and/or exported files here</string>
<key>NSDocumentsFolderUsageDescription</key>
<string>Zoo Modeling App accesses the Documents folder to load and save your project files and/or exported files here</string>
<key>NSDownloadsFolderUsageDescription</key>
<string>Zoo Modeling App accesses the Downloads folder to load and save your project files and/or exported files here</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>DTXcode</key>
<string>1501</string>
<key>DTXcodeBuild</key>
<string>15A507</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>dev.zoo.modeling-app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>zoo-modeling-app</string>
<string>zoo</string>
</array>
</dict>
</array>
<key>LSFileQuarantineEnabled</key>
<false/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.kcl</string>
</array>
<key>CFBundleTypeName</key>
<string>KCL</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.toml</string>
</array>
<key>CFBundleTypeName</key>
<string>TOML</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.gltf</string>
</array>
<key>CFBundleTypeName</key>
<string>glTF</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.glb</string>
</array>
<key>CFBundleTypeName</key>
<string>glb</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.step</string>
</array>
<key>CFBundleTypeName</key>
<string>STEP</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.fbx</string>
</array>
<key>CFBundleTypeName</key>
<string>FBX</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.sldprt</string>
</array>
<key>CFBundleTypeName</key>
<string>Solidworks Part</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.geometry-definition-format</string>
</array>
<key>CFBundleTypeName</key>
<string>OBJ</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.polygon-file-format</string>
</array>
<key>CFBundleTypeName</key>
<string>PLY</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.standard-tesselated-geometry-format</string>
</array>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.folder</string>
</array>
<key>CFBundleTypeName</key>
<string>Folders</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.kcl</string>
<key>UTTypeReferenceURL</key>
<string>https://zoo.dev/docs/kcl</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.source-code</string>
<string>public.data</string>
<string>public.text</string>
<string>public.plain-text</string>
<string>public.3d-content</string>
<string>public.script</string>
</array>
<key>UTTypeDescription</key>
<string>KCL (KittyCAD Language) document</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>kcl</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/vnd.zoo.kcl</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.gltf</string>
<key>UTTypeReferenceURL</key>
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.text</string>
<string>public.plain-text</string>
<string>public.3d-content</string>
<string>public.json</string>
</array>
<key>UTTypeDescription</key>
<string>Graphics Library Transmission Format (glTF)</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gltf</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/gltf+json</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.glb</string>
<key>UTTypeReferenceURL</key>
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
</array>
<key>UTTypeDescription</key>
<string>Graphics Library Transmission Format (glTF) binary</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>glb</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/gltf-binary</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.step</string>
<key>UTTypeReferenceURL</key>
<string>https://www.loc.gov/preservation/digital/formats/fdd/fdd000448.shtml</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
<string>public.text</string>
<string>public.plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>STEP-file, ISO 10303-21</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>step</string>
<string>stp</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/step</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.sldprt</string>
<key>UTTypeReferenceURL</key>
<string>https://docs.fileformat.com/cad/sldprt/</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
</array>
<key>UTTypeDescription</key>
<string>Solidworks Part</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>sldprt</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/vnd.solidworks.sldprt</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.fbx</string>
<key>UTTypeReferenceURL</key>
<string>https://en.wikipedia.org/wiki/FBX</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
</array>
<key>UTTypeDescription</key>
<string>Autodesk Filmbox (FBX) format</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>fbx</string>
<string>fbxb</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/vnd.autodesk.fbx</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.toml</string>
<key>UTTypeReferenceURL</key>
<string>https://toml.io/en/</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.text</string>
<string>public.plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Tom's Obvious Minimal Language</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>kcl</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/toml</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

View File

@ -8,8 +8,6 @@
],
"permissions": [
"cli:default",
"deep-link:default",
"log:default",
"path:default",
"event:default",
"window:default",

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.application-identifier</key>
<string>92H8YB3B95.dev.zoo.modeling-app</string>
<key>com.apple.developer.team-identifier</key>
<string>92H8YB3B95</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:app.zoo.dev</string>
</array>
</dict>
</plist>

View File

@ -6,19 +6,19 @@ pub(crate) mod state;
use std::{
env,
path::{Path, PathBuf},
process::Command,
};
use anyhow::Result;
use kcl_lib::settings::types::{
file::{FileEntry, Project, ProjectRoute, ProjectState},
project::ProjectConfiguration,
Configuration,
Configuration, DEFAULT_PROJECT_KCL_FILE,
};
use oauth2::TokenResponse;
use tauri::{ipc::InvokeError, Manager};
use tauri_plugin_cli::CliExt;
use tauri_plugin_shell::ShellExt;
use tokio::process::Command;
const DEFAULT_HOST: &str = "https://api.zoo.dev";
const SETTINGS_FILE_NAME: &str = "settings.toml";
@ -226,7 +226,7 @@ async fn read_dir_recursive(path: &str) -> Result<FileEntry, InvokeError> {
/// The string returned from this method is the access token.
#[tauri::command]
async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError> {
log::debug!("Logging in...");
println!("Logging in...");
// Do an OAuth 2.0 Device Authorization Grant dance to get a token.
let device_auth_url = oauth2::DeviceAuthorizationUrl::new(format!("{host}/oauth2/device/auth"))
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
@ -265,7 +265,7 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
// and bypass the shell::open call as it fails on GitHub Actions.
let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok();
if e2e_tauri_enabled {
log::warn!("E2E_TAURI_ENABLED is set, won't open {} externally", auth_uri.secret());
println!("E2E_TAURI_ENABLED is set, won't open {} externally", auth_uri.secret());
tokio::fs::write("/tmp/kittycad_user_code", details.user_code().secret())
.await
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
@ -308,7 +308,7 @@ async fn get_user(token: &str, hostname: &str) -> Result<kittycad::types::User,
baseurl = format!("http://{host}")
}
}
log::debug!("Getting user info...");
println!("Getting user info...");
// use kittycad library to fetch the user info from /user/me
let mut client = kittycad::Client::new(token);
@ -334,7 +334,7 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
#[cfg(not(unix))]
{
Command::new("explorer")
.args(["/select,", path]) // The comma after select is not a typo
.args(["/select,", &path]) // The comma after select is not a typo
.spawn()
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
}
@ -342,7 +342,7 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
#[cfg(unix)]
{
Command::new("open")
.args(["-R", path])
.args(["-R", &path])
.spawn()
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
}
@ -350,33 +350,6 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
Ok(())
}
#[allow(dead_code)]
fn open_url_sync(app: &tauri::AppHandle, url: &url::Url) {
log::debug!("Opening URL: {:?}", url);
let cloned_url = url.clone();
let runner: tauri::async_runtime::JoinHandle<Result<ProjectState>> = tauri::async_runtime::spawn(async move {
let url_str = cloned_url.path().to_string();
log::debug!("Opening URL path : {}", url_str);
let path = Path::new(url_str.as_str());
ProjectState::new_from_path(path.to_path_buf()).await
});
// Block on the handle.
match tauri::async_runtime::block_on(runner) {
Ok(Ok(store)) => {
// Create a state object to hold the project.
app.manage(state::Store::new(store));
}
Err(e) => {
log::warn!("Error opening URL:{} {:?}", url, e);
}
Ok(Err(e)) => {
log::warn!("Error opening URL:{} {:?}", url, e);
}
}
}
fn main() -> Result<()> {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
@ -398,22 +371,6 @@ fn main() -> Result<()> {
write_project_settings_file,
])
.plugin(tauri_plugin_cli::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init())
.plugin(
tauri_plugin_log::Builder::new()
.targets([
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir { file_name: None }),
])
.level(log::LevelFilter::Debug)
.build(),
)
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.setup(|app| {
// Do update things.
#[cfg(debug_assertions)]
@ -421,7 +378,6 @@ fn main() -> Result<()> {
app.get_webview("main").unwrap().open_devtools();
}
#[cfg(not(debug_assertions))]
#[cfg(feature = "updater")]
{
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
}
@ -446,7 +402,6 @@ fn main() -> Result<()> {
if let Some(source_arg) = matches.args.get("source") {
// We don't do an else here because this can be null.
if let Some(value) = source_arg.value.as_str() {
log::info!("Got path in cli argument: {}", value);
source_path = Some(Path::new(value).to_path_buf());
}
}
@ -456,10 +411,6 @@ fn main() -> Result<()> {
}
}
if verbose {
log::debug!("Verbose mode enabled.");
}
// If we have a source path to open, make sure it exists.
let Some(source_path) = source_path else {
// The user didn't provide a source path to open.
@ -477,7 +428,64 @@ fn main() -> Result<()> {
}
let runner: tauri::async_runtime::JoinHandle<Result<ProjectState>> =
tauri::async_runtime::spawn(async move { ProjectState::new_from_path(source_path).await });
tauri::async_runtime::spawn(async move {
// Fix for "." path, which is the current directory.
let source_path = if source_path == Path::new(".") {
std::env::current_dir()
.map_err(|e| anyhow::anyhow!("Error getting the current directory: {:?}", e))?
} else {
source_path
};
// If the path is a directory, let's assume it is a project directory.
if source_path.is_dir() {
// Load the details about the project from the path.
let project = Project::from_path(&source_path).await.map_err(|e| {
anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e)
})?;
if verbose {
println!("Project loaded from path: {}", source_path.display());
}
// Create the default file in the project.
// Write the initial project file.
let project_file = source_path.join(DEFAULT_PROJECT_KCL_FILE);
tokio::fs::write(&project_file, vec![]).await?;
return Ok(ProjectState {
project,
current_file: Some(project_file.display().to_string()),
});
}
// We were given a file path, not a directory.
// Let's get the parent directory of the file.
let parent = source_path.parent().ok_or_else(|| {
anyhow::anyhow!(
"Error getting the parent directory of the file: {}",
source_path.display()
)
})?;
// Load the details about the project from the parent directory.
let project = Project::from_path(&parent).await.map_err(|e| {
anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e)
})?;
if verbose {
println!(
"Project loaded from path: {}, current file: {}",
parent.display(),
source_path.display()
);
}
Ok(ProjectState {
project,
current_file: Some(source_path.display().to_string()),
})
});
// Block on the handle.
let store = tauri::async_runtime::block_on(runner)??;
@ -485,31 +493,22 @@ fn main() -> Result<()> {
// Create a state object to hold the project.
app.manage(state::Store::new(store));
// Listen on the deep links.
app.listen("deep-link://new-url", |event| {
log::info!("got deep-link url: {:?}", event);
// TODO: open_url_sync(app.handle(), event.url);
});
Ok(())
})
.build(tauri::generate_context!())?
.run(
#[allow(unused_variables)]
|app, event| {
#[cfg(any(target_os = "macos", target_os = "ios"))]
if let tauri::RunEvent::Opened { urls } = event {
log::info!("Opened URLs: {:?}", urls);
.register_uri_scheme_protocol("zoo-modeling-app", |_app, request| {
let path = request.uri().path();
dbg!(path);
println!("Requesting path: {}", path);
// Handle the first URL.
// TODO: do we want to handle more than one URL?
// Under what conditions would we even have more than one?
if let Some(url) = urls.first() {
open_url_sync(app, url);
}
}
},
);
tauri::http::Response::builder().status(200).body(b"{}").unwrap()
})
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.run(tauri::generate_context!())?;
Ok(())
}

View File

@ -1,8 +0,0 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"bundle": {
"macOS": {
"entitlements": "entitlements/app-store.entitlements"
}
}
}

View File

@ -37,7 +37,13 @@
}
},
"longDescription": "",
"macOS": {},
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all"
@ -54,25 +60,16 @@
},
{
"name": "source",
"description": "The file or directory to open",
"required": false,
"index": 1,
"takesValue": true
}
],
"subcommands": {}
},
"deep-link": {
"domains": [
{
"host": "app.zoo.dev"
}
]
},
"shell": {
"open": true
}
},
"productName": "Zoo Modeling App",
"version": "0.21.1"
"version": "0.19.3"
}

View File

@ -5,45 +5,7 @@
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
},
"fileAssociations": [
{
"ext": ["kcl"],
"mimeType": "text/vnd.zoo.kcl"
},
{
"ext": ["obj"],
"mimeType": "model/obj"
},
{
"ext": ["gltf"],
"mimeType": "model/gltf+json"
},
{
"ext": ["glb"],
"mimeType": "model/gltf+binary"
},
{
"ext": ["fbx", "fbxb"],
"mimeType": "model/fbx"
},
{
"ext": ["stl"],
"mimeType": "model/stl"
},
{
"ext": ["ply"],
"mimeType": "model/ply"
},
{
"ext": ["step", "stp"],
"mimeType": "model/step"
},
{
"ext": ["sldprt"],
"mimeType": "model/sldprt"
}
]
}
},
"plugins": {
"updater": {

View File

@ -4,6 +4,7 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { ActionButton } from 'components/ActionButton'
import usePlatform from 'hooks/usePlatform'
import { isSingleCursorInPipe } from 'lang/queryAst'
import { useKclContext } from 'lang/KclProvider'
import {
@ -11,18 +12,18 @@ import {
useNetworkStatus,
} from 'components/NetworkHealthIndicator'
import { useStore } from 'useStore'
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
export const Toolbar = () => {
const platform = usePlatform()
const { commandBarSend } = useCommandsContext()
const { state, send, context } = useModelingContext()
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
const iconClassName =
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-primary dark:group-enabled:group-hover:!text-inherit group-pressed:!text-chalkboard-10 group-ui-open:!text-chalkboard-10 dark:group-ui-open:!text-chalkboard-10'
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10'
const bgClassName =
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary/10 dark:group-enabled:group-hover:bg-primary group-pressed:bg-primary group-ui-open:bg-primary'
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary group-pressed:bg-primary'
const buttonClassName =
'bg-chalkboard-10 dark:bg-chalkboard-100 enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!border-primary ui-open:!border-primary'
'bg-chalkboard-10 dark:bg-chalkboard-100 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100'
const pathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
return false
@ -58,7 +59,10 @@ export const Toolbar = () => {
{...props}
ref={toolbarButtonsRef}
onWheel={handleToolbarButtonsWheelEvent}
className={'m-0 py-1 rounded-l-sm flex gap-2 items-center ' + className}
className={
'm-0 py-1 rounded-l-sm flex gap-2 items-center overflow-x-auto ' +
className
}
style={{ scrollbarWidth: 'thin' }}
>
{state.nextEvents.includes('Enter sketch') && (
@ -69,7 +73,7 @@ export const Toolbar = () => {
onClick={() =>
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
}
iconStart={{
icon={{
icon: 'sketch',
iconClassName,
bgClassName,
@ -86,7 +90,7 @@ export const Toolbar = () => {
className={buttonClassName}
Element="button"
onClick={() => send({ type: 'Enter sketch' })}
iconStart={{
icon={{
icon: 'sketch',
iconClassName,
bgClassName,
@ -103,7 +107,7 @@ export const Toolbar = () => {
className={buttonClassName}
Element="button"
onClick={() => send({ type: 'Cancel' })}
iconStart={{
icon={{
icon: 'arrowLeft',
iconClassName,
bgClassName,
@ -126,7 +130,7 @@ export const Toolbar = () => {
: send('Equip Line tool')
}
aria-pressed={state?.matches('Sketch.Line tool')}
iconStart={{
icon={{
icon: 'line',
iconClassName,
bgClassName,
@ -146,7 +150,7 @@ export const Toolbar = () => {
: send('Equip tangential arc to')
}
aria-pressed={state.matches('Sketch.Tangential arc to')}
iconStart={{
icon={{
icon: 'arc',
iconClassName,
bgClassName,
@ -170,7 +174,7 @@ export const Toolbar = () => {
: send('Equip rectangle tool')
}
aria-pressed={state.matches('Sketch.Rectangle tool')}
iconStart={{
icon={{
icon: 'rectangle',
iconClassName,
bgClassName,
@ -192,54 +196,52 @@ export const Toolbar = () => {
</>
)}
{state.matches('Sketch.SketchIdle') &&
state.nextEvents.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
).length > 0 && (
<ActionButtonDropdown
splitMenuItems={state.nextEvents
.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
)
.sort((a, b) => {
const aisEnabled = state.nextEvents
.filter((event) => state.can(event as any))
.includes(a)
const bIsEnabled = state.nextEvents
.filter((event) => state.can(event as any))
.includes(b)
if (aisEnabled && !bIsEnabled) {
return -1
}
if (!aisEnabled && bIsEnabled) {
return 1
}
return 0
})
.map((eventName) => ({
label: eventName
.replace('Make segment ', '')
.replace('Constrain ', ''),
onClick: () => send(eventName),
disabled:
state.nextEvents
.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
)
.sort((a, b) => {
const aisEnabled = state.nextEvents
.filter((event) => state.can(event as any))
.includes(a)
const bIsEnabled = state.nextEvents
.filter((event) => state.can(event as any))
.includes(b)
if (aisEnabled && !bIsEnabled) {
return -1
}
if (!aisEnabled && bIsEnabled) {
return 1
}
return 0
})
.map((eventName) => (
<li className="contents" key={eventName}>
<ActionButton
className={buttonClassName}
Element="button"
key={eventName}
onClick={() => send(eventName)}
disabled={
!state.nextEvents
.filter((event) => state.can(event as any))
.includes(eventName) || disableAllButtons,
}))}
className={buttonClassName}
Element="button"
iconStart={{
icon: 'dimension',
iconClassName,
bgClassName,
}}
>
Constrain
</ActionButtonDropdown>
)}
.includes(eventName) || disableAllButtons
}
title={eventName}
icon={{
icon: 'line',
iconClassName,
bgClassName,
}}
>
{eventName
.replace('Make segment ', '')
.replace('Constrain ', '')}
</ActionButton>
</li>
))}
{state.matches('idle') && (
<li className="contents">
<ActionButton
@ -257,7 +259,7 @@ export const Toolbar = () => {
? 'extrude'
: 'sketches need to be closed, or not already extruded'
}
iconStart={{
icon={{
icon: 'extrude',
iconClassName,
bgClassName,
@ -272,8 +274,17 @@ export const Toolbar = () => {
}
return (
<menu className="max-w-full whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
<ToolbarButtons />
</menu>
<div className="max-w-full flex items-stretch rounded-l-sm rounded-r-full bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
<menu className="flex-1 pl-1 pr-2 py-0 overflow-hidden rounded-l-sm whitespace-nowrap border-solid border border-primary/30 dark:border-chalkboard-90 border-r-0">
<ToolbarButtons />
</menu>
<ActionButton
Element="button"
onClick={() => commandBarSend({ type: 'Open' })}
className="rounded-r-full pr-4 self-stretch border-primary/30 hover:border-primary dark:border-chalkboard-80 dark:bg-chalkboard-80 text-primary"
>
{platform === 'macos' ? '⌘K' : 'Ctrl+/'}
</ActionButton>
</div>
)
}

View File

@ -90,8 +90,7 @@ export const ClientSideScene = ({
cursor = 'grabbing'
} else if (
state.matches('Sketch.Line tool') ||
state.matches('Sketch.Tangential arc to') ||
state.matches('Sketch.Rectangle tool')
state.matches('Sketch.Tangential arc to')
) {
cursor = 'crosshair'
} else {
@ -105,9 +104,9 @@ export const ClientSideScene = ({
style={{ cursor: cursor }}
className={`absolute inset-0 h-full w-full transition-all duration-300 ${
hideClient ? 'opacity-0' : 'opacity-100'
} ${hideServer ? 'bg-chalkboard-10 dark:bg-chalkboard-100' : ''} ${
} ${hideServer ? 'bg-black' : ''} ${
!hideClient && !hideServer && state.matches('Sketch')
? 'bg-chalkboard-10/80 dark:bg-chalkboard-100/80'
? 'bg-black/80'
: ''
}`}
></div>

View File

@ -97,7 +97,6 @@ import {
getRectangleCallExpressions,
updateRectangleSketch,
} from 'lib/rectangleTool'
import { getThemeColorForThreeJs } from 'lib/theme'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -357,7 +356,6 @@ export class SceneEntities {
id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode,
scale: factor,
theme: sceneInfra._theme,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
@ -408,7 +406,6 @@ export class SceneEntities {
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
})
} else {
seg = straightSegment({
@ -420,7 +417,6 @@ export class SceneEntities {
scale: factor,
callExpName,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
})
}
seg.layers.set(SKETCH_LAYER)
@ -968,7 +964,7 @@ export class SceneEntities {
if (!draftInfo)
// don't want to mod the user's code yet as they have't committed to the change yet
// plus this would be the truncated ast being recast, it would be wrong
codeManager.updateCodeEditor(code)
codeManager.updateCodeStateEditor(code)
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
@ -1507,10 +1503,7 @@ export class SceneEntities {
const isSelected = parent?.userData?.isSelected
colorSegment(
selected,
isSelected
? 0x0000ff
: parent?.userData?.baseColor ||
getThemeColorForThreeJs(sceneInfra._theme)
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
)
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {

View File

@ -30,7 +30,6 @@ import { CameraControls } from './CameraControls'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { settings } from 'lib/settings/initialSettings'
import { MouseState } from 'machines/modelingMachine'
import { Themes } from 'lib/theme'
type SendType = ReturnType<typeof useModelingContext>['send']
@ -102,7 +101,6 @@ export class SceneInfra {
isFovAnimationInProgress = false
_baseUnit: BaseUnit = 'mm'
_baseUnitMultiplier = 1
_theme: Themes = Themes.System
extraSegmentTexture: Texture
lastMouseState: MouseState = { type: 'idle' }
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
@ -139,9 +137,6 @@ export class SceneInfra {
this._baseUnitMultiplier
)
}
set theme(theme: Themes) {
this._theme = theme
}
resetMouseListeners = () => {
this.setCallbacks({
onDragStart: () => {},

View File

@ -37,26 +37,22 @@ import {
} from './sceneEntities'
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { ARROWHEAD } from './sceneInfra'
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
export function profileStart({
from,
id,
pathToNode,
scale = 1,
theme,
}: {
from: Coords2d
id: string
pathToNode: PathToNode
scale?: number
theme: Themes
}) {
const group = new Group()
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
const baseColor = getThemeColorForThreeJs(theme)
const body = new MeshBasicMaterial({ color: baseColor })
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
group.add(mesh)
@ -83,7 +79,6 @@ export function straightSegment({
scale = 1,
callExpName,
texture,
theme,
}: {
from: Coords2d
to: Coords2d
@ -93,7 +88,6 @@ export function straightSegment({
scale?: number
callExpName: string
texture: Texture
theme: Themes
}): Group {
const group = new Group()
@ -117,8 +111,7 @@ export function straightSegment({
})
}
const baseColor =
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
const baseColor = callExpName === 'close' ? 0x444444 : 0xffffff
const body = new MeshBasicMaterial({ color: baseColor })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
@ -141,7 +134,7 @@ export function straightSegment({
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const arrowGroup = createArrowhead(scale, theme)
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
@ -154,7 +147,7 @@ export function straightSegment({
group.add(mesh)
if (callExpName !== 'close') group.add(arrowGroup)
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
.normalize()
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
@ -169,10 +162,8 @@ export function straightSegment({
return group
}
function createArrowhead(scale = 1, theme: Themes): Group {
const arrowMaterial = new MeshBasicMaterial({
color: getThemeColorForThreeJs(theme),
})
function createArrowhead(scale = 1): Group {
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
// specify the size of the geometry in pixels (i.e. cone height = 20px, cone radius = 4.5px)
// we'll scale the group to the correct size later to match these sizes in screen space
const arrowheadMesh = new Mesh(new ConeGeometry(4.5, 20, 12), arrowMaterial)
@ -188,11 +179,7 @@ function createArrowhead(scale = 1, theme: Themes): Group {
return arrowGroup
}
function createExtraSegmentHandle(
scale: number,
texture: Texture,
theme: Themes
): Group {
function createExtraSegmentHandle(scale: number, texture: Texture): Group {
const particleMaterial = new PointsMaterial({
size: 12, // in pixels
map: texture,
@ -202,7 +189,7 @@ function createExtraSegmentHandle(
})
const mat = new MeshBasicMaterial({
transparent: true,
color: getThemeColorForThreeJs(theme),
color: 0xffffff,
opacity: 0,
})
const particleGeometry = new BufferGeometry().setFromPoints([
@ -231,7 +218,6 @@ export function tangentialArcToSegment({
isDraftSegment,
scale = 1,
texture,
theme,
}: {
prevSegment: SketchGroup['value'][number]
from: Coords2d
@ -241,7 +227,6 @@ export function tangentialArcToSegment({
isDraftSegment?: boolean
scale?: number
texture: Texture
theme: Themes
}): Group {
const group = new Group()
@ -272,8 +257,7 @@ export function tangentialArcToSegment({
scale,
})
const baseColor = getThemeColorForThreeJs(theme)
const body = new MeshBasicMaterial({ color: baseColor })
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? TANGENTIAL_ARC_TO__SEGMENT_DASH
@ -287,11 +271,10 @@ export function tangentialArcToSegment({
prevSegment,
pathToNode,
isSelected: false,
baseColor,
}
group.name = TANGENTIAL_ARC_TO_SEGMENT
const arrowGroup = createArrowhead(scale, theme)
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
@ -302,7 +285,7 @@ export function tangentialArcToSegment({
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
arrowGroup.visible = !shouldHide
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
const circumferenceInPx = (2 * Math.PI * radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2

View File

@ -1,12 +1,11 @@
import { ActionIcon, ActionIconProps } from './ActionIcon'
import React, { ForwardedRef, forwardRef } from 'react'
import React from 'react'
import { paths } from 'lib/paths'
import { Link } from 'react-router-dom'
import type { LinkProps } from 'react-router-dom'
interface BaseActionButtonProps {
iconStart?: ActionIconProps
iconEnd?: ActionIconProps
icon?: ActionIconProps
className?: string
}
@ -33,15 +32,15 @@ type ActionButtonAsElement = BaseActionButtonProps &
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
}
export type ActionButtonProps =
type ActionButtonProps =
| ActionButtonAsButton
| ActionButtonAsLink
| ActionButtonAsExternal
| ActionButtonAsElement
export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
props.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2'
export const ActionButton = (props: ActionButtonProps) => {
const classNames = `action-button m-0 group mono text-sm flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 p-[3px] text-chalkboard-100 dark:text-chalkboard-10 ${
props.icon ? 'pr-2' : 'px-2'
} ${props.className ? props.className : ''}`
switch (props.Element) {
@ -49,23 +48,11 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
// Note we have to destructure 'className' and 'Element' out of props
// because we don't want to pass them to the button element;
// the same is true for the other cases below.
const {
Element,
iconStart,
iconEnd,
children,
className: _className,
...rest
} = props
const { Element, icon, children, className: _className, ...rest } = props
return (
<button
ref={ref as ForwardedRef<HTMLButtonElement>}
className={classNames}
{...rest}
>
{iconStart && <ActionIcon {...iconStart} />}
<button className={classNames} {...rest}>
{props.icon && <ActionIcon {...icon} />}
{children}
{iconEnd && <ActionIcon {...iconEnd} />}
</button>
)
}
@ -73,22 +60,15 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
const {
Element,
to,
iconStart,
iconEnd,
icon,
children,
className: _className,
...rest
} = props
return (
<Link
ref={ref as ForwardedRef<HTMLAnchorElement>}
to={to || paths.INDEX}
className={classNames}
{...rest}
>
{iconStart && <ActionIcon {...iconStart} />}
<Link to={to || paths.INDEX} className={classNames} {...rest}>
{icon && <ActionIcon {...icon} />}
{children}
{iconEnd && <ActionIcon {...iconEnd} />}
</Link>
)
}
@ -96,42 +76,33 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
const {
Element,
to,
iconStart,
iconEnd,
icon,
children,
className: _className,
...rest
} = props
return (
<Link
ref={ref as ForwardedRef<HTMLAnchorElement>}
to={to || paths.INDEX}
className={classNames}
{...rest}
target="_blank"
>
{iconStart && <ActionIcon {...iconStart} />}
{icon && <ActionIcon {...icon} />}
{children}
{iconEnd && <ActionIcon {...iconEnd} />}
</Link>
)
}
default: {
const {
Element,
iconStart,
children,
className: _className,
...rest
} = props
const { Element, icon, children, className: _className, ...rest } = props
if (!Element) throw new Error('Element is required')
return (
<Element className={classNames} {...rest}>
{props.iconStart && <ActionIcon {...props.iconStart} />}
{props.icon && <ActionIcon {...props.icon} />}
{children}
{props.iconEnd && <ActionIcon {...props.iconEnd} />}
</Element>
)
}
}
})
}

View File

@ -1,55 +0,0 @@
import { Popover } from '@headlessui/react'
import { ActionButton, ActionButtonProps } from './ActionButton'
type ActionButtonSplitProps = Omit<ActionButtonProps, 'iconEnd'> & {
splitMenuItems: {
label: string
shortcut?: string
onClick: () => void
disabled?: boolean
}[]
}
export function ActionButtonDropdown({
splitMenuItems,
className,
...props
}: ActionButtonSplitProps) {
return (
<Popover className="relative">
<Popover.Button
as={ActionButton}
className={className}
{...props}
Element="button"
iconEnd={{
icon: 'caretDown',
className: 'ui-open:rotate-180',
bgClassName:
'bg-chalkboard-20 dark:bg-chalkboard-80 ui-open:bg-primary ui-open:text-chalkboard-10',
}}
/>
<Popover.Panel
as="ul"
className="absolute z-20 left-1/2 -translate-x-1/2 top-full mt-1 w-fit max-h-[80vh] overflow-y-auto py-2 flex flex-col gap-1 align-stretch text-inherit dark:text-chalkboard-10 bg-chalkboard-10 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-30 dark:border-chalkboard-80 text-sm m-0 p-0"
>
{splitMenuItems.map((item) => (
<li className="contents" key={item.label}>
<button
onClick={item.onClick}
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
disabled={item.disabled}
>
<span className="capitalize">{item.label}</span>
{item.shortcut && (
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
{item.shortcut}
</kbd>
)}
</button>
</li>
))}
</Popover.Panel>
</Popover>
)
}

View File

@ -29,8 +29,10 @@ export const ActionIcon = ({
size = 'md',
children,
}: ActionIconProps) => {
const computedIconClassName = `h-auto text-inherit dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
// By default, we reverse the icon color and background color in dark mode
const computedIconClassName = `h-auto text-primary dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
const computedBgClassName = `bg-primary/10 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
return (
<div

View File

@ -1,11 +1,12 @@
import { Toolbar } from '../Toolbar'
import UserSidebarMenu from 'components/UserSidebarMenu'
import UserSidebarMenu from './UserSidebarMenu'
import { type IndexLoaderData } from 'lib/types'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import styles from './AppHeader.module.css'
import { RefreshButton } from 'components/RefreshButton'
import { CommandBarOpenButton } from './CommandBarOpenButton'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { ActionButton } from './ActionButton'
import usePlatform from 'hooks/usePlatform'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
@ -21,6 +22,8 @@ export const AppHeader = ({
className = '',
enableMenu = false,
}: AppHeaderProps) => {
const platform = usePlatform()
const { commandBarSend } = useCommandsContext()
const { auth } = useSettingsAuthContext()
const user = auth?.context?.user
@ -40,17 +43,24 @@ export const AppHeader = ({
/>
{/* Toolbar if the context deems it */}
<div className="flex-grow flex justify-center max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
{showToolbar && <Toolbar />}
{showToolbar ? (
<Toolbar />
) : (
<ActionButton
Element="button"
onClick={() => commandBarSend({ type: 'Open' })}
className="text-sm self-center flex items-center w-fit gap-3"
>
Command Palette{' '}
<kbd className="bg-primary/10 dark:bg-chalkboard-100 dark:text-primary inline-block px-1 py-0.5 border-primary dark:border-chalkboard-90">
{platform === 'macos' ? '⌘K' : 'Ctrl+/'}
</kbd>
</ActionButton>
)}
</div>
<div className="flex items-center gap-1 py-1 ml-auto">
{/* If there are children, show them, otherwise show User menu */}
{children || (
<>
<CommandBarOpenButton />
<RefreshButton />
<UserSidebarMenu user={user} />
</>
)}
{children || <UserSidebarMenu user={user} />}
</div>
</header>
)

View File

@ -175,7 +175,7 @@ function ReviewingButton() {
type="submit"
form="review-form"
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
iconStart={{
icon={{
icon: 'checkmark',
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
iconClassName: '!text-chalkboard-10',
@ -193,7 +193,7 @@ function GatheringArgsButton() {
type="submit"
form="arg-form"
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
iconStart={{
icon={{
icon: 'arrowRight',
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
iconClassName: '!text-chalkboard-10',

View File

@ -7,7 +7,6 @@ import {
getSelectionType,
getSelectionTypeDisplayText,
} from 'lib/selections'
import { kclManager } from 'lib/singletons'
import { modelingMachine } from 'machines/modelingMachine'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
@ -51,14 +50,6 @@ function CommandBarSelectionInput({
inputRef.current?.focus()
}, [selection, inputRef])
// Exit engine's edit mode when this input step is active,
// and re-enter it when it's not.
// In future the engine's edit mode will go away and this will be handled differently.
useEffect(() => {
kclManager.exitEditMode()
return () => kclManager.enterEditMode()
}, [])
// Fast-forward through this arg if it's marked as skippable
// and we have a valid selection already
useEffect(() => {

View File

@ -1,19 +0,0 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform'
export function CommandBarOpenButton() {
const { commandBarSend } = useCommandsContext()
const platform = usePlatform()
return (
<button
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
onClick={() => commandBarSend({ type: 'Open' })}
>
<span>Commands</span>
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
{platform === 'macos' ? '⌘K' : '^/'}
</kbd>
</button>
)
}

View File

@ -38,7 +38,7 @@ function CommandComboBox({
<div className="flex items-center gap-2 px-4 pb-2 border-solid border-0 border-b border-b-chalkboard-20 dark:border-b-chalkboard-80">
<CustomIcon
name="search"
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
className="w-5 h-5 bg-primary/10 text-primary"
/>
<Combobox.Input
onChange={(event) => setQuery(event.target.value)}

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