diff --git a/e2e/playwright/testing-selections.spec.ts b/e2e/playwright/testing-selections.spec.ts index 840715bd2a..9a9d8783eb 100644 --- a/e2e/playwright/testing-selections.spec.ts +++ b/e2e/playwright/testing-selections.spec.ts @@ -862,4 +862,40 @@ part001 = startSketchOn(XZ) ) previousCodeContent = await page.locator('.cm-content').innerText() }) + + test('"View KCL source code" right click menu in scene', async ({ + page, + homePage, + scene, + cmdBar, + }) => { + await page.setBodyDimensions({ width: 1200, height: 500 }) + const middleX = 1200 / 2 + const middleY = 500 / 2 + + await homePage.goToModelingScene() + await scene.settled(cmdBar) + + await test.step('Empty scene should have disabled "View KCL source code"', async () => { + // Right-click in empty scene + await page.mouse.click(middleX, middleY, { button: 'right' }) + + // Verify context menu appears + await expect(page.getByTestId('view-controls-menu')).toBeVisible() + + // "View KCL source code" should be disabled in empty scene + const menuItems = page.locator( + '[data-testid="view-controls-menu"] button' + ) + const viewKclSourceCodeOption = menuItems.filter({ + hasText: 'View KCL source code', + }) + await expect(viewKclSourceCodeOption).toBeVisible() + await expect(viewKclSourceCodeOption).toBeDisabled() + }) + + // TODO: add this step for happy path, the bracket code is long enough for this + // await test.step('Right click on bracket sample leads to the right place in code', async () => { + // }) + }) }) diff --git a/src/components/ViewControlMenu.tsx b/src/components/ViewControlMenu.tsx index 8f7b213951..a3b8239818 100644 --- a/src/components/ViewControlMenu.tsx +++ b/src/components/ViewControlMenu.tsx @@ -19,6 +19,7 @@ import { selectOffsetSketchPlane, } from '@src/lib/selections' import { getSelectedPlaneId } from '@src/lang/queryAst' +import toast from 'react-hot-toast' export function useViewControlMenuItems() { const { state: modelingState, send: modelingSend } = useModelingContext() @@ -30,6 +31,20 @@ export function useViewControlMenuItems() { const shouldLockView = modelingState.matches('Sketch') && !settings.app.allowOrbitInSketchMode.current + + // Check if there's a valid selection with source range for "View KCL source code" + const firstValidSelection = useMemo(() => { + return modelingState.context.selectionRanges.graphSelections.find( + (selection) => { + return ( + selection.codeRef?.range && + selection.codeRef.range[0] !== undefined && + selection.codeRef.range[1] !== undefined + ) + } + ) + }, [modelingState.context.selectionRanges.graphSelections]) + const menuItems = useMemo( () => [ ...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => ( @@ -64,6 +79,41 @@ export function useViewControlMenuItems() { > Center view on selection , + { + if (firstValidSelection?.codeRef?.range) { + // First, open the code pane if it's not already open + if (!modelingState.context.store.openPanes.includes('code')) { + modelingSend({ + type: 'Set context', + data: { + openPanes: [...modelingState.context.store.openPanes, 'code'], + }, + }) + } + + // Navigate to the source code location + modelingSend({ + type: 'Set selection', + data: { + selectionType: 'singleCodeCursor', + selection: { + artifact: firstValidSelection.artifact, + codeRef: firstValidSelection.codeRef, + }, + scrollIntoView: true, + }, + }) + } else { + toast.error( + 'No valid selection with source range found. Please select a valid element.' + ) + } + }} + disabled={!firstValidSelection} + > + View KCL source code + , , { @@ -94,7 +144,13 @@ export function useViewControlMenuItems() { , ], // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: blanket-ignored fix me! - [VIEW_NAMES_SEMANTIC, shouldLockView, selectedPlaneId] + [ + VIEW_NAMES_SEMANTIC, + shouldLockView, + selectedPlaneId, + firstValidSelection, + modelingState.context.store.openPanes, + ] ) return menuItems }