diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index 7366fc9589..f2da619a05 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -236,7 +236,7 @@ class FileManager extends Plugin { * @param {string} dest path of the destrination file * @returns {void} */ - async copyFile(src, dest, customName) { + async copyFile(src: string, dest: string, customName?: string) { try { src = this.normalize(src) dest = this.normalize(dest) @@ -262,7 +262,7 @@ class FileManager extends Plugin { * @param {string} dest path of the destination dir * @returns {void} */ - async copyDir(src, dest) { + async copyDir(src: string, dest: string, customName?: string) { try { src = this.normalize(src) dest = this.normalize(dest) @@ -277,16 +277,16 @@ class FileManager extends Plugin { if (provider.isSubDirectory(src, dest)) { this.call('notification', 'toast', recursivePasteToastMsg()) } else { - await this.inDepthCopy(src, dest) + await this.inDepthCopy(src, dest, customName) } } catch (e) { throw new Error(e) } } - async inDepthCopy(src, dest, count = 0) { + async inDepthCopy(src: string, dest: string, customName?: string) { const content = await this.readdir(src) - let copiedFolderPath = count === 0 ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src) + let copiedFolderPath = !customName ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src) copiedFolderPath = await helper.createNonClashingDirNameAsync(copiedFolderPath, this) await this.mkdir(copiedFolderPath) @@ -295,7 +295,7 @@ class FileManager extends Plugin { if (!value.isDirectory) { await this.copyFile(key, copiedFolderPath, helper.extractNameFromKey(key)) } else { - await this.inDepthCopy(key, copiedFolderPath, count + 1) + await this.inDepthCopy(key, copiedFolderPath, helper.extractNameFromKey(key)) } } } @@ -831,15 +831,45 @@ class FileManager extends Plugin { dest = this.normalize(dest) src = this.limitPluginScope(src) dest = this.limitPluginScope(dest) - await this._handleExists(src, `Cannot copy from ${src}. Path does not exist.`) - await this._handleExists(dest, `Cannot paste content into ${dest}. Path does not exist.`) - await this._handleIsDir(dest, `Cannot paste content into ${dest}. Path is not directory.`) - - const content = await this.readFile(src) - let movedFilePath = dest + ( '/' + `${helper.extractNameFromKey(src)}`) - movedFilePath = await helper.createNonClashingNameAsync(movedFilePath, this) + await this._handleExists(src, `Cannot move ${src}. Path does not exist.`) + await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`) + await this._handleIsFile(src, `Cannot move ${src}. Path is not a file.`) + await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`) + const fileName = helper.extractNameFromKey(src) + + if (await this.exists(dest + '/' + fileName)) { + throw createError({ code: 'ENOENT', message: `Cannot move ${src}. File already exists at destination ${dest}`}) + } + await this.copyFile(src, dest, fileName) + await this.remove(src) + } catch (e) { + throw new Error(e) + } + } - await this.writeFile(movedFilePath, content) + /** + * Moves a folder to a new folder + * @param {string} src path of the source folder + * @param {string} dest path of the destination folder + * @returns {void} + */ + + async moveDir(src: string, dest: string) { + try { + src = this.normalize(src) + dest = this.normalize(dest) + src = this.limitPluginScope(src) + dest = this.limitPluginScope(dest) + await this._handleExists(src, `Cannot move ${src}. Path does not exist.`) + await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`) + await this._handleIsDir(src, `Cannot move ${src}. Path is not directory.`) + await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`) + const dirName = helper.extractNameFromKey(src) + + if (await this.exists(dest + '/' + dirName)) { + throw createError({ code: 'ENOENT', message: `Cannot move ${src}. Folder already exists at destination ${dest}`}) + } + await this.copyDir(src, dest, dirName) await this.remove(src) } catch (e) { diff --git a/libs/remix-ui/drag-n-drop/src/lib/context/moveContext.ts b/libs/remix-ui/drag-n-drop/src/lib/context/moveContext.ts index e41292a657..4eb31c87a7 100644 --- a/libs/remix-ui/drag-n-drop/src/lib/context/moveContext.ts +++ b/libs/remix-ui/drag-n-drop/src/lib/context/moveContext.ts @@ -2,7 +2,8 @@ import { createContext } from "react"; import { MoveContextType } from "../types"; export const MoveContext = createContext({ - dragged: "", - moveFile: () => {}, - currentlyMoved: () => {} + dragged: {} as { path: string, isDirectory: boolean }, + moveFile: () => null, + moveFolder: () => null, + currentlyMoved: () => null }) diff --git a/libs/remix-ui/drag-n-drop/src/lib/drag-n-drop.tsx b/libs/remix-ui/drag-n-drop/src/lib/drag-n-drop.tsx index eb220e5a75..ea94d40ca1 100644 --- a/libs/remix-ui/drag-n-drop/src/lib/drag-n-drop.tsx +++ b/libs/remix-ui/drag-n-drop/src/lib/drag-n-drop.tsx @@ -4,13 +4,14 @@ import { MoveContext } from "./context/moveContext" import { DraggableType, DragType } from "./types" export const Drag = (props: DragType) => { - const [dragged, setDragged] = useState("") + const [dragged, setDragged] = useState<{ path: string, isDirectory: boolean }>({} as { path: string, isDirectory: boolean }) return ( { setDragged(() => path) }, @@ -23,39 +24,46 @@ export const Drag = (props: DragType) => { export const Draggable = (props: DraggableType) => { const dragRef = useRef(null), - file = props.file, + destination = props.file, context = useContext(MoveContext) const handleDrop = (event: React.DragEvent) => { event.preventDefault() - if (file.isDirectory) { - context.moveFile(file.path, context.dragged) + if (destination.isDirectory) { + if (context.dragged.isDirectory) { + context.moveFolder(destination.path, context.dragged.path) + } else { + context.moveFile(destination.path, context.dragged.path) + } } else { - const path = extractParentFromKey(file.path) || '/' + const path = extractParentFromKey(destination.path) || '/' - context.moveFile(path, context.dragged) + if (context.dragged.isDirectory) { + context.moveFolder(path, context.dragged.path) + } else { + context.moveFile(path, context.dragged.path) + } } } const handleDragover = (event: React.DragEvent) => { //Checks if the folder is opened event.preventDefault() - if (file.isDirectory && !props.expandedPath.includes(file.path)) { - props.handleClickFolder(file.path, file.type) + if (destination.isDirectory && !props.expandedPath.includes(destination.path)) { + props.handleClickFolder(destination.path, destination.type) } } const handleDrag = () => { - if (context.dragged !== file.path) { - context.currentlyMoved(file.path) + if (context.dragged.path !== destination.path) { + context.currentlyMoved({ + path: destination.path, + isDirectory: destination.isDirectory + }) } } - if (props.isDraggable) { - return <>{props.children} - } - return ( <> { @@ -67,12 +75,12 @@ export const Draggable = (props: DraggableType) => { handleDrop(event) }} onDragStart={() => { - if (file) { + if (destination) { handleDrag() } }} onDragOver={(event) => { - if (file) { + if (destination) { handleDragover(event) } }} diff --git a/libs/remix-ui/drag-n-drop/src/lib/types/index.ts b/libs/remix-ui/drag-n-drop/src/lib/types/index.ts index c278555c60..a995f40117 100644 --- a/libs/remix-ui/drag-n-drop/src/lib/types/index.ts +++ b/libs/remix-ui/drag-n-drop/src/lib/types/index.ts @@ -9,10 +9,14 @@ export interface FileType { } export interface MoveContextType { - dragged: string + dragged: { + path: string, + isDirectory: boolean + } isDraggable?: boolean - moveFile: (dest: string, dragged: string) => void - currentlyMoved: (path: string) => void + moveFile: (dest: string, src: string) => void + moveFolder: (dest: string, src: string) => void + currentlyMoved: (file: { path: string, isDirectory: boolean }) => void } export interface DraggableType { @@ -25,5 +29,6 @@ export interface DraggableType { export interface DragType { children: ReactNode - onFileMoved: (dest: string, dragged: string) => void + onFileMoved: (dest: string, src: string) => void + onFolderMoved: (dest: string, src: string) => void } diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index eaf65dba08..afc401c744 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -471,4 +471,14 @@ export const moveFile = async (src: string, dest: string) => { } catch (error) { dispatch(displayPopUp('Oops! An error ocurred while performing moveFile operation.' + error)) } -} \ No newline at end of file +} + +export const moveFolder = async (src: string, dest: string) => { + const fileManager = plugin.fileManager + + try { + await fileManager.moveDir(src, dest) + } catch (error) { + dispatch(displayPopUp('Oops! An error ocurred while performing moveDir operation.' + error)) + } +} diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index bd11d75fb5..d8f9f01a37 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -410,16 +410,23 @@ export const FileExplorer = (props: FileExplorerProps) => { props.dispatchHandleExpandPath(expandPath) } - const handleFileMove = (dest: string, dragged:string)=>{ + const handleFileMove = (dest: string, src: string) => { try { - props.dispatchMoveFile(dragged, dest) + props.dispatchMoveFile(src, dest) } catch (error) { - props.modal('Moving File Failed', 'Unexpected error while moving file: ' + dragged, 'Close', async () => {}) + props.modal('Moving File Failed', 'Unexpected error while moving file: ' + src, 'Close', async () => {}) } -} + } + const handleFolderMove = (dest: string, src: string) => { + try { + props.dispatchMoveFolder(src, dest) + } catch (error) { + props.modal('Moving Folder Failed', 'Unexpected error while moving folder: ' + src, 'Close', async () => {}) + } + } return ( - +
Promise, dispatchHandleDownloadFiles: () => Promise, dispatchHandleRestoreBackup: () => Promise - dispatchCloneRepository: (url: string) => Promise + dispatchCloneRepository: (url: string) => Promise, dispatchMoveFile: (src: string, dest: string) => Promise, - + dispatchMoveFolder: (src: string, dest: string) => Promise }>(null) \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index 985f531e9a..2d4010ca31 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -7,7 +7,7 @@ import { FileSystemContext } from '../contexts' import { browserReducer, browserInitialState } from '../reducers/workspace' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, - fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile } from '../actions' + fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder } from '../actions' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Workspace } from '../remix-ui-workspace' @@ -128,9 +128,15 @@ export const FileSystemProvider = (props: WorkspaceProps) => { const dispatchCloneRepository = async (url: string) => { await cloneRepository(url) } + const dispatchMoveFile = async (src: string, dest: string) => { await moveFile(src, dest) } + + const dispatchMoveFolder = async (src: string, dest: string) => { + await moveFolder(src, dest) + } + useEffect(() => { dispatchInitWorkspace() }, []) @@ -234,7 +240,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => { dispatchHandleDownloadFiles, dispatchHandleRestoreBackup, dispatchCloneRepository, - dispatchMoveFile + dispatchMoveFile, + dispatchMoveFolder } return ( diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index ee52a4f2bd..f06b66ee91 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -331,6 +331,7 @@ export function Workspace () { dispatchAddInputField={global.dispatchAddInputField} dispatchHandleExpandPath={global.dispatchHandleExpandPath} dispatchMoveFile={global.dispatchMoveFile} + dispatchMoveFolder={global.dispatchMoveFolder} /> }
@@ -369,6 +370,7 @@ export function Workspace () { dispatchAddInputField={global.dispatchAddInputField} dispatchHandleExpandPath={global.dispatchHandleExpandPath} dispatchMoveFile={global.dispatchMoveFile} + dispatchMoveFolder={global.dispatchMoveFolder} /> } diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index bdb308587b..3aaf30de7f 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -97,8 +97,9 @@ export interface FileExplorerProps { dispatchFetchDirectory:(path: string) => Promise, dispatchRemoveInputField:(path: string) => Promise, dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise, - dispatchHandleExpandPath: (paths: string[]) => Promise + dispatchHandleExpandPath: (paths: string[]) => Promise, dispatchMoveFile: (src: string, dest: string) => Promise, + dispatchMoveFolder: (src: string, dest: string) => Promise } export interface FileExplorerMenuProps {