[Bug] fix some UI friction from imports (#6139)

* fix some UI friction from imports

* add test

* console

* Jon's comments
This commit is contained in:
Kurt Hutten
2025-04-04 19:38:53 +11:00
committed by GitHub
parent 45e5b25cda
commit f1e95156ea
5 changed files with 174 additions and 2 deletions

View File

@ -0,0 +1,120 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import * as fsp from 'fs/promises'
import path from 'path'
test.describe('Import UI tests', () => {
test('shows toast when trying to sketch on imported face', async ({
context,
page,
homePage,
toolbar,
scene,
editor,
}) => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'import-test')
await fsp.mkdir(projectDir, { recursive: true })
// Create the imported file
await fsp.writeFile(
path.join(projectDir, 'toBeImported.kcl'),
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([281.54, 305.81], sketch001)
|> angledLine([0, 123.43], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
85.99
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude(profile001, length = 100)`
)
// Create the main file that imports
await fsp.writeFile(
path.join(projectDir, 'main.kcl'),
`import "toBeImported.kcl" as importedCube
importedCube
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([-134.53, -56.17], sketch001)
|> angledLine([0, 79.05], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
76.28
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(profile001, length = 100)
sketch003 = startSketchOn(extrude001, seg02)
sketch002 = startSketchOn(extrude001, seg01)`
)
})
await homePage.openProject('import-test')
await scene.waitForExecutionDone()
await scene.moveCameraTo(
{
x: -114,
y: -897,
z: 475,
},
{
x: -114,
y: -51,
z: 83,
}
)
const [_, hoverOverNonImport] = scene.makeMouseHelpers(611, 364)
const [importedFaceClick, hoverOverImported] = scene.makeMouseHelpers(
940,
150
)
await test.step('check code highlight works for code define in the file being edited', async () => {
await hoverOverNonImport()
await editor.expectState({
highlightedCode: 'startProfileAt([-134.53,-56.17],sketch001)',
diagnostics: [],
activeLines: ['import"toBeImported.kcl"asimportedCube'],
})
})
await test.step('check code does nothing when geometry is defined in an import', async () => {
await hoverOverImported()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: ['import"toBeImported.kcl"asimportedCube'],
})
})
await test.step('check the user is warned when sketching on a imported face', async () => {
// Start sketch mode
await toolbar.startSketchPlaneSelection()
// Click on a face from the imported model
// await new Promise(() => {})
await importedFaceClick()
// Verify toast appears with correct content
await expect(page.getByText('This face is from an import')).toBeVisible()
await expect(
page.locator('.font-mono').getByText('toBeImported.kcl')
).toBeVisible()
await expect(
page.getByText('Please select this from the files pane to edit')
).toBeVisible()
})
})
})

View File

@ -0,0 +1,21 @@
import toast from 'react-hot-toast'
interface SketchOnImportToastProps {
fileName: string
}
export function SketchOnImportToast({ fileName }: SketchOnImportToastProps) {
return (
<div className="flex flex-col gap-2">
<span>This face is from an import</span>
<span className="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">
{fileName}
</span>
<span>Please select this from the files pane to edit</span>
</div>
)
}
export function showSketchOnImportToast(fileName: string) {
toast.error(<SketchOnImportToast fileName={fileName} />)
}

View File

@ -13,6 +13,7 @@ import {
} from '@src/editor/highlightextension'
import type { KclManager } from '@src/lang/KclSingleton'
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
import { isTopLevelModule } from '@src/lang/util'
import { markOnce } from '@src/lib/performance'
import type { Selection, Selections } from '@src/lib/selections'
import { processCodeMirrorRanges } from '@src/lib/selections'
@ -151,12 +152,12 @@ export default class EditorManager {
selection: Array<Selection['codeRef']['range']>
): Array<[number, number]> {
if (!this._editorView) {
return selection.map((s): [number, number] => {
return selection.filter(isTopLevelModule).map((s): [number, number] => {
return [s[0], s[1]]
})
}
return selection.map((s): [number, number] => {
return selection.filter(isTopLevelModule).map((s): [number, number] => {
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
return [s[0], safeEnd]
})

View File

@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react'
import { showSketchOnImportToast } from '@src/components/SketchOnImportToast'
import { useModelingContext } from '@src/hooks/useModelingContext'
import { getNodeFromPath } from '@src/lang/queryAst'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
@ -24,10 +25,12 @@ import {
sceneInfra,
} from '@src/lib/singletons'
import { err, reportRejection } from '@src/lib/trap'
import { getModuleId } from '@src/lib/utils'
import type {
EdgeCutInfo,
ExtrudeFacePlane,
} from '@src/machines/modelingMachine'
import toast from 'react-hot-toast'
export function useEngineConnectionSubscriptions() {
const { send, context, state } = useModelingContext()
@ -186,6 +189,29 @@ export function useEngineConnectionSubscriptions() {
faceId,
kclManager.artifactGraph
)
if (!err(extrusion)) {
const fileIndex = getModuleId(extrusion.codeRef.range)
if (fileIndex !== 0) {
const importDetails =
kclManager.execState.filenames[fileIndex]
if (!importDetails) {
toast.error("can't sketch on this face")
return
}
if (importDetails?.type === 'Local') {
const paths = importDetails.value.split('/')
const fileName = paths[paths.length - 1]
showSketchOnImportToast(fileName)
} else if (
importDetails?.type === 'Main' ||
importDetails?.type === 'Std'
) {
toast.error("can't sketch on this face")
} else {
const _exhaustiveCheck: never = importDetails
}
}
}
if (
artifact?.type !== 'cap' &&

View File

@ -469,3 +469,7 @@ export function binaryToUuid(
hexValues.slice(10, 16).join(''),
].join('-')
}
export function getModuleId(sourceRange: SourceRange) {
return sourceRange[2]
}