diff --git a/README.md b/README.md index b3a39bb..98edcca 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,20 @@ function App() { name: "Documents", isDirectory: true, // Folder path: "/Documents", // Located in Root directory + updatedAt: "2024-09-09T10:30:00Z", // Last updated time (ISO 8601 format) }, { name: "Pictures", isDirectory: true, // Folder path: "/Pictures", // Located in Root directory + updatedAt: "2024-09-09T11:00:00Z", // Last updated time (ISO 8601 format) }, { name: "Pic.png", isDirectory: false, // File path: "/Pictures/Pic.png", // Located inside the "Pictures" folder + updatedAt: "2024-09-08T16:45:00Z", // Last updated time (ISO 8601 format) + size: 2048, // File size in bytes (example: 2 KB) }, ]); @@ -53,20 +57,33 @@ function App() { } ``` +## 📂 File Structure + +The `files` prop accepts an array of objects, where each object represents a file or folder. You can customize the structure to meet your application needs. Each file or folder object follows the structure detailed below: + +```typescript +type File = { + name: string; + isDirectory: boolean; + path: string; + updatedAt?: string; + size?: number; +}; +``` + ## ⚙️ Props -| Name | Type | Description | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `files` | `Array<{ name: string, isDirectory: boolean, path: string }>` | An array of file and folder objects representing the current directory structure. Each object includes `name`, `isDirectory`, and `path` properties. | -| `isLoading` | `boolean` | A boolean state indicating whether the application is currently performing an operation, such as creating, renaming, or deleting a file/folder. Displays a loading state if set `true`. | -| `fileUploadConfig` | `{ url: string; headers?: { [key: string]: string } }` | Configuration object for file uploads. It includes the upload URL (`url`) and an optional `headers` object for setting custom HTTP headers in the upload request. The `headers` object can accept any standard or custom headers required by the server. Example: `{ url: "https://example.com/fileupload", headers: { Authorization: "Bearer" + TOKEN, "X-Custom-Header": "value" } }` | -| onCreateFolder | (name: string, parentFolder: { name: string, isDirectory: boolean, path: string }) => void | A callback function triggered when a new folder is created. Use this function to update the files state to include the new folder under the specified parent folder using create folder API call to your server. | -| `onFileUploading` | `(file: { name: string, isDirectory: boolean, path: string }, parentFolder: { name: string, isDirectory: boolean, path: string }) => { [key: string]: any }` | A callback function triggered during the file upload process. You can also return an object with key-value pairs that will be appended to the `FormData` along with the file being uploaded. The object can contain any number of valid properties. | -| `onFileUploaded` | `(response: { [key: string]: any }) => void` | A callback function triggered after a file is successfully uploaded. Provides JSON `response` holding uploaded file details, use it to extracts the uploaded file details and add it to the `files` state e.g. `setFiles((prev) => [...prev, JSON.parse(response)]);` | -| onRename | (file: { name: string, isDirectory: boolean, path: string }, newName: string) => void | A callback function triggered when a file or folder is renamed. | -| onDelete | (file: { name: string, isDirectory: boolean, path: string }) => void | A callback function triggered when a file or folder is deleted. | -| onPaste | (sourceItem: { name: string, isDirectory: boolean, path: string }, destinationFolder: { name: string, isDirectory: boolean, path: string }, operationType: "copy" or "move") => void | A callback function triggered when a file or folder is pasted into a new location. Depending on operationType, use this to either copy or move the sourceItem to the destinationFolder, updating the files state accordingly. | -| `onRefresh` | `() => void` | A callback function triggered when the file manager is refreshed. Use this to refresh the `files` state to reflect any changes or updates. | +| Name | Type | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `isLoading` | boolean | A boolean state indicating whether the application is currently performing an operation, such as creating, renaming, or deleting a file/folder. Displays a loading state if set `true`. | +| `fileUploadConfig` | { url: string; headers?: { [key: string]: string } } | Configuration object for file uploads. It includes the upload URL (`url`) and an optional `headers` object for setting custom HTTP headers in the upload request. The `headers` object can accept any standard or custom headers required by the server. Example: `{ url: "https://example.com/fileupload", headers: { Authorization: "Bearer" + TOKEN, "X-Custom-Header": "value" } }` | +| `onCreateFolder` | (name: string, parentFolder: [File](#-file-structure)) => void | A callback function triggered when a new folder is created. Use this function to update the files state to include the new folder under the specified parent folder using create folder API call to your server. | +| `onFileUploading` | (file: [File](#-file-structure), parentFolder: [File](#-file-structure)) => { [key: string]: any } | A callback function triggered during the file upload process. You can also return an object with key-value pairs that will be appended to the `FormData` along with the file being uploaded. The object can contain any number of valid properties. | +| `onFileUploaded` | (response: { [key: string]: any }) => void | A callback function triggered after a file is successfully uploaded. Provides JSON `response` holding uploaded file details, use it to extract the uploaded file details and add it to the `files` state e.g. `setFiles((prev) => [...prev, JSON.parse(response)]);` | +| `onRename` | (file: [File](#-file-structure), newName: string) => void | A callback function triggered when a file or folder is renamed. | +| `onDelete` | (file: [File](#-file-structure)) => void | A callback function triggered when a file or folder is deleted. | +| `onPaste` | (file: [File](#-file-structure), destinationFolder: [File](#-file-structure), operationType: "copy" \| "move") => void | A callback function triggered when a file or folder is pasted into a new location. Depending on `operationType`, use this to either copy or move the `sourceItem` to the `destinationFolder`, updating the files state accordingly. | +| `onRefresh` | () => void | A callback function triggered when the file manager is refreshed. Use this to refresh the `files` state to reflect any changes or updates. | ## 🤝 Contributing diff --git a/frontend/README.md b/frontend/README.md index b3a39bb..98edcca 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -32,16 +32,20 @@ function App() { name: "Documents", isDirectory: true, // Folder path: "/Documents", // Located in Root directory + updatedAt: "2024-09-09T10:30:00Z", // Last updated time (ISO 8601 format) }, { name: "Pictures", isDirectory: true, // Folder path: "/Pictures", // Located in Root directory + updatedAt: "2024-09-09T11:00:00Z", // Last updated time (ISO 8601 format) }, { name: "Pic.png", isDirectory: false, // File path: "/Pictures/Pic.png", // Located inside the "Pictures" folder + updatedAt: "2024-09-08T16:45:00Z", // Last updated time (ISO 8601 format) + size: 2048, // File size in bytes (example: 2 KB) }, ]); @@ -53,20 +57,33 @@ function App() { } ``` +## 📂 File Structure + +The `files` prop accepts an array of objects, where each object represents a file or folder. You can customize the structure to meet your application needs. Each file or folder object follows the structure detailed below: + +```typescript +type File = { + name: string; + isDirectory: boolean; + path: string; + updatedAt?: string; + size?: number; +}; +``` + ## ⚙️ Props -| Name | Type | Description | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `files` | `Array<{ name: string, isDirectory: boolean, path: string }>` | An array of file and folder objects representing the current directory structure. Each object includes `name`, `isDirectory`, and `path` properties. | -| `isLoading` | `boolean` | A boolean state indicating whether the application is currently performing an operation, such as creating, renaming, or deleting a file/folder. Displays a loading state if set `true`. | -| `fileUploadConfig` | `{ url: string; headers?: { [key: string]: string } }` | Configuration object for file uploads. It includes the upload URL (`url`) and an optional `headers` object for setting custom HTTP headers in the upload request. The `headers` object can accept any standard or custom headers required by the server. Example: `{ url: "https://example.com/fileupload", headers: { Authorization: "Bearer" + TOKEN, "X-Custom-Header": "value" } }` | -| onCreateFolder | (name: string, parentFolder: { name: string, isDirectory: boolean, path: string }) => void | A callback function triggered when a new folder is created. Use this function to update the files state to include the new folder under the specified parent folder using create folder API call to your server. | -| `onFileUploading` | `(file: { name: string, isDirectory: boolean, path: string }, parentFolder: { name: string, isDirectory: boolean, path: string }) => { [key: string]: any }` | A callback function triggered during the file upload process. You can also return an object with key-value pairs that will be appended to the `FormData` along with the file being uploaded. The object can contain any number of valid properties. | -| `onFileUploaded` | `(response: { [key: string]: any }) => void` | A callback function triggered after a file is successfully uploaded. Provides JSON `response` holding uploaded file details, use it to extracts the uploaded file details and add it to the `files` state e.g. `setFiles((prev) => [...prev, JSON.parse(response)]);` | -| onRename | (file: { name: string, isDirectory: boolean, path: string }, newName: string) => void | A callback function triggered when a file or folder is renamed. | -| onDelete | (file: { name: string, isDirectory: boolean, path: string }) => void | A callback function triggered when a file or folder is deleted. | -| onPaste | (sourceItem: { name: string, isDirectory: boolean, path: string }, destinationFolder: { name: string, isDirectory: boolean, path: string }, operationType: "copy" or "move") => void | A callback function triggered when a file or folder is pasted into a new location. Depending on operationType, use this to either copy or move the sourceItem to the destinationFolder, updating the files state accordingly. | -| `onRefresh` | `() => void` | A callback function triggered when the file manager is refreshed. Use this to refresh the `files` state to reflect any changes or updates. | +| Name | Type | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `isLoading` | boolean | A boolean state indicating whether the application is currently performing an operation, such as creating, renaming, or deleting a file/folder. Displays a loading state if set `true`. | +| `fileUploadConfig` | { url: string; headers?: { [key: string]: string } } | Configuration object for file uploads. It includes the upload URL (`url`) and an optional `headers` object for setting custom HTTP headers in the upload request. The `headers` object can accept any standard or custom headers required by the server. Example: `{ url: "https://example.com/fileupload", headers: { Authorization: "Bearer" + TOKEN, "X-Custom-Header": "value" } }` | +| `onCreateFolder` | (name: string, parentFolder: [File](#-file-structure)) => void | A callback function triggered when a new folder is created. Use this function to update the files state to include the new folder under the specified parent folder using create folder API call to your server. | +| `onFileUploading` | (file: [File](#-file-structure), parentFolder: [File](#-file-structure)) => { [key: string]: any } | A callback function triggered during the file upload process. You can also return an object with key-value pairs that will be appended to the `FormData` along with the file being uploaded. The object can contain any number of valid properties. | +| `onFileUploaded` | (response: { [key: string]: any }) => void | A callback function triggered after a file is successfully uploaded. Provides JSON `response` holding uploaded file details, use it to extract the uploaded file details and add it to the `files` state e.g. `setFiles((prev) => [...prev, JSON.parse(response)]);` | +| `onRename` | (file: [File](#-file-structure), newName: string) => void | A callback function triggered when a file or folder is renamed. | +| `onDelete` | (file: [File](#-file-structure)) => void | A callback function triggered when a file or folder is deleted. | +| `onPaste` | (file: [File](#-file-structure), destinationFolder: [File](#-file-structure), operationType: "copy" \| "move") => void | A callback function triggered when a file or folder is pasted into a new location. Depending on `operationType`, use this to either copy or move the `sourceItem` to the `destinationFolder`, updating the files state accordingly. | +| `onRefresh` | () => void | A callback function triggered when the file manager is refreshed. Use this to refresh the `files` state to reflect any changes or updates. | ## 🤝 Contributing diff --git a/frontend/index.html b/frontend/index.html index d41efea..64f5644 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + React File Manager diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5bcb2e1..92afc05 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.2", "license": "MIT", "dependencies": { - "axios": "^1.7.5", "react": "^18.3.1", "react-collapsible": "^2.10.0", "react-dom": "^18.3.1", @@ -20,6 +19,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react-swc": "^3.5.0", + "axios": "^1.7.7", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.2", "eslint-plugin-react-hooks": "^4.6.2", @@ -1888,6 +1888,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -1907,9 +1908,10 @@ } }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2217,6 +2219,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -2553,6 +2556,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3405,9 +3409,10 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, "funding": [ { "type": "individual", @@ -3438,6 +3443,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -4951,6 +4957,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4960,6 +4967,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8308,6 +8316,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, "license": "MIT" }, "node_modules/punycode": { diff --git a/frontend/package.json b/frontend/package.json index 945c804..aa1de21 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,6 @@ "semantic-release": "semantic-release" }, "dependencies": { - "axios": "^1.7.5", "react": "^18.3.1", "react-collapsible": "^2.10.0", "react-dom": "^18.3.1", @@ -30,6 +29,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react-swc": "^3.5.0", + "axios": "^1.7.7", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.2", "eslint-plugin-react-hooks": "^4.6.2", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 722950c..5ec373c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,10 +1,10 @@ import { useEffect, useRef, useState } from "react"; import FileManager from "./File Manager/FileManager"; -import { createFolderAPI } from "./Mock APIs/createFolderAPI"; -import { renameAPI } from "./Mock APIs/renameAPI"; -import { deleteAPI } from "./Mock APIs/deleteAPI"; -import { copyItemAPI, moveItemAPI } from "./Mock APIs/fileTransferAPI"; -import { getAllFilesAPI } from "./Mock APIs/getAllFilesAPI"; +import { createFolderAPI } from "./api/createFolderAPI"; +import { renameAPI } from "./api/renameAPI"; +import { deleteAPI } from "./api/deleteAPI"; +import { copyItemAPI, moveItemAPI } from "./api/fileTransferAPI"; +import { getAllFilesAPI } from "./api/getAllFilesAPI"; import "./App.scss"; function App() { diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 65d76a9..0982d6a 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -11,6 +11,6 @@ body { .file-manager-container { height: 70%; - width: 65%; + width: 67%; } } diff --git a/frontend/src/File Manager/Actions/Actions.jsx b/frontend/src/File Manager/Actions/Actions.jsx index 005c324..12820a7 100644 --- a/frontend/src/File Manager/Actions/Actions.jsx +++ b/frontend/src/File Manager/Actions/Actions.jsx @@ -1,83 +1,34 @@ import { useEffect, useState } from "react"; import Modal from "../../components/Modal/Modal"; -import CreateFolderAction from "./CreateFolder.action"; -import RenameAction from "./Rename.action"; -import DeleteAction from "./Delete.action"; +import DeleteAction from "./Delete/Delete.action"; import UploadFileAction from "./Upload File/UploadFile.action"; const Actions = ({ - files, fileUploadConfig, - triggerAction, - currentPath, - currentPathFiles, - selectedFile, - setSelectedFile, - handleCreateFolder, - handleFileUploading, - handleFileUploaded, - currentFolder, - handleRename, - setIsItemSelection, - handleDelete, + onFileUploading, + onFileUploaded, + onDelete, allowedFileExtensions, + triggerAction, }) => { const [activeAction, setActiveAction] = useState(null); const actionTypes = { - // createFolder: { - // title: "Create Folder", - // component: ( - // - // ), - // width: "25%", - // }, uploadFile: { title: "Upload Files", component: ( ), width: "35%", }, - // rename: { - // title: "Rename", - // component: ( - // - // ), - // width: "25%", - // }, delete: { title: "Delete", - component: ( - - ), + component: , width: "25%", }, preview: { diff --git a/frontend/src/File Manager/Actions/CreateFolder.action.jsx b/frontend/src/File Manager/Actions/CreateFolder.action.jsx index 5cf3128..b90728d 100644 --- a/frontend/src/File Manager/Actions/CreateFolder.action.jsx +++ b/frontend/src/File Manager/Actions/CreateFolder.action.jsx @@ -1,19 +1,14 @@ import { useEffect, useState } from "react"; import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick"; import { duplicateNameHandler } from "../../utils/duplicateNameHandler"; +import NameInput from "../../components/Name Input/NameInput"; +import ErrorTooltip from "../../components/Error Tooltip/ErrorTooltip"; +import { useFileNavigation } from "../../contexts/FileNavigationContext"; +import { useLayout } from "../../contexts/LayoutContext"; const maxNameLength = 220; -const CreateFolderAction = ({ - activeLayout, - filesViewRef, - file, - currentPathFiles, - setCurrentPathFiles, - handleCreateFolder, - currentFolder, - triggerAction, -}) => { +const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction }) => { const [folderName, setFolderName] = useState(file.name); const [folderNameError, setFolderNameError] = useState(false); const [folderErrorMessage, setFolderErrorMessage] = useState(""); @@ -23,6 +18,8 @@ const CreateFolderAction = ({ e.preventDefault(); e.stopPropagation(); }); + const { currentFolder, currentPathFiles, setCurrentPathFiles } = useFileNavigation(); + const { activeLayout } = useLayout(); // Folder name change handler function const handleFolderNameChange = (e) => { @@ -31,7 +28,7 @@ const CreateFolderAction = ({ }; // - // Validate folder name and call "handleCreateFolder" function + // Validate folder name and call "onCreateFolder" function const handleValidateFolderName = (e) => { if (e.key === "Enter") { e.preventDefault(); @@ -87,7 +84,7 @@ const CreateFolderAction = ({ newFolderName = duplicateNameHandler("New Folder", true, syncedCurrPathFiles); } - handleCreateFolder(newFolderName, currentFolder); + onCreateFolder(newFolderName, currentFolder); setCurrentPathFiles((prev) => prev.filter((f) => f.key !== file.key)); triggerAction.close(); } @@ -128,10 +125,9 @@ const CreateFolderAction = ({ }, [outsideClick.isClicked]); return ( -
-