Skip to content

Conversation

nadr0
Copy link
Contributor

@nadr0 nadr0 commented Aug 11, 2025

relates #7952

Issue

duplicate/clone was not fully recursive in the original file tree.

Implementation

  • SystemIOMachine to do a fs.copy on a src and target
  • Implemented two helper functions to test a bunch of file and folder copy scenarios
  • Added copy and paste to the context menu of the tree

Screenshots

image

Will work with other file extensions

@nadr0 nadr0 requested a review from a team as a code owner August 11, 2025 18:49
Copy link

vercel bot commented Aug 11, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
modeling-app Ready Ready Preview Comment Aug 29, 2025 3:28pm

export const getUniqueCopyPath = (
possibleCollisions: string[],
possibleCopyPath: string,
identifer: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name identifer appears to be misspelled. It should be identifier to match how it's used throughout the function body and to maintain consistent spelling across the codebase.

Suggested change
identifer: string,
identifier: string,

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, i can't spell!

@nadr0 nadr0 changed the title feature: cut and paste recursively feature: cut and paste recursively in project explorer Aug 11, 2025
@nadr0 nadr0 changed the title feature: cut and paste recursively in project explorer feature: copy and paste recursively in project explorer Aug 11, 2025
Comment on lines +121 to +123
<ContextMenuItem data-testid="context-menu-delete" onClick={onCopy}>
Copy
</ContextMenuItem>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data-testid attribute for the Copy menu item is incorrectly set to "context-menu-delete". This will cause confusion during testing and make the UI harder to test reliably. Please update it to "context-menu-copy" to maintain consistent and descriptive test identifiers.

Suggested change
<ContextMenuItem data-testid="context-menu-delete" onClick={onCopy}>
Copy
</ContextMenuItem>,
<ContextMenuItem data-testid="context-menu-copy" onClick={onCopy}>
Copy
</ContextMenuItem>,

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think bot is right here, but not going to commit it in case your tests rely on it.

Comment on lines +346 to +348
const absoluteParentPath = getParentAbsolutePath(row.path)
const parentIndex = flattenedData.findIndex((entry) => {
return entry.path === absoluteParentPath
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parent path calculation appears incorrect for paste operations. Currently, getParentAbsolutePath(row.path) is called on the target row, but this logic doesn't account for the difference between pasting into a folder versus pasting next to a file.

When right-clicking on a file, the paste should occur in that file's parent directory, but when right-clicking on a folder, the paste should occur inside that folder. The current implementation treats both cases the same way.

Consider refactoring this to check if the target is a folder:

const targetPath = row.children ? row.path : getParentAbsolutePath(row.path);
const parentIndex = flattenedData.findIndex((entry) => {
  return entry.path === targetPath;
});

This would ensure files are pasted in the expected location based on the context of the right-click.

Suggested change
const absoluteParentPath = getParentAbsolutePath(row.path)
const parentIndex = flattenedData.findIndex((entry) => {
return entry.path === absoluteParentPath
const targetPath = row.children ? row.path : getParentAbsolutePath(row.path)
const parentIndex = flattenedData.findIndex((entry) => {
return entry.path === targetPath

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

const takenNumbers = matches
.map((matchedPath) => {
const folderOrFileName = getStringAfterLastSeparator(matchedPath)
matchedPath = matchedPath.replace(fileName, '')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string replacement operation matchedPath = matchedPath.replace(fileName, '') could lead to incorrect collision detection if fileName appears multiple times in the path. Since replace() only replaces the first occurrence by default, this might work in most cases, but for paths where the filename pattern repeats in parent directories, it could remove the wrong part of the string.

Consider using a more targeted approach that ensures only the filename portion is replaced, perhaps by working with the path components directly or using a regex with end-of-string anchor. This would make the collision detection more robust when handling complex directory structures.

// Example of a more targeted approach
const fileNamePart = new RegExp(`${fileName}$`);
matchedPath = matchedPath.replace(fileNamePart, '');
Suggested change
matchedPath = matchedPath.replace(fileName, '')
matchedPath = matchedPath.replace(new RegExp(`${fileName}$`), '')

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

onError: {
target: SystemIOMachineStates.idle,
actions: [SystemIOMachineActions.toastError],
}}},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be a syntax error in the state machine definition. The onError block is missing a closing comma after its closing brace, which will cause a JavaScript syntax error when the application tries to run. The correct syntax should be:

onError: {
  target: SystemIOMachineStates.idle,
  actions: [SystemIOMachineActions.toastError],
},

This comma is needed because another state definition follows this block.

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@nadr0 nadr0 force-pushed the nadro/gh-7952/cut-copy-paste-file-tree branch from 3f4e6ba to b0df204 Compare August 22, 2025 13:22
</ContextMenuItem>,
<ContextMenuItem
disabled={!isCopying}
data-testid="context-menu-delete"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the above, should be context-menu-paste.

const lastIndexBeforeNothing = useRef<number>(-2)

// Store a path to copy and paste! Works for folders and files
const copyToClipBoard = useRef<FileEntry | null>(null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious as to why useRef and not useState here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so recursive rows can just mutate directly without having to pass handlers and state all the way down?
And clipboard state is not displayed anywhere in the UI, so fine that it's outside of react lifecycle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the handles can directly access and edit a state value. The current copy path is not rendered in react.

},
})
} else {
console.error('failed to copy and paste the result is null')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toast message?

Comment on lines +121 to +130
<ContextMenuItem data-testid="context-menu-delete" onClick={onCopy}>
Copy
</ContextMenuItem>,
<ContextMenuItem
disabled={!isCopying}
data-testid="context-menu-delete"
onClick={onPaste}
>
Paste
</ContextMenuItem>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data-testid attribute for the "Copy" and "Paste" context menu items is currently set to "context-menu-delete", which is the same identifier used by the Delete item. This will cause ambiguity in automated tests that target these elements by their test IDs.

For better test reliability and clarity, please update these attributes to more specific values such as context-menu-copy and context-menu-paste respectively:

<ContextMenuItem data-testid="context-menu-copy" onClick={onCopy}>
  Copy
</ContextMenuItem>,
<ContextMenuItem
  disabled={!isCopying}
  data-testid="context-menu-paste"
  onClick={onPaste}
>
  Paste
</ContextMenuItem>,
Suggested change
<ContextMenuItem data-testid="context-menu-delete" onClick={onCopy}>
Copy
</ContextMenuItem>,
<ContextMenuItem
disabled={!isCopying}
data-testid="context-menu-delete"
onClick={onPaste}
>
Paste
</ContextMenuItem>,
<ContextMenuItem data-testid="context-menu-copy" onClick={onCopy}>
Copy
</ContextMenuItem>,
<ContextMenuItem
disabled={!isCopying}
data-testid="context-menu-paste"
onClick={onPaste}
>
Paste
</ContextMenuItem>,

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants