[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:
120
e2e/playwright/import-ui.spec.ts
Normal file
120
e2e/playwright/import-ui.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
21
src/components/SketchOnImportToast.tsx
Normal file
21
src/components/SketchOnImportToast.tsx
Normal 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} />)
|
||||
}
|
@ -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]
|
||||
})
|
||||
|
@ -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' &&
|
||||
|
@ -469,3 +469,7 @@ export function binaryToUuid(
|
||||
hexValues.slice(10, 16).join(''),
|
||||
].join('-')
|
||||
}
|
||||
|
||||
export function getModuleId(sourceRange: SourceRange) {
|
||||
return sourceRange[2]
|
||||
}
|
||||
|
Reference in New Issue
Block a user