Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
da323e22d4 | |||
8dc3628e9b | |||
253744867b | |||
c45eb1e3e3 | |||
758aac9328 | |||
309943cf2c | |||
b3d4ab91fc | |||
5e73fa45f0 | |||
17d23a17db | |||
0460f8eaee | |||
2077cdb6fc | |||
cb0b7e8169 | |||
3a05211d30 | |||
d12d103cba | |||
04f6d3dcc8 | |||
9c9ffa0d03 | |||
c62b9f1f04 | |||
fcac3c72e4 | |||
1e2f577a9f | |||
1814f340fb | |||
43928f88aa | |||
6959036688 | |||
570d0473c6 | |||
44f0d7c25c | |||
3ccb04c4e7 | |||
00058f699a | |||
5a478fe0b3 | |||
723cf4f746 | |||
3950de0a4d | |||
901d474986 | |||
e7ab645267 | |||
cf830f9895 | |||
2c1f53f0f0 | |||
d39e2502d0 | |||
51fed9c541 | |||
b3a09abe01 | |||
cd3a2fea07 | |||
c29c4a8567 | |||
39ccd94884 | |||
d99ab22b56 | |||
20a8f2aa6a | |||
93266a9819 | |||
a9c7a7cb13 | |||
8dd9b8d192 | |||
23181d8144 | |||
834967df6a | |||
deacaac33a | |||
c55603853b | |||
93f652647e | |||
67cea620a6 | |||
ed0c7d038d | |||
d3aa789761 |
@ -3,3 +3,4 @@ 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"
|
||||
|
99
.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)
|
||||
|
||||
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,7 +130,9 @@ 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
|
||||
@ -237,6 +239,96 @@ 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' }}
|
||||
@ -261,11 +353,10 @@ jobs:
|
||||
with:
|
||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||
|
||||
# TODO: re-enable linux e2e tests when possible
|
||||
- name: Run e2e tests (linux only)
|
||||
if: false
|
||||
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: |
|
||||
cargo install tauri-driver
|
||||
cargo install tauri-driver --force
|
||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
export VITE_KC_API_BASE_URL
|
||||
xvfb-run yarn test:e2e:tauri
|
||||
|
37
.github/workflows/create-release.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
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)
|
1
.gitignore
vendored
@ -54,3 +54,4 @@ src/**/*.typegen.ts
|
||||
src-tauri/gen
|
||||
|
||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
Mac_App_Distribution.provisionprofile
|
||||
|
@ -72,7 +72,13 @@ finally, to run the web app only, run:
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Developing in Chrome
|
||||
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
|
||||
|
||||
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/).
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils } from './test-utils'
|
||||
import { makeTemplate, getUtils } from './test-utils'
|
||||
import waitOn from 'wait-on'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
@ -8,9 +8,11 @@ import {
|
||||
TEST_SETTINGS,
|
||||
TEST_SETTINGS_KEY,
|
||||
TEST_SETTINGS_CORRUPTED,
|
||||
TEST_SETTINGS_ONBOARDING,
|
||||
TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||
TEST_SETTINGS_ONBOARDING_START,
|
||||
} 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
|
||||
@ -278,7 +280,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')
|
||||
@ -295,10 +297,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')
|
||||
@ -527,6 +529,10 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
})
|
||||
|
||||
test('Auto complete works', async ({ page }) => {
|
||||
test.skip(
|
||||
true,
|
||||
'CORS issue stopping the kcl lsp from working, enable again later'
|
||||
)
|
||||
const u = getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -680,6 +686,45 @@ 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)
|
||||
|
||||
@ -692,7 +737,7 @@ test('Onboarding redirects and code updating', async ({ page }) => {
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
||||
}
|
||||
)
|
||||
|
||||
@ -910,9 +955,8 @@ 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: 'Ctrl+/' })
|
||||
.getByRole('button', { name: 'Commands', exact: false })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
@ -957,13 +1001,13 @@ test.describe('Command bar tests', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const distance = sqrt(20)
|
||||
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(%)
|
||||
`
|
||||
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(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
@ -980,7 +1024,6 @@ 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')
|
||||
@ -990,6 +1033,12 @@ 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()
|
||||
|
||||
@ -1023,9 +1072,9 @@ test.describe('Command bar tests', () => {
|
||||
`const distance = sqrt(20)
|
||||
const distance001 = 5 + 7
|
||||
const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
||||
@ -1216,6 +1265,72 @@ 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,
|
||||
}) => {
|
||||
@ -1350,7 +1465,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
@ -1649,14 +1764,13 @@ test('Sketch on face', async ({ page }) => {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
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(%)`)
|
||||
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)
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
@ -1675,15 +1789,9 @@ test('Sketch on face', async ({ page }) => {
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
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, %)`)
|
||||
const result2 = result.genNext`
|
||||
|> extrude(${[5, 5]} + 7, %)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
||||
})
|
||||
|
||||
test('Can code mod a line length', async ({ page }) => {
|
||||
|
@ -507,7 +507,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const startXPx = 600
|
||||
@ -597,12 +597,15 @@ test.describe('Client side scene scale should match engine scale', () => {
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
@ -696,12 +699,15 @@ test.describe('Client side scene scale should match engine scale', () => {
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
@ -22,11 +22,16 @@ export const TEST_SETTINGS = {
|
||||
},
|
||||
} satisfies Partial<SaveSettingsPayload>
|
||||
|
||||
export const TEST_SETTINGS_ONBOARDING = {
|
||||
export const TEST_SETTINGS_ONBOARDING_EXPORT = {
|
||||
...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,
|
||||
|
@ -182,3 +182,76 @@ 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'
|
||||
|
||||
const documentsDir = `${process.env.HOME}/Documents`
|
||||
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
|
||||
const userSettingsDir = `${process.env.HOME}/.config/dev.zoo.modeling-app`
|
||||
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
||||
const newProjectDir = `${documentsDir}/a-different-directory`
|
||||
const userCodeDir = '/tmp/kittycad_user_code'
|
||||
@ -29,8 +29,10 @@ 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(userSettingsFile, { force: true })
|
||||
await fs.rm(userSettingsDir, { force: true, recursive: true })
|
||||
await fs.mkdir(defaultProjectDir, { recursive: true })
|
||||
await fs.mkdir(newProjectDir, { recursive: true })
|
||||
|
||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||
@ -70,6 +72,7 @@ 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')
|
||||
})
|
||||
@ -117,8 +120,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 loadingText = await $('[data-testid="loading-stream"]')
|
||||
expect(await loadingText.getText()).toContain('Loading stream...')
|
||||
const errorText = await $('[data-testid="unexpected-error"]')
|
||||
expect(await errorText.getText()).toContain('unexpected error')
|
||||
await browser.execute('window.location.href = "tauri://localhost/home"')
|
||||
})
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script
|
||||
defer
|
||||
data-domain="app.zoo.dev"
|
||||
src="https://plausible.corp.zoo.dev/js/script.js"
|
||||
src="https://plausible.corp.zoo.dev/js/script.tagged-events.js"
|
||||
></script>
|
||||
<title>Zoo Modeling App</title>
|
||||
</head>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.19.4",
|
||||
"version": "0.21.0",
|
||||
"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.58",
|
||||
"@kittycad/lib": "^0.0.60",
|
||||
"@lezer/javascript": "^1.4.9",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
@ -123,6 +123,7 @@
|
||||
"@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",
|
||||
|
@ -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: 'on-first-retry',
|
||||
trace: 'retain-on-failure',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
|
15
public/.well-known/apple-app-site-association
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"applinks": {
|
||||
"details": [
|
||||
{
|
||||
"appIDs": ["92H8YB3B95.dev.zoo.modeling-app"],
|
||||
"components": [
|
||||
{
|
||||
"/": "/file/*",
|
||||
"comment": "Matches any URL whose path starts with /file/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
BIN
public/onboarding-bracket-dimensions-dark.png
Normal file
After Width: | Height: | Size: 150 KiB |
BIN
public/onboarding-bracket-dimensions.png
Normal file
After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 867 KiB |
342
src-tauri/Cargo.lock
generated
@ -38,6 +38,17 @@ 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"
|
||||
@ -81,6 +92,24 @@ 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"
|
||||
@ -154,20 +183,24 @@ 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]]
|
||||
@ -179,6 +212,12 @@ 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"
|
||||
@ -289,9 +328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -414,9 +453,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
@ -505,6 +544,30 @@ 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"
|
||||
@ -532,7 +595,7 @@ version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d43b38e074cc0de2957f10947e376a1d88b9c4dbab340b590800cc1b2e066b2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.8.11",
|
||||
"base64 0.13.1",
|
||||
"bitvec",
|
||||
"chrono",
|
||||
@ -554,6 +617,39 @@ 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"
|
||||
@ -1292,6 +1388,16 @@ 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"
|
||||
@ -1365,6 +1471,15 @@ 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"
|
||||
@ -1962,6 +2077,9 @@ 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"
|
||||
@ -2418,13 +2536,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.1.53"
|
||||
version = "0.1.54"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"bson",
|
||||
"chrono",
|
||||
"clap",
|
||||
@ -2477,9 +2595,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
|
||||
checksum = "2c6e12eb45fd9a28c8e99dbdef54556246b39acee14e4aa6f0fc43636caa62d9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -2656,6 +2774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2968,6 +3087,15 @@ 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"
|
||||
@ -3617,6 +3745,26 @@ 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"
|
||||
@ -3834,6 +3982,15 @@ 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"
|
||||
@ -3884,7 +4041,7 @@ version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@ -4042,6 +4199,35 @@ 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"
|
||||
@ -4052,6 +4238,22 @@ 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"
|
||||
@ -4134,7 +4336,7 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
@ -4197,9 +4399,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
|
||||
checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
@ -4215,14 +4417,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
|
||||
checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4247,6 +4449,12 @@ 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"
|
||||
@ -4301,9 +4509,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
version = "1.0.200"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -4319,9 +4527,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.198"
|
||||
version = "1.0.200"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -4330,13 +4538,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.26.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
|
||||
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4535,6 +4743,12 @@ 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"
|
||||
@ -4792,6 +5006,18 @@ 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"
|
||||
@ -4936,9 +5162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.0.0-beta.16"
|
||||
version = "2.0.0-beta.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d411ebb670bbe5cf948f6c24978632937329748b499de1619ab55ad31512652"
|
||||
checksum = "5fedd5490eddf117253945f0baedafded43474c971cba546a818f527d5c26266"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -4950,7 +5176,7 @@ dependencies = [
|
||||
"getrandom 0.2.14",
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"http 1.1.0",
|
||||
"jni",
|
||||
"libc",
|
||||
@ -5011,7 +5237,7 @@ version = "2.0.0-beta.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b383f341efb803852b0235a2f330ca90c4c113f422dd6d646b888685b372cace"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
"ico",
|
||||
"json-patch",
|
||||
@ -5078,6 +5304,21 @@ 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"
|
||||
@ -5136,6 +5377,27 @@ 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"
|
||||
@ -5190,7 +5452,7 @@ version = "2.0.0-beta.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f34be6851c7e84ca99b3bddd57e033d55d8bfca1dd153d6e8d18e9f1fb95469"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"dirs-next",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
@ -5214,9 +5476,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.0.0-beta.13"
|
||||
version = "2.0.0-beta.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7439729d0107c9797764919c39c4a4cc3af64306faaa48271da50d8eb4c0283"
|
||||
checksum = "148b6e6aff8e63fe5d4ae1d50159d50cfc0b4309abdeca64833c887c6b5631ef"
|
||||
dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
@ -5233,9 +5495,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.0.0-beta.13"
|
||||
version = "2.0.0-beta.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c38dcfa7f8c2b2e344c7401972e0ddaaec4fa655666788d94b1852d6c4a7fe8"
|
||||
checksum = "398d065c6e0fbf3c4304583759b6e153bc1e0daeb033bede6834ebe4df371fc3"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@ -5376,7 +5638,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.11",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
@ -5946,6 +6210,12 @@ 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"
|
||||
@ -6000,6 +6270,12 @@ 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"
|
||||
@ -6646,7 +6922,7 @@ version = "0.39.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e180ac2740d6cb4d5cec0abf63eacbea90f1b7e5e3803043b13c1c84c4b7884"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"block",
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
|
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
description = "The Zoo Modeling App"
|
||||
authors = ["Zoo Engineers <eng@zoo.dev>"]
|
||||
license = ""
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
default-run = "app"
|
||||
@ -17,22 +17,28 @@ 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"] }
|
||||
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
|
||||
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 = []
|
||||
|
376
src-tauri/Info.plist
Normal file
@ -0,0 +1,376 @@
|
||||
<?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>
|
@ -8,6 +8,8 @@
|
||||
],
|
||||
"permissions": [
|
||||
"cli:default",
|
||||
"deep-link:default",
|
||||
"log:default",
|
||||
"path:default",
|
||||
"event:default",
|
||||
"window:default",
|
||||
|
24
src-tauri/entitlements/app-store.entitlements
Normal file
@ -0,0 +1,24 @@
|
||||
<?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>
|
@ -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, DEFAULT_PROJECT_KCL_FILE,
|
||||
Configuration,
|
||||
};
|
||||
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> {
|
||||
println!("Logging in...");
|
||||
log::debug!("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 {
|
||||
println!("E2E_TAURI_ENABLED is set, won't open {} externally", auth_uri.secret());
|
||||
log::warn!("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}")
|
||||
}
|
||||
}
|
||||
println!("Getting user info...");
|
||||
log::debug!("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,6 +350,33 @@ 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![
|
||||
@ -371,6 +398,22 @@ 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)]
|
||||
@ -378,6 +421,7 @@ 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())?;
|
||||
}
|
||||
@ -402,6 +446,7 @@ 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());
|
||||
}
|
||||
}
|
||||
@ -411,6 +456,10 @@ 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.
|
||||
@ -428,74 +477,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
let runner: tauri::async_runtime::JoinHandle<Result<ProjectState>> =
|
||||
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 does not start with a slash, it is a relative path.
|
||||
// We need to convert it to an absolute path.
|
||||
let source_path = if source_path.is_relative() {
|
||||
std::env::current_dir()
|
||||
.map_err(|e| anyhow::anyhow!("Error getting the current directory: {:?}", e))?
|
||||
.join(source_path)
|
||||
} 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()),
|
||||
})
|
||||
});
|
||||
tauri::async_runtime::spawn(async move { ProjectState::new_from_path(source_path).await });
|
||||
|
||||
// Block on the handle.
|
||||
let store = tauri::async_runtime::block_on(runner)??;
|
||||
@ -503,15 +485,31 @@ 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(())
|
||||
})
|
||||
.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!())?;
|
||||
.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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
8
src-tauri/tauri.app-store.conf.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"bundle": {
|
||||
"macOS": {
|
||||
"entitlements": "entitlements/app-store.entitlements"
|
||||
}
|
||||
}
|
||||
}
|
@ -37,13 +37,7 @@
|
||||
}
|
||||
},
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"macOS": {},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": "all"
|
||||
@ -60,16 +54,25 @@
|
||||
},
|
||||
{
|
||||
"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.19.4"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
@ -5,7 +5,45 @@
|
||||
"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": {
|
||||
|
@ -4,7 +4,6 @@ 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 {
|
||||
@ -14,7 +13,6 @@ import {
|
||||
import { useStore } from 'useStore'
|
||||
|
||||
export const Toolbar = () => {
|
||||
const platform = usePlatform()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
@ -274,17 +272,8 @@ export const Toolbar = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<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>
|
||||
<menu className="max-w-full overflow-hidden whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
|
||||
<ToolbarButtons />
|
||||
</menu>
|
||||
)
|
||||
}
|
||||
|
@ -90,7 +90,8 @@ export const ClientSideScene = ({
|
||||
cursor = 'grabbing'
|
||||
} else if (
|
||||
state.matches('Sketch.Line tool') ||
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
state.matches('Sketch.Tangential arc to') ||
|
||||
state.matches('Sketch.Rectangle tool')
|
||||
) {
|
||||
cursor = 'crosshair'
|
||||
} else {
|
||||
@ -104,9 +105,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-black' : ''} ${
|
||||
} ${hideServer ? 'bg-chalkboard-10 dark:bg-chalkboard-100' : ''} ${
|
||||
!hideClient && !hideServer && state.matches('Sketch')
|
||||
? 'bg-black/80'
|
||||
? 'bg-chalkboard-10/80 dark:bg-chalkboard-100/80'
|
||||
: ''
|
||||
}`}
|
||||
></div>
|
||||
|
@ -97,6 +97,7 @@ import {
|
||||
getRectangleCallExpressions,
|
||||
updateRectangleSketch,
|
||||
} from 'lib/rectangleTool'
|
||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -356,6 +357,7 @@ export class SceneEntities {
|
||||
id: sketchGroup.start.__geoMeta.id,
|
||||
pathToNode: segPathToNode,
|
||||
scale: factor,
|
||||
theme: sceneInfra._theme,
|
||||
})
|
||||
_profileStart.layers.set(SKETCH_LAYER)
|
||||
_profileStart.traverse((child) => {
|
||||
@ -406,6 +408,7 @@ export class SceneEntities {
|
||||
isDraftSegment,
|
||||
scale: factor,
|
||||
texture: sceneInfra.extraSegmentTexture,
|
||||
theme: sceneInfra._theme,
|
||||
})
|
||||
} else {
|
||||
seg = straightSegment({
|
||||
@ -417,6 +420,7 @@ export class SceneEntities {
|
||||
scale: factor,
|
||||
callExpName,
|
||||
texture: sceneInfra.extraSegmentTexture,
|
||||
theme: sceneInfra._theme,
|
||||
})
|
||||
}
|
||||
seg.layers.set(SKETCH_LAYER)
|
||||
@ -964,7 +968,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.updateCodeStateEditor(code)
|
||||
codeManager.updateCodeEditor(code)
|
||||
const { programMemory } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
@ -1503,7 +1507,10 @@ export class SceneEntities {
|
||||
const isSelected = parent?.userData?.isSelected
|
||||
colorSegment(
|
||||
selected,
|
||||
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
|
||||
isSelected
|
||||
? 0x0000ff
|
||||
: parent?.userData?.baseColor ||
|
||||
getThemeColorForThreeJs(sceneInfra._theme)
|
||||
)
|
||||
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||
if (extraSegmentGroup) {
|
||||
|
@ -30,6 +30,7 @@ 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']
|
||||
|
||||
@ -101,6 +102,7 @@ export class SceneInfra {
|
||||
isFovAnimationInProgress = false
|
||||
_baseUnit: BaseUnit = 'mm'
|
||||
_baseUnitMultiplier = 1
|
||||
_theme: Themes = Themes.System
|
||||
extraSegmentTexture: Texture
|
||||
lastMouseState: MouseState = { type: 'idle' }
|
||||
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||
@ -137,6 +139,9 @@ export class SceneInfra {
|
||||
this._baseUnitMultiplier
|
||||
)
|
||||
}
|
||||
set theme(theme: Themes) {
|
||||
this._theme = theme
|
||||
}
|
||||
resetMouseListeners = () => {
|
||||
this.setCallbacks({
|
||||
onDragStart: () => {},
|
||||
|
@ -37,22 +37,26 @@ 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 body = new MeshBasicMaterial({ color: 0xffffff })
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const body = new MeshBasicMaterial({ color: baseColor })
|
||||
const mesh = new Mesh(geometry, body)
|
||||
|
||||
group.add(mesh)
|
||||
@ -79,6 +83,7 @@ export function straightSegment({
|
||||
scale = 1,
|
||||
callExpName,
|
||||
texture,
|
||||
theme,
|
||||
}: {
|
||||
from: Coords2d
|
||||
to: Coords2d
|
||||
@ -88,6 +93,7 @@ export function straightSegment({
|
||||
scale?: number
|
||||
callExpName: string
|
||||
texture: Texture
|
||||
theme: Themes
|
||||
}): Group {
|
||||
const group = new Group()
|
||||
|
||||
@ -111,7 +117,8 @@ export function straightSegment({
|
||||
})
|
||||
}
|
||||
|
||||
const baseColor = callExpName === 'close' ? 0x444444 : 0xffffff
|
||||
const baseColor =
|
||||
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
|
||||
const body = new MeshBasicMaterial({ color: baseColor })
|
||||
const mesh = new Mesh(geometry, body)
|
||||
mesh.userData.type = isDraftSegment
|
||||
@ -134,7 +141,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)
|
||||
const arrowGroup = createArrowhead(scale, theme)
|
||||
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))
|
||||
@ -147,7 +154,7 @@ export function straightSegment({
|
||||
group.add(mesh)
|
||||
if (callExpName !== 'close') group.add(arrowGroup)
|
||||
|
||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
|
||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
|
||||
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||
.normalize()
|
||||
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
||||
@ -162,8 +169,10 @@ export function straightSegment({
|
||||
return group
|
||||
}
|
||||
|
||||
function createArrowhead(scale = 1): Group {
|
||||
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
|
||||
function createArrowhead(scale = 1, theme: Themes): Group {
|
||||
const arrowMaterial = new MeshBasicMaterial({
|
||||
color: getThemeColorForThreeJs(theme),
|
||||
})
|
||||
// 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)
|
||||
@ -179,7 +188,11 @@ function createArrowhead(scale = 1): Group {
|
||||
return arrowGroup
|
||||
}
|
||||
|
||||
function createExtraSegmentHandle(scale: number, texture: Texture): Group {
|
||||
function createExtraSegmentHandle(
|
||||
scale: number,
|
||||
texture: Texture,
|
||||
theme: Themes
|
||||
): Group {
|
||||
const particleMaterial = new PointsMaterial({
|
||||
size: 12, // in pixels
|
||||
map: texture,
|
||||
@ -189,7 +202,7 @@ function createExtraSegmentHandle(scale: number, texture: Texture): Group {
|
||||
})
|
||||
const mat = new MeshBasicMaterial({
|
||||
transparent: true,
|
||||
color: 0xffffff,
|
||||
color: getThemeColorForThreeJs(theme),
|
||||
opacity: 0,
|
||||
})
|
||||
const particleGeometry = new BufferGeometry().setFromPoints([
|
||||
@ -218,6 +231,7 @@ export function tangentialArcToSegment({
|
||||
isDraftSegment,
|
||||
scale = 1,
|
||||
texture,
|
||||
theme,
|
||||
}: {
|
||||
prevSegment: SketchGroup['value'][number]
|
||||
from: Coords2d
|
||||
@ -227,6 +241,7 @@ export function tangentialArcToSegment({
|
||||
isDraftSegment?: boolean
|
||||
scale?: number
|
||||
texture: Texture
|
||||
theme: Themes
|
||||
}): Group {
|
||||
const group = new Group()
|
||||
|
||||
@ -257,7 +272,8 @@ export function tangentialArcToSegment({
|
||||
scale,
|
||||
})
|
||||
|
||||
const body = new MeshBasicMaterial({ color: 0xffffff })
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const body = new MeshBasicMaterial({ color: baseColor })
|
||||
const mesh = new Mesh(geometry, body)
|
||||
mesh.userData.type = isDraftSegment
|
||||
? TANGENTIAL_ARC_TO__SEGMENT_DASH
|
||||
@ -271,10 +287,11 @@ export function tangentialArcToSegment({
|
||||
prevSegment,
|
||||
pathToNode,
|
||||
isSelected: false,
|
||||
baseColor,
|
||||
}
|
||||
group.name = TANGENTIAL_ARC_TO_SEGMENT
|
||||
|
||||
const arrowGroup = createArrowhead(scale)
|
||||
const arrowGroup = createArrowhead(scale, theme)
|
||||
arrowGroup.position.set(to[0], to[1], 0)
|
||||
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
|
||||
arrowGroup.quaternion.setFromUnitVectors(
|
||||
@ -285,7 +302,7 @@ export function tangentialArcToSegment({
|
||||
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
|
||||
arrowGroup.visible = !shouldHide
|
||||
|
||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
|
||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
|
||||
const circumferenceInPx = (2 * Math.PI * radius) / scale
|
||||
const extraSegmentAngleDelta =
|
||||
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Toolbar } from '../Toolbar'
|
||||
import UserSidebarMenu from './UserSidebarMenu'
|
||||
import UserSidebarMenu from 'components/UserSidebarMenu'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import styles from './AppHeader.module.css'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { RefreshButton } from 'components/RefreshButton'
|
||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||
|
||||
interface AppHeaderProps extends React.PropsWithChildren {
|
||||
showToolbar?: boolean
|
||||
@ -22,8 +21,6 @@ export const AppHeader = ({
|
||||
className = '',
|
||||
enableMenu = false,
|
||||
}: AppHeaderProps) => {
|
||||
const platform = usePlatform()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const user = auth?.context?.user
|
||||
|
||||
@ -43,24 +40,17 @@ 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 />
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
{showToolbar && <Toolbar />}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 py-1 ml-auto">
|
||||
{/* If there are children, show them, otherwise show User menu */}
|
||||
{children || <UserSidebarMenu user={user} />}
|
||||
{children || (
|
||||
<>
|
||||
<CommandBarOpenButton />
|
||||
<RefreshButton />
|
||||
<UserSidebarMenu user={user} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
@ -7,6 +7,7 @@ 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'
|
||||
@ -50,6 +51,14 @@ 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(() => {
|
||||
|
19
src/components/CommandBarOpenButton.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -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 text-primary"
|
||||
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
|
||||
/>
|
||||
<Combobox.Input
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
|
@ -41,6 +41,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
arrowRotateRight: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.5 7.59684L15.5 8.09684L15 8.09684L10.7931 8.09684L10.7931 7.09684L13.769 7.09684C13.3052 6.54751 12.7147 6.11526 12.0452 5.83941C11.2133 5.49662 10.2977 5.41109 9.41668 5.59387C8.53566 5.77666 7.72967 6.21935 7.10277 6.8648C6.47588 7.51025 6.05687 8.32881 5.89986 9.21478C5.74284 10.1008 5.85503 11.0134 6.22194 11.835C6.58884 12.6566 7.19361 13.3493 7.95816 13.8237C8.7227 14.2981 9.61192 14.5325 10.511 14.4964C11.41 14.4604 12.2776 14.1557 13.0018 13.6216L13.5953 14.4264C12.7103 15.0792 11.6499 15.4516 10.551 15.4956C9.45216 15.5397 8.36535 15.2533 7.4309 14.6734C6.49646 14.0936 5.75729 13.2469 5.30885 12.2428C4.86041 11.2386 4.7233 10.1231 4.9152 9.04027C5.10711 7.95742 5.61923 6.95696 6.38543 6.16808C7.15164 5.3792 8.13674 4.83812 9.21354 4.61472C10.2903 4.39132 11.4094 4.49586 12.4262 4.91483C13.2286 5.24545 13.9382 5.7599 14.5 6.41286L14.5 3.38998L15.5 3.38998L15.5 7.59684Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
arrowUp: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -16,7 +16,7 @@ export const ErrorPage = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<section className="max-w-full xl:max-w-4xl mx-auto">
|
||||
<h1 className="text-4xl mb-8 font-bold">
|
||||
<h1 className="text-4xl mb-8 font-bold" data-testid="unexpected-error">
|
||||
An unexpected error occurred
|
||||
</h1>
|
||||
{isRouteErrorResponse(error) && (
|
||||
@ -26,7 +26,12 @@ export const ErrorPage = () => {
|
||||
)}
|
||||
<div className="flex justify-between gap-2 mt-6">
|
||||
{isTauri() && (
|
||||
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to={'/'}
|
||||
icon={{ icon: faHome }}
|
||||
data-testid="unexpected-error-home"
|
||||
>
|
||||
Go Home
|
||||
</ActionButton>
|
||||
)}
|
||||
|
@ -85,7 +85,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const token = auth?.context.token
|
||||
const navigate = useNavigate()
|
||||
const { overallState } = useNetworkStatus()
|
||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
||||
|
@ -24,6 +24,7 @@ export const ModelingPaneHeader = ({
|
||||
|
||||
export const ModelingPane = ({
|
||||
title,
|
||||
id,
|
||||
children,
|
||||
className,
|
||||
Menu,
|
||||
@ -43,6 +44,7 @@ export const ModelingPane = ({
|
||||
<section
|
||||
{...props}
|
||||
data-testid={detailsTestId}
|
||||
id={id}
|
||||
className={
|
||||
pointerEventsCssClass + styles.panel + ' group ' + (className || '')
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import ReactCodeMirror from '@uiw/react-codemirror'
|
||||
import { TEST } from 'env'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
|
||||
import { lineHighlightField } from 'editor/highlightextension'
|
||||
import { roundOff } from 'lib/utils'
|
||||
@ -190,13 +190,15 @@ export const KclEditorPane = () => {
|
||||
return extensions
|
||||
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
|
||||
|
||||
const initialCode = useRef(codeManager.code)
|
||||
|
||||
return (
|
||||
<div
|
||||
id="code-mirror-override"
|
||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||
>
|
||||
<ReactCodeMirror
|
||||
value={codeManager.code}
|
||||
value={initialCode.current}
|
||||
extensions={editorExtensions}
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) =>
|
||||
|
@ -165,7 +165,11 @@ function ModelingSidebarSection({
|
||||
<Tab.Panel key="none" />
|
||||
{filteredPanes.map((pane) => (
|
||||
<Tab.Panel key={pane.id} className="h-full">
|
||||
<ModelingPane title={pane.title} Menu={pane.Menu}>
|
||||
<ModelingPane
|
||||
id={`${pane.id}-pane`}
|
||||
title={pane.title}
|
||||
Menu={pane.Menu}
|
||||
>
|
||||
{pane.Content instanceof Function ? (
|
||||
<pane.Content />
|
||||
) : (
|
||||
|
37
src/components/RefreshButton.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import Tooltip from './Tooltip'
|
||||
|
||||
export function RefreshButton() {
|
||||
async function refresh() {
|
||||
if (window && 'plausible' in window) {
|
||||
const p = window.plausible as (
|
||||
event: string,
|
||||
options?: { props: Record<string, string> }
|
||||
) => Promise<void>
|
||||
// Send a refresh event to Plausible so we can track how often users get stuck
|
||||
await p('Refresh', {
|
||||
props: {
|
||||
method: 'UI button',
|
||||
// TODO: add more coredump data here
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Window may not be available in some environments
|
||||
window?.location.reload()
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={refresh}
|
||||
className="p-1 m-0 bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 rounded-full border border-solid border-chalkboard-10 dark:border-chalkboard-100"
|
||||
>
|
||||
<CustomIcon name="arrowRotateRight" className="w-5 h-5" />
|
||||
<Tooltip position="bottom-right">
|
||||
<span>Refresh and report</span>
|
||||
<br />
|
||||
<span className="text-xs">Send us data on how you got stuck</span>
|
||||
</Tooltip>
|
||||
</button>
|
||||
)
|
||||
}
|
152
src/components/Settings/SettingsFieldInput.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import { Toggle } from 'components/Toggle/Toggle'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Setting } from 'lib/settings/initialSettings'
|
||||
import {
|
||||
SetEventTypes,
|
||||
SettingsLevel,
|
||||
WildcardSetEvent,
|
||||
} from 'lib/settings/settingsTypes'
|
||||
import { getSettingInputType } from 'lib/settings/settingsUtils'
|
||||
import { useMemo } from 'react'
|
||||
import { Event } from 'xstate'
|
||||
|
||||
interface SettingsFieldInputProps {
|
||||
// We don't need the fancy types here,
|
||||
// it doesn't help us with autocomplete or anything
|
||||
category: string
|
||||
settingName: string
|
||||
settingsLevel: SettingsLevel
|
||||
setting: Setting<unknown>
|
||||
}
|
||||
|
||||
export function SettingsFieldInput({
|
||||
category,
|
||||
settingName,
|
||||
settingsLevel,
|
||||
setting,
|
||||
}: SettingsFieldInputProps) {
|
||||
const {
|
||||
settings: { context, send },
|
||||
} = useSettingsAuthContext()
|
||||
const options = useMemo(() => {
|
||||
return setting.commandConfig &&
|
||||
'options' in setting.commandConfig &&
|
||||
setting.commandConfig.options
|
||||
? setting.commandConfig.options instanceof Array
|
||||
? setting.commandConfig.options
|
||||
: setting.commandConfig.options(
|
||||
{
|
||||
argumentsToSubmit: {
|
||||
level: settingsLevel,
|
||||
},
|
||||
},
|
||||
context
|
||||
)
|
||||
: []
|
||||
}, [setting, settingsLevel, context])
|
||||
const inputType = getSettingInputType(setting)
|
||||
|
||||
switch (inputType) {
|
||||
case 'component':
|
||||
return (
|
||||
setting.Component && (
|
||||
<setting.Component
|
||||
value={setting[settingsLevel] || setting.getFallback(settingsLevel)}
|
||||
updateValue={(newValue) => {
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: newValue,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)
|
||||
case 'boolean':
|
||||
return (
|
||||
<Toggle
|
||||
offLabel="Off"
|
||||
onLabel="On"
|
||||
onChange={(e) =>
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: Boolean(e.target.checked),
|
||||
},
|
||||
} as SetEventTypes)
|
||||
}
|
||||
checked={Boolean(
|
||||
setting[settingsLevel] !== undefined
|
||||
? setting[settingsLevel]
|
||||
: setting.getFallback(settingsLevel)
|
||||
)}
|
||||
name={`${category}-${settingName}`}
|
||||
data-testid={`${category}-${settingName}`}
|
||||
/>
|
||||
)
|
||||
case 'options':
|
||||
return (
|
||||
<select
|
||||
name={`${category}-${settingName}`}
|
||||
data-testid={`${category}-${settingName}`}
|
||||
className="p-1 bg-transparent border rounded-sm border-chalkboard-30 w-full"
|
||||
value={String(
|
||||
setting[settingsLevel] || setting.getFallback(settingsLevel)
|
||||
)}
|
||||
onChange={(e) =>
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: e.target.value,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}
|
||||
>
|
||||
{options &&
|
||||
options.length > 0 &&
|
||||
options.map((option) => (
|
||||
<option key={option.name} value={String(option.value)}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
case 'string':
|
||||
return (
|
||||
<input
|
||||
name={`${category}-${settingName}`}
|
||||
data-testid={`${category}-${settingName}`}
|
||||
type="text"
|
||||
className="p-1 bg-transparent border rounded-sm border-chalkboard-30 w-full"
|
||||
defaultValue={String(
|
||||
setting[settingsLevel] || setting.getFallback(settingsLevel)
|
||||
)}
|
||||
onBlur={(e) => {
|
||||
if (
|
||||
setting[settingsLevel] === undefined
|
||||
? setting.getFallback(settingsLevel) !== e.target.value
|
||||
: setting[settingsLevel] !== e.target.value
|
||||
) {
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: e.target.value,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<p className="text-destroy-70 dark:text-destroy-20">
|
||||
No component or input type found for setting {settingName} in category{' '}
|
||||
{category}
|
||||
</p>
|
||||
)
|
||||
}
|
110
src/components/Settings/SettingsSearchBar.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Combobox } from '@headlessui/react'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import decamelize from 'decamelize'
|
||||
import Fuse from 'fuse.js'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Setting } from 'lib/settings/initialSettings'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export function SettingsSearchBar() {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
useHotkeys(
|
||||
'Ctrl+.',
|
||||
(e) => {
|
||||
e.preventDefault()
|
||||
inputRef.current?.focus()
|
||||
},
|
||||
{ enableOnFormTags: true }
|
||||
)
|
||||
const navigate = useNavigate()
|
||||
const [query, setQuery] = useState('')
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const settingsAsSearchable = useMemo(
|
||||
() =>
|
||||
Object.entries(settings.state.context).flatMap(
|
||||
([category, categorySettings]) =>
|
||||
Object.entries(categorySettings).flatMap(([settingName, setting]) => {
|
||||
const s = setting as Setting
|
||||
return ['project', 'user']
|
||||
.filter((l) => s.hideOnLevel !== l)
|
||||
.map((l) => ({
|
||||
category: decamelize(category, { separator: ' ' }),
|
||||
settingName: settingName,
|
||||
settingNameDisplay: decamelize(settingName, { separator: ' ' }),
|
||||
setting: s,
|
||||
level: l,
|
||||
}))
|
||||
})
|
||||
),
|
||||
[settings.state.context]
|
||||
)
|
||||
const [searchResults, setSearchResults] = useState(settingsAsSearchable)
|
||||
|
||||
const fuse = new Fuse(settingsAsSearchable, {
|
||||
keys: ['category', 'settingNameDisplay', 'setting.description'],
|
||||
includeScore: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const results = fuse.search(query).map((result) => result.item)
|
||||
setSearchResults(query.length > 0 ? results : settingsAsSearchable)
|
||||
}, [query])
|
||||
|
||||
function handleSelection({
|
||||
level,
|
||||
settingName,
|
||||
}: {
|
||||
category: string
|
||||
settingName: string
|
||||
setting: Setting<unknown>
|
||||
level: string
|
||||
}) {
|
||||
navigate(`?tab=${level}#${settingName}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Combobox onChange={handleSelection}>
|
||||
<div className="relative group">
|
||||
<div className="flex items-center gap-2 py-0.5 pr-1 pl-2 rounded border-solid border border-primary/10 dark:border-chalkboard-80 focus-within:border-primary dark:focus-within:border-chalkboard-30">
|
||||
<Combobox.Input
|
||||
ref={inputRef}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||
placeholder="Search settings (^.)"
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
spellCheck="false"
|
||||
autoFocus
|
||||
/>
|
||||
<CustomIcon
|
||||
name="search"
|
||||
className="w-5 h-5 rounded-sm bg-primary/10 text-primary group-focus-within:bg-primary group-focus-within:text-chalkboard-10"
|
||||
/>
|
||||
</div>
|
||||
<Combobox.Options className="absolute top-full mt-2 right-0 w-80 overflow-y-auto z-50 max-h-96 cursor-pointer bg-chalkboard-10 dark:bg-chalkboard-100 border border-solid border-primary dark:border-chalkboard-30 rounded">
|
||||
{searchResults?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={`${option.category}-${option.settingName}-${option.level}`}
|
||||
value={option}
|
||||
className="flex flex-col items-start gap-2 px-4 py-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
<p className="flex-grow text-base capitalize m-0 leading-none">
|
||||
{option.level} ·{' '}
|
||||
{decamelize(option.category, { separator: ' ' })} ·{' '}
|
||||
{option.settingNameDisplay}
|
||||
</p>
|
||||
{option.setting.description && (
|
||||
<p className="text-xs leading-tight text-chalkboard-70 dark:text-chalkboard-50">
|
||||
{option.setting.description}
|
||||
</p>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
</Combobox>
|
||||
)
|
||||
}
|
60
src/components/Settings/SettingsSection.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
|
||||
interface SettingsSectionProps extends React.HTMLProps<HTMLDivElement> {
|
||||
title: string
|
||||
description?: string
|
||||
className?: string
|
||||
parentLevel?: SettingsLevel | 'default'
|
||||
onFallback?: () => void
|
||||
settingHasChanged?: boolean
|
||||
headingClassName?: string
|
||||
}
|
||||
|
||||
export function SettingsSection({
|
||||
title,
|
||||
id,
|
||||
description,
|
||||
className,
|
||||
children,
|
||||
parentLevel,
|
||||
settingHasChanged,
|
||||
onFallback,
|
||||
headingClassName = 'text-lg font-normal capitalize tracking-wide',
|
||||
}: SettingsSectionProps) {
|
||||
return (
|
||||
<section
|
||||
id={id}
|
||||
className={
|
||||
'group p-2 pl-0 grid grid-cols-2 gap-6 items-start ' +
|
||||
className +
|
||||
(settingHasChanged ? ' border-0 border-l-2 -ml-0.5 border-primary' : '')
|
||||
}
|
||||
>
|
||||
<div className="ml-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className={headingClassName}>{title}</h2>
|
||||
{onFallback && parentLevel && settingHasChanged && (
|
||||
<button
|
||||
onClick={onFallback}
|
||||
className="hidden group-hover:block group-focus-within:block border-none p-0 hover:bg-warn-10 dark:hover:bg-warn-80 focus:bg-warn-10 dark:focus:bg-warn-80 focus:outline-none"
|
||||
>
|
||||
<CustomIcon name="refresh" className="w-4 h-4" />
|
||||
<span className="sr-only">Roll back {title}</span>
|
||||
<Tooltip position="right">
|
||||
Roll back to match {parentLevel}
|
||||
</Tooltip>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<p className="mt-2 text-xs text-chalkboard-80 dark:text-chalkboard-30">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
28
src/components/Settings/SettingsTabButton.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
|
||||
interface SettingsTabButtonProps {
|
||||
checked: boolean
|
||||
icon: CustomIconName
|
||||
text: string
|
||||
}
|
||||
|
||||
export function SettingsTabButton(props: SettingsTabButtonProps) {
|
||||
const { checked, icon, text } = props
|
||||
return (
|
||||
<div
|
||||
className={`cursor-pointer select-none flex items-center gap-1 p-1 pr-2 -mb-[1px] border-0 border-b ${
|
||||
checked
|
||||
? 'border-primary'
|
||||
: 'border-chalkboard-20 dark:border-chalkboard-30 hover:bg-primary/20 dark:hover:bg-primary/50'
|
||||
}`}
|
||||
>
|
||||
<CustomIcon
|
||||
name={icon}
|
||||
className={
|
||||
'w-5 h-5 ' + (checked ? 'bg-primary !text-chalkboard-10' : '')
|
||||
}
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
39
src/components/Settings/SettingsTabs.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { RadioGroup } from '@headlessui/react'
|
||||
import { SettingsTabButton } from './SettingsTabButton'
|
||||
|
||||
interface SettingsTabButtonProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
showProjectTab: boolean
|
||||
}
|
||||
|
||||
export function SettingsTabs({
|
||||
value,
|
||||
onChange,
|
||||
showProjectTab,
|
||||
}: SettingsTabButtonProps) {
|
||||
return (
|
||||
<RadioGroup
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex justify-start pl-4 pr-5 gap-5 border-0 border-b border-b-chalkboard-20 dark:border-b-chalkboard-90"
|
||||
>
|
||||
<RadioGroup.Option value="user">
|
||||
{({ checked }) => (
|
||||
<SettingsTabButton checked={checked} icon="person" text="User" />
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
{showProjectTab && (
|
||||
<RadioGroup.Option value="project">
|
||||
{({ checked }) => (
|
||||
<SettingsTabButton
|
||||
checked={checked}
|
||||
icon="folder"
|
||||
text="This project"
|
||||
/>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
)}
|
||||
</RadioGroup>
|
||||
)
|
||||
}
|
@ -134,6 +134,10 @@ export const SettingsAuthProviderBase = ({
|
||||
},
|
||||
})
|
||||
},
|
||||
setClientTheme: (context) => {
|
||||
const opposingTheme = getOppositeTheme(context.app.theme.current)
|
||||
sceneInfra.theme = opposingTheme
|
||||
},
|
||||
setEngineEdges: (context) => {
|
||||
engineCommandManager.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
|
@ -1,7 +1,13 @@
|
||||
.toggle {
|
||||
@apply flex items-center gap-2 w-fit;
|
||||
--toggle-size: 1.25rem;
|
||||
@apply text-chalkboard-110;
|
||||
--toggle-size: 0.75rem;
|
||||
--padding: 0.25rem;
|
||||
--border: 1px;
|
||||
}
|
||||
|
||||
:global(.dark) .toggle {
|
||||
@apply text-chalkboard-10;
|
||||
}
|
||||
|
||||
.toggle:focus-within > span {
|
||||
@ -13,9 +19,12 @@
|
||||
}
|
||||
|
||||
.toggle > span {
|
||||
@apply relative rounded border border-chalkboard-110 hover:border-chalkboard-100 cursor-pointer;
|
||||
width: calc(2 * (var(--toggle-size) + var(--padding)));
|
||||
height: calc(var(--toggle-size) + var(--padding));
|
||||
@apply relative rounded border border-chalkboard-70 hover:border-chalkboard-80 cursor-pointer;
|
||||
border-width: var(--border);
|
||||
width: calc(
|
||||
2 * (var(--toggle-size) + var(--padding) * 2 - var(--border) * 2)
|
||||
);
|
||||
height: calc(var(--toggle-size) + var(--padding) * 2 - var(--border) * 2);
|
||||
}
|
||||
|
||||
:global(.dark) .toggle > span {
|
||||
@ -23,18 +32,26 @@
|
||||
}
|
||||
|
||||
.toggle > span::after {
|
||||
width: var(--toggle-size);
|
||||
height: var(--toggle-size);
|
||||
border-radius: calc(var(--toggle-size) / 8);
|
||||
content: '';
|
||||
@apply absolute w-4 h-4 rounded-sm bg-chalkboard-110;
|
||||
@apply absolute bg-chalkboard-70;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
translate: calc(-100% - var(--padding)) -50%;
|
||||
translate: calc(-100% - var(--padding) + var(--border)) -50%;
|
||||
transition: translate 0.08s ease-out;
|
||||
}
|
||||
|
||||
:global(.dark) .toggle > span::after {
|
||||
@apply bg-chalkboard-10;
|
||||
@apply bg-chalkboard-50;
|
||||
}
|
||||
|
||||
.toggle input:checked + span::after {
|
||||
translate: calc(50% - var(--padding)) -50%;
|
||||
translate: calc(50% - var(--padding) + var(--border)) -50%;
|
||||
@apply bg-chalkboard-110;
|
||||
}
|
||||
|
||||
:global(.dark) .toggle input:checked + span::after {
|
||||
@apply bg-chalkboard-10;
|
||||
}
|
||||
|
@ -19,7 +19,11 @@ export const Toggle = ({
|
||||
}: ToggleProps) => {
|
||||
return (
|
||||
<label className={`${styles.toggle} ${className}`}>
|
||||
{offLabel}
|
||||
<p
|
||||
className={checked ? 'text-chalkboard-70 dark:text-chalkboard-50' : ''}
|
||||
>
|
||||
{offLabel}
|
||||
</p>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={name}
|
||||
@ -28,7 +32,11 @@ export const Toggle = ({
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span></span>
|
||||
{onLabel}
|
||||
<p
|
||||
className={!checked ? 'text-chalkboard-70 dark:text-chalkboard-50' : ''}
|
||||
>
|
||||
{onLabel}
|
||||
</p>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
@ -189,6 +189,7 @@ export default class EditorManager {
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
'Equip tangential arc to',
|
||||
'Equip rectangle tool',
|
||||
]
|
||||
|
||||
if (!this._modelingEvent) {
|
||||
|
@ -167,6 +167,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
if (pos === null) return null
|
||||
const dom = document.createElement('div')
|
||||
dom.classList.add('documentation')
|
||||
dom.style.zIndex = '99999999'
|
||||
if (this.allowHTMLContent) dom.innerHTML = formatContents(contents)
|
||||
else dom.textContent = formatContents(contents)
|
||||
return { pos, end, create: (view) => ({ dom }), above: true }
|
||||
|
@ -7,5 +7,8 @@ export const VITE_KC_API_BASE_URL = import.meta.env.VITE_KC_API_BASE_URL
|
||||
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
|
||||
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
|
||||
.VITE_KC_CONNECTION_TIMEOUT_MS
|
||||
export const VITE_KC_DEV_TOKEN = import.meta.env.VITE_KC_DEV_TOKEN as
|
||||
| string
|
||||
| undefined
|
||||
export const TEST = import.meta.env.TEST
|
||||
export const DEV = import.meta.env.DEV
|
||||
|
@ -1,57 +0,0 @@
|
||||
import useResizeObserver from '@react-hook/resize-observer'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface Rect {
|
||||
top: number
|
||||
left: number
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an element id and uses React refs to create a CSS clip-path rule to apply to a backdrop element
|
||||
* which excludes the element with the given id, creating a "highlight" effect.
|
||||
* @param highlightId
|
||||
*/
|
||||
export function useBackdropHighlight(target: string): string {
|
||||
const [clipPath, setClipPath] = useState('')
|
||||
const [elem, setElem] = useState(document.getElementById(target))
|
||||
|
||||
// Build the actual clip path string, cutting out the target element
|
||||
function buildClipPath({ top, left, height, width }: Rect) {
|
||||
const windowWidth = window.innerWidth
|
||||
const windowHeight = window.innerHeight
|
||||
|
||||
return `
|
||||
path(evenodd, "M0 0 l${windowWidth} 0 l0 ${windowHeight} l-${windowWidth} 0 Z \
|
||||
M${left} ${top} l${width} 0 l0 ${height} l-${width} 0 Z")
|
||||
`
|
||||
}
|
||||
|
||||
// initial setup of clip path
|
||||
useEffect(() => {
|
||||
if (!elem) {
|
||||
const newElem = document.getElementById(target)
|
||||
if (newElem === null) {
|
||||
throw new Error(
|
||||
`Could not find element with id "${target}" to highlight`
|
||||
)
|
||||
}
|
||||
setElem(document.getElementById(target))
|
||||
return
|
||||
}
|
||||
|
||||
const { top, left, height, width } = elem.getBoundingClientRect()
|
||||
setClipPath(buildClipPath({ top, left, height, width }))
|
||||
}, [elem, target])
|
||||
|
||||
// update clip path on resize
|
||||
useResizeObserver(elem, (entry) => {
|
||||
const { height, width } = entry.contentRect
|
||||
// the top and left are relative to the viewport, so we need to get the target's position
|
||||
const { top, left } = entry.target.getBoundingClientRect()
|
||||
setClipPath(buildClipPath({ top, left, height, width }))
|
||||
})
|
||||
|
||||
return clipPath
|
||||
}
|
@ -14,8 +14,8 @@ export function useEngineConnectionSubscriptions() {
|
||||
event: 'highlight_set_entity',
|
||||
callback: ({ data }) => {
|
||||
if (data?.entity_id) {
|
||||
const sourceRange =
|
||||
engineCommandManager.artifactMap?.[data.entity_id]?.range
|
||||
const sourceRange = engineCommandManager.artifactMap?.[data.entity_id]
|
||||
?.range || [0, 0]
|
||||
editorManager.setHighlightRange(sourceRange)
|
||||
} else if (
|
||||
!editorManager.highlightRange ||
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
ExtrudeGroup,
|
||||
} from 'lang/wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { codeManager, editorManager } from 'lib/singletons'
|
||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||
|
||||
export class KclManager {
|
||||
private _ast: Program = {
|
||||
@ -187,6 +187,7 @@ export class KclManager {
|
||||
ast,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
})
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
enterEditMode(programMemory, this.engineCommandManager)
|
||||
this.isExecuting = false
|
||||
// Check the cancellation token for this execution before applying side effects
|
||||
@ -219,7 +220,7 @@ export class KclManager {
|
||||
const newCode = recast(ast)
|
||||
const newAst = this.safeParse(newCode)
|
||||
if (!newAst) return
|
||||
codeManager.updateCodeStateEditor(newCode)
|
||||
codeManager.updateCodeEditor(newCode)
|
||||
// Write the file to disk.
|
||||
await codeManager.writeToFile()
|
||||
await this?.engineCommandManager?.waitForReady
|
||||
@ -316,7 +317,7 @@ export class KclManager {
|
||||
if (execute) {
|
||||
// Call execute on the set ast.
|
||||
// Update the code state and editor.
|
||||
codeManager.updateCodeStateEditor(newCode)
|
||||
codeManager.updateCodeEditor(newCode)
|
||||
// Write the file to disk.
|
||||
await codeManager.writeToFile()
|
||||
await this.executeAst(astWithUpdatedSource)
|
||||
|
@ -11,8 +11,7 @@ const PERSIST_CODE_TOKEN = 'persistCode'
|
||||
|
||||
export default class CodeManager {
|
||||
private _code: string = bracket
|
||||
private _updateState: (arg: string) => void = () => {}
|
||||
private _updateEditor: (arg: string) => void = () => {}
|
||||
#updateState: (arg: string) => void = () => {}
|
||||
private _currentFilePath: string | null = null
|
||||
|
||||
constructor() {
|
||||
@ -46,7 +45,7 @@ export default class CodeManager {
|
||||
}
|
||||
|
||||
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
|
||||
this._updateState = setCode
|
||||
this.#updateState = setCode
|
||||
}
|
||||
|
||||
updateCurrentFilePath(path: string) {
|
||||
@ -57,18 +56,20 @@ export default class CodeManager {
|
||||
updateCodeState(code: string): void {
|
||||
if (this._code !== code) {
|
||||
this.code = code
|
||||
this._updateState(code)
|
||||
this.#updateState(code)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the code in the editor.
|
||||
updateCodeEditor(code: string): void {
|
||||
const lastCode = this._code
|
||||
this.code = code
|
||||
this._updateEditor(code)
|
||||
if (editorManager.editorView) {
|
||||
editorManager.editorView.dispatch({
|
||||
changes: { from: 0, to: lastCode.length, insert: code },
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editorManager.editorView.state.doc.length,
|
||||
insert: code,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -77,8 +78,7 @@ export default class CodeManager {
|
||||
updateCodeStateEditor(code: string): void {
|
||||
if (this._code !== code) {
|
||||
this.code = code
|
||||
this._updateState(code)
|
||||
this._updateEditor(code)
|
||||
this.#updateState(code)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,7 +335,9 @@ class EngineConnection {
|
||||
// Information on the connect transaction
|
||||
|
||||
const createPeerConnection = () => {
|
||||
this.pc = new RTCPeerConnection()
|
||||
this.pc = new RTCPeerConnection({
|
||||
bundlePolicy: 'max-bundle',
|
||||
})
|
||||
|
||||
// Data channels MUST BE specified before SDP offers because requesting
|
||||
// them affects what our needs are!
|
||||
@ -540,6 +542,27 @@ class EngineConnection {
|
||||
},
|
||||
}
|
||||
})
|
||||
this.unreliableDataChannel.addEventListener('message', (event) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.engineCommandManager.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
if (
|
||||
result.type === 'highlight_set_entity' &&
|
||||
result?.data?.sequence &&
|
||||
result?.data.sequence > this.engineCommandManager.inSequence
|
||||
) {
|
||||
this.engineCommandManager.inSequence = result.data.sequence
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -652,7 +675,9 @@ failed cmd type was ${artifactThatFailed?.commandType}`
|
||||
// No ICE servers can be valid in a local dev. env.
|
||||
if (ice_servers?.length === 0) {
|
||||
console.warn('No ICE servers')
|
||||
this.pc?.setConfiguration({})
|
||||
this.pc?.setConfiguration({
|
||||
bundlePolicy: 'max-bundle',
|
||||
})
|
||||
} else {
|
||||
// When we set the Configuration, we want to always force
|
||||
// iceTransportPolicy to 'relay', since we know the topology
|
||||
@ -660,6 +685,7 @@ failed cmd type was ${artifactThatFailed?.commandType}`
|
||||
// talk to the engine in any configuration /other/ than relay
|
||||
// from a infra POV.
|
||||
this.pc?.setConfiguration({
|
||||
bundlePolicy: 'max-bundle',
|
||||
iceServers: ice_servers,
|
||||
iceTransportPolicy: 'relay',
|
||||
})
|
||||
@ -848,7 +874,7 @@ type CommandTypes = Models['ModelingCmd_type']['type'] | 'batch'
|
||||
|
||||
type UnreliableResponses = Extract<
|
||||
Models['OkModelingCmdResponse_type'],
|
||||
{ type: 'highlight_set_entity' }
|
||||
{ type: 'highlight_set_entity' | 'camera_drag_move' }
|
||||
>
|
||||
interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
||||
event: T
|
||||
@ -974,7 +1000,7 @@ export class EngineCommandManager {
|
||||
}
|
||||
|
||||
const additionalSettings = settings.enableSSAO ? '&post_effect=ssao' : ''
|
||||
const pool = this.pool == undefined ? '' : `&pool=${this.pool}`
|
||||
const pool = this.pool === undefined ? '' : `&pool=${this.pool}`
|
||||
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}${pool}`
|
||||
this.engineConnection = new EngineConnection({
|
||||
engineCommandManager: this,
|
||||
@ -1056,32 +1082,6 @@ export class EngineCommandManager {
|
||||
setIsStreamReady(false)
|
||||
},
|
||||
onConnectionStarted: (engineConnection) => {
|
||||
engineConnection?.pc?.addEventListener('datachannel', (event) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
|
||||
unreliableDataChannel.addEventListener('message', (event) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
if (
|
||||
result?.data?.sequence &&
|
||||
result?.data.sequence > this.inSequence &&
|
||||
result.type === 'highlight_set_entity'
|
||||
) {
|
||||
this.inSequence = result.data.sequence
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', (event) => {
|
||||
@ -1361,14 +1361,11 @@ export class EngineCommandManager {
|
||||
callback,
|
||||
}: Subscription<T>): () => void {
|
||||
const localUnsubscribeId = uuidv4()
|
||||
const otherEventCallbacks = this.subscriptions[event]
|
||||
if (otherEventCallbacks) {
|
||||
otherEventCallbacks[localUnsubscribeId] = callback
|
||||
} else {
|
||||
this.subscriptions[event] = {
|
||||
[localUnsubscribeId]: callback,
|
||||
}
|
||||
if (!this.subscriptions[event]) {
|
||||
this.subscriptions[event] = {}
|
||||
}
|
||||
this.subscriptions[event][localUnsubscribeId] = callback
|
||||
|
||||
return () => this.unSubscribeTo(event, localUnsubscribeId)
|
||||
}
|
||||
private unSubscribeTo(event: ModelTypes, id: string) {
|
||||
@ -1379,14 +1376,10 @@ export class EngineCommandManager {
|
||||
callback,
|
||||
}: UnreliableSubscription<T>): () => void {
|
||||
const localUnsubscribeId = uuidv4()
|
||||
const otherEventCallbacks = this.unreliableSubscriptions[event]
|
||||
if (otherEventCallbacks) {
|
||||
otherEventCallbacks[localUnsubscribeId] = callback
|
||||
} else {
|
||||
this.unreliableSubscriptions[event] = {
|
||||
[localUnsubscribeId]: callback,
|
||||
}
|
||||
if (!this.unreliableSubscriptions[event]) {
|
||||
this.unreliableSubscriptions[event] = {}
|
||||
}
|
||||
this.unreliableSubscriptions[event][localUnsubscribeId] = callback
|
||||
return () => this.unSubscribeToUnreliable(event, localUnsubscribeId)
|
||||
}
|
||||
private unSubscribeToUnreliable(
|
||||
|
@ -1,17 +1,19 @@
|
||||
export const bracket = `// Shelf Bracket
|
||||
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
|
||||
|
||||
// Define our bracket feet lengths
|
||||
const shelfMountL = 8 // The length of the bracket holding up the shelf is 6 inches
|
||||
const wallMountL = 6 // the length of the bracket
|
||||
|
||||
// Define constants required to calculate the thickness needed to support 300 lbs
|
||||
const sigmaAllow = 35000 // psi
|
||||
const width = 6 // inch
|
||||
const p = 300 // Force on shelf - lbs
|
||||
const distance = 12 // inches
|
||||
const M = 12 * 300 / 2 // Moment experienced at fixed end of bracket
|
||||
const FOS = 2 // Factor of safety of 2
|
||||
const shelfMountL = 8 // The length of the bracket holding up the shelf is 6 inches
|
||||
const wallMountL = 8 // the length of the bracket
|
||||
const L = 12 // inches
|
||||
const M = L * p / 2 // Moment experienced at fixed end of bracket
|
||||
const FOS = 2 // Factor of safety of 2 to be conservative
|
||||
|
||||
|
||||
// Calculate the thickness off the allowable bending stress and factor of safety
|
||||
// Calculate the thickness off the bending stress and factor of safety
|
||||
const thickness = sqrt(6 * M * FOS / (width * sigmaAllow))
|
||||
|
||||
// 0.25 inch fillet radius
|
||||
|
@ -14,9 +14,21 @@ interface ModelingAppFile {
|
||||
const save_ = async (file: ModelingAppFile) => {
|
||||
try {
|
||||
if (isTauri()) {
|
||||
const extension = file.name.split('.').pop() || null
|
||||
let extensions: string[] = []
|
||||
if (extension !== null) {
|
||||
extensions.push(extension)
|
||||
}
|
||||
|
||||
// Open a dialog to save the file.
|
||||
const filePath = await save({
|
||||
defaultPath: file.name,
|
||||
filters: [
|
||||
{
|
||||
name: 'model',
|
||||
extensions: extensions,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (filePath === null) {
|
||||
@ -48,7 +60,7 @@ export async function exportSave(data: ArrayBuffer) {
|
||||
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
|
||||
let uintArray = new Uint8Array(data)
|
||||
|
||||
const files: ModelingAppFile[] = deserialize_files(uintArray)
|
||||
let files: ModelingAppFile[] = deserialize_files(uintArray)
|
||||
|
||||
if (files.length > 1) {
|
||||
let zip = new JSZip()
|
||||
|
@ -65,3 +65,15 @@ export function getThemeColorForEngine(theme: Themes) {
|
||||
? { r: dark, g: dark, b: dark, a: 1 }
|
||||
: { r: light, g: light, b: light, a: 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* ThreeJS uses hex values for colors
|
||||
* @param theme
|
||||
* @returns
|
||||
*/
|
||||
export function getThemeColorForThreeJs(theme: Themes) {
|
||||
const resolvedTheme = getResolvedTheme(theme)
|
||||
const dark = 0x1c1c1c
|
||||
const light = 0xf9f9f9
|
||||
return resolvedTheme === Themes.Dark ? dark : light
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { createMachine, assign } from 'xstate'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import withBaseURL from '../lib/withBaseURL'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||
import { getUser as getUserTauri } from 'lib/tauri'
|
||||
|
||||
const SKIP_AUTH =
|
||||
@ -112,14 +112,25 @@ export const authMachine = createMachine<UserContext, Events>(
|
||||
)
|
||||
|
||||
async function getUser(context: UserContext) {
|
||||
const token =
|
||||
context.token && context.token !== ''
|
||||
? context.token
|
||||
: getCookie(COOKIE_NAME) ||
|
||||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
||||
VITE_KC_DEV_TOKEN
|
||||
const url = withBaseURL('/user')
|
||||
const headers: { [key: string]: string } = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
if (!context.token && isTauri()) throw new Error('No token found')
|
||||
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
if (SKIP_AUTH) return LOCAL_USER
|
||||
if (!token && isTauri()) throw new Error('No token found')
|
||||
if (token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
|
||||
if (SKIP_AUTH)
|
||||
return {
|
||||
user: LOCAL_USER,
|
||||
token,
|
||||
}
|
||||
|
||||
const userPromise = !isTauri()
|
||||
? fetch(url, {
|
||||
@ -136,13 +147,8 @@ async function getUser(context: UserContext) {
|
||||
if ('error_code' in user) throw new Error(user.message)
|
||||
|
||||
return {
|
||||
user,
|
||||
token:
|
||||
context.token && context.token !== ''
|
||||
? context.token
|
||||
: getCookie(COOKIE_NAME) ||
|
||||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
||||
'',
|
||||
user: user as Models['User_type'],
|
||||
token,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
|
||||
export const settingsMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlnXwFsB7CMYnKfAV20zVgG0AGAXUSgADrVidMtbEJAAPRABYAHAFZ8vAEwA2FVq0aNAZgCcARl0B2ADQgAnolO8LvfBYUXT+48YW+tCgF8Am1QMZgIiUgoqAENhYXw0AAswajA+QSQQUXEsKRl5BCM1DQUDMo0VQwVjJxt7BFMDJXwNWo0LFSU3c0DgkFCsXAiScgAlOHQAAkow4YyZHIl8rMKAWn18Q39q3ScVFQ1NFXqHXiVTfGNqhSdeXi1DJQ1TIJD0IbxCUbIAKgWsks8tJVopjFp8KZjEpqi9lKZDE0TnYzgZXO5PG0fH4+u85l9IuRQlMYsRiFNsGAAO4zD7hAEiMTLEGgda6fAKJoIiyIkwWYwWawoxpGFxaHkKFS1a7woL9bD0OAyQbhRZM4EFRBrUyOLY7SVafaHTSnBDGDqQiz6DRKbwqTxvAZ04bfUhq3KSFlyBxHdQIhR6HTQmoC02OUwKPW7GrPdy8QxygJAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlnXwEMAHW-Ae2wCNHqAnCHKZNatAFdYAbQAMAXUShajWJizNpIAB6IAbAFZN+AOwAWAIwAOYwE4AzGYBM+-ZosAaEAE9Eh62LP51ls+v0LMWt1awMAX3DnVAweAiJSCio6BjQACzAAWzAAYUZiRg5xKSQQWXlFbGU1BA9vQ0N1CwCxdVbdY1DnNwQzPp8zTTFje1D1QwtjSOj0LFx4knJKNHxMxggwYh58DYAzakFiNABVbAVi5XKFTCVSmotNY3w7YysHRuNDTXV1bvdG7w-IKTcbaazWCzTEAxOZ4QiLJIrFL4dJZMAXUpXSrVDTGazPMQPR4GXRBAx-XoGfDWIadMx4n6EqEwuLwxLLVbrTbbNKYKBpLb8tAAUWgcAxMjk11uoHumn0+DEw2sJkMulCgWsFL6YnwfX0ELsYg61jMumZs1ZCXIACU4OgAATLWGiSSXKXYu4aLz4UwWBr6DqBYYUzSePXqUlBLxmyZmC2xeZs8gxB3UYjEJ2W+YSsoem5VL0IKy6z6EsJifQ2Czq0MTHzq8Yjfz6MQmBMu5NkABUuaxBZxCDD+DD5lJgUjxssFP0xl0I+0pm06uMmg8kSiIGwXPgpRZ83dFQHRYAtA0LCO2tZjGIHJNdB5fq5EK3dc1OsNGkrA+oO1bFoe0qFrKiAnlol7BDed5zo+FK+Doc7qhCNaVv4UwbkAA */
|
||||
id: 'Settings',
|
||||
predictableActionArguments: true,
|
||||
context: {} as ReturnType<typeof createSettings>,
|
||||
@ -59,6 +59,7 @@ export const settingsMachine = createMachine(
|
||||
'setThemeClass',
|
||||
'setEngineTheme',
|
||||
'persistSettings',
|
||||
'setClientTheme',
|
||||
],
|
||||
},
|
||||
|
||||
@ -83,6 +84,7 @@ export const settingsMachine = createMachine(
|
||||
'setClientSideSceneUnits',
|
||||
'Execute AST',
|
||||
'persistSettings',
|
||||
'setClientTheme',
|
||||
],
|
||||
},
|
||||
|
||||
@ -96,6 +98,7 @@ export const settingsMachine = createMachine(
|
||||
'setClientSideSceneUnits',
|
||||
'Execute AST',
|
||||
'persistSettings',
|
||||
'setClientTheme',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { SettingsSection } from 'routes/Settings'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import {
|
||||
CameraSystem,
|
||||
cameraMouseDragGuards,
|
||||
cameraSystems,
|
||||
} from 'lib/cameraControls'
|
||||
import { SettingsSection } from 'components/Settings/SettingsSection'
|
||||
|
||||
export default function Units() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||
|
||||
export default function CodeEditor() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
@ -12,14 +11,6 @@ export default function CodeEditor() {
|
||||
|
||||
return (
|
||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||
<div
|
||||
className="fixed inset-0 bg-black opacity-50 dark:opacity-80 pointer-events-none"
|
||||
style={
|
||||
{
|
||||
/*clipPath: useBackdropHighlight('code-pane')*/
|
||||
}
|
||||
}
|
||||
></div>
|
||||
<div
|
||||
className={
|
||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
|
@ -12,7 +12,7 @@ export default function FutureWork() {
|
||||
|
||||
useEffect(() => {
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
codeManager.updateCodeEditor(bracket)
|
||||
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
||||
// If the engine is ready, promptly execute the loaded code
|
||||
kclManager.executeCode(true)
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||
import { bracketWidthConstantLine } from 'lib/exampleKcl'
|
||||
|
||||
export default function InteractiveNumbers() {
|
||||
@ -13,14 +12,6 @@ export default function InteractiveNumbers() {
|
||||
|
||||
return (
|
||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||
<div
|
||||
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||
style={
|
||||
{
|
||||
/*clipPath: useBackdropHighlight('code-pane')*/
|
||||
}
|
||||
}
|
||||
></div>
|
||||
<div
|
||||
className={
|
||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
|
@ -56,7 +56,7 @@ function OnboardingWithNewFile() {
|
||||
dismiss={dismiss}
|
||||
next={() => {
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
codeManager.updateCodeEditor(bracket)
|
||||
kclManager.executeCode(true)
|
||||
next()
|
||||
}}
|
||||
@ -80,7 +80,7 @@ function OnboardingWithNewFile() {
|
||||
dismiss={dismiss}
|
||||
next={() => {
|
||||
void createAndOpenNewProject()
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
codeManager.updateCodeEditor(bracket)
|
||||
dismiss()
|
||||
}}
|
||||
nextText="Make a new project"
|
||||
@ -113,7 +113,7 @@ export default function Introduction() {
|
||||
const isStarterCode = currentCode === '' || currentCode === bracket
|
||||
|
||||
useEffect(() => {
|
||||
if (codeManager.code === '') codeManager.updateCodeStateEditor(bracket)
|
||||
if (codeManager.code === '') codeManager.updateCodeEditor(bracket)
|
||||
}, [])
|
||||
|
||||
return isStarterCode ? (
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { bracketThicknessCalculationLine } from 'lib/exampleKcl'
|
||||
@ -29,14 +28,6 @@ export default function ParametricModeling() {
|
||||
|
||||
return (
|
||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||
<div
|
||||
className="fixed inset-0 bg-black dark:bg-black-80 opacity-50 pointer-events-none"
|
||||
style={
|
||||
{
|
||||
/*clipPath: useBackdropHighlight('code-pane')*/
|
||||
}
|
||||
}
|
||||
></div>
|
||||
<div
|
||||
className={
|
||||
'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||
@ -73,6 +64,15 @@ export default function ParametricModeling() {
|
||||
</em>
|
||||
.
|
||||
</p>
|
||||
<figure className="my-4 w-2/3 mx-auto">
|
||||
<img
|
||||
src={`/onboarding-bracket-dimensions${getImageTheme()}.png`}
|
||||
alt="Bracket Dimensions"
|
||||
/>
|
||||
<figcaption className="text-small italic text-center">
|
||||
Bracket Dimensions
|
||||
</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
<OnboardingButtons
|
||||
currentSlug={onboardingPaths.PARAMETRIC_MODELING}
|
||||
|
@ -11,7 +11,7 @@ export default function Sketching() {
|
||||
|
||||
useEffect(() => {
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor('')
|
||||
codeManager.updateCodeEditor('')
|
||||
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
||||
// If the engine is ready, promptly execute the loaded code
|
||||
kclManager.executeCode(true)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||
import { type BaseUnit, baseUnitsUnion } from 'lib/settings/settingsTypes'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { SettingsSection } from '../Settings'
|
||||
import { SettingsSection } from 'components/Settings/SettingsSection'
|
||||
import { useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { ActionButton } from '../components/ActionButton'
|
||||
import {
|
||||
SetEventTypes,
|
||||
SettingsLevel,
|
||||
WildcardSetEvent,
|
||||
} from 'lib/settings/settingsTypes'
|
||||
import { Toggle } from 'components/Toggle/Toggle'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { SetEventTypes, SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { paths } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -14,27 +9,32 @@ import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/tauriFS'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import toast from 'react-hot-toast'
|
||||
import React, { Fragment, useMemo, useRef, useState } from 'react'
|
||||
import { Fragment, useEffect, useRef } from 'react'
|
||||
import { Setting } from 'lib/settings/initialSettings'
|
||||
import decamelize from 'decamelize'
|
||||
import { Event } from 'xstate'
|
||||
import { Dialog, RadioGroup, Transition } from '@headlessui/react'
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import {
|
||||
getSettingInputType,
|
||||
shouldHideSetting,
|
||||
shouldShowSettingInput,
|
||||
} from 'lib/settings/settingsUtils'
|
||||
import { getInitialDefaultDir, showInFolder } from 'lib/tauri'
|
||||
import { SettingsSearchBar } from 'components/Settings/SettingsSearchBar'
|
||||
import { SettingsTabs } from 'components/Settings/SettingsTabs'
|
||||
import { SettingsSection } from 'components/Settings/SettingsSection'
|
||||
import { SettingsFieldInput } from 'components/Settings/SettingsFieldInput'
|
||||
|
||||
export const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
|
||||
export const Settings = () => {
|
||||
const navigate = useNavigate()
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const close = () => navigate(location.pathname.replace(paths.SETTINGS, ''))
|
||||
const location = useLocation()
|
||||
const isFileSettings = location.pathname.includes(paths.FILE)
|
||||
const searchParamTab =
|
||||
(searchParams.get('tab') as SettingsLevel) ??
|
||||
(isFileSettings ? 'project' : 'user')
|
||||
const projectPath =
|
||||
isFileSettings && isTauri()
|
||||
? decodeURI(
|
||||
@ -44,9 +44,7 @@ export const Settings = () => {
|
||||
.slice(0, decodeURI(location.pathname).lastIndexOf(sep()))
|
||||
)
|
||||
: undefined
|
||||
const [settingsLevel, setSettingsLevel] = useState<SettingsLevel>(
|
||||
isFileSettings ? 'project' : 'user'
|
||||
)
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
const dotDotSlash = useDotDotSlash()
|
||||
useHotkeys('esc', () => navigate(dotDotSlash()))
|
||||
@ -70,6 +68,20 @@ export const Settings = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll to the hash on load if it exists
|
||||
useEffect(() => {
|
||||
console.log('hash', location.hash)
|
||||
if (location.hash) {
|
||||
const element = document.getElementById(location.hash.slice(1))
|
||||
if (element) {
|
||||
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
;(
|
||||
element.querySelector('input, select, textarea') as HTMLInputElement
|
||||
)?.focus()
|
||||
}
|
||||
}
|
||||
}, [location.hash])
|
||||
|
||||
return (
|
||||
<Transition appear show={true} as={Fragment}>
|
||||
<Dialog
|
||||
@ -102,42 +114,24 @@ export const Settings = () => {
|
||||
<Dialog.Panel className="rounded relative mx-auto bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-3xl w-full max-h-[66vh] shadow-lg flex flex-col gap-8">
|
||||
<div className="p-5 pb-0 flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold">Settings</h1>
|
||||
<button
|
||||
onClick={close}
|
||||
className="p-0 m-0 focus:ring-0 focus:outline-none border-none hover:bg-destroy-10 focus:bg-destroy-10 dark:hover:bg-destroy-80/50 dark:focus:bg-destroy-80/50"
|
||||
data-testid="settings-close-button"
|
||||
>
|
||||
<CustomIcon name="close" className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="flex gap-4 items-start">
|
||||
<SettingsSearchBar />
|
||||
<button
|
||||
onClick={close}
|
||||
className="p-0 m-0 focus:ring-0 focus:outline-none border-none hover:bg-destroy-10 focus:bg-destroy-10 dark:hover:bg-destroy-80/50 dark:focus:bg-destroy-80/50"
|
||||
data-testid="settings-close-button"
|
||||
>
|
||||
<CustomIcon name="close" className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<RadioGroup
|
||||
value={settingsLevel}
|
||||
onChange={setSettingsLevel}
|
||||
className="flex justify-start pl-4 pr-5 gap-5 border-0 border-b border-b-chalkboard-20 dark:border-b-chalkboard-90"
|
||||
>
|
||||
<RadioGroup.Option value="user">
|
||||
{({ checked }) => (
|
||||
<SettingsTabButton
|
||||
checked={checked}
|
||||
icon="person"
|
||||
text="User"
|
||||
/>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
{isFileSettings && (
|
||||
<RadioGroup.Option value="project">
|
||||
{({ checked }) => (
|
||||
<SettingsTabButton
|
||||
checked={checked}
|
||||
icon="folder"
|
||||
text="This project"
|
||||
/>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
)}
|
||||
</RadioGroup>
|
||||
<SettingsTabs
|
||||
value={searchParamTab}
|
||||
onChange={(v) => setSearchParams((p) => ({ ...p, tab: v }))}
|
||||
showProjectTab={isFileSettings}
|
||||
/>
|
||||
<div
|
||||
className="flex-1 grid items-stretch pl-4 pr-5 pb-5 gap-4 overflow-hidden"
|
||||
className="flex-1 grid items-stretch pl-4 pr-5 pb-5 gap-2 overflow-hidden"
|
||||
style={{ gridTemplateColumns: 'auto 1fr' }}
|
||||
>
|
||||
<div className="flex w-32 flex-col gap-3 pr-2 py-1 border-0 border-r border-r-chalkboard-20 dark:border-r-chalkboard-90">
|
||||
@ -146,7 +140,7 @@ export const Settings = () => {
|
||||
// Filter out categories that don't have any non-hidden settings
|
||||
Object.values(categorySettings).some(
|
||||
(setting: Setting) =>
|
||||
!shouldHideSetting(setting, settingsLevel)
|
||||
!shouldHideSetting(setting, searchParamTab)
|
||||
)
|
||||
)
|
||||
.map(([category]) => (
|
||||
@ -156,7 +150,7 @@ export const Settings = () => {
|
||||
scrollRef.current
|
||||
?.querySelector(`#category-${category}`)
|
||||
?.scrollIntoView({
|
||||
block: 'nearest',
|
||||
block: 'center',
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
@ -170,7 +164,7 @@ export const Settings = () => {
|
||||
scrollRef.current
|
||||
?.querySelector(`#settings-resets`)
|
||||
?.scrollIntoView({
|
||||
block: 'nearest',
|
||||
block: 'center',
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
@ -183,7 +177,7 @@ export const Settings = () => {
|
||||
scrollRef.current
|
||||
?.querySelector(`#settings-about`)
|
||||
?.scrollIntoView({
|
||||
block: 'nearest',
|
||||
block: 'center',
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
@ -193,19 +187,19 @@ export const Settings = () => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative overflow-y-auto">
|
||||
<div ref={scrollRef} className="flex flex-col gap-6 px-2">
|
||||
<div ref={scrollRef} className="flex flex-col gap-4 px-2">
|
||||
{Object.entries(context)
|
||||
.filter(([_, categorySettings]) =>
|
||||
// Filter out categories that don't have any non-hidden settings
|
||||
Object.values(categorySettings).some(
|
||||
(setting) => !shouldHideSetting(setting, settingsLevel)
|
||||
(setting) => !shouldHideSetting(setting, searchParamTab)
|
||||
)
|
||||
)
|
||||
.map(([category, categorySettings]) => (
|
||||
<Fragment key={category}>
|
||||
<h2
|
||||
id={`category-${category}`}
|
||||
className="text-2xl mt-6 first-of-type:mt-0 capitalize font-bold"
|
||||
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
|
||||
>
|
||||
{decamelize(category, { separator: ' ' })}
|
||||
</h2>
|
||||
@ -214,44 +208,50 @@ export const Settings = () => {
|
||||
// Filter out settings that don't have a Component or inputType
|
||||
// or are hidden on the current level or the current platform
|
||||
(item: [string, Setting<unknown>]) =>
|
||||
shouldShowSettingInput(item[1], settingsLevel)
|
||||
shouldShowSettingInput(item[1], searchParamTab)
|
||||
)
|
||||
.map(([settingName, s]) => {
|
||||
const setting = s as Setting
|
||||
const parentValue =
|
||||
setting[setting.getParentLevel(settingsLevel)]
|
||||
setting[setting.getParentLevel(searchParamTab)]
|
||||
return (
|
||||
<SettingsSection
|
||||
title={decamelize(settingName, {
|
||||
separator: ' ',
|
||||
})}
|
||||
key={`${category}-${settingName}-${settingsLevel}`}
|
||||
id={settingName}
|
||||
className={
|
||||
location.hash === `#${settingName}`
|
||||
? 'bg-primary/10 dark:bg-chalkboard-90'
|
||||
: ''
|
||||
}
|
||||
key={`${category}-${settingName}-${searchParamTab}`}
|
||||
description={setting.description}
|
||||
settingHasChanged={
|
||||
setting[settingsLevel] !== undefined &&
|
||||
setting[settingsLevel] !==
|
||||
setting.getFallback(settingsLevel)
|
||||
setting[searchParamTab] !== undefined &&
|
||||
setting[searchParamTab] !==
|
||||
setting.getFallback(searchParamTab)
|
||||
}
|
||||
parentLevel={setting.getParentLevel(
|
||||
settingsLevel
|
||||
searchParamTab
|
||||
)}
|
||||
onFallback={() =>
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
level: searchParamTab,
|
||||
value:
|
||||
parentValue !== undefined
|
||||
? parentValue
|
||||
: setting.getFallback(settingsLevel),
|
||||
: setting.getFallback(searchParamTab),
|
||||
},
|
||||
} as SetEventTypes)
|
||||
}
|
||||
>
|
||||
<GeneratedSetting
|
||||
<SettingsFieldInput
|
||||
category={category}
|
||||
settingName={settingName}
|
||||
settingsLevel={settingsLevel}
|
||||
settingsLevel={searchParamTab}
|
||||
setting={setting}
|
||||
/>
|
||||
</SettingsSection>
|
||||
@ -298,7 +298,7 @@ export const Settings = () => {
|
||||
? decodeURIComponent(projectPath)
|
||||
: undefined
|
||||
)
|
||||
showInFolder(paths[settingsLevel])
|
||||
showInFolder(paths[searchParamTab])
|
||||
}}
|
||||
icon={{
|
||||
icon: 'folder',
|
||||
@ -368,226 +368,3 @@ export const Settings = () => {
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
|
||||
interface SettingsSectionProps extends React.PropsWithChildren {
|
||||
title: string
|
||||
description?: string
|
||||
className?: string
|
||||
parentLevel?: SettingsLevel | 'default'
|
||||
onFallback?: () => void
|
||||
settingHasChanged?: boolean
|
||||
headingClassName?: string
|
||||
}
|
||||
|
||||
export function SettingsSection({
|
||||
title,
|
||||
description,
|
||||
className,
|
||||
children,
|
||||
parentLevel,
|
||||
settingHasChanged,
|
||||
onFallback,
|
||||
headingClassName = 'text-base font-normal capitalize tracking-wide',
|
||||
}: SettingsSectionProps) {
|
||||
return (
|
||||
<section
|
||||
className={
|
||||
'group grid grid-cols-2 gap-6 items-start ' +
|
||||
className +
|
||||
(settingHasChanged ? ' border-0 border-l-2 -ml-0.5 border-primary' : '')
|
||||
}
|
||||
>
|
||||
<div className="ml-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className={headingClassName}>{title}</h2>
|
||||
{onFallback && parentLevel && settingHasChanged && (
|
||||
<button
|
||||
onClick={onFallback}
|
||||
className="hidden group-hover:block group-focus-within:block border-none p-0 hover:bg-warn-10 dark:hover:bg-warn-80 focus:bg-warn-10 dark:focus:bg-warn-80 focus:outline-none"
|
||||
>
|
||||
<CustomIcon name="refresh" className="w-4 h-4" />
|
||||
<span className="sr-only">Roll back {title}</span>
|
||||
<Tooltip position="right">
|
||||
Roll back to match {parentLevel}
|
||||
</Tooltip>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<p className="mt-2 text-xs text-chalkboard-80 dark:text-chalkboard-30">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
interface GeneratedSettingProps {
|
||||
// We don't need the fancy types here,
|
||||
// it doesn't help us with autocomplete or anything
|
||||
category: string
|
||||
settingName: string
|
||||
settingsLevel: SettingsLevel
|
||||
setting: Setting<unknown>
|
||||
}
|
||||
|
||||
function GeneratedSetting({
|
||||
category,
|
||||
settingName,
|
||||
settingsLevel,
|
||||
setting,
|
||||
}: GeneratedSettingProps) {
|
||||
const {
|
||||
settings: { context, send },
|
||||
} = useSettingsAuthContext()
|
||||
const options = useMemo(() => {
|
||||
return setting.commandConfig &&
|
||||
'options' in setting.commandConfig &&
|
||||
setting.commandConfig.options
|
||||
? setting.commandConfig.options instanceof Array
|
||||
? setting.commandConfig.options
|
||||
: setting.commandConfig.options(
|
||||
{
|
||||
argumentsToSubmit: {
|
||||
level: settingsLevel,
|
||||
},
|
||||
},
|
||||
context
|
||||
)
|
||||
: []
|
||||
}, [setting, settingsLevel, context])
|
||||
const inputType = getSettingInputType(setting)
|
||||
|
||||
switch (inputType) {
|
||||
case 'component':
|
||||
return (
|
||||
setting.Component && (
|
||||
<setting.Component
|
||||
value={setting[settingsLevel] || setting.getFallback(settingsLevel)}
|
||||
updateValue={(newValue) => {
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: newValue,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)
|
||||
case 'boolean':
|
||||
return (
|
||||
<Toggle
|
||||
offLabel="Off"
|
||||
onLabel="On"
|
||||
onChange={(e) =>
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: Boolean(e.target.checked),
|
||||
},
|
||||
} as SetEventTypes)
|
||||
}
|
||||
checked={Boolean(
|
||||
setting[settingsLevel] !== undefined
|
||||
? setting[settingsLevel]
|
||||
: setting.getFallback(settingsLevel)
|
||||
)}
|
||||
name={`${category}-${settingName}`}
|
||||
data-testid={`${category}-${settingName}`}
|
||||
/>
|
||||
)
|
||||
case 'options':
|
||||
return (
|
||||
<select
|
||||
name={`${category}-${settingName}`}
|
||||
data-testid={`${category}-${settingName}`}
|
||||
className="p-1 bg-transparent border rounded-sm border-chalkboard-30 w-full"
|
||||
value={String(
|
||||
setting[settingsLevel] || setting.getFallback(settingsLevel)
|
||||
)}
|
||||
onChange={(e) =>
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: e.target.value,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}
|
||||
>
|
||||
{options &&
|
||||
options.length > 0 &&
|
||||
options.map((option) => (
|
||||
<option key={option.name} value={String(option.value)}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
case 'string':
|
||||
return (
|
||||
<input
|
||||
name={`${category}-${settingName}`}
|
||||
data-testid={`${category}-${settingName}`}
|
||||
type="text"
|
||||
className="p-1 bg-transparent border rounded-sm border-chalkboard-30 w-full"
|
||||
defaultValue={String(
|
||||
setting[settingsLevel] || setting.getFallback(settingsLevel)
|
||||
)}
|
||||
onBlur={(e) => {
|
||||
if (
|
||||
setting[settingsLevel] === undefined
|
||||
? setting.getFallback(settingsLevel) !== e.target.value
|
||||
: setting[settingsLevel] !== e.target.value
|
||||
) {
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: e.target.value,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<p className="text-destroy-70 dark:text-destroy-20">
|
||||
No component or input type found for setting {settingName} in category{' '}
|
||||
{category}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
interface SettingsTabButtonProps {
|
||||
checked: boolean
|
||||
icon: CustomIconName
|
||||
text: string
|
||||
}
|
||||
|
||||
function SettingsTabButton(props: SettingsTabButtonProps) {
|
||||
const { checked, icon, text } = props
|
||||
return (
|
||||
<div
|
||||
className={`cursor-pointer select-none flex items-center gap-1 p-1 pr-2 -mb-[1px] border-0 border-b ${
|
||||
checked
|
||||
? 'border-primary'
|
||||
: 'border-chalkboard-20 dark:border-chalkboard-30 hover:bg-primary/20 dark:hover:bg-primary/50'
|
||||
}`}
|
||||
>
|
||||
<CustomIcon
|
||||
name={icon}
|
||||
className={
|
||||
'w-5 h-5 ' + (checked ? 'bg-primary !text-chalkboard-10' : '')
|
||||
}
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
189
src/wasm-lib/Cargo.lock
generated
@ -155,9 +155,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@ -240,13 +240,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -257,7 +257,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -280,7 +280,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -324,9 +324,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
@ -596,7 +596,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -846,7 +846,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -870,7 +870,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -881,7 +881,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -922,7 +922,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
"synstructure 0.13.1",
|
||||
]
|
||||
|
||||
@ -976,7 +976,7 @@ dependencies = [
|
||||
"rustfmt-wrapper",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -988,7 +988,7 @@ dependencies = [
|
||||
"diesel_table_macro_syntax",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -997,7 +997,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
|
||||
dependencies = [
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1026,7 +1026,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1108,7 +1108,7 @@ checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1325,7 +1325,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1436,7 +1436,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1808,16 +1808,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interceptor"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5927883184e6a819b22d5e4f5f7bc7ca134fde9b2026fbddd8d95249746ba21e"
|
||||
checksum = "5b12e186d2a4c21225df6beb8ae5d81817c928da12e7ce78d0953fc74d88b590"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rtcp",
|
||||
"rtp 0.9.0",
|
||||
"rtp",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"waitgroup",
|
||||
@ -1895,13 +1895,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.1.53"
|
||||
version = "0.1.54"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"base64 0.22.0",
|
||||
"base64 0.22.1",
|
||||
"bson",
|
||||
"chrono",
|
||||
"clap",
|
||||
@ -1959,14 +1959,14 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
|
||||
checksum = "2c6e12eb45fd9a28c8e99dbdef54556246b39acee14e4aa6f0fc43636caa62d9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -2003,9 +2003,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-execution-plan"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae99665cd699f8800da8ea4b01889c0c9c61619d2a9dc62d1d5028f1b21110bd"
|
||||
checksum = "0936396491f132c163e9411a3dd699e5b2daa49c19436691c5153f7ad2d4953d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"gltf-json",
|
||||
@ -2032,7 +2032,7 @@ checksum = "0611fc9b9786175da21d895ffa0f65039e19c9111e94a41b7af999e3b95f045f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2048,9 +2048,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.21"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e326955e8f315590a1926c17ff6a6082d3013f472c881aba56d73bfa170cf5b3"
|
||||
checksum = "c390a9f643aa00d2c3ffe880f1810066f52a13877ec3ecab858ff53cad84bd5e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -2083,7 +2083,7 @@ checksum = "385775cc9d5bf25579f3029824ca1a6e7ab1b7c338e972ec8e8fcefff801f353"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2457,7 +2457,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2614,7 +2614,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.7.5",
|
||||
"structmeta 0.2.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2628,7 +2628,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.2",
|
||||
"structmeta 0.3.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2694,7 +2694,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2841,9 +2841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -3239,19 +3239,6 @@ dependencies = [
|
||||
"webrtc-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e60482acbe8afb31edf6b1413103b7bca7a65004c423b3c3993749a083994fbe"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"webrtc-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtp"
|
||||
version = "0.10.0"
|
||||
@ -3432,9 +3419,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
|
||||
checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
@ -3449,14 +3436,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
|
||||
checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3532,9 +3519,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
version = "1.0.201"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -3550,24 +3537,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.198"
|
||||
version = "1.0.201"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.26.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
|
||||
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3590,7 +3577,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3611,7 +3598,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3786,7 +3773,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive 0.2.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3798,7 +3785,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive 0.3.0",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3809,7 +3796,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3820,7 +3807,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3892,9 +3879,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
version = "2.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3927,7 +3914,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4013,22 +4000,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4135,7 +4122,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4298,7 +4285,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4326,7 +4313,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4403,7 +4390,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -4599,7 +4586,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4675,7 +4662,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -4710,7 +4697,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -4795,9 +4782,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "webrtc"
|
||||
version = "0.9.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91e7cf018f7185552bf6a5dd839f4ed9827aea33b746763c9a215f84a0d0b34"
|
||||
checksum = "1fbdf025f0fa62f4bf252b2fb0cff0a04d3eac2021c440096649e62f4e48553d"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
@ -4810,9 +4797,9 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"rcgen",
|
||||
"regex",
|
||||
"ring 0.16.20",
|
||||
"ring 0.17.8",
|
||||
"rtcp",
|
||||
"rtp 0.9.0",
|
||||
"rtp",
|
||||
"rustls 0.21.11",
|
||||
"sdp",
|
||||
"serde",
|
||||
@ -4852,9 +4839,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webrtc-dtls"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b140b953f986e97828aa33ec6318186b05d862bee689efbc57af04a243e832"
|
||||
checksum = "188ce061a2371bdf4df54b136c89a6df243ed0ef6b03431b4bd18482cd718dfe"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
@ -4872,7 +4859,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"rand_core 0.6.4",
|
||||
"rcgen",
|
||||
"ring 0.16.20",
|
||||
"ring 0.17.8",
|
||||
"rustls 0.21.11",
|
||||
"sec1",
|
||||
"serde",
|
||||
@ -4932,7 +4919,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"rand 0.8.5",
|
||||
"rtp 0.10.0",
|
||||
"rtp",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@ -4955,9 +4942,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webrtc-srtp"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1db1f36c1c81e4b1e531c0b9678ba0c93809e196ce62122d87259bb71c03b9f"
|
||||
checksum = "383b0f0f73ee6cce396bdbc4d54ec661861a59eae9fc988914c1a8d82c5ac272"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
@ -4968,7 +4955,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"log",
|
||||
"rtcp",
|
||||
"rtp 0.9.0",
|
||||
"rtp",
|
||||
"sha1",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
@ -5282,7 +5269,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5302,7 +5289,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -62,11 +62,11 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
kittycad = { version = "0.3.0", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-execution-plan = "0.1.5"
|
||||
kittycad = { version = "0.3.1", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-execution-plan = "0.1.6"
|
||||
kittycad-execution-plan-macros = "0.1.9"
|
||||
kittycad-execution-plan-traits = "0.1.14"
|
||||
kittycad-modeling-cmds = "0.2.21"
|
||||
kittycad-modeling-cmds = "0.2.24"
|
||||
kittycad-modeling-session = "0.1.4"
|
||||
|
||||
[[test]]
|
||||
|
@ -18,12 +18,12 @@ once_cell = "1.19.0"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde = { version = "1.0.201", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.60", features = ["full"] }
|
||||
syn = { version = "2.0.61", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.82"
|
||||
anyhow = "1.0.83"
|
||||
expectorate = "1.1.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
rustfmt-wrapper = "0.2.1"
|
||||
|
@ -14,7 +14,7 @@ kittycad-execution-plan-traits = { workspace = true }
|
||||
kittycad-execution-plan-macros = { workspace = true }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
kittycad-modeling-session = { workspace = true }
|
||||
thiserror = "1.0.59"
|
||||
thiserror = "1.0.60"
|
||||
tokio = { version = "1.37.0", features = ["macros", "rt"] }
|
||||
twenty-twenty = "0.7.0"
|
||||
uuid = "1.8"
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 79 KiB |
@ -642,14 +642,15 @@ impl Callable for StartSketchAt {
|
||||
// Next, enter sketch mode.
|
||||
stack_api_call(
|
||||
&mut instructions,
|
||||
ModelingCmdEndpoint::SketchModeEnable,
|
||||
ModelingCmdEndpoint::EnableSketchMode,
|
||||
None,
|
||||
Uuid::new_v4().into(),
|
||||
[
|
||||
Some(axes.z).into_parts(),
|
||||
vec![false.into()], // animated
|
||||
vec![false.into()], // ortho mode
|
||||
vec![plane_id.into()],
|
||||
vec![false.into()], // adjust camera
|
||||
vec![false.into()], // animated
|
||||
vec![false.into()], // ortho mode
|
||||
vec![plane_id.into()], // entity id (plane in this case)
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -15,7 +15,7 @@ databake = "0.1.7"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2.0.60", features = ["full"] }
|
||||
syn = { version = "2.0.61", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.1.53"
|
||||
version = "0.1.54"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
@ -11,10 +11,10 @@ keywords = ["kcl", "KittyCAD", "CAD"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.82", features = ["backtrace"] }
|
||||
async-recursion = "1.1.0"
|
||||
anyhow = { version = "1.0.83", features = ["backtrace"] }
|
||||
async-recursion = "1.1.1"
|
||||
async-trait = "0.1.80"
|
||||
base64 = "0.22.0"
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.4", default-features = false, optional = true }
|
||||
dashmap = "5.5.3"
|
||||
@ -32,11 +32,11 @@ mime_guess = "2.0.4"
|
||||
parse-display = "0.9.0"
|
||||
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
|
||||
ropey = "1.6.1"
|
||||
schemars = { version = "0.8.16", features = ["impl_json_schema", "url", "uuid1"] }
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
||||
serde = { version = "1.0.201", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "1.0.59"
|
||||
thiserror = "1.0.60"
|
||||
toml = "0.8.12"
|
||||
ts-rs = { version = "7.1.1", features = ["uuid-impl", "url-impl", "chrono-impl"] }
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
@ -73,7 +73,7 @@ debug = true
|
||||
debug = true # Flamegraphs of benchmarks require accurate debug symbols
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = "0.22.0"
|
||||
base64 = "0.22.1"
|
||||
convert_case = "0.6.0"
|
||||
criterion = "0.5.1"
|
||||
expectorate = "1.1.0"
|
||||
|
@ -36,6 +36,28 @@ pub struct Program {
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
// Check if we are in the non code meta.
|
||||
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
|
||||
for node in &meta.start {
|
||||
if node.contains(pos) {
|
||||
// We only care about the shebang.
|
||||
if let NonCodeValue::Shebang { value: _ } = &node.value {
|
||||
let source_range: SourceRange = node.into();
|
||||
return Some(Hover::Comment {
|
||||
value: r#"The `#!` at the start of a script, known as a shebang, specifies the path to the interpreter that should execute the script. This line is not necessary for your `kcl` to run in the modeling-app. You can safely delete it. If you wish to learn more about what you _can_ do with a shebang, read this doc: [zoo.dev/docs/faq/shebang](https://zoo.dev/docs/faq/shebang)."#.to_string(),
|
||||
range: source_range.to_lsp_range(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value = self.get_value_for_position(pos)?;
|
||||
|
||||
value.get_hover_value_for_position(pos, code)
|
||||
}
|
||||
|
||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
let indentation = options.get_indentation(indentation_level);
|
||||
let result = self
|
||||
@ -814,6 +836,18 @@ pub struct NonCodeNode {
|
||||
pub value: NonCodeValue,
|
||||
}
|
||||
|
||||
impl From<NonCodeNode> for SourceRange {
|
||||
fn from(value: NonCodeNode) -> Self {
|
||||
Self([value.start, value.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&NonCodeNode> for SourceRange {
|
||||
fn from(value: &NonCodeNode) -> Self {
|
||||
Self([value.start, value.end])
|
||||
}
|
||||
}
|
||||
|
||||
impl NonCodeNode {
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
self.start <= pos && pos <= self.end
|
||||
@ -821,6 +855,7 @@ impl NonCodeNode {
|
||||
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
NonCodeValue::Shebang { value } => value.clone(),
|
||||
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
|
||||
@ -830,6 +865,7 @@ impl NonCodeNode {
|
||||
|
||||
pub fn format(&self, indentation: &str) -> String {
|
||||
match &self.value {
|
||||
NonCodeValue::Shebang { value } => format!("{}\n\n", value),
|
||||
NonCodeValue::InlineComment {
|
||||
value,
|
||||
style: CommentStyle::Line,
|
||||
@ -882,6 +918,15 @@ pub enum CommentStyle {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum NonCodeValue {
|
||||
/// A shebang.
|
||||
/// This is a special type of comment that is at the top of the file.
|
||||
/// It looks like this:
|
||||
/// ```python,no_run
|
||||
/// #!/usr/bin/env python
|
||||
/// ```
|
||||
Shebang {
|
||||
value: String,
|
||||
},
|
||||
/// An inline comment.
|
||||
/// Here are examples:
|
||||
/// `1 + 1 // This is an inline comment`.
|
||||
@ -2976,6 +3021,10 @@ pub enum Hover {
|
||||
parameter_index: u32,
|
||||
range: LspRange,
|
||||
},
|
||||
Comment {
|
||||
value: String,
|
||||
range: LspRange,
|
||||
},
|
||||
}
|
||||
|
||||
/// Format options.
|
||||
@ -3273,6 +3322,117 @@ fn ghi = (x) => {
|
||||
assert_eq!(recasted, r#""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_shebang_only() {
|
||||
let some_program_string = r#"#!/usr/local/env zoo kcl"#;
|
||||
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let result = parser.ast();
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().to_string(),
|
||||
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([21, 24])], message: "Unexpected end of file. The compiler expected a function body items (functions are made up of variable declarations, expressions, and return statements, each of those is a possible body item" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_shebang() {
|
||||
let some_program_string = r#"#!/usr/local/env zoo kcl
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
"#;
|
||||
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
|
||||
let recasted = program.recast(&Default::default(), 0);
|
||||
assert_eq!(
|
||||
recasted,
|
||||
r#"#!/usr/local/env zoo kcl
|
||||
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_shebang_new_lines() {
|
||||
let some_program_string = r#"#!/usr/local/env zoo kcl
|
||||
|
||||
|
||||
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
"#;
|
||||
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
|
||||
let recasted = program.recast(&Default::default(), 0);
|
||||
assert_eq!(
|
||||
recasted,
|
||||
r#"#!/usr/local/env zoo kcl
|
||||
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_shebang_with_comments() {
|
||||
let some_program_string = r#"#!/usr/local/env zoo kcl
|
||||
|
||||
// Yo yo my comments.
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
"#;
|
||||
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
|
||||
let recasted = program.recast(&Default::default(), 0);
|
||||
assert_eq!(
|
||||
recasted,
|
||||
r#"#!/usr/local/env zoo kcl
|
||||
|
||||
// Yo yo my comments.
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_large_file() {
|
||||
let some_program_string = r#"// define constants
|
||||
|
@ -67,7 +67,7 @@ impl ProgramMemory {
|
||||
|
||||
/// Add to the program memory.
|
||||
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
||||
if self.root.get(key).is_some() {
|
||||
if self.root.contains_key(key) {
|
||||
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
||||
message: format!("Cannot redefine {}", key),
|
||||
source_ranges: vec![source_range],
|
||||
|