Compare commits
1 Commits
v0.21.3
...
url-scheme
Author | SHA1 | Date | |
---|---|---|---|
a21c2c50b3 |
@ -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_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
|
||||||
|
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
# Upload the WASM bundle as an artifact
|
# Upload the WASM bundle as an artifact
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: wasm-bundle
|
name: wasm-bundle
|
||||||
path: src/wasm-lib/pkg
|
path: src/wasm-lib/pkg
|
||||||
|
101
.github/workflows/ci.yml
vendored
@ -13,7 +13,7 @@ on:
|
|||||||
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
||||||
|
|
||||||
env:
|
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:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
@ -130,9 +130,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [macos-14, ubuntu-latest, windows-latest]
|
os: [macos-14, ubuntu-latest, windows-latest]
|
||||||
env:
|
env:
|
||||||
# Specific Apple Universal target for macos
|
|
||||||
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
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' || '' }}
|
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -239,96 +237,6 @@ jobs:
|
|||||||
includeDebug: true
|
includeDebug: true
|
||||||
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
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
|
- name: Build the app (release) and sign
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||||
@ -353,10 +261,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||||
|
|
||||||
|
# TODO: re-enable linux e2e tests when possible
|
||||||
- name: Run e2e tests (linux only)
|
- name: Run e2e tests (linux only)
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
if: false
|
||||||
run: |
|
run: |
|
||||||
cargo install tauri-driver --force
|
cargo install tauri-driver
|
||||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||||
export VITE_KC_API_BASE_URL
|
export VITE_KC_API_BASE_URL
|
||||||
xvfb-run yarn test:e2e:tauri
|
xvfb-run yarn test:e2e:tauri
|
||||||
@ -440,7 +349,7 @@ jobs:
|
|||||||
cat last_download.json
|
cat last_download.json
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v2.1.3'
|
uses: 'google-github-actions/auth@v2.1.2'
|
||||||
with:
|
with:
|
||||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||||
|
|
||||||
|
37
.github/workflows/create-release.yml
vendored
@ -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)
|
|
52
.github/workflows/playwright.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
- id: filter
|
- id: filter
|
||||||
name: Check for Rust changes
|
name: Check for Rust changes
|
||||||
uses: dorny/paths-filter@v3
|
uses: dorny/paths-filter@v2
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
rust:
|
rust:
|
||||||
@ -48,37 +48,38 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
- name: Download Wasm Cache
|
- name: download wasm
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: actions/download-artifact@v2
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
name: wasm-bundle
|
name: wasm-bundle
|
||||||
workflow: build-and-store-wasm.yml
|
|
||||||
branch: main
|
|
||||||
path: src/wasm-lib/pkg
|
path: src/wasm-lib/pkg
|
||||||
- name: copy wasm blob
|
- name: copy wasm blob
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
uses: dtolnay/rust-toolchain@stable
|
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'
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
- name: OR Cache Wasm (because wasm cache failed)
|
- name: Cache wasm
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
- name: Build Wasm (because rust diff)
|
- name: build wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: OR Build Wasm (because wasm cache failed)
|
- name: build wasm
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: build web
|
- name: build web
|
||||||
@ -130,6 +131,11 @@ jobs:
|
|||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
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:
|
playwright-macos:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@ -145,37 +151,38 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
- name: Download Wasm Cache
|
- name: download wasm
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
continue-on-error: true
|
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
name: wasm-bundle
|
name: wasm-bundle
|
||||||
workflow: build-and-store-wasm.yml
|
|
||||||
branch: main
|
|
||||||
path: src/wasm-lib/pkg
|
path: src/wasm-lib/pkg
|
||||||
|
continue-on-error: true
|
||||||
- name: copy wasm blob
|
- name: copy wasm blob
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
uses: dtolnay/rust-toolchain@stable
|
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'
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
- name: OR Cache Wasm (because wasm cache failed)
|
- name: Cache wasm
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
- name: Build Wasm (because rust diff)
|
- name: build wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: OR Build Wasm (because wasm cache failed)
|
- name: build wasm
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: build web
|
- name: build web
|
||||||
@ -193,3 +200,8 @@ jobs:
|
|||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
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
@ -54,4 +54,3 @@ src/**/*.typegen.ts
|
|||||||
src-tauri/gen
|
src-tauri/gen
|
||||||
|
|
||||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||||
Mac_App_Distribution.provisionprofile
|
|
||||||
|
12
README.md
@ -59,10 +59,6 @@ followed by:
|
|||||||
```
|
```
|
||||||
yarn build:wasm-dev
|
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)
|
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
|
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.
|
## Developing in Chrome
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
Chrome is in the process of rolling out a new default which
|
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/).
|
[blocks Third-Party Cookies](https://developer.chrome.com/en/docs/privacy-sandbox/third-party-cookie-phase-out/).
|
||||||
|
177
docs/kcl/getExtrudeWallTransform.md
Normal file
@ -31,6 +31,7 @@ layout: manual
|
|||||||
* [`fillet`](kcl/fillet)
|
* [`fillet`](kcl/fillet)
|
||||||
* [`floor`](kcl/floor)
|
* [`floor`](kcl/floor)
|
||||||
* [`getEdge`](kcl/getEdge)
|
* [`getEdge`](kcl/getEdge)
|
||||||
|
* [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform)
|
||||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { makeTemplate, getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import waitOn from 'wait-on'
|
import waitOn from 'wait-on'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
@ -8,11 +8,9 @@ import {
|
|||||||
TEST_SETTINGS,
|
TEST_SETTINGS,
|
||||||
TEST_SETTINGS_KEY,
|
TEST_SETTINGS_KEY,
|
||||||
TEST_SETTINGS_CORRUPTED,
|
TEST_SETTINGS_CORRUPTED,
|
||||||
TEST_SETTINGS_ONBOARDING_EXPORT,
|
TEST_SETTINGS_ONBOARDING,
|
||||||
TEST_SETTINGS_ONBOARDING_START,
|
|
||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
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
|
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
|
// selected two lines therefore there should be two cursors
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Constrain' }).click()
|
|
||||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
@ -265,88 +262,6 @@ test('Can moving camera', async ({ page, context }) => {
|
|||||||
}, [1, -94, -94])
|
}, [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 }) => {
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
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
|
const bottomAng = 25
|
||||||
*/
|
*/
|
||||||
await page.click('.cm-content')
|
await page.click('.cm-content')
|
||||||
await page.keyboard.type('$ error')
|
await page.keyboard.type('# error')
|
||||||
|
|
||||||
// press arrows to clear autocomplete
|
// press arrows to clear autocomplete
|
||||||
await page.keyboard.press('ArrowLeft')
|
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
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
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
|
// 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.press('End')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('Home')
|
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')
|
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 }) => {
|
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
|
|
||||||
@ -816,7 +692,7 @@ test('Onboarding redirects and code updating', async ({ page }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
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
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await constrainButton.click()
|
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
@ -958,14 +831,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// same selection but click the axis first
|
// same selection but click the axis first
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// 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
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -1041,8 +910,9 @@ test.describe('Command bar tests', () => {
|
|||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
|
||||||
// First try opening the command bar and closing it
|
// 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
|
await page
|
||||||
.getByRole('button', { name: 'Commands', exact: false })
|
.getByRole('button', { name: 'Ctrl+/' })
|
||||||
.or(page.getByRole('button', { name: '⌘K' }))
|
.or(page.getByRole('button', { name: '⌘K' }))
|
||||||
.click()
|
.click()
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
@ -1087,13 +957,13 @@ test.describe('Command bar tests', () => {
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`const distance = sqrt(20)
|
`const distance = sqrt(20)
|
||||||
const part001 = startSketchOn('-XZ')
|
const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([-6.95, 10.98], %)
|
|> startProfileAt([-6.95, 4.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -20.93], %)
|
|> line([0.73, -14.93], %)
|
||||||
|> line([-23.44, 0.52], %)
|
|> line([-23.44, 0.52], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1110,6 +980,7 @@ test.describe('Command bar tests', () => {
|
|||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
|
await page.getByText('|> line([0.73, -14.93], %)').click()
|
||||||
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
@ -1119,12 +990,6 @@ test.describe('Command bar tests', () => {
|
|||||||
// Search for extrude command and choose it
|
// Search for extrude command and choose it
|
||||||
await page.getByRole('option', { name: 'Extrude' }).click()
|
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
|
// Assert that we're on the distance step
|
||||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
||||||
|
|
||||||
@ -1158,9 +1023,9 @@ test.describe('Command bar tests', () => {
|
|||||||
`const distance = sqrt(20)
|
`const distance = sqrt(20)
|
||||||
const distance001 = 5 + 7
|
const distance001 = 5 + 7
|
||||||
const part001 = startSketchOn('-XZ')
|
const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([-6.95, 10.98], %)
|
|> startProfileAt([-6.95, 4.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -20.93], %)
|
|> line([0.73, -14.93], %)
|
||||||
|> line([-23.44, 0.52], %)
|
|> line([-23.44, 0.52], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
|> 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 ({
|
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -1551,7 +1350,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
|||||||
`const part001 = startSketchOn('-XZ')`
|
`const part001 = startSketchOn('-XZ')`
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.waitForTimeout(600)
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
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)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
const result = makeTemplate`const part002 = startSketchOn(part001, 'seg01')
|
await expect(page.locator('.cm-content'))
|
||||||
|> startProfileAt([-12.83, 6.7], %)
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|> line([-3.05, -1.47], %)
|
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
|
||||||
|> close(%)`
|
process?.env?.CI ? 0.07 : 0.07
|
||||||
|
}], %)
|
||||||
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
|> line([-3.05, -1.47], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
@ -1875,9 +1675,15 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
const result2 = result.genNext`
|
await expect(page.locator('.cm-content'))
|
||||||
|> extrude(${[5, 5]} + 7, %)`
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
|> 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 }) => {
|
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
|
const startXPx = 500
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
await page.mouse.click(615, 102)
|
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.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByText('Add constraining value').click()
|
await page.getByText('Add constraining value').click()
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
|
|||||||
`const part001 = startSketchOn('-XZ')`
|
`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()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
@ -597,15 +597,12 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.doAndWaitForImageDiff(
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
|
||||||
200
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -699,15 +696,12 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.doAndWaitForImageDiff(
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
|
||||||
200
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
@ -22,16 +22,11 @@ export const TEST_SETTINGS = {
|
|||||||
},
|
},
|
||||||
} satisfies Partial<SaveSettingsPayload>
|
} satisfies Partial<SaveSettingsPayload>
|
||||||
|
|
||||||
export const TEST_SETTINGS_ONBOARDING_EXPORT = {
|
export const TEST_SETTINGS_ONBOARDING = {
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export' },
|
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export' },
|
||||||
} satisfies Partial<SaveSettingsPayload>
|
} satisfies Partial<SaveSettingsPayload>
|
||||||
|
|
||||||
export const TEST_SETTINGS_ONBOARDING_START = {
|
|
||||||
...TEST_SETTINGS,
|
|
||||||
app: { ...TEST_SETTINGS.app, onboardingStatus: '' },
|
|
||||||
} satisfies Partial<SaveSettingsPayload>
|
|
||||||
|
|
||||||
export const TEST_SETTINGS_CORRUPTED = {
|
export const TEST_SETTINGS_CORRUPTED = {
|
||||||
app: {
|
app: {
|
||||||
theme: Themes.Dark,
|
theme: Themes.Dark,
|
||||||
|
@ -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
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@ import { browser, $, expect } from '@wdio/globals'
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
const documentsDir = `${process.env.HOME}/Documents`
|
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 defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
||||||
const newProjectDir = `${documentsDir}/a-different-directory`
|
const newProjectDir = `${documentsDir}/a-different-directory`
|
||||||
const userCodeDir = '/tmp/kittycad_user_code'
|
const userCodeDir = '/tmp/kittycad_user_code'
|
||||||
@ -29,10 +29,8 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
// Clean up filesystem from previous tests
|
// Clean up filesystem from previous tests
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
await fs.rm(defaultProjectDir, { force: true, recursive: true })
|
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(userCodeDir, { force: true })
|
||||||
await fs.rm(userSettingsDir, { force: true, recursive: true })
|
await fs.rm(userSettingsFile, { force: true })
|
||||||
await fs.mkdir(defaultProjectDir, { recursive: true })
|
|
||||||
await fs.mkdir(newProjectDir, { recursive: true })
|
await fs.mkdir(newProjectDir, { recursive: true })
|
||||||
|
|
||||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||||
@ -72,7 +70,6 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
console.log(cr.status)
|
console.log(cr.status)
|
||||||
|
|
||||||
// Now should be signed in
|
// Now should be signed in
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10000))
|
|
||||||
const newFileButton = await $('[data-testid="home-new-file"]')
|
const newFileButton = await $('[data-testid="home-new-file"]')
|
||||||
expect(await newFileButton.getText()).toEqual('New project')
|
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 () => {
|
it('opens the new file and expects a loading stream', async () => {
|
||||||
const projectLink = await $('[data-testid="project-link"]')
|
const projectLink = await $('[data-testid="project-link"]')
|
||||||
await click(projectLink)
|
await click(projectLink)
|
||||||
const errorText = await $('[data-testid="unexpected-error"]')
|
const loadingText = await $('[data-testid="loading-stream"]')
|
||||||
expect(await errorText.getText()).toContain('unexpected error')
|
expect(await loadingText.getText()).toContain('Loading stream...')
|
||||||
await browser.execute('window.location.href = "tauri://localhost/home"')
|
await browser.execute('window.location.href = "tauri://localhost/home"')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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"
|
|
@ -15,7 +15,7 @@
|
|||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
data-domain="app.zoo.dev"
|
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>
|
></script>
|
||||||
<title>Zoo Modeling App</title>
|
<title>Zoo Modeling App</title>
|
||||||
</head>
|
</head>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.21.3",
|
"version": "0.19.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.60",
|
"@kittycad/lib": "^0.0.58",
|
||||||
"@lezer/javascript": "^1.4.9",
|
"@lezer/javascript": "^1.4.9",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
@ -86,7 +86,6 @@
|
|||||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
|
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
|
||||||
"fmt-check": "prettier --check ./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-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": "(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",
|
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||||
@ -123,7 +122,6 @@
|
|||||||
"@tauri-apps/cli": "^2.0.0-beta.13",
|
"@tauri-apps/cli": "^2.0.0-beta.13",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/debounce-promise": "^3.1.9",
|
"@types/debounce-promise": "^3.1.9",
|
||||||
"@types/mocha": "^10.0.6",
|
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
|
@ -27,7 +27,7 @@ export default defineConfig({
|
|||||||
baseURL: 'http://localhost:3000',
|
baseURL: 'http://localhost:3000',
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'retain-on-failure',
|
trace: 'on-first-retry',
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"applinks": {
|
|
||||||
"details": [
|
|
||||||
{
|
|
||||||
"appIDs": ["92H8YB3B95.dev.zoo.modeling-app"],
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"/": "/file/*",
|
|
||||||
"comment": "Matches any URL whose path starts with /file/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 152 KiB |