Merge branch 'main' into pierremtb/issue7349-transform-codemods-out-of-pipe
This commit is contained in:
		| @ -534,7 +534,7 @@ profile001 = startProfile(sketch001, at = [-484.34, 484.95]) | ||||
|   |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) | ||||
|   |> close() | ||||
| ` | ||||
|         const targetURL = `?create-file&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop` | ||||
|         const targetURL = `?create-file=true&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop=true` | ||||
|         await page.goto(page.url() + targetURL) | ||||
|         expect(page.url()).toContain(targetURL) | ||||
|       }) | ||||
|  | ||||
| @ -2362,7 +2362,7 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07] | ||||
|  | ||||
|     await test.step('add new profile', async () => { | ||||
|       await toolbar.rectangleBtn.click() | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.waitForTimeout(200) | ||||
|       await rectStart() | ||||
|       await editor.expectEditor.toContain( | ||||
|         `profile005 = startProfile(sketch001, at = [15.68, -3.84])` | ||||
|  | ||||
| @ -3,187 +3,250 @@ import { uuidv4 } from '@src/lib/utils' | ||||
|  | ||||
| import { getUtils } from '@e2e/playwright/test-utils' | ||||
| import { expect, test } from '@e2e/playwright/zoo-test' | ||||
| import type { Page } from '@playwright/test' | ||||
| import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture' | ||||
|  | ||||
| test.describe('Testing Camera Movement', () => { | ||||
|   test('Can move camera reliably', async ({ | ||||
|   /** | ||||
|    * hack that we're implemented our own retry instead of using retries built into playwright. | ||||
|    * however each of these camera drags can be flaky, because of udp | ||||
|    * and so putting them together means only one needs to fail to make this test extra flaky. | ||||
|    * this way we can retry within the test | ||||
|    * We could break them out into separate tests, but the longest past of the test is waiting | ||||
|    * for the stream to start, so it can be good to bundle related things together. | ||||
|    */ | ||||
|   const bakeInRetries = async ({ | ||||
|     mouseActions, | ||||
|     afterPosition, | ||||
|     beforePosition, | ||||
|     retryCount = 0, | ||||
|     page, | ||||
|     context, | ||||
|     homePage, | ||||
|     scene, | ||||
|   }: { | ||||
|     mouseActions: () => Promise<void> | ||||
|     beforePosition: [number, number, number] | ||||
|     afterPosition: [number, number, number] | ||||
|     retryCount?: number | ||||
|     page: Page | ||||
|     scene: SceneFixture | ||||
|   }) => { | ||||
|     const acceptableCamError = 5 | ||||
|     const u = await getUtils(page) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     await scene.connectionEstablished() | ||||
|     await test.step('Set up initial camera position', async () => | ||||
|       await scene.moveCameraTo({ | ||||
|         x: beforePosition[0], | ||||
|         y: beforePosition[1], | ||||
|         z: beforePosition[2], | ||||
|       })) | ||||
|  | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await u.closeKclCodePanel() | ||||
|  | ||||
|     const camPos: [number, number, number] = [0, 85, 85] | ||||
|     const bakeInRetries = async ( | ||||
|       mouseActions: any, | ||||
|       xyz: [number, number, number], | ||||
|       cnt = 0 | ||||
|     ) => { | ||||
|       // hack that we're implemented our own retry instead of using retries built into playwright. | ||||
|       // however each of these camera drags can be flaky, because of udp | ||||
|       // and so putting them together means only one needs to fail to make this test extra flaky. | ||||
|       // this way we can retry within the test | ||||
|       // We could break them out into separate tests, but the longest past of the test is waiting | ||||
|       // for the stream to start, so it can be good to bundle related things together. | ||||
|  | ||||
|       const camCommand: EngineCommand = { | ||||
|         type: 'modeling_cmd_req', | ||||
|         cmd_id: uuidv4(), | ||||
|         cmd: { | ||||
|           type: 'default_camera_look_at', | ||||
|           center: { x: 0, y: 0, z: 0 }, | ||||
|           vantage: { x: camPos[0], y: camPos[1], z: camPos[2] }, | ||||
|           up: { x: 0, y: 0, z: 1 }, | ||||
|         }, | ||||
|       } | ||||
|       const updateCamCommand: EngineCommand = { | ||||
|         type: 'modeling_cmd_req', | ||||
|         cmd_id: uuidv4(), | ||||
|         cmd: { | ||||
|           type: 'default_camera_get_settings', | ||||
|         }, | ||||
|       } | ||||
|       await u.sendCustomCmd(camCommand) | ||||
|       await page.waitForTimeout(100) | ||||
|       await u.sendCustomCmd(updateCamCommand) | ||||
|       await page.waitForTimeout(100) | ||||
|  | ||||
|       // rotate | ||||
|       await u.closeDebugPanel() | ||||
|       await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|       await page.waitForTimeout(100) | ||||
|       // const yo = page.getByTestId('cam-x-position').inputValue() | ||||
|  | ||||
|       await u.doAndWaitForImageDiff(async () => { | ||||
|     await test.step('Do actions and watch for changes', async () => | ||||
|       u.doAndWaitForImageDiff(async () => { | ||||
|         await mouseActions() | ||||
|  | ||||
|         await u.openAndClearDebugPanel() | ||||
|  | ||||
|         await u.closeDebugPanel() | ||||
|         await page.waitForTimeout(100) | ||||
|       }, 300) | ||||
|       }, 300)) | ||||
|  | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await expect(page.getByTestId('cam-x-position')).toBeAttached() | ||||
|  | ||||
|     const vals = await Promise.all([ | ||||
|       page.getByTestId('cam-x-position').inputValue(), | ||||
|       page.getByTestId('cam-y-position').inputValue(), | ||||
|       page.getByTestId('cam-z-position').inputValue(), | ||||
|     ]) | ||||
|     const errors = vals.map((v, i) => Math.abs(Number(v) - afterPosition[i])) | ||||
|     let shouldRetry = false | ||||
|  | ||||
|     if (errors.some((e) => e > acceptableCamError)) { | ||||
|       if (retryCount > 2) { | ||||
|         console.log('xVal', vals[0], 'xError', errors[0]) | ||||
|         console.log('yVal', vals[1], 'yError', errors[1]) | ||||
|         console.log('zVal', vals[2], 'zError', errors[2]) | ||||
|  | ||||
|         throw new Error('Camera position not as expected', { | ||||
|           cause: { | ||||
|             vals, | ||||
|             errors, | ||||
|           }, | ||||
|         }) | ||||
|       } | ||||
|       shouldRetry = true | ||||
|     } | ||||
|     if (shouldRetry) { | ||||
|       await bakeInRetries({ | ||||
|         mouseActions, | ||||
|         afterPosition: afterPosition, | ||||
|         beforePosition: beforePosition, | ||||
|         retryCount: retryCount + 1, | ||||
|         page, | ||||
|         scene, | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   test( | ||||
|     'Can pan and zoom camera reliably', | ||||
|     { | ||||
|       tag: '@web', | ||||
|     }, | ||||
|     async ({ page, homePage, scene, cmdBar }) => { | ||||
|       const u = await getUtils(page) | ||||
|       const camInitialPosition: [number, number, number] = [0, 85, 85] | ||||
|  | ||||
|       await homePage.goToModelingScene() | ||||
|       await scene.settled(cmdBar) | ||||
|  | ||||
|       await u.openAndClearDebugPanel() | ||||
|       await page.getByTestId('cam-x-position').isVisible() | ||||
|       await u.closeKclCodePanel() | ||||
|  | ||||
|       const vals = await Promise.all([ | ||||
|         page.getByTestId('cam-x-position').inputValue(), | ||||
|         page.getByTestId('cam-y-position').inputValue(), | ||||
|         page.getByTestId('cam-z-position').inputValue(), | ||||
|       ]) | ||||
|       const xError = Math.abs(Number(vals[0]) + xyz[0]) | ||||
|       const yError = Math.abs(Number(vals[1]) + xyz[1]) | ||||
|       const zError = Math.abs(Number(vals[2]) + xyz[2]) | ||||
|       await test.step('Pan', async () => { | ||||
|         await bakeInRetries({ | ||||
|           mouseActions: async () => { | ||||
|             await page.keyboard.down('Shift') | ||||
|             await page.mouse.move(600, 200) | ||||
|             await page.mouse.down({ button: 'right' }) | ||||
|             // Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine. | ||||
|             await page.mouse.move(700, 200) | ||||
|             await page.mouse.up({ button: 'right' }) | ||||
|             await page.keyboard.up('Shift') | ||||
|             await page.waitForTimeout(200) | ||||
|           }, | ||||
|           afterPosition: [19, 85, 85], | ||||
|           beforePosition: camInitialPosition, | ||||
|           page, | ||||
|           scene, | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       let shouldRetry = false | ||||
|       await test.step('Zoom with click and drag', async () => { | ||||
|         await bakeInRetries({ | ||||
|           mouseActions: async () => { | ||||
|             await page.keyboard.down('Control') | ||||
|             await page.mouse.move(700, 400) | ||||
|             await page.mouse.down({ button: 'right' }) | ||||
|             await page.mouse.move(700, 300) | ||||
|             await page.mouse.up({ button: 'right' }) | ||||
|             await page.keyboard.up('Control') | ||||
|           }, | ||||
|           afterPosition: [0, 118, 118], | ||||
|           beforePosition: camInitialPosition, | ||||
|           page, | ||||
|           scene, | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       if (xError > 5 || yError > 5 || zError > 5) { | ||||
|         if (cnt > 2) { | ||||
|           console.log('xVal', vals[0], 'xError', xError) | ||||
|           console.log('yVal', vals[1], 'yError', yError) | ||||
|           console.log('zVal', vals[2], 'zError', zError) | ||||
|  | ||||
|           throw new Error('Camera position not as expected') | ||||
|       await test.step('Zoom with scrollwheel', async () => { | ||||
|         const refreshCamValuesCmd: EngineCommand = { | ||||
|           type: 'modeling_cmd_req', | ||||
|           cmd_id: uuidv4(), | ||||
|           cmd: { | ||||
|             type: 'default_camera_get_settings', | ||||
|           }, | ||||
|         } | ||||
|         shouldRetry = true | ||||
|       } | ||||
|       await page.getByRole('button', { name: 'Exit Sketch' }).click() | ||||
|       await page.waitForTimeout(100) | ||||
|       if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1) | ||||
|         await bakeInRetries({ | ||||
|           mouseActions: async () => { | ||||
|             await page.mouse.move(700, 400) | ||||
|             await page.mouse.wheel(0, -150) | ||||
|  | ||||
|             // Scroll zooming doesn't update the debug pane's cam position values, | ||||
|             // so we have to force a refresh. | ||||
|             await u.openAndClearDebugPanel() | ||||
|             await u.sendCustomCmd(refreshCamValuesCmd) | ||||
|             await u.waitForCmdReceive('default_camera_get_settings') | ||||
|             await u.closeDebugPanel() | ||||
|           }, | ||||
|           afterPosition: [0, 42.5, 42.5], | ||||
|           beforePosition: camInitialPosition, | ||||
|           page, | ||||
|           scene, | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
|     await bakeInRetries(async () => { | ||||
|       await page.mouse.move(700, 200) | ||||
|       await page.mouse.down({ button: 'right' }) | ||||
|       await page.waitForTimeout(100) | ||||
|   ) | ||||
|  | ||||
|       const appLogoBBox = await page.getByTestId('app-logo').boundingBox() | ||||
|       expect(appLogoBBox).not.toBeNull() | ||||
|       if (!appLogoBBox) throw new Error('app logo not found') | ||||
|       await page.mouse.move( | ||||
|         appLogoBBox.x + appLogoBBox.width / 2, | ||||
|         appLogoBBox.y + appLogoBBox.height / 2 | ||||
|       ) | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.mouse.move(600, 303) | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.mouse.up({ button: 'right' }) | ||||
|     }, [4, -10.5, -120]) | ||||
|   test( | ||||
|     'Can orbit camera reliably', | ||||
|     { | ||||
|       tag: '@web', | ||||
|     }, | ||||
|     async ({ page, homePage, scene, cmdBar }) => { | ||||
|       const u = await getUtils(page) | ||||
|       const initialCamPosition: [number, number, number] = [0, 85, 85] | ||||
|  | ||||
|     await bakeInRetries(async () => { | ||||
|       await page.keyboard.down('Shift') | ||||
|       await page.mouse.move(600, 200) | ||||
|       await page.mouse.down({ button: 'right' }) | ||||
|       // Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine. | ||||
|       await page.mouse.move(700, 200) | ||||
|       await page.mouse.up({ button: 'right' }) | ||||
|       await page.keyboard.up('Shift') | ||||
|     }, [-19, -85, -85]) | ||||
|       await homePage.goToModelingScene() | ||||
|       // this turns on the debug pane setting as well | ||||
|       await scene.settled(cmdBar) | ||||
|  | ||||
|     const camCommand: EngineCommand = { | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_look_at', | ||||
|         center: { x: 0, y: 0, z: 0 }, | ||||
|         vantage: { x: camPos[0], y: camPos[1], z: camPos[2] }, | ||||
|         up: { x: 0, y: 0, z: 1 }, | ||||
|       }, | ||||
|       await u.openAndClearDebugPanel() | ||||
|       await u.closeKclCodePanel() | ||||
|  | ||||
|       await test.step('Test orbit with spherical mode', async () => { | ||||
|         await bakeInRetries({ | ||||
|           mouseActions: async () => { | ||||
|             await page.mouse.move(700, 200) | ||||
|             await page.mouse.down({ button: 'right' }) | ||||
|             await page.waitForTimeout(100) | ||||
|  | ||||
|             const appLogoBBox = await page.getByTestId('app-logo').boundingBox() | ||||
|             expect(appLogoBBox).not.toBeNull() | ||||
|             if (!appLogoBBox) throw new Error('app logo not found') | ||||
|             await page.mouse.move( | ||||
|               appLogoBBox.x + appLogoBBox.width / 2, | ||||
|               appLogoBBox.y + appLogoBBox.height / 2 | ||||
|             ) | ||||
|             await page.waitForTimeout(100) | ||||
|             await page.mouse.move(600, 303) | ||||
|             await page.waitForTimeout(100) | ||||
|             await page.mouse.up({ button: 'right' }) | ||||
|           }, | ||||
|           afterPosition: [-4, 10.5, 120], | ||||
|           beforePosition: initialCamPosition, | ||||
|           page, | ||||
|           scene, | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       await test.step('Test orbit with trackball mode', async () => { | ||||
|         await test.step('Set orbitMode to trackball', async () => { | ||||
|           await cmdBar.openCmdBar() | ||||
|           await cmdBar.selectOption({ name: 'camera orbit' }).click() | ||||
|           await cmdBar.selectOption({ name: 'trackball' }).click() | ||||
|           await expect( | ||||
|             page.getByText(`camera orbit to "trackball"`) | ||||
|           ).toBeVisible() | ||||
|         }) | ||||
|  | ||||
|         await bakeInRetries({ | ||||
|           mouseActions: async () => { | ||||
|             await page.mouse.move(700, 200) | ||||
|             await page.mouse.down({ button: 'right' }) | ||||
|             await page.waitForTimeout(100) | ||||
|  | ||||
|             const appLogoBBox = await page.getByTestId('app-logo').boundingBox() | ||||
|             expect(appLogoBBox).not.toBeNull() | ||||
|             if (!appLogoBBox) { | ||||
|               throw new Error('app logo not found') | ||||
|             } | ||||
|             await page.mouse.move( | ||||
|               appLogoBBox.x + appLogoBBox.width / 2, | ||||
|               appLogoBBox.y + appLogoBBox.height / 2 | ||||
|             ) | ||||
|             await page.waitForTimeout(100) | ||||
|             await page.mouse.move(600, 303) | ||||
|             await page.waitForTimeout(100) | ||||
|             await page.mouse.up({ button: 'right' }) | ||||
|           }, | ||||
|           afterPosition: [18.06, -42.79, 110.87], | ||||
|           beforePosition: initialCamPosition, | ||||
|           page, | ||||
|           scene, | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
|     const updateCamCommand: EngineCommand = { | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_get_settings', | ||||
|       }, | ||||
|     } | ||||
|     await u.sendCustomCmd(camCommand) | ||||
|     await page.waitForTimeout(100) | ||||
|     await u.sendCustomCmd(updateCamCommand) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await u.clearCommandLogs() | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|     await page.waitForTimeout(200) | ||||
|  | ||||
|     // zoom | ||||
|     await u.doAndWaitForImageDiff(async () => { | ||||
|       await page.keyboard.down('Control') | ||||
|       await page.mouse.move(700, 400) | ||||
|       await page.mouse.down({ button: 'right' }) | ||||
|       await page.mouse.move(700, 300) | ||||
|       await page.mouse.up({ button: 'right' }) | ||||
|       await page.keyboard.up('Control') | ||||
|  | ||||
|       await u.openDebugPanel() | ||||
|       await page.waitForTimeout(300) | ||||
|       await u.clearCommandLogs() | ||||
|  | ||||
|       await u.closeDebugPanel() | ||||
|     }, 300) | ||||
|  | ||||
|     // zoom with scroll | ||||
|     await u.openAndClearDebugPanel() | ||||
|     // TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom | ||||
|     // await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5) | ||||
|     // await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5) | ||||
|     // await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5) | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Exit Sketch' }).click() | ||||
|  | ||||
|     await bakeInRetries(async () => { | ||||
|       await page.mouse.move(700, 400) | ||||
|       await page.mouse.wheel(0, -100) | ||||
|     }, [0, -85, -85]) | ||||
|   }) | ||||
|   ) | ||||
|  | ||||
|   // TODO: fix after electron migration is merged | ||||
|   test('Zoom should be consistent when exiting or entering sketches', async ({ | ||||
|  | ||||
							
								
								
									
										30
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -627,26 +627,22 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "criterion" | ||||
| version = "0.5.1" | ||||
| version = "0.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" | ||||
| checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" | ||||
| dependencies = [ | ||||
|  "anes", | ||||
|  "cast", | ||||
|  "ciborium", | ||||
|  "clap", | ||||
|  "criterion-plot", | ||||
|  "futures", | ||||
|  "is-terminal", | ||||
|  "itertools 0.10.5", | ||||
|  "itertools 0.13.0", | ||||
|  "num-traits 0.2.19", | ||||
|  "once_cell", | ||||
|  "oorandom", | ||||
|  "plotters", | ||||
|  "rayon", | ||||
|  "regex", | ||||
|  "serde", | ||||
|  "serde_derive", | ||||
|  "serde_json", | ||||
|  "tinytemplate", | ||||
|  "tokio", | ||||
| @ -1815,7 +1811,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-bumper" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "clap", | ||||
| @ -1826,7 +1822,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-derive-docs" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "Inflector", | ||||
|  "anyhow", | ||||
| @ -1845,7 +1841,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-directory-test-macro" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "convert_case", | ||||
|  "proc-macro2", | ||||
| @ -1855,7 +1851,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-language-server" | ||||
| version = "0.2.78" | ||||
| version = "0.2.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "clap", | ||||
| @ -1876,7 +1872,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-language-server-release" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "clap", | ||||
| @ -1896,7 +1892,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-lib" | ||||
| version = "0.2.78" | ||||
| version = "0.2.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "approx 0.5.1", | ||||
| @ -1973,7 +1969,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-python-bindings" | ||||
| version = "0.3.78" | ||||
| version = "0.3.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "kcl-lib", | ||||
| @ -1988,7 +1984,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-test-server" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "hyper 0.14.32", | ||||
| @ -2001,7 +1997,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-to-core" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "async-trait", | ||||
| @ -2015,7 +2011,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-wasm-lib" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "bson", | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
|  | ||||
| [package] | ||||
| name = "kcl-bumper" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| repository = "https://github.com/KittyCAD/modeling-api" | ||||
| rust-version = "1.76" | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-derive-docs" | ||||
| description = "A tool for generating documentation from Rust derive macros" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-directory-test-macro" | ||||
| description = "A tool for generating tests from a directory of kcl files" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "kcl-language-server-release" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| authors = ["KittyCAD Inc <kcl@kittycad.io>"] | ||||
| publish = false | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| name = "kcl-language-server" | ||||
| description = "A language server for KCL." | ||||
| authors = ["KittyCAD Inc <kcl@kittycad.io>"] | ||||
| version = "0.2.78" | ||||
| version = "0.2.79" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-lib" | ||||
| description = "KittyCAD Language implementation and tools" | ||||
| version = "0.2.78" | ||||
| version = "0.2.79" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
| @ -130,7 +130,7 @@ tabled = ["dep:tabled"] | ||||
| [dev-dependencies] | ||||
| approx = "0.5" | ||||
| base64 = "0.22.1" | ||||
| criterion = { version = "0.5.1", features = ["async_tokio"] } | ||||
| criterion = { version = "0.6.0", features = ["async_tokio"] } | ||||
| expectorate = "1.1.0" | ||||
| handlebars = "6.3.2" | ||||
| image = { version = "0.25.6", default-features = false, features = ["png"] } | ||||
|  | ||||
| @ -1,9 +1,10 @@ | ||||
| use std::{ | ||||
|     fs, | ||||
|     hint::black_box, | ||||
|     path::{Path, PathBuf}, | ||||
| }; | ||||
|  | ||||
| use criterion::{black_box, criterion_group, criterion_main, Criterion}; | ||||
| use criterion::{criterion_group, criterion_main, Criterion}; | ||||
|  | ||||
| const IGNORE_DIRS: [&str; 2] = ["step", "screenshots"]; | ||||
|  | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| use criterion::{black_box, criterion_group, criterion_main, Criterion}; | ||||
| use std::hint::black_box; | ||||
|  | ||||
| use criterion::{criterion_group, criterion_main, Criterion}; | ||||
|  | ||||
| pub fn bench_parse(c: &mut Criterion) { | ||||
|     for (name, file) in [ | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; | ||||
| use std::hint::black_box; | ||||
|  | ||||
| use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; | ||||
| use kcl_lib::kcl_lsp_server; | ||||
| use tokio::runtime::Runtime; | ||||
| use tower_lsp::LanguageServer; | ||||
|  | ||||
| @ -67,6 +67,7 @@ pub struct TcpRead { | ||||
|  | ||||
| /// Occurs when client couldn't read from the WebSocket to the engine. | ||||
| // #[derive(Debug)] | ||||
| #[allow(clippy::large_enum_variant)] | ||||
| pub enum WebSocketReadError { | ||||
|     /// Could not read a message due to WebSocket errors. | ||||
|     Read(tokio_tungstenite::tungstenite::Error), | ||||
|  | ||||
| @ -1351,7 +1351,7 @@ pub(crate) async fn execute_pipe_body( | ||||
|     // Now that we've evaluated the first child expression in the pipeline, following child expressions | ||||
|     // should use the previous child expression for %. | ||||
|     // This means there's no more need for the previous pipe_value from the parent AST node above this one. | ||||
|     let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output)); | ||||
|     let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output); | ||||
|     // Evaluate remaining elements. | ||||
|     let result = inner_execute_pipe_body(exec_state, body, ctx).await; | ||||
|     // Restore the previous pipe value. | ||||
|  | ||||
| @ -24,6 +24,7 @@ type Point3D = kcmc::shared::Point3d<f64>; | ||||
| #[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type")] | ||||
| #[allow(clippy::large_enum_variant)] | ||||
| pub enum Geometry { | ||||
|     Sketch(Sketch), | ||||
|     Solid(Solid), | ||||
| @ -52,6 +53,7 @@ impl Geometry { | ||||
| #[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type")] | ||||
| #[allow(clippy::large_enum_variant)] | ||||
| pub enum GeometryWithImportedGeometry { | ||||
|     Sketch(Sketch), | ||||
|     Solid(Solid), | ||||
|  | ||||
| @ -187,7 +187,7 @@ impl RuntimeType { | ||||
|                 }; | ||||
|                 RuntimeType::Primitive(PrimitiveType::Number(ty)) | ||||
|             } | ||||
|             AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?, | ||||
|             AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?, | ||||
|             AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag), | ||||
|             AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry), | ||||
|             AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function), | ||||
|  | ||||
| @ -1189,7 +1189,6 @@ impl LanguageServer for Backend { | ||||
|     } | ||||
|  | ||||
|     async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> { | ||||
|         // ADAM: This is the entrypoint. | ||||
|         let mut completions = vec![CompletionItem { | ||||
|             label: PIPE_OPERATOR.to_string(), | ||||
|             label_details: None, | ||||
|  | ||||
| @ -228,7 +228,7 @@ impl PrimitiveType { | ||||
|         let mut hasher = Sha256::new(); | ||||
|         match self { | ||||
|             PrimitiveType::Any => hasher.update(b"any"), | ||||
|             PrimitiveType::Named(id) => hasher.update(id.compute_digest()), | ||||
|             PrimitiveType::Named { id } => hasher.update(id.compute_digest()), | ||||
|             PrimitiveType::String => hasher.update(b"string"), | ||||
|             PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()), | ||||
|             PrimitiveType::Boolean => hasher.update(b"bool"), | ||||
|  | ||||
| @ -454,7 +454,7 @@ impl Node<Program> { | ||||
|                         alpha: c.a, | ||||
|                     }, | ||||
|                 }; | ||||
|                 if colors.borrow().iter().any(|c| *c == color) { | ||||
|                 if colors.borrow().contains(&color) { | ||||
|                     return; | ||||
|                 } | ||||
|                 colors.borrow_mut().push(color); | ||||
| @ -3230,7 +3230,7 @@ pub enum PrimitiveType { | ||||
|     /// `fn`, type of functions. | ||||
|     Function(FunctionType), | ||||
|     /// An identifier used as a type (not really a primitive type, but whatever). | ||||
|     Named(Node<Identifier>), | ||||
|     Named { id: Node<Identifier> }, | ||||
| } | ||||
|  | ||||
| impl PrimitiveType { | ||||
| @ -3286,7 +3286,7 @@ impl fmt::Display for PrimitiveType { | ||||
|                 } | ||||
|                 Ok(()) | ||||
|             } | ||||
|             PrimitiveType::Named(n) => write!(f, "{}", n.name), | ||||
|             PrimitiveType::Named { id: n } => write!(f, "{}", n.name), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2938,7 +2938,7 @@ fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> { | ||||
|         (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| { | ||||
|             let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id); | ||||
|             result.inner = | ||||
|                 PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident)); | ||||
|                 PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident }); | ||||
|             result | ||||
|         }), | ||||
|     )) | ||||
|  | ||||
| @ -3504,3 +3504,24 @@ mod var_ref_in_own_def { | ||||
|         super::execute(TEST_NAME, true).await | ||||
|     } | ||||
| } | ||||
| mod ascription_unknown_type { | ||||
|     const TEST_NAME: &str = "ascription_unknown_type"; | ||||
|  | ||||
|     /// Test parsing KCL. | ||||
|     #[test] | ||||
|     fn parse() { | ||||
|         super::parse(TEST_NAME) | ||||
|     } | ||||
|  | ||||
|     /// Test that parsing and unparsing KCL produces the original KCL input. | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn unparse() { | ||||
|         super::unparse(TEST_NAME).await | ||||
|     } | ||||
|  | ||||
|     /// Test that KCL is executed correctly. | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn kcl_test_execute() { | ||||
|         super::execute(TEST_NAME, true).await | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,7 @@ use crate::{ | ||||
| #[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(untagged)] | ||||
| #[allow(clippy::large_enum_variant)] | ||||
| pub enum SweepPath { | ||||
|     Sketch(Sketch), | ||||
|     Helix(Box<Helix>), | ||||
|  | ||||
| @ -0,0 +1,32 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Artifact commands ascription_unknown_type.kcl | ||||
| --- | ||||
| [ | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [], | ||||
|     "command": { | ||||
|       "type": "edge_lines_visible", | ||||
|       "hidden": false | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [], | ||||
|     "command": { | ||||
|       "type": "object_visible", | ||||
|       "object_id": "[uuid]", | ||||
|       "hidden": true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [], | ||||
|     "command": { | ||||
|       "type": "object_visible", | ||||
|       "object_id": "[uuid]", | ||||
|       "hidden": true | ||||
|     } | ||||
|   } | ||||
| ] | ||||
| @ -0,0 +1,6 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Artifact graph flowchart ascription_unknown_type.kcl | ||||
| extension: md | ||||
| snapshot_kind: binary | ||||
| --- | ||||
| @ -0,0 +1,3 @@ | ||||
| ```mermaid | ||||
| flowchart LR | ||||
| ``` | ||||
							
								
								
									
										67
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ast.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ast.snap
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Result of parsing ascription_unknown_type.kcl | ||||
| --- | ||||
| { | ||||
|   "Ok": { | ||||
|     "body": [ | ||||
|       { | ||||
|         "commentStart": 0, | ||||
|         "declaration": { | ||||
|           "commentStart": 0, | ||||
|           "end": 0, | ||||
|           "id": { | ||||
|             "commentStart": 0, | ||||
|             "end": 0, | ||||
|             "name": "z", | ||||
|             "start": 0, | ||||
|             "type": "Identifier" | ||||
|           }, | ||||
|           "init": { | ||||
|             "commentStart": 0, | ||||
|             "end": 0, | ||||
|             "expr": { | ||||
|               "commentStart": 0, | ||||
|               "end": 0, | ||||
|               "raw": "10", | ||||
|               "start": 0, | ||||
|               "type": "Literal", | ||||
|               "type": "Literal", | ||||
|               "value": { | ||||
|                 "value": 10.0, | ||||
|                 "suffix": "None" | ||||
|               } | ||||
|             }, | ||||
|             "start": 0, | ||||
|             "ty": { | ||||
|               "commentStart": 0, | ||||
|               "end": 0, | ||||
|               "id": { | ||||
|                 "commentStart": 0, | ||||
|                 "end": 0, | ||||
|                 "name": "NotARealType", | ||||
|                 "start": 0, | ||||
|                 "type": "Identifier" | ||||
|               }, | ||||
|               "p_type": "Named", | ||||
|               "start": 0, | ||||
|               "type": "Primitive" | ||||
|             }, | ||||
|             "type": "AscribedExpression", | ||||
|             "type": "AscribedExpression" | ||||
|           }, | ||||
|           "start": 0, | ||||
|           "type": "VariableDeclarator" | ||||
|         }, | ||||
|         "end": 0, | ||||
|         "kind": "const", | ||||
|         "start": 0, | ||||
|         "type": "VariableDeclaration", | ||||
|         "type": "VariableDeclaration" | ||||
|       } | ||||
|     ], | ||||
|     "commentStart": 0, | ||||
|     "end": 0, | ||||
|     "start": 0 | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Error from executing ascription_unknown_type.kcl | ||||
| --- | ||||
| KCL Semantic error | ||||
|  | ||||
|   × semantic: Unknown type: NotARealType | ||||
|    ╭──── | ||||
|  1 │ z = 10: NotARealType | ||||
|    ·     ─┬ | ||||
|    ·      ╰── tests/ascription_unknown_type/input.kcl | ||||
|    ╰──── | ||||
							
								
								
									
										1
									
								
								rust/kcl-lib/tests/ascription_unknown_type/input.kcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								rust/kcl-lib/tests/ascription_unknown_type/input.kcl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| z = 10: NotARealType | ||||
							
								
								
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ops.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ops.snap
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Operations executed ascription_unknown_type.kcl | ||||
| --- | ||||
| [] | ||||
							
								
								
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/unparsed.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/unparsed.snap
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Result of unparsing ascription_unknown_type.kcl | ||||
| --- | ||||
| z = 10: NotARealType | ||||
| @ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "kcl-python-bindings" | ||||
| version = "0.3.78" | ||||
| version = "0.3.79" | ||||
| edition = "2021" | ||||
| repository = "https://github.com/kittycad/modeling-app" | ||||
| exclude = ["tests/*", "files/*", "venv/*"] | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-test-server" | ||||
| description = "A test server for KCL" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
|  | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-to-core" | ||||
| description = "Utility methods to convert kcl to engine core executable tests" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "kcl-wasm-lib" | ||||
| version = "0.1.78" | ||||
| version = "0.1.79" | ||||
| edition = "2021" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
| rust-version = "1.83" | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use gloo_utils::format::JsValueSerdeExt; | ||||
| use kcl_lib::{wasm_engine::FileManager, EngineManager, Program}; | ||||
| use kcl_lib::{wasm_engine::FileManager, EngineManager, ExecOutcome, KclError, KclErrorWithOutputs, Program}; | ||||
| use wasm_bindgen::prelude::*; | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| @ -56,7 +56,7 @@ impl Context { | ||||
|             return Ok(kcl_lib::ExecutorContext::new_mock( | ||||
|                 self.mock_engine.clone(), | ||||
|                 self.fs.clone(), | ||||
|                 settings.into(), | ||||
|                 settings, | ||||
|             )); | ||||
|         } | ||||
|  | ||||
| @ -74,18 +74,38 @@ impl Context { | ||||
|         program_ast_json: &str, | ||||
|         path: Option<String>, | ||||
|         settings: &str, | ||||
|     ) -> Result<JsValue, String> { | ||||
|     ) -> Result<JsValue, JsValue> { | ||||
|         console_error_panic_hook::set_once(); | ||||
|  | ||||
|         let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?; | ||||
|         self.execute_typed(program_ast_json, path, settings) | ||||
|             .await | ||||
|             .and_then(|outcome| JsValue::from_serde(&outcome).map_err(|e| { | ||||
|                 // The serde-wasm-bindgen does not work here because of weird HashMap issues. | ||||
|                 // DO NOT USE serde_wasm_bindgen::to_value it will break the frontend. | ||||
|                 KclErrorWithOutputs::no_outputs(KclError::internal( | ||||
|                     format!("Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"), | ||||
|                 ))})) | ||||
|             .map_err(|e: KclErrorWithOutputs| JsValue::from_serde(&e).unwrap()) | ||||
|     } | ||||
|  | ||||
|         let ctx = self.create_executor_ctx(settings, path, false)?; | ||||
|         match ctx.run_with_caching(program).await { | ||||
|             // The serde-wasm-bindgen does not work here because of weird HashMap issues. | ||||
|             // DO NOT USE serde_wasm_bindgen::to_value it will break the frontend. | ||||
|             Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()), | ||||
|             Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?), | ||||
|         } | ||||
|     async fn execute_typed( | ||||
|         &self, | ||||
|         program_ast_json: &str, | ||||
|         path: Option<String>, | ||||
|         settings: &str, | ||||
|     ) -> Result<ExecOutcome, KclErrorWithOutputs> { | ||||
|         let program: Program = serde_json::from_str(program_ast_json).map_err(|e| { | ||||
|                 let err = KclError::internal( | ||||
|                     format!("Could not deserialize KCL AST. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"), | ||||
|                 ); | ||||
|                 KclErrorWithOutputs::no_outputs(err) | ||||
|             })?; | ||||
|         let ctx = self | ||||
|                 .create_executor_ctx(settings, path, false) | ||||
|                 .map_err(|e| KclErrorWithOutputs::no_outputs(KclError::internal( | ||||
|                     format!("Could not create KCL executor context. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"), | ||||
|                 )))?; | ||||
|         ctx.run_with_caching(program).await | ||||
|     } | ||||
|  | ||||
|     /// Reset the scene and bust the cache. | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| [toolchain] | ||||
| channel = "1.86" | ||||
| channel = "1.87" | ||||
| components = ["clippy", "rustfmt"] | ||||
|  | ||||
| @ -1315,7 +1315,7 @@ export class CameraControls { | ||||
|     ) | ||||
|     if ( | ||||
|       initialInteractionType === 'rotate' && | ||||
|       this.engineCommandManager.settings.cameraOrbit === 'trackball' | ||||
|       this.getSettings?.().modeling.cameraOrbit.current === 'trackball' | ||||
|     ) { | ||||
|       return 'rotatetrackball' | ||||
|     } | ||||
|  | ||||
| @ -1,8 +1,11 @@ | ||||
| import { useEffect } from 'react' | ||||
| import { useLocation, useNavigate } from 'react-router-dom' | ||||
| import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' | ||||
|  | ||||
| import { isDesktop } from '@src/lib/isDesktop' | ||||
| import { PATHS } from '@src/lib/paths' | ||||
| import { IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM } from '@src/lib/constants' | ||||
| import { useAuthState } from '@src/lib/singletons' | ||||
| import { generateSignInUrl } from '@src/routes/utils' | ||||
|  | ||||
| /** | ||||
|  * A simple hook that listens to the auth state of the app and navigates | ||||
| @ -12,6 +15,10 @@ export function useAuthNavigation() { | ||||
|   const navigate = useNavigate() | ||||
|   const location = useLocation() | ||||
|   const authState = useAuthState() | ||||
|   const [searchParams] = useSearchParams() | ||||
|   const requestingImmediateSignInIfNecessary = searchParams.has( | ||||
|     IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM | ||||
|   ) | ||||
|  | ||||
|   // Subscribe to the auth state of the app and navigate accordingly. | ||||
|   useEffect(() => { | ||||
| @ -24,6 +31,11 @@ export function useAuthNavigation() { | ||||
|       authState.matches('loggedOut') && | ||||
|       !location.pathname.includes(PATHS.SIGN_IN) | ||||
|     ) { | ||||
|       if (requestingImmediateSignInIfNecessary && !isDesktop()) { | ||||
|         window.location.href = generateSignInUrl() | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       navigate(PATHS.SIGN_IN + (location.search || '')) | ||||
|     } | ||||
|   }, [authState, location.pathname]) | ||||
|  | ||||
| @ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom' | ||||
| import { base64ToString } from '@src/lib/base64' | ||||
| import type { ProjectsCommandSchema } from '@src/lib/commandBarConfigs/projectsCommandConfig' | ||||
| import { | ||||
|   ASK_TO_OPEN_QUERY_PARAM, | ||||
|   CMD_GROUP_QUERY_PARAM, | ||||
|   CMD_NAME_QUERY_PARAM, | ||||
|   CREATE_FILE_URL_PARAM, | ||||
| @ -36,8 +37,15 @@ export type CreateFileSchemaMethodOptional = Omit< | ||||
| export function useQueryParamEffects() { | ||||
|   const authState = useAuthState() | ||||
|   const [searchParams, setSearchParams] = useSearchParams() | ||||
|   const shouldInvokeCreateFile = searchParams.has(CREATE_FILE_URL_PARAM) | ||||
|   const hasAskToOpen = !isDesktop() && searchParams.has(ASK_TO_OPEN_QUERY_PARAM) | ||||
|   // Let hasAskToOpen be handled by the OpenInDesktopAppHandler component first to avoid racing with it, | ||||
|   // only deal with other params after user decided to open in desktop or web. | ||||
|   // Without this the "Zoom to fit to shared model on web" test fails, although manually testing works due | ||||
|   // to different timings. | ||||
|   const shouldInvokeCreateFile = | ||||
|     !hasAskToOpen && searchParams.has(CREATE_FILE_URL_PARAM) | ||||
|   const shouldInvokeGenericCmd = | ||||
|     !hasAskToOpen && | ||||
|     searchParams.has(CMD_NAME_QUERY_PARAM) && | ||||
|     searchParams.has(CMD_GROUP_QUERY_PARAM) | ||||
|  | ||||
|  | ||||
| @ -245,6 +245,38 @@ ${insertCode} | ||||
| }) | ||||
|  | ||||
| describe('testing getConstraintInfo', () => { | ||||
|   describe('when user edits KCL to be invalid', () => { | ||||
|     const code = `part001 = startSketchOn(XZ) | ||||
|   |> startProfile(at = [0,]) // Missing y coordinate | ||||
|   |> line(end = [3, 4])` | ||||
|     test.each([ | ||||
|       [ | ||||
|         'startProfile', | ||||
|         [ | ||||
|           // No constraints | ||||
|         ], | ||||
|       ], | ||||
|     ])('testing %s when inputs are unconstrained', (functionName, expected) => { | ||||
|       const ast = assertParse(code) | ||||
|       const match = new RegExp(functionName).exec(code) | ||||
|       expect(match).toBeTruthy() | ||||
|       if (match === null) { | ||||
|         return | ||||
|       } | ||||
|       const start = code.indexOf(match[0]) | ||||
|       expect(start).toBeGreaterThanOrEqual(0) | ||||
|       const sourceRange = topLevelRange(start, start + functionName.length) | ||||
|       if (err(ast)) return ast | ||||
|       const pathToNode = getNodePathFromSourceRange(ast, sourceRange) | ||||
|       const callExp = getNodeFromPath<Node<CallExpressionKw>>(ast, pathToNode, [ | ||||
|         'CallExpressionKw', | ||||
|       ]) | ||||
|       if (err(callExp)) return callExp | ||||
|       const result = getConstraintInfoKw(callExp.node, code, pathToNode) | ||||
|       expect(result).toEqual(expected) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   describe('object notation', () => { | ||||
|     const code = `part001 = startSketchOn(-XZ) | ||||
|   |> startProfile(at = [0,0]) | ||||
|  | ||||
| @ -1183,7 +1183,7 @@ export const startProfile: SketchLineHelperKw = { | ||||
|       return [] | ||||
|     } | ||||
|     const argIndex = findKwArgAnyIndex([ARG_AT], callExp) | ||||
|     if (argIndex === undefined) { | ||||
|     if (argIndex === undefined || expr.elements.length < 2) { | ||||
|       return [] | ||||
|     } | ||||
|     const pathToXYArray: PathToNode = [ | ||||
| @ -1471,7 +1471,9 @@ export const circle: SketchLineHelperKw = { | ||||
|           key: ARG_RADIUS, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|     ] | ||||
|     if (centerInfo.expr.elements.length >= 2) { | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circle', | ||||
|         type: 'xAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(centerInfo.expr.elements[0]), | ||||
| @ -1489,8 +1491,8 @@ export const circle: SketchLineHelperKw = { | ||||
|           index: 0, | ||||
|           key: ARG_CIRCLE_CENTER, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|       }) | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circle', | ||||
|         type: 'yAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(centerInfo.expr.elements[1]), | ||||
| @ -1508,8 +1510,8 @@ export const circle: SketchLineHelperKw = { | ||||
|           index: 1, | ||||
|           key: 'center', | ||||
|         }, | ||||
|       }, | ||||
|     ] | ||||
|       }) | ||||
|     } | ||||
|     return constraints | ||||
|   }, | ||||
| } | ||||
| @ -2256,134 +2258,103 @@ export const circleThreePoint: SketchLineHelperKw = { | ||||
|     const pathToP3XArg: PathToNode = [...pathToP3ArrayExpression, [0, 'index']] | ||||
|     const pathToP3YArg: PathToNode = [...pathToP3ArrayExpression, [1, 'index']] | ||||
|  | ||||
|     const constraints: (ConstrainInfo & { filterValue: string })[] = [ | ||||
|       { | ||||
|     const constraints: (ConstrainInfo & { filterValue: string })[] = [] | ||||
|     if (p1Details.expr.elements.length >= 2) { | ||||
|       const p1XArg = p1Details.expr.elements[0] | ||||
|       const p1YArg = p1Details.expr.elements[1] | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circleThreePoint', | ||||
|         type: 'xAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[0]), | ||||
|         sourceRange: [ | ||||
|           p1Details.expr.elements[0].start, | ||||
|           p1Details.expr.elements[0].end, | ||||
|           0, | ||||
|         ], | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p1XArg), | ||||
|         sourceRange: topLevelRange(p1XArg.start, p1XArg.end), | ||||
|         pathToNode: pathToP1XArg, | ||||
|         value: code.slice( | ||||
|           p1Details.expr.elements[0].start, | ||||
|           p1Details.expr.elements[0].end | ||||
|         ), | ||||
|         value: code.slice(p1XArg.start, p1XArg.end), | ||||
|         argPosition: { | ||||
|           type: 'labeledArgArrayItem', | ||||
|           index: 0, | ||||
|           key: 'p1', | ||||
|         }, | ||||
|         filterValue: 'p1', | ||||
|       }, | ||||
|       { | ||||
|       }) | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circleThreePoint', | ||||
|         type: 'yAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[1]), | ||||
|         sourceRange: [ | ||||
|           p1Details.expr.elements[1].start, | ||||
|           p1Details.expr.elements[1].end, | ||||
|           0, | ||||
|         ], | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p1YArg), | ||||
|         sourceRange: topLevelRange(p1YArg.start, p1YArg.end), | ||||
|         pathToNode: pathToP1YArg, | ||||
|         value: code.slice( | ||||
|           p1Details.expr.elements[1].start, | ||||
|           p1Details.expr.elements[1].end | ||||
|         ), | ||||
|         value: code.slice(p1YArg.start, p1YArg.end), | ||||
|         argPosition: { | ||||
|           type: 'labeledArgArrayItem', | ||||
|           index: 1, | ||||
|           key: 'p1', | ||||
|         }, | ||||
|         filterValue: 'p1', | ||||
|       }, | ||||
|       { | ||||
|       }) | ||||
|     } | ||||
|     if (p2Details.expr.elements.length >= 2) { | ||||
|       const p2XArg = p2Details.expr.elements[0] | ||||
|       const p2YArg = p2Details.expr.elements[1] | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circleThreePoint', | ||||
|         type: 'xAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[0]), | ||||
|         sourceRange: [ | ||||
|           p2Details.expr.elements[0].start, | ||||
|           p2Details.expr.elements[0].end, | ||||
|           0, | ||||
|         ], | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p2XArg), | ||||
|         sourceRange: topLevelRange(p2XArg.start, p2XArg.end), | ||||
|         pathToNode: pathToP2XArg, | ||||
|         value: code.slice( | ||||
|           p2Details.expr.elements[0].start, | ||||
|           p2Details.expr.elements[0].end | ||||
|         ), | ||||
|         value: code.slice(p2XArg.start, p2XArg.end), | ||||
|         argPosition: { | ||||
|           type: 'labeledArgArrayItem', | ||||
|           index: 0, | ||||
|           key: 'p2', | ||||
|         }, | ||||
|         filterValue: 'p2', | ||||
|       }, | ||||
|       { | ||||
|       }) | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circleThreePoint', | ||||
|         type: 'yAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[1]), | ||||
|         sourceRange: [ | ||||
|           p2Details.expr.elements[1].start, | ||||
|           p2Details.expr.elements[1].end, | ||||
|           0, | ||||
|         ], | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p2YArg), | ||||
|         sourceRange: topLevelRange(p2YArg.start, p2YArg.end), | ||||
|         pathToNode: pathToP2YArg, | ||||
|         value: code.slice( | ||||
|           p2Details.expr.elements[1].start, | ||||
|           p2Details.expr.elements[1].end | ||||
|         ), | ||||
|         value: code.slice(p2YArg.start, p2YArg.end), | ||||
|         argPosition: { | ||||
|           type: 'labeledArgArrayItem', | ||||
|           index: 1, | ||||
|           key: 'p2', | ||||
|         }, | ||||
|         filterValue: 'p2', | ||||
|       }, | ||||
|       { | ||||
|       }) | ||||
|     } | ||||
|     if (p3Details.expr.elements.length >= 2) { | ||||
|       const p3XArg = p3Details.expr.elements[0] | ||||
|       const p3YArg = p3Details.expr.elements[1] | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circleThreePoint', | ||||
|         type: 'xAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[0]), | ||||
|         sourceRange: [ | ||||
|           p3Details.expr.elements[0].start, | ||||
|           p3Details.expr.elements[0].end, | ||||
|           0, | ||||
|         ], | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p3XArg), | ||||
|         sourceRange: topLevelRange(p3XArg.start, p3XArg.end), | ||||
|         pathToNode: pathToP3XArg, | ||||
|         value: code.slice( | ||||
|           p3Details.expr.elements[0].start, | ||||
|           p3Details.expr.elements[0].end | ||||
|         ), | ||||
|         value: code.slice(p3XArg.start, p3XArg.end), | ||||
|         argPosition: { | ||||
|           type: 'labeledArgArrayItem', | ||||
|           index: 0, | ||||
|           key: 'p3', | ||||
|         }, | ||||
|         filterValue: 'p3', | ||||
|       }, | ||||
|       { | ||||
|       }) | ||||
|       constraints.push({ | ||||
|         stdLibFnName: 'circleThreePoint', | ||||
|         type: 'yAbsolute', | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[1]), | ||||
|         sourceRange: [ | ||||
|           p3Details.expr.elements[1].start, | ||||
|           p3Details.expr.elements[1].end, | ||||
|           0, | ||||
|         ], | ||||
|         isConstrained: isNotLiteralArrayOrStatic(p3YArg), | ||||
|         sourceRange: topLevelRange(p3YArg.start, p3YArg.end), | ||||
|         pathToNode: pathToP3YArg, | ||||
|         value: code.slice( | ||||
|           p3Details.expr.elements[1].start, | ||||
|           p3Details.expr.elements[1].end | ||||
|         ), | ||||
|         value: code.slice(p3YArg.start, p3YArg.end), | ||||
|         argPosition: { | ||||
|           type: 'labeledArgArrayItem', | ||||
|           index: 1, | ||||
|           key: 'p3', | ||||
|         }, | ||||
|         filterValue: 'p3', | ||||
|       }, | ||||
|     ] | ||||
|       }) | ||||
|     } | ||||
|     const finalConstraints: ConstrainInfo[] = [] | ||||
|     constraints.forEach((constraint) => { | ||||
|       if (!filterValue) { | ||||
|  | ||||
| @ -389,7 +389,20 @@ export function sketchFromKclValue( | ||||
| } | ||||
|  | ||||
| export const errFromErrWithOutputs = (e: any): KCLError => { | ||||
|   const parsed: KclErrorWithOutputs = JSON.parse(e.toString()) | ||||
|   // `e` is any, so let's figure out something useful to do with it. | ||||
|   const parsed: KclErrorWithOutputs = (() => { | ||||
|     // No need to parse, it's already an object. | ||||
|     if (typeof e === 'object') { | ||||
|       return e | ||||
|     } | ||||
|     // It's a string, so parse it. | ||||
|     if (typeof e === 'string') { | ||||
|       return JSON.parse(e) | ||||
|     } | ||||
|     // It can be converted to a string, then parsed. | ||||
|     return JSON.parse(e.toString()) | ||||
|   })() | ||||
|  | ||||
|   return new KCLError( | ||||
|     parsed.error.kind, | ||||
|     parsed.error.details.msg, | ||||
|  | ||||
| @ -218,3 +218,6 @@ export const POOL_QUERY_PARAM = 'pool' | ||||
|  * @deprecated: supporting old share links with this. For new command URLs, use "cmd" | ||||
|  */ | ||||
| export const CREATE_FILE_URL_PARAM = 'create-file' | ||||
| /** A query parameter to skip the sign-on view if unnecessary. */ | ||||
| export const IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM = | ||||
|   'immediate-sign-in-if-necessary' | ||||
|  | ||||
| @ -10,12 +10,11 @@ import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env' | ||||
| import { APP_NAME } from '@src/lib/constants' | ||||
| import { isDesktop } from '@src/lib/isDesktop' | ||||
| import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' | ||||
| import { PATHS } from '@src/lib/paths' | ||||
| import { Themes, getSystemTheme } from '@src/lib/theme' | ||||
| import { reportRejection } from '@src/lib/trap' | ||||
| import { toSync } from '@src/lib/utils' | ||||
| import { authActor, useSettings } from '@src/lib/singletons' | ||||
| import { APP_VERSION } from '@src/routes/utils' | ||||
| import { APP_VERSION, generateSignInUrl } from '@src/routes/utils' | ||||
|  | ||||
| const subtleBorder = | ||||
|   'border border-solid border-chalkboard-30 dark:border-chalkboard-80' | ||||
| @ -36,11 +35,7 @@ const SignIn = () => { | ||||
|   const { | ||||
|     app: { theme }, | ||||
|   } = useSettings() | ||||
|   const signInUrl = `${VITE_KC_SITE_BASE_URL}${ | ||||
|     PATHS.SIGN_IN | ||||
|   }?callbackUrl=${encodeURIComponent( | ||||
|     typeof window !== 'undefined' && window.location.href.replace('signin', '') | ||||
|   )}` | ||||
|   const signInUrl = generateSignInUrl() | ||||
|   const kclSampleUrl = `${VITE_KC_SITE_BASE_URL}/docs/kcl-samples/car-wheel-assembly` | ||||
|  | ||||
|   const getThemeText = useCallback( | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { NODE_ENV } from '@src/env' | ||||
| import { NODE_ENV, VITE_KC_SITE_BASE_URL } from '@src/env' | ||||
| import { isDesktop } from '@src/lib/isDesktop' | ||||
| import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants' | ||||
| import { PATHS } from '@src/lib/paths' | ||||
|  | ||||
| const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true' | ||||
|  | ||||
| @ -27,3 +28,11 @@ export function getReleaseUrl(version: string = APP_VERSION) { | ||||
|  | ||||
|   return `https://github.com/KittyCAD/modeling-app/releases/tag/v${version}` | ||||
| } | ||||
|  | ||||
| export function generateSignInUrl() { | ||||
|   return `${VITE_KC_SITE_BASE_URL}${ | ||||
|     PATHS.SIGN_IN | ||||
|   }?callbackUrl=${encodeURIComponent( | ||||
|     typeof window !== 'undefined' && window.location.href.replace('signin', '') | ||||
|   )}` | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user