Compare commits
132 Commits
v0.41.0
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
0dab8d6f05 | |||
28b5a088d2 | |||
057face2e2 | |||
57f3cd1ba4 | |||
32d2fb639c | |||
95ad629558 | |||
ae9731b21a | |||
c4ceaaf6b2 | |||
60299c6520 | |||
fcfdffaab8 | |||
fe3ab30627 | |||
259844ef11 | |||
f7ab29047c | |||
82ad0ac8c7 | |||
4463833460 | |||
f48c8fefd6 | |||
73a878b0aa | |||
d04ac2bbb4 | |||
f5ec62ab27 | |||
2c28ad6866 | |||
989a2278cb | |||
01cb6df3b4 | |||
ab00c7bcc8 | |||
1c589960bf | |||
a6b2d32b95 | |||
78e1bec0c7 | |||
c18c8f9e81 | |||
95b647676b | |||
b5182c89d0 | |||
069b1cb370 | |||
012da726ec | |||
7b3cfb1c2f | |||
5f3cf61d0f | |||
1f3455e840 | |||
260ec9fe45 | |||
1ae55c2946 | |||
0400f33fc3 | |||
734e590e09 | |||
2a5433a2a7 | |||
519041c8fa | |||
c6d563f08f | |||
82d6446e5e | |||
e34a4db077 | |||
665ec8a6b5 | |||
ef8131f33d | |||
b7ec0eeb6e | |||
43c12e523b | |||
b74216378d | |||
7031b6f0a7 | |||
e1ce83a72e | |||
f6c335181e | |||
25d9c19763 | |||
871890415c | |||
7f9526bb81 | |||
aafa1a398d | |||
fda7acf0ff | |||
bf306e774d | |||
2abd516db4 | |||
e26d7e741f | |||
91df6b9511 | |||
3cecb99aa1 | |||
e8961d4190 | |||
da3788afba | |||
12ed315244 | |||
9c35c06e58 | |||
a69da088cd | |||
38e59df85c | |||
8bac097743 | |||
09df8a734a | |||
da2072e005 | |||
cb4a47705c | |||
1227b38e12 | |||
8e343a254f | |||
2559309f85 | |||
26f01bce76 | |||
729a34efed | |||
5191013db2 | |||
d653b852dd | |||
f3a2922ba0 | |||
ce8aeab508 | |||
ee6014d0cf | |||
bd329de3e3 | |||
5d1df40de5 | |||
21ea1d785a | |||
987152e166 | |||
6b37bf1809 | |||
617dcfee34 | |||
10c446151b | |||
99866b5f9a | |||
cd022fb087 | |||
fda4aeb505 | |||
d9fe9bafba | |||
9dc06f6b92 | |||
9e778280b3 | |||
141c38c92b | |||
fc7e6993ca | |||
ad8336ef57 | |||
a819d16798 | |||
1c8cc54c9d | |||
70766cef69 | |||
a69ccf960f | |||
308b7121fb | |||
6522f1cbe7 | |||
2943ea1fd6 | |||
ae0860a775 | |||
ff71250d5d | |||
81e2fd201d | |||
de2dfc24dc | |||
84cd2c0c88 | |||
440173d045 | |||
ddafbf059c | |||
e86a5fa614 | |||
d386c92c8e | |||
248b2d2a2b | |||
72f84c3a8f | |||
ecec739632 | |||
cd28a4a9a1 | |||
18b1d84e5c | |||
fb59996c54 | |||
d66c7cfe33 | |||
e917eb9af6 | |||
cc9a14ddd9 | |||
235e526e2d | |||
725ffd952c | |||
2848d6c77c | |||
989ca83971 | |||
bdeda51078 | |||
b079af20da | |||
641efb0750 | |||
69f036e61b | |||
57ca6d52d5 | |||
a186517bd0 |
@ -2,8 +2,8 @@ NODE_ENV=development
|
|||||||
DEV=true
|
DEV=true
|
||||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||||
|
BASE_URL=https://api.dev.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
|
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
NODE_ENV=production
|
|
||||||
DEV=false
|
|
||||||
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
||||||
VITE_KC_SITE_APP_URL=https://app.zoo.dev
|
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||||
|
@ -29,13 +29,6 @@
|
|||||||
{
|
{
|
||||||
"name": "isNaN",
|
"name": "isNaN",
|
||||||
"message": "Use Number.isNaN() instead."
|
"message": "Use Number.isNaN() instead."
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "CallExpression[callee.object.name='Array'][callee.property.name='isArray']",
|
|
||||||
"message": "Use isArray() in lib/utils.ts instead of Array.isArray()."
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"semi": [
|
"semi": [
|
||||||
|
16
.github/workflows/build-apps.yml
vendored
@ -134,6 +134,8 @@ jobs:
|
|||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: yarn install
|
command: yarn install
|
||||||
|
|
||||||
|
- run: yarn tronb:vite
|
||||||
|
|
||||||
- name: Prepare certificate and variables (Windows only)
|
- name: Prepare certificate and variables (Windows only)
|
||||||
if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
|
if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
|
||||||
run: |
|
run: |
|
||||||
@ -163,8 +165,8 @@ jobs:
|
|||||||
- name: Build the app (debug)
|
- name: Build the app (debug)
|
||||||
if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }}
|
if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }}
|
||||||
# electron-builder doesn't have a concept of release vs debug,
|
# electron-builder doesn't have a concept of release vs debug,
|
||||||
# this is just not doing any codesign or release yml generation, and points to dev infra
|
# this is just not doing any codesign or release yml generation
|
||||||
run: yarn tronb:package:dev
|
run: yarn electron-builder --config
|
||||||
|
|
||||||
- name: Build the app (release)
|
- name: Build the app (release)
|
||||||
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
|
||||||
@ -183,7 +185,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: yarn tronb:package:prod
|
command: yarn electron-builder --config --publish always
|
||||||
|
|
||||||
- name: List artifacts in out/
|
- name: List artifacts in out/
|
||||||
run: ls -R out
|
run: ls -R out
|
||||||
@ -244,7 +246,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: yarn tronb:package:prod
|
command: yarn electron-builder --config --publish always
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ env.IS_RELEASE == 'true' }}
|
if: ${{ env.IS_RELEASE == 'true' }}
|
||||||
@ -388,19 +390,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
uses: 'google-github-actions/auth@v2.1.8'
|
uses: 'google-github-actions/auth@v2.1.7'
|
||||||
with:
|
with:
|
||||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||||
|
|
||||||
- name: Set up Google Cloud SDK
|
- name: Set up Google Cloud SDK
|
||||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
uses: google-github-actions/setup-gcloud@v2.1.4
|
uses: google-github-actions/setup-gcloud@v2.1.2
|
||||||
with:
|
with:
|
||||||
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
||||||
|
|
||||||
- name: Upload nightly files to public bucket
|
- name: Upload nightly files to public bucket
|
||||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.2.2
|
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||||
with:
|
with:
|
||||||
path: out
|
path: out
|
||||||
glob: '*'
|
glob: '*'
|
||||||
|
68
.github/workflows/e2e-tests.yml
vendored
@ -46,15 +46,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs: check-rust-changes
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/create-github-app-token@v1
|
|
||||||
id: app-token
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
|
|
||||||
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
|
|
||||||
owner: ${{ github.repository_owner }}
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
@ -131,23 +123,23 @@ jobs:
|
|||||||
if: steps.download-wasm.outcome == 'failure'
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: build web
|
- name: build electron
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn tronb:vite:dev
|
run: yarn tron:package
|
||||||
- name: Run ubuntu/chrome snapshots
|
# - name: Run ubuntu/chrome snapshots
|
||||||
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
# if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||||
shell: bash
|
# shell: bash
|
||||||
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
|
# # TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
|
||||||
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
|
# # but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
|
||||||
run: |
|
# run: |
|
||||||
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
# PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||||
env:
|
# env:
|
||||||
CI: true
|
# CI: true
|
||||||
NODE_ENV: development
|
# NODE_ENV: development
|
||||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
# VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
VITE_KC_SKIP_AUTH: true
|
# VITE_KC_SKIP_AUTH: true
|
||||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
# token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
# snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: ${{ !cancelled() && (success() || failure()) }}
|
||||||
with:
|
with:
|
||||||
@ -170,20 +162,20 @@ jobs:
|
|||||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Commit changes, if any
|
# - name: Commit changes, if any
|
||||||
if: steps.git-check.outputs.modified == 'true'
|
# if: steps.git-check.outputs.modified == 'true'
|
||||||
shell: bash
|
# shell: bash
|
||||||
run: |
|
# run: |
|
||||||
git add .
|
# git add .
|
||||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
# git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
# git config --local user.name "github-actions[bot]"
|
||||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
# git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||||
git fetch origin
|
# git fetch origin
|
||||||
echo ${{ github.head_ref }}
|
# echo ${{ github.head_ref }}
|
||||||
git checkout ${{ github.head_ref }}
|
# git checkout ${{ github.head_ref }}
|
||||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
# git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||||
git push
|
# git push
|
||||||
git push origin ${{ github.head_ref }}
|
# git push origin ${{ github.head_ref }}
|
||||||
# only upload artifacts if there's actually changes
|
# only upload artifacts if there's actually changes
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: steps.git-check.outputs.modified == 'true'
|
if: steps.git-check.outputs.modified == 'true'
|
||||||
|
6
.github/workflows/publish-apps-release.yml
vendored
@ -108,17 +108,17 @@ jobs:
|
|||||||
run: yarn files:set-notes
|
run: yarn files:set-notes
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v2.1.8'
|
uses: 'google-github-actions/auth@v2.1.7'
|
||||||
with:
|
with:
|
||||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||||
|
|
||||||
- name: Set up Google Cloud SDK
|
- name: Set up Google Cloud SDK
|
||||||
uses: google-github-actions/setup-gcloud@v2.1.4
|
uses: google-github-actions/setup-gcloud@v2.1.2
|
||||||
with:
|
with:
|
||||||
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
||||||
|
|
||||||
- name: Upload release files to public bucket
|
- name: Upload release files to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.2.2
|
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||||
with:
|
with:
|
||||||
path: out
|
path: out
|
||||||
glob: '*'
|
glob: '*'
|
||||||
|
@ -101,7 +101,7 @@ This will start the application and hot-reload on changes.
|
|||||||
|
|
||||||
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
|
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
|
||||||
|
|
||||||
To build with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables)
|
To build, run `yarn tron:package`.
|
||||||
|
|
||||||
## Checking out commits / Bisecting
|
## Checking out commits / Bisecting
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ async function doBasicSketch(
|
|||||||
|> xLine(-segLen(seg01), %)`)
|
|> xLine(-segLen(seg01), %)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('Basic sketch', { tag: ['@skipWin'] }, () => {
|
test.describe('Basic sketch', () => {
|
||||||
test.fixme('code pane open at start', async ({ page, homePage }) => {
|
test.fixme('code pane open at start', async ({ page, homePage }) => {
|
||||||
await doBasicSketch(page, homePage, ['code'])
|
await doBasicSketch(page, homePage, ['code'])
|
||||||
})
|
})
|
||||||
|
@ -4,125 +4,112 @@ import { getUtils } from './test-utils'
|
|||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
|
||||||
test.describe(
|
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||||
'Can create sketches on all planes and their back sides',
|
const sketchOnPlaneAndBackSideTest = async (
|
||||||
{ tag: ['@skipWin'] },
|
page: Page,
|
||||||
() => {
|
homePage: HomePageFixture,
|
||||||
const sketchOnPlaneAndBackSideTest = async (
|
plane: string,
|
||||||
page: Page,
|
clickCoords: { x: number; y: number }
|
||||||
homePage: HomePageFixture,
|
) => {
|
||||||
plane: string,
|
const u = await getUtils(page)
|
||||||
clickCoords: { x: number; y: number }
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
) => {
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
const u = await getUtils(page)
|
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const coord =
|
const coord =
|
||||||
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
||||||
const camCommand: EngineCommand = {
|
const camCommand: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_look_at',
|
type: 'default_camera_look_at',
|
||||||
center: { x: 0, y: 0, z: 0 },
|
center: { x: 0, y: 0, z: 0 },
|
||||||
vantage: { x: coord, y: coord, z: coord },
|
vantage: { x: coord, y: coord, z: coord },
|
||||||
up: { x: 0, y: 0, z: 1 },
|
up: { x: 0, y: 0, z: 1 },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const updateCamCommand: EngineCommand = {
|
const updateCamCommand: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_get_settings',
|
type: 'default_camera_get_settings',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = `sketch001 = startSketchOn('${plane}')
|
const code = `sketch001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([0.9, -1.22], %)`
|
|> startProfileAt([0.9, -1.22], %)`
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
await u.sendCustomCmd(camCommand)
|
await u.sendCustomCmd(camCommand)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await u.sendCustomCmd(updateCamCommand)
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||||
await page.waitForTimeout(300) // wait for animation
|
await page.waitForTimeout(300) // wait for animation
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'line Line', exact: true })
|
page.getByRole('button', { name: 'line Line', exact: true })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
// draw a line
|
// draw a line
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'line Line', exact: true })
|
.getByRole('button', { name: 'line Line', exact: true })
|
||||||
.first()
|
.first()
|
||||||
.click()
|
.click()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await u.removeCurrentCode()
|
await u.removeCurrentCode()
|
||||||
}
|
|
||||||
test('XY', async ({ page, homePage }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
'XY',
|
|
||||||
{ x: 600, y: 388 } // red plane
|
|
||||||
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('YZ', async ({ page, homePage }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', {
|
|
||||||
x: 700,
|
|
||||||
y: 250,
|
|
||||||
}) // green plane
|
|
||||||
})
|
|
||||||
|
|
||||||
test('XZ', async ({ page, homePage }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', {
|
|
||||||
x: 700,
|
|
||||||
y: 80,
|
|
||||||
}) // blue plane
|
|
||||||
})
|
|
||||||
|
|
||||||
test('-XY', async ({ page, homePage }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', {
|
|
||||||
x: 600,
|
|
||||||
y: 118,
|
|
||||||
}) // back of red plane
|
|
||||||
})
|
|
||||||
|
|
||||||
test('-YZ', async ({ page, homePage }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', {
|
|
||||||
x: 700,
|
|
||||||
y: 219,
|
|
||||||
}) // back of green plan
|
|
||||||
})
|
|
||||||
|
|
||||||
test('-XZ', async ({ page, homePage }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', {
|
|
||||||
x: 700,
|
|
||||||
y: 427,
|
|
||||||
}) // back of blue plane
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
test('XY', async ({ page, homePage }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
'XY',
|
||||||
|
{ x: 600, y: 388 } // red plane
|
||||||
|
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('YZ', async ({ page, homePage }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', { x: 700, y: 250 }) // green plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('XZ', async ({ page, homePage }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-XY', async ({ page, homePage }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', {
|
||||||
|
x: 600,
|
||||||
|
y: 118,
|
||||||
|
}) // back of red plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-YZ', async ({ page, homePage }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', {
|
||||||
|
x: 700,
|
||||||
|
y: 219,
|
||||||
|
}) // back of green plan
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-XZ', async ({ page, homePage }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -6,7 +6,7 @@ import { bracket } from 'lib/exampleKcl'
|
|||||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
test.describe('Code pane and errors', () => {
|
||||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
@ -4,7 +4,7 @@ import { executorInputPath, getUtils } from './test-utils'
|
|||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
test.describe('Command bar tests', () => {
|
||||||
test('Extrude from command bar selects extrude line after', async ({
|
test('Extrude from command bar selects extrude line after', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
@ -167,54 +167,54 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||||
'Command bar keybinding works from code editor and can change a setting',
|
page,
|
||||||
{ tag: ['@skipWin'] },
|
homePage,
|
||||||
async ({ page, homePage }) => {
|
}) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
// Put the cursor in the code editor
|
// Put the cursor in the code editor
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
// Now try the same, but with the keyboard shortcut, check focus
|
// Now try the same, but with the keyboard shortcut, check focus
|
||||||
await page.keyboard.press('ControlOrMeta+K')
|
await page.keyboard.press('ControlOrMeta+K')
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
await expect(cmdSearchBar).toBeFocused()
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
// Try typing in the command bar
|
// Try typing in the command bar
|
||||||
await cmdSearchBar.fill('theme')
|
await cmdSearchBar.fill('theme')
|
||||||
const themeOption = page.getByRole('option', {
|
const themeOption = page.getByRole('option', {
|
||||||
name: 'Settings · app · theme',
|
name: 'Settings · app · theme',
|
||||||
})
|
})
|
||||||
await expect(themeOption).toBeVisible()
|
await expect(themeOption).toBeVisible()
|
||||||
await themeOption.click()
|
await themeOption.click()
|
||||||
const themeInput = page.getByPlaceholder('dark')
|
const themeInput = page.getByPlaceholder('dark')
|
||||||
await expect(themeInput).toBeVisible()
|
await expect(themeInput).toBeVisible()
|
||||||
await expect(themeInput).toBeFocused()
|
await expect(themeInput).toBeFocused()
|
||||||
// Select dark theme
|
// Select dark theme
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await expect(
|
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||||
page.getByRole('option', { name: 'system' })
|
'data-headlessui-state',
|
||||||
).toHaveAttribute('data-headlessui-state', 'active')
|
'active'
|
||||||
await page.keyboard.press('Enter')
|
)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Check the toast appeared
|
// Check the toast appeared
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(`Set theme to "system" as a user default`)
|
page.getByText(`Set theme to "system" as a user default`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
// Check that the theme changed
|
// Check that the theme changed
|
||||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test('Can extrude from the command bar', async ({ page, homePage }) => {
|
test('Can extrude from the command bar', async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
test.describe('Editor tests', () => {
|
||||||
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
|
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
@ -966,106 +966,106 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|> close()`)
|
|> close()`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test('Can undo a sketch modification with ctrl+z', async ({
|
||||||
'Can undo a sketch modification with ctrl+z',
|
page,
|
||||||
{ tag: ['@skipWin'] },
|
homePage,
|
||||||
async ({ page, homePage }) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -10.01], %)
|
|> startProfileAt([4.61, -10.01], %)
|
||||||
|> line(end = [12.73, -0.09])
|
|> line(end = [12.73, -0.09])
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`
|
|> extrude(length = 5)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_look_at',
|
type: 'default_camera_look_at',
|
||||||
vantage: { x: 0, y: -1250, z: 580 },
|
vantage: { x: 0, y: -1250, z: 580 },
|
||||||
center: { x: 0, y: 0, z: 0 },
|
center: { x: 0, y: 0, z: 0 },
|
||||||
up: { x: 0, y: 0, z: 1 },
|
up: { x: 0, y: 0, z: 1 },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_get_settings',
|
type: 'default_camera_get_settings',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const startPX = [1200 / 2, 500 / 2]
|
const startPX = [1200 / 2, 500 / 2]
|
||||||
|
|
||||||
const dragPX = 40
|
const dragPX = 40
|
||||||
|
|
||||||
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
|
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(400)
|
await page.waitForTimeout(400)
|
||||||
let prevContent = await page.locator('.cm-content').innerText()
|
let prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||||
|
|
||||||
// drag startProfileAt handle
|
// drag startProfileAt handle
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
||||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
// drag line handle
|
// drag line handle
|
||||||
// we wait so it saves the code
|
// we wait so it saves the code
|
||||||
await page.waitForTimeout(800)
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||||
})
|
})
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
// we wait so it saves the code
|
// we wait so it saves the code
|
||||||
await page.waitForTimeout(800)
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
// drag tangentialArcTo handle
|
// drag tangentialArcTo handle
|
||||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||||
targetPosition: {
|
targetPosition: {
|
||||||
x: tangentEnd.x + dragPX,
|
x: tangentEnd.x + dragPX,
|
||||||
y: tangentEnd.y + dragPX,
|
y: tangentEnd.y + dragPX,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
|
|
||||||
// expect the code to have changed
|
// expect the code to have changed
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([2.71, -2.71], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line(end = [15.4, -2.78])
|
|> line(end = [15.4, -2.78])
|
||||||
|> tangentialArcTo([27.6, -3.05], %)
|
|> tangentialArcTo([27.6, -3.05], %)
|
||||||
@ -1073,26 +1073,26 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|> extrude(length = 5)
|
|> extrude(length = 5)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Hit undo
|
// Hit undo
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([2.71, -2.71], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line(end = [15.4, -2.78])
|
|> line(end = [15.4, -2.78])
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`)
|
|> extrude(length = 5)`)
|
||||||
|
|
||||||
// Hit undo again.
|
// Hit undo again.
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([2.71, -2.71], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line(end = [12.73, -0.09])
|
|> line(end = [12.73, -0.09])
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
@ -1100,21 +1100,20 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|> extrude(length = 5)
|
|> extrude(length = 5)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Hit undo again.
|
// Hit undo again.
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -10.01], %)
|
|> startProfileAt([4.61, -10.01], %)
|
||||||
|> line(end = [12.73, -0.09])
|
|> line(end = [12.73, -0.09])
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`)
|
|> extrude(length = 5)`)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
`Can use the import stdlib function on a local OBJ file`,
|
`Can use the import stdlib function on a local OBJ file`,
|
||||||
|
@ -19,7 +19,7 @@ test.describe('integrations tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const [clickObj] = await scene.makeMouseHelpers(726, 272)
|
const [clickObj] = await scene.makeMouseHelpers(600, 300)
|
||||||
|
|
||||||
await test.step('setup test', async () => {
|
await test.step('setup test', async () => {
|
||||||
await homePage.expectState({
|
await homePage.expectState({
|
||||||
@ -61,7 +61,6 @@ test.describe('integrations tests', () => {
|
|||||||
})
|
})
|
||||||
await test.step('setup for next assertion', async () => {
|
await test.step('setup for next assertion', async () => {
|
||||||
await toolbar.openFile('main.kcl')
|
await toolbar.openFile('main.kcl')
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
await clickObj()
|
await clickObj()
|
||||||
await scene.moveNoWhere()
|
await scene.moveNoWhere()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
@ -1186,56 +1185,4 @@ test.describe('Undo and redo do not keep history when navigating between files',
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
|
||||||
`cloned file has an incremented name and same contents`,
|
|
||||||
{ tag: '@electron' },
|
|
||||||
async ({ page, context, homePage }, testInfo) => {
|
|
||||||
const { panesOpen, createNewFile, cloneFile } = await getUtils(page, test)
|
|
||||||
|
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
|
||||||
const finalDir = join(dir, 'testDefault')
|
|
||||||
await fsp.mkdir(finalDir, { recursive: true })
|
|
||||||
await fsp.copyFile(
|
|
||||||
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
|
||||||
join(finalDir, 'lee.kcl')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const contentOriginal = await fsp.readFile(
|
|
||||||
join(dir, 'testDefault', 'lee.kcl'),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
await panesOpen(['files'])
|
|
||||||
await homePage.openProject('testDefault')
|
|
||||||
|
|
||||||
await cloneFile('lee.kcl')
|
|
||||||
await cloneFile('lee-1.kcl')
|
|
||||||
await cloneFile('lee-2.kcl')
|
|
||||||
await cloneFile('lee-3.kcl')
|
|
||||||
await cloneFile('lee-4.kcl')
|
|
||||||
|
|
||||||
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
|
|
||||||
await expect(
|
|
||||||
page
|
|
||||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
|
||||||
.filter({ hasText: /lee[-]?[0-5]?/ })
|
|
||||||
).toHaveCount(5)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Postcondition: the files have the same contents', async () => {
|
|
||||||
for (let n = 0; n < 5; n += 1) {
|
|
||||||
const content = await fsp.readFile(
|
|
||||||
join(dir, 'testDefault', `lee-${n + 1}.kcl`),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
await expect(content).toEqual(contentOriginal)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
@ -89,11 +89,18 @@ export class HomePageFixture {
|
|||||||
* Maybe there a good sanity check we can do each time?
|
* Maybe there a good sanity check we can do each time?
|
||||||
*/
|
*/
|
||||||
expectState = async (expectedState: HomePageState) => {
|
expectState = async (expectedState: HomePageState) => {
|
||||||
await expect.poll(this._serialiseSortBy).toEqual(expectedState.sortBy)
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
for (const projectCard of expectedState.projectCards) {
|
const [projectCards, sortBy] = await Promise.all([
|
||||||
await expect.poll(this._serialiseProjectCards).toContainEqual(projectCard)
|
this._serialiseProjectCards(),
|
||||||
}
|
this._serialiseSortBy(),
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
projectCards,
|
||||||
|
sortBy,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toEqual(expectedState)
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
||||||
|
@ -18,7 +18,6 @@ export class ToolbarFixture {
|
|||||||
filletButton!: Locator
|
filletButton!: Locator
|
||||||
chamferButton!: Locator
|
chamferButton!: Locator
|
||||||
shellButton!: Locator
|
shellButton!: Locator
|
||||||
revolveButton!: Locator
|
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
@ -48,7 +47,6 @@ export class ToolbarFixture {
|
|||||||
this.filletButton = page.getByTestId('fillet3d')
|
this.filletButton = page.getByTestId('fillet3d')
|
||||||
this.chamferButton = page.getByTestId('chamfer3d')
|
this.chamferButton = page.getByTestId('chamfer3d')
|
||||||
this.shellButton = page.getByTestId('shell')
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.revolveButton = page.getByTestId('revolve')
|
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
this.lineBtn = page.getByTestId('line')
|
this.lineBtn = page.getByTestId('line')
|
||||||
@ -62,9 +60,7 @@ export class ToolbarFixture {
|
|||||||
this.filePane = page.locator('#files-pane')
|
this.filePane = page.locator('#files-pane')
|
||||||
this.featureTreePane = page.locator('#feature-tree-pane')
|
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||||
this.fileCreateToast = page.getByText('Successfully created')
|
this.fileCreateToast = page.getByText('Successfully created')
|
||||||
this.exeIndicator = page.getByTestId(
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
'model-state-indicator-receive-reliable'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get logoLink() {
|
get logoLink() {
|
||||||
|
@ -185,7 +185,7 @@ test(
|
|||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
const crypticErrorText = `Expected a tag declarator`
|
const crypticErrorText = `Expected a tag identifier`
|
||||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||||
|
|
||||||
// black pixel means the scene has been cleared.
|
// black pixel means the scene has been cleared.
|
||||||
@ -572,7 +572,7 @@ test(
|
|||||||
fs.utimesSync(`${dir}/lego/main.kcl`, _1995, _1995)
|
fs.utimesSync(`${dir}/lego/main.kcl`, _1995, _1995)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 600 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
@ -1527,32 +1527,34 @@ test(
|
|||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page, cmdBar, homePage }, testInfo) => {
|
async ({ context, page, cmdBar, homePage }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(path.join(dir, 'router-template-slate'), {
|
await Promise.all([
|
||||||
recursive: true,
|
fsp.mkdir(path.join(dir, 'router-template-slate'), { recursive: true }),
|
||||||
})
|
fsp.mkdir(path.join(dir, 'bracket'), { recursive: true }),
|
||||||
await fsp.copyFile(
|
])
|
||||||
path.join(
|
await Promise.all([
|
||||||
'src',
|
fsp.copyFile(
|
||||||
'wasm-lib',
|
path.join(
|
||||||
'tests',
|
'src',
|
||||||
'executor',
|
'wasm-lib',
|
||||||
'inputs',
|
'tests',
|
||||||
'router-template-slate.kcl'
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'router-template-slate.kcl'
|
||||||
|
),
|
||||||
|
path.join(dir, 'router-template-slate', 'main.kcl')
|
||||||
),
|
),
|
||||||
path.join(dir, 'router-template-slate', 'main.kcl')
|
fsp.copyFile(
|
||||||
)
|
path.join(
|
||||||
await fsp.mkdir(path.join(dir, 'bracket'), { recursive: true })
|
'src',
|
||||||
await fsp.copyFile(
|
'wasm-lib',
|
||||||
path.join(
|
'tests',
|
||||||
'src',
|
'executor',
|
||||||
'wasm-lib',
|
'inputs',
|
||||||
'tests',
|
'focusrite_scarlett_mounting_braket.kcl'
|
||||||
'executor',
|
),
|
||||||
'inputs',
|
path.join(dir, 'bracket', 'main.kcl')
|
||||||
'focusrite_scarlett_mounting_braket.kcl'
|
|
||||||
),
|
),
|
||||||
path.join(dir, 'bracket', 'main.kcl')
|
])
|
||||||
)
|
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
@ -35,7 +35,7 @@ sketch003 = startSketchOn('XY')
|
|||||||
extrude003 = extrude(sketch003, length = 20)
|
extrude003 = extrude(sketch003, length = 20)
|
||||||
`
|
`
|
||||||
|
|
||||||
test.fixme('Check the happy path, for basic changing color', () => {
|
test.describe('Check the happy path, for basic changing color', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
desc: 'User accepts change',
|
desc: 'User accepts change',
|
||||||
@ -134,7 +134,7 @@ test.fixme('Check the happy path, for basic changing color', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('bad path', { tag: ['@skipWin'] }, () => {
|
test.describe('bad path', () => {
|
||||||
test(`bad edit prompt`, async ({
|
test(`bad edit prompt`, async ({
|
||||||
context,
|
context,
|
||||||
homePage,
|
homePage,
|
||||||
|
@ -5,7 +5,7 @@ import { getUtils, executorInputPath } from './test-utils'
|
|||||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
test.describe('Regression tests', { tag: ['@skipWin'] }, () => {
|
test.describe('Regression tests', () => {
|
||||||
// bugs we found that don't fit neatly into other categories
|
// bugs we found that don't fit neatly into other categories
|
||||||
test('bad model has inline error #3251', async ({
|
test('bad model has inline error #3251', async ({
|
||||||
context,
|
context,
|
||||||
@ -239,6 +239,12 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
'Position _ Is Out Of Range... regression test',
|
'Position _ Is Out Of Range... regression test',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ context, page, homePage }) => {
|
async ({ context, page, homePage }) => {
|
||||||
|
// SKip on windows, its being weird.
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'This test is being weird on windows'
|
||||||
|
)
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -419,6 +425,13 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
'ensure you can not export while an export is already going',
|
'ensure you can not export while an export is already going',
|
||||||
{ tag: ['@skipLinux', '@skipWin'] },
|
{ tag: ['@skipLinux', '@skipWin'] },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
|
// This is being weird on ubuntu and windows.
|
||||||
|
test.skip(
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
process.platform === 'linux' || process.platform === 'win32',
|
||||||
|
'This test is being weird on ubuntu'
|
||||||
|
)
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await test.step('Set up the code and durations', async () => {
|
await test.step('Set up the code and durations', async () => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
@ -547,6 +560,8 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
import { uuidv4, roundOff } from 'lib/utils'
|
import { uuidv4, roundOff } from 'lib/utils'
|
||||||
|
|
||||||
test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
test.describe('Sketch tests', () => {
|
||||||
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
||||||
page,
|
page,
|
||||||
context,
|
context,
|
||||||
@ -312,40 +312,32 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|> line(end = [1.97, 2.06])
|
|> line(end = [1.97, 2.06])
|
||||||
|> close()`)
|
|> close()`)
|
||||||
}
|
}
|
||||||
test(
|
test('code pane open at start-handles', async ({ page, homePage }) => {
|
||||||
'code pane open at start-handles',
|
// Load the app with the code panes
|
||||||
{ tag: ['@skipWin'] },
|
await page.addInitScript(async () => {
|
||||||
async ({ page, homePage }) => {
|
localStorage.setItem(
|
||||||
// Load the app with the code panes
|
'store',
|
||||||
await page.addInitScript(async () => {
|
JSON.stringify({
|
||||||
localStorage.setItem(
|
state: {
|
||||||
'store',
|
openPanes: ['code'],
|
||||||
JSON.stringify({
|
},
|
||||||
state: {
|
version: 0,
|
||||||
openPanes: ['code'],
|
})
|
||||||
},
|
)
|
||||||
version: 0,
|
})
|
||||||
})
|
await doEditSegmentsByDraggingHandle(page, homePage, ['code'])
|
||||||
)
|
})
|
||||||
})
|
|
||||||
await doEditSegmentsByDraggingHandle(page, homePage, ['code'])
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('code pane closed at start-handles', async ({ page, homePage }) => {
|
||||||
'code pane closed at start-handles',
|
// Load the app with the code panes
|
||||||
{ tag: ['@skipWin'] },
|
await page.addInitScript(async (persistModelingContext) => {
|
||||||
async ({ page, homePage }) => {
|
localStorage.setItem(
|
||||||
// Load the app with the code panes
|
persistModelingContext,
|
||||||
await page.addInitScript(async (persistModelingContext) => {
|
JSON.stringify({ openPanes: [] })
|
||||||
localStorage.setItem(
|
)
|
||||||
persistModelingContext,
|
}, PERSIST_MODELING_CONTEXT)
|
||||||
JSON.stringify({ openPanes: [] })
|
await doEditSegmentsByDraggingHandle(page, homePage, [])
|
||||||
)
|
})
|
||||||
}, PERSIST_MODELING_CONTEXT)
|
|
||||||
await doEditSegmentsByDraggingHandle(page, homePage, [])
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Can edit a circle center and radius by dragging its handles', async ({
|
test('Can edit a circle center and radius by dragging its handles', async ({
|
||||||
@ -644,6 +636,8 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|> revolve({ axis = "X" }, %)`)
|
|> revolve({ axis = "X" }, %)`)
|
||||||
})
|
})
|
||||||
test('Can add multiple sketches', async ({ page, homePage }) => {
|
test('Can add multiple sketches', async ({ page, homePage }) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
@ -841,6 +835,8 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -890,7 +886,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
// sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
|
// sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
|
||||||
// otherwise the cmdbar would be waiting for a selection.
|
// otherwise the cmdbar would be waiting for a selection.
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'selection : 1 segment', exact: false })
|
page.getByRole('button', { name: 'selection : 1 face', exact: false })
|
||||||
).toBeVisible({
|
).toBeVisible({
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
@ -1431,7 +1427,7 @@ test.describe('Redirecting to home page and back to the original file should cle
|
|||||||
'persistCode',
|
'persistCode',
|
||||||
` sketch001 = startSketchOn('XZ')
|
` sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([256.85, 14.41], %)
|
|> startProfileAt([256.85, 14.41], %)
|
||||||
|> line(endAbsolute = [0, 211.07])
|
|> lineTo([0, 211.07], %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -55,6 +55,13 @@ test.skip(
|
|||||||
'exports of each format should work',
|
'exports of each format should work',
|
||||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||||
async ({ page, context }) => {
|
async ({ page, context }) => {
|
||||||
|
// skip on macos and windows.
|
||||||
|
test.skip(
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
process.platform === 'darwin' || process.platform === 'win32',
|
||||||
|
'Skip on macos and windows'
|
||||||
|
)
|
||||||
|
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -552,16 +552,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
cloneFile: async (name: string) => {
|
|
||||||
return test?.step(`Cloning file '${name}'`, async () => {
|
|
||||||
await page
|
|
||||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
|
||||||
.filter({ hasText: name })
|
|
||||||
.click({ button: 'right' })
|
|
||||||
await page.getByTestId('context-menu-clone').click()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
selectFile: async (name: string) => {
|
selectFile: async (name: string) => {
|
||||||
return test?.step(`Select ${name}`, async () => {
|
return test?.step(`Select ${name}`, async () => {
|
||||||
await page
|
await page
|
||||||
|
@ -3,8 +3,13 @@ import { EngineCommand } from 'lang/std/artifactGraph'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
|
|
||||||
test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
test.describe('Testing Camera Movement', () => {
|
||||||
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
||||||
|
// TODO: fix this test on windows too after the electron migration
|
||||||
|
const winOrMac =
|
||||||
|
process.platform === 'win32' || process.platform === 'darwin'
|
||||||
|
// eslint-disable-next-line
|
||||||
|
test.skip(winOrMac, 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -342,6 +347,8 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
|||||||
homePage,
|
homePage,
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
/**
|
/**
|
||||||
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
||||||
* set within cameraMouseDragGuards in cameraControls.ts,
|
* set within cameraMouseDragGuards in cameraControls.ts,
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { XOR } from 'lib/utils'
|
import { XOR } from 'lib/utils'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
test.describe('Testing constraints', () => {
|
||||||
test('Can constrain line length', async ({ page, homePage }) => {
|
test('Can constrain line length', async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -129,6 +129,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
})
|
})
|
||||||
test.describe('Test perpendicular distance constraint', () => {
|
test.describe('Test perpendicular distance constraint', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Add variable',
|
testName: 'Add variable',
|
||||||
@ -249,6 +251,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test distance between constraint', () => {
|
test.describe('Test distance between constraint', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Add variable',
|
testName: 'Add variable',
|
||||||
@ -468,6 +472,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test Angle constraint double segment selection', () => {
|
test.describe('Test Angle constraint double segment selection', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Add variable',
|
testName: 'Add variable',
|
||||||
@ -658,6 +664,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test Length constraint single selection', () => {
|
test.describe('Test Length constraint single selection', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Length - Add variable',
|
testName: 'Length - Add variable',
|
||||||
@ -843,6 +851,8 @@ part002 = startSketchOn('XZ')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Two segment - no modal constraints', () => {
|
test.describe('Two segment - no modal constraints', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
codeAfter: `|> angledLine([83, segLen(seg01)], %)`,
|
codeAfter: `|> angledLine([83, segLen(seg01)], %)`,
|
||||||
|
@ -3,7 +3,9 @@ import { getUtils } from './test-utils'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { TEST_CODE_GIZMO } from './storageStates'
|
import { TEST_CODE_GIZMO } from './storageStates'
|
||||||
|
|
||||||
test.describe('Testing Gizmo', { tag: ['@skipWin'] }, () => {
|
test.describe('Testing Gizmo', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testDescription: 'top view',
|
testDescription: 'top view',
|
||||||
|
@ -69,6 +69,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
|
|
||||||
await editor.expectEditor.toContain('// ' + newSample.title)
|
await editor.expectEditor.toContain('// ' + newSample.title)
|
||||||
|
await expect(unitsToast('in')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -157,6 +158,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
|
await expect(unitsToast('in')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Now overwrite the current file`, async () => {
|
await test.step(`Now overwrite the current file`, async () => {
|
||||||
@ -186,6 +188,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||||
await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
|
await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
|
await expect(unitsToast('mm')).toBeVisible()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -5,248 +5,256 @@ import { Coords2d } from 'lang/std/sketch'
|
|||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
|
||||||
test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
test.describe('Testing selections', () => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
test('Selections work on fresh and edited sketch', async ({
|
test(
|
||||||
page,
|
'Selections work on fresh and edited sketch',
|
||||||
homePage,
|
{ tag: ['@skipWin'] },
|
||||||
}) => {
|
async ({ page, homePage }) => {
|
||||||
// tests mapping works on fresh sketch and edited sketch
|
// Skip on windows its being weird.
|
||||||
// tests using hovers which is the same as selections, because if
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
// source ranges are wrong, hovers won't work
|
|
||||||
const u = await getUtils(page)
|
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
// tests mapping works on fresh sketch and edited sketch
|
||||||
await u.openDebugPanel()
|
// tests using hovers which is the same as selections, because if
|
||||||
|
// source ranges are wrong, hovers won't work
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
const yAxisClick = () =>
|
await homePage.goToModelingScene()
|
||||||
test.step('Click on Y axis', async () => {
|
await u.openDebugPanel()
|
||||||
await page.mouse.move(600, 200, { steps: 5 })
|
|
||||||
await page.mouse.click(600, 200)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
})
|
|
||||||
const xAxisClickAfterExitingSketch = () =>
|
|
||||||
test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => {
|
|
||||||
await page.mouse.click(639, 278)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
})
|
|
||||||
const emptySpaceHover = () =>
|
|
||||||
test.step('Hover over empty space', async () => {
|
|
||||||
await page.mouse.move(700, 143, { steps: 5 })
|
|
||||||
await expect(page.locator('.hover-highlight')).not.toBeVisible()
|
|
||||||
})
|
|
||||||
const emptySpaceClick = () =>
|
|
||||||
test.step(`Click in empty space`, async () => {
|
|
||||||
await page.mouse.click(700, 143)
|
|
||||||
await expect(page.locator('.cm-line').last()).toHaveClass(
|
|
||||||
/cm-activeLine/
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const topHorzSegmentClick = () =>
|
|
||||||
page.mouse
|
|
||||||
.click(startXPx, 500 - PUR * 20)
|
|
||||||
.then(() => page.waitForTimeout(100))
|
|
||||||
const bottomHorzSegmentClick = () =>
|
|
||||||
page.mouse
|
|
||||||
.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
||||||
.then(() => page.waitForTimeout(100))
|
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
const yAxisClick = () =>
|
||||||
await expect(
|
test.step('Click on Y axis', async () => {
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
await page.mouse.move(600, 200, { steps: 5 })
|
||||||
).not.toBeDisabled()
|
await page.mouse.click(600, 200)
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.waitForTimeout(100)
|
||||||
|
})
|
||||||
|
const xAxisClickAfterExitingSketch = () =>
|
||||||
|
test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => {
|
||||||
|
await page.mouse.click(639, 278)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
})
|
||||||
|
const emptySpaceHover = () =>
|
||||||
|
test.step('Hover over empty space', async () => {
|
||||||
|
await page.mouse.move(700, 143, { steps: 5 })
|
||||||
|
await expect(page.locator('.hover-highlight')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
const emptySpaceClick = () =>
|
||||||
|
test.step(`Click in empty space`, async () => {
|
||||||
|
await page.mouse.click(700, 143)
|
||||||
|
await expect(page.locator('.cm-line').last()).toHaveClass(
|
||||||
|
/cm-activeLine/
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const topHorzSegmentClick = () =>
|
||||||
|
page.mouse
|
||||||
|
.click(startXPx, 500 - PUR * 20)
|
||||||
|
.then(() => page.waitForTimeout(100))
|
||||||
|
const bottomHorzSegmentClick = () =>
|
||||||
|
page.mouse
|
||||||
|
.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
.then(() => page.waitForTimeout(100))
|
||||||
|
|
||||||
// select a plane
|
await u.clearCommandLogs()
|
||||||
await page.mouse.click(700, 200)
|
await expect(
|
||||||
await page.waitForTimeout(700) // wait for animation
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
const startXPx = 600
|
// select a plane
|
||||||
await u.closeDebugPanel()
|
await page.mouse.click(700, 200)
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.waitForTimeout(700) // wait for animation
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
const startXPx = 600
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> xLine(${commonPoints.num1}, %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> xLine(${commonPoints.num1}, %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
const selectionSequence = async () => {
|
const selectionSequence = async () => {
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
|
|
||||||
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
|
||||||
// bg-yellow-300/70 is more brittle than hover-highlight, but is closer to the user experience
|
|
||||||
// and will be an easy fix if it breaks because we change the colour
|
|
||||||
await expect(page.locator('.bg-yellow-300\\/70')).toBeVisible()
|
|
||||||
// check mousing off, than mousing onto another line
|
|
||||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
|
||||||
|
|
||||||
// now check clicking works including axis
|
|
||||||
|
|
||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
|
||||||
const constrainButton = page.getByRole('button', {
|
|
||||||
name: 'Length: open menu',
|
|
||||||
})
|
|
||||||
const absXButton = page.getByRole('button', { name: 'Absolute X' })
|
|
||||||
|
|
||||||
await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => {
|
|
||||||
await topHorzSegmentClick()
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absXButton).toBeDisabled()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await yAxisClick()
|
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
await constrainButton.click()
|
|
||||||
await absXButton.and(page.locator(':not([disabled])')).waitFor()
|
|
||||||
await expect(absXButton).not.toBeDisabled()
|
|
||||||
})
|
|
||||||
|
|
||||||
await emptySpaceClick()
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||||
await page.waitForTimeout(100)
|
// bg-yellow-300/70 is more brittle than hover-highlight, but is closer to the user experience
|
||||||
|
// and will be an easy fix if it breaks because we change the colour
|
||||||
|
await expect(page.locator('.bg-yellow-300\\/70')).toBeVisible()
|
||||||
|
// check mousing off, than mousing onto another line
|
||||||
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||||
|
|
||||||
await test.step(`Same selection but click the axis first`, async () => {
|
// now check clicking works including axis
|
||||||
await yAxisClick()
|
|
||||||
await constrainButton.click()
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await expect(absXButton).toBeDisabled()
|
const constrainButton = page.getByRole('button', {
|
||||||
await page.keyboard.down('Shift')
|
name: 'Length: open menu',
|
||||||
await page.waitForTimeout(100)
|
})
|
||||||
await topHorzSegmentClick()
|
const absXButton = page.getByRole('button', { name: 'Absolute X' })
|
||||||
|
|
||||||
|
await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => {
|
||||||
|
await topHorzSegmentClick()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absXButton).toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await yAxisClick()
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await absXButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
|
await expect(absXButton).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
await emptySpaceClick()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await test.step(`Same selection but click the axis first`, async () => {
|
||||||
await constrainButton.click()
|
await yAxisClick()
|
||||||
await expect(absXButton).not.toBeDisabled()
|
await constrainButton.click()
|
||||||
})
|
await expect(absXButton).toBeDisabled()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await topHorzSegmentClick()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
await page.keyboard.up('Shift')
|
||||||
await emptySpaceClick()
|
await constrainButton.click()
|
||||||
|
await expect(absXButton).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// clear selection by clicking on nothing
|
||||||
await test.step(`Same selection but code selection then axis`, async () => {
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
|
await test.step(`Same selection but code selection then axis`, async () => {
|
||||||
|
await page
|
||||||
|
.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
|
.click()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absXButton).toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await yAxisClick()
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absXButton).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
// clear selection by clicking on nothing
|
||||||
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
// select segment in editor than another segment in scene and check there are two cursors
|
||||||
|
// TODO change this back to shift click in the scene, not cmd click in the editor
|
||||||
|
await bottomHorzSegmentClick()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||||
|
|
||||||
|
await page.keyboard.down(
|
||||||
|
process.platform === 'linux' ? 'Control' : 'Meta'
|
||||||
|
)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
|
await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await constrainButton.click()
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
await expect(absXButton).toBeDisabled()
|
await page.waitForTimeout(500)
|
||||||
await page.waitForTimeout(100)
|
await page.keyboard.up(
|
||||||
await yAxisClick()
|
process.platform === 'linux' ? 'Control' : 'Meta'
|
||||||
await page.keyboard.up('Shift')
|
)
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absXButton).not.toBeDisabled()
|
// clear selection by clicking on nothing
|
||||||
|
await emptySpaceClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Test hovering and selecting on fresh sketch`, async () => {
|
||||||
|
await selectionSequence()
|
||||||
})
|
})
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// hovering in fresh sketch worked, lets try exiting and re-entering
|
||||||
await emptySpaceClick()
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// wait for execution done
|
||||||
|
|
||||||
// select segment in editor than another segment in scene and check there are two cursors
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
// TODO change this back to shift click in the scene, not cmd click in the editor
|
await u.closeDebugPanel()
|
||||||
await bottomHorzSegmentClick()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
|
||||||
|
await topHorzSegmentClick()
|
||||||
await page.keyboard.down(
|
await xAxisClickAfterExitingSketch()
|
||||||
process.platform === 'linux' ? 'Control' : 'Meta'
|
|
||||||
)
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
|
await emptySpaceHover()
|
||||||
|
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
// enter sketch again
|
||||||
await page.waitForTimeout(500)
|
await u.doAndWaitForCmd(
|
||||||
await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta')
|
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||||
|
'default_camera_get_settings'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.waitForTimeout(450) // wait for animation
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: { x: 0, y: 0, z: 0 },
|
||||||
|
vantage: { x: 0, y: -1378.01, z: 0 },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
|
||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await test.step(`Test hovering and selecting on edited sketch`, async () => {
|
||||||
|
await selectionSequence()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
)
|
||||||
await test.step(`Test hovering and selecting on fresh sketch`, async () => {
|
|
||||||
await selectionSequence()
|
|
||||||
})
|
|
||||||
|
|
||||||
// hovering in fresh sketch worked, lets try exiting and re-entering
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// wait for execution done
|
|
||||||
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
|
|
||||||
await topHorzSegmentClick()
|
|
||||||
await xAxisClickAfterExitingSketch()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await emptySpaceHover()
|
|
||||||
|
|
||||||
// enter sketch again
|
|
||||||
await u.doAndWaitForCmd(
|
|
||||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
|
||||||
'default_camera_get_settings'
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.waitForTimeout(450) // wait for animation
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await u.sendCustomCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
center: { x: 0, y: 0, z: 0 },
|
|
||||||
vantage: { x: 0, y: -1378.01, z: 0 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await u.sendCustomCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_get_settings',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await emptySpaceClick()
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await test.step(`Test hovering and selecting on edited sketch`, async () => {
|
|
||||||
await selectionSequence()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Solids should be select and deletable', async ({ page, homePage }) => {
|
test('Solids should be select and deletable', async ({ page, homePage }) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
@ -484,6 +492,8 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
@ -435,6 +435,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
// Let this test run longer since we've seen it timeout.
|
// Let this test run longer since we've seen it timeout.
|
||||||
test.setTimeout(180_000)
|
test.setTimeout(180_000)
|
||||||
|
// skip on windows
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'This test is flaky, skipping for now'
|
||||||
|
)
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
@ -75,6 +75,3 @@ publish:
|
|||||||
channel: latest
|
channel: latest
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotesFile: release-notes.md
|
releaseNotesFile: release-notes.md
|
||||||
protocols:
|
|
||||||
- name: Zoo Studio
|
|
||||||
schemes: ['zoo-studio']
|
|
||||||
|
@ -9,8 +9,23 @@ const rootDir = process.cwd()
|
|||||||
const config: ForgeConfig = {
|
const config: ForgeConfig = {
|
||||||
packagerConfig: {
|
packagerConfig: {
|
||||||
asar: true,
|
asar: true,
|
||||||
|
osxSign: (process.env.BUILD_RELEASE === 'true' && {}) || undefined,
|
||||||
|
osxNotarize:
|
||||||
|
(process.env.BUILD_RELEASE === 'true' && {
|
||||||
|
appleId: process.env.APPLE_ID || '',
|
||||||
|
appleIdPassword: process.env.APPLE_PASSWORD || '',
|
||||||
|
teamId: process.env.APPLE_TEAM_ID || '',
|
||||||
|
}) ||
|
||||||
|
undefined,
|
||||||
executableName: 'zoo-modeling-app',
|
executableName: 'zoo-modeling-app',
|
||||||
icon: path.resolve(rootDir, 'assets', 'icon'),
|
icon: path.resolve(rootDir, 'assets', 'icon'),
|
||||||
|
protocols: [
|
||||||
|
{
|
||||||
|
name: 'Zoo Studio',
|
||||||
|
schemes: ['zoo-studio'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extendInfo: 'Info.plist', // Information for file associations.
|
||||||
},
|
},
|
||||||
rebuildConfig: {},
|
rebuildConfig: {},
|
||||||
makers: [],
|
makers: [],
|
||||||
|
2
interface.d.ts
vendored
@ -32,7 +32,6 @@ export interface IElectronAPI {
|
|||||||
callback: (eventType: string, path: string) => void
|
callback: (eventType: string, path: string) => void
|
||||||
) => void
|
) => void
|
||||||
readFile: typeof fs.readFile
|
readFile: typeof fs.readFile
|
||||||
copyFile: typeof fs.copyFile
|
|
||||||
watchFileOff: (path: string, key: string) => void
|
watchFileOff: (path: string, key: string) => void
|
||||||
writeFile: (
|
writeFile: (
|
||||||
path: string,
|
path: string,
|
||||||
@ -66,7 +65,6 @@ export interface IElectronAPI {
|
|||||||
VITE_KC_API_WS_MODELING_URL: string
|
VITE_KC_API_WS_MODELING_URL: string
|
||||||
VITE_KC_API_BASE_URL: string
|
VITE_KC_API_BASE_URL: string
|
||||||
VITE_KC_SITE_BASE_URL: string
|
VITE_KC_SITE_BASE_URL: string
|
||||||
VITE_KC_SITE_APP_URL: string
|
|
||||||
VITE_KC_SKIP_AUTH: string
|
VITE_KC_SKIP_AUTH: string
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS: string
|
VITE_KC_CONNECTION_TIMEOUT_MS: string
|
||||||
VITE_KC_DEV_TOKEN: string
|
VITE_KC_DEV_TOKEN: string
|
||||||
|
18
package.json
@ -103,11 +103,11 @@
|
|||||||
"make:dev": "make dev",
|
"make:dev": "make dev",
|
||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
"tron:start": "electron-forge start",
|
"tron:start": "electron-forge start",
|
||||||
|
"tron:package": "electron-forge package",
|
||||||
"chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'",
|
"chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'",
|
||||||
"tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",
|
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"tronb:vite:prod": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
||||||
"tronb:package:dev": "yarn tronb:vite:dev && electron-builder --config electron-builder.yml",
|
"tronb:package": "electron-builder --config electron-builder.yml",
|
||||||
"tronb:package:prod": "yarn tronb:vite:prod && electron-builder --config electron-builder.yml --publish always",
|
|
||||||
"test-setup": "yarn install && yarn build:wasm",
|
"test-setup": "yarn install && yarn build:wasm",
|
||||||
"test": "vitest --mode development",
|
"test": "vitest --mode development",
|
||||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||||
@ -116,10 +116,10 @@
|
|||||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet",
|
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet",
|
||||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet",
|
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet",
|
||||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet",
|
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet",
|
||||||
"test:playwright:electron:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:electron:windows:local": "yarn tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||||
"test:playwright:electron:macos:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
"test:playwright:electron:ubuntu:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
||||||
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
||||||
},
|
},
|
||||||
@ -204,7 +204,7 @@
|
|||||||
"vite": "^5.4.12",
|
"vite": "^5.4.12",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.6.1",
|
"vitest": "^1.6.0",
|
||||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
"wasm-pack": "^0.13.1",
|
"wasm-pack": "^0.13.1",
|
||||||
"ws": "^8.17.0",
|
"ws": "^8.17.0",
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"rollup": "^4.29.1",
|
"rollup": "^4.29.1",
|
||||||
"rollup-plugin-dts": "^6.1.1",
|
"rollup-plugin-dts": "^6.1.1",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^2.1.9"
|
"vitest": "^2.1.8"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/"
|
"dist/"
|
||||||
|
@ -313,62 +313,62 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||||
|
|
||||||
"@vitest/expect@2.1.9":
|
"@vitest/expect@2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.9.tgz#b566ea20d58ea6578d8dc37040d6c1a47ebe5ff8"
|
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1"
|
||||||
integrity sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==
|
integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/spy" "2.1.9"
|
"@vitest/spy" "2.1.8"
|
||||||
"@vitest/utils" "2.1.9"
|
"@vitest/utils" "2.1.8"
|
||||||
chai "^5.1.2"
|
chai "^5.1.2"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
"@vitest/mocker@2.1.9":
|
"@vitest/mocker@2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.9.tgz#36243b27351ca8f4d0bbc4ef91594ffd2dc25ef5"
|
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.8.tgz#51dec42ac244e949d20009249e033e274e323f73"
|
||||||
integrity sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==
|
integrity sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/spy" "2.1.9"
|
"@vitest/spy" "2.1.8"
|
||||||
estree-walker "^3.0.3"
|
estree-walker "^3.0.3"
|
||||||
magic-string "^0.30.12"
|
magic-string "^0.30.12"
|
||||||
|
|
||||||
"@vitest/pretty-format@2.1.9", "@vitest/pretty-format@^2.1.9":
|
"@vitest/pretty-format@2.1.8", "@vitest/pretty-format@^2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz#434ff2f7611689f9ce70cd7d567eceb883653fdf"
|
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca"
|
||||||
integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==
|
integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
"@vitest/runner@2.1.9":
|
"@vitest/runner@2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.9.tgz#cc18148d2d797fd1fd5908d1f1851d01459be2f6"
|
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.8.tgz#b0e2dd29ca49c25e9323ea2a45a5125d8729759f"
|
||||||
integrity sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==
|
integrity sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/utils" "2.1.9"
|
"@vitest/utils" "2.1.8"
|
||||||
pathe "^1.1.2"
|
pathe "^1.1.2"
|
||||||
|
|
||||||
"@vitest/snapshot@2.1.9":
|
"@vitest/snapshot@2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.9.tgz#24260b93f798afb102e2dcbd7e61c6dfa118df91"
|
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.8.tgz#d5dc204f4b95dc8b5e468b455dfc99000047d2de"
|
||||||
integrity sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==
|
integrity sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/pretty-format" "2.1.9"
|
"@vitest/pretty-format" "2.1.8"
|
||||||
magic-string "^0.30.12"
|
magic-string "^0.30.12"
|
||||||
pathe "^1.1.2"
|
pathe "^1.1.2"
|
||||||
|
|
||||||
"@vitest/spy@2.1.9":
|
"@vitest/spy@2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.9.tgz#cb28538c5039d09818b8bfa8edb4043c94727c60"
|
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447"
|
||||||
integrity sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==
|
integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==
|
||||||
dependencies:
|
dependencies:
|
||||||
tinyspy "^3.0.2"
|
tinyspy "^3.0.2"
|
||||||
|
|
||||||
"@vitest/utils@2.1.9":
|
"@vitest/utils@2.1.8":
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.9.tgz#4f2486de8a54acf7ecbf2c5c24ad7994a680a6c1"
|
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388"
|
||||||
integrity sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==
|
integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/pretty-format" "2.1.9"
|
"@vitest/pretty-format" "2.1.8"
|
||||||
loupe "^3.1.2"
|
loupe "^3.1.2"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
@ -662,10 +662,10 @@ typescript@^5.7.2:
|
|||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||||
|
|
||||||
vite-node@2.1.9:
|
vite-node@2.1.8:
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f"
|
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.8.tgz#9495ca17652f6f7f95ca7c4b568a235e0c8dbac5"
|
||||||
integrity sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==
|
integrity sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==
|
||||||
dependencies:
|
dependencies:
|
||||||
cac "^6.7.14"
|
cac "^6.7.14"
|
||||||
debug "^4.3.7"
|
debug "^4.3.7"
|
||||||
@ -693,18 +693,18 @@ vite@^5.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.3"
|
fsevents "~2.3.3"
|
||||||
|
|
||||||
vitest@^2.1.9:
|
vitest@^2.1.8:
|
||||||
version "2.1.9"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.9.tgz#7d01ffd07a553a51c87170b5e80fea3da7fb41e7"
|
resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.8.tgz#2e6a00bc24833574d535c96d6602fb64163092fa"
|
||||||
integrity sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==
|
integrity sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/expect" "2.1.9"
|
"@vitest/expect" "2.1.8"
|
||||||
"@vitest/mocker" "2.1.9"
|
"@vitest/mocker" "2.1.8"
|
||||||
"@vitest/pretty-format" "^2.1.9"
|
"@vitest/pretty-format" "^2.1.8"
|
||||||
"@vitest/runner" "2.1.9"
|
"@vitest/runner" "2.1.8"
|
||||||
"@vitest/snapshot" "2.1.9"
|
"@vitest/snapshot" "2.1.8"
|
||||||
"@vitest/spy" "2.1.9"
|
"@vitest/spy" "2.1.8"
|
||||||
"@vitest/utils" "2.1.9"
|
"@vitest/utils" "2.1.8"
|
||||||
chai "^5.1.2"
|
chai "^5.1.2"
|
||||||
debug "^4.3.7"
|
debug "^4.3.7"
|
||||||
expect-type "^1.1.0"
|
expect-type "^1.1.0"
|
||||||
@ -716,7 +716,7 @@ vitest@^2.1.9:
|
|||||||
tinypool "^1.0.1"
|
tinypool "^1.0.1"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
vite "^5.0.0"
|
vite "^5.0.0"
|
||||||
vite-node "2.1.9"
|
vite-node "2.1.8"
|
||||||
why-is-node-running "^2.3.0"
|
why-is-node-running "^2.3.0"
|
||||||
|
|
||||||
w3c-keyname@^2.2.4:
|
w3c-keyname@^2.2.4:
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* A safer type guard for arrays since the built-in Array.isArray() asserts `any[]`.
|
|
||||||
*/
|
|
||||||
export function isArray(val: any): val is unknown[] {
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
return Array.isArray(val)
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ import { Text } from '@codemirror/state'
|
|||||||
import { Marked } from '@ts-stack/markdown'
|
import { Marked } from '@ts-stack/markdown'
|
||||||
|
|
||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
import { isArray } from '../lib/utils'
|
|
||||||
|
|
||||||
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
|
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
|
||||||
export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
||||||
@ -46,7 +45,7 @@ export function offsetToPos(doc: Text, offset: number) {
|
|||||||
export function formatMarkdownContents(
|
export function formatMarkdownContents(
|
||||||
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
||||||
): string {
|
): string {
|
||||||
if (isArray(contents)) {
|
if (Array.isArray(contents)) {
|
||||||
return contents.map((c) => formatMarkdownContents(c) + '\n\n').join('')
|
return contents.map((c) => formatMarkdownContents(c) + '\n\n').join('')
|
||||||
} else if (typeof contents === 'string') {
|
} else if (typeof contents === 'string') {
|
||||||
return Marked.parse(contents)
|
return Marked.parse(contents)
|
||||||
|
43
src/App.tsx
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef } from 'react'
|
||||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||||
import { Stream } from './components/Stream'
|
import { Stream } from './components/Stream'
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
@ -24,12 +24,7 @@ import { UnitsMenu } from 'components/UnitsMenu'
|
|||||||
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
||||||
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
||||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||||
import { takeScreenshotOfVideoStreamCanvas } from 'lib/screenshot'
|
|
||||||
import { writeProjectThumbnailFile } from 'lib/desktop'
|
|
||||||
import { useRouteLoaderData } from 'react-router-dom'
|
|
||||||
import { useEngineCommands } from 'components/EngineCommands'
|
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
maybeWriteToDisk()
|
maybeWriteToDisk()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
@ -59,20 +54,14 @@ export function App() {
|
|||||||
|
|
||||||
const projectName = project?.name || null
|
const projectName = project?.name || null
|
||||||
const projectPath = project?.path || null
|
const projectPath = project?.path || null
|
||||||
|
|
||||||
const [commands] = useEngineCommands()
|
|
||||||
const [capturedCanvas, setCapturedCanvas] = useState(false)
|
|
||||||
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
|
||||||
const lastCommandType = commands[commands.length - 1]?.type
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onProjectOpen({ name: projectName, path: projectPath }, file || null)
|
onProjectOpen({ name: projectName, path: projectPath }, file || null)
|
||||||
}, [projectName, projectPath])
|
}, [projectName, projectPath])
|
||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
|
|
||||||
const { settings } = useSettingsAuthContext()
|
const { auth, settings } = useSettingsAuthContext()
|
||||||
const token = useToken()
|
const token = auth?.context?.token
|
||||||
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
@ -102,32 +91,6 @@ export function App() {
|
|||||||
|
|
||||||
useEngineConnectionSubscriptions()
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
// Generate thumbnail.png when loading the app
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
isDesktop() &&
|
|
||||||
!capturedCanvas &&
|
|
||||||
lastCommandType === 'execution-done'
|
|
||||||
) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const projectDirectoryWithoutEndingSlash = loaderData?.project?.path
|
|
||||||
if (!projectDirectoryWithoutEndingSlash) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const dataUrl: string = takeScreenshotOfVideoStreamCanvas()
|
|
||||||
// zoom to fit command does not wait, wait 500ms to see if zoom to fit finishes
|
|
||||||
writeProjectThumbnailFile(dataUrl, projectDirectoryWithoutEndingSlash)
|
|
||||||
.then(() => {})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(
|
|
||||||
`Failed to generate thumbnail for ${projectDirectoryWithoutEndingSlash}`
|
|
||||||
)
|
|
||||||
console.error(e)
|
|
||||||
})
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}, [lastCommandType])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full flex flex-col" ref={ref}>
|
<div className="relative h-full flex flex-col" ref={ref}>
|
||||||
<AppHeader
|
<AppHeader
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useAuthState } from 'machines/appMachine'
|
|
||||||
import Loading from './components/Loading'
|
import Loading from './components/Loading'
|
||||||
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
|
|
||||||
// Wrapper around protected routes, used in src/Router.tsx
|
// Wrapper around protected routes, used in src/Router.tsx
|
||||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||||
const authState = useAuthState()
|
const { auth } = useSettingsAuthContext()
|
||||||
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggingIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>
|
<Loading>
|
||||||
|
@ -37,6 +37,7 @@ import { KclContextProvider } from 'lang/KclProvider'
|
|||||||
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { coreDump } from 'lang/wasm'
|
import { coreDump } from 'lang/wasm'
|
||||||
@ -46,7 +47,6 @@ import { reportRejection } from 'lib/trap'
|
|||||||
import { RouteProvider } from 'components/RouteProvider'
|
import { RouteProvider } from 'components/RouteProvider'
|
||||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
||||||
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
|
|
||||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||||
|
|
||||||
@ -203,7 +203,8 @@ export const Router = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CoreDump() {
|
function CoreDump() {
|
||||||
const token = useToken()
|
const { auth } = useSettingsAuthContext()
|
||||||
|
const token = auth?.context?.token
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
|
@ -22,7 +22,6 @@ import {
|
|||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import { isArray } from 'lib/utils'
|
|
||||||
|
|
||||||
export function Toolbar({
|
export function Toolbar({
|
||||||
className = '',
|
className = '',
|
||||||
@ -122,7 +121,7 @@ export function Toolbar({
|
|||||||
return toolbarConfig[currentMode].items.map((maybeIconConfig) => {
|
return toolbarConfig[currentMode].items.map((maybeIconConfig) => {
|
||||||
if (maybeIconConfig === 'break') {
|
if (maybeIconConfig === 'break') {
|
||||||
return 'break'
|
return 'break'
|
||||||
} else if (isArray(maybeIconConfig)) {
|
} else if (Array.isArray(maybeIconConfig)) {
|
||||||
return maybeIconConfig.map(resolveItemConfig)
|
return maybeIconConfig.map(resolveItemConfig)
|
||||||
} else {
|
} else {
|
||||||
return resolveItemConfig(maybeIconConfig)
|
return resolveItemConfig(maybeIconConfig)
|
||||||
@ -181,7 +180,7 @@ export function Toolbar({
|
|||||||
className="h-5 w-[1px] block bg-chalkboard-30 dark:bg-chalkboard-80"
|
className="h-5 w-[1px] block bg-chalkboard-30 dark:bg-chalkboard-80"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (isArray(maybeIconConfig)) {
|
} else if (Array.isArray(maybeIconConfig)) {
|
||||||
// A button with a dropdown
|
// A button with a dropdown
|
||||||
return (
|
return (
|
||||||
<ActionButtonDropdown
|
<ActionButtonDropdown
|
||||||
|
@ -29,7 +29,6 @@ import * as TWEEN from '@tweenjs/tween.js'
|
|||||||
import { isQuaternionVertical } from './helpers'
|
import { isQuaternionVertical } from './helpers'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
|
||||||
|
|
||||||
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
||||||
const FRAMES_TO_ANIMATE_IN = 30
|
const FRAMES_TO_ANIMATE_IN = 30
|
||||||
@ -407,7 +406,7 @@ export class CameraControls {
|
|||||||
.sub(this.mouseDownPosition)
|
.sub(this.mouseDownPosition)
|
||||||
this.mouseDownPosition.copy(this.mouseNewPosition)
|
this.mouseDownPosition.copy(this.mouseNewPosition)
|
||||||
|
|
||||||
let interaction = this.getInteractionType(event)
|
const interaction = this.getInteractionType(event)
|
||||||
if (interaction === 'none') return
|
if (interaction === 'none') return
|
||||||
|
|
||||||
// If there's a valid interaction and the mouse is moving,
|
// If there's a valid interaction and the mouse is moving,
|
||||||
@ -754,6 +753,8 @@ export class CameraControls {
|
|||||||
didChange = true
|
didChange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.safeLookAtTarget(this.camera.up)
|
||||||
|
|
||||||
// Update the camera's matrices
|
// Update the camera's matrices
|
||||||
this.camera.updateMatrixWorld()
|
this.camera.updateMatrixWorld()
|
||||||
if (didChange || forceUpdate) {
|
if (didChange || forceUpdate) {
|
||||||
@ -1188,24 +1189,14 @@ export class CameraControls {
|
|||||||
this.deferReactUpdate(this.reactCameraProperties)
|
this.deferReactUpdate(this.reactCameraProperties)
|
||||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||||
}
|
}
|
||||||
getInteractionType = (
|
getInteractionType = (event: MouseEvent) =>
|
||||||
event: MouseEvent
|
_getInteractionType(
|
||||||
): CameraDragInteractionType_type | 'none' => {
|
|
||||||
const initialInteractionType = _getInteractionType(
|
|
||||||
this.interactionGuards,
|
this.interactionGuards,
|
||||||
event,
|
event,
|
||||||
this.enablePan,
|
this.enablePan,
|
||||||
this.enableRotate,
|
this.enableRotate,
|
||||||
this.enableZoom
|
this.enableZoom
|
||||||
)
|
)
|
||||||
if (
|
|
||||||
initialInteractionType === 'rotate' &&
|
|
||||||
this.engineCommandManager.settings.cameraOrbit === 'trackball'
|
|
||||||
) {
|
|
||||||
return 'rotatetrackball'
|
|
||||||
}
|
|
||||||
return initialInteractionType
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pure function helpers
|
// Pure function helpers
|
||||||
|
@ -2,11 +2,11 @@ import { Toolbar } from '../Toolbar'
|
|||||||
import UserSidebarMenu from 'components/UserSidebarMenu'
|
import UserSidebarMenu from 'components/UserSidebarMenu'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
import { RefreshButton } from 'components/RefreshButton'
|
import { RefreshButton } from 'components/RefreshButton'
|
||||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { useUser } from 'machines/appMachine'
|
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -24,7 +24,8 @@ export const AppHeader = ({
|
|||||||
style,
|
style,
|
||||||
enableMenu = false,
|
enableMenu = false,
|
||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const user = useUser()
|
const { auth } = useSettingsAuthContext()
|
||||||
|
const user = auth?.context?.user
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
|
@ -7,7 +7,6 @@ import { trap } from 'lib/trap'
|
|||||||
import { codeToIdSelections } from 'lib/selections'
|
import { codeToIdSelections } from 'lib/selections'
|
||||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||||
import { defaultSourceRange, SourceRange, topLevelRange } from 'lang/wasm'
|
import { defaultSourceRange, SourceRange, topLevelRange } from 'lang/wasm'
|
||||||
import { isArray } from 'lib/utils'
|
|
||||||
|
|
||||||
export function AstExplorer() {
|
export function AstExplorer() {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
@ -167,12 +166,12 @@ function DisplayObj({
|
|||||||
{Object.entries(obj).map(([key, value]) => {
|
{Object.entries(obj).map(([key, value]) => {
|
||||||
if (filterKeys.includes(key)) {
|
if (filterKeys.includes(key)) {
|
||||||
return null
|
return null
|
||||||
} else if (isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
return (
|
return (
|
||||||
<li key={key}>
|
<li key={key}>
|
||||||
{`${key}: [`}
|
{`${key}: [`}
|
||||||
<DisplayBody
|
<DisplayBody
|
||||||
body={value as any}
|
body={value}
|
||||||
filterKeys={filterKeys}
|
filterKeys={filterKeys}
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
|
@ -30,7 +30,6 @@ import {
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -48,8 +47,7 @@ export const FileMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings, auth } = useSettingsAuthContext()
|
||||||
const token = useToken()
|
|
||||||
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const { project, file } = projectData
|
const { project, file } = projectData
|
||||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||||
@ -124,43 +122,22 @@ export const FileMachineProvider = ({
|
|||||||
let createdName = input.name.trim() || DEFAULT_FILE_NAME
|
let createdName = input.name.trim() || DEFAULT_FILE_NAME
|
||||||
let createdPath: string
|
let createdPath: string
|
||||||
|
|
||||||
if (
|
if (input.makeDir) {
|
||||||
(input.targetPathToClone &&
|
|
||||||
(await window.electron.statIsDirectory(
|
|
||||||
input.targetPathToClone
|
|
||||||
))) ||
|
|
||||||
input.makeDir
|
|
||||||
) {
|
|
||||||
let { name, path } = getNextDirName({
|
let { name, path } = getNextDirName({
|
||||||
entryName: input.targetPathToClone
|
entryName: createdName,
|
||||||
? window.electron.path.basename(input.targetPathToClone)
|
baseDir: input.selectedDirectory.path,
|
||||||
: createdName,
|
|
||||||
baseDir: input.targetPathToClone
|
|
||||||
? window.electron.path.dirname(input.targetPathToClone)
|
|
||||||
: input.selectedDirectory.path,
|
|
||||||
})
|
})
|
||||||
createdName = name
|
createdName = name
|
||||||
createdPath = path
|
createdPath = path
|
||||||
await window.electron.mkdir(createdPath)
|
await window.electron.mkdir(createdPath)
|
||||||
} else {
|
} else {
|
||||||
const { name, path } = getNextFileName({
|
const { name, path } = getNextFileName({
|
||||||
entryName: input.targetPathToClone
|
entryName: createdName,
|
||||||
? window.electron.path.basename(input.targetPathToClone)
|
baseDir: input.selectedDirectory.path,
|
||||||
: createdName,
|
|
||||||
baseDir: input.targetPathToClone
|
|
||||||
? window.electron.path.dirname(input.targetPathToClone)
|
|
||||||
: input.selectedDirectory.path,
|
|
||||||
})
|
})
|
||||||
createdName = name
|
createdName = name
|
||||||
createdPath = path
|
createdPath = path
|
||||||
if (input.targetPathToClone) {
|
await window.electron.writeFile(createdPath, input.content ?? '')
|
||||||
await window.electron.copyFile(
|
|
||||||
input.targetPathToClone,
|
|
||||||
createdPath
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
await window.electron.writeFile(createdPath, input.content ?? '')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -320,7 +297,7 @@ export const FileMachineProvider = ({
|
|||||||
const kclCommandMemo = useMemo(
|
const kclCommandMemo = useMemo(
|
||||||
() =>
|
() =>
|
||||||
kclCommands({
|
kclCommands({
|
||||||
authToken: token ?? '',
|
authToken: auth?.context?.token ?? '',
|
||||||
projectData,
|
projectData,
|
||||||
settings: {
|
settings: {
|
||||||
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
||||||
|
@ -153,7 +153,6 @@ const FileTreeItem = ({
|
|||||||
onClickDirectory,
|
onClickDirectory,
|
||||||
onCreateFile,
|
onCreateFile,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
onCloneFileOrFolder,
|
|
||||||
newTreeEntry,
|
newTreeEntry,
|
||||||
level = 0,
|
level = 0,
|
||||||
treeSelection,
|
treeSelection,
|
||||||
@ -172,7 +171,6 @@ const FileTreeItem = ({
|
|||||||
) => void
|
) => void
|
||||||
onCreateFile: (name: string) => void
|
onCreateFile: (name: string) => void
|
||||||
onCreateFolder: (name: string) => void
|
onCreateFolder: (name: string) => void
|
||||||
onCloneFileOrFolder: (path: string) => void
|
|
||||||
newTreeEntry: TreeEntry
|
newTreeEntry: TreeEntry
|
||||||
level?: number
|
level?: number
|
||||||
treeSelection: FileEntry | undefined
|
treeSelection: FileEntry | undefined
|
||||||
@ -405,7 +403,6 @@ const FileTreeItem = ({
|
|||||||
currentFile={currentFile}
|
currentFile={currentFile}
|
||||||
onCreateFile={onCreateFile}
|
onCreateFile={onCreateFile}
|
||||||
onCreateFolder={onCreateFolder}
|
onCreateFolder={onCreateFolder}
|
||||||
onCloneFileOrFolder={onCloneFileOrFolder}
|
|
||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
lastDirectoryClicked={lastDirectoryClicked}
|
lastDirectoryClicked={lastDirectoryClicked}
|
||||||
onClickDirectory={onClickDirectory}
|
onClickDirectory={onClickDirectory}
|
||||||
@ -444,7 +441,6 @@ const FileTreeItem = ({
|
|||||||
itemRef={itemRef}
|
itemRef={itemRef}
|
||||||
onRename={addCurrentItemToRenaming}
|
onRename={addCurrentItemToRenaming}
|
||||||
onDelete={() => setIsConfirmingDelete(true)}
|
onDelete={() => setIsConfirmingDelete(true)}
|
||||||
onClone={() => onCloneFileOrFolder(fileOrDir.path)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -454,14 +450,12 @@ interface FileTreeContextMenuProps {
|
|||||||
itemRef: React.RefObject<HTMLElement>
|
itemRef: React.RefObject<HTMLElement>
|
||||||
onRename: () => void
|
onRename: () => void
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
onClone: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileTreeContextMenu({
|
function FileTreeContextMenu({
|
||||||
itemRef,
|
itemRef,
|
||||||
onRename,
|
onRename,
|
||||||
onDelete,
|
onDelete,
|
||||||
onClone,
|
|
||||||
}: FileTreeContextMenuProps) {
|
}: FileTreeContextMenuProps) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const metaKey = platform === 'macos' ? '⌘' : 'Ctrl'
|
const metaKey = platform === 'macos' ? '⌘' : 'Ctrl'
|
||||||
@ -484,13 +478,6 @@ function FileTreeContextMenu({
|
|||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
<ContextMenuItem
|
|
||||||
data-testid="context-menu-clone"
|
|
||||||
onClick={onClone}
|
|
||||||
hotkey=""
|
|
||||||
>
|
|
||||||
Clone
|
|
||||||
</ContextMenuItem>,
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -597,22 +584,9 @@ export const useFileTreeOperations = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneFileOrDir(args: { path: string }) {
|
|
||||||
send({
|
|
||||||
type: 'Create file',
|
|
||||||
data: {
|
|
||||||
name: '',
|
|
||||||
makeDir: false,
|
|
||||||
shouldSetToRename: false,
|
|
||||||
targetPathToClone: args.path,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createFile,
|
createFile,
|
||||||
createFolder,
|
createFolder,
|
||||||
cloneFileOrDir,
|
|
||||||
newTreeEntry,
|
newTreeEntry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -621,8 +595,7 @@ export const FileTree = ({
|
|||||||
className = '',
|
className = '',
|
||||||
onNavigateToFile: closePanel,
|
onNavigateToFile: closePanel,
|
||||||
}: FileTreeProps) => {
|
}: FileTreeProps) => {
|
||||||
const { createFile, createFolder, cloneFileOrDir, newTreeEntry } =
|
const { createFile, createFolder, newTreeEntry } = useFileTreeOperations()
|
||||||
useFileTreeOperations()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
@ -638,7 +611,6 @@ export const FileTree = ({
|
|||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
onCreateFile={(name: string) => createFile({ dryRun: false, name })}
|
onCreateFile={(name: string) => createFile({ dryRun: false, name })}
|
||||||
onCreateFolder={(name: string) => createFolder({ dryRun: false, name })}
|
onCreateFolder={(name: string) => createFolder({ dryRun: false, name })}
|
||||||
onCloneFileOrFolder={(path: string) => cloneFileOrDir({ path })}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -648,12 +620,10 @@ export const FileTreeInner = ({
|
|||||||
onNavigateToFile,
|
onNavigateToFile,
|
||||||
onCreateFile,
|
onCreateFile,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
onCloneFileOrFolder,
|
|
||||||
newTreeEntry,
|
newTreeEntry,
|
||||||
}: {
|
}: {
|
||||||
onCreateFile: (name: string) => void
|
onCreateFile: (name: string) => void
|
||||||
onCreateFolder: (name: string) => void
|
onCreateFolder: (name: string) => void
|
||||||
onCloneFileOrFolder: (path: string) => void
|
|
||||||
newTreeEntry: TreeEntry
|
newTreeEntry: TreeEntry
|
||||||
onNavigateToFile?: () => void
|
onNavigateToFile?: () => void
|
||||||
}) => {
|
}) => {
|
||||||
@ -762,7 +732,6 @@ export const FileTreeInner = ({
|
|||||||
fileOrDir={fileOrDir}
|
fileOrDir={fileOrDir}
|
||||||
onCreateFile={onCreateFile}
|
onCreateFile={onCreateFile}
|
||||||
onCreateFolder={onCreateFolder}
|
onCreateFolder={onCreateFolder}
|
||||||
onCloneFileOrFolder={onCloneFileOrFolder}
|
|
||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
onClickDirectory={onClickDirectory}
|
onClickDirectory={onClickDirectory}
|
||||||
onNavigateToFile={onNavigateToFile_}
|
onNavigateToFile={onNavigateToFile_}
|
||||||
|
@ -27,7 +27,6 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { codeManager } from 'lib/singletons'
|
import { codeManager } from 'lib/singletons'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
return []
|
return []
|
||||||
@ -70,7 +69,8 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||||
|
|
||||||
const token = useToken()
|
const { auth } = useSettingsAuthContext()
|
||||||
|
const token = auth?.context.token
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { useEngineCommands } from './EngineCommands'
|
import { useEngineCommands } from './EngineCommands'
|
||||||
import { Spinner } from './Spinner'
|
import { Spinner } from './Spinner'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
|
||||||
export const ModelStateIndicator = () => {
|
export const ModelStateIndicator = () => {
|
||||||
const [commands] = useEngineCommands()
|
const [commands] = useEngineCommands()
|
||||||
|
|
||||||
const lastCommandType = commands[commands.length - 1]?.type
|
const lastCommandType = commands[commands.length - 1]?.type
|
||||||
|
|
||||||
let className = 'w-6 h-6 '
|
let className = 'w-6 h-6 '
|
||||||
|
@ -89,7 +89,6 @@ import { Node } from 'wasm-lib/kcl/bindings/Node'
|
|||||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -111,6 +110,7 @@ export const ModelingMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
|
auth,
|
||||||
settings: {
|
settings: {
|
||||||
context: {
|
context: {
|
||||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||||
@ -119,7 +119,6 @@ export const ModelingMachineProvider = ({
|
|||||||
cameraProjection,
|
cameraProjection,
|
||||||
highlightEdges,
|
highlightEdges,
|
||||||
showScaleGrid,
|
showScaleGrid,
|
||||||
cameraOrbit,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -128,7 +127,7 @@ export const ModelingMachineProvider = ({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { context, send: fileMachineSend } = useFileContext()
|
const { context, send: fileMachineSend } = useFileContext()
|
||||||
const { file } = useLoaderData() as IndexLoaderData
|
const { file } = useLoaderData() as IndexLoaderData
|
||||||
const token = useToken()
|
const token = auth?.context?.token
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||||
|
|
||||||
@ -1155,7 +1154,6 @@ export const ModelingMachineProvider = ({
|
|||||||
enableSSAO: enableSSAO.current,
|
enableSSAO: enableSSAO.current,
|
||||||
showScaleGrid: showScaleGrid.current,
|
showScaleGrid: showScaleGrid.current,
|
||||||
cameraProjection: cameraProjection.current,
|
cameraProjection: cameraProjection.current,
|
||||||
cameraOrbit: cameraOrbit.current,
|
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
)
|
)
|
||||||
@ -1185,13 +1183,6 @@ export const ModelingMachineProvider = ({
|
|||||||
editorManager.selectionRanges = modelingState.context.selectionRanges
|
editorManager.selectionRanges = modelingState.context.selectionRanges
|
||||||
}, [modelingState.context.selectionRanges])
|
}, [modelingState.context.selectionRanges])
|
||||||
|
|
||||||
// When changing camera modes reset the camera to the default orientation to correct
|
|
||||||
// the up vector otherwise the conconical orientation for the camera modes will be
|
|
||||||
// wrong
|
|
||||||
useEffect(() => {
|
|
||||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
|
||||||
}, [cameraOrbit.current])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onConnectionStateChanged = ({ detail }: CustomEvent) => {
|
const onConnectionStateChanged = ({ detail }: CustomEvent) => {
|
||||||
// If we are in sketch mode we need to exit it.
|
// If we are in sketch mode we need to exit it.
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
} from '@codemirror/state'
|
} from '@codemirror/state'
|
||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from '@codemirror/view'
|
||||||
import { oneDark } from '@codemirror/theme-one-dark'
|
import { oneDark } from '@codemirror/theme-one-dark'
|
||||||
import { isArray } from 'lib/utils'
|
|
||||||
|
|
||||||
//reference: https://github.com/sachinraja/rodemirror/blob/main/src/use-first-render.ts
|
//reference: https://github.com/sachinraja/rodemirror/blob/main/src/use-first-render.ts
|
||||||
const useFirstRender = () => {
|
const useFirstRender = () => {
|
||||||
@ -87,18 +86,6 @@ const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>((props, ref) => {
|
|||||||
return <div ref={editor}></div>
|
return <div ref={editor}></div>
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* The extensions type is quite weird. We need a special helper to preserve the
|
|
||||||
* readonly array type.
|
|
||||||
*
|
|
||||||
* @see https://github.com/microsoft/TypeScript/issues/17002
|
|
||||||
*/
|
|
||||||
function isExtensionArray(
|
|
||||||
extensions: Extension
|
|
||||||
): extensions is readonly Extension[] {
|
|
||||||
return isArray(extensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCodeMirror(props: UseCodeMirror) {
|
export function useCodeMirror(props: UseCodeMirror) {
|
||||||
const {
|
const {
|
||||||
onCreateEditor,
|
onCreateEditor,
|
||||||
@ -116,7 +103,7 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|||||||
const isFirstRender = useFirstRender()
|
const isFirstRender = useFirstRender()
|
||||||
|
|
||||||
const targetExtensions = useMemo(() => {
|
const targetExtensions = useMemo(() => {
|
||||||
let exts = isExtensionArray(extensions) ? extensions : []
|
let exts = Array.isArray(extensions) ? extensions : []
|
||||||
if (theme === 'dark') {
|
if (theme === 'dark') {
|
||||||
exts = [...exts, oneDark]
|
exts = [...exts, oneDark]
|
||||||
} else if (theme === 'light') {
|
} else if (theme === 'light') {
|
||||||
|
@ -122,8 +122,7 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
sidebarName: 'Project Files',
|
sidebarName: 'Project Files',
|
||||||
Content: (props: { id: SidebarType; onClose: () => void }) => {
|
Content: (props: { id: SidebarType; onClose: () => void }) => {
|
||||||
const { createFile, createFolder, cloneFileOrDir, newTreeEntry } =
|
const { createFile, createFolder, newTreeEntry } = useFileTreeOperations()
|
||||||
useFileTreeOperations()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -144,7 +143,6 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
onCreateFolder={(name: string) =>
|
onCreateFolder={(name: string) =>
|
||||||
createFolder({ dryRun: false, name })
|
createFolder({ dryRun: false, name })
|
||||||
}
|
}
|
||||||
onCloneFileOrFolder={(path: string) => cloneFileOrDir({ path })}
|
|
||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -33,7 +33,7 @@ export const OpenInDesktopAppHandler = (props: React.PropsWithChildren) => {
|
|||||||
function onOpenInDesktopApp() {
|
function onOpenInDesktopApp() {
|
||||||
const newSearchParams = new URLSearchParams(globalThis.location.search)
|
const newSearchParams = new URLSearchParams(globalThis.location.search)
|
||||||
newSearchParams.delete(ASK_TO_OPEN_QUERY_PARAM)
|
newSearchParams.delete(ASK_TO_OPEN_QUERY_PARAM)
|
||||||
const newURL = `${ZOO_STUDIO_PROTOCOL}://${globalThis.location.pathname.replace(
|
const newURL = `${ZOO_STUDIO_PROTOCOL}${globalThis.location.pathname.replace(
|
||||||
'/',
|
'/',
|
||||||
''
|
''
|
||||||
)}${searchParams.size > 0 ? `?${newSearchParams.toString()}` : ''}`
|
)}${searchParams.size > 0 ? `?${newSearchParams.toString()}` : ''}`
|
||||||
|
@ -2,7 +2,7 @@ import { FormEvent, useEffect, useRef, useState } from 'react'
|
|||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { ActionButton } from '../ActionButton'
|
import { ActionButton } from '../ActionButton'
|
||||||
import { FILE_EXT, PROJECT_IMAGE_NAME } from 'lib/constants'
|
import { FILE_EXT } from 'lib/constants'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import Tooltip from '../Tooltip'
|
import Tooltip from '../Tooltip'
|
||||||
import { DeleteConfirmationDialog } from './DeleteProjectDialog'
|
import { DeleteConfirmationDialog } from './DeleteProjectDialog'
|
||||||
@ -29,7 +29,7 @@ function ProjectCard({
|
|||||||
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
|
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
|
||||||
const [numberOfFiles, setNumberOfFiles] = useState(1)
|
const [numberOfFiles, setNumberOfFiles] = useState(1)
|
||||||
const [numberOfFolders, setNumberOfFolders] = useState(0)
|
const [numberOfFolders, setNumberOfFolders] = useState(0)
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
// const [imageUrl, setImageUrl] = useState('')
|
||||||
|
|
||||||
let inputRef = useRef<HTMLInputElement>(null)
|
let inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
@ -53,21 +53,18 @@ function ProjectCard({
|
|||||||
setNumberOfFolders(project.directory_count)
|
setNumberOfFolders(project.directory_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupImageUrl() {
|
// async function setupImageUrl() {
|
||||||
const projectImagePath = window.electron.path.join(
|
// const projectImagePath = await join(project.file.path, PROJECT_IMAGE_NAME)
|
||||||
project.path,
|
// if (await exists(projectImagePath)) {
|
||||||
PROJECT_IMAGE_NAME
|
// const imageData = await readFile(projectImagePath)
|
||||||
)
|
// const blob = new Blob([imageData], { type: 'image/jpg' })
|
||||||
if (await window.electron.exists(projectImagePath)) {
|
// const imageUrl = URL.createObjectURL(blob)
|
||||||
const imageData = await window.electron.readFile(projectImagePath)
|
// setImageUrl(imageUrl)
|
||||||
const blob = new Blob([imageData], { type: 'image/png' })
|
// }
|
||||||
const imageUrl = URL.createObjectURL(blob)
|
// }
|
||||||
setImageUrl(imageUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void getNumberOfFiles()
|
void getNumberOfFiles()
|
||||||
void setupImageUrl()
|
// void setupImageUrl()
|
||||||
}, [project.kcl_file_count, project.directory_count])
|
}, [project.kcl_file_count, project.directory_count])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -87,7 +84,7 @@ function ProjectCard({
|
|||||||
to={`${PATHS.FILE}/${encodeURIComponent(project.default_file)}`}
|
to={`${PATHS.FILE}/${encodeURIComponent(project.default_file)}`}
|
||||||
className="flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 group-hover:!hue-rotate-0 min-h-[5em] divide-y divide-primary/40 dark:divide-chalkboard-80 group-hover:!divide-primary"
|
className="flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 group-hover:!hue-rotate-0 min-h-[5em] divide-y divide-primary/40 dark:divide-chalkboard-80 group-hover:!divide-primary"
|
||||||
>
|
>
|
||||||
<div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
|
{/* <div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
|
||||||
{imageUrl && (
|
{imageUrl && (
|
||||||
<img
|
<img
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
@ -95,7 +92,7 @@ function ProjectCard({
|
|||||||
className="h-full w-full transition-transform group-hover:scale-105 object-cover"
|
className="h-full w-full transition-transform group-hover:scale-105 object-cover"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
<div className="pb-2 flex flex-col flex-grow flex-auto gap-2 rounded-b-sm">
|
<div className="pb-2 flex flex-col flex-grow flex-auto gap-2 rounded-b-sm">
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<ProjectCardRenameForm
|
<ProjectCardRenameForm
|
||||||
|
@ -19,8 +19,7 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
|||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import { copyFileShareLink } from 'lib/links'
|
import { copyFileShareLink } from 'lib/links'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
import { DEV } from 'env'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -104,15 +103,13 @@ function ProjectMenuPopover({
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings, auth } = useSettingsAuthContext()
|
||||||
const token = useToken()
|
|
||||||
const machineManager = useContext(MachineManagerContext)
|
const machineManager = useContext(MachineManagerContext)
|
||||||
const commands = useSelector(commandBarActor, commandsSelector)
|
const commands = useSelector(commandBarActor, commandsSelector)
|
||||||
|
|
||||||
const { onProjectClose } = useLspContext()
|
const { onProjectClose } = useLspContext()
|
||||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||||
const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
|
const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
|
||||||
const shareCommandInfo = { name: 'share-file-link', groupId: 'code' }
|
|
||||||
const findCommand = (obj: { name: string; groupId: string }) =>
|
const findCommand = (obj: { name: string; groupId: string }) =>
|
||||||
Boolean(
|
Boolean(
|
||||||
commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
|
commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
|
||||||
@ -194,10 +191,10 @@ function ProjectMenuPopover({
|
|||||||
id: 'share-link',
|
id: 'share-link',
|
||||||
Element: 'button',
|
Element: 'button',
|
||||||
children: 'Share link to file',
|
children: 'Share link to file',
|
||||||
disabled: IS_NIGHTLY_OR_DEBUG || !findCommand(shareCommandInfo),
|
disabled: !DEV,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await copyFileShareLink({
|
await copyFileShareLink({
|
||||||
token: token ?? '',
|
token: auth?.context.token || '',
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
name: project?.name || '',
|
name: project?.name || '',
|
||||||
units: settings.context.modeling.defaultUnit.current,
|
units: settings.context.modeling.defaultUnit.current,
|
||||||
|
@ -8,10 +8,10 @@ import Tooltip from './Tooltip'
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { useToken } from 'machines/appMachine'
|
|
||||||
|
|
||||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||||
const token = useToken()
|
const { auth } = useSettingsAuthContext()
|
||||||
|
const token = auth?.context?.token
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
|
@ -2,12 +2,10 @@ import { useEffect, useState, createContext, ReactNode } from 'react'
|
|||||||
import { useNavigation, useLocation } from 'react-router-dom'
|
import { useNavigation, useLocation } from 'react-router-dom'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { useAuthNavigation } from 'hooks/useAuthNavigation'
|
|
||||||
|
|
||||||
export const RouteProviderContext = createContext({})
|
export const RouteProviderContext = createContext({})
|
||||||
|
|
||||||
export function RouteProvider({ children }: { children: ReactNode }) {
|
export function RouteProvider({ children }: { children: ReactNode }) {
|
||||||
useAuthNavigation()
|
|
||||||
const [first, setFirstState] = useState(true)
|
const [first, setFirstState] = useState(true)
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
@ -2,7 +2,10 @@ import { trap } from 'lib/trap'
|
|||||||
import { useMachine, useSelector } from '@xstate/react'
|
import { useMachine, useSelector } from '@xstate/react'
|
||||||
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
||||||
import { PATHS, BROWSER_PATH } from 'lib/paths'
|
import { PATHS, BROWSER_PATH } from 'lib/paths'
|
||||||
|
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
|
||||||
|
import withBaseUrl from '../lib/withBaseURL'
|
||||||
import React, { createContext, useEffect, useState } from 'react'
|
import React, { createContext, useEffect, useState } from 'react'
|
||||||
|
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||||
import { settingsMachine } from 'machines/settingsMachine'
|
import { settingsMachine } from 'machines/settingsMachine'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import {
|
import {
|
||||||
@ -13,6 +16,7 @@ import {
|
|||||||
} from 'lib/theme'
|
} from 'lib/theme'
|
||||||
import decamelize from 'decamelize'
|
import decamelize from 'decamelize'
|
||||||
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
||||||
|
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
@ -46,6 +50,7 @@ type MachineContext<T extends AnyStateMachine> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SettingsAuthContextType = {
|
type SettingsAuthContextType = {
|
||||||
|
auth: MachineContext<typeof authMachine>
|
||||||
settings: MachineContext<typeof settingsMachine>
|
settings: MachineContext<typeof settingsMachine>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,9 +370,40 @@ export const SettingsAuthProviderBase = ({
|
|||||||
)
|
)
|
||||||
}, [settingsState.context.textEditor.blinkingCursor.current])
|
}, [settingsState.context.textEditor.blinkingCursor.current])
|
||||||
|
|
||||||
|
// Auth machine setup
|
||||||
|
const [authState, authSend, authActor] = useMachine(
|
||||||
|
authMachine.provide({
|
||||||
|
actions: {
|
||||||
|
goToSignInPage: () => {
|
||||||
|
navigate(PATHS.SIGN_IN)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
logout()
|
||||||
|
},
|
||||||
|
goToIndexPage: () => {
|
||||||
|
if (location.pathname.includes(PATHS.SIGN_IN)) {
|
||||||
|
navigate(PATHS.INDEX)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
useStateMachineCommands({
|
||||||
|
machineId: 'auth',
|
||||||
|
state: authState,
|
||||||
|
send: authSend,
|
||||||
|
commandBarConfig: authCommandBarConfig,
|
||||||
|
actor: authActor,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsAuthContext.Provider
|
<SettingsAuthContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
auth: {
|
||||||
|
state: authState,
|
||||||
|
context: authState.context,
|
||||||
|
send: authSend,
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
state: settingsState,
|
state: settingsState,
|
||||||
context: settingsState.context,
|
context: settingsState.context,
|
||||||
@ -381,3 +417,12 @@ export const SettingsAuthProviderBase = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAuthProvider
|
export default SettingsAuthProvider
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
||||||
|
if (isDesktop()) return Promise.resolve(null)
|
||||||
|
return fetch(withBaseUrl('/logout'), {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -4,12 +4,12 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
import { Fragment, useMemo, useState } from 'react'
|
import { Fragment, useMemo, useState } from 'react'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { authActor } from 'machines/appMachine'
|
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const send = authActor.send
|
const send = useSettingsAuthContext()?.auth?.send
|
||||||
|
|
||||||
// We filter this memoized list so that no orphan "break" elements are rendered.
|
// We filter this memoized list so that no orphan "break" elements are rendered.
|
||||||
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||||
|
@ -45,10 +45,10 @@ export const lineHighlightField = StateField.define({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const matchDeco = Decoration.mark({
|
const matchDeco = Decoration.mark({
|
||||||
class: 'bg-yellow-300/70 dark:bg-blue-800/50',
|
class: 'bg-yellow-300/70',
|
||||||
attributes: { 'data-testid': 'hover-highlight' },
|
attributes: { 'data-testid': 'hover-highlight' },
|
||||||
})
|
})
|
||||||
const matchDeco2 = Decoration.mark({
|
const matchDeco2 = Decoration.mark({
|
||||||
class: 'bg-yellow-200/40 dark:bg-blue-700/50',
|
class: 'bg-yellow-200/40',
|
||||||
attributes: { 'data-testid': 'hover-highlight' },
|
attributes: { 'data-testid': 'hover-highlight' },
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
import { Range, Extension, Text } from '@codemirror/state'
|
import { Range, Extension, Text } from '@codemirror/state'
|
||||||
import { NodeProp, Tree } from '@lezer/common'
|
import { NodeProp, Tree } from '@lezer/common'
|
||||||
import { language, syntaxTree } from '@codemirror/language'
|
import { language, syntaxTree } from '@codemirror/language'
|
||||||
import { isArray } from 'lib/utils'
|
|
||||||
|
|
||||||
interface PickerState {
|
interface PickerState {
|
||||||
from: number
|
from: number
|
||||||
@ -80,7 +79,7 @@ function discoverColorsInKCL(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (maybeWidgetOptions) {
|
if (maybeWidgetOptions) {
|
||||||
if (isArray(maybeWidgetOptions)) {
|
if (Array.isArray(maybeWidgetOptions)) {
|
||||||
console.error('Unexpected nested overlays')
|
console.error('Unexpected nested overlays')
|
||||||
ret.push(...maybeWidgetOptions)
|
ret.push(...maybeWidgetOptions)
|
||||||
} else {
|
} else {
|
||||||
@ -151,7 +150,7 @@ function colorPickersDecorations(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isArray(maybeWidgetOptions)) {
|
if (!Array.isArray(maybeWidgetOptions)) {
|
||||||
widgets.push(
|
widgets.push(
|
||||||
Decoration.widget({
|
Decoration.widget({
|
||||||
widget: new ColorPickerWidget(maybeWidgetOptions),
|
widget: new ColorPickerWidget(maybeWidgetOptions),
|
||||||
|
@ -10,7 +10,6 @@ export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as
|
|||||||
| undefined
|
| undefined
|
||||||
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL as string
|
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL as string
|
||||||
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL as string
|
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL as string
|
||||||
export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL as string
|
|
||||||
export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
|
export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
|
||||||
export const VITE_KC_CONNECTION_TIMEOUT_MS =
|
export const VITE_KC_CONNECTION_TIMEOUT_MS =
|
||||||
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined
|
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { PATHS } from 'lib/paths'
|
|
||||||
import { useAuthState } from 'machines/appMachine'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple hook that listens to the auth state of the app and navigates
|
|
||||||
* accordingly.
|
|
||||||
*/
|
|
||||||
export function useAuthNavigation() {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const location = useLocation()
|
|
||||||
const authState = useAuthState()
|
|
||||||
|
|
||||||
// Subscribe to the auth state of the app and navigate accordingly.
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
authState.matches('loggedIn') &&
|
|
||||||
location.pathname.includes(PATHS.SIGN_IN)
|
|
||||||
) {
|
|
||||||
navigate(PATHS.INDEX)
|
|
||||||
} else if (
|
|
||||||
authState.matches('loggedOut') &&
|
|
||||||
!location.pathname.includes(PATHS.SIGN_IN)
|
|
||||||
) {
|
|
||||||
navigate(PATHS.SIGN_IN)
|
|
||||||
}
|
|
||||||
}, [authState])
|
|
||||||
}
|
|
@ -16,15 +16,14 @@ export function useSetupEngineManager(
|
|||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
modelingSend: ReturnType<typeof useModelingContext>['send'],
|
modelingSend: ReturnType<typeof useModelingContext>['send'],
|
||||||
modelingContext: ReturnType<typeof useModelingContext>['context'],
|
modelingContext: ReturnType<typeof useModelingContext>['context'],
|
||||||
settings: SettingsViaQueryString = {
|
settings = {
|
||||||
pool: null,
|
pool: null,
|
||||||
theme: Themes.System,
|
theme: Themes.System,
|
||||||
highlightEdges: true,
|
highlightEdges: true,
|
||||||
enableSSAO: true,
|
enableSSAO: true,
|
||||||
showScaleGrid: false,
|
showScaleGrid: false,
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
cameraOrbit: 'spherical',
|
} as SettingsViaQueryString,
|
||||||
},
|
|
||||||
token?: string
|
token?: string
|
||||||
) {
|
) {
|
||||||
const networkContext = useNetworkContext()
|
const networkContext = useNetworkContext()
|
||||||
|
@ -57,7 +57,6 @@ import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
|||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||||
import { findKwArg } from './util'
|
import { findKwArg } from './util'
|
||||||
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
|
||||||
|
|
||||||
export function startSketchOnDefault(
|
export function startSketchOnDefault(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
@ -1468,8 +1467,6 @@ export async function deleteFromSelection(
|
|||||||
}
|
}
|
||||||
// await prom
|
// await prom
|
||||||
return astClone
|
return astClone
|
||||||
} else if (selection.artifact?.type === 'edgeCut') {
|
|
||||||
return deleteEdgeTreatment(astClone, selection)
|
|
||||||
} else if (varDec.node.init.type === 'PipeExpression') {
|
} else if (varDec.node.init.type === 'PipeExpression') {
|
||||||
const pipeBody = varDec.node.init.body
|
const pipeBody = varDec.node.init.body
|
||||||
if (
|
if (
|
||||||
|
@ -21,19 +21,13 @@ import {
|
|||||||
FilletParameters,
|
FilletParameters,
|
||||||
ChamferParameters,
|
ChamferParameters,
|
||||||
EdgeTreatmentParameters,
|
EdgeTreatmentParameters,
|
||||||
deleteEdgeTreatment,
|
|
||||||
} from './addEdgeTreatment'
|
} from './addEdgeTreatment'
|
||||||
import { getNodeFromPath } from '../queryAst'
|
import { getNodeFromPath } from '../queryAst'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createLiteral } from 'lang/modifyAst'
|
import { createLiteral } from 'lang/modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import {
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
codeManager,
|
|
||||||
editorManager,
|
|
||||||
engineCommandManager,
|
|
||||||
kclManager,
|
|
||||||
} from 'lib/singletons'
|
|
||||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||||
@ -61,13 +55,6 @@ afterAll(() => {
|
|||||||
engineCommandManager.tearDown()
|
engineCommandManager.tearDown()
|
||||||
})
|
})
|
||||||
|
|
||||||
const dependencies = {
|
|
||||||
kclManager,
|
|
||||||
engineCommandManager,
|
|
||||||
editorManager,
|
|
||||||
codeManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
const runGetPathToExtrudeForSegmentSelectionTest = async (
|
const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||||
code: string,
|
code: string,
|
||||||
selectedSegmentSnippet: string,
|
selectedSegmentSnippet: string,
|
||||||
@ -146,8 +133,7 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
|||||||
const pathResult = getPathToExtrudeForSegmentSelection(
|
const pathResult = getPathToExtrudeForSegmentSelection(
|
||||||
ast,
|
ast,
|
||||||
selection,
|
selection,
|
||||||
artifactGraph,
|
artifactGraph
|
||||||
dependencies
|
|
||||||
)
|
)
|
||||||
if (err(pathResult)) return pathResult
|
if (err(pathResult)) return pathResult
|
||||||
const { pathToExtrudeNode } = pathResult
|
const { pathToExtrudeNode } = pathResult
|
||||||
@ -304,13 +290,8 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
|||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply edge treatment to selection
|
// apply edge treatment to seleciton
|
||||||
const result = modifyAstWithEdgeTreatmentAndTag(
|
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
||||||
ast,
|
|
||||||
selection,
|
|
||||||
parameters,
|
|
||||||
dependencies
|
|
||||||
)
|
|
||||||
if (err(result)) {
|
if (err(result)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -320,46 +301,6 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
|||||||
|
|
||||||
expect(newCode).toContain(expectedCode)
|
expect(newCode).toContain(expectedCode)
|
||||||
}
|
}
|
||||||
const runDeleteEdgeTreatmentTest = async (
|
|
||||||
code: string,
|
|
||||||
edgeTreatmentSnippet: string,
|
|
||||||
expectedCode: string
|
|
||||||
) => {
|
|
||||||
// parse ast
|
|
||||||
const ast = assertParse(code)
|
|
||||||
|
|
||||||
// update artifact graph
|
|
||||||
await kclManager.executeAst({ ast })
|
|
||||||
const artifactGraph = engineCommandManager.artifactGraph
|
|
||||||
|
|
||||||
// define snippet range
|
|
||||||
const edgeTreatmentRange = topLevelRange(
|
|
||||||
code.indexOf(edgeTreatmentSnippet),
|
|
||||||
code.indexOf(edgeTreatmentSnippet) + edgeTreatmentSnippet.length
|
|
||||||
)
|
|
||||||
|
|
||||||
// find artifact
|
|
||||||
const maybeArtifact = [...artifactGraph].find(([, artifact]) => {
|
|
||||||
if (!('codeRef' in artifact)) return false
|
|
||||||
return isOverlap(artifact.codeRef.range, edgeTreatmentRange)
|
|
||||||
})
|
|
||||||
|
|
||||||
// build selection
|
|
||||||
const selection: Selection = {
|
|
||||||
codeRef: codeRefFromRange(edgeTreatmentRange, ast),
|
|
||||||
artifact: maybeArtifact ? maybeArtifact[1] : undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete edge treatment
|
|
||||||
const result = await deleteEdgeTreatment(ast, selection)
|
|
||||||
if (err(result)) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// recast and check
|
|
||||||
const newCode = recast(result)
|
|
||||||
expect(newCode).toContain(expectedCode)
|
|
||||||
}
|
|
||||||
const createFilletParameters = (radiusValue: number): FilletParameters => ({
|
const createFilletParameters = (radiusValue: number): FilletParameters => ({
|
||||||
type: EdgeTreatmentType.Fillet,
|
type: EdgeTreatmentType.Fillet,
|
||||||
radius: {
|
radius: {
|
||||||
@ -636,191 +577,6 @@ extrude002 = extrude(sketch002, length = -25)
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe(`Testing deleteEdgeTreatment with ${edgeTreatmentType}s`, () => {
|
|
||||||
// simple cases
|
|
||||||
it(`should delete a piped ${edgeTreatmentType} from a single segment`, async () => {
|
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
|
|
||||||
const edgeTreatmentSnippet = `${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)`
|
|
||||||
|
|
||||||
await runDeleteEdgeTreatmentTest(
|
|
||||||
code,
|
|
||||||
edgeTreatmentSnippet,
|
|
||||||
expectedCode
|
|
||||||
)
|
|
||||||
})
|
|
||||||
it(`should delete a non-piped ${edgeTreatmentType} from a single segment`, async () => {
|
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, extrude001)`
|
|
||||||
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, extrude001)`
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)`
|
|
||||||
|
|
||||||
await runDeleteEdgeTreatmentTest(
|
|
||||||
code,
|
|
||||||
edgeTreatmentSnippet,
|
|
||||||
expectedCode
|
|
||||||
)
|
|
||||||
})
|
|
||||||
// getOppositeEdge and getNextAdjacentEdge cases
|
|
||||||
it(`should delete a piped ${edgeTreatmentType} tagged with getOppositeEdge`, async () => {
|
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
|
||||||
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)`
|
|
||||||
|
|
||||||
await runDeleteEdgeTreatmentTest(
|
|
||||||
code,
|
|
||||||
edgeTreatmentSnippet,
|
|
||||||
expectedCode
|
|
||||||
)
|
|
||||||
})
|
|
||||||
it(`should delete a non-piped ${edgeTreatmentType} tagged with getNextAdjacentEdge`, async () => {
|
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getNextAdjacentEdge(seg01)] }, extrude001)`
|
|
||||||
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getNextAdjacentEdge(seg01)] }, extrude001)`
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0])
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg01)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)`
|
|
||||||
|
|
||||||
await runDeleteEdgeTreatmentTest(
|
|
||||||
code,
|
|
||||||
edgeTreatmentSnippet,
|
|
||||||
expectedCode
|
|
||||||
)
|
|
||||||
})
|
|
||||||
// cases with several edge treatments
|
|
||||||
it(`should delete a piped ${edgeTreatmentType} from a body with multiple treatments`, async () => {
|
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0], tag = $seg01)
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg02)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)
|
|
||||||
|> fillet({ radius = 5, tags = [getOppositeEdge(seg02)] }, %)
|
|
||||||
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)
|
|
||||||
chamfer001 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
|
||||||
const edgeTreatmentSnippet = `${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0], tag = $seg01)
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg02)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
|> fillet({
|
|
||||||
radius = 5,
|
|
||||||
tags = [getOppositeEdge(seg02)]
|
|
||||||
}, %)
|
|
||||||
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)
|
|
||||||
chamfer001 = chamfer({
|
|
||||||
length = 5,
|
|
||||||
tags = [getOppositeEdge(seg01)]
|
|
||||||
}, extrude001)`
|
|
||||||
|
|
||||||
await runDeleteEdgeTreatmentTest(
|
|
||||||
code,
|
|
||||||
edgeTreatmentSnippet,
|
|
||||||
expectedCode
|
|
||||||
)
|
|
||||||
})
|
|
||||||
it(`should delete a non-piped ${edgeTreatmentType} from a body with multiple treatments`, async () => {
|
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0], tag = $seg01)
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg02)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)
|
|
||||||
|> fillet({ radius = 5, tags = [getOppositeEdge(seg02)] }, %)
|
|
||||||
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)
|
|
||||||
chamfer001 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
|
||||||
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)`
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, 10], %)
|
|
||||||
|> line(end = [20, 0], tag = $seg01)
|
|
||||||
|> line(end = [0, -20])
|
|
||||||
|> line(end = [-20, 0], tag = $seg02)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()
|
|
||||||
extrude001 = extrude(sketch001, length = -15)
|
|
||||||
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)
|
|
||||||
|> fillet({
|
|
||||||
radius = 5,
|
|
||||||
tags = [getOppositeEdge(seg02)]
|
|
||||||
}, %)
|
|
||||||
chamfer001 = chamfer({
|
|
||||||
length = 5,
|
|
||||||
tags = [getOppositeEdge(seg01)]
|
|
||||||
}, extrude001)`
|
|
||||||
|
|
||||||
await runDeleteEdgeTreatmentTest(
|
|
||||||
code,
|
|
||||||
edgeTreatmentSnippet,
|
|
||||||
expectedCode
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
Identifier,
|
Identifier,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
PipeExpression,
|
|
||||||
Program,
|
Program,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
@ -36,14 +35,15 @@ import {
|
|||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import { KclCommandValue } from 'lib/commandTypes'
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
import { isArray } from 'lib/utils'
|
|
||||||
import { Artifact, getSweepFromSuspectedPath } from 'lang/std/artifactGraph'
|
import { Artifact, getSweepFromSuspectedPath } from 'lang/std/artifactGraph'
|
||||||
|
import {
|
||||||
|
kclManager,
|
||||||
|
engineCommandManager,
|
||||||
|
editorManager,
|
||||||
|
codeManager,
|
||||||
|
} from 'lib/singletons'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { findKwArg } from 'lang/util'
|
import { findKwArg } from 'lang/util'
|
||||||
import { KclManager } from 'lang/KclSingleton'
|
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
|
||||||
import EditorManager from 'editor/manager'
|
|
||||||
import CodeManager from 'lang/codeManager'
|
|
||||||
|
|
||||||
// Edge Treatment Types
|
// Edge Treatment Types
|
||||||
export enum EdgeTreatmentType {
|
export enum EdgeTreatmentType {
|
||||||
@ -65,38 +65,21 @@ export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
|
|||||||
export async function applyEdgeTreatmentToSelection(
|
export async function applyEdgeTreatmentToSelection(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
selection: Selections,
|
selection: Selections,
|
||||||
parameters: EdgeTreatmentParameters,
|
parameters: EdgeTreatmentParameters
|
||||||
dependencies: {
|
|
||||||
kclManager: KclManager
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
editorManager: EditorManager
|
|
||||||
codeManager: CodeManager
|
|
||||||
}
|
|
||||||
): Promise<void | Error> {
|
): Promise<void | Error> {
|
||||||
// 1. clone and modify with edge treatment and tag
|
// 1. clone and modify with edge treatment and tag
|
||||||
const result = modifyAstWithEdgeTreatmentAndTag(
|
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
||||||
ast,
|
|
||||||
selection,
|
|
||||||
parameters,
|
|
||||||
dependencies
|
|
||||||
)
|
|
||||||
if (err(result)) return result
|
if (err(result)) return result
|
||||||
const { modifiedAst, pathToEdgeTreatmentNode } = result
|
const { modifiedAst, pathToEdgeTreatmentNode } = result
|
||||||
|
|
||||||
// 2. update ast
|
// 2. update ast
|
||||||
await updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode, dependencies)
|
await updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modifyAstWithEdgeTreatmentAndTag(
|
export function modifyAstWithEdgeTreatmentAndTag(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
selections: Selections,
|
selections: Selections,
|
||||||
parameters: EdgeTreatmentParameters,
|
parameters: EdgeTreatmentParameters
|
||||||
dependencies: {
|
|
||||||
kclManager: KclManager
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
editorManager: EditorManager
|
|
||||||
codeManager: CodeManager
|
|
||||||
}
|
|
||||||
):
|
):
|
||||||
| { modifiedAst: Node<Program>; pathToEdgeTreatmentNode: Array<PathToNode> }
|
| { modifiedAst: Node<Program>; pathToEdgeTreatmentNode: Array<PathToNode> }
|
||||||
| Error {
|
| Error {
|
||||||
@ -106,7 +89,7 @@ export function modifyAstWithEdgeTreatmentAndTag(
|
|||||||
const astResult = insertParametersIntoAst(clonedAst, parameters)
|
const astResult = insertParametersIntoAst(clonedAst, parameters)
|
||||||
if (err(astResult)) return astResult
|
if (err(astResult)) return astResult
|
||||||
|
|
||||||
const artifactGraph = dependencies.engineCommandManager.artifactGraph
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
|
||||||
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
|
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
|
||||||
const extrudeToTagsMap: Map<
|
const extrudeToTagsMap: Map<
|
||||||
@ -119,8 +102,7 @@ export function modifyAstWithEdgeTreatmentAndTag(
|
|||||||
const result = getPathToExtrudeForSegmentSelection(
|
const result = getPathToExtrudeForSegmentSelection(
|
||||||
clonedAstForGetExtrude,
|
clonedAstForGetExtrude,
|
||||||
selection,
|
selection,
|
||||||
artifactGraph,
|
artifactGraph
|
||||||
dependencies
|
|
||||||
)
|
)
|
||||||
if (err(result)) return result
|
if (err(result)) return result
|
||||||
const { pathToSegmentNode, pathToExtrudeNode } = result
|
const { pathToSegmentNode, pathToExtrudeNode } = result
|
||||||
@ -276,13 +258,7 @@ function insertParametersIntoAst(
|
|||||||
export function getPathToExtrudeForSegmentSelection(
|
export function getPathToExtrudeForSegmentSelection(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
artifactGraph: ArtifactGraph,
|
artifactGraph: ArtifactGraph
|
||||||
dependencies: {
|
|
||||||
kclManager: KclManager
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
editorManager: EditorManager
|
|
||||||
codeManager: CodeManager
|
|
||||||
}
|
|
||||||
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
||||||
const pathToSegmentNode = getNodePathFromSourceRange(
|
const pathToSegmentNode = getNodePathFromSourceRange(
|
||||||
ast,
|
ast,
|
||||||
@ -298,7 +274,7 @@ export function getPathToExtrudeForSegmentSelection(
|
|||||||
const sketchVar = varDecNode.node.declaration.id.name
|
const sketchVar = varDecNode.node.declaration.id.name
|
||||||
|
|
||||||
const sketch = sketchFromKclValue(
|
const sketch = sketchFromKclValue(
|
||||||
dependencies.kclManager.programMemory.get(sketchVar),
|
kclManager.programMemory.get(sketchVar),
|
||||||
sketchVar
|
sketchVar
|
||||||
)
|
)
|
||||||
if (trap(sketch)) return sketch
|
if (trap(sketch)) return sketch
|
||||||
@ -317,28 +293,16 @@ export function getPathToExtrudeForSegmentSelection(
|
|||||||
|
|
||||||
async function updateAstAndFocus(
|
async function updateAstAndFocus(
|
||||||
modifiedAst: Node<Program>,
|
modifiedAst: Node<Program>,
|
||||||
pathToEdgeTreatmentNode: Array<PathToNode>,
|
pathToEdgeTreatmentNode: Array<PathToNode>
|
||||||
dependencies: {
|
|
||||||
kclManager: KclManager
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
editorManager: EditorManager
|
|
||||||
codeManager: CodeManager
|
|
||||||
}
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const updatedAst = await dependencies.kclManager.updateAst(
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
modifiedAst,
|
focusPath: pathToEdgeTreatmentNode,
|
||||||
true,
|
})
|
||||||
{
|
|
||||||
focusPath: pathToEdgeTreatmentNode,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await dependencies.codeManager.updateEditorWithAstAndWriteToFile(
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
updatedAst.newAst
|
|
||||||
)
|
|
||||||
|
|
||||||
if (updatedAst?.selections) {
|
if (updatedAst?.selections) {
|
||||||
dependencies.editorManager.selectRange(updatedAst?.selections)
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,142 +782,3 @@ export const isTagUsedInEdgeTreatment = ({
|
|||||||
|
|
||||||
return edges
|
return edges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete Edge Treatment
|
|
||||||
export async function deleteEdgeTreatment(
|
|
||||||
ast: Node<Program>,
|
|
||||||
selection: Selection
|
|
||||||
): Promise<Node<Program> | Error> {
|
|
||||||
/**
|
|
||||||
* Deletes an edge treatment (fillet or chamfer)
|
|
||||||
* from the AST based on the selection.
|
|
||||||
* Handles both standalone treatments
|
|
||||||
* and those within a PipeExpression.
|
|
||||||
*
|
|
||||||
* Supported cases:
|
|
||||||
* [+] fillet and chamfer
|
|
||||||
* [+] piped and non-piped edge treatments
|
|
||||||
* [-] delete single tag from array of tags (currently whole expression is deleted)
|
|
||||||
* [-] multiple selections with different edge treatments (currently single selection is supported)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 1. Validate Selection Type
|
|
||||||
const { artifact } = selection
|
|
||||||
if (!artifact || artifact.type !== 'edgeCut') {
|
|
||||||
return new Error('Selection is not an edge cut')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { subType: edgeTreatmentType } = artifact
|
|
||||||
if (
|
|
||||||
!edgeTreatmentType ||
|
|
||||||
!['fillet', 'chamfer'].includes(edgeTreatmentType)
|
|
||||||
) {
|
|
||||||
return new Error('Unsupported or missing edge treatment type')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Clone ast and retrieve the VariableDeclarator
|
|
||||||
const astClone = structuredClone(ast)
|
|
||||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
|
||||||
ast,
|
|
||||||
selection?.codeRef?.pathToNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)
|
|
||||||
if (err(varDec)) return varDec
|
|
||||||
|
|
||||||
// 3: Check if edge treatment is in a pipe
|
|
||||||
const inPipe = varDec.node.init.type === 'PipeExpression'
|
|
||||||
|
|
||||||
// 4A. Handle standalone edge treatment
|
|
||||||
if (!inPipe) {
|
|
||||||
const varDecPathStep = varDec.shallowPath[1]
|
|
||||||
|
|
||||||
if (!isArray(varDecPathStep) || typeof varDecPathStep[0] !== 'number') {
|
|
||||||
return new Error(
|
|
||||||
'Invalid shallowPath structure: expected a number at shallowPath[1][0]'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const varDecIndex: number = varDecPathStep[0]
|
|
||||||
|
|
||||||
// Remove entire VariableDeclarator from the ast
|
|
||||||
astClone.body.splice(varDecIndex, 1)
|
|
||||||
return astClone
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4B. Handle edge treatment within pipe
|
|
||||||
if (inPipe) {
|
|
||||||
// Retrieve the CallExpression path
|
|
||||||
const callExp =
|
|
||||||
getNodeFromPath<CallExpression>(
|
|
||||||
ast,
|
|
||||||
selection?.codeRef?.pathToNode,
|
|
||||||
'CallExpression'
|
|
||||||
) ?? null
|
|
||||||
if (err(callExp)) return callExp
|
|
||||||
|
|
||||||
const shallowPath = callExp.shallowPath
|
|
||||||
|
|
||||||
// Initialize variables to hold the PipeExpression path and callIndex
|
|
||||||
let pipeExpressionPath: PathToNode | null = null
|
|
||||||
let callIndex: number | null = null
|
|
||||||
|
|
||||||
// Iterate through the shallowPath to find the PipeExpression and callIndex
|
|
||||||
for (let i = 0; i < shallowPath.length - 1; i++) {
|
|
||||||
const [key, value] = shallowPath[i]
|
|
||||||
|
|
||||||
if (key === 'body' && value === 'PipeExpression') {
|
|
||||||
pipeExpressionPath = shallowPath.slice(0, i + 1)
|
|
||||||
|
|
||||||
const nextStep = shallowPath[i + 1]
|
|
||||||
if (
|
|
||||||
nextStep &&
|
|
||||||
nextStep[1] === 'index' &&
|
|
||||||
typeof nextStep[0] === 'number'
|
|
||||||
) {
|
|
||||||
callIndex = nextStep[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pipeExpressionPath) {
|
|
||||||
return new Error('PipeExpression not found in path')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callIndex === null) {
|
|
||||||
return new Error('Failed to extract CallExpression index')
|
|
||||||
}
|
|
||||||
// Retrieve the PipeExpression node
|
|
||||||
const pipeExpressionNode = getNodeFromPath<PipeExpression>(
|
|
||||||
astClone,
|
|
||||||
pipeExpressionPath,
|
|
||||||
'PipeExpression'
|
|
||||||
)
|
|
||||||
if (err(pipeExpressionNode)) return pipeExpressionNode
|
|
||||||
|
|
||||||
// Ensure that the PipeExpression.body is an array
|
|
||||||
if (!isArray(pipeExpressionNode.node.body)) {
|
|
||||||
return new Error('PipeExpression body is not an array')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the CallExpression at the specified index
|
|
||||||
pipeExpressionNode.node.body.splice(callIndex, 1)
|
|
||||||
|
|
||||||
// Remove VariableDeclarator if PipeExpression.body is empty
|
|
||||||
if (pipeExpressionNode.node.body.length === 0) {
|
|
||||||
const varDecPathStep = varDec.shallowPath[1]
|
|
||||||
if (!isArray(varDecPathStep) || typeof varDecPathStep[0] !== 'number') {
|
|
||||||
return new Error(
|
|
||||||
'Invalid shallowPath structure: expected a number at shallowPath[1][0]'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const varDecIndex: number = varDecPathStep[0]
|
|
||||||
astClone.body.splice(varDecIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return astClone
|
|
||||||
}
|
|
||||||
|
|
||||||
return Error('Delete fillets not implemented')
|
|
||||||
}
|
|
||||||
|
@ -19,28 +19,17 @@ import {
|
|||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||||
import { KclManager } from 'lang/KclSingleton'
|
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
|
||||||
import EditorManager from 'editor/manager'
|
|
||||||
import CodeManager from 'lang/codeManager'
|
|
||||||
|
|
||||||
export function addShell({
|
export function addShell({
|
||||||
node,
|
node,
|
||||||
selection,
|
selection,
|
||||||
artifactGraph,
|
artifactGraph,
|
||||||
thickness,
|
thickness,
|
||||||
dependencies,
|
|
||||||
}: {
|
}: {
|
||||||
node: Node<Program>
|
node: Node<Program>
|
||||||
selection: Selections
|
selection: Selections
|
||||||
artifactGraph: ArtifactGraph
|
artifactGraph: ArtifactGraph
|
||||||
thickness: Expr
|
thickness: Expr
|
||||||
dependencies: {
|
|
||||||
kclManager: KclManager
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
editorManager: EditorManager
|
|
||||||
codeManager: CodeManager
|
|
||||||
}
|
|
||||||
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||||
const modifiedAst = structuredClone(node)
|
const modifiedAst = structuredClone(node)
|
||||||
|
|
||||||
@ -53,8 +42,7 @@ export function addShell({
|
|||||||
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
|
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
|
||||||
clonedAstForGetExtrude,
|
clonedAstForGetExtrude,
|
||||||
graphSelection,
|
graphSelection,
|
||||||
artifactGraph,
|
artifactGraph
|
||||||
dependencies
|
|
||||||
)
|
)
|
||||||
if (err(extrudeLookupResult)) {
|
if (err(extrudeLookupResult)) {
|
||||||
return new Error("Couldn't find extrude")
|
return new Error("Couldn't find extrude")
|
||||||
|
@ -5,11 +5,7 @@ import {
|
|||||||
PathToNode,
|
PathToNode,
|
||||||
Identifier,
|
Identifier,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
PipeExpression,
|
|
||||||
CallExpression,
|
|
||||||
VariableDeclarator,
|
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { ProgramMemory } from 'lang/wasm'
|
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
@ -29,11 +25,9 @@ import {
|
|||||||
createCallExpression,
|
createCallExpression,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
createCallExpressionStdLib,
|
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { codeRefFromRange } from './std/artifactGraph'
|
import { codeRefFromRange } from './std/artifactGraph'
|
||||||
import { addCallExpressionsToPipe, addCloseToPipe } from 'lang/std/sketch'
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -686,115 +680,3 @@ myNestedVar = [
|
|||||||
expect(pathToNode).toEqual(pathToNode2)
|
expect(pathToNode).toEqual(pathToNode2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Testing specific sketch getNodeFromPath workflow', () => {
|
|
||||||
it('should parse the code', () => {
|
|
||||||
const openSketch = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.02, 0.22], %)
|
|
||||||
|> xLine(0.39, %)
|
|
||||||
|> line([0.02, -0.17], %)
|
|
||||||
|> yLine(-0.15, %)
|
|
||||||
|> line([-0.21, -0.02], %)
|
|
||||||
|> xLine(-0.15, %)
|
|
||||||
|> line([-0.02, 0.21], %)
|
|
||||||
|> line([-0.08, 0.05], %)`
|
|
||||||
const ast = assertParse(openSketch)
|
|
||||||
expect(ast.start).toEqual(0)
|
|
||||||
expect(ast.end).toEqual(227)
|
|
||||||
})
|
|
||||||
it('should find the location to add new lineTo', () => {
|
|
||||||
const openSketch = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.02, 0.22], %)
|
|
||||||
|> xLine(0.39, %)
|
|
||||||
|> line([0.02, -0.17], %)
|
|
||||||
|> yLine(-0.15, %)
|
|
||||||
|> line([-0.21, -0.02], %)
|
|
||||||
|> xLine(-0.15, %)
|
|
||||||
|> line([-0.02, 0.21], %)
|
|
||||||
|> line([-0.08, 0.05], %)`
|
|
||||||
const ast = assertParse(openSketch)
|
|
||||||
|
|
||||||
const sketchSnippet = `startProfileAt([0.02, 0.22], %)`
|
|
||||||
const sketchRange = topLevelRange(
|
|
||||||
openSketch.indexOf(sketchSnippet),
|
|
||||||
openSketch.indexOf(sketchSnippet) + sketchSnippet.length
|
|
||||||
)
|
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
|
||||||
const modifiedAst = addCallExpressionsToPipe({
|
|
||||||
node: ast,
|
|
||||||
programMemory: ProgramMemory.empty(),
|
|
||||||
pathToNode: sketchPathToNode,
|
|
||||||
expressions: [
|
|
||||||
createCallExpressionStdLib(
|
|
||||||
'lineTo', // We are forcing lineTo!
|
|
||||||
[
|
|
||||||
createArrayExpression([
|
|
||||||
createCallExpressionStdLib('profileStartX', [
|
|
||||||
createPipeSubstitution(),
|
|
||||||
]),
|
|
||||||
createCallExpressionStdLib('profileStartY', [
|
|
||||||
createPipeSubstitution(),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (err(modifiedAst)) throw modifiedAst
|
|
||||||
const recasted = recast(modifiedAst)
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.02, 0.22], %)
|
|
||||||
|> xLine(0.39, %)
|
|
||||||
|> line([0.02, -0.17], %)
|
|
||||||
|> yLine(-0.15, %)
|
|
||||||
|> line([-0.21, -0.02], %)
|
|
||||||
|> xLine(-0.15, %)
|
|
||||||
|> line([-0.02, 0.21], %)
|
|
||||||
|> line([-0.08, 0.05], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
`
|
|
||||||
expect(recasted).toEqual(expectedCode)
|
|
||||||
})
|
|
||||||
it('it should find the location to add close', () => {
|
|
||||||
const openSketch = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.02, 0.22], %)
|
|
||||||
|> xLine(0.39, %)
|
|
||||||
|> line([0.02, -0.17], %)
|
|
||||||
|> yLine(-0.15, %)
|
|
||||||
|> line([-0.21, -0.02], %)
|
|
||||||
|> xLine(-0.15, %)
|
|
||||||
|> line([-0.02, 0.21], %)
|
|
||||||
|> line([-0.08, 0.05], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
`
|
|
||||||
const ast = assertParse(openSketch)
|
|
||||||
const sketchSnippet = `startProfileAt([0.02, 0.22], %)`
|
|
||||||
const sketchRange = topLevelRange(
|
|
||||||
openSketch.indexOf(sketchSnippet),
|
|
||||||
openSketch.indexOf(sketchSnippet) + sketchSnippet.length
|
|
||||||
)
|
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
|
||||||
const modifiedAst = addCloseToPipe({
|
|
||||||
node: ast,
|
|
||||||
programMemory: ProgramMemory.empty(),
|
|
||||||
pathToNode: sketchPathToNode,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (err(modifiedAst)) throw modifiedAst
|
|
||||||
const recasted = recast(modifiedAst)
|
|
||||||
const expectedCode = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.02, 0.22], %)
|
|
||||||
|> xLine(0.39, %)
|
|
||||||
|> line([0.02, -0.17], %)
|
|
||||||
|> yLine(-0.15, %)
|
|
||||||
|> line([-0.21, -0.02], %)
|
|
||||||
|> xLine(-0.15, %)
|
|
||||||
|> line([-0.02, 0.21], %)
|
|
||||||
|> line([-0.08, 0.05], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close()
|
|
||||||
`
|
|
||||||
expect(recasted).toEqual(expectedCode)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
@ -22,12 +22,11 @@ import {
|
|||||||
topLevelRange,
|
topLevelRange,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
recast,
|
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||||
import { getAngle, isArray } from '../lib/utils'
|
import { getAngle } from '../lib/utils'
|
||||||
import { ARG_TAG, getArgForEnd, getFirstArg } from './std/sketch'
|
import { ARG_TAG, getArgForEnd, getFirstArg } from './std/sketch'
|
||||||
import {
|
import {
|
||||||
getConstraintLevelFromSourceRange,
|
getConstraintLevelFromSourceRange,
|
||||||
@ -80,28 +79,7 @@ export function getNodeFromPath<T>(
|
|||||||
deepPath: successfulPaths,
|
deepPath: successfulPaths,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const stackTraceError = new Error()
|
return new Error('not an object')
|
||||||
const sourceCode = recast(node)
|
|
||||||
const levels = stackTraceError.stack?.split('\n')
|
|
||||||
const aFewFunctionNames: string[] = []
|
|
||||||
let tree = ''
|
|
||||||
levels?.forEach((val, index) => {
|
|
||||||
const fnName = val.trim().split(' ')[1]
|
|
||||||
const ending = index === levels.length - 1 ? ' ' : ' > '
|
|
||||||
tree += fnName + ending
|
|
||||||
if (index < 3) {
|
|
||||||
aFewFunctionNames.push(fnName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const error = new Error(
|
|
||||||
`Failed to stopAt ${stopAt}, ${aFewFunctionNames
|
|
||||||
.filter((a) => a)
|
|
||||||
.join(' > ')}`
|
|
||||||
)
|
|
||||||
console.error(tree)
|
|
||||||
console.error(sourceCode)
|
|
||||||
console.error(error.stack)
|
|
||||||
return error
|
|
||||||
}
|
}
|
||||||
parent = currentNode
|
parent = currentNode
|
||||||
parentEdge = pathItem[0]
|
parentEdge = pathItem[0]
|
||||||
@ -112,7 +90,7 @@ export function getNodeFromPath<T>(
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
typeof stopAt !== 'undefined' &&
|
typeof stopAt !== 'undefined' &&
|
||||||
(isArray(stopAt)
|
(Array.isArray(stopAt)
|
||||||
? stopAt.includes(currentNode.type)
|
? stopAt.includes(currentNode.type)
|
||||||
: currentNode.type === stopAt)
|
: currentNode.type === stopAt)
|
||||||
) {
|
) {
|
||||||
@ -167,7 +145,6 @@ export function getNodeFromPathCurry(
|
|||||||
type KCLNode = Node<
|
type KCLNode = Node<
|
||||||
| Expr
|
| Expr
|
||||||
| ExpressionStatement
|
| ExpressionStatement
|
||||||
| ImportStatement
|
|
||||||
| VariableDeclaration
|
| VariableDeclaration
|
||||||
| VariableDeclarator
|
| VariableDeclarator
|
||||||
| ReturnStatement
|
| ReturnStatement
|
||||||
@ -264,14 +241,10 @@ export function traverse(
|
|||||||
// hmm this smell
|
// hmm this smell
|
||||||
_traverse(_node.object, [...pathToNode, ['object', 'MemberExpression']])
|
_traverse(_node.object, [...pathToNode, ['object', 'MemberExpression']])
|
||||||
_traverse(_node.property, [...pathToNode, ['property', 'MemberExpression']])
|
_traverse(_node.property, [...pathToNode, ['property', 'MemberExpression']])
|
||||||
} else if (_node.type === 'ImportStatement') {
|
} else if ('body' in _node && Array.isArray(_node.body)) {
|
||||||
// Do nothing.
|
_node.body.forEach((expression, index) =>
|
||||||
} else if ('body' in _node && isArray(_node.body)) {
|
|
||||||
// TODO: Program should have a type field, but it currently doesn't.
|
|
||||||
const program = node as Node<Program>
|
|
||||||
program.body.forEach((expression, index) => {
|
|
||||||
_traverse(expression, [...pathToNode, ['body', ''], [index, 'index']])
|
_traverse(expression, [...pathToNode, ['body', ''], [index, 'index']])
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
option?.leave?.(_node)
|
option?.leave?.(_node)
|
||||||
}
|
}
|
||||||
|
@ -248,8 +248,6 @@ class EngineConnection extends EventTarget {
|
|||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
idleMode: boolean = false
|
idleMode: boolean = false
|
||||||
promise?: Promise<void>
|
promise?: Promise<void>
|
||||||
sdpAnswer?: Models['RtcSessionDescription_type']
|
|
||||||
triggeredStart = false
|
|
||||||
|
|
||||||
onIceCandidate = function (
|
onIceCandidate = function (
|
||||||
this: RTCPeerConnection,
|
this: RTCPeerConnection,
|
||||||
@ -555,7 +553,6 @@ class EngineConnection extends EventTarget {
|
|||||||
* did not establish.
|
* did not establish.
|
||||||
*/
|
*/
|
||||||
connect(reconnecting?: boolean): Promise<void> {
|
connect(reconnecting?: boolean): Promise<void> {
|
||||||
const that = this
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.isConnecting() || this.isReady()) {
|
if (this.isConnecting() || this.isReady()) {
|
||||||
return
|
return
|
||||||
@ -586,38 +583,8 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const initiateConnectingExclusive = () => {
|
|
||||||
if (that.triggeredStart) return
|
|
||||||
that.triggeredStart = true
|
|
||||||
|
|
||||||
// Start connecting.
|
|
||||||
that.state = {
|
|
||||||
type: EngineConnectionStateType.Connecting,
|
|
||||||
value: {
|
|
||||||
type: ConnectingType.WebRTCConnecting,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// As soon as this is set, RTCPeerConnection tries to
|
|
||||||
// establish a connection.
|
|
||||||
// @ts-expect-error: Have to ignore because dom.ts doesn't have the right type
|
|
||||||
void that.pc?.setRemoteDescription(that.sdpAnswer)
|
|
||||||
|
|
||||||
that.state = {
|
|
||||||
type: EngineConnectionStateType.Connecting,
|
|
||||||
value: {
|
|
||||||
type: ConnectingType.SetRemoteDescription,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||||
console.log('icecandidate', event.candidate)
|
|
||||||
|
|
||||||
// This is null when the ICE gathering state is done.
|
|
||||||
// Windows ONLY uses this to signal it's done!
|
|
||||||
if (event.candidate === null) {
|
if (event.candidate === null) {
|
||||||
initiateConnectingExclusive()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,6 +595,7 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request a candidate to use
|
||||||
this.send({
|
this.send({
|
||||||
type: 'trickle_ice',
|
type: 'trickle_ice',
|
||||||
candidate: {
|
candidate: {
|
||||||
@ -637,38 +605,8 @@ class EngineConnection extends EventTarget {
|
|||||||
usernameFragment: event.candidate.usernameFragment || undefined,
|
usernameFragment: event.candidate.usernameFragment || undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sometimes the remote end doesn't report the end of candidates.
|
|
||||||
// They have 3 seconds to.
|
|
||||||
setTimeout(() => {
|
|
||||||
initiateConnectingExclusive()
|
|
||||||
}, 3000)
|
|
||||||
}
|
}
|
||||||
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
|
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
|
||||||
this.pc?.addEventListener?.(
|
|
||||||
'icegatheringstatechange',
|
|
||||||
function (_event) {
|
|
||||||
console.log('icegatheringstatechange', this.iceGatheringState)
|
|
||||||
|
|
||||||
if (this.iceGatheringState !== 'complete') return
|
|
||||||
initiateConnectingExclusive()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
this.pc?.addEventListener?.(
|
|
||||||
'iceconnectionstatechange',
|
|
||||||
function (_event) {
|
|
||||||
console.log('iceconnectionstatechange', this.iceConnectionState)
|
|
||||||
console.log('iceconnectionstatechange', this.iceGatheringState)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this.pc?.addEventListener?.('negotiationneeded', function (_event) {
|
|
||||||
console.log('negotiationneeded', this.iceConnectionState)
|
|
||||||
console.log('negotiationneeded', this.iceGatheringState)
|
|
||||||
})
|
|
||||||
this.pc?.addEventListener?.('signalingstatechange', function (event) {
|
|
||||||
console.log('signalingstatechange', this.signalingState)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.onIceCandidateError = (_event: Event) => {
|
this.onIceCandidateError = (_event: Event) => {
|
||||||
const event = _event as RTCPeerConnectionIceErrorEvent
|
const event = _event as RTCPeerConnectionIceErrorEvent
|
||||||
@ -696,8 +634,6 @@ class EngineConnection extends EventTarget {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case 'connecting':
|
|
||||||
break
|
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
case 'failed':
|
case 'failed':
|
||||||
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
||||||
@ -1190,8 +1126,25 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sdpAnswer = answer
|
// As soon as this is set, RTCPeerConnection tries to
|
||||||
|
// establish a connection.
|
||||||
|
// @ts-ignore
|
||||||
|
// Have to ignore because dom.ts doesn't have the right type
|
||||||
|
void this.pc?.setRemoteDescription(answer)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Connecting,
|
||||||
|
value: {
|
||||||
|
type: ConnectingType.SetRemoteDescription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Connecting,
|
||||||
|
value: {
|
||||||
|
type: ConnectingType.WebRTCConnecting,
|
||||||
|
},
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'trickle_ice':
|
case 'trickle_ice':
|
||||||
@ -1282,7 +1235,6 @@ class EngineConnection extends EventTarget {
|
|||||||
if (closedPc && closedUDC && closedWS) {
|
if (closedPc && closedUDC && closedWS) {
|
||||||
// Do not notify the rest of the program that we have cut off anything.
|
// Do not notify the rest of the program that we have cut off anything.
|
||||||
this.state = { type: EngineConnectionStateType.Disconnected }
|
this.state = { type: EngineConnectionStateType.Disconnected }
|
||||||
this.triggeredStart = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1437,7 +1389,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
enableSSAO: true,
|
enableSSAO: true,
|
||||||
showScaleGrid: false,
|
showScaleGrid: false,
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
cameraOrbit: 'spherical',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1486,7 +1437,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
enableSSAO: true,
|
enableSSAO: true,
|
||||||
showScaleGrid: false,
|
showScaleGrid: false,
|
||||||
cameraProjection: 'orthographic',
|
cameraProjection: 'orthographic',
|
||||||
cameraOrbit: 'spherical',
|
|
||||||
},
|
},
|
||||||
// When passed, use a completely separate connecting code path that simply
|
// When passed, use a completely separate connecting code path that simply
|
||||||
// opens a websocket and this is a function that is called when connected.
|
// opens a websocket and this is a function that is called when connected.
|
||||||
@ -2049,7 +1999,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
|
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
|
||||||
/*noop*/
|
/*noop*/
|
||||||
return e
|
return null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +60,7 @@ import {
|
|||||||
mutateObjExpProp,
|
mutateObjExpProp,
|
||||||
findUniqueName,
|
findUniqueName,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { roundOff, getLength, getAngle, isArray } from 'lib/utils'
|
import { roundOff, getLength, getAngle } from 'lib/utils'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { perpendicularDistance } from 'sketch-helpers'
|
import { perpendicularDistance } from 'sketch-helpers'
|
||||||
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||||
@ -96,7 +96,7 @@ export function createFirstArg(
|
|||||||
sketchFn: ToolTip,
|
sketchFn: ToolTip,
|
||||||
val: Expr | [Expr, Expr] | [Expr, Expr, Expr]
|
val: Expr | [Expr, Expr] | [Expr, Expr, Expr]
|
||||||
): Expr | Error {
|
): Expr | Error {
|
||||||
if (isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
'angledLine',
|
'angledLine',
|
||||||
|
@ -57,7 +57,7 @@ import {
|
|||||||
getSketchSegmentFromPathToNode,
|
getSketchSegmentFromPathToNode,
|
||||||
getSketchSegmentFromSourceRange,
|
getSketchSegmentFromSourceRange,
|
||||||
} from './sketchConstraints'
|
} from './sketchConstraints'
|
||||||
import { getAngle, roundOff, normaliseAngle, isArray } from '../../lib/utils'
|
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { findKwArg, findKwArgAny } from 'lang/util'
|
import { findKwArg, findKwArgAny } from 'lang/util'
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ function createCallWrapper(
|
|||||||
tag?: Expr,
|
tag?: Expr,
|
||||||
valueUsedInTransform?: number
|
valueUsedInTransform?: number
|
||||||
): CreatedSketchExprResult {
|
): CreatedSketchExprResult {
|
||||||
if (isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
if (tooltip === 'line') {
|
if (tooltip === 'line') {
|
||||||
const labeledArgs = [createLabeledArg('end', createArrayExpression(val))]
|
const labeledArgs = [createLabeledArg('end', createArrayExpression(val))]
|
||||||
if (tag) {
|
if (tag) {
|
||||||
@ -1330,12 +1330,12 @@ export function getRemoveConstraintsTransform(
|
|||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
if (isTwoValFree) {
|
if (isTwoValFree) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
if (isOneValFree) {
|
if (isOneValFree) {
|
||||||
return transformInfo
|
return transformInfo
|
||||||
}
|
}
|
||||||
@ -1649,7 +1649,7 @@ export function getConstraintType(
|
|||||||
// and for one val sketch functions that the arg is NOT locked down
|
// and for one val sketch functions that the arg is NOT locked down
|
||||||
// these conditions should have been checked previously.
|
// these conditions should have been checked previously.
|
||||||
// completely locked down or not locked down at all does not depend on the fnName so we can check that first
|
// completely locked down or not locked down at all does not depend on the fnName so we can check that first
|
||||||
const isArr = isArray(val)
|
const isArr = Array.isArray(val)
|
||||||
if (!isArr) {
|
if (!isArr) {
|
||||||
if (fnName === 'xLine') return 'yRelative'
|
if (fnName === 'xLine') return 'yRelative'
|
||||||
if (fnName === 'yLine') return 'xRelative'
|
if (fnName === 'yLine') return 'xRelative'
|
||||||
@ -2113,9 +2113,9 @@ export function getConstraintLevelFromSourceRange(
|
|||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
|
|
||||||
if (isTwoValFree) return { level: 'free', range: range }
|
if (isTwoValFree) return { level: 'free', range: range }
|
||||||
if (isOneValFree) return { level: 'partial', range: range }
|
if (isOneValFree) return { level: 'partial', range: range }
|
||||||
@ -2128,7 +2128,7 @@ export function isLiteralArrayOrStatic(
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (!val) return false
|
if (!val) return false
|
||||||
|
|
||||||
if (isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
const a = val[0]
|
const a = val[0]
|
||||||
const b = val[1]
|
const b = val[1]
|
||||||
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
|
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
|
||||||
@ -2142,7 +2142,7 @@ export function isLiteralArrayOrStatic(
|
|||||||
export function isNotLiteralArrayOrStatic(
|
export function isNotLiteralArrayOrStatic(
|
||||||
val: Expr | [Expr, Expr] | [Expr, Expr, Expr]
|
val: Expr | [Expr, Expr] | [Expr, Expr, Expr]
|
||||||
): boolean {
|
): boolean {
|
||||||
if (isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
const a = val[0]
|
const a = val[0]
|
||||||
const b = val[1]
|
const b = val[1]
|
||||||
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
|
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
NumericSuffix,
|
NumericSuffix,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { filterArtifacts } from 'lang/std/artifactGraph'
|
import { filterArtifacts } from 'lang/std/artifactGraph'
|
||||||
import { isArray, isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
|
|
||||||
export function updatePathToNodeFromMap(
|
export function updatePathToNodeFromMap(
|
||||||
oldPath: PathToNode,
|
oldPath: PathToNode,
|
||||||
@ -40,8 +40,8 @@ export function isCursorInSketchCommandRange(
|
|||||||
predicate: (artifact) => {
|
predicate: (artifact) => {
|
||||||
return selectionRanges.graphSelections.some(
|
return selectionRanges.graphSelections.some(
|
||||||
(selection) =>
|
(selection) =>
|
||||||
isArray(selection?.codeRef?.range) &&
|
Array.isArray(selection?.codeRef?.range) &&
|
||||||
isArray(artifact?.codeRef?.range) &&
|
Array.isArray(artifact?.codeRef?.range) &&
|
||||||
isOverlap(selection?.codeRef?.range, artifact.codeRef.range)
|
isOverlap(selection?.codeRef?.range, artifact.codeRef.range)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
default_project_settings,
|
default_project_settings,
|
||||||
base64_decode,
|
base64_decode,
|
||||||
clear_scene_and_bust_cache,
|
clear_scene_and_bust_cache,
|
||||||
change_kcl_settings,
|
|
||||||
reloadModule,
|
reloadModule,
|
||||||
} from 'lib/wasm_lib_wrapper'
|
} from 'lib/wasm_lib_wrapper'
|
||||||
|
|
||||||
@ -57,7 +56,6 @@ import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifa
|
|||||||
import { Artifact } from './std/artifactGraph'
|
import { Artifact } from './std/artifactGraph'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
||||||
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
|
|
||||||
|
|
||||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
@ -158,12 +156,6 @@ export function isTopLevelModule(range: SourceRange): boolean {
|
|||||||
return range[2] === 0
|
return range[2] === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstSourceRange(error: RustKclError): SourceRange {
|
|
||||||
return error.sourceRanges.length > 0
|
|
||||||
? sourceRangeFromRust(error.sourceRanges[0])
|
|
||||||
: defaultSourceRange()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const wasmUrl = () => {
|
export const wasmUrl = () => {
|
||||||
// For when we're in electron (file based) or web server (network based)
|
// For when we're in electron (file based) or web server (network based)
|
||||||
// For some reason relative paths don't work as expected. Otherwise we would
|
// For some reason relative paths don't work as expected. Otherwise we would
|
||||||
@ -261,7 +253,7 @@ export const parse = (code: string | Error): ParseResult | Error => {
|
|||||||
return new KCLError(
|
return new KCLError(
|
||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
firstSourceRange(parsed),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
defaultArtifactGraph()
|
defaultArtifactGraph()
|
||||||
@ -628,7 +620,7 @@ export const executor = async (
|
|||||||
const kclError = new KCLError(
|
const kclError = new KCLError(
|
||||||
parsed.error.kind,
|
parsed.error.kind,
|
||||||
parsed.error.msg,
|
parsed.error.msg,
|
||||||
firstSourceRange(parsed.error),
|
sourceRangeFromRust(parsed.error.sourceRanges[0]),
|
||||||
parsed.operations,
|
parsed.operations,
|
||||||
parsed.artifactCommands,
|
parsed.artifactCommands,
|
||||||
rustArtifactGraphToMap(parsed.artifactGraph)
|
rustArtifactGraphToMap(parsed.artifactGraph)
|
||||||
@ -697,7 +689,7 @@ export const modifyAstForSketch = async (
|
|||||||
const kclError = new KCLError(
|
const kclError = new KCLError(
|
||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
firstSourceRange(parsed),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
defaultArtifactGraph()
|
defaultArtifactGraph()
|
||||||
@ -768,7 +760,7 @@ export function programMemoryInit(): ProgramMemory | Error {
|
|||||||
return new KCLError(
|
return new KCLError(
|
||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
firstSourceRange(parsed),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
defaultArtifactGraph()
|
defaultArtifactGraph()
|
||||||
@ -856,17 +848,3 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
|
|||||||
return new Error('Caught error decoding base64 string: ' + e)
|
return new Error('Caught error decoding base64 string: ' + e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the meta settings for the kcl file.
|
|
||||||
/// Returns the new kcl string with the updated settings.
|
|
||||||
export function changeKclSettings(
|
|
||||||
kcl: string,
|
|
||||||
settings: MetaSettings
|
|
||||||
): string | Error {
|
|
||||||
try {
|
|
||||||
return change_kcl_settings(kcl, JSON.stringify(settings))
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Caught error changing kcl settings: ' + e)
|
|
||||||
return new Error('Caught error changing kcl settings: ' + e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { Command } from 'lib/commandTypes'
|
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||||
import { authActor } from 'machines/appMachine'
|
import { authMachine } from 'machines/authMachine'
|
||||||
import { ACTOR_IDS } from 'machines/machineConstants'
|
|
||||||
|
|
||||||
export const authCommands: Command[] = [
|
type AuthCommandSchema = {}
|
||||||
{
|
|
||||||
groupId: ACTOR_IDS.AUTH,
|
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||||
name: 'log-out',
|
typeof authMachine,
|
||||||
displayName: 'Log out',
|
AuthCommandSchema
|
||||||
icon: 'arrowLeft',
|
> = {
|
||||||
needsReview: false,
|
'Log in': {
|
||||||
onSubmit: () => authActor.send({ type: 'Log out' }),
|
hide: 'both',
|
||||||
},
|
},
|
||||||
]
|
'Log out': {
|
||||||
|
args: [],
|
||||||
|
icon: 'arrowLeft',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -308,6 +308,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
description:
|
description:
|
||||||
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
||||||
icon: 'sweep',
|
icon: 'sweep',
|
||||||
|
status: 'development',
|
||||||
needsReview: false,
|
needsReview: false,
|
||||||
args: {
|
args: {
|
||||||
target: {
|
target: {
|
||||||
@ -316,6 +317,8 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
|
warningMessage:
|
||||||
|
'The sweep workflow is new and under tested. Please break it and report issues.',
|
||||||
},
|
},
|
||||||
trajectory: {
|
trajectory: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
@ -365,6 +368,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
Revolve: {
|
Revolve: {
|
||||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
|
status: 'development',
|
||||||
needsReview: true,
|
needsReview: true,
|
||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
@ -373,6 +377,8 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
|
warningMessage:
|
||||||
|
'The revolve workflow is new and under tested. Please break it and report issues.',
|
||||||
},
|
},
|
||||||
axisOrEdge: {
|
axisOrEdge: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { parseEngineErrorMessage } from './validators'
|
|
||||||
|
|
||||||
describe('parseEngineErrorMessage', () => {
|
|
||||||
it('takes an engine error string and parses its json message', () => {
|
|
||||||
const engineError =
|
|
||||||
'engine error: [{"error_code":"internal_engine","message":"Trajectory curve must be G1 continuous (with continuous tangents)"}]'
|
|
||||||
const message = parseEngineErrorMessage(engineError)
|
|
||||||
expect(message).toEqual(
|
|
||||||
'Trajectory curve must be G1 continuous (with continuous tangents)'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('retuns undefined on strings with different formats', () => {
|
|
||||||
const s1 = 'engine error: []'
|
|
||||||
const s2 = 'blabla'
|
|
||||||
expect(parseEngineErrorMessage(s1)).toBeUndefined()
|
|
||||||
expect(parseEngineErrorMessage(s2)).toBeUndefined()
|
|
||||||
})
|
|
||||||
})
|
|
@ -3,7 +3,6 @@ import { engineCommandManager } from 'lib/singletons'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { CommandBarContext } from 'machines/commandBarMachine'
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { ApiError_type } from '@kittycad/lib/dist/types/src/models'
|
|
||||||
|
|
||||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
for (let tries = 0; tries < numberOfRetries; tries++) {
|
for (let tries = 0; tries < numberOfRetries; tries++) {
|
||||||
@ -47,20 +46,6 @@ function isSelections(selections: unknown): selections is Selections {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseEngineErrorMessage(engineError: string) {
|
|
||||||
const parts = engineError.split('engine error: ')
|
|
||||||
if (parts.length < 2) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const errors = JSON.parse(parts[1]) as ApiError_type[]
|
|
||||||
if (!errors[0]) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors[0].message
|
|
||||||
}
|
|
||||||
|
|
||||||
export const revolveAxisValidator = async ({
|
export const revolveAxisValidator = async ({
|
||||||
data,
|
data,
|
||||||
context,
|
context,
|
||||||
@ -98,7 +83,7 @@ export const revolveAxisValidator = async ({
|
|||||||
value: 360,
|
value: 360,
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = async () => {
|
const revolveAboutEdgeCommand = async () => {
|
||||||
return await engineCommandManager.sendSceneCommand({
|
return await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
@ -107,18 +92,17 @@ export const revolveAxisValidator = async ({
|
|||||||
angle: angleInDegrees,
|
angle: angleInDegrees,
|
||||||
edge_id: edgeSelection,
|
edge_id: edgeSelection,
|
||||||
target: sketchSelection,
|
target: sketchSelection,
|
||||||
// Gotcha: Playwright will fail with larger tolerances, need to use a smaller one.
|
tolerance: 0.0001,
|
||||||
tolerance: 1e-7,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const result = await dryRunWrapper(command)
|
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
|
||||||
if (result?.success) {
|
if (attemptRevolve?.success) {
|
||||||
return true
|
return true
|
||||||
|
} else {
|
||||||
|
// return error message for the toast
|
||||||
|
return 'Unable to revolve with selected edge'
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = parseEngineErrorMessage(result) || 'unknown'
|
|
||||||
return `Unable to revolve with the current selection. Reason: ${reason}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loftValidator = async ({
|
export const loftValidator = async ({
|
||||||
@ -144,7 +128,7 @@ export const loftValidator = async ({
|
|||||||
return 'Unable to loft, selection contains less than two solid2ds'
|
return 'Unable to loft, selection contains less than two solid2ds'
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = async () => {
|
const loftCommand = async () => {
|
||||||
// TODO: check what to do with these
|
// TODO: check what to do with these
|
||||||
const DEFAULT_V_DEGREE = 2
|
const DEFAULT_V_DEGREE = 2
|
||||||
const DEFAULT_TOLERANCE = 2
|
const DEFAULT_TOLERANCE = 2
|
||||||
@ -161,13 +145,13 @@ export const loftValidator = async ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const result = await dryRunWrapper(command)
|
const attempt = await dryRunWrapper(loftCommand)
|
||||||
if (result?.success) {
|
if (attempt?.success) {
|
||||||
return true
|
return true
|
||||||
|
} else {
|
||||||
|
// return error message for the toast
|
||||||
|
return 'Unable to loft with selected sketches'
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = parseEngineErrorMessage(result) || 'unknown'
|
|
||||||
return `Unable to loft with the current selection. Reason: ${reason}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shellValidator = async ({
|
export const shellValidator = async ({
|
||||||
@ -196,7 +180,7 @@ export const shellValidator = async ({
|
|||||||
return "Unable to shell, couldn't find the solid"
|
return "Unable to shell, couldn't find the solid"
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = async () => {
|
const shellCommand = async () => {
|
||||||
// TODO: figure out something better than an arbitrarily small value
|
// TODO: figure out something better than an arbitrarily small value
|
||||||
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
||||||
const DEFAULT_HOLLOW = false
|
const DEFAULT_HOLLOW = false
|
||||||
@ -216,13 +200,12 @@ export const shellValidator = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await dryRunWrapper(command)
|
const attemptShell = await dryRunWrapper(shellCommand)
|
||||||
if (result?.success) {
|
if (attemptShell?.success) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = parseEngineErrorMessage(result) || 'unknown'
|
return 'Unable to shell with the provided selection'
|
||||||
return `Unable to shell with the current selection. Reason: ${reason}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sweepValidator = async ({
|
export const sweepValidator = async ({
|
||||||
@ -258,7 +241,7 @@ export const sweepValidator = async ({
|
|||||||
}
|
}
|
||||||
const target = targetArtifact.pathId
|
const target = targetArtifact.pathId
|
||||||
|
|
||||||
const command = async () => {
|
const sweepCommand = async () => {
|
||||||
// TODO: second look on defaults here
|
// TODO: second look on defaults here
|
||||||
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
|
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
|
||||||
const DEFAULT_SECTIONAL = false
|
const DEFAULT_SECTIONAL = false
|
||||||
@ -278,11 +261,10 @@ export const sweepValidator = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await dryRunWrapper(command)
|
const attemptSweep = await dryRunWrapper(sweepCommand)
|
||||||
if (result?.success) {
|
if (attemptSweep?.success) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = parseEngineErrorMessage(result) || 'unknown'
|
return 'Unable to sweep with the provided selection'
|
||||||
return `Unable to sweep with the current selection. Reason: ${reason}`
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export const FILE_EXT = '.kcl'
|
|||||||
/** Default file to open when a project is opened */
|
/** Default file to open when a project is opened */
|
||||||
export const PROJECT_ENTRYPOINT = `main${FILE_EXT}` as const
|
export const PROJECT_ENTRYPOINT = `main${FILE_EXT}` as const
|
||||||
/** Thumbnail file name */
|
/** Thumbnail file name */
|
||||||
export const PROJECT_IMAGE_NAME = `thumbnail.png` as const
|
export const PROJECT_IMAGE_NAME = `main.jpg` as const
|
||||||
/** The localStorage key for last-opened projects */
|
/** The localStorage key for last-opened projects */
|
||||||
export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const
|
export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const
|
||||||
/** The default name given to new kcl files in a project */
|
/** The default name given to new kcl files in a project */
|
||||||
@ -68,6 +68,8 @@ export const KCL_DEFAULT_DEGREE = `360`
|
|||||||
/** localStorage key for the playwright test-specific app settings file */
|
/** localStorage key for the playwright test-specific app settings file */
|
||||||
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
|
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
|
||||||
|
|
||||||
|
export const DEFAULT_HOST = 'https://api.zoo.dev'
|
||||||
|
export const PROD_APP_URL = 'https://app.zoo.dev'
|
||||||
export const SETTINGS_FILE_NAME = 'settings.toml'
|
export const SETTINGS_FILE_NAME = 'settings.toml'
|
||||||
export const TOKEN_FILE_NAME = 'token.txt'
|
export const TOKEN_FILE_NAME = 'token.txt'
|
||||||
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
|
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
|
||||||
@ -143,7 +145,7 @@ export const VIEW_NAMES_SEMANTIC = {
|
|||||||
export const SIDEBAR_BUTTON_SUFFIX = '-pane-button'
|
export const SIDEBAR_BUTTON_SUFFIX = '-pane-button'
|
||||||
|
|
||||||
/** Custom URL protocol our desktop registers */
|
/** Custom URL protocol our desktop registers */
|
||||||
export const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
|
export const ZOO_STUDIO_PROTOCOL = 'zoo-studio:'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A query parameter that triggers a modal
|
* A query parameter that triggers a modal
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
PROJECT_ENTRYPOINT,
|
PROJECT_ENTRYPOINT,
|
||||||
PROJECT_FOLDER,
|
PROJECT_FOLDER,
|
||||||
PROJECT_IMAGE_NAME,
|
|
||||||
PROJECT_SETTINGS_FILE_NAME,
|
PROJECT_SETTINGS_FILE_NAME,
|
||||||
SETTINGS_FILE_NAME,
|
SETTINGS_FILE_NAME,
|
||||||
TELEMETRY_FILE_NAME,
|
TELEMETRY_FILE_NAME,
|
||||||
@ -626,19 +625,3 @@ export const getUser = async (
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error('unreachable'))
|
return Promise.reject(new Error('unreachable'))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const writeProjectThumbnailFile = async (
|
|
||||||
dataUrl: string,
|
|
||||||
projectDirectoryPath: string
|
|
||||||
) => {
|
|
||||||
const filePath = window.electron.path.join(
|
|
||||||
projectDirectoryPath,
|
|
||||||
PROJECT_IMAGE_NAME
|
|
||||||
)
|
|
||||||
const data = atob(dataUrl.substring('data:image/png;base64,'.length))
|
|
||||||
const asArray = new Uint8Array(data.length)
|
|
||||||
for (let i = 0, len = data.length; i < len; ++i) {
|
|
||||||
asArray[i] = data.charCodeAt(i)
|
|
||||||
}
|
|
||||||
return window.electron.writeFile(filePath, asArray)
|
|
||||||
}
|
|
||||||
|
@ -73,7 +73,7 @@ filletSketch = startSketchOn('XZ')
|
|||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
// Sketch the bend
|
// Sketch the bend
|
||||||
filletExtrude = extrude(filletSketch, length = -width)
|
filletExtrude = extrude(-width, filletSketch)
|
||||||
|
|
||||||
// Create a custom plane for the leg that sits on the wall
|
// Create a custom plane for the leg that sits on the wall
|
||||||
customPlane = {
|
customPlane = {
|
||||||
@ -102,7 +102,7 @@ bracketLeg2Sketch = startSketchOn(customPlane)
|
|||||||
}, %), %)
|
}, %), %)
|
||||||
|
|
||||||
// Extrude the second leg
|
// Extrude the second leg
|
||||||
bracketLeg2Extrude = extrude(bracketLeg2Sketch, length = -thickness)
|
bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|
||||||
|> fillet({
|
|> fillet({
|
||||||
radius = extFilletRadius,
|
radius = extFilletRadius,
|
||||||
tags = [
|
tags = [
|
||||||
|
@ -2,12 +2,13 @@ import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarnin
|
|||||||
import { Command, CommandArgumentOption } from './commandTypes'
|
import { Command, CommandArgumentOption } from './commandTypes'
|
||||||
import { codeManager, kclManager } from './singletons'
|
import { codeManager, kclManager } from './singletons'
|
||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import { FILE_EXT } from './constants'
|
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
|
||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { reportRejection } from './trap'
|
import { parseProjectSettings } from 'lang/wasm'
|
||||||
import { IndexLoaderData } from './types'
|
import { err, reportRejection } from './trap'
|
||||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
|
||||||
import { copyFileShareLink } from './links'
|
import { copyFileShareLink } from './links'
|
||||||
|
import { IndexLoaderData } from './types'
|
||||||
|
|
||||||
interface OnSubmitProps {
|
interface OnSubmitProps {
|
||||||
sampleName: string
|
sampleName: string
|
||||||
@ -67,23 +68,56 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||||
projectPathPart
|
projectPathPart
|
||||||
)}/${encodeURIComponent(primaryKclFile)}`
|
)}/${encodeURIComponent(primaryKclFile)}`
|
||||||
|
const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||||
|
projectPathPart
|
||||||
|
)}/${PROJECT_SETTINGS_FILE_NAME}`
|
||||||
|
|
||||||
fetch(sampleCodeUrl)
|
Promise.allSettled([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
|
||||||
.then(async (codeResponse): Promise<OnSubmitProps> => {
|
.then((results) => {
|
||||||
if (!codeResponse.ok) {
|
const a =
|
||||||
console.error(
|
'value' in results[0] ? results[0].value : results[0].reason
|
||||||
'Failed to fetch sample code:',
|
const b =
|
||||||
codeResponse.statusText
|
'value' in results[1] ? results[1].value : results[1].reason
|
||||||
)
|
return [a, b]
|
||||||
return Promise.reject(new Error('Failed to fetch sample code'))
|
|
||||||
}
|
|
||||||
const code = await codeResponse.text()
|
|
||||||
return {
|
|
||||||
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
|
||||||
code,
|
|
||||||
method: data.method,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
.then(
|
||||||
|
async ([
|
||||||
|
codeResponse,
|
||||||
|
settingsResponse,
|
||||||
|
]): Promise<OnSubmitProps> => {
|
||||||
|
if (!codeResponse.ok) {
|
||||||
|
console.error(
|
||||||
|
'Failed to fetch sample code:',
|
||||||
|
codeResponse.statusText
|
||||||
|
)
|
||||||
|
return Promise.reject(new Error('Failed to fetch sample code'))
|
||||||
|
}
|
||||||
|
const code = await codeResponse.text()
|
||||||
|
|
||||||
|
// It's possible that a sample doesn't have a project.toml
|
||||||
|
// associated with it.
|
||||||
|
let projectSettingsPayload: ReturnType<
|
||||||
|
typeof projectConfigurationToSettingsPayload
|
||||||
|
> = {}
|
||||||
|
if (settingsResponse.ok) {
|
||||||
|
const parsedProjectSettings = parseProjectSettings(
|
||||||
|
await settingsResponse.text()
|
||||||
|
)
|
||||||
|
if (!err(parsedProjectSettings)) {
|
||||||
|
projectSettingsPayload =
|
||||||
|
projectConfigurationToSettingsPayload(parsedProjectSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
||||||
|
code,
|
||||||
|
method: data.method,
|
||||||
|
sampleUnits:
|
||||||
|
projectSettingsPayload.modeling?.defaultUnit || 'mm',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
.then((props) => {
|
.then((props) => {
|
||||||
if (props?.code) {
|
if (props?.code) {
|
||||||
commandProps.specialPropsForSampleCommand
|
commandProps.specialPropsForSampleCommand
|
||||||
@ -134,22 +168,21 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
name: 'share-file-link',
|
// name: 'share-file-link',
|
||||||
displayName: 'Share file',
|
// displayName: 'Share file',
|
||||||
hide: IS_NIGHTLY_OR_DEBUG ? undefined : 'desktop',
|
// description: 'Create a link that contains a copy of the current file.',
|
||||||
description: 'Create a link that contains a copy of the current file.',
|
// groupId: 'code',
|
||||||
groupId: 'code',
|
// needsReview: false,
|
||||||
needsReview: false,
|
// icon: 'link',
|
||||||
icon: 'link',
|
// onSubmit: () => {
|
||||||
onSubmit: () => {
|
// copyFileShareLink({
|
||||||
copyFileShareLink({
|
// token: commandProps.authToken,
|
||||||
token: commandProps.authToken,
|
// code: codeManager.code,
|
||||||
code: codeManager.code,
|
// name: commandProps.projectData.project?.name || '',
|
||||||
name: commandProps.projectData.project?.name || '',
|
// units: commandProps.settings.defaultUnit,
|
||||||
units: commandProps.settings.defaultUnit,
|
// }).catch(reportRejection)
|
||||||
}).catch(reportRejection)
|
// },
|
||||||
},
|
// },
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { VITE_KC_SITE_APP_URL } from 'env'
|
|
||||||
import { createCreateFileUrl } from './links'
|
import { createCreateFileUrl } from './links'
|
||||||
|
|
||||||
describe(`link creation tests`, () => {
|
describe(`link creation tests`, () => {
|
||||||
@ -9,7 +8,7 @@ describe(`link creation tests`, () => {
|
|||||||
|
|
||||||
// Converted with external online tools
|
// Converted with external online tools
|
||||||
const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D`
|
const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D`
|
||||||
const expectedLink = `${VITE_KC_SITE_APP_URL}/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true`
|
const expectedLink = `http://localhost:3000/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true`
|
||||||
|
|
||||||
const result = createCreateFileUrl({ code, name, units })
|
const result = createCreateFileUrl({ code, name, units })
|
||||||
expect(result.toString()).toBe(expectedLink)
|
expect(result.toString()).toBe(expectedLink)
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { ASK_TO_OPEN_QUERY_PARAM, CREATE_FILE_URL_PARAM } from './constants'
|
import {
|
||||||
|
ASK_TO_OPEN_QUERY_PARAM,
|
||||||
|
CREATE_FILE_URL_PARAM,
|
||||||
|
PROD_APP_URL,
|
||||||
|
} from './constants'
|
||||||
import { stringToBase64 } from './base64'
|
import { stringToBase64 } from './base64'
|
||||||
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from 'env'
|
import { DEV, VITE_KC_API_BASE_URL } from 'env'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { err } from './trap'
|
import { err } from './trap'
|
||||||
export interface FileLinkParams {
|
export interface FileLinkParams {
|
||||||
@ -47,7 +51,8 @@ export async function copyFileShareLink(
|
|||||||
* open the URL in the desktop app.
|
* open the URL in the desktop app.
|
||||||
*/
|
*/
|
||||||
export function createCreateFileUrl({ code, name, units }: FileLinkParams) {
|
export function createCreateFileUrl({ code, name, units }: FileLinkParams) {
|
||||||
let origin = VITE_KC_SITE_APP_URL
|
// Use the dev server if we are in development mode
|
||||||
|
let origin = DEV ? 'http://localhost:3000' : PROD_APP_URL
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
[CREATE_FILE_URL_PARAM]: String(true),
|
[CREATE_FILE_URL_PARAM]: String(true),
|
||||||
name,
|
name,
|
||||||
|
@ -29,8 +29,8 @@ describe('library rectangleTool helper functions', () => {
|
|||||||
segAng(rectangleSegmentA001),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close()
|
|> close(%)
|
||||||
`
|
`
|
||||||
// Create ast
|
// Create ast
|
||||||
const _ast = assertParse(sourceCode)
|
const _ast = assertParse(sourceCode)
|
||||||
@ -82,8 +82,8 @@ segAng(rectangleSegmentA001),
|
|||||||
segAng(rectangleSegmentA001),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close()
|
|> close(%)
|
||||||
`
|
`
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toEqual(expectedSourceCode)
|
expect(recasted).toEqual(expectedSourceCode)
|
||||||
|