[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'
|
} from '@src/editor/highlightextension'
|
||||||
import type { KclManager } from '@src/lang/KclSingleton'
|
import type { KclManager } from '@src/lang/KclSingleton'
|
||||||
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
|
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
|
||||||
|
import { isTopLevelModule } from '@src/lang/util'
|
||||||
import { markOnce } from '@src/lib/performance'
|
import { markOnce } from '@src/lib/performance'
|
||||||
import type { Selection, Selections } from '@src/lib/selections'
|
import type { Selection, Selections } from '@src/lib/selections'
|
||||||
import { processCodeMirrorRanges } from '@src/lib/selections'
|
import { processCodeMirrorRanges } from '@src/lib/selections'
|
||||||
@ -151,12 +152,12 @@ export default class EditorManager {
|
|||||||
selection: Array<Selection['codeRef']['range']>
|
selection: Array<Selection['codeRef']['range']>
|
||||||
): Array<[number, number]> {
|
): Array<[number, number]> {
|
||||||
if (!this._editorView) {
|
if (!this._editorView) {
|
||||||
return selection.map((s): [number, number] => {
|
return selection.filter(isTopLevelModule).map((s): [number, number] => {
|
||||||
return [s[0], s[1]]
|
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])
|
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
||||||
return [s[0], safeEnd]
|
return [s[0], safeEnd]
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
import { showSketchOnImportToast } from '@src/components/SketchOnImportToast'
|
||||||
import { useModelingContext } from '@src/hooks/useModelingContext'
|
import { useModelingContext } from '@src/hooks/useModelingContext'
|
||||||
import { getNodeFromPath } from '@src/lang/queryAst'
|
import { getNodeFromPath } from '@src/lang/queryAst'
|
||||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||||
@ -24,10 +25,12 @@ import {
|
|||||||
sceneInfra,
|
sceneInfra,
|
||||||
} from '@src/lib/singletons'
|
} from '@src/lib/singletons'
|
||||||
import { err, reportRejection } from '@src/lib/trap'
|
import { err, reportRejection } from '@src/lib/trap'
|
||||||
|
import { getModuleId } from '@src/lib/utils'
|
||||||
import type {
|
import type {
|
||||||
EdgeCutInfo,
|
EdgeCutInfo,
|
||||||
ExtrudeFacePlane,
|
ExtrudeFacePlane,
|
||||||
} from '@src/machines/modelingMachine'
|
} from '@src/machines/modelingMachine'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
export function useEngineConnectionSubscriptions() {
|
export function useEngineConnectionSubscriptions() {
|
||||||
const { send, context, state } = useModelingContext()
|
const { send, context, state } = useModelingContext()
|
||||||
@ -186,6 +189,29 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
faceId,
|
faceId,
|
||||||
kclManager.artifactGraph
|
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 (
|
if (
|
||||||
artifact?.type !== 'cap' &&
|
artifact?.type !== 'cap' &&
|
||||||
|
@ -469,3 +469,7 @@ export function binaryToUuid(
|
|||||||
hexValues.slice(10, 16).join(''),
|
hexValues.slice(10, 16).join(''),
|
||||||
].join('-')
|
].join('-')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getModuleId(sourceRange: SourceRange) {
|
||||||
|
return sourceRange[2]
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user