Compare commits
77 Commits
derive-doc
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
a947e23ef9 | |||
76de64780c | |||
2d804dee2b | |||
c094d0ced1 | |||
a90fe3c066 | |||
f3ea7fd0e2 | |||
704ff0df62 | |||
eba17e92b7 | |||
d9d700624e | |||
1e547aeef0 | |||
22899c9e69 | |||
25702f454c | |||
11faf86983 | |||
67d73382b1 | |||
15b9f43f2c | |||
d28555a070 | |||
7bf116629f | |||
fe45b5b54d | |||
bcbd3f5bfd | |||
959433e357 | |||
b3695c060d | |||
09374081ea | |||
3d40650cab | |||
d18e35b7ea | |||
596c9a0ee6 | |||
9106a81c77 | |||
8b5ebe67b2 | |||
a7f539eca6 | |||
f4c87c994c | |||
3d4ae05145 | |||
f5ee346408 | |||
544a7565e3 | |||
979046f7e6 | |||
07ae5106b9 | |||
e9ae484332 | |||
2a86ffc09a | |||
93903a8a47 | |||
45e85a1f81 | |||
c187989d18 | |||
47b5fa1459 | |||
d85781ef99 | |||
233f81a879 | |||
8ac0bf4953 | |||
24caeece65 | |||
f493cf11a0 | |||
594e888c12 | |||
b32295e1d9 | |||
e0838c1198 | |||
f03f34d8be | |||
108bb4ee90 | |||
092d459026 | |||
c4f7296e32 | |||
1cbd422d7f | |||
849685a986 | |||
359b3c1f35 | |||
f4ff5e43f2 | |||
daf7350c9e | |||
a829cdb006 | |||
1a7a19ee85 | |||
b045a89854 | |||
221f037eaa | |||
a93b72f7e1 | |||
63f36cbcbf | |||
79b50ef7d4 | |||
3d16dcd30d | |||
d605d4a029 | |||
77f51530f9 | |||
76480f1a43 | |||
f850f80de1 | |||
15ebbe6947 | |||
01beba42da | |||
509e372ed2 | |||
b0417114af | |||
0360a4021b | |||
6f36371e6d | |||
ebcc19e757 | |||
84cbcddff1 |
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas
|
||||
|
12
.github/dependabot.yml
vendored
@ -9,15 +9,27 @@ updates:
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src-tauri/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
|
@ -7,23 +7,23 @@ on:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-criterion.yml
|
||||
- .github/workflows/cargo-bench.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-criterion.yml
|
||||
- .github/workflows/cargo-bench.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
name: cargo criterion
|
||||
name: cargo bench
|
||||
jobs:
|
||||
cargocriterion:
|
||||
name: cargo criterion
|
||||
cargo-bench:
|
||||
name: Benchmark with iai
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -31,10 +31,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cargo install cargo-criterion
|
||||
sudo apt update
|
||||
sudo apt install -y valgrind
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
- name: Benchmark kcl library
|
||||
shell: bash
|
||||
run: |-
|
||||
cd src/wasm-lib/kcl; cargo criterion
|
||||
cd src/wasm-lib/kcl; cargo bench -- iai
|
||||
|
26
.github/workflows/ci.yml
vendored
@ -125,6 +125,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-latest, windows-latest]
|
||||
env:
|
||||
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
||||
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -144,10 +147,12 @@ jobs:
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install -y
|
||||
libgtk-3-dev
|
||||
libgtksourceview-3.0-dev
|
||||
webkit2gtk-4.0
|
||||
libappindicator3-dev
|
||||
libayatana-appindicator3-dev
|
||||
webkit2gtk-driver
|
||||
libsoup-3.0-dev
|
||||
libjavascriptcoregtk-4.1-dev
|
||||
libwebkit2gtk-4.1-dev
|
||||
at-spi2-core
|
||||
xvfb
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
@ -161,7 +166,9 @@ jobs:
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# TODO: re-enable for Windows builds, see https://github.com/tauri-apps/tauri/issues/9045
|
||||
- name: Setup Rust cache
|
||||
if: matrix.os != 'windows-latest'
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
@ -224,14 +231,14 @@ jobs:
|
||||
with:
|
||||
includeRelease: false
|
||||
includeDebug: true
|
||||
args: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
||||
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||
|
||||
- name: Build the app (release) and sign
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
@ -240,7 +247,7 @@ jobs:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
||||
with:
|
||||
args: "${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
|
||||
args: "${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
@ -250,10 +257,11 @@ jobs:
|
||||
with:
|
||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||
|
||||
# TODO: re-enable linux e2e tests when possible
|
||||
- name: Run e2e tests (linux only)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: false
|
||||
run: |
|
||||
cargo install tauri-driver@0.1.3
|
||||
cargo install tauri-driver
|
||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
export VITE_KC_API_BASE_URL
|
||||
xvfb-run yarn test:e2e:tauri
|
||||
|
5
.github/workflows/generate-website-docs.yml
vendored
@ -15,7 +15,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
generate-website-docs:
|
||||
name: generate-website-docs
|
||||
name: generate-website-docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -56,6 +56,9 @@ jobs:
|
||||
gh pr create --title "Update KCL docs" \
|
||||
--body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \
|
||||
--head "$NEW_BRANCH" \
|
||||
--reviewer jessfraz \
|
||||
--reviewer irev-dev \
|
||||
--reviewer franknoirot \
|
||||
--base main || true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
1
.gitignore
vendored
@ -51,5 +51,6 @@ e2e/playwright/export-snapshots/*
|
||||
|
||||
## generated files
|
||||
src/**/*.typegen.ts
|
||||
src-tauri/gen
|
||||
|
||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
|
@ -281,7 +281,7 @@ https://github.com/KittyCAD/modeling-app/assets/29681384/6f5e8e85-1003-4fd9-be7f
|
||||
<details>
|
||||
|
||||
<summary>
|
||||
Ps for the debug panel, the following JSON is useful for snapping the camera
|
||||
PS: for the debug panel, the following JSON is useful for snapping the camera
|
||||
</summary>
|
||||
|
||||
```JSON
|
||||
|
@ -42165,7 +42165,7 @@
|
||||
"format": "double"
|
||||
},
|
||||
"center": {
|
||||
"description": "The center about which to make th pattern. This is a 2D vector.",
|
||||
"description": "The center about which to make the pattern. This is a 2D vector.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
@ -44168,7 +44168,7 @@
|
||||
"minItems": 3
|
||||
},
|
||||
"center": {
|
||||
"description": "The center about which to make th pattern. This is a 3D vector.",
|
||||
"description": "The center about which to make the pattern. This is a 3D vector.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
@ -1,10 +1,16 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { secrets } from './secrets'
|
||||
import { getUtils } from './test-utils'
|
||||
import waitOn from 'wait-on'
|
||||
import { Themes } from '../../src/lib/theme'
|
||||
import { initialSettings } from '../../src/lib/settings/initialSettings'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { secrets } from './secrets'
|
||||
import {
|
||||
TEST_SETTINGS,
|
||||
TEST_SETTINGS_KEY,
|
||||
TEST_SETTINGS_CORRUPTED,
|
||||
TEST_SETTINGS_ONBOARDING,
|
||||
} from './storageStates'
|
||||
|
||||
/*
|
||||
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
||||
@ -30,24 +36,19 @@ test.beforeEach(async ({ context, page }) => {
|
||||
resources: ['tcp:3000'],
|
||||
timeout: 5000,
|
||||
})
|
||||
await context.addInitScript(async (token) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(
|
||||
'SETTINGS_PERSIST_KEY',
|
||||
JSON.stringify({
|
||||
baseUnit: 'in',
|
||||
cameraControls: 'KittyCAD',
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: 'project-$nnn',
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'system',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
}, secrets.token)
|
||||
|
||||
await context.addInitScript(
|
||||
async ({ token, settingsKey, settings }) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||
}
|
||||
)
|
||||
// kill animations, speeds up tests and reduced flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
})
|
||||
@ -324,9 +325,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('executes on load', async ({ page, context }) => {
|
||||
test('executes on load', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
@ -356,9 +357,9 @@ test('executes on load', async ({ page, context }) => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('re-executes', async ({ page, context }) => {
|
||||
test('re-executes', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async (token) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', `const myVar = 5`)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
@ -528,70 +529,104 @@ test('Auto complete works', async ({ page }) => {
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
|
||||
// Stored settings validation test
|
||||
test('Stored settings are validated and fall back to defaults', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
// Override beforeEach test setup
|
||||
// with corrupted settings
|
||||
await context.addInitScript(async () => {
|
||||
const storedSettings = JSON.parse(
|
||||
localStorage.getItem('SETTINGS_PERSIST_KEY') || '{}'
|
||||
)
|
||||
|
||||
// Corrupt the settings
|
||||
storedSettings.baseUnit = 'invalid'
|
||||
storedSettings.cameraControls = `() => alert('hack the planet')`
|
||||
storedSettings.defaultDirectory = 123
|
||||
storedSettings.defaultProjectName = false
|
||||
|
||||
localStorage.setItem('SETTINGS_PERSIST_KEY', JSON.stringify(storedSettings))
|
||||
})
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Error validating persisted settings:`, {
|
||||
exact: false,
|
||||
})
|
||||
).toBeVisible()
|
||||
await page.goto('/')
|
||||
|
||||
// Check the settings were reset
|
||||
const storedSettings = JSON.parse(
|
||||
const storedSettings = TOML.parse(
|
||||
await page.evaluate(
|
||||
() => localStorage.getItem('SETTINGS_PERSIST_KEY') || '{}'
|
||||
({ settingsKey }) => localStorage.getItem(settingsKey) || '{}',
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
)
|
||||
await expect(storedSettings.baseUnit).toBe(initialSettings.baseUnit)
|
||||
await expect(storedSettings.cameraControls).toBe(
|
||||
initialSettings.cameraControls
|
||||
)
|
||||
await expect(storedSettings.defaultDirectory).toBe(
|
||||
initialSettings.defaultDirectory
|
||||
)
|
||||
await expect(storedSettings.defaultProjectName).toBe(
|
||||
initialSettings.defaultProjectName
|
||||
)
|
||||
) as { settings: SaveSettingsPayload }
|
||||
|
||||
expect(storedSettings.settings.app?.theme).toBe('dark')
|
||||
|
||||
// Check that the invalid settings were removed
|
||||
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
|
||||
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
|
||||
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
|
||||
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
|
||||
})
|
||||
|
||||
// Onboarding tests
|
||||
test('Onboarding redirects and code updating', async ({ page, context }) => {
|
||||
test('Project settings can be set and override user settings', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('Meta+Shift+,')
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||
).toBeVisible()
|
||||
await page
|
||||
.locator('select[name="app-theme"]')
|
||||
.selectOption({ value: 'light' })
|
||||
|
||||
// Verify the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "light" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
// Check that the user setting was not changed
|
||||
await page.getByRole('radio', { name: 'User' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
|
||||
|
||||
// Roll back to default "system" theme
|
||||
await page
|
||||
.getByText(
|
||||
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
|
||||
)
|
||||
.hover()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Roll back theme ; Has tooltip: Roll back to match default',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||
|
||||
// Check that the project setting did not change
|
||||
await page.getByRole('radio', { name: 'Project' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||
})
|
||||
|
||||
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await context.addInitScript(async () => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
|
||||
|
||||
const storedSettings = JSON.parse(
|
||||
localStorage.getItem('SETTINGS_PERSIST_KEY') || '{}'
|
||||
)
|
||||
storedSettings.onboardingStatus = '/export'
|
||||
localStorage.setItem('SETTINGS_PERSIST_KEY', JSON.stringify(storedSettings))
|
||||
})
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
@ -599,13 +634,13 @@ test('Onboarding redirects and code updating', async ({ page, context }) => {
|
||||
|
||||
// Test that the redirect happened
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/new/onboarding/export`
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that you come back to this page when you refresh
|
||||
await page.reload()
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/new/onboarding/export`
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
@ -779,129 +814,137 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
await selectionSequence()
|
||||
})
|
||||
|
||||
test('Command bar works and can change a setting', async ({ page }) => {
|
||||
// Brief boilerplate
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
test.describe('Command bar tests', () => {
|
||||
test('Command bar works and can change a setting', async ({ page }) => {
|
||||
// Brief boilerplate
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
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+/' })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
// 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+/' })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
const themeOption = page.getByRole('option', { name: 'Set Theme' })
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('system')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await expect(page.getByRole('option', { name: Themes.Dark })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(page.getByText(`Set Theme to "${Themes.Dark}"`)).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).toHaveClass(`body-bg ${Themes.Dark}`)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({ page, context }) => {
|
||||
await context.addInitScript(async (token) => {
|
||||
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(%)
|
||||
`
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('Select an option')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "system" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
test('Can extrude from the command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
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(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Search for extrude command and choose it
|
||||
await page.getByRole('option', { name: 'Extrude' }).click()
|
||||
await expect(page.locator('#arg-form > label')).toContainText(
|
||||
'Please select one face'
|
||||
)
|
||||
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
|
||||
// Make sure the stream is up
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click to select face and set distance
|
||||
await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Extrude' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// Assert that we're on the distance step
|
||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
// Assert that the an alternative variable name is chosen,
|
||||
// since the default variable name is already in use (distance)
|
||||
await page.getByRole('button', { name: 'Create new variable' }).click()
|
||||
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
|
||||
'distance001'
|
||||
)
|
||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled()
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
// Search for extrude command and choose it
|
||||
await page.getByRole('option', { name: 'Extrude' }).click()
|
||||
|
||||
// Review step and argument hotkeys
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit command' })
|
||||
).toBeEnabled()
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Distance 12', exact: false })
|
||||
).toBeDisabled()
|
||||
await page.keyboard.press('Enter')
|
||||
// Assert that we're on the distance step
|
||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
||||
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
// Assert that the an alternative variable name is chosen,
|
||||
// since the default variable name is already in use (distance)
|
||||
await page.getByRole('button', { name: 'Create new variable' }).click()
|
||||
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
|
||||
'distance001'
|
||||
)
|
||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled()
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
// Check that the code was updated
|
||||
await page.keyboard.press('Enter')
|
||||
// Unfortunately this indentation seems to matter for the test
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const distance = sqrt(20)
|
||||
// Review step and argument hotkeys
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit command' })
|
||||
).toBeEnabled()
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Distance 12', exact: false })
|
||||
).toBeDisabled()
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
|
||||
// Check that the code was updated
|
||||
await page.keyboard.press('Enter')
|
||||
// Unfortunately this indentation seems to matter for the test
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const distance = sqrt(20)
|
||||
const distance001 = 5 + 7
|
||||
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(%)
|
||||
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
||||
)
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('Can add multiple sketches', async ({ page }) => {
|
||||
@ -1029,9 +1072,9 @@ const part002 = startSketchOn('XY')
|
||||
)
|
||||
})
|
||||
|
||||
test('ProgramMemory can be serialised', async ({ page, context }) => {
|
||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part = startSketchOn('XY')
|
||||
@ -1070,7 +1113,6 @@ test('ProgramMemory can be serialised', async ({ page, context }) => {
|
||||
|
||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
const u = getUtils(page)
|
||||
const selectionsSnippets = {
|
||||
@ -1079,7 +1121,7 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
|
||||
extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)',
|
||||
editOnly: '|> startProfileAt([15.79, -14.6], %)',
|
||||
}
|
||||
await context.addInitScript(
|
||||
await page.addInitScript(
|
||||
async ({
|
||||
extrudeAndEditBlocked,
|
||||
extrudeAndEditBlockedInFunction,
|
||||
@ -1239,12 +1281,9 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
})
|
||||
|
||||
test('Can edit segments by dragging their handles', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
test('Can edit segments by dragging their handles', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
@ -1396,9 +1435,9 @@ test('Snap to close works (at any scale)', async ({ page }) => {
|
||||
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page, context }) => {
|
||||
test('Sketch on face', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
@ -1470,9 +1509,13 @@ test('Sketch on face', async ({ page, context }) => {
|
||||
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.updateCamPosition([452, -152, 1166])
|
||||
await u.closeDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
const pointToDragFirst = [691, 237]
|
||||
const pointToDragFirst = [787, 565]
|
||||
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
||||
@ -1486,7 +1529,9 @@ test('Sketch on face', async ({ page, context }) => {
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([2.81, -0.33], %)
|
||||
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
|
||||
process?.env?.CI ? 0.24 : 0.2
|
||||
}], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)`)
|
||||
|
||||
@ -1501,6 +1546,7 @@ test('Sketch on face', async ({ page, context }) => {
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
@ -1509,7 +1555,9 @@ test('Sketch on face', async ({ page, context }) => {
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([2.81, -0.33], %)
|
||||
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
|
||||
process?.env?.CI ? 0.24 : 0.2
|
||||
}], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`)
|
||||
|
@ -7,28 +7,26 @@ import { spawn } from 'child_process'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import JSZip from 'jszip'
|
||||
import path from 'path'
|
||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await context.addInitScript(async (token) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(
|
||||
'SETTINGS_PERSIST_KEY',
|
||||
JSON.stringify({
|
||||
baseUnit: 'in',
|
||||
cameraControls: 'KittyCAD',
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: 'project-$nnn',
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
}, secrets.token)
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// reducedMotion kills animations, which speeds up tests and reduces flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
|
||||
// set the default settings
|
||||
await page.addInitScript(
|
||||
async ({ token, settingsKey, settings }) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.setTimeout(60_000)
|
||||
@ -332,6 +330,22 @@ test('extrude on each default plane should be stable', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'SETTINGS_PERSIST_KEY',
|
||||
JSON.stringify({
|
||||
baseUnit: 'in',
|
||||
cameraControls: 'KittyCAD',
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: 'project-$nnn',
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
})
|
||||
const u = getUtils(page)
|
||||
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
||||
|> startProfileAt([7.00, 4.40], %)
|
||||
@ -353,20 +367,18 @@ test('extrude on each default plane should be stable', async ({
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await page.getByText('Code').click()
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
await page.getByText('Code').click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
|
||||
// clear code
|
||||
await u.removeCurrentCode()
|
||||
// add makeCode('XZ')
|
||||
await page.locator('.cm-content').fill(makeCode(plane))
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.locator('.cm-content').fill(makeCode(plane)),
|
||||
200
|
||||
)
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
@ -376,6 +388,7 @@ test('extrude on each default plane should be stable', async ({
|
||||
})
|
||||
await page.getByText('Code').click()
|
||||
}
|
||||
await runSnapshotsForOtherPlanes('XY')
|
||||
await runSnapshotsForOtherPlanes('-XY')
|
||||
|
||||
await runSnapshotsForOtherPlanes('XZ')
|
||||
@ -386,22 +399,6 @@ test('extrude on each default plane should be stable', async ({
|
||||
})
|
||||
|
||||
test('Draft segments should look right', async ({ page, context }) => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'SETTINGS_PERSIST_KEY',
|
||||
JSON.stringify({
|
||||
baseUnit: 'in',
|
||||
cameraControls: 'KittyCAD',
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: 'project-$nnn',
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
})
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -460,202 +457,187 @@ test('Draft segments should look right', async ({ page, context }) => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Client side scene scale should match engine scale inch', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'SETTINGS_PERSIST_KEY',
|
||||
JSON.stringify({
|
||||
baseUnit: 'in',
|
||||
cameraControls: 'KittyCAD',
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: 'project-$nnn',
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
test.describe('Client side scene scale should match engine scale', () => {
|
||||
test('Inch', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
})
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)`)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)
|
||||
|> tangentialArcTo([27.34, -3.08], %)`)
|
||||
|
||||
// click tangential arc tool again to unequip it
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('Client side scene scale should match engine scale mm', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'SETTINGS_PERSIST_KEY',
|
||||
JSON.stringify({
|
||||
baseUnit: 'mm',
|
||||
cameraControls: 'KittyCAD',
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: 'project-$nnn',
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'dark',
|
||||
unitSystem: 'metric',
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
})
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)`)
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.32], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([9.06, -12.22], %)
|
||||
|> line([9.14, 0], %)
|
||||
|> tangentialArcTo([27.34, -3.08], %)`)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)`)
|
||||
// click tangential arc tool again to unequip it
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)
|
||||
|> tangentialArcTo([694.43, -78.12], %)`)
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
test('Millimeters', async ({ page }) => {
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: {
|
||||
...TEST_SETTINGS,
|
||||
modeling: {
|
||||
...TEST_SETTINGS.modeling,
|
||||
defaultUnit: 'mm',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.32], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)`)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)
|
||||
|> tangentialArcTo([694.43, -78.12], %)`)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
// wait for execution done
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -666,14 +648,14 @@ test('Sketch on face with none z-up', async ({ page, context }) => {
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([1.4, 2.47], %)
|
||||
|> line({ to: [9.31, 10.55], tag: 'seg01' }, %)
|
||||
|> line([9.31, 10.55], %, 'seg01')
|
||||
|> line([11.91, -10.42], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([-2.89, 1.82], %)
|
||||
|> line([4.68, 3.05], %)
|
||||
|> line({ to: [0, -7.79], tag: 'seg02' }, %)
|
||||
|> line([0, -7.79], %, 'seg02')
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
`
|
||||
@ -700,6 +682,4 @@ const part002 = startSketchOn(part001, 'seg01')
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
})
|
||||
|
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 48 KiB |
46
e2e/playwright/storageStates.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { Themes } from 'lib/theme'
|
||||
|
||||
export const TEST_SETTINGS_KEY = '/user.toml'
|
||||
export const TEST_SETTINGS = {
|
||||
app: {
|
||||
theme: Themes.Dark,
|
||||
onboardingStatus: 'dismissed',
|
||||
projectDirectory: '',
|
||||
},
|
||||
modeling: {
|
||||
defaultUnit: 'in',
|
||||
mouseControls: 'KittyCAD',
|
||||
showDebugPanel: true,
|
||||
},
|
||||
projects: {
|
||||
defaultProjectName: 'project-$nnn',
|
||||
},
|
||||
textEditor: {
|
||||
textWrapping: true,
|
||||
},
|
||||
} satisfies Partial<SaveSettingsPayload>
|
||||
|
||||
export const TEST_SETTINGS_ONBOARDING = {
|
||||
...TEST_SETTINGS,
|
||||
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export ' },
|
||||
} satisfies Partial<SaveSettingsPayload>
|
||||
|
||||
export const TEST_SETTINGS_CORRUPTED = {
|
||||
app: {
|
||||
theme: Themes.Dark,
|
||||
onboardingStatus: 'dismissed',
|
||||
projectDirectory: 123 as any,
|
||||
},
|
||||
modeling: {
|
||||
defaultUnit: 'invalid' as any,
|
||||
mouseControls: `() => alert('hack the planet')` as any,
|
||||
showDebugPanel: true,
|
||||
},
|
||||
projects: {
|
||||
defaultProjectName: false as any,
|
||||
},
|
||||
textEditor: {
|
||||
textWrapping: true,
|
||||
},
|
||||
} satisfies Partial<SaveSettingsPayload>
|
@ -33,7 +33,7 @@ async function clearCommandLogs(page: Page) {
|
||||
}
|
||||
|
||||
async function expectCmdLog(page: Page, locatorStr: string) {
|
||||
await expect(page.locator(locatorStr)).toBeVisible()
|
||||
await expect(page.locator(locatorStr).last()).toBeVisible()
|
||||
}
|
||||
|
||||
async function waitForDefaultPlanesToBeVisible(page: Page) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { browser, $, expect } from '@wdio/globals'
|
||||
import fs from 'fs/promises'
|
||||
|
||||
const defaultDir = `${process.env.HOME}/Documents/zoo-modeling-app-projects`
|
||||
const documentsDir = `${process.env.HOME}/Documents`
|
||||
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
|
||||
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
||||
const newProjectDir = `${documentsDir}/a-different-directory`
|
||||
const userCodeDir = '/tmp/kittycad_user_code'
|
||||
|
||||
async function click(element: WebdriverIO.Element): Promise<void> {
|
||||
@ -10,12 +13,25 @@ async function click(element: WebdriverIO.Element): Promise<void> {
|
||||
await browser.execute('arguments[0].click();', element)
|
||||
}
|
||||
|
||||
/* Shoutout to @Sheap on Github for a great workaround utility:
|
||||
* https://github.com/tauri-apps/tauri/issues/6541#issue-1638944060
|
||||
*/
|
||||
async function setDatasetValue(
|
||||
field: WebdriverIO.Element,
|
||||
property: string,
|
||||
value: string
|
||||
) {
|
||||
await browser.execute(`arguments[0].dataset.${property} = "${value}"`, field)
|
||||
}
|
||||
|
||||
describe('ZMA (Tauri, Linux)', () => {
|
||||
it('opens the auth page and signs in', async () => {
|
||||
// Clean up filesystem from previous tests
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
await fs.rm(defaultDir, { force: true, recursive: true })
|
||||
await fs.rm(defaultProjectDir, { force: true, recursive: true })
|
||||
await fs.rm(userCodeDir, { force: true })
|
||||
await fs.rm(userSettingsFile, { force: true })
|
||||
await fs.mkdir(newProjectDir, { recursive: true })
|
||||
|
||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||
expect(await signInButton.getText()).toEqual('Sign in')
|
||||
@ -65,13 +81,25 @@ describe('ZMA (Tauri, Linux)', () => {
|
||||
const settingsButton = await $('[data-testid="settings-button"]')
|
||||
await click(settingsButton)
|
||||
|
||||
const defaultDirInput = await $('[data-testid="default-directory-input"]')
|
||||
expect(await defaultDirInput.getValue()).toEqual(defaultDir)
|
||||
const projectDirInput = await $('[data-testid="project-directory-input"]')
|
||||
expect(await projectDirInput.getValue()).toEqual(defaultProjectDir)
|
||||
|
||||
const nameInput = await $('[data-testid="name-input"]')
|
||||
/*
|
||||
* We've set up the project directory input (in initialSettings.tsx)
|
||||
* to be able to skip the folder selection dialog if data-testValue
|
||||
* has a value, allowing us to test the input otherwise works.
|
||||
*/
|
||||
await setDatasetValue(projectDirInput, 'testValue', newProjectDir)
|
||||
const projectDirButton = await $('[data-testid="project-directory-button"]')
|
||||
await click(projectDirButton)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
// This line is broken. I need a different way to grab the toast
|
||||
await expect(await $('div*=Set project directory to')).toBeDisplayed()
|
||||
|
||||
const nameInput = await $('[data-testid="projects-defaultProjectName"]')
|
||||
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
||||
|
||||
const closeButton = await $('[data-testid="close-button"]')
|
||||
const closeButton = await $('[data-testid="settings-close-button"]')
|
||||
await click(closeButton)
|
||||
})
|
||||
|
||||
|
51
package.json
@ -1,36 +1,44 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.17.0",
|
||||
"version": "0.17.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.15.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@kittycad/lib": "^0.0.56",
|
||||
"@lezer/javascript": "^1.4.9",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@replit/codemirror-interact": "^6.3.0",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"@tauri-apps/api": "2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"@tweenjs/tween.js": "^23.1.1",
|
||||
"@types/node": "^18.19.26",
|
||||
"@types/react": "^18.2.73",
|
||||
"@types/node": "^18.19.31",
|
||||
"@types/react": "^18.2.75",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@uiw/react-codemirror": "^4.21.24",
|
||||
"@uiw/react-codemirror": "^4.21.25",
|
||||
"@xstate/inspect": "^0.8.0",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"formik": "^2.4.3",
|
||||
"decamelize": "^6.0.0",
|
||||
"formik": "^2.4.5",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.4.3",
|
||||
"http-server": "^14.1.1",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
@ -45,19 +53,19 @@
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"swr": "^2.2.2",
|
||||
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
||||
"three": "^0.160.0",
|
||||
"swr": "^2.2.5",
|
||||
"three": "^0.163.0",
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uuid": "^9.0.1",
|
||||
"vitest": "^1.4.0",
|
||||
"vscode-jsonrpc": "^8.1.0",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"wasm-pack": "^0.12.1",
|
||||
"web-vitals": "^3.5.2",
|
||||
"ws": "^8.13.0",
|
||||
"ws": "^8.16.0",
|
||||
"xstate": "^4.38.2",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
@ -84,7 +92,7 @@
|
||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||
"lint": "eslint --fix src",
|
||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
||||
"postinstall": "yarn xstate:typegen",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
|
||||
},
|
||||
@ -108,19 +116,20 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.23.3",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@tauri-apps/cli": "^1.5.11",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@playwright/test": "^1.43.0",
|
||||
"@tauri-apps/cli": "^2.0.0-beta.12",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/debounce-promise": "^3.1.9",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.160.0",
|
||||
"@types/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wait-on": "^5.3.4",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@wdio/cli": "^8.24.3",
|
||||
"@wdio/globals": "^8.24.3",
|
||||
@ -132,7 +141,7 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"happy-dom": "^14.3.1",
|
||||
"happy-dom": "^14.3.10",
|
||||
"husky": "^9.0.11",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
@ -141,12 +150,12 @@
|
||||
"prettier": "^2.8.0",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^5.2.2",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wait-on": "^7.2.0",
|
||||
"yarn": "^1.22.19"
|
||||
"yarn": "^1.22.22"
|
||||
}
|
||||
}
|
||||
|
BIN
public/clientSideSceneAssets/extra-segment-texture.png
Normal file
After Width: | Height: | Size: 327 B |
2559
src-tauri/Cargo.lock
generated
@ -7,22 +7,27 @@ license = ""
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
rust-version = "1.70"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5.1", features = [] }
|
||||
tauri-build = { version = "2.0.0-beta", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
kittycad = "0.2.63"
|
||||
kittycad = "0.2.66"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "1.6.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
|
||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tokio = { version = "1.36.0", features = ["time"] }
|
||||
tauri = { version = "2.0.0-beta", features = [ "devtools", "unstable"] }
|
||||
tauri-plugin-dialog = { version = "2.0.0-beta.0" }
|
||||
tauri-plugin-fs = { version = "2.0.0-beta.0" }
|
||||
tauri-plugin-http = { version = "2.0.0-beta.0" }
|
||||
tauri-plugin-os = { version = "2.0.0-beta.0" }
|
||||
tauri-plugin-shell = { version = "2.0.0-beta.0" }
|
||||
tauri-plugin-updater = { version = "2.0.0-beta.0" }
|
||||
tokio = { version = "1.37.0", features = ["time"] }
|
||||
toml = "0.8.2"
|
||||
|
||||
[features]
|
||||
|
87
src-tauri/capabilities/desktop.json
Normal file
@ -0,0 +1,87 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "main-capability",
|
||||
"description": "Capability for the main window",
|
||||
"context": "local",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"path:default",
|
||||
"event:default",
|
||||
"window:default",
|
||||
"app:default",
|
||||
"resources:default",
|
||||
"menu:default",
|
||||
"tray:default",
|
||||
"fs:allow-create",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-read-text-file",
|
||||
"fs:allow-write-file",
|
||||
"fs:allow-write-text-file",
|
||||
"fs:allow-read-dir",
|
||||
"fs:allow-copy-file",
|
||||
"fs:allow-mkdir",
|
||||
"fs:allow-remove",
|
||||
"fs:allow-remove",
|
||||
"fs:allow-rename",
|
||||
"fs:allow-exists",
|
||||
"fs:allow-stat",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
{
|
||||
"path": "$HOME/**/*"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.config"
|
||||
},
|
||||
{
|
||||
"path": "$HOME/.config/**/*"
|
||||
},
|
||||
{
|
||||
"path": "$APPCONFIG"
|
||||
},
|
||||
{
|
||||
"path": "$APPCONFIG/**/*"
|
||||
},
|
||||
{
|
||||
"path": "$DOCUMENT"
|
||||
},
|
||||
{
|
||||
"path": "$DOCUMENT/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"shell:allow-open",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-message",
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-confirm",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
"https://dev.kittycad.io/*",
|
||||
"https://dev.zoo.dev/*",
|
||||
"https://kittycad.io/*",
|
||||
"https://zoo.dev/*",
|
||||
"https://api.dev.kittycad.io/*",
|
||||
"https://api.dev.zoo.dev/*"
|
||||
]
|
||||
},
|
||||
"os:allow-platform",
|
||||
"os:allow-version",
|
||||
"os:allow-os-type",
|
||||
"os:allow-family",
|
||||
"os:allow-arch",
|
||||
"os:allow-exe-extension",
|
||||
"os:allow-locale",
|
||||
"os:allow-hostname"
|
||||
],
|
||||
"platforms": [
|
||||
"linux",
|
||||
"macOS",
|
||||
"windows"
|
||||
]
|
||||
}
|
@ -4,11 +4,15 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use oauth2::TokenResponse;
|
||||
use serde::Serialize;
|
||||
use std::process::Command;
|
||||
use tauri::{InvokeError, Manager};
|
||||
use tauri::ipc::InvokeError;
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
const DEFAULT_HOST: &str = "https://api.kittycad.io";
|
||||
|
||||
/// This command returns the a json string parse from a toml file at the path.
|
||||
@ -24,6 +28,56 @@ fn read_toml(path: &str) -> Result<String, InvokeError> {
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// From https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/dir.rs#L51
|
||||
/// Removed from tauri v2
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct DiskEntry {
|
||||
/// The path to the entry.
|
||||
pub path: PathBuf,
|
||||
/// The name of the entry (file name with extension or directory name).
|
||||
pub name: Option<String>,
|
||||
/// The children of this entry if it's a directory.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub children: Option<Vec<DiskEntry>>,
|
||||
}
|
||||
|
||||
/// From https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/dir.rs#L51
|
||||
/// Removed from tauri v2
|
||||
fn is_dir<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||
std::fs::metadata(path)
|
||||
.map(|md| md.is_dir())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// From https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/dir.rs#L51
|
||||
/// Removed from tauri v2
|
||||
#[tauri::command]
|
||||
fn read_dir_recursive(path: &str) -> Result<Vec<DiskEntry>, InvokeError> {
|
||||
let mut files_and_dirs: Vec<DiskEntry> = vec![];
|
||||
// let path = path.as_ref();
|
||||
for entry in fs::read_dir(path).map_err(|e| InvokeError::from_anyhow(e.into()))? {
|
||||
let path = entry
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?
|
||||
.path();
|
||||
|
||||
if let Ok(flag) = is_dir(&path) {
|
||||
files_and_dirs.push(DiskEntry {
|
||||
path: path.clone(),
|
||||
children: if flag {
|
||||
Some(read_dir_recursive(path.to_str().expect("No path"))?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
name: path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy())
|
||||
.map(|name| name.to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(files_and_dirs)
|
||||
}
|
||||
|
||||
/// This command returns a string that is the contents of a file at the path.
|
||||
#[tauri::command]
|
||||
fn read_txt_file(path: &str) -> Result<String, InvokeError> {
|
||||
@ -85,7 +139,8 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
||||
fs::write("/tmp/kittycad_user_code", details.user_code().secret())
|
||||
.expect("Unable to write /tmp/kittycad_user_code file");
|
||||
} else {
|
||||
tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None)
|
||||
app.shell()
|
||||
.open(auth_uri.secret(), None)
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
}
|
||||
|
||||
@ -165,12 +220,15 @@ fn show_in_folder(path: String) {
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|_app| {
|
||||
#[cfg(debug_assertions)] // only include this code on debug builds
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let window = _app.get_window("main").unwrap();
|
||||
// comment out the below if you don't devtools to open everytime.
|
||||
// it's useful because otherwise devtools shuts everytime rust code changes.
|
||||
window.open_devtools();
|
||||
use tauri::Manager;
|
||||
_app.get_webview("main").unwrap().open_devtools();
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
_app.handle()
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
@ -179,9 +237,14 @@ fn main() {
|
||||
login,
|
||||
read_toml,
|
||||
read_txt_file,
|
||||
read_dir_recursive,
|
||||
show_in_folder,
|
||||
])
|
||||
.plugin(tauri_plugin_fs_extra::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
@ -1,90 +1,9 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn start",
|
||||
"devPath": "http://localhost:3000",
|
||||
"distDir": "../build"
|
||||
},
|
||||
"package": {
|
||||
"productName": "zoo-modeling-app",
|
||||
"version": "0.17.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"dialog": {
|
||||
"all": true,
|
||||
"ask": true,
|
||||
"confirm": true,
|
||||
"message": true,
|
||||
"open": true,
|
||||
"save": true
|
||||
},
|
||||
"fs": {
|
||||
"scope": [
|
||||
"$HOME/**/*",
|
||||
"$APPCONFIG",
|
||||
"$APPCONFIG/**/*",
|
||||
"$DOCUMENT",
|
||||
"$DOCUMENT/**/*"
|
||||
],
|
||||
"all": true
|
||||
},
|
||||
"http": {
|
||||
"request": true,
|
||||
"scope": [
|
||||
"https://dev.kittycad.io/*",
|
||||
"https://dev.zoo.dev/*",
|
||||
"https://kittycad.io/*",
|
||||
"https://zoo.dev/*",
|
||||
"https://api.dev.kittycad.io/*",
|
||||
"https://api.dev.zoo.dev/*"
|
||||
]
|
||||
},
|
||||
"os": {
|
||||
"all": true
|
||||
},
|
||||
"shell": {
|
||||
"open": true
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "dev.zoo.modeling-app",
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": "all"
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
@ -94,5 +13,47 @@
|
||||
"width": 1800
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn start",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"frontendDist": "../build"
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": []
|
||||
}
|
||||
},
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": "all"
|
||||
},
|
||||
"identifier": "dev.zoo.modeling-app",
|
||||
"plugins": {
|
||||
"shell": {
|
||||
"open": true
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.17.3"
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"package": {
|
||||
"productName": "Zoo Modeling App"
|
||||
}
|
||||
}
|
@ -1,6 +1,13 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"windows": {
|
||||
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.digicert.com"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"active": true,
|
||||
"endpoints": [
|
||||
@ -8,14 +15,6 @@
|
||||
],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.kittycad.modeling-app",
|
||||
"windows": {
|
||||
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.digicert.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"package": {
|
||||
"productName": "Zoo Modeling App"
|
||||
}
|
||||
}
|
38
src/App.tsx
@ -1,6 +1,6 @@
|
||||
import { useCallback, MouseEventHandler, useEffect } from 'react'
|
||||
import { useCallback, MouseEventHandler, useEffect, useRef } from 'react'
|
||||
import { DebugPanel } from './components/DebugPanel'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { PaneType, useStore } from './useStore'
|
||||
import { Logs, KCLErrors } from './components/Logs'
|
||||
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
||||
@ -33,14 +33,17 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { useLspContext } from 'components/LspProvider'
|
||||
import { useValidateSettings } from 'hooks/useValidateSettings'
|
||||
import { useRefreshSettings } from 'hooks/useRefreshSettings'
|
||||
|
||||
export function App() {
|
||||
useValidateSettings()
|
||||
useRefreshSettings(paths.FILE + 'SETTINGS')
|
||||
const { project, file } = useLoaderData() as IndexLoaderData
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const { onProjectOpen } = useLspContext()
|
||||
// We need the ref for the outermost div so we can screenshot the app for
|
||||
// the coredump.
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const projectName = project?.name || null
|
||||
const projectPath = project?.path || null
|
||||
@ -55,19 +58,29 @@ export function App() {
|
||||
setOpenPanes,
|
||||
didDragInStream,
|
||||
streamDimensions,
|
||||
setHtmlRef,
|
||||
} = useStore((s) => ({
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
openPanes: s.openPanes,
|
||||
setOpenPanes: s.setOpenPanes,
|
||||
didDragInStream: s.didDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
setHtmlRef: s.setHtmlRef,
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
setHtmlRef(ref)
|
||||
}, [ref])
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
|
||||
const {
|
||||
modeling: { showDebugPanel },
|
||||
app: { theme, onboardingStatus },
|
||||
} = settings.context
|
||||
const { state, send } = useModelingContext()
|
||||
|
||||
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
||||
const editorTheme =
|
||||
theme.current === Themes.System ? getSystemTheme() : theme.current
|
||||
|
||||
// Pane toggling keyboard shortcuts
|
||||
const togglePane = useCallback(
|
||||
@ -95,7 +108,7 @@ export function App() {
|
||||
)
|
||||
|
||||
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||
(p) => p === onboardingStatus
|
||||
(p) => p === onboardingStatus.current
|
||||
)
|
||||
? 'opacity-20'
|
||||
: didDragInStream
|
||||
@ -136,6 +149,7 @@ export function App() {
|
||||
<div
|
||||
className="relative h-full flex flex-col"
|
||||
onMouseMove={handleMouseMove}
|
||||
ref={ref}
|
||||
>
|
||||
<AppHeader
|
||||
className={
|
||||
@ -149,7 +163,7 @@ export function App() {
|
||||
<ModalContainer />
|
||||
<Resizable
|
||||
className={
|
||||
'pointer-events-none h-full flex flex-col flex-1 z-10 my-5 ml-5 pr-1 transition-opacity transition-duration-75 ' +
|
||||
'pointer-events-none h-full flex flex-col flex-1 z-10 my-2 ml-2 pr-1 transition-opacity transition-duration-75 ' +
|
||||
+paneOpacity
|
||||
}
|
||||
defaultSize={{
|
||||
@ -162,8 +176,8 @@ export function App() {
|
||||
maxHeight={'auto'}
|
||||
handleClasses={{
|
||||
right:
|
||||
'hover:bg-chalkboard-10/50 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ' +
|
||||
(buttonDownInStream || onboardingStatus === 'camera'
|
||||
'hover:bg-chalkboard-10 hover:dark:bg-chalkboard-110 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ' +
|
||||
(buttonDownInStream || onboardingStatus.current === 'camera'
|
||||
? 'pointer-events-none '
|
||||
: 'pointer-events-auto'),
|
||||
}}
|
||||
@ -198,13 +212,13 @@ export function App() {
|
||||
theme={editorTheme}
|
||||
open={openPanes.includes('kclErrors')}
|
||||
title="KCL Errors"
|
||||
iconClassNames={{ icon: 'group-open:text-destroy-30' }}
|
||||
iconClassNames={{ bg: 'group-open:bg-destroy-70' }}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</Resizable>
|
||||
<Stream className="absolute inset-0 z-0" />
|
||||
{showDebugPanel && (
|
||||
{showDebugPanel.current && (
|
||||
<DebugPanel
|
||||
title="Debug"
|
||||
className={
|
||||
|
@ -22,19 +22,18 @@ import { paths } from 'lib/paths'
|
||||
import {
|
||||
fileLoader,
|
||||
homeLoader,
|
||||
indexLoader,
|
||||
onboardingRedirectLoader,
|
||||
settingsLoader,
|
||||
} from 'lib/routeLoaders'
|
||||
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
|
||||
import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
||||
import LspProvider from 'components/LspProvider'
|
||||
import { KclContextProvider } from 'lang/KclProvider'
|
||||
|
||||
export const BROWSER_FILE_NAME = 'new'
|
||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
loader: indexLoader,
|
||||
loader: settingsLoader,
|
||||
id: paths.INDEX,
|
||||
element: (
|
||||
<CommandBarProvider>
|
||||
@ -47,14 +46,14 @@ const router = createBrowserRouter([
|
||||
</KclContextProvider>
|
||||
</CommandBarProvider>
|
||||
),
|
||||
errorElement: <ErrorPage />,
|
||||
children: [
|
||||
{
|
||||
path: paths.INDEX,
|
||||
loader: () =>
|
||||
isTauri()
|
||||
? redirect(paths.HOME)
|
||||
: redirect(paths.FILE + '/' + BROWSER_FILE_NAME),
|
||||
errorElement: <ErrorPage />,
|
||||
: redirect(paths.FILE + '/%2F' + BROWSER_PROJECT_NAME),
|
||||
},
|
||||
{
|
||||
loader: fileLoader,
|
||||
@ -67,29 +66,29 @@ const router = createBrowserRouter([
|
||||
<Outlet />
|
||||
<App />
|
||||
<CommandBar />
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</ModelingMachineProvider>
|
||||
<WasmErrBanner />
|
||||
</FileMachineProvider>
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</Auth>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
loader: onboardingRedirectLoader,
|
||||
index: true,
|
||||
element: <></>,
|
||||
},
|
||||
{
|
||||
id: paths.FILE + 'SETTINGS',
|
||||
loader: settingsLoader,
|
||||
children: [
|
||||
{
|
||||
loader: onboardingRedirectLoader,
|
||||
index: true,
|
||||
element: <></>,
|
||||
},
|
||||
{
|
||||
path: makeUrlPathRelative(paths.SETTINGS),
|
||||
loader: indexLoader, // very rare someone will load into settings first, but it's possible in the browser
|
||||
element: <Settings />,
|
||||
},
|
||||
{
|
||||
path: makeUrlPathRelative(paths.ONBOARDING.INDEX),
|
||||
element: <Onboarding />,
|
||||
loader: indexLoader, // very rare someone will load into settings first, but it's possible in the browser
|
||||
children: onboardingRoutes,
|
||||
},
|
||||
],
|
||||
@ -108,8 +107,15 @@ const router = createBrowserRouter([
|
||||
id: paths.HOME,
|
||||
loader: homeLoader,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <></>,
|
||||
id: paths.HOME + 'SETTINGS',
|
||||
loader: settingsLoader,
|
||||
},
|
||||
{
|
||||
path: makeUrlPathRelative(paths.SETTINGS),
|
||||
loader: settingsLoader,
|
||||
element: <Settings />,
|
||||
},
|
||||
],
|
||||
|
@ -18,8 +18,12 @@ export const Toolbar = () => {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
const iconClassName =
|
||||
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10'
|
||||
const bgClassName =
|
||||
'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
|
||||
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary group-pressed:bg-primary'
|
||||
const buttonClassName =
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-100 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100'
|
||||
const pathId = useMemo(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||
return false
|
||||
@ -64,12 +68,14 @@ export const Toolbar = () => {
|
||||
{state.nextEvents.includes('Enter sketch') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||
}
|
||||
icon={{
|
||||
icon: 'sketch',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -81,10 +87,12 @@ export const Toolbar = () => {
|
||||
{state.nextEvents.includes('Enter sketch') && pathId && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Enter sketch' })}
|
||||
icon={{
|
||||
icon: 'sketch',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -96,10 +104,12 @@ export const Toolbar = () => {
|
||||
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Cancel' })}
|
||||
icon={{
|
||||
icon: 'arrowLeft',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -112,6 +122,7 @@ export const Toolbar = () => {
|
||||
<>
|
||||
<li className="contents" key="line-button">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
state?.matches('Sketch.Line tool')
|
||||
@ -119,9 +130,9 @@ export const Toolbar = () => {
|
||||
: send('Equip Line tool')
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||
icon={{
|
||||
icon: 'line',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -131,6 +142,7 @@ export const Toolbar = () => {
|
||||
</li>
|
||||
<li className="contents" key="tangential-arc-button">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
@ -138,9 +150,9 @@ export const Toolbar = () => {
|
||||
: send('Equip tangential arc to')
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||
icon={{
|
||||
icon: 'arc',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
@ -179,8 +191,8 @@ export const Toolbar = () => {
|
||||
.map((eventName) => (
|
||||
<li className="contents" key={eventName}>
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
className="text-sm"
|
||||
key={eventName}
|
||||
onClick={() => send(eventName)}
|
||||
disabled={
|
||||
@ -191,6 +203,7 @@ export const Toolbar = () => {
|
||||
title={eventName}
|
||||
icon={{
|
||||
icon: 'line',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
@ -203,8 +216,8 @@ export const Toolbar = () => {
|
||||
{state.matches('idle') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
className="text-sm"
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
@ -219,6 +232,7 @@ export const Toolbar = () => {
|
||||
}
|
||||
icon={{
|
||||
icon: 'extrude',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
@ -231,16 +245,16 @@ export const Toolbar = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-full flex items-stretch rounded-l-sm rounded-r-full bg-chalkboard-10 dark:bg-chalkboard-100 relative">
|
||||
<menu className="flex-1 pl-1 pr-2 py-0 overflow-hidden rounded-l-sm whitespace-nowrap bg-chalkboard-10 dark:bg-chalkboard-100 border-solid border border-energy-10 dark:border-chalkboard-90 border-r-0">
|
||||
<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-energy-10 hover:border-energy-10 dark:border-chalkboard-80 bg-energy-10/50 hover:bg-energy-10 dark:bg-chalkboard-80 dark:text-energy-10"
|
||||
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 === 'darwin' ? '⌘K' : 'Ctrl+/'}
|
||||
{platform === 'macos' ? '⌘K' : 'Ctrl+/'}
|
||||
</ActionButton>
|
||||
</div>
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
Subscription,
|
||||
EngineCommandManager,
|
||||
} from 'lang/std/engineConnection'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { deg2Rad } from 'lib/utils2d'
|
||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||
import * as TWEEN from '@tweenjs/tween.js'
|
||||
|