Assemblies: UX improvements around foreign file imports (#6159)

* WIP: Add point-and-click Import for geometry
Will eventually fix #6120
Right now the whole loop is there but the codemod doesn't work yet

* Better pathToNOde, log on non-working cm dispatch call

* Add workaround to updateModelingState not working

* Back to updateModelingState with a skip flag

* Better todo

* Change working from Import to Insert, cleanups

* Sister command in kclCommands to populate file options

* Improve path selector

* Unsure: move importAstMod to kclCommands onSubmit 😶

* Add e2e test

* Clean up for review

* Add native file menu entry and test

* No await yo lint said so

* WIP: UX improvements around foreign file imports
Fixes #6152

* @lrev-Dev's suggestion to remove a comment

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

* Update to scene.settled(cmdBar)

* Add partNNN default name for alias

* Lint

* Lint

* Fix unit tests

* Add sad path insert test
Thanks @Irev-Dev for the suggestion

* Add step insert test

* Lint

* Add test for second foreign import thru file tree click

* Add default value for local name alias

* Aligning tests

* Fix tests

* Add padding for filenames starting with a digit

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit is contained in:
Pierre Jacquier
2025-04-09 07:47:57 -04:00
committed by GitHub
parent ae9d8be4e4
commit e78100eaac
12 changed files with 454 additions and 104 deletions

View File

@ -1,4 +1,5 @@
import { useEffect, useRef } from 'react'
import { useSelector } from '@xstate/react'
import { useEffect, useMemo, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import type { CommandArgument } from '@src/lib/commandTypes'
@ -6,6 +7,11 @@ import {
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import type { AnyStateMachine, SnapshotFrom } from 'xstate'
// TODO: remove the need for this selector once we decouple all actors from React
const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) =>
snapshot?.context
function CommandBarBasicInput({
arg,
@ -22,6 +28,19 @@ function CommandBarBasicInput({
const commandBarState = useCommandBarState()
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const inputRef = useRef<HTMLInputElement>(null)
const argMachineContext = useSelector(
arg.machineActor,
machineContextSelector
)
const defaultValue = useMemo(
() =>
arg.defaultValue
? arg.defaultValue instanceof Function
? arg.defaultValue(commandBarState.context, argMachineContext)
: arg.defaultValue
: '',
[arg.defaultValue, commandBarState.context, argMachineContext]
)
useEffect(() => {
if (inputRef.current) {
@ -53,11 +72,7 @@ function CommandBarBasicInput({
required
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
placeholder="Enter a value"
defaultValue={
(commandBarState.context.argumentsToSubmit[arg.name] as
| string
| undefined) || (arg.defaultValue as string)
}
defaultValue={defaultValue}
onKeyDown={(event) => {
if (event.key === 'Backspace' && event.shiftKey) {
stepBack()

View File

@ -19,7 +19,7 @@ import { useKclContext } from '@src/lang/KclProvider'
import type { KCLError } from '@src/lang/errors'
import { kclErrorsByFilename } from '@src/lang/errors'
import { normalizeLineEndings } from '@src/lib/codeEditor'
import { FILE_EXT } from '@src/lib/constants'
import { FILE_EXT, INSERT_FOREIGN_TOAST_ID } from '@src/lib/constants'
import { sortFilesAndDirectories } from '@src/lib/desktopFS'
import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
import { PATHS } from '@src/lib/paths'
@ -28,6 +28,9 @@ import { codeManager, kclManager } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap'
import type { IndexLoaderData } from '@src/lib/types'
import { ToastInsert } from '@src/components/ToastInsert'
import { commandBarActor } from '@src/machines/commandBarMachine'
import toast from 'react-hot-toast'
import styles from './FileTree.module.css'
function getIndentationCSS(level: number) {
@ -264,16 +267,26 @@ const FileTreeItem = ({
if (fileOrDir.children !== null) return // Don't open directories
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
// Import non-kcl files
// We want to update both the state and editor here.
codeManager.updateCodeStateEditor(
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
codeManager.code
toast.custom(
ToastInsert({
onInsert: () => {
const relativeFilePath = fileOrDir.path.replace(
project.path + window.electron.sep,
''
)
commandBarActor.send({
type: 'Find and select command',
data: {
name: 'Insert',
groupId: 'code',
argDefaultValues: { path: relativeFilePath },
},
})
toast.dismiss(INSERT_FOREIGN_TOAST_ID)
},
}),
{ duration: 30000, id: INSERT_FOREIGN_TOAST_ID }
)
await codeManager.writeToFile()
// Prevent seeing the model built one piece at a time when changing files
await kclManager.executeCode()
} else {
// Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null)

View File

@ -0,0 +1,40 @@
import toast from 'react-hot-toast'
import { ActionButton } from '@src/components/ActionButton'
export function ToastInsert({ onInsert }: { onInsert: () => void }) {
return (
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<p className="text-md">
Non-KCL files aren't editable here in Zoo Studio, but you may insert
them using the button below or the Insert command.
</p>
<div className="mt-4 flex justify-between gap-8">
<ActionButton
Element="button"
iconStart={{
icon: 'checkmark',
}}
name="insert"
onClick={onInsert}
>
Insert into my current file
</ActionButton>
<ActionButton
Element="button"
iconStart={{
icon: 'close',
}}
name="dismiss"
onClick={() => {
toast.dismiss()
}}
>
Dismiss
</ActionButton>
</div>
</div>
</div>
)
}