Compare commits

..

9 Commits

Author SHA1 Message Date
ae068b7ed6 closer
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 17:02:37 -07:00
5bdc899831 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:25:08 -07:00
e0e6acf231 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:08:11 -07:00
668bc863df updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:06:56 -07:00
59ce602d6e updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:06:44 -07:00
8ce819b28f updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:58:08 -07:00
ed5e276377 working
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:47:38 -07:00
8549bd9486 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:41:03 -07:00
56f9078812 init
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:33:31 -07:00
68 changed files with 969 additions and 1169 deletions

2
.nvmrc
View File

@ -1 +1 @@
v21.7.1
v20.5.0

View File

@ -1,3 +1,3 @@
module.exports = {
presets: ['@babel/preset-env'],
presets: ["@babel/preset-env"],
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -104,7 +104,6 @@ test('Basic sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
@ -329,7 +328,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
})
test('if your kcl gets an error from the engine it is inlined', async ({
/* Ignore this test for now since its causing engine to crash
*
* test('if your kcl gets an error from the engine it is inlined', async ({
page,
}) => {
const u = getUtils(page)
@ -348,7 +349,7 @@ const sketch001 = startSketchOn(box, "revolveAxis")
|> startProfileAt([5, 10], %)
|> line([0, -10], %)
|> line([2, 0], %)
|> line([0, -10], %)
|> line([0, 10], %)
|> close(%)
|> revolve({
axis: getEdge('revolveAxis', box),
@ -363,7 +364,7 @@ angle: 90
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
@ -377,7 +378,7 @@ angle: 90
'sketch profile must lie entirely on one side of the revolution axis'
)
).toBeVisible()
})
})*/
test('executes on load', async ({ page }) => {
const u = getUtils(page)
@ -735,7 +736,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.openDebugPanel()
const xAxisClick = () =>
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
const emptySpaceClick = () =>
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
const topHorzSegmentClick = () =>
@ -760,7 +761,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
@ -768,14 +768,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
@ -788,14 +786,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByRole('button', { name: 'Line' }).click()
await u.closeDebugPanel()
const selectionSequence = async (isSecondTime = false) => {
const selectionSequence = async () => {
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(100)
await page.mouse.move(
startXPx + PUR * 15,
isSecondTime ? 430 : 500 - PUR * 10
)
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
@ -805,10 +799,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// check mousing off, than mousing onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(
startXPx + PUR * 10,
isSecondTime ? 295 : 500 - PUR * 20
) // mouse onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// now check clicking works including axis
@ -818,7 +809,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.keyboard.down('Shift')
const absYButton = page.getByRole('button', { name: 'ABS Y' })
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await absYButton.and(page.locator(':not([disabled])')).waitFor()
@ -827,12 +817,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// clear selection by clicking on nothing
await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.keyboard.up('Shift')
@ -845,7 +833,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await expect(absYButton).not.toBeDisabled()
@ -888,16 +875,11 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(100)
// enter sketch again
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings'
)
await page.waitForTimeout(150)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(300) // wait for animation
// hover again and check it works
await selectionSequence(true)
await selectionSequence()
})
test.describe('Command bar tests', () => {
@ -1083,7 +1065,6 @@ test('Can add multiple sketches', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
@ -1099,33 +1080,24 @@ test('Can add multiple sketches', async ({ page }) => {
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([100, 100, 100])
await page.waitForTimeout(250)
await u.updateCamPosition([0, 100, 100])
// start a new sketch
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(400)
await page.mouse.click(650, 450)
await page.waitForTimeout(100)
await page.mouse.click(673, 384)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await u.clearAndCloseDebugPanel()
// on mock os there are issues with getting the camera to update
// it should not be selecting the 'XZ' plane here if the camera updated
// properly, but if we just role with it we can still verify everything
// in the rest of the test
const plane = process.platform === 'darwin' ? 'XZ' : 'XY'
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 =
process.platform === 'darwin' ? '[9.75, -13.16]' : '[0.93, -1.25]'
const startAt2 = '[0.93,-1.25]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
@ -1134,12 +1106,12 @@ const part002 = startSketchOn('${plane}')
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = process.platform === 'darwin' ? 9.84 : 0.94
const num2 = 0.94
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)`.replace(/\s/g, '')
)
@ -1149,29 +1121,21 @@ const part002 = startSketchOn('${plane}')
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)
|> line([-${process.platform === 'darwin' ? 19.59 : 1.87}, 0], %)`.replace(
/\s/g,
''
)
|> line([0, ${roundOff(num2 - 0.01)}], %)
|> line([-1.87, 0], %)`.replace(/\s/g, '')
)
})
@ -1375,12 +1339,10 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(700, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(750, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
@ -1405,16 +1367,16 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const startPX = [665, 458]
const lineEndPX = [842, 458]
const arcEndPX = [971, 342]
const startPX = [652, 418]
const lineEndPX = [794, 416]
const arcEndPX = [893, 318]
const dragPX = 30
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await page.waitForTimeout(100)
let prevContent = await page.locator('.cm-content').innerText()
const step5 = { steps: 5 }
@ -1424,7 +1386,7 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
@ -1452,9 +1414,9 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([6.44, -12.07], %)
|> line([14.04, 2.03], %)
|> tangentialArcTo([27.19, -4.2], %)`)
|> startProfileAt([7.01, -11.79], %)
|> line([14.69, 2.73], %)
|> tangentialArcTo([27.6, -3.25], %)`)
})
const doSnapAtDifferentScales = async (
@ -1573,46 +1535,38 @@ test('Sketch on face', async ({ page }) => {
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
await u.openAndClearDebugPanel()
await u.doAndWaitForCmd(
() => page.mouse.click(793, 133),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.mouse.click(793, 133)
const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242]
const thirdClickPosition = [609, 267]
await page.waitForTimeout(300)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([2.87, -0.23], %)
|> line([-3.05, -1.47], %)
|> startProfileAt([1.03, 1.03], %)
|> line([4.18, -0.35], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
await u.openAndClearDebugPanel()
@ -1622,14 +1576,9 @@ test('Sketch on face', async ({ page }) => {
await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel()
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.setViewportSize({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166])
@ -1649,11 +1598,11 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-3.05, -1.47], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
// exit sketch
@ -1661,7 +1610,7 @@ test('Sketch on face', async ({ page }) => {
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
await page.getByRole('button', { name: 'Extrude' }).click()
@ -1675,11 +1624,11 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-3.05, -1.47], %)
|> line([-4.44, -2.13], %)
|> close(%)
|> extrude(5 + 7, %)`)
})
@ -1712,11 +1661,11 @@ test('Can code mod a line length', async ({ page }) => {
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(350) // wait for animation
await page.waitForTimeout(300) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 102)
await page.mouse.click(615, 133)
await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByText('Add constraining value').click()
@ -1724,42 +1673,3 @@ test('Can code mod a line length', async ({ page }) => {
`const length001 = 20const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> xLine(-length001, %)`
)
})
test('Extrude from command bar selects extrude line after', async ({
page,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> xLine(-20, %)
|> close(%)
`
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(100)
await page.keyboard.press('Enter')
await page.waitForTimeout(100)
await page.keyboard.press('Enter')
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toHaveText(
` |> extrude(5 + 7, %)`
)
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -6,7 +6,7 @@ import { PNG } from 'pngjs'
async function waitForPageLoad(page: Page) {
// wait for 'Loading stream...' spinner
// await page.getByTestId('loading-stream').waitFor()
await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })

View File

@ -57,7 +57,7 @@ echo "New version number without 'v': $new_version_number"
git checkout -b "cut-release-$new_version"
echo "$(jq --arg v "$new_version_number" '.version=$v' package.json --indent 2)" > package.json
echo "$(jq --arg v "$new_version_number" '.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
echo "$(jq --arg v "$new_version_number" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
git add package.json src-tauri/tauri.conf.json
git commit -m "Cut release $new_version"

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.18.1",
"version": "0.17.3",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.16.0",
@ -8,7 +8,7 @@
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.58",
"@lezer/javascript": "^1.4.9",
@ -84,8 +84,8 @@
"test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e",
"fmt": "prettier --write ./src && prettier --write ./e2e",
"fmt-check": "prettier --check ./src && prettier --check ./e2e",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
@ -132,7 +132,6 @@
"@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/web-worker": "^1.5.0",
"@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.36.0",
"@wdio/local-runner": "^8.36.0",

View File

@ -49,6 +49,8 @@ export default defineConfig({
// use: { ...devices['Desktop Chrome'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
@ -76,4 +78,4 @@ export default defineConfig({
// url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
})
})

View File

@ -55,5 +55,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.18.1"
"version": "0.17.3"
}

View File

@ -246,31 +246,13 @@ export class CameraControls {
camSettings.center.y,
camSettings.center.z
)
this.camera.up.set(camSettings.up.x, camSettings.up.y, camSettings.up.z)
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera()
}
if (this.camera instanceof OrthographicCamera && !camSettings.ortho) {
this.usePerspectiveCamera()
}
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
this.camera.fov = camSettings.fov_y
} else if (
this.camera instanceof OrthographicCamera &&
camSettings.ortho_scale
) {
const distanceToTarget = new Vector3(
camSettings.pos.x,
camSettings.pos.y,
camSettings.pos.z
).distanceTo(
new Vector3(
camSettings.center.x,
camSettings.center.y,
camSettings.center.z
)
)
this.camera.zoom = (camSettings.ortho_scale * 40) / distanceToTarget
this.camera.zoom = camSettings.ortho_scale
}
this.onCameraChange()
}
@ -983,10 +965,10 @@ export class CameraControls {
// Pure function helpers
function calculateNearFarFromFOV(fov: number) {
// const nearFarRatio = (fov - 3) / (45 - 3)
const nearFarRatio = (fov - 3) / (45 - 3)
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
// const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far: 1000 }
const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far }
}
function convertThreeCamValuesToEngineCam({
@ -1061,62 +1043,3 @@ function _getInteractionType(
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
return state
}
/**
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
* to ensure the client-side camera is synchronized with the engine's camera state.
*
* @param engineCommandManager Our websocket singleton
* @param entityId - The ID of face or sketchPlane.
*/
export async function letEngineAnimateAndSyncCamAfter(
engineCommandManager: EngineCommandManager,
entityId: string
) {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: !isReducedMotion(),
ortho: false,
entity_id: entityId,
},
})
// wait 600ms (animation takes 500, + 100 for safety)
await new Promise((resolve) =>
setTimeout(resolve, isReducedMotion() ? 100 : 600)
)
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: false,
ortho: true,
entity_id: entityId,
},
})
await new Promise((resolve) => setTimeout(resolve, 50))
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}

View File

@ -3,6 +3,7 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useStore } from 'useStore'
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls'
import { throttle } from 'lib/utils'
@ -46,6 +47,10 @@ export const ClientSideScene = ({
const canvasRef = useRef<HTMLDivElement>(null)
const { state, send, context } = useModelingContext()
const { hideClient, hideServer } = useShouldHideScene()
const { setHighlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
// Listen for changes to the camera controls setting
// and update the client-side scene's controls accordingly.
@ -64,6 +69,7 @@ export const ClientSideScene = ({
const canvas = canvasRef.current
canvas.appendChild(sceneInfra.renderer.domElement)
sceneInfra.animate()
sceneInfra.setHighlightCallback(setHighlightRange)
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)

View File

@ -57,7 +57,6 @@ import {
kclManager,
sceneInfra,
codeManager,
editorManager,
} from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst, useStore } from 'useStore'
@ -215,9 +214,8 @@ export class SceneEntities {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
const axisPixelWidth = 1.6
const xAxisGeometry = new BoxGeometry(100000, axisPixelWidth, 0.01)
const yAxisGeometry = new BoxGeometry(axisPixelWidth, 100000, 0.01)
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
const yAxisGeometry = new BoxGeometry(0.3, 100000, 0.01)
const xAxisMaterial = new MeshBasicMaterial({
color: baseXColor,
depthTest: false,
@ -1325,31 +1323,30 @@ export class SceneEntities {
selected.material.color = defaultPlaneColor(type)
},
onClick: async (args) => {
const checkExtrudeFaceClick = async (): Promise<
['face' | 'plane' | 'other', string]
> => {
const checkExtrudeFaceClick = async (): Promise<boolean> => {
const { streamDimensions } = useStore.getState()
const { entity_id } = await sendSelectEventToEngine(
args?.mouseEvent,
document.getElementById('video-stream') as HTMLVideoElement,
streamDimensions
)
if (!entity_id) return ['other', '']
if (
engineCommandManager.defaultPlanes?.xy === entity_id ||
engineCommandManager.defaultPlanes?.xz === entity_id ||
engineCommandManager.defaultPlanes?.yz === entity_id
) {
return ['plane', entity_id]
}
if (!entity_id) return false
const artifact = this.engineCommandManager.artifactMap[entity_id]
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
return ['other', entity_id]
const faceInfo = await getFaceDetails(entity_id)
return false
const faceInfo: Models['FaceIsPlanar_type'] = (
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: entity_id,
},
})
)?.data?.data
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return ['other', entity_id]
const { z_axis, y_axis, origin } = faceInfo
return false
const { z_axis, origin, y_axis } = faceInfo
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
@ -1369,15 +1366,12 @@ export class SceneEntities {
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
faceId: entity_id,
},
})
return ['face', entity_id]
return true
}
const faceResult = await checkExtrudeFaceClick()
console.log('faceResult', faceResult)
if (faceResult[0] === 'face') return
if (await checkExtrudeFaceClick()) return
if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
@ -1403,7 +1397,6 @@ export class SceneEntities {
plane: planeString,
zAxis,
yAxis,
planeId: faceResult[1],
},
})
},
@ -1430,7 +1423,7 @@ export class SceneEntities {
parent.userData.pathToNode,
'CallExpression'
).node
editorManager.setHighlightRange([node.start, node.end])
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -1466,10 +1459,10 @@ export class SceneEntities {
}
return
}
editorManager.setHighlightRange([0, 0])
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([0, 0])
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
@ -1687,7 +1680,7 @@ export async function getSketchOrientationDetails(
sketchPathToNode: PathToNode
): Promise<{
quat: Quaternion
sketchDetails: SketchDetails & { faceId?: string }
sketchDetails: SketchDetails
}> {
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
@ -1703,13 +1696,20 @@ export async function getSketchOrientationDetails(
zAxis: [zAxis.x, zAxis.y, zAxis.z],
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
origin: [0, 0, 0],
faceId: sketchGroup.on.id,
},
}
}
if (sketchGroup.on.type === 'face') {
const faceInfo = await getFaceDetails(sketchGroup.on.faceId)
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: sketchGroup.on.faceId,
},
})
)?.data?.data
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
throw new Error('faceInfo')
const { z_axis, y_axis, origin } = faceInfo
@ -1724,7 +1724,6 @@ export async function getSketchOrientationDetails(
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
origin: [origin.x, origin.y, origin.z],
faceId: sketchGroup.on.faceId,
},
}
}
@ -1733,46 +1732,6 @@ export async function getSketchOrientationDetails(
)
}
/**
* Retrieves orientation details for a given entity representing a face (brep face or default plane).
* This function asynchronously fetches and returns the origin, x-axis, y-axis, and z-axis details
* for a specified entity ID. It is primarily used to obtain the orientation of a face in the scene,
* which is essential for calculating the correct positioning and alignment of the client side sketch.
*
* @param entityId - The ID of the entity for which orientation details are being fetched.
* @returns A promise that resolves with the orientation details of the face.
*/
async function getFaceDetails(
entityId: string
): Promise<Models['FaceIsPlanar_type']> {
// TODO mode engine connection to allow batching returns and batch the following
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: false,
animated: false,
ortho: false,
entity_id: entityId,
},
})
// TODO change typing to get_sketch_mode_plane once lib is updated
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'get_sketch_mode_plane' },
})
)?.data?.data
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
return faceInfo
}
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
const dummyCam = new PerspectiveCamera()
dummyCam.up.set(0, 0, 1)

View File

@ -24,6 +24,7 @@ import {
import { compareVec2Epsilon2 } from 'lang/std/sketch'
import { useModelingContext } from 'hooks/useModelingContext'
import * as TWEEN from '@tweenjs/tween.js'
import { SourceRange } from 'lang/wasm'
import { Axis } from 'lib/selections'
import { type BaseUnit } from 'lib/settings/settingsTypes'
import { CameraControls } from './CameraControls'
@ -148,6 +149,10 @@ export class SceneInfra {
onMouseLeave: () => {},
})
}
highlightCallback: (a: SourceRange) => void = () => {}
setHighlightCallback(cb: (a: SourceRange) => void) {
this.highlightCallback = cb
}
modelingSend: SendType = (() => {}) as any
setSend(send: SendType) {

View File

@ -1,9 +1,11 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { editorManager, kclManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore'
export function AstExplorer() {
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { context } = useModelingContext()
const pathToNode = getNodePathFromSourceRange(
// TODO maybe need to have callback to make sure it stays in sync
@ -40,7 +42,7 @@ export function AstExplorer() {
<div
className="h-full relative"
onMouseLeave={(e) => {
editorManager.setHighlightRange([0, 0])
setHighlightRange([0, 0])
}}
>
<pre className="text-xs">
@ -86,6 +88,7 @@ function DisplayObj({
filterKeys: string[]
node: any
}) {
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { send } = useModelingContext()
const ref = useRef<HTMLPreElement>(null)
const [hasCursor, setHasCursor] = useState(false)
@ -109,12 +112,12 @@ function DisplayObj({
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
}`}
onMouseEnter={(e) => {
editorManager.setHighlightRange([obj?.start || 0, obj.end])
setHighlightRange([obj?.start || 0, obj.end])
e.stopPropagation()
}}
onMouseMove={(e) => {
e.stopPropagation()
editorManager.setHighlightRange([obj?.start || 0, obj.end])
setHighlightRange([obj?.start || 0, obj.end])
}}
onClick={(e) => {
send({

View File

@ -137,33 +137,34 @@ export function useCalc({
setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
useEffect(() => {
useEffect(async () => {
try {
const code = `const __result__ = ${value}`
const ast = parse(code)
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
executeAst({
ast,
engineCommandManager,
useFakeExecutor: true,
programMemoryOverride: JSON.parse(
JSON.stringify(kclManager.programMemory)
),
}).then(({ programMemory }) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
parse(code).then((ast) => {
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
executeAst({
ast,
engineCommandManager,
useFakeExecutor: true,
programMemoryOverride: JSON.parse(
JSON.stringify(kclManager.programMemory)
),
}).then(({ programMemory }) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})
})
} catch (e) {
setCalcResult('NAN')

View File

@ -1,7 +1,6 @@
import { useMachine } from '@xstate/react'
import { editorManager } from 'lib/singletons'
import { commandBarMachine } from 'machines/commandBarMachine'
import { createContext, useEffect } from 'react'
import { createContext } from 'react'
import { EventFrom, StateFrom } from 'xstate'
type CommandsContextType = {
@ -31,10 +30,6 @@ export const CommandBarProvider = ({
},
})
useEffect(() => {
editorManager.setCommandBarSend(commandBarSend)
})
return (
<CommandsContext.Provider
value={{

View File

@ -17,7 +17,6 @@ import {
sceneInfra,
engineCommandManager,
codeManager,
editorManager,
} from 'lib/singletons'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import {
@ -54,9 +53,10 @@ import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { CoreDumpManager } from 'lib/coredump'
import { useHotkeys } from 'react-hotkeys-hook'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -98,6 +98,17 @@ export const ModelingMachineProvider = ({
)
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
const {
isShiftDown,
editorView,
setLastCodeMirrorSelectionUpdatedFromScene,
} = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
setLastCodeMirrorSelectionUpdatedFromScene:
s.setLastCodeMirrorSelectionUpdatedFromScene,
}))
// Settings machine setup
// const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
@ -124,33 +135,29 @@ export const ModelingMachineProvider = ({
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (!editorManager.editorView) return {}
if (!editorView) return {}
const dispatchSelection = (selection?: EditorSelection) => {
if (!selection) return // TODO less of hack for the below please
editorManager.lastSelectionEvent = Date.now()
setTimeout(() => {
if (editorManager.editorView) {
editorManager.editorView.dispatch({ selection })
}
})
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
setTimeout(() => editorView.dispatch({ selection }))
}
let selections: Selections = {
codeBasedSelections: [],
otherSelections: [],
}
if (setSelections.selectionType === 'singleCodeCursor') {
if (!setSelections.selection && editorManager.isShiftDown) {
} else if (!setSelections.selection && !editorManager.isShiftDown) {
if (!setSelections.selection && isShiftDown) {
} else if (!setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [],
otherSelections: [],
}
} else if (setSelections.selection && !editorManager.isShiftDown) {
} else if (setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [setSelections.selection],
otherSelections: [],
}
} else if (setSelections.selection && editorManager.isShiftDown) {
} else if (setSelections.selection && isShiftDown) {
selections = {
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
@ -173,7 +180,6 @@ export const ModelingMachineProvider = ({
engineCommandManager.sendSceneCommand(event)
)
updateSceneObjectColors()
return {
selectionRanges: selections,
}
@ -186,7 +192,7 @@ export const ModelingMachineProvider = ({
}
if (setSelections.selectionType === 'otherSelection') {
if (editorManager.isShiftDown) {
if (isShiftDown) {
selections = {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections: [setSelections.selection],
@ -318,9 +324,16 @@ export const ModelingMachineProvider = ({
)
await kclManager.executeAstMock(modifiedAst)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
data.faceId
const forward = new Vector3(...data.zAxis)
const up = new Vector3(...data.yAxis)
let target = new Vector3(...data.position).multiplyScalar(
sceneInfra._baseUnitMultiplier
)
const quaternion = quaternionFromUpNForward(up, forward)
await sceneInfra.camControls.tweenCameraToQuaternion(
quaternion,
target
)
return {
@ -335,7 +348,6 @@ export const ModelingMachineProvider = ({
data.plane
)
await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.syncDirection = 'clientToEngine'
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
return {
@ -352,9 +364,9 @@ export const ModelingMachineProvider = ({
sourceRange
)
const info = await getSketchOrientationDetails(sketchPathToNode || [])
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
info?.sketchDetails?.faceId || ''
await sceneInfra.camControls.tweenCameraToQuaternion(
info.quat,
new Vector3(...info.sketchDetails.origin)
)
return {
sketchPathToNode: sketchPathToNode || [],
@ -504,19 +516,6 @@ export const ModelingMachineProvider = ({
})
}, [modelingSend])
// Give the state back to the editorManager.
useEffect(() => {
editorManager.modelingSend = modelingSend
}, [modelingSend])
useEffect(() => {
editorManager.modelingEvent = modelingState.event
}, [modelingState.event])
useEffect(() => {
editorManager.selectionRanges = modelingState.context.selectionRanges
}, [modelingState.context.selectionRanges])
useStateMachineCommands({
machineId: 'modeling',
state: modelingState,

View File

@ -1,8 +1,13 @@
import { undo, redo } from '@codemirror/commands'
import ReactCodeMirror from '@uiw/react-codemirror'
import { TEST } from 'env'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes, getSystemTheme } from 'lib/theme'
import { useEffect, useMemo } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useStore } from 'useStore'
import { processCodeMirrorRanges } from 'lib/selections'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
import { lineHighlightField } from 'editor/highlightextension'
import { roundOff } from 'lib/utils'
@ -24,7 +29,7 @@ import {
historyKeymap,
history,
} from '@codemirror/commands'
import { lintGutter, lintKeymap } from '@codemirror/lint'
import { lintGutter, lintKeymap, linter } from '@codemirror/lint'
import {
foldGutter,
foldKeymap,
@ -34,20 +39,25 @@ import {
syntaxHighlighting,
defaultHighlightStyle,
} from '@codemirror/language'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
import { kclManager, editorManager, codeManager } from 'lib/singletons'
import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { useHotkeys } from 'react-hotkeys-hook'
import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'lib/paths'
import makeUrlPathRelative from 'lib/makeUrlPathRelative'
import { useLspContext } from 'components/LspProvider'
import { Prec, EditorState, Extension } from '@codemirror/state'
import { Prec, EditorState, Extension, SelectionRange } from '@codemirror/state'
import {
closeBrackets,
closeBracketsKeymap,
completionKeymap,
hasNextSnippetField,
} from '@codemirror/autocomplete'
import { kclErrorsToDiagnostics } from 'lang/errors'
export const editorShortcutMeta = {
formatCode: {
@ -67,6 +77,13 @@ export const KclEditorPane = () => {
context.app.theme.current === Themes.System
? getSystemTheme()
: context.app.theme.current
const { editorView, setEditorView, isShiftDown } = useStore((s) => ({
editorView: s.editorView,
setEditorView: s.setEditorView,
isShiftDown: s.isShiftDown,
}))
const { editorCode, errors } = useKclContext()
const lastEvent = useRef({ event: '', time: Date.now() })
const { copilotLSP, kclLSP } = useLspContext()
const navigate = useNavigate()
@ -79,15 +96,90 @@ export const KclEditorPane = () => {
useHotkeys('mod+z', (e) => {
e.preventDefault()
editorManager.undo()
if (editorView) {
undo(editorView)
}
})
useHotkeys('mod+shift+z', (e) => {
e.preventDefault()
editorManager.redo()
if (editorView) {
redo(editorView)
}
})
const textWrapping = context.textEditor.textWrapping
const cursorBlinking = context.textEditor.blinkingCursor
const {
context: { selectionRanges },
send,
state,
} = useModelingContext()
const { settings } = useSettingsAuthContext()
const textWrapping = settings.context.textEditor.textWrapping
const cursorBlinking = settings.context.textEditor.blinkingCursor
const { commandBarSend } = useCommandsContext()
const { enable: convertEnabled, handleClick: convertCallback } =
useConvertToVariable()
const lastSelection = useRef('')
const onUpdate = (viewUpdate: ViewUpdate) => {
// If we are just fucking around in a snippet, return early and don't
// trigger stuff below that might cause the component to re-render.
// Otherwise we will not be able to tab thru the snippet portions.
// We explicitly dont check HasPrevSnippetField because we always add
// a ${} to the end of the function so that's fine.
if (hasNextSnippetField(viewUpdate.view.state)) {
return
}
if (!editorView) {
setEditorView(viewUpdate.view)
}
const selString = stringifyRanges(
viewUpdate?.state?.selection?.ranges || []
)
if (selString === lastSelection.current) {
// onUpdate is noisy and is fired a lot by extensions
// since we're only interested in selections changes we can ignore most of these.
return
}
lastSelection.current = selString
if (
// TODO find a less lazy way of getting the last
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
150
)
return // update triggered by scene selection
if (sceneInfra.selected) return // mid drag
const ignoreEvents: ModelingMachineEvent['type'][] = [
'Equip Line tool',
'Equip tangential arc to',
]
if (ignoreEvents.includes(state.event.type)) return
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges,
isShiftDown,
})
if (!eventInfo) return
const deterministicEventInfo = {
...eventInfo,
engineEvents: eventInfo.engineEvents.map((e) => ({
...e,
cmd_id: 'static',
})),
}
const stringEvent = JSON.stringify(deterministicEventInfo)
if (
stringEvent === lastEvent.current.event &&
Date.now() - lastEvent.current.time < 500
)
return // don't repeat events
lastEvent.current = { event: stringEvent, time: Date.now() }
send(eventInfo.modelingEvent)
eventInfo.engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
}
const editorExtensions = useMemo(() => {
const extensions = [
@ -110,7 +202,7 @@ export const KclEditorPane = () => {
{
key: 'Meta-k',
run: () => {
editorManager.commandBarSend({ type: 'Open' })
commandBarSend({ type: 'Open' })
return false
},
},
@ -124,7 +216,11 @@ export const KclEditorPane = () => {
{
key: editorShortcutMeta.convertToVariable.codeMirror,
run: () => {
return editorManager.convertToVariable()
if (convertEnabled) {
convertCallback()
return true
}
return false
},
},
]),
@ -137,6 +233,9 @@ export const KclEditorPane = () => {
if (!TEST) {
extensions.push(
lintGutter(),
linter((_view: EditorView) => {
return kclErrorsToDiagnostics(errors)
}),
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
@ -189,10 +288,13 @@ export const KclEditorPane = () => {
}
return extensions
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
let debounceTimer: ReturnType<typeof setTimeout> | null = null
const updateDelay = 100
}, [
kclLSP,
copilotLSP,
textWrapping.current,
cursorBlinking.current,
convertCallback,
])
return (
<div
@ -200,26 +302,18 @@ export const KclEditorPane = () => {
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
>
<ReactCodeMirror
value={codeManager.code}
value={editorCode}
extensions={editorExtensions}
onUpdate={onUpdate}
theme={theme}
onCreateEditor={(_editorView) =>
editorManager.setEditorView(_editorView)
}
onUpdate={(view: ViewUpdate) => {
// debounce the view update.
// otherwise it is laggy for typing.
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
editorManager.handleOnViewUpdate(view)
}, updateDelay)
}}
onCreateEditor={(_editorView) => setEditorView(_editorView)}
indentWithTab={false}
basicSetup={false}
/>
</div>
)
}
function stringifyRanges(ranges: readonly SelectionRange[]): string {
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
}

View File

@ -2,9 +2,7 @@ import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers'
import { initPromise, parse } from '../../../lang/wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('processMemory', () => {
it('should grab the values and remove and geo data', async () => {
@ -28,7 +26,7 @@ describe('processMemory', () => {
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)`
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast, {
root: {},
return: null,

View File

@ -1,238 +0,0 @@
import { hasNextSnippetField } from '@codemirror/autocomplete'
import { EditorView, ViewUpdate } from '@codemirror/view'
import { EditorSelection, SelectionRange } from '@codemirror/state'
import { engineCommandManager, sceneInfra } from 'lib/singletons'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
import { undo, redo } from '@codemirror/commands'
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
import { addLineHighlight } from './highlightextension'
import { setDiagnostics, Diagnostic } from '@codemirror/lint'
export default class EditorManager {
private _editorView: EditorView | null = null
private _isShiftDown: boolean = false
private _selectionRanges: Selections = {
otherSelections: [],
codeBasedSelections: [],
}
private _lastSelectionEvent: number | null = null
private _lastSelection: string = ''
private _lastEvent: { event: string; time: number } | null = null
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
private _modelingEvent: ModelingMachineEvent | null = null
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
() => {}
private _convertToVariableEnabled: boolean = false
private _convertToVariableCallback: () => void = () => {}
private _highlightRange: [number, number] = [0, 0]
setEditorView(editorView: EditorView) {
this._editorView = editorView
}
get editorView(): EditorView | null {
return this._editorView
}
get isShiftDown(): boolean {
return this._isShiftDown
}
setIsShiftDown(isShiftDown: boolean) {
this._isShiftDown = isShiftDown
}
set selectionRanges(selectionRanges: Selections) {
this._selectionRanges = selectionRanges
}
set lastSelectionEvent(time: number) {
this._lastSelectionEvent = time
}
set modelingSend(send: (eventInfo: ModelingMachineEvent) => void) {
this._modelingSend = send
}
set modelingEvent(event: ModelingMachineEvent) {
this._modelingEvent = event
}
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
this._commandBarSend = send
}
commandBarSend(eventInfo: CommandBarMachineEvent): void {
return this._commandBarSend(eventInfo)
}
get highlightRange(): [number, number] {
return this._highlightRange
}
setHighlightRange(selection: Selection['range']): void {
this._highlightRange = selection
const editorView = this.editorView
const safeEnd = Math.min(
selection[1],
editorView?.state.doc.length || selection[1]
)
if (editorView) {
editorView.dispatch({
effects: addLineHighlight.of([selection[0], safeEnd]),
})
}
}
setDiagnostics(diagnostics: Diagnostic[]): void {
if (!this.editorView) return
this.editorView.dispatch(setDiagnostics(this.editorView.state, diagnostics))
}
undo() {
if (this._editorView) {
undo(this._editorView)
}
}
redo() {
if (this._editorView) {
redo(this._editorView)
}
}
set convertToVariableEnabled(enabled: boolean) {
this._convertToVariableEnabled = enabled
}
set convertToVariableCallback(callback: () => void) {
this._convertToVariableCallback = callback
}
convertToVariable() {
if (this._convertToVariableEnabled) {
this._convertToVariableCallback()
return true
}
return false
}
selectRange(selections: Selections) {
if (selections.codeBasedSelections.length === 0) {
return
}
if (!this.editorView) {
return
}
let codeBasedSelections = []
for (const selection of selections.codeBasedSelections) {
codeBasedSelections.push(
EditorSelection.range(selection.range[0], selection.range[1])
)
}
codeBasedSelections.push(
EditorSelection.cursor(
selections.codeBasedSelections[
selections.codeBasedSelections.length - 1
].range[1]
)
)
this.editorView.dispatch({
selection: EditorSelection.create(codeBasedSelections, 1),
})
}
handleOnViewUpdate(viewUpdate: ViewUpdate): void {
// If we are just fucking around in a snippet, return early and don't
// trigger stuff below that might cause the component to re-render.
// Otherwise we will not be able to tab thru the snippet portions.
// We explicitly dont check HasPrevSnippetField because we always add
// a ${} to the end of the function so that's fine.
if (hasNextSnippetField(viewUpdate.view.state)) {
return
}
if (this.editorView === null) {
this.setEditorView(viewUpdate.view)
}
const selString = stringifyRanges(
viewUpdate?.state?.selection?.ranges || []
)
if (selString === this._lastSelection) {
// onUpdate is noisy and is fired a lot by extensions
// since we're only interested in selections changes we can ignore most of these.
return
}
this._lastSelection = selString
if (
this._lastSelectionEvent &&
Date.now() - this._lastSelectionEvent < 150
) {
return // update triggered by scene selection
}
if (sceneInfra.selected) {
return // mid drag
}
const ignoreEvents: ModelingMachineEvent['type'][] = [
'Equip Line tool',
'Equip tangential arc to',
]
if (!this._modelingEvent) {
return
}
if (ignoreEvents.includes(this._modelingEvent.type)) {
return
}
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges: this._selectionRanges,
isShiftDown: this._isShiftDown,
})
if (!eventInfo) {
return
}
const deterministicEventInfo = {
...eventInfo,
engineEvents: eventInfo.engineEvents.map((e) => ({
...e,
cmd_id: 'static',
})),
}
const stringEvent = JSON.stringify(deterministicEventInfo)
if (
this._lastEvent &&
stringEvent === this._lastEvent.event &&
Date.now() - this._lastEvent.time < 500
) {
return // don't repeat events
}
this._lastEvent = { event: stringEvent, time: Date.now() }
this._modelingSend(eventInfo.modelingEvent)
eventInfo.engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
}
}
function stringifyRanges(ranges: readonly SelectionRange[]): string {
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
}

View File

@ -21,7 +21,7 @@ import { LanguageServerClient } from 'editor/plugins/lsp'
import { Marked } from '@ts-stack/markdown'
import { posToOffset } from 'editor/plugins/lsp/util'
import { Program, ProgramMemory } from 'lang/wasm'
import { codeManager, editorManager, kclManager } from 'lib/singletons'
import { codeManager, kclManager } from 'lib/singletons'
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
@ -57,9 +57,6 @@ export class LanguageServerPlugin implements PluginValue {
},
contentChanges: [{ text: code }],
})
if (editorManager.editorView) {
//editorManager.handleOnViewUpdate(editorManager.editorView)
}
} catch (e) {
console.error(e)
}
@ -360,9 +357,15 @@ export class LanguageServerPlugin implements PluginValue {
try {
switch (notification.method) {
case 'textDocument/publishDiagnostics':
//const params = notification.params as PublishDiagnosticsParams
// this is sometimes slower than our actual typing.
//this.processDiagnostics(params)
const params = notification.params as PublishDiagnosticsParams
this.processDiagnostics(params)
// Update the kcl errors pane.
/*if (!kclManager.isExecuting) {
kclManager.kclErrors = lspDiagnosticsToKclErrors(
this.view.state.doc,
params.diagnostics
)
}*/
break
case 'window/logMessage':
console.log(
@ -382,6 +385,17 @@ export class LanguageServerPlugin implements PluginValue {
// The server has updated the AST, we should update elsewhere.
let updatedAst = notification.params as Program
console.log('[lsp]: Updated AST', updatedAst)
// Update the ast when we are not already executing.
/* if (!kclManager.isExecuting) {
kclManager.ast = updatedAst
// Execute the ast.
console.log('[lsp]: executing ast')
await kclManager.executeAst(updatedAst)
console.log('[lsp]: executed ast', kclManager.kclErrors)
let diagnostics = kclErrorsToDiagnostics(kclManager.kclErrors)
this.view.dispatch(setDiagnostics(this.view.state, diagnostics))
console.log('[lsp]: updated diagnostics')
}*/
// Update the folding ranges, since the AST has changed.
// This is a hack since codemirror does not support async foldService.

View File

@ -1,9 +1,14 @@
import { useEffect } from 'react'
import { editorManager, engineCommandManager } from 'lib/singletons'
import { useStore } from 'useStore'
import { engineCommandManager } from 'lib/singletons'
import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'
export function useEngineConnectionSubscriptions() {
const { setHighlightRange, highlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
const { send, context } = useModelingContext()
useEffect(() => {
@ -16,13 +21,12 @@ export function useEngineConnectionSubscriptions() {
if (data?.entity_id) {
const sourceRange =
engineCommandManager.artifactMap?.[data.entity_id]?.range
editorManager.setHighlightRange(sourceRange)
setHighlightRange(sourceRange)
} else if (
!editorManager.highlightRange ||
(editorManager.highlightRange[0] !== 0 &&
editorManager.highlightRange[1] !== 0)
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
editorManager.setHighlightRange([0, 0])
setHighlightRange([0, 0])
}
},
})
@ -39,5 +43,10 @@ export function useEngineConnectionSubscriptions() {
unSubHover()
unSubClick()
}
}, [engineCommandManager, context?.sketchEnginePathId])
}, [
engineCommandManager,
setHighlightRange,
highlightRange,
context?.sketchEnginePathId,
])
}

View File

@ -1,4 +1,4 @@
import { editorManager } from 'lib/singletons'
import { useStore } from '../useStore'
import { useEffect } from 'react'
// Kurt's note: codeMirror styling overrides were needed to make this work
@ -6,17 +6,20 @@ import { useEffect } from 'react'
// search for code-mirror-override in the repo to find the relevant styles
export function useHotKeyListener() {
const { setIsShiftDown } = useStore((s) => ({
setIsShiftDown: s.setIsShiftDown,
}))
const keyName = 'Shift'
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) =>
event.key === keyName && editorManager.setIsShiftDown(true)
event.key === keyName && setIsShiftDown(true)
const handleKeyUp = (event: KeyboardEvent) =>
event.key === keyName && editorManager.setIsShiftDown(false)
event.key === keyName && setIsShiftDown(false)
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
return () => {
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
}
})
}, [setIsShiftDown])
}

View File

@ -2,7 +2,7 @@ import {
SetVarNameModal,
createSetVarNameModal,
} from 'components/SetVarNameModal'
import { editorManager, kclManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react'
@ -13,11 +13,6 @@ const getModalInfo = createSetVarNameModal(SetVarNameModal)
export function useConvertToVariable() {
const { context } = useModelingContext()
const [enable, setEnabled] = useState(false)
useEffect(() => {
editorManager.convertToVariableEnabled = enable
}, [enable])
useEffect(() => {
const { isSafe, value } = isNodeSafeToReplace(
kclManager.ast,
@ -50,7 +45,5 @@ export function useConvertToVariable() {
}
}
editorManager.convertToVariableCallback = handleClick
return { enable, handleClick }
}

View File

@ -2,10 +2,12 @@ import { KCLError } from './errors'
import { createContext, useContext, useEffect, useState } from 'react'
import { type IndexLoaderData } from 'lib/types'
import { useLoaderData } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons'
const KclContext = createContext({
code: codeManager?.code || '',
editorCode: codeManager?.code || '',
programMemory: kclManager?.programMemory,
ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting,
@ -28,6 +30,7 @@ export function KclContextProvider({
const { code: loadedCode } = useLoaderData() as IndexLoaderData
// Both the code state and the editor state start off with the same code.
const [code, setCode] = useState(loadedCode || codeManager.code)
const [editorCode, setEditorCode] = useState(code)
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
@ -39,6 +42,7 @@ export function KclContextProvider({
useEffect(() => {
codeManager.registerCallBacks({
setCode,
setEditorCode,
})
kclManager.registerCallBacks({
setProgramMemory,
@ -50,10 +54,15 @@ export function KclContextProvider({
})
}, [])
const params = useParams()
useEffect(() => {
codeManager.setParams(params)
}, [params])
return (
<KclContext.Provider
value={{
code,
editorCode,
programMemory,
ast,
isExecuting,

View File

@ -1,6 +1,6 @@
import { executeAst } from 'useStore'
import { Selections } from 'lib/selections'
import { KCLError, kclErrorsToDiagnostics } from './errors'
import { KCLError } from './errors'
import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection'
@ -17,7 +17,7 @@ import {
ExtrudeGroup,
} from 'lang/wasm'
import { getNodeFromPath } from './queryAst'
import { codeManager, editorManager } from 'lib/singletons'
import { codeManager } from 'lib/singletons'
export class KclManager {
private _ast: Program = {
@ -43,14 +43,16 @@ export class KclManager {
const ast = this.safeParse(code)
if (!ast) return
try {
parse(recast(ast)).then((newAst) => {
const fmtAndStringify = (ast: Program) =>
JSON.stringify(parse(recast(ast)))
JSON.stringify(newAst)
const isAstTheSame = fmtAndStringify(ast) === fmtAndStringify(this._ast)
if (isAstTheSame) return
this.executeAst(ast)
})
} catch (e) {
console.error(e)
}
this.executeAst(ast)
}, 600)
private _isExecutingCallback: (arg: boolean) => void = () => {}
@ -90,8 +92,6 @@ export class KclManager {
}
set kclErrors(kclErrors) {
this._kclErrors = kclErrors
let diagnostics = kclErrorsToDiagnostics(kclErrors)
editorManager.setDiagnostics(diagnostics)
this._kclErrorsCallBack(kclErrors)
}
@ -145,9 +145,9 @@ export class KclManager {
this._executeCallback = callback
}
safeParse(code: string): Program | null {
async safeParse(code: string): Promise<Program | null> {
try {
const ast = parse(code)
const ast = await parse(code)
this.kclErrors = []
return ast
} catch (e) {
@ -347,16 +347,6 @@ export class KclManager {
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
enterEditMode() {
enterEditMode(this.programMemory, this.engineCommandManager)
}
exitEditMode() {
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
}
}
function enterEditMode(

View File

@ -1,13 +1,11 @@
import { KCLError } from './errors'
import { initPromise, parse } from './wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing AST', () => {
test('5 + 6', () => {
const result = parse('5 +6')
test('5 + 6', async () => {
const result = await parse('5 +6')
delete (result as any).nonCodeMeta
expect(result.body).toEqual([
{
@ -37,8 +35,8 @@ describe('testing AST', () => {
},
])
})
test('const myVar = 5', () => {
const { body } = parse('const myVar = 5')
test('const myVar = 5', async () => {
const { body } = await parse('const myVar = 5')
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -68,11 +66,11 @@ describe('testing AST', () => {
},
])
})
test('multi-line', () => {
test('multi-line', async () => {
const code = `const myVar = 5
const newVar = myVar + 1
`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -143,8 +141,8 @@ const newVar = myVar + 1
})
describe('testing function declaration', () => {
test('fn funcN = (a, b) => {return a + b}', () => {
const { body } = parse(
test('fn funcN = (a, b) => {return a + b}', async () => {
const { body } = await parse(
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
@ -226,10 +224,10 @@ describe('testing function declaration', () => {
},
])
})
test('call expression assignment', () => {
test('call expression assignment', async () => {
const code = `fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)`
const { body } = parse(code)
const { body } = await parse(code)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
expect(body).toEqual([
{
@ -359,14 +357,14 @@ const myVar = funcN(1, 2)`
})
describe('testing pipe operator special', () => {
test('pipe operator with sketch', () => {
test('pipe operator with sketch', async () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo([2, 3], %)
|> lineTo([0, 1], %, "myPath")
|> lineTo([1, 1], %)
|> rx(45, %)
`
const { body } = parse(code)
const { body } = await parse(code)
delete (body[0] as any).declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -564,9 +562,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('pipe operator with binary expression', () => {
test('pipe operator with binary expression', async () => {
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
const { body } = parse(code)
const { body } = await parse(code)
delete (body as any)[0].declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -643,9 +641,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('array expression', () => {
test('array expression', async () => {
let code = `const yo = [1, '2', three, 4 + 5]`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -715,12 +713,12 @@ describe('testing pipe operator special', () => {
},
])
})
test('object expression ast', () => {
test('object expression ast', async () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -860,11 +858,11 @@ describe('testing pipe operator special', () => {
},
])
})
test('nested object expression ast', () => {
test('nested object expression ast', async () => {
const code = `const yo = {key: {
key2: 'value'
}}`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -930,9 +928,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object expression with array ast', () => {
test('object expression with array ast', async () => {
const code = `const yo = {key: [1, '2']}`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -994,9 +992,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object memberExpression simple', () => {
test('object memberExpression simple', async () => {
const code = `const prop = yo.one.two`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1049,9 +1047,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object memberExpression with square braces', () => {
test('object memberExpression with square braces', async () => {
const code = `const prop = yo.one["two"]`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1105,9 +1103,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object memberExpression with two square braces literal and identifier', () => {
test('object memberExpression with two square braces literal and identifier', async () => {
const code = `const prop = yo["one"][two]`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1164,9 +1162,9 @@ describe('testing pipe operator special', () => {
})
describe('nests binary expressions correctly', () => {
it('works with the simple case', () => {
it('works with the simple case', async () => {
const code = `const yo = 1 + 2`
const { body } = parse(code)
const { body } = await parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1207,10 +1205,10 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest according to precedence with multiply first', () => {
it('should nest according to precedence with multiply first', async () => {
// should be binExp { binExp { lit-1 * lit-2 } + lit}
const code = `const yo = 1 * 2 + 3`
const { body } = parse(code)
const { body } = await parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1264,10 +1262,10 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest according to precedence with sum first', () => {
it('should nest according to precedence with sum first', async () => {
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
const code = `const yo = 1 + 2 * 3`
const { body } = parse(code)
const { body } = await parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1321,9 +1319,9 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest properly with two operators of equal precedence', () => {
it('should nest properly with two operators of equal precedence', async () => {
const code = `const yo = 1 + 2 - 3`
const { body } = parse(code)
const { body } = await parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1358,9 +1356,9 @@ describe('nests binary expressions correctly', () => {
},
})
})
it('should nest properly with two operators of equal (but higher) precedence', () => {
it('should nest properly with two operators of equal (but higher) precedence', async () => {
const code = `const yo = 1 * 2 / 3`
const { body } = parse(code)
const { body } = await parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1395,9 +1393,9 @@ describe('nests binary expressions correctly', () => {
},
})
})
it('should nest properly with longer example', () => {
it('should nest properly with longer example', async () => {
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
const { body } = parse(code)
const { body } = await parse(code)
const init = (body[0] as any).declarations[0].init
expect(init).toEqual({
type: 'BinaryExpression',
@ -1445,7 +1443,7 @@ describe('nests binary expressions correctly', () => {
})
describe('check nonCodeMeta data is attached to the AST correctly', () => {
it('comments between expressions', () => {
it('comments between expressions', async () => {
const code = `
const yo = { a: { b: { c: '123' } } }
// this is a comment
@ -1460,12 +1458,14 @@ const key = 'c'`
value: 'this is a comment',
},
}
const { nonCodeMeta } = parse(code)
const { nonCodeMeta } = await parse(code)
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
const codeWithExtraStartWhitespace = '\n\n\n' + code
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
const { nonCodeMeta: nonCodeMeta2 } = await parse(
codeWithExtraStartWhitespace
)
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
nonCodeMetaInstance.value
)
@ -1473,7 +1473,7 @@ const key = 'c'`
nonCodeMetaInstance.start
)
})
it('comments nested within a block statement', () => {
it('comments nested within a block statement', async () => {
const code = `const mySketch = startSketchAt([0,0])
|> lineTo([0, 1], %, 'myPath')
|> lineTo([1, 1], %) /* this is
@ -1483,7 +1483,7 @@ const key = 'c'`
|> close(%)
`
const { body } = parse(code)
const { body } = await parse(code)
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.nonCodeNodes
@ -1498,7 +1498,7 @@ const key = 'c'`
},
})
})
it('comments in a pipe expression', () => {
it('comments in a pipe expression', async () => {
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
@ -1508,7 +1508,7 @@ const key = 'c'`
' |> rx(90, %)',
].join('\n')
const { body } = parse(code)
const { body } = await parse(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.nonCodeNodes[3][0]
expect(sketchNonCodeMeta).toEqual({
@ -1525,9 +1525,9 @@ const key = 'c'`
})
describe('test UnaryExpression', () => {
it('should parse a unary expression in simple var dec situation', () => {
it('should parse a unary expression in simple var dec situation', async () => {
const code = `const myVar = -min(4, 100)`
const { body } = parse(code)
const { body } = await parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'UnaryExpression',
@ -1550,9 +1550,9 @@ describe('test UnaryExpression', () => {
})
describe('testing nested call expressions', () => {
it('callExp in a binExp in a callExp', () => {
it('callExp in a binExp in a callExp', async () => {
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
const { body } = parse(code)
const { body } = await parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'CallExpression',
@ -1587,8 +1587,8 @@ describe('testing nested call expressions', () => {
describe('should recognise callExpresions in binaryExpressions', () => {
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
it('should recognise the callExp', () => {
const { body } = parse(code)
it('should recognise the callExp', async () => {
const { body } = await parse(code)
const callExpArgs = (body?.[0] as any).expression?.arguments
expect(callExpArgs).toEqual([
{

View File

@ -1,9 +1,7 @@
import { parse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing artifacts', () => {
// Enable rotations #152
@ -14,7 +12,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)`
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
// @ts-ignore
const sketch001 = programMemory?.root?.mySketch001
expect(sketch001).toEqual({
@ -70,7 +68,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)`
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
// @ts-ignore
const sketch001 = programMemory?.root?.mySketch001
expect(sketch001).toEqual({
@ -152,7 +150,7 @@ const sk2 = startSketchOn('XY')
|> extrude(2, %)
`
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
// @ts-ignore
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
expect(geos).toEqual([

View File

@ -5,7 +5,7 @@ import { bracket } from 'lib/exampleKcl'
import { isTauri } from 'lib/isTauri'
import { writeTextFile } from '@tauri-apps/plugin-fs'
import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons'
import { Params } from 'react-router-dom'
const PERSIST_CODE_TOKEN = 'persistCode'
@ -13,7 +13,7 @@ export default class CodeManager {
private _code: string = bracket
private _updateState: (arg: string) => void = () => {}
private _updateEditor: (arg: string) => void = () => {}
private _currentFilePath: string | null = null
private _params: Params<string> = {}
constructor() {
if (isTauri()) {
@ -45,12 +45,19 @@ export default class CodeManager {
return this._code
}
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
registerCallBacks({
setCode,
setEditorCode,
}: {
setCode: (arg: string) => void
setEditorCode: (arg: string) => void
}) {
this._updateState = setCode
this._updateEditor = setEditorCode
}
updateCurrentFilePath(path: string) {
this._currentFilePath = path
setParams(params: Params<string>) {
this._params = params
}
// This updates the code state and calls the updateState function.
@ -63,14 +70,11 @@ export default class CodeManager {
// Update the code in the editor.
updateCodeEditor(code: string): void {
const lastCode = this._code
this.code = code
this._updateEditor(code)
if (editorManager.editorView) {
editorManager.editorView.dispatch({
changes: { from: 0, to: lastCode.length, insert: code },
})
if (this._code !== code) {
this.code = code
this._updateEditor(code)
}
this._updateEditor(code)
}
// Update the code, state, and the code the code mirror editor sees.
@ -87,8 +91,8 @@ export default class CodeManager {
setTimeout(() => {
// Wait one event loop to give a chance for params to be set
// Save the file to disk
this._currentFilePath &&
writeTextFile(this._currentFilePath, this.code).catch((err) => {
this._params.id &&
writeTextFile(this._params.id, this.code).catch((err) => {
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')

View File

@ -4,9 +4,7 @@ import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { KCLError } from './errors'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('test executor', () => {
it('test assigning two variables, the second summing with the first', async () => {
@ -400,7 +398,7 @@ async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, return: null }
) {
const ast = parse(code)
const ast = await parse(code)
const result = await enginelessExecutor(ast, programMemory)
return result

View File

@ -1,12 +1,10 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { Identifier, parse, initPromise, Parameter } from './wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing getNodePathFromSourceRange', () => {
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', () => {
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', async () => {
const code = `
const myVar = 5
const sk3 = startSketchAt([0, 0])
@ -21,14 +19,14 @@ const sk3 = startSketchAt([0, 0])
lineToSubstringIndex + subStr.length,
]
const ast = parse(code)
const ast = await parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const { node } = getNodeFromPath<any>(ast, nodePath)
expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
it('gets path right for function definition params', async () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
@ -46,7 +44,7 @@ const b1 = cube([0,0], 10)`
subStrIndex + 'pos'.length,
]
const ast = parse(code)
const ast = await parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Parameter>(ast, nodePath).node
@ -62,7 +60,7 @@ const b1 = cube([0,0], 10)`
expect(node.type).toBe('Parameter')
expect(node.identifier.name).toBe('pos')
})
it('gets path right for deep within function definition body', () => {
it('gets path right for deep within function definition body', async () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
@ -80,7 +78,7 @@ const b1 = cube([0,0], 10)`
subStrIndex + 'scale'.length,
]
const ast = parse(code)
const ast = await parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([

View File

@ -17,9 +17,7 @@ import {
import { enginelessExecutor } from '../lib/testHelpers'
import { getNodePathFromSourceRange } from './queryAst'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('Testing createLiteral', () => {
it('should create a literal', () => {

81
src/lang/parser.ts Normal file
View File

@ -0,0 +1,81 @@
import {
ParserWorkerResponse,
WasmWorker,
WasmWorkerEventType,
ParserWorkerCallResponse,
} from 'lang/workers/types'
import Worker from 'lang/workers/parser?worker'
import { Program, wasmUrl } from 'lang/wasm'
import { KCLError } from 'lang/errors'
import { v4 as uuidv4 } from 'uuid'
export default class Parser {
worker: any = new Worker({ name: 'parse' })
intitalized: boolean = false
mappings: Map<string, Program | KCLError> = new Map()
async parse(code: string): Promise<Program> {
this.ensureWorker()
const uuid = uuidv4()
this.worker.postMessage({
worker: WasmWorker.Parser,
eventType: WasmWorkerEventType.Call,
eventData: {
uuid,
code,
},
})
let result = await this.waitForResult(uuid)
if (result instanceof KCLError) {
throw result
}
return result
}
waitForResult(uuid: string): Promise<Program | KCLError> {
return new Promise((resolve) => {
const result = this.mappings.get(uuid)
if (result) {
this.mappings.delete(uuid)
resolve(result)
} else {
setTimeout(() => {
resolve(this.waitForResult(uuid))
}, 100)
}
})
}
ensureWorker() {
if (!this.intitalized) {
this.start()
}
}
// Start the worker.
start() {
if (this.intitalized) {
console.log('Worker already initialized')
return
}
this.worker.postMessage({
worker: WasmWorker.Parser,
eventType: WasmWorkerEventType.Init,
eventData: {
wasmUrl: wasmUrl(),
},
})
this.worker.onmessage = function (r: ParserWorkerResponse) {
switch (r.eventType) {
case WasmWorkerEventType.Init:
this.intitalized = true
break
case WasmWorkerEventType.Call:
const c = r.response as ParserWorkerCallResponse
this.mappings.set(c.uuid, c.response)
break
}
}
}
}

View File

@ -15,9 +15,7 @@ import {
createPipeSubstitution,
} from './modifyAst'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('findAllPreviousVariables', () => {
it('should find all previous variables', async () => {

View File

@ -1,58 +1,56 @@
import { parse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('recast', () => {
it('recasts a simple program', () => {
it('recasts a simple program', async () => {
const code = '1 + 2'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('variable declaration', () => {
it('variable declaration', async () => {
const code = 'const myVar = 5'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it("variable declaration that's binary with string", () => {
it("variable declaration that's binary with string", async () => {
const code = "const myVar = 5 + 'yo'"
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
const { ast: ast2 } = code2ast(codeWithOtherQuotes)
const { ast: ast2 } = await code2ast(codeWithOtherQuotes)
expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
})
it('test assigning two variables, the second summing with the first', () => {
it('test assigning two variables, the second summing with the first', async () => {
const code = `const myVar = 5
const newVar = myVar + 1
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('test assigning a var by cont concatenating two strings string', () => {
it('test assigning a var by cont concatenating two strings string', async () => {
const code = fs.readFileSync(
'./src/lang/testExamples/variableDeclaration.cado',
'utf-8'
)
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('test with function call', () => {
it('test with function call', async () => {
const code = `const myVar = "hello"
log(5, myVar)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('function declaration with call', () => {
it('function declaration with call', async () => {
const code = [
'fn funcN = (a, b) => {',
' return a + b',
@ -60,22 +58,22 @@ log(5, myVar)
'const theVar = 60',
'const magicNum = funcN(9, theVar)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('recast sketch declaration', () => {
it('recast sketch declaration', async () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo([0, 1], %, "myPath")
|> lineTo([1, 1], %)
|> lineTo([1, 0], %, "rightPath")
|> close(%)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('sketch piped into callExpression', () => {
it('sketch piped into callExpression', async () => {
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
@ -83,11 +81,11 @@ log(5, myVar)
' |> lineTo([1, 1], %)',
' |> rx(90, %)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast BinaryExpression piped into CallExpression', () => {
it('recast BinaryExpression piped into CallExpression', async () => {
const code = [
'fn myFn = (a) => {',
' return a + 1',
@ -95,49 +93,49 @@ log(5, myVar)
'const myVar = 5 + 1',
' |> myFn(%)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('recast nested binary expression', () => {
it('recast nested binary expression', async () => {
const code = ['const myVar = 1 + 2 * 5'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast nested binary expression with parans', () => {
it('recast nested binary expression with parans', async () => {
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('unnecessary paran wrap will be remove', () => {
it('unnecessary paran wrap will be remove', async () => {
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
})
it('complex nested binary expression', () => {
it('complex nested binary expression', async () => {
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('multiplied paren expressions', () => {
it('multiplied paren expressions', async () => {
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast array declaration', () => {
it('recast array declaration', async () => {
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
'\n'
)
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast long array declaration', () => {
it('recast long array declaration', async () => {
const code = [
'const three = 3',
'const yo = [',
@ -148,11 +146,11 @@ log(5, myVar)
" 'hey oooooo really long long long'",
']',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast long object execution', () => {
it('recast long object execution', async () => {
const code = `const three = 3
const yo = {
aStr: 'str',
@ -161,43 +159,43 @@ const yo = {
binExp: 4 + 5
}
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('recast short object execution', () => {
it('recast short object execution', async () => {
const code = `const yo = { key: 'val' }
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('recast object execution with member expression', () => {
it('recast object execution with member expression', async () => {
const code = `const yo = { a: { b: { c: '123' } } }
const key = 'c'
const myVar = yo.a['b'][key]
const key2 = 'b'
const myVar2 = yo['a'][key2].c
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
})
describe('testing recasting with comments and whitespace', () => {
it('code with comments', () => {
it('code with comments', async () => {
const code = `const yo = { a: { b: { c: '123' } } }
// this is a comment
const key = 'c'
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('code with comment and extra lines', () => {
it('code with comment and extra lines', async () => {
const code = `const yo = 'c'
/* this is
@ -205,23 +203,23 @@ a
comment */
const yo = 'bing'
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('comments at the start and end', () => {
it('comments at the start and end', async () => {
const code = `// this is a comment
const yo = { a: { b: { c: '123' } } }
const key = 'c'
// this is also a comment
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('comments in a fn block', () => {
const code = `fn myFn = () => {
it('comments in a fn block', async () => {
const code = `fn myFn = async () => {
// this is a comment
const yo = { a: { b: { c: '123' } } }
@ -231,11 +229,11 @@ const key = 'c'
// this is also a comment
}
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('comments in a pipe expression', () => {
it('comments in a pipe expression', async () => {
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
@ -244,11 +242,11 @@ const key = 'c'
' // a comment',
' |> rx(90, %)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('comments sprinkled in all over the place', () => {
it('comments sprinkled in all over the place', async () => {
const code = `
/* comment at start */
@ -270,7 +268,7 @@ const mySk1 = startSketchAt([0, 0])
one more for good measure
*/
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(`/* comment at start */
@ -291,37 +289,37 @@ const mySk1 = startSketchAt([0, 0])
})
describe('testing call Expressions in BinaryExpressions and UnaryExpressions', () => {
it('nested callExpression in binaryExpression', () => {
it('nested callExpression in binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('nested callExpression in unaryExpression', () => {
it('nested callExpression in unaryExpression', async () => {
const code = 'const myVar = -min(100, legLen(5, 3))'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('with unaryExpression in callExpression', () => {
it('with unaryExpression in callExpression', async () => {
const code = 'const myVar = min(5, -legLen(5, 4))'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('with unaryExpression in sketch situation', () => {
it('with unaryExpression in sketch situation', async () => {
const code = [
'const part001 = startSketchAt([0, 0])',
' |> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
})
describe('it recasts wrapped object expressions in pipe bodies with correct indentation', () => {
it('with a single line', () => {
it('with a single line', async () => {
const code = `const part001 = startSketchAt([-0.01, -0.08])
|> line([0.62, 4.15], %, 'seg01')
|> line([2.77, -1.24], %)
@ -332,35 +330,35 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
}, %)
|> line([-0.42, -1.72], %)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('recasts wrapped object expressions NOT in pipe body correctly', () => {
it('recasts wrapped object expressions NOT in pipe body correctly', async () => {
const code = `angledLineThatIntersects({
angle: 201,
offset: -1.35,
intersectTag: 'seg01'
}, %)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
})
describe('it recasts binary expression using brackets where needed', () => {
it('when there are two minus in a row', () => {
it('when there are two minus in a row', async () => {
const code = `const part001 = 1 - (def - abc)
`
const recasted = recast(code2ast(code).ast)
const recasted = recast((await code2ast(code)).ast)
expect(recasted).toBe(code)
})
})
// helpers
function code2ast(code: string): { ast: Program } {
const ast = parse(code)
async function code2ast(code: string): Promise<{ ast: Program }> {
const ast = await parse(code)
return { ast }
}

View File

@ -1171,10 +1171,7 @@ export class EngineCommandManager {
type: 'receive-reliable',
data: message,
id,
cmd_type:
command?.commandType ||
this.lastArtifactMap[id]?.commandType ||
sceneCommand?.commandType,
cmd_type: command?.commandType || this.lastArtifactMap[id]?.commandType,
})
Object.values(this.subscriptions[modelingResponse.type] || {}).forEach(
(callback) => callback(modelingResponse)

View File

@ -25,9 +25,7 @@ const eachQuad: [number, [number, number]][] = [
[675, [1, -1]],
]
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing getYComponent', () => {
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
@ -106,7 +104,7 @@ describe('testing changeSketchArguments', () => {
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
const { modifiedAst } = changeSketchArguments(
@ -130,7 +128,7 @@ const mySketch001 = startSketchOn('XY')
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)`
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(95)
@ -192,7 +190,7 @@ describe('testing addTagForSketchOnFace', () => {
|> lineTo([0.46, -5.82], %)
`
const code = genCode(originalLine)
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number] = [

View File

@ -8,9 +8,7 @@ import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { Selection } from 'lib/selections'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
// testing helper function
async function testingSwapSketchFnCall({
@ -30,7 +28,7 @@ async function testingSwapSketchFnCall({
type: 'default',
range: [startIndex, startIndex + callToSwap.length],
}
const ast = parse(inputCode)
const ast = await parse(inputCode)
const programMemory = await enginelessExecutor(ast)
const selections = {
codeBasedSelections: [range],
@ -353,7 +351,7 @@ const part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)`
it('normal case works', async () => {
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
const index = code.indexOf('// normal-segment') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,
@ -367,7 +365,7 @@ const part001 = startSketchOn('XY')
})
})
it('verify it works when the segment is in the `start` property', async () => {
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
const index = code.indexOf('// segment-in-start') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,

View File

@ -11,59 +11,57 @@ import { ToolTip } from '../../useStore'
import { Selections } from 'lib/selections'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing getConstraintType', () => {
const helper = getConstraintTypeFromSourceHelper
it('testing line', () => {
expect(helper(`line([5, myVar], %)`)).toBe('yRelative')
expect(helper(`line([myVar, 5], %)`)).toBe('xRelative')
it('testing line', async () => {
expect(await helper(`line([5, myVar], %)`)).toBe('yRelative')
expect(await helper(`line([myVar, 5], %)`)).toBe('xRelative')
})
it('testing lineTo', () => {
expect(helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
expect(helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
it('testing lineTo', async () => {
expect(await helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
expect(await helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
})
it('testing angledLine', () => {
expect(helper(`angledLine([5, myVar], %)`)).toBe('length')
expect(helper(`angledLine([myVar, 5], %)`)).toBe('angle')
it('testing angledLine', async () => {
expect(await helper(`angledLine([5, myVar], %)`)).toBe('length')
expect(await helper(`angledLine([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineOfXLength', () => {
expect(helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
expect(helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
it('testing angledLineOfXLength', async () => {
expect(await helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
expect(await helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineToX', () => {
expect(helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
expect(helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
it('testing angledLineToX', async () => {
expect(await helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
expect(await helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineOfYLength', () => {
expect(helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
expect(helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
it('testing angledLineOfYLength', async () => {
expect(await helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
expect(await helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineToY', () => {
expect(helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
expect(helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
it('testing angledLineToY', async () => {
expect(await helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
expect(await helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
})
const helper2 = getConstraintTypeFromSourceHelper2
it('testing xLine', () => {
expect(helper2(`xLine(5, %)`)).toBe('yRelative')
it('testing xLine', async () => {
expect(await helper2(`xLine(5, %)`)).toBe('yRelative')
})
it('testing yLine', () => {
expect(helper2(`yLine(5, %)`)).toBe('xRelative')
it('testing yLine', async () => {
expect(await helper2(`yLine(5, %)`)).toBe('xRelative')
})
it('testing xLineTo', () => {
expect(helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
it('testing xLineTo', async () => {
expect(await helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
})
it('testing yLineTo', () => {
expect(helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
it('testing yLineTo', async () => {
expect(await helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
})
})
function getConstraintTypeFromSourceHelper(
async function getConstraintTypeFromSourceHelper(
code: string
): ReturnType<typeof getConstraintType> {
const ast = parse(code)
): Promise<ReturnType<typeof getConstraintType>> {
const ast = await parse(code)
const args = (ast.body[0] as any).expression.arguments[0].elements as [
Value,
Value
@ -71,10 +69,10 @@ function getConstraintTypeFromSourceHelper(
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
return getConstraintType(args, fnName)
}
function getConstraintTypeFromSourceHelper2(
async function getConstraintTypeFromSourceHelper2(
code: string
): ReturnType<typeof getConstraintType> {
const ast = parse(code)
): Promise<ReturnType<typeof getConstraintType>> {
const ast = await parse(code)
const arg = (ast.body[0] as any).expression.arguments[0] as Value
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
return getConstraintType(arg, fnName)
@ -199,7 +197,7 @@ const part001 = startSketchOn('XY')
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
`
it('should transform the ast', async () => {
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('//'))
@ -286,7 +284,7 @@ const part001 = startSketchOn('XY')
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
`
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for horizontal constraint'))
@ -344,7 +342,7 @@ const part001 = startSketchOn('XY')
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10
`
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for vertical constraint'))
@ -435,7 +433,7 @@ async function helperThing(
linesOfInterest: string[],
constraint: ConstraintType
): Promise<string> {
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) =>
@ -467,7 +465,7 @@ async function helperThing(
}
describe('testing getConstraintLevelFromSourceRange', () => {
it('should divide up lines into free, partial and fully contrained', () => {
it('should divide up lines into free, partial and fully contrained', async () => {
const code = `const baseLength = 3
const baseThick = 1
const armThick = 0.5
@ -497,7 +495,7 @@ const part001 = startSketchOn('XY')
|> line([-1.49, 1.06], %) // free
|> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
const ast = parse(code)
const ast = await parse(code)
const constraintLevels: ReturnType<
typeof getConstraintLevelFromSourceRange
>['level'][] = ['full', 'partial', 'free']

View File

@ -1,9 +1,7 @@
import { parse, initPromise } from '../wasm'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing angledLineThatIntersects', () => {
it('angledLineThatIntersects should intersect with another line', async () => {
@ -17,9 +15,9 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset},
}, %, "yo2")
const intersect = segEndX('yo2', part001)`
const { root } = await enginelessExecutor(parse(code('-1')))
const { root } = await enginelessExecutor(await parse(code('-1')))
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
const { root: noOffset } = await enginelessExecutor(await parse(code('0')))
expect(noOffset.intersect.value).toBeCloseTo(1)
})
})

View File

@ -1,8 +1,6 @@
import { lexer, initPromise } from './wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing lexer', () => {
it('async lexer works too', async () => {

View File

@ -25,7 +25,8 @@ import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { TEST } from 'env'
import { rangeTypeFix } from './workers/types'
import { parser } from 'lib/singletons'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value'
@ -96,23 +97,26 @@ export const wasmUrl = () => {
// Initialise the wasm module.
const initialise = async () => {
try {
const fullUrl = wasmUrl()
const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer()
return await init(buffer)
} catch (e) {
console.log('Error initialising WASM', e)
throw e
}
const fullUrl = wasmUrl()
const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer()
return init(buffer)
}
export const initPromise = initialise()
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
ranges.map(([start, end]) => [start, end])
export type PathToNode = [string | number, string][]
export const parse = (code: string): Program => {
interface Memory {
[key: string]: MemoryItem
}
export interface ProgramMemory {
root: Memory
return: ProgramReturn | null
}
/*export const parse = (code: string): Program => {
try {
const program: Program = parse_wasm(code)
return program
@ -127,17 +131,10 @@ export const parse = (code: string): Program => {
console.log(kclError)
throw kclError
}
}
}*/
export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: MemoryItem
}
export interface ProgramMemory {
root: Memory
return: ProgramReturn | null
export const parse = async (code: string): Promise<Program> => {
return parser.parse(code)
}
export const executor = async (
@ -159,6 +156,10 @@ export const executor = async (
return _programMemory
}
const getSettingsState = import('components/SettingsAuthProvider').then(
(module) => module.getSettingsState
)
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
@ -166,14 +167,8 @@ export const _executor = async (
isMock: boolean
): Promise<ProgramMemory> => {
try {
let baseUnit = 'mm'
if (!TEST) {
const getSettingsState = import('components/SettingsAuthProvider').then(
(module) => module.getSettingsState
)
baseUnit =
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
}
const baseUnit =
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
const memory: ProgramMemory = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory),

View File

@ -0,0 +1,60 @@
import init, { parse_wasm } from 'wasm-lib/pkg/wasm_lib'
import { Program } from 'lang/wasm'
import { KclError as RustKclError } from 'wasm-lib/kcl/bindings/KclError'
import { KCLError } from 'lang/errors'
import {
WasmWorkerEventType,
WasmWorkerEvent,
WasmWorkerOptions,
ParserWorkerCall,
rangeTypeFix,
} from 'lang/workers/types'
// Initialise the wasm module.
const initialise = async (wasmUrl: string) => {
const input = await fetch(wasmUrl)
const buffer = await input.arrayBuffer()
return init(buffer)
}
onmessage = function (event) {
const { worker, eventType, eventData }: WasmWorkerEvent = event.data
switch (eventType) {
case WasmWorkerEventType.Init:
let { wasmUrl }: WasmWorkerOptions = eventData as WasmWorkerOptions
initialise(wasmUrl)
.then((instantiatedModule) => {
console.log('Worker: WASM module loaded', worker, instantiatedModule)
postMessage({
eventType: WasmWorkerEventType.Init,
response: { worker: worker, initialized: true },
})
})
.catch((error) => {
console.error('Worker: Error loading wasm module', worker, error)
})
break
case WasmWorkerEventType.Call:
const data = eventData as ParserWorkerCall
try {
const program: Program = parse_wasm(data.code)
postMessage({ uuid: data.uuid, response: program })
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
postMessage({
eventType: WasmWorkerEventType.Call,
response: { uuid: data.uuid, response: kclError },
})
}
break
default:
console.error('Worker: Unknown message type', worker, eventType)
}
}

43
src/lang/workers/types.ts Normal file
View File

@ -0,0 +1,43 @@
import { KCLError } from 'lang/errors'
import { Program } from 'lang/wasm'
export enum WasmWorker {
Parser = 'parser',
}
export enum WasmWorkerEventType {
Init = 'init',
Call = 'call',
}
export interface WasmWorkerOptions {
wasmUrl: string
}
export interface ParserWorkerCall {
uuid: string
code: string
}
export interface ParserWorkerInitResponse {
worker: WasmWorker
initialized: boolean
}
export interface ParserWorkerCallResponse {
uuid: string
response: Program | KCLError
}
export interface ParserWorkerResponse {
eventType: WasmWorkerEventType
response: ParserWorkerInitResponse | ParserWorkerCallResponse
}
export interface WasmWorkerEvent {
eventType: WasmWorkerEventType
eventData: Uint8Array | WasmWorkerOptions | ParserWorkerCall
worker: WasmWorker
}
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
ranges.map(([start, end]) => [start, end])

View File

@ -103,7 +103,6 @@ export const fileLoader: LoaderFunction = async ({
// Update both the state and the editor's code.
// We explicitly do not write to the file here since we are loading from
// the file system and not the editor.
codeManager.updateCurrentFilePath(currentFilePath)
codeManager.updateCodeStateEditor(code)
kclManager.executeCode(true)

View File

@ -119,9 +119,9 @@ export async function getEventForSelectWithPoint(
}
}
export function getEventForSegmentSelection(
export async function getEventForSegmentSelection(
obj: Object3D<Object3DEventMap>
): ModelingMachineEvent | null {
): Promise<ModelingMachineEvent | null> {
const group = getParentGroup(obj, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
@ -143,7 +143,7 @@ export function getEventForSegmentSelection(
// previous drags don't update ast for efficiency reasons
// So we want to make sure we have and updated ast with
// accurate source ranges
const updatedAst = parse(codeManager.code)
const updatedAst = await parse(codeManager.code)
const node = getNodeFromPath<CallExpression>(
updatedAst,
pathToNode,
@ -254,10 +254,10 @@ export function processCodeMirrorRanges({
}
}
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
async function updateSceneObjectColors(codeBasedSelections: Selection[]) {
let updated: Program
try {
updated = parse(recast(kclManager.ast))
updated = await parse(recast(kclManager.ast))
} catch (e) {
console.error('error parsing code in processCodeMirrorRanges', e)
return

View File

@ -1,15 +1,15 @@
import { SceneEntities } from 'clientSideScene/sceneEntities'
import { SceneInfra } from 'clientSideScene/sceneInfra'
import EditorManager from 'editor/manager'
import { KclManager } from 'lang/KclSingleton'
import CodeManager from 'lang/codeManager'
import Parser from 'lang/parser'
import { EngineCommandManager } from 'lang/std/engineConnection'
export const parser = new Parser()
export const codeManager = new CodeManager()
export const engineCommandManager = new EngineCommandManager()
// This needs to be after codeManager is created.
export const kclManager = new KclManager(engineCommandManager)
engineCommandManager.getAstCb = () => kclManager.ast
@ -17,18 +17,3 @@ export const sceneInfra = new SceneInfra(engineCommandManager)
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
export const sceneEntitiesManager = new SceneEntities(engineCommandManager)
// This needs to be after sceneInfra and engineCommandManager are is created.
export const editorManager = new EditorManager()
if (typeof window !== 'undefined') {
;(window as any).engineCommandManager = engineCommandManager
;(window as any).kclManager = kclManager
;(window as any).sceneInfra = sceneInfra
;(window as any).sceneEntitiesManager = sceneEntitiesManager
;(window as any).editorManager = editorManager
;(window as any).enableMousePositionLogs = () =>
document.addEventListener('mousemove', (e) =>
console.log(`await page.mouse.click(${e.clientX}, ${e.clientY})`)
)
}

View File

@ -82,7 +82,7 @@ export function useCalculateKclExpression({
useEffect(() => {
const execAstAndSetResult = async () => {
const code = `const __result__ = ${value}`
const ast = parse(code)
const ast = await parse(code)
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }

View File

@ -1,9 +1,7 @@
import { Coords2d } from 'lang/std/sketch'
import { isPointsCCW, initPromise } from 'lang/wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('test isPointsCW', () => {
test('basic test', () => {

View File

@ -16,54 +16,6 @@ export type CommandBarContext = {
argumentsToSubmit: { [x: string]: unknown }
}
export type CommandBarMachineEvent =
| { type: 'Open' }
| { type: 'Close' }
| { type: 'Clear' }
| {
type: 'Select command'
data: { command: Command }
}
| { type: 'Deselect command' }
| { type: 'Submit command'; data: { [x: string]: unknown } }
| {
type: 'Add argument'
data: { argument: CommandArgumentWithName<unknown> }
}
| {
type: 'Remove argument'
data: { [x: string]: CommandArgumentWithName<unknown> }
}
| {
type: 'Edit argument'
data: { arg: CommandArgumentWithName<unknown> }
}
| {
type: 'Add commands'
data: { commands: Command[] }
}
| {
type: 'Remove commands'
data: { commands: Command[] }
}
| { type: 'Submit argument'; data: { [x: string]: unknown } }
| {
type: 'done.invoke.validateArguments'
data: { [x: string]: unknown }
}
| {
type: 'error.platform.validateArguments'
data: { message: string; arg: CommandArgumentWithName<unknown> }
}
| {
type: 'Find and select command'
data: { name: string; ownerMachine: string }
}
| {
type: 'Change current argument'
data: { [x: string]: CommandArgumentWithName<unknown> }
}
export const commandBarMachine = createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */
@ -275,7 +227,53 @@ export const commandBarMachine = createMachine(
},
},
schema: {
events: {} as CommandBarMachineEvent,
events: {} as
| { type: 'Open' }
| { type: 'Close' }
| { type: 'Clear' }
| {
type: 'Select command'
data: { command: Command }
}
| { type: 'Deselect command' }
| { type: 'Submit command'; data: { [x: string]: unknown } }
| {
type: 'Add argument'
data: { argument: CommandArgumentWithName<unknown> }
}
| {
type: 'Remove argument'
data: { [x: string]: CommandArgumentWithName<unknown> }
}
| {
type: 'Edit argument'
data: { arg: CommandArgumentWithName<unknown> }
}
| {
type: 'Add commands'
data: { commands: Command[] }
}
| {
type: 'Remove commands'
data: { commands: Command[] }
}
| { type: 'Submit argument'; data: { [x: string]: unknown } }
| {
type: 'done.invoke.validateArguments'
data: { [x: string]: unknown }
}
| {
type: 'error.platform.validateArguments'
data: { message: string; arg: CommandArgumentWithName<unknown> }
}
| {
type: 'Find and select command'
data: { name: string; ownerMachine: string }
}
| {
type: 'Change current argument'
data: { [x: string]: CommandArgumentWithName<unknown> }
},
},
preserveActionOrder: true,
},

View File

@ -7,7 +7,6 @@ import {
sceneInfra,
sceneEntitiesManager,
engineCommandManager,
editorManager,
} from 'lib/singletons'
import {
horzVertInfo,
@ -102,14 +101,12 @@ export type ModelingMachineEvent =
| {
type: 'defaultPlane'
plane: DefaultPlaneStr
planeId: string
}
| {
type: 'extrudeFace'
position: [number, number, number]
extrudeSegmentPathToNode: PathToNode
cap: 'start' | 'end' | 'none'
faceId: string
}
)
}
@ -155,7 +152,7 @@ export type MoveDesc = { line: number; snippet: string }
export const modelingMachine = createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEFTU2VJYWFxDUNDbPFxCQ1dAwQZO0lDW3EcuQzlBRoNFzd0LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5JTJVUNNBUNlLQ05YsQsjQWNLVE5Yw0aZXFmkHc2-ElsCEwwAgBRXBGAJ0CAaz9yAAsxqITOK8WYyZTCCpg1JaKw5cFFfSIGSmXYaLKyPI0UwKZSmC5XTxQW73R4vd5fH7-QyRZisSbcEFCaw0STZRaiNTiNQ0YRbBAnGSSWzCZSiKz1QziFbKfGtQnEh7PPjsN69DAA2mxKaMpKieSSZE0US40QaUQrPk5DkGnEKFIcmz7QyyjztBWkvgsN7sDXROnAhJM9Q25llXIo818jKGBZiLKihoyYRml3XIneb7sP7EMiUTAZym+oHawMIHI4io8-YNBpmWSWpYx8srSWWBTm1PygtZ36Sbt-ACSJIIyBI30CYCgXTAr38ADcwN7sOQSJgi-6SzMjMimwoZJoRZoaEnLXlpDQL3IJEmlE1XJc5W7+73n0PFaPx0EpzOAr9UG9sAAL24IY13ocYNwZUsrGTBY1FUMUYPBGRLWqGNTXkSwzmMPUZE7J9Mz+PtCN+N9HiIbhYBVEg8H8P8AOA15V38CBsCo3NRnAwFIOmfht1NCwNi5bEslMJYFD5GQzDSI09QvM0xMlfCbmfYjKTI4hKOo2iFyXFdMBYtihgoTiaT9LUoK3MswQUIVDnbVZsjUDZJKTKRDmyA8MjBU1lPTEi1J7DSKNwKi3ho3B-AAQQAIW8fwAA11ws3jEisdtWXEMRw2qZEpMk0wBMK8EGmPURTEyc57wJAjKUCwdhxCsKIuiuL-AATWS+lUu3DLrDsZQjmNY0UMRBAuWDYQrzFVRHH2Pz6pfEjgq08LaLIKAHi6gMrMlNFpCUPd20UQxhFMSSOQhORjoyY0zoqhbVNfRrVpah58HYf4uM1bqdUlLLpFFVQxNkapREk5QlnSXFsjO6sVGER6AuexUmu0yKmEXTHcFY8hekwEgPlY9iTO2zc+Os-dWRSXKTiWVRlEklZdmESo7W8-YNDEJG6pR8jXto+igJA1dMD0fxV2wKBcDJyyKfFQV22WQpTVOcRLRmg1Cq8pNjXyHme0WlbQvR+dF04fSxYlnBpdlnrrLOzL9zOmazDFS1BtESRzQq7F6mRRwqpaV0VOR5aXpNtbItgXASCYfx2FQRK7b+6opDBKU1AqvczEkxpBTKY1lGLo1HDKA2iL5zTI5amO44TpPOu+8zfugtOLBFaoMiy45JNyCFTTL8rTo0NYg4fEP-N58PUYFyKwAAR16Zj3qgT6U+g5F3P7s7hCNOQsstTIWTREwLyNIG8Wqx9Q+n9SI+a2imEJ0XqGb4s5bS2QIWPDPDHKrII9LSmABiYaokMUg8jUKICuS176zxrrRN4YAegLn8OQOe7BYAb12tYWykNrAjU0P-HQY0ljVAsKsWwjR8jwTwtfSei0jbDgAEpgEEGAPgIRegjBwfLAoUhJSLGmmIRmY1UhpHKt3c0VgxKNFgcwxUTwl7YHjgAGTwGABuqAwJmQ-vbJYqRvb5DWPaLI6g+RKC9pdfqchR6IQUVXZRvRVEJw2j+bAzFCbkAbnwxIdg5CsixEaQq+RR7qzIVnA0eoVCnD9kaceNVb6GycSo+OyDQgbQeNo3REEUo6jKGIA0xhNDIn2ByCJJQrCWFZAHRMW9MgKKigAdxor+f8wsmIGWJsZSg-g8AADNUAEAgNwMAtxcBzlQN8SQMB2CCCFoxUCghBmoD8UiLkqJR4nAqmYCqmRJLZHMO2fcUkqFyHBE01pHA6IdKWcxHpHF+m4CGQQRcbx-ySCYATdgQy3hdFmX4BZdyRaYBWS8tZ78eIFK5F7HITo9r2QqpJKUgpGinAvAUJMkMrltLNnpB5RknmrJGWMiZUyZlzMELpC2q5wVDPWaUXKFQsorBMOCSoJ4xplCUJIRwhxDj-VOvQ4OaYmEtLxTS5chKSZ9JJe8z53zhh-IBVSqV+l6WQr0dC0sZzFZGjEGJMQmhXIbAqEDY8UojpKFxTc2K8UErPNeaM3A4y8AUvGVSkgAAjWAgg+CasZWctIIoZCXgPpGblqRBS5EaI5WQg1GkMLFapCVdq2qOvlW8D5bwvk-JVYC+ZPq-UBtWUGrumUrUFFMEaLQBVVjSHNKKaojRkxX1FV2AKaaAj2o6k64ZLq3WTOmZ6oFxbBB6EDVC-JurwGNvkHqWwIDkzgzGlycE1M7QcnNLCu8HbaqG27a1eK7V+1vOzYq-N-5VVjt9ROqd2qZ1WTOT-bFWUwRnDZHycBuw4nF0LpKKatqAhZK0SSwd5KR2FsEKBh9eTW7PorVyHIWLyqFUMBdY0QpYWWH-k2jIwGJb4GyVmnNeblXXug7Bst06EMUzObZPe2R9zVLtKdC6p0KiSkwnIfelzk2drqke1en0z0QfdVBqlInfhwe4k++jYMnZmFhhsY8vJuX1C9mrLuKhTpWEI9Js9Crc1Kt+ZRqTM414yZo4+uj-i53GBMDI+oXNRolDBGa-9WgURiFujKATB6iJHsxm8bGuN8aE0MrKsDELSWusg5SoFIWwvLgi28QQjyTKyZ+jtejYJBTawxINGymxuV7i9j3Ueop7QH0I8lmc4WCZEyJSZIzF6TNXv+dB+rONUtNYyy1yg2WW65f8fluyShrDFf9qu9zGQISVeLmhgViSb5T0Nho11OTnhpLcfgDxXi3g+MTkGs6aRsT9xocukBfJs5BOqFoUUU1IYKAUZtrRicdE7ZcekkIxlNofdQDooNutilYiyKzS19YyH-xDVNRzigHJyDe5onJkgBy4A4AQEHKg+VSjFA0FEchzpjQlAaKUICa1ZTOlJFHW3PuYHR5j9g2PqTwdG0iJMgoOQsbWNU3EUZHbhIkLYY0NajR08BzoyQAA5JOAAFVAeAsEECihACAgxCYBBYMroNxddg1uGkThoaw+TF3MGiX2e9EdSkl2juX-hFfK9gDmEyuS5N2aMJocwJgpJ7ixAcCxY094xhsBA-I6UzgKIx1jxlxgG0HD3rEw4AfLRSSkKPUNDNjx8+j8z1ntmOdliNDGO0wTwlhssAiKpO50jtnjOKGwKgFEABV3GvE8QZbxDdvuuPezkvX+xMo8nbIVU4bnEAojh2UAoe9TiZ4Ub0HGSd2LemfAAeVwHFodHrJBRW8C3wQS-RmCFX+wDfMtaNF-+rZQ4mRFL7AKGoPk5oWSpEqM5TISgsoKLYZk4jUujO3a7Q-gHSUAeAqu6u-gGS-22SYBeAfCVS2QXs6gjY+4LaWI6m-ijgkimyYuFUhisC-guAScAyJAlAPgwQoQLEYAZB+M2uBMrqjKggdgu4KI1Oj2bas2iAg0aQKwUkaI90IoNgC0ZA2AXQwwIBic-gSqrq2+CW4yYhEhIwggicggZBlAjKeou4I+6Kv8PIZuVMuUtC+4FUWgr2AWNwShkh+ADc-gnCRkIBsAJE8hEmMy1hKhahzhhYV+5MiQnsFg+WZ0Kwpo7636UMxcX+qQ6gPmFh94JBGA8AUQSSUA7OfhQgDQMYsg8gSgs0ywlSRgZqo8lQooO6-8Z2C0dwDwaRn8Qgawt+4Iuymg5oXMliKwxiag-8xhlg7aE8KaJENR9s0RrIx07K+8qe3KyIQo4e2IdQxcp0jiM8YAgxf0Jw6cZhNOzRwRDYKIFQzG66+QSeq2jCqa1y7SDEoKUWvSMWQyKx0EtorIOyigrMSYe8E+TKtg0gZoXIpwuISYzolh62QWZx+KtK3Sg2NxqAdxu06gMaqglgAig0Ykrk1ox4nkuQS2YohGvamaEK0J8sdoUgnIsiKgdgF4JO7mICoed0OIWEpcIqfRgmh6IJvap6qy+JaUdo3O+4aISw+c36pwXshceohqsJvRKR4qIJoG-aHJXuhUCwPxaIDghwN23KHItkOc7YxoPIg0BQBmlmom7JHu1+Ze92w0KwICxcYi7mZwIaYksxjRZoUegJkpeKPWjWkWmWcqeJxp6RZY7YdpoozkjQqQ+QkkJ06QbMw8IpqQduDOsppQ9gHc+Q7YNg6g9QL+7RlOx8SwHImmcZQOjOMe7ACZdgYoyZ+OaZtoZunxqwn64SNao84pa2TC-eDOkgJB-yq4pZNgEIXMmQhotYZQUYNSYelQEe+4ZwDJEpqkbZhZsuCuSurwSROWfphC6cSguQbKoMZwUYg0NoOQkoSwwhKweeHACZOE5gawpozIUoZguQDY7RzYKGbYHYLpqkbe+2Heh2x2UJvptRpQ5oP8qKGwo8YIGJlogpewFU5UjgPIKQ05LZqkx+K+Qwa+JEm+F59Q5gEgg8esO6yIL+o8rIEChCNJxov+f20p8Z-59sB8aQ15K6cYfJ1pvUWRKwjgqQU0vxlF-+AOaOwBthQQGCOM6C-4rqbwpZLRew8gZozFJuGsNamUaw2UR5SgzZJxAUf+sBgBe+ZxIB8BvE+iBSkMaQeQUIvZhip4-8fKHF6UGBD075JExBpB5ByxtFOoLB+QCw5oYa-UawflfIXM6cI00adQdYohmOyhUhScsh7lq5AFqwUgFUuIIZ8g-OhhMkVqIog8E0iMLpHhMV9hfAjhQlAxHl0EFU6QRcPGuQ1QtgTMLIJcZezIOI+4LgLgQAA */
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8Ql8AgFtUAFcgwPYSdjAI3hiOLnjQIsFRQzLVDUNlRbSJWeVdRJLkjIUJdPzlVWVGkHcW-ElsCEwwAgBRXCGAJ0CAaz9yAAsRqLG43iJQQyZTCSRWZSmZYKKxzUEaQqIGSmDSSHI5ayGcQ0KqQ07nTxQK43O6PF7vT4-QyRZiscbcQFCaw0NFLSyiNTYhSZREIDIySS2YTKWYgmHiGEnVxnZqE4m3B58djPboYX602ITRkIGbySTImiiSGiDSiGG8uYc-UKZQKUymDk2DSLfGy1ry0l8FjPdjq6J0gEJJnqa3MmR2J1m3nCYTzMTpEXc-Km10ed3eD7sb7EMiUTAZyl+-5aoMIOY28GZZ3c7lmWQWwww8GNmFYyx7BSpi5EgtZr6SXvfACSJIIyBIH0CYCgHTAT38ADcwD7sOQSJgiwGS6BElZTGVGzJNMLNKkCvojIspNiaKl6vklBou3LB-3XyOFePJ0EZ3OAl9UGebAAC9uAGDd6FGLcGVLKxhFRVQbUNOZDmEGQLXEOZylNLIlkww08mfdNM2+AcSK+D87iIbhYGVEg8H8ACgNAp5138CBsFo3Nhkgv5oMmfgjDkVFsjSHFH33dReTSKELAqDlslEfcsSIy5XzIylKOIGi6IYpcVzXTB2M4gYKB4ml-U1GCdyE21BTyPYlkWNQNHQi8EHDWpykxY8YxBE1VJ7ciNL7LTqNwWjnno3B-AAQQAIW8fwAA1NysgTdyPUQ0XEMRDBsTDkTSaSHVRB1QW5O9TFqBppQJYjKRC4dR3CyLorixL-AATTS+kMqEvZ9VcpY5EMI0jTcoouQUSQ0PktlHGdQKmrfciwp0qKGLIKBbl6wMbLLdJryUao9kUQxhFMaSOTBOQzpjI1Luq5b1PfFqNva258HYH5eI1PrtSxXLpBFVR91kTDRGk5RG1myFFku6sVGEF7grehVWt0mKmGXHHcA48hukwEhXg4rizL27dBLLcMZpsUxCoyRtjmkmFUVjKpqrQ50NDEVHGvRqiPoYpiQLA9dMD0fx12wKBcEp6zqYhAUOy0CRTQyZRxAtdZ9QdXz8iNEp+b7Fb1oirHF2XThDMl6WcDlhX+ppy6cqPS71jMWYLWUORyjtS6bWqKpahN0jBe0i3Npi2BcBIJh-HYVAUqdwHMKkEEJTUarqjMaTRP1OQjiOQ1HHDMPVs096o-a2P48T5Oer+yyAdg9OLGFTCY1y4wEXciMwRNMulIujRllqpo0zUtG1urtqGLAABHbo2K+qAftT2DkSkWZecurI8gkc8igu7EcpMW9DVB0wK7NufLaYEmJeoZvi0V3dZDBVJM7GhmEb7k+DMv6lBhvaTIahRC3wjpjaO-hnhgC6Eufw5BhZPFgJvA6VgtCSBhtYCamgxo6Hco2TCFgli2GyCUNQvsoGzwVAAJTAIIMAfAQjdCGBgpWdQpBYlUFYeSYgNjuShMocoXM6gwmRL3WhVcFT3GXtgBOAAZPAYAG6oAghZN+ztGyyX2Mse06spLuSUNlG61hwwmhFHMGRoVRzyO6IoxO20-zYDYiTcgDdOGJDsH7DIphDQOhKGPbWxDs76nqMcXEhoJ4yinkFAWdDSQKITvA0I21bjqM0VBdK2pPLZTsC5ZEzoOShJPjISwaJkTqG-kA2J9Vp6NVigAd3ov+QCYtWJGTJqZSg-g8AADNUAEAgNwMAVxcALlQB8SQMB2CCFFixcCghBmoG8UibEqJnTLBxLUe03dpKLHMHsI8aRyFFxRnVN0jTTYtLaYxDpSy2I9O4v03AQyCDLmeIBSQTBibsCGc8Dosy-ALMeeLTAKz3lrNfvxPJ2JspzGdHMdIDlqrSQlAKbINBVAmDyMKQwt87kcCtgZZ5JlXmrJGWMiZUyZlzMEPpG264oVDPWR5Qq4JcowhMKCQwQppJHikI4Q+dhShoSJa0klTLVzkvJn0qlXyfl-MGIC4FDKZWGVZTCrRcLSxnJVoaMQ+4xCaEFa5cEoNUgSlOkoSV9yEpJWSm8j5ozcDjLwHS8ZDKSAACNYCCD4Nq9lZzRHCgqd-NYCgSq+3BBkRY48QT5HqdchJtypUBEdSlF1wylXPF+f8tVIL5l+oDUG1ZIau45RtXUAJQ8SpLGkGaEU+FeY2HtSSrNXUc3UvdbS6Z3rQWlsEHoYNsLcn6swqIuo8gKi2CAaaXk2JQRok5hyM0CKnxXPiStSQxLM2dW7Yq543z80qoBYBdVQ7-UjrHbqidB0zlf0OLlEEWsE1LqchYSERxLFYgldu7su793S3wJkqlbqPWTIHcWwQGSmEVvHa3R9VbsQosPoYww10jSCgRZYMazaYwdoCPBnteaC2qsvbB+Dd6cnIepmcmaWQViSOzhda6F1mx1CyHIQ0IJLmTyA+pEDa8fo9sg-2+loLRNfFo3xB9DHIZuzMP-Cp+RoaDRxeKlQF0rDEf8DJsjJ7lWFqowymTcn-r7UU424weLJTHkmkiZYApf1aBRGIB6UpBMvmCiBnGzw8YEyJiTYy8q1EQZpZ6mDDKAtBdXCF54ggXlmUsy3azPik32SUNYX2Yo5Csz9j3MeIpDFyBTTu4TGb-BxbnMF4mpMKVmSM6eijF6gWwdq-jBLDXktNcoGl7ReSsv61kGPcMtoCv9xjGCYrRwlIOnK7fFR7qskPBSc4-Arj3HPE8UnENl1RFVHysxusl0ymIBzmiEw+UbTyBBI2ZbqismSCHLgDgBAQ1pFESdWY3IURyCuu5W0ZQ0h2jMCYGMtgKtCeCittRScNGvfe+wT71I6MZaRPkAUHIjxOSsDioHRQodoi1hIWwRoAmGie6txHmBJAADlk4AAVUB4HYLAAgsUIAQH6CTAILB2chqOGVK+SlXLcmWLyI45gcjVWqpkdQEoacI9QEjpn-hWfs856QMy2T5P0d3JocwJgwcVJRB2aN7lcKrtAaUI8Wtb5vY++y4wja1ZZBUHxnEAChIlFJ+G44qRliEsA75xqzvUdUHRwbzHZZkKSDtDibIWsI35QtFI2aewEwQgKt5uJsPGoABUXFPDcUZDxDd1uOOUc9unwvnTn0UEpDkvteQojDWhVYWQcVjwEwX8Pptuj42TlxH0r4ADyuBe1Qa9Xu7wRfBDD9GYIMf7BJ-yyQ3HoGM18VmCRTkDkVuihmhZFCflLlahKFyrfRh6SwOq6R-u1o-gOlQDwFznncCQimR2mot-eAnCJ8iw2U6gjYVgSgdm6KDGjgoiEgikkI1UuiFc-guAycAyJAlAPgwQoQ7EYAGBRMAuxM7q7KwIWEuiOQMYWg8IDo0uIIieVgR0T0wo7aYe7oZA2AHQgwL+ScNWxBdwEm0WMyHBXBQwggScggGBlA7KFQB43Iig2K38PIwOR40gjB2IR41UWgnYbBlwIh3B+ADc-gLCJkL+sA5EM+km4y+hYhEh5hhYW+VMiQvs2Uwe+Qx2Jor6n6M0RwV+UI6gHmOhPmDUps8Oa2DiTiaSv+mS9ejh78WOYg+oxgf8sY1q9YxCY0nehSiujkcgLg0oaBGA8AUQDSUAGOThQg3IZQuQigXu6gWgF2ZYkIZCt4mgvG5Orky01wtw5R8ROo0I5QoI1UEopox4vIYB5QXIY0hUaGN8uhaa3wvRzs-haIZ0vK3uKI1006VCmQeEewlgtizUPRseFRZYGQGcWhl0R4OEVQDYKIcajBWmuUJgMOg+pEIGiyEKYWvSEW0KSxgMQcaI-iigsYNQqQGKyQqKN4Mu+QoewRNy7x1WmqcqPxOa-xsE6gAobIlg3Cvs+4gqVoqQiwbM82sw+mWazqqy6JmC4OFgkB28vsUBJUpCpoFUfCqgZoMg5Jh6aJJxfRuiOO1xmE3IrkS6OKBScgFQxqmJcx8JCx-YIGpGVJfJOido2UWst4OQDgeQDM10agieYOFQqg4adQ+mhmypVmpxuiLI5x40MIDMxc0MKgs0+4IcQxrJrxIRiJ9yXW9WoWKWCqfxKpAJOGh2R+8aUIJQ0k50s0-Kew+4UpUIKuWS1JDG9gHcJQewNg6gtovInJzYtokido2hXJ8xu6YRdOyOHAqZPiY0ZQmQmZJoVQQc0uyQSw76ISASY8spA+Xp-YFZau9OaBQK64NZSINgYIe8sgAStY4Y0YlSNgduhUNosgyZlZGuWuaCY5HkpoGckBWg9g32jR4a1ocwWIjYLBMITuKO25xgSkOCGszIEo++vuZY4BzYYBbYjYZot8JeW2ZeO2e2qA25d0wC1Qrk42MMY8Fo4pD51USkjgmQ9opZcpu6y+o+Aw4+5EU+t5to5gawasZOOWROiAVipO-KeCj0ew+epRu6d+0Rj+mAIFEgoiyw8gOEEgjYOKGeTYaEDoumqKOIt+P+pGlZz+hhQQKC+MyCgE7qzwzFmgD57Fs6wp3FxCSkNpKgvMsw55SgPZtF6k9Folg5e6GaL+ABAkQ2+qMMoiV4IIKISBdoFoIBieMIRZ+EuyKBaB-gUhYA25wI-uiYamrkLm4J7kvMGcE0UINQWsDMy0NhPBycKq7quFUg1UkI2QDooJalRQvsoiJg6gwoQ8XI-etFCVhhvBJhtEZh5Et51Us0RoIoPG+UmEtgrMLIJcSezINoR4+RTgQAA */
id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
@ -528,11 +525,7 @@ export const modelingMachine = createMachine(
'engineToClient cam sync direction',
],
entry: [
'add axis n grid',
'conditionally equip line tool',
'clientToEngine cam sync direction',
],
entry: ['add axis n grid', 'conditionally equip line tool'],
},
'Sketch no face': {
@ -556,6 +549,8 @@ export const modelingMachine = createMachine(
actions: 'set new sketch metadata',
},
},
entry: 'clientToEngine cam sync direction',
},
'animating to existing sketch': {
@ -569,6 +564,8 @@ export const modelingMachine = createMachine(
},
},
],
entry: 'clientToEngine cam sync direction',
},
},
@ -803,7 +800,7 @@ export const modelingMachine = createMachine(
sketchDetails.origin
)
},
'AST extrude': async (_, event) => {
'AST extrude': (_, event) => {
if (!event.data) return
const { selection, distance } = event.data
let ast = kclManager.ast
@ -832,12 +829,10 @@ export const modelingMachine = createMachine(
? distance.variableIdentifierAst
: distance.valueAst
)
const selections = await kclManager.updateAst(modifiedAst, true, {
// TODO not handling focusPath correctly I think
kclManager.updateAst(modifiedAst, true, {
focusPath: pathToExtrudeArg,
})
if (selections) {
editorManager.selectRange(selections)
}
},
'conditionally equip line tool': (_, { type }) => {
if (type === 'done.invoke.animate-to-face') {
@ -868,52 +863,7 @@ export const modelingMachine = createMachine(
})()
},
'animate after sketch': () => {
engineCommandManager
.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_disable_sketch_mode',
},
})
.then(async () => {
// there doesn't appear to be an animation, but if there was one we could add a wait here
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_set_perspective',
},
})
sceneInfra.camControls.syncDirection = 'engineToClient'
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_set_perspective',
},
})
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
vantage: sceneInfra.camControls.camera.position,
up: { x: 0, y: 0, z: 1 },
},
})
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
})
sceneEntitiesManager.animateAfterSketch()
},
'tear down client sketch': () => {
if (sceneEntitiesManager.activeSegments) {
@ -1030,10 +980,18 @@ export const modelingMachine = createMachine(
cmd_id: uuidv4(),
cmd: {
type: 'set_selection_filter',
filter: ['face', 'plane'],
filter: ['face'],
},
}),
'set selection filter to defaults': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_selection_filter',
filter: ['face', 'edge', 'solid2d'],
},
}),
'set selection filter to defaults': () => kclManager.enterEditMode(),
},
// end actions
}

View File

@ -1,11 +1,13 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { addLineHighlight, EditorView } from './editor/highlightextension'
import {
Program,
_executor,
ProgramMemory,
programMemoryInit,
} from './lang/wasm'
import { Selection } from 'lib/selections'
import { enginelessExecutor } from './lib/testHelpers'
import { EngineCommandManager } from './lang/std/engineConnection'
import { KCLError } from './lang/errors'
@ -53,6 +55,12 @@ export type PaneType =
| 'lspMessages'
export interface StoreState {
editorView: EditorView | null
setEditorView: (editorView: EditorView) => void
highlightRange: [number, number]
setHighlightRange: (range: Selection['range']) => void
isShiftDown: boolean
setIsShiftDown: (isShiftDown: boolean) => void
mediaStream?: MediaStream
setMediaStream: (mediaStream: MediaStream) => void
isStreamReady: boolean
@ -84,12 +92,34 @@ export interface StoreState {
path: string
}[]
setHomeMenuItems: (items: { name: string; path: string }[]) => void
lastCodeMirrorSelectionUpdatedFromScene: number
setLastCodeMirrorSelectionUpdatedFromScene: (time: number) => void
}
export const useStore = create<StoreState>()(
persist(
(set, get) => {
return {
editorView: null,
setEditorView: (editorView) => {
set({ editorView })
},
highlightRange: [0, 0],
setHighlightRange: (selection) => {
set({ highlightRange: selection })
const editorView = get().editorView
const safeEnd = Math.min(
selection[1],
editorView?.state.doc.length || selection[1]
)
if (editorView) {
editorView.dispatch({
effects: addLineHighlight.of([selection[0], safeEnd]),
})
}
},
isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
setMediaStream: (mediaStream) => set({ mediaStream }),
isStreamReady: false,
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
@ -129,6 +159,9 @@ export const useStore = create<StoreState>()(
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
homeMenuItems: [],
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
lastCodeMirrorSelectionUpdatedFromScene: Date.now(),
setLastCodeMirrorSelectionUpdatedFromScene: (time) =>
set({ lastCodeMirrorSelectionUpdatedFromScene: time }),
}
},
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -4,14 +4,13 @@
"paths": {
"/*": ["src/*"]
},
"types": [
"vite/client",
"@types/wicg-file-system-access",
"node",
"@wdio/globals/types"
],
"types": ["vite/client", "@types/wicg-file-system-access", "node", "@wdio/globals/types"],
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@ -26,6 +25,10 @@
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "e2e", "./*.ts"],
"include": [
"src",
"e2e",
"./*.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -5,5 +5,7 @@
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
"include": [
"vite.config.ts"
]
}

View File

@ -1,9 +1,15 @@
import react from '@vitejs/plugin-react'
import viteTsconfigPaths from 'vite-tsconfig-paths'
import eslint from 'vite-plugin-eslint'
import dns from 'dns'
import { defineConfig, configDefaults } from 'vitest/config'
import version from 'vite-plugin-package-version'
// Only needed because we run Node < 17
// and we want to open `localhost` not `127.0.0.1` on server start
// reference: https://vitejs.dev/config/server-options.html#server-host
dns.setDefaultResultOrder('verbatim')
const config = defineConfig({
server: {
open: true,
@ -19,38 +25,32 @@ const config = defineConfig({
forks: {
maxForks: 2,
minForks: 1,
},
}
},
setupFiles: ['src/setupTests.ts', '@vitest/web-worker'],
setupFiles: 'src/setupTests.ts',
environment: 'happy-dom',
coverage: {
provider: 'istanbul', // or 'v8'
provider: 'istanbul' // or 'v8'
},
exclude: [...configDefaults.exclude, '**/e2e/playwright/**/*'],
deps: {
optimizer: {
web: {
include: ['vitest-canvas-mock'],
},
},
},
clearMocks: true,
restoreMocks: true,
mockReset: true,
reporters: process.env.GITHUB_ACTIONS
? ['dot', 'github-actions']
: ['verbose', 'hanging-process'],
testTimeout: 1000,
hookTimeout: 1000,
teardownTimeout: 1000,
inline: ['vitest-canvas-mock']
}
},
build: {
outDir: 'build',
},
plugins: [react(), viteTsconfigPaths(), eslint(), version()],
plugins: [
react(),
viteTsconfigPaths(),
eslint(),
version(),
],
worker: {
plugins: () => [viteTsconfigPaths()],
},
plugins: () => [
viteTsconfigPaths(),
],
}
})
export default config

View File

@ -1675,10 +1675,10 @@
dependencies:
"@hapi/hoek" "^9.0.0"
"@headlessui/react@^1.7.19":
version "1.7.19"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.19.tgz#91c78cf5fcb254f4a0ebe96936d48421caf75f40"
integrity sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==
"@headlessui/react@^1.7.18":
version "1.7.18"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.18.tgz#30af4634d2215b2ca1aa29d07f33d02bea82d9d7"
integrity sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==
dependencies:
"@tanstack/react-virtual" "^3.0.0-beta.60"
client-only "^0.0.1"
@ -1703,9 +1703,9 @@
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
version "2.0.2"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==
"@iarna/toml@^2.2.5":
version "2.2.5"
@ -2360,14 +2360,19 @@
integrity sha512-awNxydYSU+E2vL7EiOAMtiSOfL5gUM5X4YSE2A92qpxDJQ/rXz6oMPYBFDcDywlUmvIDI6zsqgq17cGm5CITQw==
"@types/eslint@^8.4.5":
version "8.56.10"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==
version "8.44.1"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.1.tgz#d1811559bb6bcd1a76009e3f7883034b78a0415e"
integrity sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0":
"@types/estree@*":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
"@types/estree@1.0.5", "@types/estree@^1.0.0":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
@ -2412,12 +2417,7 @@
expect "^29.0.0"
pretty-format "^29.0.0"
"@types/json-schema@*":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/json-schema@^7.0.9":
"@types/json-schema@*", "@types/json-schema@^7.0.9":
version "7.0.12"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
@ -2764,13 +2764,6 @@
loupe "^2.3.7"
pretty-format "^29.7.0"
"@vitest/web-worker@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@vitest/web-worker/-/web-worker-1.5.0.tgz#9de0e20b4a9cfed2a4958d757bd0594a76a2d4e9"
integrity sha512-WxX5VAgp8knJGTZknTDUICAVtNiDsBDQN1z/ldsDFqhRUFp09WLhRxWnKqCQc+CXk1W+kpUZmWDjxaD3i8QBmA==
dependencies:
debug "^4.3.4"
"@wdio/cli@^8.24.3":
version "8.24.3"
resolved "https://registry.yarnpkg.com/@wdio/cli/-/cli-8.24.3.tgz#305adc66cd2264ed4ef4549266bbb327826e10ef"
@ -5087,12 +5080,11 @@ find-up@^6.3.0:
path-exists "^5.0.0"
flat-cache@^3.0.4:
version "3.2.0"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
dependencies:
flatted "^3.2.9"
keyv "^4.5.3"
flatted "^3.1.0"
rimraf "^3.0.2"
flat@^5.0.2:
@ -5100,10 +5092,10 @@ flat@^5.0.2:
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
flatted@^3.2.9:
version "3.3.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
flatted@^3.1.0:
version "3.2.7"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
flux@^4.0.1:
version "4.0.4"
@ -5444,9 +5436,9 @@ globals@^11.1.0:
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^13.19.0:
version "13.24.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
version "13.20.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82"
integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==
dependencies:
type-fest "^0.20.2"
@ -8490,9 +8482,9 @@ tinybench@^2.5.1:
integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==
tinypool@^0.8.3:
version "0.8.4"
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8"
integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==
version "0.8.3"
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.3.tgz#e17d0a5315a7d425f875b05f7af653c225492d39"
integrity sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==
tinyspy@^2.2.0:
version "2.2.1"