Compare commits
113 Commits
v0.40.0
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
6c78dbd4c8 | |||
059593372a | |||
1ba8c5af00 | |||
410b4e81eb | |||
30275d86cc | |||
39c40b2cde | |||
907102a8fa | |||
353eca110e | |||
fb56820811 | |||
fb37bb83a8 | |||
f90811695d | |||
5c1dfe0c8e | |||
f06873a0e2 | |||
09025179f9 | |||
521a593451 | |||
87c4e6c74e | |||
82cd106898 | |||
e14cc4ace3 | |||
2a2a31d0ef | |||
f2669223c5 | |||
c3bc1fad6d | |||
96ff1dd55b | |||
82bd04631a | |||
abec2d6d66 | |||
6089b1932a | |||
074fd2b5c7 | |||
b2485b804c | |||
e753082653 | |||
634745bb81 | |||
e3660c75fc | |||
ef61d10615 | |||
c208e16c76 | |||
585ca7e80f | |||
f7bae1d221 | |||
339de00e68 | |||
4f02e45da3 | |||
1908383f0e | |||
68204bb23d | |||
5438a987ab | |||
fa3f934948 | |||
08e714080e | |||
df01c233e4 | |||
b30a37a0b3 | |||
82aefec34d | |||
679b65f643 | |||
d64270d494 | |||
c06b2b4029 | |||
8b8a2bc4e2 | |||
af702ae1b2 | |||
83e72dafa3 | |||
e417e60053 | |||
ebc6b6460d | |||
91f0cfe467 | |||
a2ff0aeceb | |||
f05acf92cc | |||
670faac1e8 | |||
ca09224c92 | |||
5cbd11cec8 | |||
28eb99f655 | |||
c29be6e341 | |||
2193d563c5 | |||
570d159c29 | |||
713886b274 | |||
2aa4a01cb7 | |||
2048c26b9f | |||
cbb8df5904 | |||
bb67a9e9cf | |||
b84d5951b7 | |||
1e5954e5ed | |||
d58a147b7d | |||
96b06247a4 | |||
36d49b1bcb | |||
4748c2d1e0 | |||
698ce671df | |||
a2330a0dbc | |||
c882e34ea9 | |||
1ce3d8ccd0 | |||
15bedd56f4 | |||
746ebf80d1 | |||
02b249bd31 | |||
524fcb03ad | |||
3a9e0c72a8 | |||
5dc983ad7b | |||
81411033d7 | |||
30a24c8ae6 | |||
403cee5f16 | |||
14eeafb70a | |||
f4ecd16ffa | |||
48380be480 | |||
80e32b337f | |||
9378d9862b | |||
1f515b712b | |||
372f2eebcc | |||
e22a9edde8 | |||
75e3f843eb | |||
f0136a5939 | |||
3d2e48732c | |||
7545b61b49 | |||
d1be6d7b64 | |||
8ab24ceee7 | |||
f163870b86 | |||
3fc707a2a4 | |||
238163d7db | |||
bfccb79c1c | |||
fe6d1f8119 | |||
f496d94258 | |||
5d8f3f988a | |||
4f06524776 | |||
d7fe827a9e | |||
049e487ac4 | |||
5bd89047b2 | |||
5822321f35 | |||
401dcf8152 |
1
.github/workflows/build-apps.yml
vendored
@ -5,6 +5,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- pierremtb/4088/create-file-url
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
schedule:
|
||||
|
56
.github/workflows/e2e-tests.yml
vendored
@ -126,20 +126,20 @@ jobs:
|
||||
- name: build electron
|
||||
shell: bash
|
||||
run: yarn tron:package
|
||||
# - name: Run ubuntu/chrome snapshots
|
||||
# if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||
# shell: bash
|
||||
# # 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.
|
||||
# run: |
|
||||
# PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||
# env:
|
||||
# CI: true
|
||||
# NODE_ENV: development
|
||||
# VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
# VITE_KC_SKIP_AUTH: true
|
||||
# token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
# snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||
shell: bash
|
||||
# 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.
|
||||
run: |
|
||||
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
@ -162,20 +162,20 @@ jobs:
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
# - name: Commit changes, if any
|
||||
# if: steps.git-check.outputs.modified == 'true'
|
||||
# shell: bash
|
||||
# run: |
|
||||
# git add .
|
||||
# git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
# 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 fetch origin
|
||||
# echo ${{ github.head_ref }}
|
||||
# git checkout ${{ github.head_ref }}
|
||||
# git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||
# git push
|
||||
# git push origin ${{ github.head_ref }}
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
git add .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
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 fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
# only upload artifacts if there's actually changes
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
|
2
.gitignore
vendored
@ -44,7 +44,7 @@ e2e/playwright/temp3.png
|
||||
e2e/playwright/export-snapshots/*
|
||||
!e2e/playwright/export-snapshots/*.png
|
||||
|
||||
/kcl-samples
|
||||
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
|
@ -4,16 +4,14 @@ excerpt: "Import a CAD file."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Import a CAD file.
|
||||
|
||||
**DEPRECATED** Prefer to use import statements.
|
||||
|
||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
|
||||
|
||||
Note: The import command currently only works when using the native Modeling App.
|
||||
|
||||
For importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).
|
||||
|
||||
```js
|
||||
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
||||
```
|
||||
|
@ -51,6 +51,7 @@ layout: manual
|
||||
* [`helixRevolutions`](kcl/helixRevolutions)
|
||||
* [`hole`](kcl/hole)
|
||||
* [`hollow`](kcl/hollow)
|
||||
* [`import`](kcl/import)
|
||||
* [`inch`](kcl/inch)
|
||||
* [`lastSegX`](kcl/lastSegX)
|
||||
* [`lastSegY`](kcl/lastSegY)
|
||||
|
@ -92765,7 +92765,7 @@
|
||||
{
|
||||
"name": "import",
|
||||
"summary": "Import a CAD file.",
|
||||
"description": "**DEPRECATED** Prefer to use import statements.\n\nFor formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.\n\nNote: The import command currently only works when using the native Modeling App.",
|
||||
"description": "For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.\n\nNote: The import command currently only works when using the native Modeling App.\n\nFor importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).",
|
||||
"tags": [],
|
||||
"keywordArguments": false,
|
||||
"args": [
|
||||
@ -93168,7 +93168,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
"unpublished": false,
|
||||
"deprecated": true,
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"model = import(\"tests/inputs/cube.obj\")",
|
||||
"model = import(\"tests/inputs/cube.obj\", { format = \"obj\", units = \"m\" })",
|
||||
|
@ -38,14 +38,14 @@ test.describe('Debug pane', () => {
|
||||
// Set the code in the code editor.
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(code, { delay: 0 })
|
||||
// Scroll to the artifact graph.
|
||||
// Scroll to the feature tree.
|
||||
await tree.scrollIntoViewIfNeeded()
|
||||
// Expand the artifact graph.
|
||||
await tree.getByText('Artifact Graph').click()
|
||||
// Expand the feature tree.
|
||||
await tree.getByText('Feature Tree').click()
|
||||
// Just expanded the details, making the element taller, so scroll again.
|
||||
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
|
||||
})
|
||||
// Extract the artifact IDs from the debug artifact graph.
|
||||
// Extract the artifact IDs from the debug feature tree.
|
||||
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
|
||||
// The artifact ID should include a UUID.
|
||||
expect(initialSegmentIds).toMatch(
|
||||
|
@ -4,6 +4,7 @@ import { expect } from '@playwright/test'
|
||||
type CmdBarSerialised =
|
||||
| {
|
||||
stage: 'commandBarClosed'
|
||||
// TODO no more properties needed but needs to be implemented in _serialiseCmdBar
|
||||
}
|
||||
| {
|
||||
stage: 'pickCommand'
|
||||
@ -36,9 +37,6 @@ export class CmdBarFixture {
|
||||
}
|
||||
|
||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
||||
return { stage: 'commandBarClosed' }
|
||||
}
|
||||
const reviewForm = this.page.locator('#review-form')
|
||||
const getHeaderArgs = async () => {
|
||||
const inputs = await this.page.getByTestId('cmd-bar-input-tab').all()
|
||||
|
@ -963,31 +963,37 @@ sketch002 = startSketchOn('XZ')
|
||||
await toolbar.sweepButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
currentArgKey: 'target',
|
||||
currentArgKey: 'profile',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Target: '',
|
||||
Trajectory: '',
|
||||
Path: '',
|
||||
Profile: '',
|
||||
},
|
||||
highlightedHeaderArg: 'target',
|
||||
highlightedHeaderArg: 'profile',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch1()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
currentArgKey: 'trajectory',
|
||||
currentArgKey: 'path',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Target: '1 face',
|
||||
Trajectory: '',
|
||||
Path: '',
|
||||
Profile: '1 face',
|
||||
},
|
||||
highlightedHeaderArg: 'trajectory',
|
||||
highlightedHeaderArg: 'path',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch2()
|
||||
await page.waitForTimeout(500)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
headerArguments: {
|
||||
Path: '1 face',
|
||||
Profile: '1 face',
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
@ -1014,75 +1020,6 @@ sketch002 = startSketchOn('XZ')
|
||||
})
|
||||
})
|
||||
|
||||
test(`Sweep point-and-click failing validation`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||
|> circle({
|
||||
center = [0, 0],
|
||||
radius = 500
|
||||
}, %)
|
||||
sketch002 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(-500, %)
|
||||
|> lineTo([-2000, 500], %)
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 700, y: 250 }
|
||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
|
||||
|
||||
await test.step(`Look for sketch001`, async () => {
|
||||
await toolbar.closePane('code')
|
||||
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
|
||||
await toolbar.sweepButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
currentArgKey: 'target',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Target: '',
|
||||
Trajectory: '',
|
||||
},
|
||||
highlightedHeaderArg: 'target',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch1()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
currentArgKey: 'trajectory',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Target: '1 face',
|
||||
Trajectory: '',
|
||||
},
|
||||
highlightedHeaderArg: 'trajectory',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch2()
|
||||
await page.waitForTimeout(500)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(
|
||||
page.getByText('Unable to sweep with the current selection. Reason:')
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test(`Fillet point-and-click`, async ({
|
||||
context,
|
||||
page,
|
||||
@ -1846,7 +1783,7 @@ sweep001 = sweep({ path = sketch002 }, sketch001)
|
||||
await page.waitForTimeout(500)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(
|
||||
page.getByText('Unable to shell with the current selection. Reason:')
|
||||
page.getByText('Unable to shell with the provided selection')
|
||||
).toBeVisible()
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
|
@ -1525,7 +1525,7 @@ extrude001 = extrude(200, sketch001)`)
|
||||
test(
|
||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page, cmdBar, homePage }, testInfo) => {
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
await Promise.all([
|
||||
fsp.mkdir(path.join(dir, 'router-template-slate'), { recursive: true }),
|
||||
@ -1563,38 +1563,11 @@ test(
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project via command palette should load the stream', async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: 'bracket',
|
||||
fileCount: 1,
|
||||
},
|
||||
{
|
||||
title: 'router-template-slate',
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.chooseCommand('open project')
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Open project',
|
||||
currentArgKey: 'name',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Name: '',
|
||||
},
|
||||
highlightedHeaderArg: 'name',
|
||||
})
|
||||
await cmdBar.argumentInput.fill('brac')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'commandBarClosed',
|
||||
})
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await u.waitForPageLoad()
|
||||
|
||||
@ -1615,7 +1588,7 @@ test(
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Opening the router-template project via link should load the stream', async () => {
|
||||
await test.step('Opening the router-template project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
|
||||
@ -1632,26 +1605,16 @@ test(
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await test.step('The projects on the home page should still be normal', async () => {
|
||||
await test.step('Opening the router-template project should load the stream', async () => {
|
||||
await page.getByTestId('project-sidebar-toggle').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Go to Home' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Go to Home' }).click()
|
||||
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: 'bracket',
|
||||
fileCount: 1,
|
||||
},
|
||||
{
|
||||
title: 'router-template-slate',
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ test.describe('Sketch tests', () => {
|
||||
screwRadius = 3
|
||||
wireRadius = 2
|
||||
wireOffset = 0.5
|
||||
|
||||
|
||||
screwHole = startSketchOn('XY')
|
||||
${startProfileAt1}
|
||||
|> arc({
|
||||
@ -42,7 +42,7 @@ test.describe('Sketch tests', () => {
|
||||
angleStart = 0,
|
||||
angleEnd = 360
|
||||
}, %)
|
||||
|
||||
|
||||
part001 = startSketchOn('XY')
|
||||
${startProfileAt2}
|
||||
|> xLine(width * .5, %)
|
||||
@ -51,7 +51,7 @@ test.describe('Sketch tests', () => {
|
||||
|> close(%)
|
||||
|> hole(screwHole, %)
|
||||
|> extrude(thickness, %)
|
||||
|
||||
|
||||
part002 = startSketchOn('-XZ')
|
||||
${startProfileAt3}
|
||||
|> xLine(width / 4, %)
|
||||
@ -99,7 +99,6 @@ test.describe('Sketch tests', () => {
|
||||
test('Can delete most of a sketch and the line tool will still work', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -113,13 +112,12 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
await expect(async () => {
|
||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeEnabled({ timeout: 2000 })
|
||||
).toBeEnabled({ timeout: 1000 })
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
|
||||
@ -1065,7 +1063,7 @@ test.describe('Sketch tests', () => {
|
||||
`lugHeadLength = 0.25
|
||||
lugDiameter = 0.5
|
||||
lugLength = 2
|
||||
|
||||
|
||||
fn lug = (origin, length, diameter, plane) => {
|
||||
lugSketch = startSketchOn(plane)
|
||||
|> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %)
|
||||
@ -1074,10 +1072,10 @@ test.describe('Sketch tests', () => {
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "Y" }, %)
|
||||
|
||||
|
||||
return lugSketch
|
||||
}
|
||||
|
||||
|
||||
lug([0, 0], 10, .5, "XY")`
|
||||
)
|
||||
})
|
||||
@ -1129,14 +1127,14 @@ test.describe('Sketch tests', () => {
|
||||
`fn in2mm = (inches) => {
|
||||
return inches * 25.4
|
||||
}
|
||||
|
||||
|
||||
const railTop = in2mm(.748)
|
||||
const railSide = in2mm(.024)
|
||||
const railBaseWidth = in2mm(.612)
|
||||
const railWideWidth = in2mm(.835)
|
||||
const railBaseLength = in2mm(.200)
|
||||
const railClampable = in2mm(.200)
|
||||
|
||||
|
||||
const rail = startSketchOn('XZ')
|
||||
|> startProfileAt([
|
||||
-railTop / 2,
|
||||
@ -1407,46 +1405,3 @@ test.describe(`Click based selection don't brick the app when clicked out of ran
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4372
|
||||
test.describe('Redirecting to home page and back to the original file should clear sketch DOM elements', () => {
|
||||
test('Can redirect to home page and back to original file and have a cleared DOM', async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
` sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([256.85, 14.41], %)
|
||||
|> lineTo([0, 211.07], %)
|
||||
`
|
||||
)
|
||||
})
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const [objClick] = scene.makeMouseHelpers(634, 274)
|
||||
await objClick()
|
||||
|
||||
// Enter sketch mode
|
||||
await toolbar.editSketch()
|
||||
|
||||
await expect(page.getByText('323.49')).toBeVisible()
|
||||
|
||||
// Open navigation side bar
|
||||
await page.getByTestId('project-sidebar-toggle').click()
|
||||
const goToHome = page.getByRole('button', {
|
||||
name: 'Go to Home',
|
||||
})
|
||||
|
||||
await goToHome.click()
|
||||
await homePage.openProject('testDefault')
|
||||
await expect(page.getByText('323.49')).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 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: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -69,6 +69,7 @@ test.describe('Testing in-app sample loading', () => {
|
||||
await confirmButton.click()
|
||||
|
||||
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 expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
await expect(unitsToast('in')).toBeVisible()
|
||||
})
|
||||
|
||||
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(sampleTwo.file)).not.toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
await expect(unitsToast('mm')).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -113,9 +113,9 @@
|
||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||
"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:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet",
|
||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@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 tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||
@ -201,7 +201,7 @@
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.19.1",
|
||||
"vite": "^5.4.12",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
|
@ -683,9 +683,9 @@ vite-tsconfig-paths@^4.3.2:
|
||||
tsconfck "^3.0.3"
|
||||
|
||||
vite@^5.0.0:
|
||||
version "5.4.14"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
|
||||
integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
|
||||
version "5.4.11"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"
|
||||
integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==
|
||||
dependencies:
|
||||
esbuild "^0.21.3"
|
||||
postcss "^8.4.43"
|
||||
|
10
src/App.tsx
@ -23,19 +23,19 @@ import { CoreDumpManager } from 'lib/coredump'
|
||||
import { UnitsMenu } from 'components/UnitsMenu'
|
||||
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
||||
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
maybeWriteToDisk()
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
|
||||
export function App() {
|
||||
const { project, file } = useLoaderData() as IndexLoaderData
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
|
||||
useCreateFileLinkQuery((argDefaultValues) => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'projects',
|
||||
@ -61,8 +61,8 @@ export function App() {
|
||||
|
||||
useHotKeyListener()
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const token = useToken()
|
||||
const { auth, settings } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useAuthState } from 'machines/appMachine'
|
||||
import Loading from './components/Loading'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
|
||||
// Wrapper around protected routes, used in src/Router.tsx
|
||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||
const authState = useAuthState()
|
||||
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||
|
||||
return isLoggingIn ? (
|
||||
<Loading>
|
||||
|
@ -31,12 +31,14 @@ import {
|
||||
settingsLoader,
|
||||
telemetryLoader,
|
||||
} from 'lib/routeLoaders'
|
||||
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
|
||||
import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
||||
import LspProvider from 'components/LspProvider'
|
||||
import { KclContextProvider } from 'lang/KclProvider'
|
||||
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import toast from 'react-hot-toast'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
@ -46,7 +48,6 @@ import { reportRejection } from 'lib/trap'
|
||||
import { RouteProvider } from 'components/RouteProvider'
|
||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
||||
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||
|
||||
@ -59,21 +60,23 @@ const router = createRouter([
|
||||
* inefficient re-renders, use the react profiler to see. */
|
||||
element: (
|
||||
<OpenInDesktopAppHandler>
|
||||
<RouteProvider>
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<ProjectsContextProvider>
|
||||
<KclContextProvider>
|
||||
<AppStateProvider>
|
||||
<MachineManagerProvider>
|
||||
<Outlet />
|
||||
</MachineManagerProvider>
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
</RouteProvider>
|
||||
<CommandBarProvider>
|
||||
<RouteProvider>
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<ProjectsContextProvider>
|
||||
<KclContextProvider>
|
||||
<AppStateProvider>
|
||||
<MachineManagerProvider>
|
||||
<Outlet />
|
||||
</MachineManagerProvider>
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
</RouteProvider>
|
||||
</CommandBarProvider>
|
||||
</OpenInDesktopAppHandler>
|
||||
),
|
||||
errorElement: <ErrorPage />,
|
||||
@ -203,7 +206,8 @@ export const Router = () => {
|
||||
}
|
||||
|
||||
function CoreDump() {
|
||||
const token = useToken()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
[]
|
||||
|
@ -2,6 +2,7 @@ import { useRef, useMemo, memo, useCallback, useState } from 'react'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
@ -21,13 +22,13 @@ import {
|
||||
} from 'lib/toolbar'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLElement>) {
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const iconClassName =
|
||||
'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
|
||||
const bgClassName = '!bg-transparent'
|
||||
@ -70,9 +71,10 @@ export function Toolbar({
|
||||
() => ({
|
||||
modelingState: state,
|
||||
modelingSend: send,
|
||||
commandBarSend,
|
||||
sketchPathId,
|
||||
}),
|
||||
[state, send, commandBarActor.send, sketchPathId]
|
||||
[state, send, commandBarSend, sketchPathId]
|
||||
)
|
||||
|
||||
const tooltipContentClassName = !showRichContent
|
||||
|
@ -46,8 +46,8 @@ import {
|
||||
} from 'lang/modifyAst'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||
const [isCamMoving, setIsCamMoving] = useState(false)
|
||||
@ -124,14 +124,6 @@ export const ClientSideScene = ({
|
||||
'mouseup',
|
||||
toSync(sceneInfra.onMouseUp, reportRejection)
|
||||
)
|
||||
sceneEntitiesManager
|
||||
.tearDownSketch()
|
||||
.then(() => {
|
||||
// no op
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -518,6 +510,7 @@ const ConstraintSymbol = ({
|
||||
constrainInfo: ConstrainInfo
|
||||
verticalPosition: 'top' | 'bottom'
|
||||
}) => {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { context } = useModelingContext()
|
||||
const varNameMap: {
|
||||
[key in ConstrainInfo['type']]: {
|
||||
@ -637,7 +630,7 @@ const ConstraintSymbol = ({
|
||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||
onClick={toSync(async () => {
|
||||
if (!isConstrained) {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
name: 'Constrain with named value',
|
||||
@ -763,6 +756,7 @@ export const CamDebugSettings = () => {
|
||||
sceneInfra.camControls.reactCameraProperties
|
||||
)
|
||||
const [fov, setFov] = useState(12)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
|
||||
@ -781,7 +775,7 @@ export const CamDebugSettings = () => {
|
||||
type="checkbox"
|
||||
checked={camSettings.type === 'perspective'}
|
||||
onChange={() =>
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'settings',
|
||||
|
@ -69,8 +69,7 @@ import {
|
||||
codeManager,
|
||||
editorManager,
|
||||
} from 'lib/singletons'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst, ToolTip } from 'lang/langHelpers'
|
||||
import {
|
||||
createProfileStartHandle,
|
||||
|
@ -61,7 +61,6 @@ import { SegmentInputs } from 'lang/std/stdTypes'
|
||||
import { err } from 'lib/trap'
|
||||
import { editorManager, sceneInfra } from 'lib/singletons'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
interface CreateSegmentArgs {
|
||||
input: SegmentInputs
|
||||
@ -848,7 +847,7 @@ function createLengthIndicator({
|
||||
})
|
||||
|
||||
// Command Bar
|
||||
commandBarActor.send({
|
||||
editorManager.commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
name: 'Constrain length',
|
||||
|
@ -2,11 +2,11 @@ import { Toolbar } from '../Toolbar'
|
||||
import UserSidebarMenu from 'components/UserSidebarMenu'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import styles from './AppHeader.module.css'
|
||||
import { RefreshButton } from 'components/RefreshButton'
|
||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { useUser } from 'machines/appMachine'
|
||||
|
||||
interface AppHeaderProps extends React.PropsWithChildren {
|
||||
showToolbar?: boolean
|
||||
@ -24,7 +24,8 @@ export const AppHeader = ({
|
||||
style,
|
||||
enableMenu = false,
|
||||
}: AppHeaderProps) => {
|
||||
const user = useUser()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const user = auth?.context?.user
|
||||
|
||||
return (
|
||||
<header
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { trap } from 'lib/trap'
|
||||
import { codeToIdSelections } from 'lib/selections'
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Combobox } from '@headlessui/react'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import Fuse from 'fuse.js'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||
|
||||
@ -23,7 +23,7 @@ function CommandArgOptionInput({
|
||||
placeholder?: string
|
||||
}) {
|
||||
const actorContext = useSelector(arg.machineActor, contextSelector)
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||
const resolvedOptions = useMemo(
|
||||
() =>
|
||||
typeof arg.options === 'function'
|
||||
@ -143,7 +143,7 @@ function CommandArgOptionInput({
|
||||
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
if (event.metaKey && event.key === 'k')
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||||
stepBack()
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Dialog, Popover, Transition } from '@headlessui/react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import CommandBarArgument from './CommandBarArgument'
|
||||
import CommandComboBox from '../CommandComboBox'
|
||||
import CommandBarReview from './CommandBarReview'
|
||||
@ -7,13 +8,12 @@ import { useLocation } from 'react-router-dom'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
|
||||
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
||||
|
||||
export const CommandBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const {
|
||||
context: { selectedCommand, currentArgument, commands },
|
||||
} = commandBarState
|
||||
@ -23,16 +23,16 @@ export const CommandBar = () => {
|
||||
// Close the command bar when navigating
|
||||
useEffect(() => {
|
||||
if (commandBarState.matches('Closed')) return
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
}, [pathname])
|
||||
|
||||
// Hook up keyboard shortcuts
|
||||
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
||||
if (commandBarState.context.commands.length === 0) return
|
||||
if (commandBarState.matches('Closed')) {
|
||||
commandBarActor.send({ type: 'Open' })
|
||||
commandBarSend({ type: 'Open' })
|
||||
} else {
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
}
|
||||
})
|
||||
|
||||
@ -52,14 +52,14 @@ export const CommandBar = () => {
|
||||
...entries[entries.length - 1][1],
|
||||
}
|
||||
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Edit argument',
|
||||
data: {
|
||||
arg: currentArg,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
commandBarActor.send({ type: 'Deselect command' })
|
||||
commandBarSend({ type: 'Deselect command' })
|
||||
}
|
||||
} else {
|
||||
const entries = Object.entries(selectedCommand?.args || {})
|
||||
@ -68,9 +68,9 @@ export const CommandBar = () => {
|
||||
)
|
||||
|
||||
if (index === 0) {
|
||||
commandBarActor.send({ type: 'Deselect command' })
|
||||
commandBarSend({ type: 'Deselect command' })
|
||||
} else {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Change current argument',
|
||||
data: {
|
||||
arg: { name: entries[index - 1][0], ...entries[index - 1][1] },
|
||||
@ -85,20 +85,19 @@ export const CommandBar = () => {
|
||||
show={!commandBarState.matches('Closed') || false}
|
||||
afterLeave={() => {
|
||||
if (selectedCommand?.onCancel) selectedCommand.onCancel()
|
||||
commandBarActor.send({ type: 'Clear' })
|
||||
commandBarSend({ type: 'Clear' })
|
||||
}}
|
||||
as={Fragment}
|
||||
>
|
||||
<WrapperComponent
|
||||
open={!commandBarState.matches('Closed') || isSelectionArgument}
|
||||
onClose={() => {
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
}}
|
||||
className={
|
||||
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
|
||||
(isSelectionArgument ? 'pointer-events-none' : '')
|
||||
}
|
||||
data-testid="command-bar-wrapper"
|
||||
>
|
||||
<Transition.Child
|
||||
enter="duration-100 ease-out"
|
||||
@ -123,7 +122,7 @@ export const CommandBar = () => {
|
||||
)
|
||||
)}
|
||||
<button
|
||||
onClick={() => commandBarActor.send({ type: 'Close' })}
|
||||
onClick={() => commandBarSend({ type: 'Close' })}
|
||||
className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent"
|
||||
>
|
||||
<CustomIcon
|
||||
|
@ -2,13 +2,13 @@ import CommandArgOptionInput from './CommandArgOptionInput'
|
||||
import CommandBarBasicInput from './CommandBarBasicInput'
|
||||
import CommandBarSelectionInput from './CommandBarSelectionInput'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import CommandBarHeader from './CommandBarHeader'
|
||||
import CommandBarKclInput from './CommandBarKclInput'
|
||||
import CommandBarTextareaInput from './CommandBarTextareaInput'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
|
||||
function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const {
|
||||
context: { currentArgument },
|
||||
} = commandBarState
|
||||
@ -16,7 +16,7 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
||||
function onSubmit(data: unknown) {
|
||||
if (!currentArgument) return
|
||||
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Submit argument',
|
||||
data: {
|
||||
[currentArgument.name]: data,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
@ -15,8 +15,8 @@ function CommandBarBasicInput({
|
||||
stepBack: () => void
|
||||
onSubmit: (event: unknown) => void
|
||||
}) {
|
||||
const commandBarState = useCommandBarState()
|
||||
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
|
||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from '../CustomIcon'
|
||||
import React, { useState } from 'react'
|
||||
import { ActionButton } from '../ActionButton'
|
||||
@ -6,10 +7,9 @@ import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
|
||||
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const {
|
||||
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
||||
} = commandBarState
|
||||
@ -49,7 +49,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
]
|
||||
const arg = selectedCommand?.args[argName]
|
||||
if (!argName || !arg) return
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Change current argument',
|
||||
data: { arg: { ...arg, name: argName } },
|
||||
})
|
||||
@ -100,7 +100,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
}
|
||||
disabled={!isReviewing && currentArgument?.name === argName}
|
||||
onClick={() => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: isReviewing
|
||||
? 'Edit argument'
|
||||
: 'Change current argument',
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
} from '@codemirror/autocomplete'
|
||||
import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
|
||||
import { getSystemTheme } from 'lib/theme'
|
||||
@ -19,7 +20,6 @@ import styles from './CommandBarKclInput.module.css'
|
||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
|
||||
const machineContextSelector = (snapshot?: {
|
||||
context: Record<string, unknown>
|
||||
@ -37,7 +37,7 @@ function CommandBarKclInput({
|
||||
stepBack: () => void
|
||||
onSubmit: (event: unknown) => void
|
||||
}) {
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||
const previouslySetValue = commandBarState.context.argumentsToSubmit[
|
||||
arg.name
|
||||
] as KclCommandValue | undefined
|
||||
@ -82,7 +82,7 @@ function CommandBarKclInput({
|
||||
false
|
||||
)
|
||||
const [canSubmit, setCanSubmit] = useState(true)
|
||||
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
const editorRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
|
43
src/components/CommandBar/CommandBarProvider.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { createActorContext } from '@xstate/react'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export const CommandsContext = createActorContext(
|
||||
commandBarMachine.provide({
|
||||
guards: {
|
||||
'Command has no arguments': ({ context }) => {
|
||||
return (
|
||||
!context.selectedCommand?.args ||
|
||||
Object.keys(context.selectedCommand?.args).length === 0
|
||||
)
|
||||
},
|
||||
'All arguments are skippable': ({ context }) => {
|
||||
return Object.values(context.selectedCommand!.args!).every(
|
||||
(argConfig) => argConfig.skip
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export const CommandBarProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<CommandsContext.Provider>
|
||||
<CommandBarProviderInner>{children}</CommandBarProviderInner>
|
||||
</CommandsContext.Provider>
|
||||
)
|
||||
}
|
||||
function CommandBarProviderInner({ children }: { children: React.ReactNode }) {
|
||||
const commandBarActor = CommandsContext.useActorRef()
|
||||
|
||||
useEffect(() => {
|
||||
editorManager.setCommandBarSend(commandBarActor.send)
|
||||
})
|
||||
|
||||
return children
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import CommandBarHeader from './CommandBarHeader'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const {
|
||||
context: { argumentsToSubmit, selectedCommand },
|
||||
} = commandBarState
|
||||
@ -33,7 +33,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
parseInt(b.keys[0], 10) - 1
|
||||
]
|
||||
const arg = selectedCommand?.args[argName]
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Edit argument',
|
||||
data: { arg: { ...arg, name: argName } },
|
||||
})
|
||||
@ -50,7 +50,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
|
||||
function submitCommand(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Submit command',
|
||||
output: argumentsToSubmit,
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Artifact } from 'lang/std/artifactGraph'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import {
|
||||
@ -9,7 +10,6 @@ import {
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { StateFrom } from 'xstate'
|
||||
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
|
||||
onSubmit: (data: unknown) => void
|
||||
}) {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
||||
const selectionsByType = useMemo(() => {
|
||||
@ -145,7 +145,7 @@ function CommandBarSelectionInput({
|
||||
if (event.key === 'Backspace') {
|
||||
stepBack()
|
||||
} else if (event.key === 'Escape') {
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
}
|
||||
}}
|
||||
onChange={handleChange}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import { RefObject, useEffect, useRef } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
@ -15,8 +15,8 @@ function CommandBarTextareaInput({
|
||||
stepBack: () => void
|
||||
onSubmit: (event: unknown) => void
|
||||
}) {
|
||||
const commandBarState = useCommandBarState()
|
||||
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
|
||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||
useTextareaAutoGrow(inputRef)
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
export function CommandBarOpenButton() {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const platform = usePlatform()
|
||||
|
||||
return (
|
||||
<button
|
||||
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
|
||||
onClick={() => commandBarActor.send({ type: 'Open' })}
|
||||
onClick={() => commandBarSend({ type: 'Open' })}
|
||||
data-testid="command-bar-open-button"
|
||||
>
|
||||
<span>Commands</span>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Combobox } from '@headlessui/react'
|
||||
import Fuse from 'fuse.js'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { getActorNextEvents } from 'lib/utils'
|
||||
import { sortCommands } from 'lib/commandUtils'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
function CommandComboBox({
|
||||
options,
|
||||
@ -14,6 +14,7 @@ function CommandComboBox({
|
||||
options: Command[]
|
||||
placeholder?: string
|
||||
}) {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const [query, setQuery] = useState('')
|
||||
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
|
||||
|
||||
@ -40,7 +41,7 @@ function CommandComboBox({
|
||||
}, [query])
|
||||
|
||||
function handleSelection(command: Command) {
|
||||
commandBarActor.send({ type: 'Select command', data: { command } })
|
||||
commandBarSend({ type: 'Select command', data: { command } })
|
||||
}
|
||||
|
||||
return (
|
||||
@ -60,7 +61,7 @@ function CommandComboBox({
|
||||
(event.key === 'Backspace' && !event.currentTarget.value)
|
||||
) {
|
||||
event.preventDefault()
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
@ -75,40 +76,34 @@ function CommandComboBox({
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
{filteredOptions?.length ? (
|
||||
<Combobox.Options
|
||||
static
|
||||
className="overflow-y-auto max-h-96 cursor-pointer"
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.groupId + option.name + (option.displayName || '')}
|
||||
value={option}
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
||||
disabled={optionIsDisabled(option)}
|
||||
data-testid={`cmd-bar-option`}
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
)}
|
||||
<div className="flex-grow flex flex-col">
|
||||
<p className="my-0 leading-tight">
|
||||
{option.displayName || option.name}{' '}
|
||||
<Combobox.Options
|
||||
static
|
||||
className="overflow-y-auto max-h-96 cursor-pointer"
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.groupId + option.name + (option.displayName || '')}
|
||||
value={option}
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
||||
disabled={optionIsDisabled(option)}
|
||||
data-testid={`cmd-bar-option`}
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
)}
|
||||
<div className="flex-grow flex flex-col">
|
||||
<p className="my-0 leading-tight">
|
||||
{option.displayName || option.name}{' '}
|
||||
</p>
|
||||
{option.description && (
|
||||
<p className="my-0 text-xs text-chalkboard-60 dark:text-chalkboard-50">
|
||||
{option.description}
|
||||
</p>
|
||||
{option.description && (
|
||||
<p className="my-0 text-xs text-chalkboard-60 dark:text-chalkboard-50">
|
||||
{option.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
) : (
|
||||
<p className="px-4 pt-2 text-chalkboard-60 dark:text-chalkboard-50">
|
||||
No results found
|
||||
</p>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
)
|
||||
}
|
||||
|
@ -4,18 +4,18 @@ import { expandPlane, PlaneArtifactRich } from 'lang/std/artifactGraph'
|
||||
import { ArtifactGraph } from 'lang/wasm'
|
||||
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
|
||||
|
||||
export function DebugArtifactGraph() {
|
||||
const artifactGraphTree = useMemo(() => {
|
||||
export function DebugFeatureTree() {
|
||||
const featureTree = useMemo(() => {
|
||||
return computeTree(engineCommandManager.artifactGraph)
|
||||
}, [engineCommandManager.artifactGraph])
|
||||
|
||||
const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
|
||||
return (
|
||||
<details data-testid="debug-feature-tree" className="relative">
|
||||
<summary>Artifact Graph</summary>
|
||||
{artifactGraphTree.length > 0 ? (
|
||||
<summary>Feature Tree</summary>
|
||||
{featureTree.length > 0 ? (
|
||||
<pre className="text-xs">
|
||||
<DebugDisplayArray arr={artifactGraphTree} filterKeys={filterKeys} />
|
||||
<DebugDisplayArray arr={featureTree} filterKeys={filterKeys} />
|
||||
</pre>
|
||||
) : (
|
||||
<p>(Empty)</p>
|
@ -12,6 +12,7 @@ import {
|
||||
StateFrom,
|
||||
fromPromise,
|
||||
} from 'xstate'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { fileMachine } from 'machines/fileMachine'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import {
|
||||
@ -29,8 +30,6 @@ import {
|
||||
} from 'lib/getKclSamplesManifest'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -48,8 +47,8 @@ export const FileMachineProvider = ({
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const token = useToken()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { settings, auth } = useSettingsAuthContext()
|
||||
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||
const { project, file } = projectData
|
||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||
@ -92,7 +91,7 @@ export const FileMachineProvider = ({
|
||||
navigateToFile: ({ context, event }) => {
|
||||
if (event.type !== 'xstate.done.actor.create-and-open-file') return
|
||||
if (event.output && 'name' in event.output) {
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
navigate(
|
||||
`..${PATHS.FILE}/${encodeURIComponent(
|
||||
context.selectedDirectory +
|
||||
@ -299,7 +298,7 @@ export const FileMachineProvider = ({
|
||||
const kclCommandMemo = useMemo(
|
||||
() =>
|
||||
kclCommands({
|
||||
authToken: token ?? '',
|
||||
authToken: auth?.context?.token ?? '',
|
||||
projectData,
|
||||
settings: {
|
||||
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
||||
@ -345,18 +344,15 @@ export const FileMachineProvider = ({
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
commandBarActor.send({
|
||||
type: 'Add commands',
|
||||
data: { commands: kclCommandMemo },
|
||||
})
|
||||
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } })
|
||||
|
||||
return () => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: { commands: kclCommandMemo },
|
||||
})
|
||||
}
|
||||
}, [commandBarActor.send, kclCommandMemo])
|
||||
}, [commandBarSend, kclCommandMemo])
|
||||
|
||||
return (
|
||||
<FileContext.Provider
|
||||
|
@ -27,7 +27,6 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||
import { err } from 'lib/trap'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||
return []
|
||||
@ -70,7 +69,8 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||
|
||||
const token = useToken()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context.token
|
||||
const navigate = useNavigate()
|
||||
|
||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { createContext, useEffect, useState } from 'react'
|
||||
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
export type MachinesListing = Array<
|
||||
components['schemas']['MachineInfoResponse']
|
||||
@ -42,6 +42,8 @@ export const MachineManagerProvider = ({
|
||||
components['schemas']['MachineInfoResponse'] | null
|
||||
>(null)
|
||||
|
||||
const commandBarActor = CommandsContext.useActorRef()
|
||||
|
||||
// Get the reason message for why there are no machines.
|
||||
const noMachinesReason = (): string | undefined => {
|
||||
if (machines.length > 0) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useMachine, useSelector } from '@xstate/react'
|
||||
import { useMachine } from '@xstate/react'
|
||||
import React, {
|
||||
createContext,
|
||||
useEffect,
|
||||
@ -11,7 +11,6 @@ import {
|
||||
AnyStateMachine,
|
||||
ContextFrom,
|
||||
Prop,
|
||||
SnapshotFrom,
|
||||
StateFrom,
|
||||
assign,
|
||||
fromPromise,
|
||||
@ -68,14 +67,18 @@ import {
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import { artifactIsPlaneWithPaths, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import {
|
||||
artifactIsPlaneWithPaths,
|
||||
getNodePathFromSourceRange,
|
||||
isSingleCursorInPipe,
|
||||
} from 'lang/queryAst'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import {
|
||||
ExportIntent,
|
||||
EngineConnectionStateType,
|
||||
@ -88,8 +91,6 @@ import { IndexLoaderData } from 'lib/types'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -101,16 +102,13 @@ export const ModelingMachineContext = createContext(
|
||||
{} as MachineContext<typeof modelingMachine>
|
||||
)
|
||||
|
||||
const commandBarIsClosedSelector = (
|
||||
state: SnapshotFrom<typeof commandBarActor>
|
||||
) => state.matches('Closed')
|
||||
|
||||
export const ModelingMachineProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const {
|
||||
auth,
|
||||
settings: {
|
||||
context: {
|
||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||
@ -127,17 +125,15 @@ export const ModelingMachineProvider = ({
|
||||
const navigate = useNavigate()
|
||||
const { context, send: fileMachineSend } = useFileContext()
|
||||
const { file } = useLoaderData() as IndexLoaderData
|
||||
const token = useToken()
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||
|
||||
let [searchParams] = useSearchParams()
|
||||
const pool = searchParams.get('pool')
|
||||
|
||||
const isCommandBarClosed = useSelector(
|
||||
commandBarActor,
|
||||
commandBarIsClosedSelector
|
||||
)
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
|
||||
// Settings machine setup
|
||||
// const retrievedSettings = useRef(
|
||||
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
||||
@ -392,16 +388,7 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'completeSelection') {
|
||||
const codeMirrorSelection = editorManager.createEditorSelection(
|
||||
setSelections.selection
|
||||
)
|
||||
kclEditorActor.send({
|
||||
type: 'setLastSelectionEvent',
|
||||
data: {
|
||||
codeMirrorSelection,
|
||||
scrollIntoView: false,
|
||||
},
|
||||
})
|
||||
editorManager.selectRange(setSelections.selection)
|
||||
if (!sketchDetails)
|
||||
return {
|
||||
selectionRanges: setSelections.selection,
|
||||
@ -542,6 +529,7 @@ export const ModelingMachineProvider = ({
|
||||
trimmedPrompt,
|
||||
fileMachineSend,
|
||||
navigate,
|
||||
commandBarSend,
|
||||
context,
|
||||
token,
|
||||
settings: {
|
||||
@ -555,7 +543,7 @@ export const ModelingMachineProvider = ({
|
||||
'has valid selection for deletion': ({
|
||||
context: { selectionRanges },
|
||||
}) => {
|
||||
if (!isCommandBarClosed) return false
|
||||
if (!commandBarState.matches('Closed')) return false
|
||||
if (selectionRanges.graphSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DebugArtifactGraph } from 'components/DebugArtifactGraph'
|
||||
import { DebugFeatureTree } from 'components/DebugFeatureTree'
|
||||
import { AstExplorer } from '../../AstExplorer'
|
||||
import { EngineCommands } from '../../EngineCommands'
|
||||
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
|
||||
@ -14,7 +14,7 @@ export const DebugPane = () => {
|
||||
<EngineCommands />
|
||||
<CamDebugSettings />
|
||||
<AstExplorer />
|
||||
<DebugArtifactGraph />
|
||||
<DebugFeatureTree />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -3,7 +3,6 @@
|
||||
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
|
||||
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
||||
@apply transition-colors ease-out;
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
:global(.dark) .button {
|
||||
|
@ -9,11 +9,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
|
||||
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||
useConvertToVariable()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
@ -84,7 +85,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'code',
|
||||
|
@ -15,12 +15,12 @@ import { ModelingPane } from './ModelingPane'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { CustomIconName } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
interface ModelingSidebarProps {
|
||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||
@ -37,6 +37,7 @@ function getPlatformString(): 'web' | 'desktop' {
|
||||
|
||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const kclContext = useKclContext()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const onboardingStatus = settings.context.app.onboardingStatus
|
||||
@ -65,7 +66,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
icon: 'floppyDiskArrow',
|
||||
keybinding: 'Ctrl + Shift + E',
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Export', groupId: 'modeling' },
|
||||
}),
|
||||
@ -78,7 +79,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
keybinding: 'Ctrl + Shift + M',
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
action: async () => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Make', groupId: 'modeling' },
|
||||
})
|
||||
@ -297,7 +298,7 @@ function ModelingPaneButton({
|
||||
})
|
||||
|
||||
return (
|
||||
<div id={paneConfig.id + '-button-holder'} className="relative">
|
||||
<div id={paneConfig.id + '-button-holder'}>
|
||||
<button
|
||||
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||
onClick={onClick}
|
||||
@ -339,7 +340,7 @@ function ModelingPaneButton({
|
||||
<p
|
||||
id={`${paneConfig.id}-badge`}
|
||||
className={
|
||||
'absolute m-0 p-0 bottom-4 left-4 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
||||
'absolute m-0 p-0 top-1 right-0 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
||||
}
|
||||
onClick={showBadge.onClick}
|
||||
title={`Click to view ${showBadge.value} notification${
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
||||
import {
|
||||
NETWORK_HEALTH_TEXT,
|
||||
NetworkHealthIndicator,
|
||||
@ -11,7 +12,9 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
||||
// wrap in router and xState context
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
||||
</CommandBarProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
||||
import { Project } from 'lib/project'
|
||||
|
||||
const now = new Date()
|
||||
@ -32,9 +33,11 @@ describe('ProjectSidebarMenu tests', () => {
|
||||
test('Disables popover menu by default', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<SettingsAuthProviderJest>
|
||||
<ProjectSidebarMenu project={projectWellFormed} />
|
||||
</SettingsAuthProviderJest>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthProviderJest>
|
||||
<ProjectSidebarMenu project={projectWellFormed} />
|
||||
</SettingsAuthProviderJest>
|
||||
</CommandBarProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Fragment, useMemo, useContext } from 'react'
|
||||
import { Logo } from './Logo'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { codeManager, engineCommandManager, kclManager } from 'lib/singletons'
|
||||
@ -14,13 +15,8 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import Tooltip from './Tooltip'
|
||||
import { SnapshotFrom } from 'xstate'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { copyFileShareLink } from 'lib/links'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { DEV } from 'env'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
const ProjectSidebarMenu = ({
|
||||
project,
|
||||
@ -90,9 +86,6 @@ function AppLogoLink({
|
||||
)
|
||||
}
|
||||
|
||||
const commandsSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
|
||||
state.context.commands
|
||||
|
||||
function ProjectMenuPopover({
|
||||
project,
|
||||
file,
|
||||
@ -104,17 +97,18 @@ function ProjectMenuPopover({
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const token = useToken()
|
||||
const { settings, auth } = useSettingsAuthContext()
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const commands = useSelector(commandBarActor, commandsSelector)
|
||||
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||
const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
|
||||
const findCommand = (obj: { name: string; groupId: string }) =>
|
||||
Boolean(
|
||||
commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
|
||||
commandBarState.context.commands.find(
|
||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||
)
|
||||
)
|
||||
const machineCount = machineManager.machines.length
|
||||
|
||||
@ -159,7 +153,7 @@ function ProjectMenuPopover({
|
||||
),
|
||||
disabled: !findCommand(exportCommandInfo),
|
||||
onClick: () =>
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: exportCommandInfo,
|
||||
}),
|
||||
@ -183,7 +177,7 @@ function ProjectMenuPopover({
|
||||
),
|
||||
disabled: !findCommand(makeCommandInfo) || machineCount === 0,
|
||||
onClick: () => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: makeCommandInfo,
|
||||
})
|
||||
@ -193,10 +187,9 @@ function ProjectMenuPopover({
|
||||
id: 'share-link',
|
||||
Element: 'button',
|
||||
children: 'Share link to file',
|
||||
disabled: !DEV,
|
||||
onClick: async () => {
|
||||
await copyFileShareLink({
|
||||
token: token ?? '',
|
||||
token: auth?.context.token || '',
|
||||
code: codeManager.code,
|
||||
name: project?.name || '',
|
||||
units: settings.context.modeling.defaultUnit.current,
|
||||
@ -222,7 +215,7 @@ function ProjectMenuPopover({
|
||||
[
|
||||
platform,
|
||||
findCommand,
|
||||
commandBarActor.send,
|
||||
commandBarSend,
|
||||
engineCommandManager,
|
||||
onProjectClose,
|
||||
isDesktop,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useMachine } from '@xstate/react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||
import { useProjectsLoader } from 'hooks/useProjectsLoader'
|
||||
import { projectsMachine } from 'machines/projectsMachine'
|
||||
@ -24,7 +25,6 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import {
|
||||
CREATE_FILE_URL_PARAM,
|
||||
FILE_EXT,
|
||||
@ -197,6 +197,7 @@ const ProjectsContextDesktop = ({
|
||||
searchParams.delete('units')
|
||||
setSearchParams(searchParams)
|
||||
}, [searchParams, setSearchParams])
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { onProjectOpen } = useLspContext()
|
||||
const {
|
||||
settings: { context: settings },
|
||||
@ -242,7 +243,7 @@ const ProjectsContextDesktop = ({
|
||||
},
|
||||
null
|
||||
)
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
commandBarSend({ type: 'Close' })
|
||||
const newPathName = `${PATHS.FILE}/${encodeURIComponent(
|
||||
projectPath
|
||||
)}`
|
||||
|
@ -8,10 +8,10 @@ import Tooltip from './Tooltip'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||
const token = useToken()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
[]
|
||||
|
@ -2,16 +2,13 @@ import { useEffect, useState, createContext, ReactNode } from 'react'
|
||||
import { useNavigation, useLocation } from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { useAuthNavigation } from 'hooks/useAuthNavigation'
|
||||
|
||||
export const RouteProviderContext = createContext({})
|
||||
|
||||
export function RouteProvider({ children }: { children: ReactNode }) {
|
||||
useAuthNavigation()
|
||||
const [first, setFirstState] = useState(true)
|
||||
const navigation = useNavigation()
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
// On initialization, the react-router-dom does not send a 'loading' state event.
|
||||
// it sends an idle event first.
|
||||
|
@ -2,7 +2,10 @@ import { trap } from 'lib/trap'
|
||||
import { useMachine, useSelector } from '@xstate/react'
|
||||
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
||||
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 useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import {
|
||||
@ -13,6 +16,7 @@ import {
|
||||
} from 'lib/theme'
|
||||
import decamelize from 'decamelize'
|
||||
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||
import {
|
||||
kclManager,
|
||||
sceneInfra,
|
||||
@ -25,6 +29,7 @@ import {
|
||||
createSettingsCommand,
|
||||
settingsWithCommandConfigs,
|
||||
} from 'lib/commandBarConfigs/settingsCommandConfig'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
import { BaseUnit } from 'lib/settings/settingsTypes'
|
||||
import {
|
||||
@ -37,7 +42,6 @@ import { isDesktop } from 'lib/isDesktop'
|
||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -46,6 +50,7 @@ type MachineContext<T extends AnyStateMachine> = {
|
||||
}
|
||||
|
||||
type SettingsAuthContextType = {
|
||||
auth: MachineContext<typeof authMachine>
|
||||
settings: MachineContext<typeof settingsMachine>
|
||||
}
|
||||
|
||||
@ -104,6 +109,7 @@ export const SettingsAuthProviderBase = ({
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const [settingsPath, setSettingsPath] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
@ -272,10 +278,10 @@ export const SettingsAuthProviderBase = ({
|
||||
)
|
||||
.filter((c) => c !== null) as Command[]
|
||||
|
||||
commandBarActor.send({ type: 'Add commands', data: { commands: commands } })
|
||||
commandBarSend({ type: 'Add commands', data: { commands: commands } })
|
||||
|
||||
return () => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: { commands },
|
||||
})
|
||||
@ -284,7 +290,7 @@ export const SettingsAuthProviderBase = ({
|
||||
settingsState,
|
||||
settingsSend,
|
||||
settingsActor,
|
||||
commandBarActor.send,
|
||||
commandBarSend,
|
||||
settingsWithCommandConfigs,
|
||||
])
|
||||
|
||||
@ -297,7 +303,7 @@ export const SettingsAuthProviderBase = ({
|
||||
encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
|
||||
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
|
||||
createRouteCommands(navigate, location, filePath)
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: {
|
||||
commands: [
|
||||
@ -308,12 +314,12 @@ export const SettingsAuthProviderBase = ({
|
||||
},
|
||||
})
|
||||
if (location.pathname === PATHS.HOME) {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Add commands',
|
||||
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
|
||||
})
|
||||
} else if (location.pathname.includes(PATHS.FILE)) {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Add commands',
|
||||
data: {
|
||||
commands: [
|
||||
@ -365,9 +371,40 @@ export const SettingsAuthProviderBase = ({
|
||||
)
|
||||
}, [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 (
|
||||
<SettingsAuthContext.Provider
|
||||
value={{
|
||||
auth: {
|
||||
state: authState,
|
||||
context: authState.context,
|
||||
send: authSend,
|
||||
},
|
||||
settings: {
|
||||
state: settingsState,
|
||||
context: settingsState.context,
|
||||
@ -381,3 +418,12 @@ export const SettingsAuthProviderBase = ({
|
||||
}
|
||||
|
||||
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',
|
||||
})
|
||||
}
|
||||
|
@ -17,11 +17,10 @@ import {
|
||||
import { useRouteLoaderData } from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { IndexLoaderData } from 'lib/types'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||
import { ViewControlContextMenu } from './ViewControlMenu'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import { useSelector } from '@xstate/react'
|
||||
|
||||
enum StreamState {
|
||||
Playing = 'playing',
|
||||
@ -36,7 +35,7 @@ export const Stream = () => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send } = useModelingContext()
|
||||
const commandBarState = useCommandBarState()
|
||||
const { commandBarState } = useCommandsContext()
|
||||
const { mediaStream } = useAppStream()
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const [streamState, setStreamState] = useState(StreamState.Unset)
|
||||
|
@ -28,7 +28,7 @@ import { base64Decode } from 'lang/wasm'
|
||||
import { sendTelemetry } from 'lib/textToCad'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { EventFrom } from 'xstate'
|
||||
import { fileMachine } from 'machines/fileMachine'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
@ -43,10 +43,15 @@ export function ToastTextToCadError({
|
||||
toastId,
|
||||
message,
|
||||
prompt,
|
||||
commandBarSend,
|
||||
}: {
|
||||
toastId: string
|
||||
message: string
|
||||
prompt: string
|
||||
commandBarSend: (
|
||||
event: EventFrom<typeof commandBarMachine>,
|
||||
data?: unknown
|
||||
) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col justify-between gap-6">
|
||||
@ -76,7 +81,7 @@ export function ToastTextToCadError({
|
||||
}}
|
||||
name="Edit prompt"
|
||||
onClick={() => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'modeling',
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
||||
import { getNodeFromPath } from '../../lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
getNodeFromPath,
|
||||
} from '../../lang/queryAst'
|
||||
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
|
||||
import {
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from 'react-router-dom'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
||||
|
||||
type User = Models['User_type']
|
||||
|
||||
@ -123,7 +124,9 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
||||
<Route
|
||||
path="/file/:id"
|
||||
element={
|
||||
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
||||
</CommandBarProvider>
|
||||
}
|
||||
/>
|
||||
),
|
||||
|
@ -4,12 +4,12 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Fragment, useMemo, useState } from 'react'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import Tooltip from './Tooltip'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { authActor } from 'machines/appMachine'
|
||||
|
||||
type User = Models['User_type']
|
||||
|
||||
@ -20,7 +20,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
const displayedName = getDisplayName(user)
|
||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||
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.
|
||||
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||
|
@ -5,6 +5,7 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
|
||||
import { undo, redo } from '@codemirror/commands'
|
||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
||||
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
|
||||
import {
|
||||
Diagnostic,
|
||||
@ -51,6 +52,9 @@ export default class EditorManager {
|
||||
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
|
||||
private _modelingState: StateFrom<typeof modelingMachine> | null = null
|
||||
|
||||
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
|
||||
() => {}
|
||||
|
||||
private _convertToVariableEnabled: boolean = false
|
||||
private _convertToVariableCallback: () => void = () => {}
|
||||
|
||||
@ -157,6 +161,14 @@ export default class EditorManager {
|
||||
this._modelingState = state
|
||||
}
|
||||
|
||||
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
|
||||
this._commandBarSend = send
|
||||
}
|
||||
|
||||
commandBarSend(eventInfo: CommandBarMachineEvent): void {
|
||||
return this._commandBarSend(eventInfo)
|
||||
}
|
||||
|
||||
get highlightRange(): Array<[number, number]> {
|
||||
return this._highlightRange
|
||||
}
|
||||
@ -303,21 +315,6 @@ export default class EditorManager {
|
||||
if (selections?.graphSelections?.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._editorView) {
|
||||
return
|
||||
}
|
||||
const codeBaseSelections = this.createEditorSelection(selections)
|
||||
this._editorView.dispatch({
|
||||
selection: codeBaseSelections,
|
||||
annotations: [
|
||||
updateOutsideEditorEvent,
|
||||
Transaction.addToHistory.of(false),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
createEditorSelection(selections: Selections) {
|
||||
let codeBasedSelections = []
|
||||
for (const selection of selections.graphSelections) {
|
||||
const safeEnd = Math.min(
|
||||
@ -334,7 +331,18 @@ export default class EditorManager {
|
||||
.range[1]
|
||||
const safeEnd = Math.min(end, this._editorView?.state.doc.length || end)
|
||||
codeBasedSelections.push(EditorSelection.cursor(safeEnd))
|
||||
return EditorSelection.create(codeBasedSelections, 1)
|
||||
|
||||
if (!this._editorView) {
|
||||
return
|
||||
}
|
||||
|
||||
this._editorView.dispatch({
|
||||
selection: EditorSelection.create(codeBasedSelections, 1),
|
||||
annotations: [
|
||||
updateOutsideEditorEvent,
|
||||
Transaction.addToHistory.of(false),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// We will ONLY get here if the user called a select event.
|
||||
|
@ -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])
|
||||
}
|
11
src/hooks/useCommandsContext.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
|
||||
|
||||
export const useCommandsContext = () => {
|
||||
const commandBarActor = CommandsContext.useActorRef()
|
||||
const commandBarState = CommandsContext.useSelector((state) => state)
|
||||
return {
|
||||
commandBarSend: commandBarActor.send,
|
||||
commandBarState,
|
||||
commandBarActor,
|
||||
}
|
||||
}
|
@ -17,8 +17,7 @@ import {
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { CallExpression, defaultSourceRange } from 'lang/wasm'
|
||||
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect } from 'react'
|
||||
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
|
||||
import { createMachineCommand } from '../lib/createMachineCommand'
|
||||
import { useCommandsContext } from './useCommandsContext'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
@ -14,7 +15,6 @@ import { useKclContext } from 'lang/KclProvider'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { useAppState } from 'AppState'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
// This might not be necessary, AnyStateMachine from xstate is working
|
||||
export type AllMachines =
|
||||
@ -48,6 +48,7 @@ export default function useStateMachineCommands<
|
||||
allCommandsRequireNetwork = false,
|
||||
onCancel,
|
||||
}: UseStateMachineCommandsArgs<T, S>) {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { overallState } = useNetworkContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useAppState()
|
||||
@ -75,13 +76,10 @@ export default function useStateMachineCommands<
|
||||
})
|
||||
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
|
||||
|
||||
commandBarActor.send({
|
||||
type: 'Add commands',
|
||||
data: { commands: newCommands },
|
||||
})
|
||||
commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
|
||||
|
||||
return () => {
|
||||
commandBarActor.send({
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: { commands: newCommands },
|
||||
})
|
||||
|
@ -322,7 +322,6 @@ export class KclManager {
|
||||
await this.ensureWasmInit()
|
||||
const { logs, errors, execState, isInterrupted } = await executeAst({
|
||||
ast,
|
||||
path: codeManager.currentFilePath || undefined,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
})
|
||||
|
||||
|
@ -80,10 +80,6 @@ export default class CodeManager {
|
||||
}))
|
||||
}
|
||||
|
||||
get currentFilePath(): string | null {
|
||||
return this._currentFilePath
|
||||
}
|
||||
|
||||
updateCurrentFilePath(path: string) {
|
||||
this._currentFilePath = path
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||
import {
|
||||
Identifier,
|
||||
assertParse,
|
||||
|
@ -52,22 +52,27 @@ afterAll(async () => {
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
describe('Test KCL Samples from public Github repository', () => {
|
||||
describe('when performing enginelessExecutor', () => {
|
||||
afterEach(() => {
|
||||
process.chdir('..')
|
||||
})
|
||||
|
||||
// The tests have to be sequential because we need to change directories
|
||||
// to support `import` working properly.
|
||||
// @ts-expect-error
|
||||
describe.sequential('Test KCL Samples from public Github repository', () => {
|
||||
// @ts-expect-error
|
||||
describe.sequential('when performing enginelessExecutor', () => {
|
||||
manifest.forEach((file: KclSampleFile) => {
|
||||
it(
|
||||
// @ts-expect-error
|
||||
it.sequential(
|
||||
`should execute ${file.title} (${file.file}) successfully`,
|
||||
async () => {
|
||||
const code = await fs.readFile(
|
||||
file.pathFromProjectDirectoryToFirstFile,
|
||||
'utf-8'
|
||||
)
|
||||
const [dirProject, fileKcl] =
|
||||
file.pathFromProjectDirectoryToFirstFile.split('/')
|
||||
process.chdir(dirProject)
|
||||
const code = await fs.readFile(fileKcl, 'utf-8')
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(
|
||||
ast,
|
||||
programMemoryInit(),
|
||||
file.pathFromProjectDirectoryToFirstFile
|
||||
)
|
||||
await enginelessExecutor(ast, programMemoryInit())
|
||||
},
|
||||
files.length * 1000
|
||||
)
|
||||
|
@ -46,14 +46,12 @@ export const toolTips: Array<ToolTip> = [
|
||||
|
||||
export async function executeAst({
|
||||
ast,
|
||||
path,
|
||||
engineCommandManager,
|
||||
// If you set programMemoryOverride we assume you mean mock mode. Since that
|
||||
// is the only way to go about it.
|
||||
programMemoryOverride,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
path?: string
|
||||
engineCommandManager: EngineCommandManager
|
||||
programMemoryOverride?: ProgramMemory
|
||||
isInterrupted?: boolean
|
||||
@ -65,8 +63,8 @@ export async function executeAst({
|
||||
}> {
|
||||
try {
|
||||
const execState = await (programMemoryOverride
|
||||
? enginelessExecutor(ast, programMemoryOverride, path)
|
||||
: executor(ast, engineCommandManager, path))
|
||||
? enginelessExecutor(ast, programMemoryOverride)
|
||||
: executor(ast, engineCommandManager))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
|
@ -5,8 +5,6 @@ import {
|
||||
Identifier,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
LiteralValue,
|
||||
Literal,
|
||||
} from './wasm'
|
||||
import {
|
||||
createLiteral,
|
||||
@ -27,8 +25,7 @@ import {
|
||||
deleteFromSelection,
|
||||
} from './modifyAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { findUsesOfTagInPipe } from './queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { SimplifiedArgDetails } from './std/stdTypes'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
@ -39,26 +36,10 @@ beforeAll(async () => {
|
||||
})
|
||||
|
||||
describe('Testing createLiteral', () => {
|
||||
it('should create a literal number without units', () => {
|
||||
it('should create a literal', () => {
|
||||
const result = createLiteral(5)
|
||||
expect(result.type).toBe('Literal')
|
||||
expect((result as any).value.value).toBe(5)
|
||||
expect((result as any).value.suffix).toBe('None')
|
||||
expect((result as Literal).raw).toBe('5')
|
||||
})
|
||||
it('should create a literal number with units', () => {
|
||||
const lit: LiteralValue = { value: 5, suffix: 'Mm' }
|
||||
const result = createLiteral(lit)
|
||||
expect(result.type).toBe('Literal')
|
||||
expect((result as any).value.value).toBe(5)
|
||||
expect((result as any).value.suffix).toBe('Mm')
|
||||
expect((result as Literal).raw).toBe('5mm')
|
||||
})
|
||||
it('should create a literal boolean', () => {
|
||||
const result = createLiteral(false)
|
||||
expect(result.type).toBe('Literal')
|
||||
expect((result as Literal).value).toBe(false)
|
||||
expect((result as Literal).raw).toBe('false')
|
||||
})
|
||||
})
|
||||
describe('Testing createIdentifier', () => {
|
||||
|
@ -20,17 +20,16 @@ import {
|
||||
SourceRange,
|
||||
sketchFromKclValue,
|
||||
isPathToNodeNumber,
|
||||
formatNumber,
|
||||
} from './wasm'
|
||||
import {
|
||||
isNodeSafeToReplacePath,
|
||||
findAllPreviousVariables,
|
||||
findAllPreviousVariablesPath,
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
isNodeSafeToReplace,
|
||||
traverse,
|
||||
} from './queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { addTagForSketchOnFace, getConstraintInfo } from './std/sketch'
|
||||
import {
|
||||
PathToNodeMap,
|
||||
@ -744,26 +743,11 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
|
||||
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This depends on WASM, but it's not async. Callers are responsible for
|
||||
* awaiting init of the WASM module.
|
||||
*/
|
||||
export function createLiteral(value: LiteralValue | number): Node<Literal> {
|
||||
const raw = `${value}`
|
||||
if (typeof value === 'number') {
|
||||
value = { value, suffix: 'None' }
|
||||
}
|
||||
let raw: string
|
||||
if (typeof value === 'string') {
|
||||
// TODO: Should we handle escape sequences?
|
||||
raw = `${value}`
|
||||
} else if (typeof value === 'boolean') {
|
||||
raw = `${value}`
|
||||
} else if (typeof value.value === 'number' && value.suffix === 'None') {
|
||||
// Fast path for numbers when there are no units.
|
||||
raw = `${value.value}`
|
||||
} else {
|
||||
raw = formatNumber(value.value, value.suffix)
|
||||
}
|
||||
return {
|
||||
type: 'Literal',
|
||||
start: 0,
|
||||
|
@ -21,8 +21,7 @@ import {
|
||||
ChamferParameters,
|
||||
EdgeTreatmentParameters,
|
||||
} from './addEdgeTreatment'
|
||||
import { getNodeFromPath } from '../queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { createLiteral } from 'lang/modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
|
@ -20,10 +20,10 @@ import {
|
||||
} from '../modifyAst'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
hasSketchPipeBeenExtruded,
|
||||
traverse,
|
||||
} from '../queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import {
|
||||
addTagForSketchOnFace,
|
||||
getTagFromCallExpression,
|
||||
|
@ -19,8 +19,7 @@ import {
|
||||
findUniqueName,
|
||||
createVariableDeclaration,
|
||||
} from 'lang/modifyAst'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import {
|
||||
mutateAstWithTagForSketchSegment,
|
||||
getEdgeTagCall,
|
||||
|
@ -5,15 +5,12 @@ import {
|
||||
PathToNode,
|
||||
Identifier,
|
||||
topLevelRange,
|
||||
PipeExpression,
|
||||
CallExpression,
|
||||
VariableDeclarator,
|
||||
} from './wasm'
|
||||
import { ProgramMemory } from 'lang/wasm'
|
||||
import {
|
||||
findAllPreviousVariables,
|
||||
isNodeSafeToReplace,
|
||||
isTypeInValue,
|
||||
getNodePathFromSourceRange,
|
||||
hasExtrudeSketch,
|
||||
findUsesOfTagInPipe,
|
||||
hasSketchPipeBeenExtruded,
|
||||
@ -22,18 +19,15 @@ import {
|
||||
getNodeFromPath,
|
||||
doesSceneHaveExtrudedSketch,
|
||||
} from './queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import {
|
||||
createArrayExpression,
|
||||
createCallExpression,
|
||||
createLiteral,
|
||||
createPipeSubstitution,
|
||||
createCallExpressionStdLib,
|
||||
} from './modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { codeRefFromRange } from './std/artifactGraph'
|
||||
import { addCallExpressionsToPipe, addCloseToPipe } from 'lang/std/sketch'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -686,115 +680,3 @@ myNestedVar = [
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
@ -21,9 +21,7 @@ import {
|
||||
topLevelRange,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
recast,
|
||||
} from './wasm'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||
import { getAngle } from '../lib/utils'
|
||||
@ -69,28 +67,7 @@ export function getNodeFromPath<T>(
|
||||
deepPath: successfulPaths,
|
||||
}
|
||||
}
|
||||
const stackTraceError = new Error()
|
||||
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
|
||||
return new Error('not an object')
|
||||
}
|
||||
currentNode = currentNode?.[pathItem[0]]
|
||||
successfulPaths.push(pathItem)
|
||||
@ -148,6 +125,311 @@ export function getNodeFromPathCurry(
|
||||
}
|
||||
}
|
||||
|
||||
function moreNodePathFromSourceRange(
|
||||
node: Node<
|
||||
| Expr
|
||||
| ImportStatement
|
||||
| ExpressionStatement
|
||||
| VariableDeclaration
|
||||
| ReturnStatement
|
||||
>,
|
||||
sourceRange: SourceRange,
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
const [start, end] = sourceRange
|
||||
let path: PathToNode = [...previousPath]
|
||||
const _node = { ...node }
|
||||
|
||||
if (start < _node.start || end > _node.end) return path
|
||||
|
||||
const isInRange = _node.start <= start && _node.end >= end
|
||||
|
||||
if (
|
||||
(_node.type === 'Identifier' ||
|
||||
_node.type === 'Literal' ||
|
||||
_node.type === 'TagDeclarator') &&
|
||||
isInRange
|
||||
) {
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'CallExpression' && isInRange) {
|
||||
const { callee, arguments: args } = _node
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.start <= start &&
|
||||
callee.end >= end
|
||||
) {
|
||||
path.push(['callee', 'CallExpression'])
|
||||
return path
|
||||
}
|
||||
if (args.length > 0) {
|
||||
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||
const arg = args[argIndex]
|
||||
if (arg.start <= start && arg.end >= end) {
|
||||
path.push(['arguments', 'CallExpression'])
|
||||
path.push([argIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'CallExpressionKw' && isInRange) {
|
||||
const { callee, arguments: args } = _node
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.start <= start &&
|
||||
callee.end >= end
|
||||
) {
|
||||
path.push(['callee', 'CallExpressionKw'])
|
||||
return path
|
||||
}
|
||||
if (args.length > 0) {
|
||||
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||
const arg = args[argIndex].arg
|
||||
if (arg.start <= start && arg.end >= end) {
|
||||
path.push(['arguments', 'CallExpressionKw'])
|
||||
path.push([argIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'BinaryExpression' && isInRange) {
|
||||
const { left, right } = _node
|
||||
if (left.start <= start && left.end >= end) {
|
||||
path.push(['left', 'BinaryExpression'])
|
||||
return moreNodePathFromSourceRange(left, sourceRange, path)
|
||||
}
|
||||
if (right.start <= start && right.end >= end) {
|
||||
path.push(['right', 'BinaryExpression'])
|
||||
return moreNodePathFromSourceRange(right, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'PipeExpression' && isInRange) {
|
||||
const { body } = _node
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const pipe = body[i]
|
||||
if (pipe.start <= start && pipe.end >= end) {
|
||||
path.push(['body', 'PipeExpression'])
|
||||
path.push([i, 'index'])
|
||||
return moreNodePathFromSourceRange(pipe, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ArrayExpression' && isInRange) {
|
||||
const { elements } = _node
|
||||
for (let elIndex = 0; elIndex < elements.length; elIndex++) {
|
||||
const element = elements[elIndex]
|
||||
if (element.start <= start && element.end >= end) {
|
||||
path.push(['elements', 'ArrayExpression'])
|
||||
path.push([elIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(element, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ObjectExpression' && isInRange) {
|
||||
const { properties } = _node
|
||||
for (let propIndex = 0; propIndex < properties.length; propIndex++) {
|
||||
const property = properties[propIndex]
|
||||
if (property.start <= start && property.end >= end) {
|
||||
path.push(['properties', 'ObjectExpression'])
|
||||
path.push([propIndex, 'index'])
|
||||
if (property.key.start <= start && property.key.end >= end) {
|
||||
path.push(['key', 'Property'])
|
||||
return moreNodePathFromSourceRange(property.key, sourceRange, path)
|
||||
}
|
||||
if (property.value.start <= start && property.value.end >= end) {
|
||||
path.push(['value', 'Property'])
|
||||
return moreNodePathFromSourceRange(property.value, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ExpressionStatement' && isInRange) {
|
||||
const { expression } = _node
|
||||
path.push(['expression', 'ExpressionStatement'])
|
||||
return moreNodePathFromSourceRange(expression, sourceRange, path)
|
||||
}
|
||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||
const declaration = _node.declaration
|
||||
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||
const declaration = _node.declaration
|
||||
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'UnaryExpression' && isInRange) {
|
||||
const { argument } = _node
|
||||
if (argument.start <= start && argument.end >= end) {
|
||||
path.push(['argument', 'UnaryExpression'])
|
||||
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'FunctionExpression' && isInRange) {
|
||||
for (let i = 0; i < _node.params.length; i++) {
|
||||
const param = _node.params[i]
|
||||
if (param.identifier.start <= start && param.identifier.end >= end) {
|
||||
path.push(['params', 'FunctionExpression'])
|
||||
path.push([i, 'index'])
|
||||
return moreNodePathFromSourceRange(param.identifier, sourceRange, path)
|
||||
}
|
||||
}
|
||||
if (_node.body.start <= start && _node.body.end >= end) {
|
||||
path.push(['body', 'FunctionExpression'])
|
||||
const fnBody = _node.body.body
|
||||
for (let i = 0; i < fnBody.length; i++) {
|
||||
const statement = fnBody[i]
|
||||
if (statement.start <= start && statement.end >= end) {
|
||||
path.push(['body', 'FunctionExpression'])
|
||||
path.push([i, 'index'])
|
||||
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ReturnStatement' && isInRange) {
|
||||
const { argument } = _node
|
||||
if (argument.start <= start && argument.end >= end) {
|
||||
path.push(['argument', 'ReturnStatement'])
|
||||
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'MemberExpression' && isInRange) {
|
||||
const { object, property } = _node
|
||||
if (object.start <= start && object.end >= end) {
|
||||
path.push(['object', 'MemberExpression'])
|
||||
return moreNodePathFromSourceRange(object, sourceRange, path)
|
||||
}
|
||||
if (property.start <= start && property.end >= end) {
|
||||
path.push(['property', 'MemberExpression'])
|
||||
return moreNodePathFromSourceRange(property, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
||||
|
||||
if (_node.type === 'IfExpression' && isInRange) {
|
||||
const { cond, then_val, else_ifs, final_else } = _node
|
||||
if (cond.start <= start && cond.end >= end) {
|
||||
path.push(['cond', 'IfExpression'])
|
||||
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||
}
|
||||
if (then_val.start <= start && then_val.end >= end) {
|
||||
path.push(['then_val', 'IfExpression'])
|
||||
path.push(['body', 'IfExpression'])
|
||||
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||
}
|
||||
for (let i = 0; i < else_ifs.length; i++) {
|
||||
const else_if = else_ifs[i]
|
||||
if (else_if.start <= start && else_if.end >= end) {
|
||||
path.push(['else_ifs', 'IfExpression'])
|
||||
path.push([i, 'index'])
|
||||
const { cond, then_val } = else_if
|
||||
if (cond.start <= start && cond.end >= end) {
|
||||
path.push(['cond', 'IfExpression'])
|
||||
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||
}
|
||||
path.push(['then_val', 'IfExpression'])
|
||||
path.push(['body', 'IfExpression'])
|
||||
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||
}
|
||||
}
|
||||
if (final_else.start <= start && final_else.end >= end) {
|
||||
path.push(['final_else', 'IfExpression'])
|
||||
path.push(['body', 'IfExpression'])
|
||||
return getNodePathFromSourceRange(final_else, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'ImportStatement' && isInRange) {
|
||||
if (_node.selector && _node.selector.type === 'List') {
|
||||
path.push(['selector', 'ImportStatement'])
|
||||
const { items } = _node.selector
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.start <= start && item.end >= end) {
|
||||
path.push(['items', 'ImportSelector'])
|
||||
path.push([i, 'index'])
|
||||
if (item.name.start <= start && item.name.end >= end) {
|
||||
path.push(['name', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
if (
|
||||
item.alias &&
|
||||
item.alias.start <= start &&
|
||||
item.alias.end >= end
|
||||
) {
|
||||
path.push(['alias', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
console.error('not implemented: ' + node.type)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
export function getNodePathFromSourceRange(
|
||||
node: Program,
|
||||
sourceRange: SourceRange,
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
const [start, end] = sourceRange || []
|
||||
let path: PathToNode = [...previousPath]
|
||||
const _node = { ...node }
|
||||
|
||||
// loop over each statement in body getting the index with a for loop
|
||||
for (
|
||||
let statementIndex = 0;
|
||||
statementIndex < _node.body.length;
|
||||
statementIndex++
|
||||
) {
|
||||
const statement = _node.body[statementIndex]
|
||||
if (statement.start <= start && statement.end >= end) {
|
||||
path.push([statementIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
type KCLNode = Node<
|
||||
| Expr
|
||||
| ExpressionStatement
|
||||
|
@ -1,316 +0,0 @@
|
||||
import {
|
||||
Expr,
|
||||
ExpressionStatement,
|
||||
VariableDeclaration,
|
||||
ReturnStatement,
|
||||
SourceRange,
|
||||
PathToNode,
|
||||
Program,
|
||||
} from './wasm'
|
||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
function moreNodePathFromSourceRange(
|
||||
node: Node<
|
||||
| Expr
|
||||
| ImportStatement
|
||||
| ExpressionStatement
|
||||
| VariableDeclaration
|
||||
| ReturnStatement
|
||||
>,
|
||||
sourceRange: SourceRange,
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
const [start, end] = sourceRange
|
||||
let path: PathToNode = [...previousPath]
|
||||
const _node = { ...node }
|
||||
|
||||
if (start < _node.start || end > _node.end) return path
|
||||
|
||||
const isInRange = _node.start <= start && _node.end >= end
|
||||
|
||||
if (
|
||||
(_node.type === 'Identifier' ||
|
||||
_node.type === 'Literal' ||
|
||||
_node.type === 'TagDeclarator') &&
|
||||
isInRange
|
||||
) {
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'CallExpression' && isInRange) {
|
||||
const { callee, arguments: args } = _node
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.start <= start &&
|
||||
callee.end >= end
|
||||
) {
|
||||
path.push(['callee', 'CallExpression'])
|
||||
return path
|
||||
}
|
||||
if (args.length > 0) {
|
||||
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||
const arg = args[argIndex]
|
||||
if (arg.start <= start && arg.end >= end) {
|
||||
path.push(['arguments', 'CallExpression'])
|
||||
path.push([argIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'CallExpressionKw' && isInRange) {
|
||||
const { callee, arguments: args } = _node
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.start <= start &&
|
||||
callee.end >= end
|
||||
) {
|
||||
path.push(['callee', 'CallExpressionKw'])
|
||||
return path
|
||||
}
|
||||
if (args.length > 0) {
|
||||
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||
const arg = args[argIndex].arg
|
||||
if (arg.start <= start && arg.end >= end) {
|
||||
path.push(['arguments', 'CallExpressionKw'])
|
||||
path.push([argIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'BinaryExpression' && isInRange) {
|
||||
const { left, right } = _node
|
||||
if (left.start <= start && left.end >= end) {
|
||||
path.push(['left', 'BinaryExpression'])
|
||||
return moreNodePathFromSourceRange(left, sourceRange, path)
|
||||
}
|
||||
if (right.start <= start && right.end >= end) {
|
||||
path.push(['right', 'BinaryExpression'])
|
||||
return moreNodePathFromSourceRange(right, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'PipeExpression' && isInRange) {
|
||||
const { body } = _node
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const pipe = body[i]
|
||||
if (pipe.start <= start && pipe.end >= end) {
|
||||
path.push(['body', 'PipeExpression'])
|
||||
path.push([i, 'index'])
|
||||
return moreNodePathFromSourceRange(pipe, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ArrayExpression' && isInRange) {
|
||||
const { elements } = _node
|
||||
for (let elIndex = 0; elIndex < elements.length; elIndex++) {
|
||||
const element = elements[elIndex]
|
||||
if (element.start <= start && element.end >= end) {
|
||||
path.push(['elements', 'ArrayExpression'])
|
||||
path.push([elIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(element, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ObjectExpression' && isInRange) {
|
||||
const { properties } = _node
|
||||
for (let propIndex = 0; propIndex < properties.length; propIndex++) {
|
||||
const property = properties[propIndex]
|
||||
if (property.start <= start && property.end >= end) {
|
||||
path.push(['properties', 'ObjectExpression'])
|
||||
path.push([propIndex, 'index'])
|
||||
if (property.key.start <= start && property.key.end >= end) {
|
||||
path.push(['key', 'Property'])
|
||||
return moreNodePathFromSourceRange(property.key, sourceRange, path)
|
||||
}
|
||||
if (property.value.start <= start && property.value.end >= end) {
|
||||
path.push(['value', 'Property'])
|
||||
return moreNodePathFromSourceRange(property.value, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ExpressionStatement' && isInRange) {
|
||||
const { expression } = _node
|
||||
path.push(['expression', 'ExpressionStatement'])
|
||||
return moreNodePathFromSourceRange(expression, sourceRange, path)
|
||||
}
|
||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||
const declaration = _node.declaration
|
||||
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||
const declaration = _node.declaration
|
||||
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'UnaryExpression' && isInRange) {
|
||||
const { argument } = _node
|
||||
if (argument.start <= start && argument.end >= end) {
|
||||
path.push(['argument', 'UnaryExpression'])
|
||||
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'FunctionExpression' && isInRange) {
|
||||
for (let i = 0; i < _node.params.length; i++) {
|
||||
const param = _node.params[i]
|
||||
if (param.identifier.start <= start && param.identifier.end >= end) {
|
||||
path.push(['params', 'FunctionExpression'])
|
||||
path.push([i, 'index'])
|
||||
return moreNodePathFromSourceRange(param.identifier, sourceRange, path)
|
||||
}
|
||||
}
|
||||
if (_node.body.start <= start && _node.body.end >= end) {
|
||||
path.push(['body', 'FunctionExpression'])
|
||||
const fnBody = _node.body.body
|
||||
for (let i = 0; i < fnBody.length; i++) {
|
||||
const statement = fnBody[i]
|
||||
if (statement.start <= start && statement.end >= end) {
|
||||
path.push(['body', 'FunctionExpression'])
|
||||
path.push([i, 'index'])
|
||||
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'ReturnStatement' && isInRange) {
|
||||
const { argument } = _node
|
||||
if (argument.start <= start && argument.end >= end) {
|
||||
path.push(['argument', 'ReturnStatement'])
|
||||
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if (_node.type === 'MemberExpression' && isInRange) {
|
||||
const { object, property } = _node
|
||||
if (object.start <= start && object.end >= end) {
|
||||
path.push(['object', 'MemberExpression'])
|
||||
return moreNodePathFromSourceRange(object, sourceRange, path)
|
||||
}
|
||||
if (property.start <= start && property.end >= end) {
|
||||
path.push(['property', 'MemberExpression'])
|
||||
return moreNodePathFromSourceRange(property, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
||||
|
||||
if (_node.type === 'IfExpression' && isInRange) {
|
||||
const { cond, then_val, else_ifs, final_else } = _node
|
||||
if (cond.start <= start && cond.end >= end) {
|
||||
path.push(['cond', 'IfExpression'])
|
||||
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||
}
|
||||
if (then_val.start <= start && then_val.end >= end) {
|
||||
path.push(['then_val', 'IfExpression'])
|
||||
path.push(['body', 'IfExpression'])
|
||||
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||
}
|
||||
for (let i = 0; i < else_ifs.length; i++) {
|
||||
const else_if = else_ifs[i]
|
||||
if (else_if.start <= start && else_if.end >= end) {
|
||||
path.push(['else_ifs', 'IfExpression'])
|
||||
path.push([i, 'index'])
|
||||
const { cond, then_val } = else_if
|
||||
if (cond.start <= start && cond.end >= end) {
|
||||
path.push(['cond', 'IfExpression'])
|
||||
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||
}
|
||||
path.push(['then_val', 'IfExpression'])
|
||||
path.push(['body', 'IfExpression'])
|
||||
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||
}
|
||||
}
|
||||
if (final_else.start <= start && final_else.end >= end) {
|
||||
path.push(['final_else', 'IfExpression'])
|
||||
path.push(['body', 'IfExpression'])
|
||||
return getNodePathFromSourceRange(final_else, sourceRange, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
if (_node.type === 'ImportStatement' && isInRange) {
|
||||
if (_node.selector && _node.selector.type === 'List') {
|
||||
path.push(['selector', 'ImportStatement'])
|
||||
const { items } = _node.selector
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.start <= start && item.end >= end) {
|
||||
path.push(['items', 'ImportSelector'])
|
||||
path.push([i, 'index'])
|
||||
if (item.name.start <= start && item.name.end >= end) {
|
||||
path.push(['name', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
if (
|
||||
item.alias &&
|
||||
item.alias.start <= start &&
|
||||
item.alias.end >= end
|
||||
) {
|
||||
path.push(['alias', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
console.error('not implemented: ' + node.type)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
export function getNodePathFromSourceRange(
|
||||
node: Program,
|
||||
sourceRange: SourceRange,
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
const [start, end] = sourceRange || []
|
||||
let path: PathToNode = [...previousPath]
|
||||
const _node = { ...node }
|
||||
|
||||
// loop over each statement in body getting the index with a for loop
|
||||
for (
|
||||
let statementIndex = 0;
|
||||
statementIndex < _node.body.length;
|
||||
statementIndex++
|
||||
) {
|
||||
const statement = _node.body[statementIndex]
|
||||
if (statement.start <= start && statement.end >= end) {
|
||||
path.push([statementIndex, 'index'])
|
||||
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
@ -16,7 +16,7 @@ import {
|
||||
EdgeCut,
|
||||
} from 'lang/wasm'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||
|
@ -1999,7 +1999,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
.catch((e) => {
|
||||
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
|
||||
/*noop*/
|
||||
return e
|
||||
return null
|
||||
})
|
||||
}
|
||||
/**
|
||||
|
@ -31,9 +31,6 @@ class FileSystemManager {
|
||||
}
|
||||
|
||||
async join(dir: string, path: string): Promise<string> {
|
||||
if (path.startsWith(dir)) {
|
||||
path = path.slice(dir.length)
|
||||
}
|
||||
return Promise.resolve(window.electron.path.join(dir, path))
|
||||
}
|
||||
|
||||
|
@ -14,8 +14,7 @@ import {
|
||||
CallExpression,
|
||||
topLevelRange,
|
||||
} from '../wasm'
|
||||
import { getNodeFromPath } from '../queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
import { err } from 'lib/trap'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
@ -17,9 +17,9 @@ import {
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodeFromPathCurry,
|
||||
getNodePathFromSourceRange,
|
||||
getObjExprProperty,
|
||||
} from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import {
|
||||
isLiteralArrayOrStatic,
|
||||
isNotLiteralArrayOrStatic,
|
||||
|
@ -22,8 +22,11 @@ import {
|
||||
SourceRange,
|
||||
LiteralValue,
|
||||
} from '../wasm'
|
||||
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodeFromPathCurry,
|
||||
getNodePathFromSourceRange,
|
||||
} from '../queryAst'
|
||||
import {
|
||||
createArrayExpression,
|
||||
createBinaryExpression,
|
||||
|
@ -6,8 +6,6 @@ import {
|
||||
ArrayExpression,
|
||||
BinaryExpression,
|
||||
ArtifactGraph,
|
||||
LiteralValue,
|
||||
NumericSuffix,
|
||||
} from './wasm'
|
||||
import { filterArtifacts } from 'lang/std/artifactGraph'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
@ -71,15 +69,3 @@ export function isLiteral(e: any): e is Literal {
|
||||
export function isBinaryExpression(e: any): e is BinaryExpression {
|
||||
return e && e.type === 'BinaryExpression'
|
||||
}
|
||||
|
||||
export function isLiteralValueNumber(
|
||||
e: LiteralValue
|
||||
): e is { value: number; suffix: NumericSuffix } {
|
||||
return (
|
||||
typeof e === 'object' &&
|
||||
'value' in e &&
|
||||
typeof e.value === 'number' &&
|
||||
'suffix' in e &&
|
||||
typeof e.suffix === 'string'
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { err } from 'lib/trap'
|
||||
import { formatNumber, initPromise, parse, ParseResult } from './wasm'
|
||||
import { initPromise, parse, ParseResult } from './wasm'
|
||||
import { enginelessExecutor } from 'lib/testHelpers'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
@ -20,12 +20,3 @@ it('can execute parsed AST', async () => {
|
||||
expect(err(execState)).toEqual(false)
|
||||
expect(execState.memory.get('x')?.value).toEqual(1)
|
||||
})
|
||||
|
||||
it('formats numbers with units', () => {
|
||||
expect(formatNumber(1, 'None')).toEqual('1')
|
||||
expect(formatNumber(1, 'Count')).toEqual('1_')
|
||||
expect(formatNumber(1, 'Mm')).toEqual('1mm')
|
||||
expect(formatNumber(1, 'Inch')).toEqual('1in')
|
||||
expect(formatNumber(0.5, 'Mm')).toEqual('0.5mm')
|
||||
expect(formatNumber(-0.5, 'Mm')).toEqual('-0.5mm')
|
||||
})
|
||||
|
@ -2,7 +2,6 @@ import {
|
||||
init,
|
||||
parse_wasm,
|
||||
recast_wasm,
|
||||
format_number,
|
||||
execute,
|
||||
kcl_lint,
|
||||
modify_ast_for_sketch_wasm,
|
||||
@ -18,7 +17,6 @@ import {
|
||||
default_project_settings,
|
||||
base64_decode,
|
||||
clear_scene_and_bust_cache,
|
||||
change_kcl_settings,
|
||||
reloadModule,
|
||||
} from 'lib/wasm_lib_wrapper'
|
||||
|
||||
@ -55,9 +53,7 @@ import { ArtifactId } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { Artifact } from './std/artifactGraph'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
||||
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
|
||||
import { getNodePathFromSourceRange } from './queryAst'
|
||||
|
||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
@ -94,7 +90,6 @@ export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
||||
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
||||
export type { SourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||
export type { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
||||
|
||||
export type SyntaxType =
|
||||
| 'Program'
|
||||
@ -571,19 +566,9 @@ export function sketchFromKclValue(
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a KCL program.
|
||||
* @param node The AST of the program to execute.
|
||||
* @param path The full path of the file being executed. Use `null` for
|
||||
* expressions that don't have a file, like expressions in the command bar.
|
||||
* @param programMemoryOverride If this is not `null`, this will be used as the
|
||||
* initial program memory, and the execution will be engineless (AKA mock
|
||||
* execution).
|
||||
*/
|
||||
export const executor = async (
|
||||
node: Node<Program>,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
path?: string,
|
||||
programMemoryOverride: ProgramMemory | Error | null = null
|
||||
): Promise<ExecState> => {
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
@ -605,7 +590,6 @@ export const executor = async (
|
||||
}
|
||||
const execOutcome: RustExecOutcome = await execute(
|
||||
JSON.stringify(node),
|
||||
path,
|
||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
||||
JSON.stringify({ settings: jsAppSettings }),
|
||||
engineCommandManager,
|
||||
@ -643,13 +627,6 @@ export const recast = (ast: Program): string | Error => {
|
||||
return recast_wasm(JSON.stringify(ast))
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number with suffix as KCL.
|
||||
*/
|
||||
export function formatNumber(value: number, suffix: NumericSuffix): string {
|
||||
return format_number(value, JSON.stringify(suffix))
|
||||
}
|
||||
|
||||
export const makeDefaultPlanes = async (
|
||||
engineCommandManager: EngineCommandManager
|
||||
): Promise<DefaultPlanes> => {
|
||||
@ -846,17 +823,3 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
|
||||
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 { authActor } from 'machines/appMachine'
|
||||
import { ACTOR_IDS } from 'machines/machineConstants'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
|
||||
export const authCommands: Command[] = [
|
||||
{
|
||||
groupId: ACTOR_IDS.AUTH,
|
||||
name: 'log-out',
|
||||
displayName: 'Log out',
|
||||
icon: 'arrowLeft',
|
||||
needsReview: false,
|
||||
onSubmit: () => authActor.send({ type: 'Log out' }),
|
||||
type AuthCommandSchema = {}
|
||||
|
||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof authMachine,
|
||||
AuthCommandSchema
|
||||
> = {
|
||||
'Log in': {
|
||||
hide: 'both',
|
||||
},
|
||||
]
|
||||
'Log out': {
|
||||
args: [],
|
||||
icon: 'arrowLeft',
|
||||
},
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
loftValidator,
|
||||
revolveAxisValidator,
|
||||
shellValidator,
|
||||
sweepValidator,
|
||||
} from './validators'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
@ -43,8 +42,8 @@ export type ModelingCommandSchema = {
|
||||
distance: KclCommandValue
|
||||
}
|
||||
Sweep: {
|
||||
target: Selections
|
||||
trajectory: Selections
|
||||
path: Selections
|
||||
profile: Selections
|
||||
}
|
||||
Loft: {
|
||||
selection: Selections
|
||||
@ -309,24 +308,25 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
||||
icon: 'sweep',
|
||||
status: 'development',
|
||||
needsReview: false,
|
||||
needsReview: true,
|
||||
args: {
|
||||
target: {
|
||||
profile: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['solid2d'],
|
||||
required: true,
|
||||
skip: true,
|
||||
multiple: false,
|
||||
// TODO: add dry-run validation
|
||||
warningMessage:
|
||||
'The sweep workflow is new and under tested. Please break it and report issues.',
|
||||
},
|
||||
trajectory: {
|
||||
path: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['segment', 'path'],
|
||||
required: true,
|
||||
skip: false,
|
||||
skip: true,
|
||||
multiple: false,
|
||||
validation: sweepValidator,
|
||||
// TODO: add dry-run validation
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -41,11 +41,12 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
|
||||
name: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
options: (_, context) =>
|
||||
context?.projects.map((p) => ({
|
||||
name: p.name,
|
||||
value: p.name,
|
||||
})) || [],
|
||||
options: [],
|
||||
optionsFromContext: (context) =>
|
||||
context.projects.map((p) => ({
|
||||
name: p.name!,
|
||||
value: p.name!,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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 { CommandBarContext } from 'machines/commandBarMachine'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { ApiError_type } from '@kittycad/lib/dist/types/src/models'
|
||||
|
||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||
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 ({
|
||||
data,
|
||||
context,
|
||||
@ -98,7 +83,7 @@ export const revolveAxisValidator = async ({
|
||||
value: 360,
|
||||
}
|
||||
|
||||
const command = async () => {
|
||||
const revolveAboutEdgeCommand = async () => {
|
||||
return await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
@ -111,13 +96,13 @@ export const revolveAxisValidator = async ({
|
||||
},
|
||||
})
|
||||
}
|
||||
const result = await dryRunWrapper(command)
|
||||
if (result?.success) {
|
||||
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
|
||||
if (attemptRevolve?.success) {
|
||||
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 ({
|
||||
@ -143,7 +128,7 @@ export const loftValidator = async ({
|
||||
return 'Unable to loft, selection contains less than two solid2ds'
|
||||
}
|
||||
|
||||
const command = async () => {
|
||||
const loftCommand = async () => {
|
||||
// TODO: check what to do with these
|
||||
const DEFAULT_V_DEGREE = 2
|
||||
const DEFAULT_TOLERANCE = 2
|
||||
@ -160,13 +145,13 @@ export const loftValidator = async ({
|
||||
},
|
||||
})
|
||||
}
|
||||
const result = await dryRunWrapper(command)
|
||||
if (result?.success) {
|
||||
const attempt = await dryRunWrapper(loftCommand)
|
||||
if (attempt?.success) {
|
||||
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 ({
|
||||
@ -195,7 +180,7 @@ export const shellValidator = async ({
|
||||
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
|
||||
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
||||
const DEFAULT_HOLLOW = false
|
||||
@ -215,73 +200,10 @@ export const shellValidator = async ({
|
||||
})
|
||||
}
|
||||
|
||||
const result = await dryRunWrapper(command)
|
||||
if (result?.success) {
|
||||
const attemptShell = await dryRunWrapper(shellCommand)
|
||||
if (attemptShell?.success) {
|
||||
return true
|
||||
}
|
||||
|
||||
const reason = parseEngineErrorMessage(result) || 'unknown'
|
||||
return `Unable to shell with the current selection. Reason: ${reason}`
|
||||
}
|
||||
|
||||
export const sweepValidator = async ({
|
||||
context,
|
||||
data,
|
||||
}: {
|
||||
context: CommandBarContext
|
||||
data: { trajectory: Selections }
|
||||
}): Promise<boolean | string> => {
|
||||
if (!isSelections(data.trajectory)) {
|
||||
console.log('Unable to sweep, selections are missing')
|
||||
return 'Unable to sweep, selections are missing'
|
||||
}
|
||||
|
||||
// Retrieve the parent path from the segment selection directly
|
||||
const trajectoryArtifact = data.trajectory.graphSelections[0].artifact
|
||||
if (!trajectoryArtifact) {
|
||||
return "Unable to sweep, couldn't find the trajectory artifact"
|
||||
}
|
||||
if (trajectoryArtifact.type !== 'segment') {
|
||||
return "Unable to sweep, couldn't find the target from a non-segment selection"
|
||||
}
|
||||
const trajectory = trajectoryArtifact.pathId
|
||||
|
||||
// Get the former arg in the command bar flow, and retrieve the path from the solid2d directly
|
||||
const targetArg = context.argumentsToSubmit['target'] as Selections
|
||||
const targetArtifact = targetArg.graphSelections[0].artifact
|
||||
if (!targetArtifact) {
|
||||
return "Unable to sweep, couldn't find the profile artifact"
|
||||
}
|
||||
if (targetArtifact.type !== 'solid2d') {
|
||||
return "Unable to sweep, couldn't find the target from a non-solid2d selection"
|
||||
}
|
||||
const target = targetArtifact.pathId
|
||||
|
||||
const command = async () => {
|
||||
// TODO: second look on defaults here
|
||||
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
|
||||
const DEFAULT_SECTIONAL = false
|
||||
const cmdArgs = {
|
||||
target,
|
||||
trajectory,
|
||||
sectional: DEFAULT_SECTIONAL,
|
||||
tolerance: DEFAULT_TOLERANCE,
|
||||
}
|
||||
return await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'sweep',
|
||||
...cmdArgs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const result = await dryRunWrapper(command)
|
||||
if (result?.success) {
|
||||
return true
|
||||
}
|
||||
|
||||
const reason = parseEngineErrorMessage(result) || 'unknown'
|
||||
return `Unable to sweep with the current selection. Reason: ${reason}`
|
||||
return 'Unable to shell with the provided selection'
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
||||
import { Command, CommandArgumentOption } from './commandTypes'
|
||||
import { kclManager } from './singletons'
|
||||
import { codeManager, kclManager } from './singletons'
|
||||
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 { reportRejection } from './trap'
|
||||
import { parseProjectSettings } from 'lang/wasm'
|
||||
import { err, reportRejection } from './trap'
|
||||
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
|
||||
import { copyFileShareLink } from './links'
|
||||
import { IndexLoaderData } from './types'
|
||||
|
||||
interface OnSubmitProps {
|
||||
@ -65,23 +68,56 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
projectPathPart
|
||||
)}/${encodeURIComponent(primaryKclFile)}`
|
||||
const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
projectPathPart
|
||||
)}/${PROJECT_SETTINGS_FILE_NAME}`
|
||||
|
||||
fetch(sampleCodeUrl)
|
||||
.then(async (codeResponse): 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()
|
||||
return {
|
||||
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
||||
code,
|
||||
method: data.method,
|
||||
}
|
||||
Promise.allSettled([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
|
||||
.then((results) => {
|
||||
const a =
|
||||
'value' in results[0] ? results[0].value : results[0].reason
|
||||
const b =
|
||||
'value' in results[1] ? results[1].value : results[1].reason
|
||||
return [a, b]
|
||||
})
|
||||
.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) => {
|
||||
if (props?.code) {
|
||||
commandProps.specialPropsForSampleCommand
|
||||
@ -132,21 +168,21 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: 'share-file-link',
|
||||
// displayName: 'Share file',
|
||||
// description: 'Create a link that contains a copy of the current file.',
|
||||
// groupId: 'code',
|
||||
// needsReview: false,
|
||||
// icon: 'link',
|
||||
// onSubmit: () => {
|
||||
// copyFileShareLink({
|
||||
// token: commandProps.authToken,
|
||||
// code: codeManager.code,
|
||||
// name: commandProps.projectData.project?.name || '',
|
||||
// units: commandProps.settings.defaultUnit,
|
||||
// }).catch(reportRejection)
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: 'share-file-link',
|
||||
displayName: 'Share file',
|
||||
description: 'Create a link that contains a copy of the current file.',
|
||||
groupId: 'code',
|
||||
needsReview: false,
|
||||
icon: 'link',
|
||||
onSubmit: () => {
|
||||
copyFileShareLink({
|
||||
token: commandProps.authToken,
|
||||
code: codeManager.code,
|
||||
name: commandProps.projectData.project?.name || '',
|
||||
units: commandProps.settings.defaultUnit,
|
||||
}).catch(reportRejection)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
import { expect } from 'vitest'
|
||||
import {
|
||||
recast,
|
||||
assertParse,
|
||||
topLevelRange,
|
||||
VariableDeclaration,
|
||||
initPromise,
|
||||
} from 'lang/wasm'
|
||||
import { updateCenterRectangleSketch } from './rectangleTool'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { findUniqueName } from 'lang/modifyAst'
|
||||
import { err, trap } from './trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('library rectangleTool helper functions', () => {
|
||||
describe('updateCenterRectangleSketch', () => {
|
||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/5157
|
||||
test('should update AST and source code', async () => {
|
||||
// Base source code that will be edited in place
|
||||
const sourceCode = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([120.37, 162.76], %)
|
||||
|> angledLine([0, 0], %, $rectangleSegmentA001)
|
||||
|> angledLine([segAng(rectangleSegmentA001) + 90, 0], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
// Create ast
|
||||
const _ast = assertParse(sourceCode)
|
||||
let ast = structuredClone(_ast)
|
||||
|
||||
// Find some nodes and paths to reference
|
||||
const sketchSnippet = `startProfileAt([120.37, 162.76], %)`
|
||||
const sketchRange = topLevelRange(
|
||||
sourceCode.indexOf(sketchSnippet),
|
||||
sourceCode.indexOf(sketchSnippet) + sketchSnippet.length
|
||||
)
|
||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||
const _node = getNodeFromPath<VariableDeclaration>(
|
||||
ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
// Hard code inputs that a user would have taken with their mouse
|
||||
const x = 40
|
||||
const y = 60
|
||||
const rectangleOrigin = [120, 180]
|
||||
const tags: [string, string, string] = [
|
||||
'rectangleSegmentA001',
|
||||
'rectangleSegmentB001',
|
||||
'rectangleSegmentC001',
|
||||
]
|
||||
|
||||
// Update the ast
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
updateCenterRectangleSketch(
|
||||
sketchInit,
|
||||
x,
|
||||
y,
|
||||
tags[0],
|
||||
rectangleOrigin[0],
|
||||
rectangleOrigin[1]
|
||||
)
|
||||
}
|
||||
|
||||
// ast is edited in place from the updateCenterRectangleSketch
|
||||
const expectedSourceCode = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([80, 120], %)
|
||||
|> angledLine([0, 80], %, $rectangleSegmentA001)
|
||||
|> angledLine([segAng(rectangleSegmentA001) + 90, 120], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
const recasted = recast(ast)
|
||||
expect(recasted).toEqual(expectedSourceCode)
|
||||
})
|
||||
})
|
||||
})
|
@ -8,19 +8,13 @@ import {
|
||||
createTagDeclarator,
|
||||
createUnaryExpression,
|
||||
} from 'lang/modifyAst'
|
||||
import {
|
||||
ArrayExpression,
|
||||
CallExpression,
|
||||
PipeExpression,
|
||||
recast,
|
||||
} from 'lang/wasm'
|
||||
import { ArrayExpression, CallExpression, PipeExpression } from 'lang/wasm'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import {
|
||||
isCallExpression,
|
||||
isArrayExpression,
|
||||
isLiteral,
|
||||
isBinaryExpression,
|
||||
isLiteralValueNumber,
|
||||
} from 'lang/util'
|
||||
|
||||
/**
|
||||
@ -146,12 +140,10 @@ export function updateCenterRectangleSketch(
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
const literal = arrayExpression.elements[0]
|
||||
if (isLiteral(literal)) {
|
||||
if (isLiteralValueNumber(literal.value)) {
|
||||
callExpression.arguments[0] = createArrayExpression([
|
||||
createLiteral(literal.value),
|
||||
createLiteral(Math.abs(twoX)),
|
||||
])
|
||||
}
|
||||
callExpression.arguments[0] = createArrayExpression([
|
||||
createLiteral(literal.value),
|
||||
createLiteral(Math.abs(twoX)),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,11 @@ import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||
import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { Program } from 'lang/wasm'
|
||||
import { getNodeFromPath, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
isSingleCursorInPipe,
|
||||
} from 'lang/queryAst'
|
||||
import { CommandArgument } from './commandTypes'
|
||||
import {
|
||||
DefaultPlaneStr,
|
||||
|
@ -80,8 +80,7 @@ class MockEngineCommandManager {
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Node<Program>,
|
||||
pmo: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
path?: string
|
||||
pmo: ProgramMemory | Error = ProgramMemory.empty()
|
||||
): Promise<ExecState> {
|
||||
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
|
||||
|
||||
@ -91,7 +90,7 @@ export async function enginelessExecutor(
|
||||
}) as any as EngineCommandManager
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
mockEngineCommandManager.startNewSession()
|
||||
const execState = await executor(ast, mockEngineCommandManager, path, pmo)
|
||||
const execState = await executor(ast, mockEngineCommandManager, pmo)
|
||||
await mockEngineCommandManager.waitForAllCommands()
|
||||
return execState
|
||||
}
|
||||
|