#6793 Fix file, folder renaming issues (#6869)

* fix going into rename mode for files with parents

* lastDirectoryClicked is not used

* fix file/folder rename bugs: renaming within folders

* Turn form into div to fix issue of child renaming continues into renaming the parent folder when hitting enter

* ContextMenu stopPropagation not needed anymore, maybe because of form refactor

* ContextMenu IS needed actually, with multiple nested folders

* make lint happy

* Update src/components/ContextMenu.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* re-add <form> instead of <div> for file renaming

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
This commit is contained in:
Andrew Varga
2025-05-12 21:59:10 +02:00
committed by GitHub
parent 566c9eaf10
commit 1a8f80a7dc
4 changed files with 41 additions and 19 deletions

View File

@ -43,6 +43,10 @@ export function ContextMenu({
(e: globalThis.MouseEvent) => {
if (guard && !guard(e)) return
e.preventDefault()
// This stopPropagation is needed in case multiple nested items use a separate context menu each,
// which would cause the parent context menu to receive the event even if the child was clicked on.
// Eg. this happens in FileTree causing a bug where the parent folder is going into renaming mode when trying to rename a child.
e.stopPropagation()
setPosition({ x: e.clientX, y: e.clientY })
setOpen(true)
},

View File

@ -273,11 +273,11 @@ export const FileMachineProvider = ({
: newName + FILE_EXT
: DEFAULT_FILE_NAME
const oldPath = window.electron.path.join(
input.selectedDirectory.path,
input.parentDirectory.path,
oldName
)
const newPath = window.electron.path.join(
input.selectedDirectory.path,
input.parentDirectory.path,
name
)

View File

@ -69,37 +69,57 @@ function TreeEntryInput(props: {
function RenameForm({
fileOrDir,
parentDir,
onSubmit,
level = 0,
}: {
fileOrDir: FileEntry
parentDir: FileEntry | undefined
onSubmit: () => void
level?: number
}) {
const { send } = useFileContext()
const inputRef = useRef<HTMLInputElement>(null)
const fileContext = useFileContext()
function handleRenameSubmit(e: React.KeyboardEvent<HTMLElement>) {
if (e.key !== 'Enter') {
return
}
function handleRenameSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
send({
type: 'Rename file',
data: {
oldName: fileOrDir.name || '',
newName: inputRef.current?.value || fileOrDir.name || '',
isDir: fileOrDir.children !== null,
parentDirectory: parentDir ?? fileContext.context.project,
},
})
// To get out of the renaming state, without this the current file is still in renaming mode
onSubmit()
}
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Escape') {
e.stopPropagation()
onSubmit()
} else if (e.key === 'Enter') {
// This is needed to prevent events to bubble up and the form to be submitted.
// (Alternatively the form could be changed into a div.)
// Bug without this:
// - open a parent folder (close and open if it's already open)
// - right click -> rename one of its children
// - give new name and press enter
// -> new name is not applied, old name is reverted
e.preventDefault()
e.stopPropagation()
}
}
return (
<form onSubmit={handleRenameSubmit}>
<form onKeyUp={handleRenameSubmit}>
<label>
<span className="sr-only">Rename file</span>
<input
@ -156,7 +176,6 @@ const FileTreeItem = ({
parentDir,
project,
currentFile,
lastDirectoryClicked,
fileOrDir,
onNavigateToFile,
onClickDirectory,
@ -173,7 +192,6 @@ const FileTreeItem = ({
parentDir: FileEntry | undefined
project?: IndexLoaderData['project']
currentFile?: IndexLoaderData['file']
lastDirectoryClicked?: FileEntry
fileOrDir: FileEntry
onNavigateToFile?: () => void
onClickDirectory: (
@ -355,6 +373,7 @@ const FileTreeItem = ({
) : (
<RenameForm
fileOrDir={fileOrDir}
parentDir={parentDir}
onSubmit={removeCurrentItemFromRenaming}
level={level}
/>
@ -401,6 +420,7 @@ const FileTreeItem = ({
/>
<RenameForm
fileOrDir={fileOrDir}
parentDir={parentDir}
onSubmit={removeCurrentItemFromRenaming}
level={-1}
/>
@ -454,7 +474,6 @@ const FileTreeItem = ({
onCloneFileOrFolder={onCloneFileOrFolder}
onOpenInNewWindow={onOpenInNewWindow}
newTreeEntry={newTreeEntry}
lastDirectoryClicked={lastDirectoryClicked}
onClickDirectory={onClickDirectory}
onNavigateToFile={onNavigateToFile}
level={level + 1}
@ -690,10 +709,6 @@ export const FileTreeInner = ({
const { errors } = useKclContext()
const runtimeErrors = kclErrorsByFilename(errors)
const [lastDirectoryClicked, setLastDirectoryClicked] = useState<
FileEntry | undefined
>(undefined)
const [treeSelection, setTreeSelection] = useState<FileEntry | undefined>(
loaderData.file
)
@ -751,7 +766,6 @@ export const FileTreeInner = ({
if (!target) return
setTreeSelection(target)
setLastDirectoryClicked(target)
fileSend({
type: 'Set selected directory',
directory: target,
@ -787,7 +801,6 @@ export const FileTreeInner = ({
parentDir={fileContext.project}
project={fileContext.project}
currentFile={loaderData?.file}
lastDirectoryClicked={lastDirectoryClicked}
fileOrDir={fileOrDir}
onCreateFile={onCreateFile}
onCreateFolder={onCreateFolder}

View File

@ -3,7 +3,7 @@ import { assign, fromPromise, setup } from 'xstate'
import type { FileEntry, Project } from '@src/lib/project'
type FileMachineContext = {
project: Project
project: Project // this is also the root directory
selectedDirectory: FileEntry
itemsBeingRenamed: (FileEntry | string)[]
}
@ -13,7 +13,12 @@ type FileMachineEvents =
| { type: 'Open file in new window'; data: { name: string } }
| {
type: 'Rename file'
data: { oldName: string; newName: string; isDir: boolean }
data: {
oldName: string
newName: string
isDir: boolean
parentDirectory: FileEntry
}
}
| {
type: 'Create file'
@ -145,7 +150,7 @@ export const fileMachine = setup({
oldName: string
newName: string
isDir: boolean
selectedDirectory: FileEntry
parentDirectory: FileEntry
}
}) => Promise.resolve({ message: '', newPath: '', oldPath: '' })
),
@ -324,14 +329,14 @@ export const fileMachine = setup({
oldName: '',
newName: '',
isDir: false,
selectedDirectory: {} as FileEntry,
parentDirectory: {} as FileEntry,
}
}
return {
oldName: event.data.oldName,
newName: event.data.newName,
isDir: event.data.isDir,
selectedDirectory: context.selectedDirectory,
parentDirectory: event.data.parentDirectory,
}
},