From b902cf419a0cfa3b9f493970748e19e460e40e5f Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Thu, 29 Aug 2024 22:47:34 +0530 Subject: [PATCH] create, rename, move file and folder --- .eslintrc.cjs | 4 +- .../workspace/WorkSpace/WorkSpace.tsx | 16 ++-- .../workspace/tree/FileTree/FileTree.tsx | 31 ++++---- .../workspace/tree/FileTree/TreeNode.tsx | 73 ++++++++++++------- .../tree/FileTree/TreePlaceholderInput.tsx | 30 ++------ src/hooks/projectV2.hooks.ts | 50 ++++++++++++- src/lib/fs.ts | 20 ++++- 7 files changed, 148 insertions(+), 76 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 68fa9e8..ef1bb44 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -61,7 +61,7 @@ module.exports = { "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-extra-non-null-assertion": "error", "@typescript-eslint/no-extraneous-class": "error", - "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-for-in-array": "error", "no-implied-eval": "off", "@typescript-eslint/no-implied-eval": "error", @@ -71,7 +71,7 @@ module.exports = { "@typescript-eslint/no-loss-of-precision": "error", "@typescript-eslint/no-meaningless-void-operator": "error", "@typescript-eslint/no-misused-new": "error", - "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/no-misused-promises": "off", "@typescript-eslint/no-mixed-enums": "error", "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error", diff --git a/src/components/workspace/WorkSpace/WorkSpace.tsx b/src/components/workspace/WorkSpace/WorkSpace.tsx index 6be465d..ed9916c 100644 --- a/src/components/workspace/WorkSpace/WorkSpace.tsx +++ b/src/components/workspace/WorkSpace/WorkSpace.tsx @@ -32,7 +32,7 @@ import s from './WorkSpace.module.scss'; const WorkSpace: FC = () => { const workspaceAction = useWorkspaceActions(); - const { clearLog } = useLogActivity(); + const { clearLog, createLog } = useLogActivity(); const router = useRouter(); const [activeMenu, setActiveMenu] = useState('code'); @@ -41,15 +41,17 @@ const WorkSpace: FC = () => { const [contract, setContract] = useState(''); const { tab } = router.query; - const { activeProject } = useProjects(); + const { activeProject, newFileFolder } = useProjects(); const activeFile = workspaceAction.activeFile(activeProject as string); - const commitItemCreation = (type: string, name: string) => { - console.log('commitItemCreation', type, name); - // workspaceAction - // .createNewItem('', name, type, projectId as string) - // .catch(() => {}); + const commitItemCreation = async (type: Tree['type'], name: string) => { + if (!name) return; + try { + await newFileFolder(name, type); + } catch (error) { + createLog((error as Error).message, 'error'); + } }; const createSandbox = async (force: boolean = false) => { diff --git a/src/components/workspace/tree/FileTree/FileTree.tsx b/src/components/workspace/tree/FileTree/FileTree.tsx index c110528..584f64c 100644 --- a/src/components/workspace/tree/FileTree/FileTree.tsx +++ b/src/components/workspace/tree/FileTree/FileTree.tsx @@ -9,45 +9,50 @@ import { import { FC } from 'react'; import { DndProvider } from 'react-dnd'; import s from './FileTree.module.scss'; -import TreeNode from './TreeNode'; +import TreeNode, { TreeNodeData } from './TreeNode'; interface Props { projectId: string; } const FileTree: FC = ({ projectId }) => { - const { projectFiles } = useProjects(); + const { activeProject, projectFiles, moveItem } = useProjects(); const getProjectFiles = (): NodeModel[] => { - return [...projectFiles].map((item) => { + if (!activeProject) return []; + return projectFiles.map((item) => { return { - id: item.path, - parent: item.parent ?? 0, + id: `${activeProject}/${item.path}`, + parent: item.parent ? `${activeProject}/${item.parent}` : activeProject, droppable: item.type === 'directory', text: item.name, + data: { + path: item.path, + }, }; }); }; - const handleDrop = (_: unknown, _options: DropOptions) => { - // workspaceAction.moveFile( - // options.dragSourceId as string, - // options.dropTargetId as string, - // projectId, - // ); + const handleDrop = async (_: unknown, options: DropOptions) => { + await moveItem( + options.dragSourceId as string, + options.dropTargetId as string, + ); }; + if (!activeProject) return null; + return (
( } depth={depth} isOpen={isOpen} onToggle={onToggle} diff --git a/src/components/workspace/tree/FileTree/TreeNode.tsx b/src/components/workspace/tree/FileTree/TreeNode.tsx index 629ae96..e89e8b4 100644 --- a/src/components/workspace/tree/FileTree/TreeNode.tsx +++ b/src/components/workspace/tree/FileTree/TreeNode.tsx @@ -1,3 +1,5 @@ +import { useLogActivity } from '@/hooks/logActivity.hooks'; +import { useProjects } from '@/hooks/projectV2.hooks'; import { useWorkspaceActions } from '@/hooks/workspace.hooks'; import { Project, Tree } from '@/interfaces/workspace.interface'; import { fileTypeFromFileName } from '@/utility/utils'; @@ -10,13 +12,17 @@ import ItemAction, { actionsTypes } from './ItemActions'; import TreePlaceholderInput from './TreePlaceholderInput'; interface Props { - node: NodeModel; + node: NodeModel; depth: number; isOpen: boolean; onToggle: (id: NodeModel['id']) => void; projectId: Project['id']; } +export interface TreeNodeData { + path: string; +} + const TreeNode: FC = ({ node, depth, isOpen, onToggle }) => { const { droppable } = node; const indent = (depth + 1) * 15; @@ -27,13 +33,15 @@ const TreeNode: FC = ({ node, depth, isOpen, onToggle }) => { const router = useRouter(); const { id: projectId } = router.query; - const { openFile, renameItem, deleteItem, createNewItem, isProjectEditable } = - useWorkspaceActions(); + const { openFile, isProjectEditable } = useWorkspaceActions(); + const { deleteProjectFile, renameProjectFile, newFileFolder } = useProjects(); + const { createLog } = useLogActivity(); const disallowedFile = [ 'message.cell.ts', 'stateInit.cell.ts', 'test.spec.js', + 'setting.json', ]; const handleClick = (e: React.MouseEvent) => { @@ -51,19 +59,32 @@ const TreeNode: FC = ({ node, depth, isOpen, onToggle }) => { setIsEditing(true); }; - const commitEditing = (name: string) => { - renameItem(node.id as string, name, projectId as string); - reset(); + const commitEditing = async (name: string) => { + try { + await renameProjectFile(node.data?.path as string, name); + reset(); + } catch (error) { + createLog((error as Error).message, 'error'); + } + }; + + const commitItemCreation = async (name: string) => { + if (!newItemAdd) return; + const path = `${node.data?.path}/${name}`; + try { + await newFileFolder(path, newItemAdd); + reset(); + } catch (error) { + createLog((error as Error).message, 'error'); + } }; - const commitItemCreation = (name: string) => { - createNewItem( - node.id as string, - name, - newItemAdd, - projectId as string, - ).catch(() => {}); - reset(); + const updateItemTypeCreation = (type: Tree['type']) => { + if (!isAllowed()) return; + if (node.droppable && !isOpen) { + onToggle(node.id); + } + setNewItemAdd(type); }; const reset = () => { @@ -86,8 +107,14 @@ const TreeNode: FC = ({ node, depth, isOpen, onToggle }) => { return ['Edit', 'Close']; }; - const deleteItemFromNode = () => { - deleteItem(node.id as string, projectId as string); + const deleteItemFromNode = async () => { + const nodePath = node.data?.path; + if (!nodePath) { + createLog(`'${nodePath}' not found`, 'error'); + return; + } + + await deleteProjectFile(nodePath); }; const isAllowed = () => { @@ -133,19 +160,13 @@ const TreeNode: FC = ({ node, depth, isOpen, onToggle }) => { }} allowedActions={getAllowedActions() as actionsTypes[]} onNewFile={() => { - if (!isAllowed()) { - return; - } - setNewItemAdd('file'); + updateItemTypeCreation('file'); }} onNewDirectory={() => { - if (!isAllowed()) { - return; - } - setNewItemAdd('directory'); + updateItemTypeCreation('directory'); }} onDelete={() => { - deleteItemFromNode(); + deleteItemFromNode().catch(() => {}); }} /> )} @@ -163,7 +184,7 @@ const TreeNode: FC = ({ node, depth, isOpen, onToggle }) => {
{newItemAdd && ( = ({ return (
{type === 'directory' ? ( - + ) : ( = ({ inputRef, defaultValue, style }) => { +const FolderEdit: FC = ({ inputRef, defaultValue }) => { return ( - + ); }; @@ -109,19 +99,9 @@ interface FileEditProps { style?: React.CSSProperties; } -const FileEdit: FC = ({ - inputRef, - updateExt, - defaultValue, - style, -}) => { +const FileEdit: FC = ({ inputRef, updateExt, defaultValue }) => { return ( - + ); }; diff --git a/src/hooks/projectV2.hooks.ts b/src/hooks/projectV2.hooks.ts index fb5b1b1..94e1c60 100644 --- a/src/hooks/projectV2.hooks.ts +++ b/src/hooks/projectV2.hooks.ts @@ -165,12 +165,58 @@ export const useProjects = () => { return projectName; }; + const newFileFolder = async (path: string, type: 'file' | 'directory') => { + if (!activeProject) return; + const newPath = `${baseProjectPath}${activeProject}/${path}`; + await fileSystem.create(newPath, type); + await loadProjectFiles(activeProject); + }; + + const deleteProjectFile = async (path: string) => { + if (!activeProject) return; + await fileSystem.remove(`${baseProjectPath}${activeProject}/${path}`, { + recursive: true, + }); + await loadProjectFiles(activeProject); + }; + + const moveItem = async (oldPath: string, targetPath: string) => { + if (!activeProject) return; + if (oldPath === targetPath) return; + + const newPath = targetPath + '/' + oldPath.split('/').pop(); + + await fileSystem.rename( + `${baseProjectPath}/${oldPath}`, + `${baseProjectPath}/${newPath}`, + ); + await loadProjectFiles(activeProject); + }; + + const renameProjectFile = async (oldPath: string, newName: string) => { + if (!activeProject) return; + const newPath = oldPath.includes('/') + ? oldPath.split('/').slice(0, -1).join('/') + '/' + newName + : newName; + + const success = await fileSystem.rename( + `${baseProjectPath}${activeProject}/${oldPath}`, + `${baseProjectPath}${activeProject}/${newPath}`, + ); + if (!success) return; + await loadProjectFiles(activeProject); + }; + return { projects, - createProject, projectFiles, - deleteProject, activeProject, + createProject, + deleteProject, + newFileFolder, + deleteProjectFile, + moveItem, + renameProjectFile, setActiveProject, }; }; diff --git a/src/lib/fs.ts b/src/lib/fs.ts index ca633d9..ab13b9d 100644 --- a/src/lib/fs.ts +++ b/src/lib/fs.ts @@ -103,6 +103,19 @@ class FileSystem { return newPath; } + async create(path: string, type: 'file' | 'directory') { + if (await this.exists(path)) { + const name = path.substring(path.lastIndexOf('/') + 1); + throw new Error( + `File or folder already exists with the same name: ${name}`, + ); + } + if (type === 'file') { + return this.writeFile(path, ''); + } + return this.mkdir(path); + } + async rmdir(path: string, options: { recursive?: boolean } = {}) { if (!options.recursive) { return this.fs.rmdir(path); @@ -145,7 +158,12 @@ class FileSystem { } async rename(oldPath: string, newPath: string) { - return this.fs.rename(oldPath, newPath); + if (oldPath === newPath) return false; + if (await this.exists(newPath)) { + throw new Error(`File or folder already exists with the same name`); + } + await this.fs.rename(oldPath, newPath); + return true; } async copy(oldPath: string, newPath: string) {