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 (
-
+ >
);
};
diff --git a/frontend/src/File Manager/Actions/Delete.action.jsx b/frontend/src/File Manager/Actions/Delete/Delete.action.jsx
similarity index 68%
rename from frontend/src/File Manager/Actions/Delete.action.jsx
rename to frontend/src/File Manager/Actions/Delete/Delete.action.jsx
index 48b4664..60eb6e7 100644
--- a/frontend/src/File Manager/Actions/Delete.action.jsx
+++ b/frontend/src/File Manager/Actions/Delete/Delete.action.jsx
@@ -1,17 +1,13 @@
import React from "react";
-import Button from "../../components/Button/Button";
+import Button from "../../../components/Button/Button";
+import "./Delete.action.scss";
+import { useSelection } from "../../../contexts/SelectionContext";
+
+const DeleteAction = ({ triggerAction, onDelete }) => {
+ const { selectedFile, setSelectedFile } = useSelection();
-const DeleteAction = ({
- files,
- selectedFile,
- triggerAction,
- handleDelete,
- setIsItemSelection,
- setSelectedFile,
-}) => {
const handleDeleting = (file) => {
- handleDelete(file);
- setIsItemSelection(false);
+ onDelete(file);
setSelectedFile(null);
triggerAction.close();
};
diff --git a/frontend/src/File Manager/Actions/Delete/Delete.action.scss b/frontend/src/File Manager/Actions/Delete/Delete.action.scss
new file mode 100644
index 0000000..4b5bf9f
--- /dev/null
+++ b/frontend/src/File Manager/Actions/Delete/Delete.action.scss
@@ -0,0 +1,16 @@
+.file-delete-confirm {
+ .file-delete-confirm-text {
+ border-bottom: 1px solid #dddddd;
+ padding: 10px 15px;
+ margin-bottom: 1rem;
+ word-wrap: break-word;
+ }
+
+ .file-delete-confirm-actions {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: flex-end;
+ margin-bottom: 1rem;
+ margin-right: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/File Manager/Actions/Rename.action.jsx b/frontend/src/File Manager/Actions/Rename.action.jsx
index 744761e..acd0fcb 100644
--- a/frontend/src/File Manager/Actions/Rename.action.jsx
+++ b/frontend/src/File Manager/Actions/Rename.action.jsx
@@ -4,24 +4,22 @@ import { IoWarningOutline } from "react-icons/io5";
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
import Modal from "../../components/Modal/Modal";
import { getFileExtension } from "../../utils/getFileExtension";
+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 RenameAction = ({
- activeLayout,
- filesViewRef,
- file,
- currentPathFiles,
- setCurrentPathFiles,
- handleRename,
- triggerAction,
-}) => {
+const RenameAction = ({ filesViewRef, file, onRename, triggerAction }) => {
const [renameFile, setRenameFile] = useState(file?.name);
const [renameFileWarning, setRenameFileWarning] = useState(false);
const [fileRenameError, setFileRenameError] = useState(false);
const [renameErrorMessage, setRenameErrorMessage] = useState("");
const [errorXPlacement, setErrorXPlacement] = useState("right");
const [errorYPlacement, setErrorYPlacement] = useState("bottom");
+ const { currentPathFiles, setCurrentPathFiles } = useFileNavigation();
+ const { activeLayout } = useLayout();
const warningModalRef = useRef(null);
const outsideClick = useDetectOutsideClick((e) => {
@@ -90,7 +88,7 @@ const RenameAction = ({
}
}
setFileRenameError(false);
- handleRename(file, renameFile);
+ onRename(file, renameFile);
setCurrentPathFiles((prev) => prev.filter((f) => f.key !== file.key)); // Todo: Should only filter on success API call
triggerAction.close();
}
@@ -142,26 +140,25 @@ const RenameAction = ({
return (
<>
-
+ )}
{
const [files, setFiles] = useState([]);
const [isDragging, setIsDragging] = useState(false);
const [isUploading, setIsUploading] = useState({});
+ const { currentFolder } = useFileNavigation();
// Todo: Also validate allowed file extensions on drop
const handleDrop = (e) => {
@@ -22,7 +24,7 @@ const UploadFileAction = ({
const droppedFiles = Array.from(e.dataTransfer.files);
if (droppedFiles.length > 0) {
const choosenFiles = droppedFiles.map((file) => {
- const appendData = handleFileUploading(file, currentFolder);
+ const appendData = onFileUploading(file, currentFolder);
return {
file: file,
appendData: appendData,
@@ -36,7 +38,7 @@ const UploadFileAction = ({
const selectedFiles = Array.from(e.target.files);
if (selectedFiles.length > 0) {
const choosenFiles = selectedFiles.map((file) => {
- const appendData = handleFileUploading(file, currentFolder);
+ const appendData = onFileUploading(file, currentFolder);
return {
file: file,
appendData: appendData,
@@ -95,7 +97,7 @@ const UploadFileAction = ({
fileData={fileData}
fileUploadConfig={fileUploadConfig}
setIsUploading={setIsUploading}
- handleFileUploaded={handleFileUploaded}
+ onFileUploaded={onFileUploaded}
/>
))}
diff --git a/frontend/src/File Manager/Actions/Upload File/UploadFile.action.scss b/frontend/src/File Manager/Actions/Upload File/UploadFile.action.scss
new file mode 100644
index 0000000..4c3c137
--- /dev/null
+++ b/frontend/src/File Manager/Actions/Upload File/UploadFile.action.scss
@@ -0,0 +1,138 @@
+@import "../../../styles/variables";
+
+.fm-upload-file {
+ padding: 18px 15px;
+ display: flex;
+ gap: 18px;
+
+ .select-files {
+ width: 100%;
+
+ .draggable-file-input {
+ color: #696969;
+ background-color: #f7f7f7;
+ margin-bottom: 18px;
+ height: 220px;
+ border: 2px dashed #ccc;
+ border-radius: 5px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .input-text {
+ pointer-events: none; // This ensures that the drag and drop event isn't binded with it's children
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ &:hover {
+ border-color: $primary-color;
+ }
+ }
+
+ .draggable-file-input.dragging {
+ border-color: $primary-color;
+ }
+
+ .btn-choose-file {
+ display: flex;
+ justify-content: center;
+
+ label {
+ display: inline-block;
+ padding: 0.4rem 0.8rem;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .choose-file-input {
+ display: none;
+ }
+ }
+ }
+
+ .files-progress {
+ width: 60%;
+
+ .heading {
+ display: flex;
+ gap: 4px;
+ }
+
+ h2 {
+ font-size: 0.9em;
+ margin: 0;
+ }
+
+ ul {
+ padding-left: 0px;
+ padding-right: 5px;
+ padding-bottom: 10px;
+ margin-top: 0.7rem;
+ height: 220px;
+ @include overflow-y-scroll;
+ font-weight: 500;
+
+ li {
+ list-style: none;
+ border-bottom: 1px solid #c6c6c6;
+ display: flex;
+ gap: 5px;
+ margin-bottom: 18px;
+ padding-bottom: 12px;
+
+ .file-icon {
+ width: 10%;
+ }
+
+ .file {
+ width: 90%;
+
+ .file-details {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 5px;
+
+ .file-info {
+ width: 90%;
+ display: flex;
+ align-items: baseline;
+
+ .file-name {
+ display: inline-block;
+ max-width: 72%;
+ margin-right: 8px;
+ }
+ }
+
+ .file-size {
+ font-size: 0.7em;
+ }
+
+ .retry-upload {
+ padding: 3px;
+ border-radius: 50%;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgba(0, 0, 0, 0.07);
+ color: $primary-color;
+ }
+ }
+
+ .rm-file {
+ &:hover {
+ cursor: pointer;
+ color: red;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/File Manager/Actions/Upload File/UploadItem.jsx b/frontend/src/File Manager/Actions/Upload File/UploadItem.jsx
index 98047cd..604ed0c 100644
--- a/frontend/src/File Manager/Actions/Upload File/UploadItem.jsx
+++ b/frontend/src/File Manager/Actions/Upload File/UploadItem.jsx
@@ -8,7 +8,7 @@ import { getDataSize } from "../../../utils/getDataSize";
import { FaRegCheckCircle } from "react-icons/fa";
import { IoMdRefresh } from "react-icons/io";
-const UploadItem = ({ index, fileData, setIsUploading, fileUploadConfig, handleFileUploaded }) => {
+const UploadItem = ({ index, fileData, setIsUploading, fileUploadConfig, onFileUploaded }) => {
const [uploadProgress, setUploadProgress] = useState(0);
const [isUploaded, setIsUploaded] = useState(false);
const [isCanceled, setIsCanceled] = useState(false);
@@ -38,7 +38,7 @@ const UploadItem = ({ index, fileData, setIsUploading, fileUploadConfig, handleF
}));
if (xhr.status === 200 || xhr.status === 201) {
setIsUploaded(true);
- handleFileUploaded(xhr.response);
+ onFileUploaded(xhr.response);
resolve(xhr.response);
} else {
reject(xhr.statusText);
diff --git a/frontend/src/File Manager/Bread Crumb/BreadCrumb.jsx b/frontend/src/File Manager/Bread Crumb/BreadCrumb.jsx
index bbf3689..812b90a 100644
--- a/frontend/src/File Manager/Bread Crumb/BreadCrumb.jsx
+++ b/frontend/src/File Manager/Bread Crumb/BreadCrumb.jsx
@@ -1,8 +1,11 @@
import { useEffect, useState } from "react";
import { MdHome, MdOutlineNavigateNext } from "react-icons/md";
+import "./BreadCrumb.scss";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
-const BreadCrumb = ({ currentPath, setCurrentPath }) => {
+const BreadCrumb = () => {
const [folders, setFolders] = useState([]);
+ const { currentPath, setCurrentPath } = useFileNavigation();
useEffect(() => {
setFolders(currentPath?.split("/"));
@@ -21,11 +24,7 @@ const BreadCrumb = ({ currentPath, setCurrentPath }) => {
return (
{folders.map((folder, index) => (
-
switchPath(index)}
- >
+ switchPath(index)}>
{index === 0 ? (
<>
Home
diff --git a/frontend/src/File Manager/Bread Crumb/BreadCrumb.scss b/frontend/src/File Manager/Bread Crumb/BreadCrumb.scss
new file mode 100644
index 0000000..da2a02f
--- /dev/null
+++ b/frontend/src/File Manager/Bread Crumb/BreadCrumb.scss
@@ -0,0 +1,18 @@
+@import "../../styles/variables";
+
+.breadcrumb {
+ height: calc(5.8% - 21px);
+ display: flex;
+ gap: 0.5rem;
+ border-bottom: 1px solid #dddddd;
+ padding: 10px 15px;
+ overflow-x: scroll;
+
+ &::-webkit-scrollbar {
+ height: 3px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: $primary-color !important;
+ }
+}
diff --git a/frontend/src/File Manager/FileManager.jsx b/frontend/src/File Manager/FileManager.jsx
index bc0abdc..363ced2 100644
--- a/frontend/src/File Manager/FileManager.jsx
+++ b/frontend/src/File Manager/FileManager.jsx
@@ -1,5 +1,3 @@
-import { useEffect, useRef, useState } from "react";
-import "./FileManager.scss";
import Toolbar from "./Toolbar/Toolbar";
import NavigationPane from "./Navigation Pane/NavigationPane";
import BreadCrumb from "./Bread Crumb/BreadCrumb";
@@ -8,6 +6,13 @@ import { useTriggerAction } from "../hooks/useTriggerAction";
import Actions from "./Actions/Actions";
import Loader from "../components/Loader/Loader";
import PropTypes from "prop-types";
+import { FilesProvider } from "../contexts/FilesContext";
+import { FileNavigationProvider } from "../contexts/FileNavigationContext";
+import { useColumnResize } from "../hooks/useColumnResize";
+import { SelectionProvider } from "../contexts/SelectionContext";
+import { ClipBoardProvider } from "../contexts/ClipboardContext";
+import { LayoutProvider } from "../contexts/LayoutContext";
+import "./FileManager.scss";
const FileManager = ({
files,
@@ -24,136 +29,63 @@ const FileManager = ({
allowedFileExtensions,
}) => {
const triggerAction = useTriggerAction();
-
- // States
- const [isItemSelection, setIsItemSelection] = useState(false);
- const [selectedFile, setSelectedFile] = useState(null); // This will be selectedFiles as an array for multiple selection in future
- const [currentPath, setCurrentPath] = useState("");
- const [currentFolder, setCurrentFolder] = useState(null);
- const [currentPathFiles, setCurrentPathFiles] = useState([]);
- const [clipBoard, setClipBoard] = useState(null);
- const [activeLayout, setActiveLayout] = useState("grid");
- //
-
- // Settings Current Path Files
- useEffect(() => {
- if (Array.isArray(files)) {
- setCurrentPathFiles(() => {
- return files.filter((file) => file.path === `${currentPath}/${file.name}`);
- });
-
- setCurrentFolder(() => {
- return files?.find((file) => file.path === currentPath);
- });
- }
- }, [files, currentPath]);
- //
-
- // Dragging Resizer
- const [colSizes, setColSizes] = useState({ col1: "20", col2: "80" });
- const [isDragging, setIsDragging] = useState(false);
- const containerRef = useRef(null);
-
- const handleMouseDown = () => {
- setIsDragging(true);
- };
-
- const handleMouseUp = () => {
- setIsDragging(false);
- };
-
- const handleMouseMove = (e) => {
- if (!isDragging) return;
- // Prevent text selection during drag
- e.preventDefault();
-
- // Calculate new sizes based on mouse movement
- const container = containerRef.current;
- const containerRect = container.getBoundingClientRect();
- const newCol1Size = ((e.clientX - containerRect.left) / containerRect.width) * 100;
-
- // Limiting the resizing to 15% to 60% for better UX
- if (newCol1Size >= 15 && newCol1Size <= 60) {
- setColSizes({ col1: newCol1Size, col2: 100 - newCol1Size });
- }
- };
- //
+ const { containerRef, colSizes, isDragging, handleMouseMove, handleMouseUp, handleMouseDown } =
+ useColumnResize(20, 80);
return (
e.preventDefault()}>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
);
};
diff --git a/frontend/src/File Manager/FileManager.scss b/frontend/src/File Manager/FileManager.scss
index e0b9a78..ca2ceb5 100644
--- a/frontend/src/File Manager/FileManager.scss
+++ b/frontend/src/File Manager/FileManager.scss
@@ -1,8 +1,5 @@
@import url("https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap");
-
-$secondary-color: #6155b4;
-$primary-color: #6155b4;
-$secondary-color-lighter: #5549a3;
+@import "../styles/variables";
.background-secondary {
background-color: $secondary-color !important;
@@ -38,9 +35,11 @@ svg {
.file-explorer {
font-family: "Nunito Sans", sans-serif;
+
button {
font-family: "Nunito Sans", sans-serif;
}
+
border: 1px solid #dddddd;
border-radius: 8px;
height: 100%;
@@ -53,149 +52,6 @@ svg {
-ms-user-select: none;
user-select: none;
- .file-explorer-loading {
- z-index: 3;
- position: fixed;
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
- width: -webkit-fill-available;
- background-color: rgba(255, 255, 255, 0.542);
- }
-
- .toolbar {
- height: calc(7.6% - 13px);
- min-height: 35px;
- border-bottom: 1px solid #dddddd;
- padding: 6px 10px;
-
- .file-action-container {
- display: flex;
- justify-content: space-between;
-
- > div {
- display: flex;
- }
-
- .file-action {
- background-color: transparent;
- gap: 5px;
-
- &:hover:not(:disabled) {
- cursor: pointer;
- background-color: rgb(0, 0, 0, 0.55) !important;
- border-radius: 3px;
- color: white;
- text-shadow: 0px 0px 1px white;
- }
-
- &:hover:disabled {
- cursor: default;
- background-color: transparent !important;
- color: #b0b0b0;
- text-shadow: none;
- }
- }
- }
-
- .fm-toolbar {
- display: flex;
- justify-content: space-between;
-
- > div {
- display: flex;
- position: relative;
- }
-
- .toolbar-left-items {
- display: flex;
- }
-
- .toggle-view {
- position: absolute;
- z-index: 3;
- top: 105%;
- right: 22%;
- background-color: white;
- margin: 0;
- border: 1px solid #c4c4c4;
- border-radius: 5px;
- ul {
- list-style: none;
- padding-left: 0;
- margin: 0.4em 0;
- display: flex;
- flex-direction: column;
- gap: 1px;
-
- li {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 5px 20px 5px 10px;
-
- &:hover {
- cursor: pointer;
- background-color: rgba(0, 0, 0, 0.075);
- }
-
- span:nth-child(1) {
- width: 13px;
- }
- }
- }
- }
- }
-
- .item-action {
- background-color: white;
- display: flex;
- align-items: center;
- gap: 7px;
- padding: 8px 12px;
- font-size: 14px;
- width: fit-content;
- border: none;
-
- &:hover {
- cursor: pointer;
- background-color: rgb(0 0 0 / 12%) !important;
- border-radius: 3px;
- color: $secondary-color-lighter;
- }
-
- .toggle-view-icon {
- background-color: transparent;
- border: none;
-
- &:hover {
- cursor: pointer;
- color: $secondary-color-lighter;
- }
- }
- }
-
- .icon-only {
- padding: 8px !important;
-
- &:focus {
- background-color: rgb(0 0 0 / 12%);
- }
- }
-
- .item-separator {
- height: 36px;
- background: #dee1e3;
- width: 1px;
- margin: 0 5px;
- }
- }
-
- .file-selected {
- background-color: rgb(0, 0, 0, 0.04);
- }
-
.files-container {
display: flex;
height: 92.4%;
@@ -204,108 +60,6 @@ svg {
z-index: 1;
padding-top: 8px;
- .sb-folders-list {
- list-style: none;
- margin: 0px 4px;
- height: 100%;
-
- overflow-y: auto !important;
-
- &::-webkit-scrollbar {
- width: 5px;
- height: 8px;
- padding-top: 2px;
- }
-
- &::-webkit-scrollbar-thumb {
- background: $primary-color !important;
- border-radius: 8px;
- }
-
- .folder-collapsible {
- margin-left: 10px;
- }
-
- .sb-folders-list-item {
- display: flex;
- align-items: center;
- margin-bottom: 4px;
- padding: 6px 5px;
- border-radius: 4px;
-
- &:hover {
- cursor: pointer;
- background-color: rgb(0, 0, 0, 0.04);
- }
-
- .non-expanable {
- min-width: 20px;
- }
-
- .sb-folder-details {
- display: flex;
- align-items: center;
-
- .folder-open-icon {
- margin: 0 7px;
- }
-
- .folder-close-icon {
- margin: 1px 9px 0px 8px;
- }
-
- .sb-folder-name {
- width: max-content;
- }
- }
-
- .folder-rotate-down {
- animation: rotateFolderDown 0.5s linear forwards;
- }
-
- @keyframes rotateFolderDown {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(90deg);
- }
- }
-
- .folder-rotate-right {
- animation: rotateRight 0.5s linear forwards;
- }
-
- @keyframes rotateRight {
- from {
- transform: rotate(90deg);
- }
-
- to {
- transform: rotate(0deg);
- }
- }
- }
-
- .active-list-item {
- background-color: $primary-color;
- color: white;
-
- &:hover {
- cursor: pointer;
- background-color: $primary-color;
- }
- }
-
- .empty-nav-pane {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
- }
- }
-
position: relative;
.sidebar-resize {
@@ -336,25 +90,6 @@ svg {
border-bottom-right-radius: 8px;
}
- .breadcrumb {
- height: calc(5.8% - 21px);
- display: flex;
- gap: 0.5rem;
- overflow-x: scroll;
- border-bottom: 1px solid #dddddd;
- padding: 10px 15px;
-
- &::-webkit-scrollbar {
- width: 5px;
- padding-top: 2px;
- scrollbar-width: none;
- }
-
- &::-webkit-scrollbar-thumb {
- background: $primary-color !important;
- }
- }
-
.folder-name {
display: flex;
align-items: center;
@@ -367,273 +102,6 @@ svg {
color: $secondary-color;
}
}
-
- .files {
- position: relative;
- display: flex;
- align-content: flex-start;
- flex-wrap: wrap;
- column-gap: 0.5em;
- row-gap: 5px;
- height: calc(94.2% - 16px);
- overflow-y: auto;
- padding: 8px;
- padding-right: 4px;
-
- &::-webkit-scrollbar {
- width: 5px;
- padding-top: 2px;
- }
-
- &::-webkit-scrollbar-thumb {
- background: $primary-color !important;
- }
-
- .file-item-container {
- border-radius: 5px;
- }
-
- .file-item {
- position: relative;
- height: 81px;
- width: 138px;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- align-items: center;
- justify-content: space-between;
- padding-top: 13px;
- padding-bottom: 1px;
- margin: 5px 0;
- border-radius: 5px;
-
- &:hover {
- cursor: pointer;
- background-color: rgb(0, 0, 0, 0.04);
- }
-
- .rename-file-container {
- position: absolute;
- top: 62px;
- width: 100%;
- text-align: center;
- }
-
- .rename-file-container.list {
- top: 6px;
- left: 48px;
- text-align: left;
-
- .rename-file {
- min-width: 15%;
- text-align: left;
- border-radius: 3px;
- border: none;
- top: 5px;
- white-space: nowrap;
- overflow-x: hidden;
- max-width: calc(100% - 48px);
- }
-
- .folder-name-error.right {
- left: 0px;
- bottom: -72px;
- }
- }
-
- .rename-file {
- position: relative;
- min-width: 69%;
- max-width: -webkit-fill-available;
- text-align: center;
- border: 4px solid $primary-color;
- border-radius: 10px;
- font-size: 15px;
- font-family: "Nunito Sans", sans-serif;
- padding: 2px 7px;
- resize: none;
- field-sizing: content;
- overflow-y: hidden;
-
- &:focus {
- outline: none;
- }
- }
-
- .folder-name-error {
- position: absolute;
- z-index: 1;
- bottom: -68px;
- left: 16px;
- padding: 8px;
- width: 292px;
- border-radius: 5px;
- background-color: #696969;
- text-align: left;
- margin: 0;
- font-size: 0.9em;
- &::before {
- content: "";
- position: absolute;
- top: -20%;
- rotate: -45deg;
- border: 15px solid #696969;
- border-color: transparent #696969 transparent transparent;
- }
- }
-
- .folder-name-error.right {
- left: 16px;
- &::before {
- left: 11%;
- }
- }
-
- .folder-name-error.left {
- left: -180px;
- &::before {
- left: 76%;
- transform: rotate(90deg) scaleX(-1);
- }
- }
-
- .folder-name-error.top {
- bottom: unset !important;
- top: -68px;
- &::before {
- content: none;
- }
- &:after {
- content: "";
- position: absolute;
- bottom: -20%;
- left: 11%;
- rotate: -45deg;
- border: 15px solid #696969;
- border-color: transparent transparent #696969 transparent;
- }
- }
-
- .folder-name-error.top.left {
- &::after {
- left: 76%;
- transform: rotate(90deg) scaleX(-1);
- }
- }
-
- .file-name {
- max-width: 115px;
- }
- }
-
- .file-selected {
- background-color: $primary-color;
- color: white;
-
- &:hover {
- cursor: pointer;
- background-color: $primary-color;
- }
- }
-
- .file-moving {
- opacity: 0.5;
- }
- }
-
- .files.list {
- flex-direction: column;
- flex-wrap: nowrap;
- gap: 0;
- padding-left: 0px;
- padding-top: 0px;
-
- .files-header {
- font-size: 0.83em;
- font-weight: 600;
- display: flex;
- gap: 5px;
- border-bottom: 1px solid #dddddd;
- padding: 4px 0;
- position: sticky;
- top: 0;
- background-color: #f5f5f5;
- z-index: 1;
-
- .file-name {
- width: calc(70% - 65px);
- padding-left: 65px;
- }
-
- .file-date {
- text-align: center;
- width: 20%;
- }
-
- .file-size {
- text-align: center;
- width: 10%;
- }
- }
-
- .file-item-container {
- display: flex;
- width: 100%;
-
- &:hover {
- cursor: pointer;
- background-color: rgb(0, 0, 0, 0.04);
- }
- }
-
- .file-item-container.file-selected {
- &:hover {
- background-color: $primary-color !important;
- }
- }
-
- .file-item {
- flex-direction: row;
- height: 13px;
- justify-content: unset;
- padding: 15px;
- padding-left: 20px;
- margin: 0;
- width: calc(70% - 30px);
-
- &:hover {
- background-color: unset;
- }
-
- .file-name {
- max-width: 285px;
- }
- }
-
- .modified-date {
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.8em;
- width: calc(20%);
- }
-
- .size {
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.8em;
- width: calc(10%);
- }
- }
-
- .empty-folder {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
- }
}
}
@@ -641,10 +109,6 @@ svg {
.file-explorer {
height: 63dvh;
- .file-explorer-loading {
- height: 63dvh;
- }
-
.files-container {
height: calc(63dvh - 51px);
@@ -659,10 +123,6 @@ svg {
.file-explorer {
height: 58dvh;
- .file-explorer-loading {
- height: 58dvh;
- }
-
.files-container {
height: calc(58dvh - 51px);
@@ -701,51 +161,6 @@ svg {
}
}
-.folder-error {
- color: #dc3545;
- font-size: 0.8em;
- margin-top: 4px;
-}
-
-.file-upload-container {
- .rs-uploader-file-items {
- max-height: 165px;
- overflow-y: auto !important;
- overflow-x: hidden !important;
-
- &::-webkit-scrollbar {
- width: 5px;
- padding-top: 2px;
- }
-
- &::-webkit-scrollbar-thumb {
- background: $primary-color !important;
- }
- }
-
- .rs-uploader-file-items::before {
- content: "Selected files";
- color: $primary-color;
- font-weight: 600;
- display: none;
- margin-left: 1px;
- }
-
- .rs-uploader-file-item {
- border-radius: 5px;
- margin: 13px 5px 3px;
- box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.22);
- -webkit-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.22);
- -moz-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.22);
- }
-}
-
-.file-upload-container.show-uploaded-files {
- .rs-uploader-file-items::before {
- display: block;
- }
-}
-
.file-previewer {
height: 75vh;
@@ -764,70 +179,6 @@ svg {
}
}
-.file-context-menu-list {
- font-size: 1.1em;
-
- ul {
- list-style-type: none;
- padding-left: 0;
- margin: 0;
-
- li {
- display: flex;
- gap: 6px;
- align-items: center;
- padding: 6px 22px 6px 14px;
-
- &:hover {
- cursor: pointer;
- background-color: rgb(0, 0, 0, 0.04);
- }
- }
-
- li.disable-paste {
- opacity: 0.5;
-
- &:hover {
- cursor: default;
- background-color: transparent;
- }
- }
- }
-}
-
-.file-delete-confirm {
- .file-delete-confirm-text {
- border-bottom: 1px solid #dddddd;
- padding: 10px 15px;
- margin-bottom: 1rem;
- word-wrap: break-word;
- }
-
- .file-delete-confirm-actions {
- display: flex;
- gap: 0.5rem;
- justify-content: flex-end;
- margin-bottom: 1rem;
- margin-right: 1rem;
- }
-}
-
-.fm-create-folder-container {
- padding: 8px 0;
-
- .fm-create-folder-input {
- border-bottom: 1px solid #c6c6c6;
- padding: 8px 12px 12px;
- }
-
- .fm-create-folder-action {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
- padding: 8px 8px 0 0;
- }
-}
-
.fm-rename-folder-container {
padding: 8px 0;
@@ -850,152 +201,8 @@ svg {
}
}
-.fm-upload-file {
- padding: 18px 15px;
- display: flex;
- gap: 18px;
-
- .select-files {
- width: 100%;
- .draggable-file-input {
- color: #696969;
- background-color: #f7f7f7;
- margin-bottom: 18px;
- height: 220px;
- border: 2px dashed #ccc;
- border-radius: 5px;
- display: flex;
- justify-content: center;
- align-items: center;
-
- .input-text {
- pointer-events: none; // This ensures that the drag and drop event isn't binded with it's children
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- &:hover {
- border-color: $primary-color;
- }
- }
-
- .draggable-file-input.dragging {
- border-color: $primary-color;
- }
-
- .btn-choose-file {
- display: flex;
- justify-content: center;
-
- label {
- display: inline-block;
- padding: 0.4rem 0.8rem;
- &:hover {
- cursor: pointer;
- }
- }
-
- .choose-file-input {
- display: none;
- }
- }
- }
-
- .files-progress {
- width: 60%;
-
- .heading {
- display: flex;
- gap: 4px;
- }
-
- h2 {
- font-size: 0.9em;
- margin: 0;
- }
-
- ul {
- padding-left: 0px;
- padding-right: 5px;
- padding-bottom: 10px;
- margin-top: 0.7rem;
- height: 220px;
- overflow-y: auto !important;
- font-weight: 500;
-
- &::-webkit-scrollbar {
- width: 3px;
- height: 8px;
- padding-top: 2px;
- }
-
- &::-webkit-scrollbar-thumb {
- background: $primary-color !important;
- }
-
- li {
- list-style: none;
- border-bottom: 1px solid #c6c6c6;
- display: flex;
- gap: 5px;
- margin-bottom: 18px;
- padding-bottom: 12px;
-
- .file-icon {
- width: 10%;
- }
-
- .file {
- width: 90%;
-
- .file-details {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 5px;
-
- .file-info {
- width: 90%;
- display: flex;
- align-items: baseline;
-
- .file-name {
- display: inline-block;
- max-width: 72%;
- margin-right: 8px;
- }
- }
-
- .file-size {
- font-size: 0.7em;
- }
-
- .retry-upload {
- padding: 3px;
- border-radius: 50%;
- &:hover {
- cursor: pointer;
- background-color: rgba(0, 0, 0, 0.07);
- color: $primary-color;
- }
- }
-
- .rm-file {
- &:hover {
- cursor: pointer;
- color: red;
- }
- }
- }
- }
- }
- }
- }
-}
-
.file-selcted {
.select-files {
width: 40%;
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/File Manager/Files/FileItem.jsx b/frontend/src/File Manager/Files/FileItem.jsx
index afab826..6937829 100644
--- a/frontend/src/File Manager/Files/FileItem.jsx
+++ b/frontend/src/File Manager/Files/FileItem.jsx
@@ -6,38 +6,33 @@ import ContextMenu from "../../components/Context Menu/ContextMenu";
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
import { BiRename } from "react-icons/bi";
import { BsCopy, BsScissors } from "react-icons/bs";
-import { createFolderTree } from "../../utils/createFolderTree";
import { useFileIcons } from "../../hooks/useFileIcons";
import CreateFolderAction from "../Actions/CreateFolder.action";
import RenameAction from "../Actions/Rename.action";
import { getDataSize } from "../../utils/getDataSize";
import { formatDate } from "../../utils/formatDate";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
+import { useSelection } from "../../contexts/SelectionContext";
+import { useClipBoard } from "../../contexts/ClipboardContext";
+import { useLayout } from "../../contexts/LayoutContext";
const FileItem = ({
- activeLayout,
- filesViewRef,
- file,
index,
+ file,
+ onCreateFolder,
+ onPaste,
+ onRename,
+ filesViewRef,
selectedFileIndex,
setSelectedFileIndex,
- setCurrentPath,
- isItemSelection,
- setIsItemSelection,
- setSelectedFile,
- currentPath,
- clipBoard,
- setClipBoard,
- handlePaste,
- files,
triggerAction,
- currentFolder,
- handleCreateFolder,
- currentPathFiles,
- setCurrentPathFiles,
- handleRename,
}) => {
+ const { activeLayout } = useLayout();
const iconSize = activeLayout === "grid" ? 48 : 20;
const fileIcons = useFileIcons(iconSize);
+ const { setCurrentPath, currentPathFiles } = useFileNavigation();
+ const { isItemSelection, setSelectedFile } = useSelection();
+ const { clipBoard, setClipBoard } = useClipBoard();
const [visible, setVisible] = useState(false);
const [fileSelected, setFileSelected] = useState(false);
@@ -54,7 +49,7 @@ const FileItem = ({
const handleCutCopy = (e, isMoving) => {
e.stopPropagation();
setClipBoard({
- files: [{ ...createFolderTree(file, files) }],
+ files: [file],
isMoving: isMoving,
});
setVisible(false);
@@ -63,23 +58,12 @@ const FileItem = ({
const handleFilePasting = (e) => {
e.stopPropagation();
if (clipBoard) {
- const pastePath = file.path;
const selectedCopiedFile = clipBoard.files[0];
- const copiedFiles = files.filter((f) => {
- const folderToCopy =
- f.path === selectedCopiedFile.path && f.name === selectedCopiedFile.name;
- const folderChildren = f.path.startsWith(
- selectedCopiedFile.path + "/" + selectedCopiedFile.name
- );
- return folderToCopy || folderChildren;
- });
-
- const destinationFolder = files.find((f) => f.path === pastePath);
const operationType = clipBoard.isMoving ? "move" : "copy";
- handlePaste(selectedCopiedFile, destinationFolder, operationType);
+ onPaste(selectedCopiedFile, file, operationType);
+
clipBoard.isMoving && setClipBoard(null);
- setIsItemSelection(false);
setSelectedFile(null);
setVisible(false);
}
@@ -112,12 +96,11 @@ const FileItem = ({
e.stopPropagation();
if (file.isEditing) return;
- setIsItemSelection(true);
setSelectedFile(file);
setSelectedFileIndex(index);
const currentTime = new Date().getTime();
if (currentTime - lastClickTime < 300) {
- setIsItemSelection(false);
+ setSelectedFile(null);
handleFileAccess();
}
setLastClickTime(currentTime);
@@ -195,7 +178,6 @@ const FileItem = ({
e.stopPropagation();
return;
}
- setIsItemSelection(true);
setSelectedFile(file);
setSelectedFileIndex(index);
}}
@@ -213,30 +195,23 @@ const FileItem = ({
)}
{file.isEditing ? (
- <>
+
{triggerAction.actionType === "createFolder" ? (
) : (
)}
- >
+
) : (
{file.name}
)}
diff --git a/frontend/src/File Manager/Files/Files.jsx b/frontend/src/File Manager/Files/Files.jsx
index c4d93e6..2c5e812 100644
--- a/frontend/src/File Manager/Files/Files.jsx
+++ b/frontend/src/File Manager/Files/Files.jsx
@@ -1,31 +1,20 @@
import { useEffect, useRef, useState } from "react";
import FileItem from "./FileItem";
import { duplicateNameHandler } from "../../utils/duplicateNameHandler";
+import "./Files.scss";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
+import { useSelection } from "../../contexts/SelectionContext";
+import { useLayout } from "../../contexts/LayoutContext";
-const Files = ({
- activeLayout,
- currentPathFiles,
- setCurrentPathFiles,
- setCurrentPath,
- isItemSelection,
- setIsItemSelection,
- setSelectedFile,
- currentPath,
- clipBoard,
- setClipBoard,
- handlePaste,
- files,
- triggerAction,
- currentFolder,
- handleCreateFolder,
- handleRename,
-}) => {
+const Files = ({ onCreateFolder, onPaste, onRename, triggerAction }) => {
const [selectedFileIndex, setSelectedFileIndex] = useState(null);
+ const { currentPath, currentPathFiles, setCurrentPathFiles } = useFileNavigation();
const filesViewRef = useRef(null);
+ const { setSelectedFile } = useSelection();
+ const { activeLayout } = useLayout();
useEffect(() => {
setSelectedFileIndex(null);
- setIsItemSelection(false);
setSelectedFile(null);
}, [currentPath]);
@@ -52,7 +41,6 @@ const Files = ({
return prev;
});
- setIsItemSelection(false);
setSelectedFileIndex(null);
setSelectedFile(null);
};
@@ -76,7 +64,6 @@ const Files = ({
className={`files ${activeLayout}`}
onClick={(e) => {
setSelectedFileIndex(null);
- setIsItemSelection(false);
setSelectedFile(null);
}}
>
@@ -91,28 +78,16 @@ const Files = ({
<>
{currentPathFiles.map((file, index) => (
))}
>
diff --git a/frontend/src/File Manager/Files/Files.scss b/frontend/src/File Manager/Files/Files.scss
new file mode 100644
index 0000000..22ad3ca
--- /dev/null
+++ b/frontend/src/File Manager/Files/Files.scss
@@ -0,0 +1,214 @@
+@import "../../styles/variables";
+
+.files {
+ position: relative;
+ display: flex;
+ align-content: flex-start;
+ flex-wrap: wrap;
+ column-gap: 0.5em;
+ row-gap: 5px;
+ height: calc(94.2% - 16px);
+ @include overflow-y-scroll;
+ padding: 8px;
+ padding-right: 4px;
+
+ .file-item-container {
+ border-radius: 5px;
+ }
+
+ .file-selected {
+ background-color: rgb(0, 0, 0, 0.04);
+ }
+
+ .file-item {
+ position: relative;
+ height: 81px;
+ width: 138px;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 13px;
+ padding-bottom: 1px;
+ margin: 5px 0;
+ border-radius: 5px;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgb(0, 0, 0, 0.04);
+ }
+
+ .rename-file-container {
+ position: absolute;
+ top: 62px;
+ width: 100%;
+ text-align: center;
+ }
+
+ .rename-file-container.list {
+ top: 6px;
+ left: 48px;
+ text-align: left;
+
+ .rename-file {
+ min-width: 15%;
+ text-align: left;
+ border-radius: 3px;
+ border: none;
+ top: 5px;
+ white-space: nowrap;
+ overflow-x: hidden;
+ max-width: calc(100% - 48px);
+ }
+
+ .folder-name-error.right {
+ left: 0px;
+ bottom: -72px;
+ }
+ }
+
+ .file-name {
+ max-width: 115px;
+ }
+ }
+
+ .file-selected {
+ background-color: $primary-color;
+ color: white;
+
+ &:hover {
+ cursor: pointer;
+ background-color: $primary-color;
+ }
+ }
+
+ .file-moving {
+ opacity: 0.5;
+ }
+
+ .file-context-menu-list {
+ font-size: 1.1em;
+
+ ul {
+ list-style-type: none;
+ padding-left: 0;
+ margin: 0;
+
+ li {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ padding: 6px 22px 6px 14px;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgb(0, 0, 0, 0.04);
+ }
+ }
+
+ li.disable-paste {
+ opacity: 0.5;
+
+ &:hover {
+ cursor: default;
+ background-color: transparent;
+ }
+ }
+ }
+ }
+}
+
+.files.list {
+ flex-direction: column;
+ flex-wrap: nowrap;
+ gap: 0;
+ padding-left: 0px;
+ padding-top: 0px;
+
+ .files-header {
+ font-size: 0.83em;
+ font-weight: 600;
+ display: flex;
+ gap: 5px;
+ border-bottom: 1px solid #dddddd;
+ padding: 4px 0;
+ position: sticky;
+ top: 0;
+ background-color: #f5f5f5;
+ z-index: 1;
+
+ .file-name {
+ width: calc(70% - 65px);
+ padding-left: 65px;
+ }
+
+ .file-date {
+ text-align: center;
+ width: 20%;
+ }
+
+ .file-size {
+ text-align: center;
+ width: 10%;
+ }
+ }
+
+ .file-item-container {
+ display: flex;
+ width: 100%;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgb(0, 0, 0, 0.04);
+ }
+ }
+
+ .file-item-container.file-selected {
+ &:hover {
+ background-color: $primary-color !important;
+ }
+ }
+
+ .file-item {
+ flex-direction: row;
+ height: 13px;
+ justify-content: unset;
+ padding: 15px;
+ padding-left: 20px;
+ margin: 0;
+ width: calc(70% - 30px);
+
+ &:hover {
+ background-color: unset;
+ }
+
+ .file-name {
+ max-width: 285px;
+ }
+ }
+
+ .modified-date {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8em;
+ width: calc(20%);
+ }
+
+ .size {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8em;
+ width: calc(10%);
+ }
+}
+
+.empty-folder {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/frontend/src/File Manager/Navigation Pane/NavigationPane.jsx b/frontend/src/File Manager/Navigation Pane/NavigationPane.jsx
index c4b3d36..76c17f3 100644
--- a/frontend/src/File Manager/Navigation Pane/NavigationPane.jsx
+++ b/frontend/src/File Manager/Navigation Pane/NavigationPane.jsx
@@ -1,9 +1,12 @@
import React, { useEffect, useState } from "react";
import SideBarSubDirectories from "./SideBarSubDirectories";
import { getParentPath } from "../../utils/getParentPath";
+import { useFiles } from "../../contexts/FilesContext";
+import "./NavigationPane.scss";
-const NavigationPane = ({ files, currentPath, setCurrentPath }) => {
+const NavigationPane = () => {
const [foldersTree, setFoldersTree] = useState([]);
+ const { files } = useFiles();
const createChildRecursive = (path, foldersStruct) => {
if (!foldersStruct[path]) return []; // No children for this path (folder)
@@ -33,14 +36,7 @@ const NavigationPane = ({ files, currentPath, setCurrentPath }) => {
{foldersTree?.length > 0 ? (
<>
{foldersTree?.map((folder, index) => {
- return (
-
- );
+ return ;
})}
>
) : (
diff --git a/frontend/src/File Manager/Navigation Pane/NavigationPane.scss b/frontend/src/File Manager/Navigation Pane/NavigationPane.scss
new file mode 100644
index 0000000..44fbef1
--- /dev/null
+++ b/frontend/src/File Manager/Navigation Pane/NavigationPane.scss
@@ -0,0 +1,91 @@
+@import "../../styles/variables";
+
+.sb-folders-list {
+ list-style: none;
+ margin: 0px 4px;
+ height: 100%;
+ @include overflow-y-scroll;
+
+ .folder-collapsible {
+ margin-left: 10px;
+ }
+
+ .sb-folders-list-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 4px;
+ padding: 6px 5px;
+ border-radius: 4px;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgb(0, 0, 0, 0.04);
+ }
+
+ .non-expanable {
+ min-width: 20px;
+ }
+
+ .sb-folder-details {
+ display: flex;
+ align-items: center;
+
+ .folder-open-icon {
+ margin: 0 7px;
+ }
+
+ .folder-close-icon {
+ margin: 1px 9px 0px 8px;
+ }
+
+ .sb-folder-name {
+ width: max-content;
+ }
+ }
+
+ .folder-rotate-down {
+ animation: rotateFolderDown 0.5s linear forwards;
+ }
+
+ @keyframes rotateFolderDown {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(90deg);
+ }
+ }
+
+ .folder-rotate-right {
+ animation: rotateRight 0.5s linear forwards;
+ }
+
+ @keyframes rotateRight {
+ from {
+ transform: rotate(90deg);
+ }
+
+ to {
+ transform: rotate(0deg);
+ }
+ }
+ }
+
+ .active-list-item {
+ background-color: $primary-color;
+ color: white;
+
+ &:hover {
+ cursor: pointer;
+ background-color: $primary-color;
+ }
+ }
+
+ .empty-nav-pane {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ }
+}
diff --git a/frontend/src/File Manager/Navigation Pane/SideBarSubDirectories.jsx b/frontend/src/File Manager/Navigation Pane/SideBarSubDirectories.jsx
index 9105d41..27c1839 100644
--- a/frontend/src/File Manager/Navigation Pane/SideBarSubDirectories.jsx
+++ b/frontend/src/File Manager/Navigation Pane/SideBarSubDirectories.jsx
@@ -2,10 +2,12 @@ import React, { useEffect, useState } from "react";
import Collapsible from "react-collapsible";
import { FaRegFolder, FaRegFolderOpen } from "react-icons/fa";
import { MdKeyboardArrowRight } from "react-icons/md";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
-const SideBarSubDirectories = ({ folder, setCurrentPath, currentPath }) => {
+const SideBarSubDirectories = ({ folder }) => {
const [isOpen, setIsOpen] = useState(false);
const [isActive, setIsActive] = useState(false);
+ const { currentPath, setCurrentPath } = useFileNavigation();
const handleFolderSwitch = () => {
setIsActive(true);
@@ -59,12 +61,7 @@ const SideBarSubDirectories = ({ folder, setCurrentPath, currentPath }) => {
{folder.subDirectories.map((item, index) => (
-
+
))}
diff --git a/frontend/src/File Manager/Toolbar/ToggleView.jsx b/frontend/src/File Manager/Toolbar/ToggleView.jsx
index 1b3e8cb..558a6db 100644
--- a/frontend/src/File Manager/Toolbar/ToggleView.jsx
+++ b/frontend/src/File Manager/Toolbar/ToggleView.jsx
@@ -1,11 +1,14 @@
import { BsGridFill } from "react-icons/bs";
import { FaCheck, FaListUl } from "react-icons/fa6";
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
+import { useLayout } from "../../contexts/LayoutContext";
-const ToggleView = ({ activeLayout, setActiveLayout, setShowToggleViewMenu, onLayoutChange }) => {
+const ToggleView = ({ setShowToggleViewMenu, onLayoutChange }) => {
const toggleViewRef = useDetectOutsideClick(() => {
setShowToggleViewMenu(false);
});
+ const { activeLayout, setActiveLayout } = useLayout();
+
const layoutOptions = [
{
key: "grid",
@@ -21,8 +24,8 @@ const ToggleView = ({ activeLayout, setActiveLayout, setShowToggleViewMenu, onLa
const handleSelection = (key) => {
setActiveLayout(key);
- setShowToggleViewMenu(false);
onLayoutChange(key);
+ setShowToggleViewMenu(false);
};
return (
diff --git a/frontend/src/File Manager/Toolbar/Toolbar.jsx b/frontend/src/File Manager/Toolbar/Toolbar.jsx
index bd8a663..a24a38e 100644
--- a/frontend/src/File Manager/Toolbar/Toolbar.jsx
+++ b/frontend/src/File Manager/Toolbar/Toolbar.jsx
@@ -1,33 +1,32 @@
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { BsCopy, BsFolderPlus, BsGridFill, BsScissors } from "react-icons/bs";
import { FiRefreshCw } from "react-icons/fi";
import { MdClear, MdOutlineDelete, MdOutlineFileUpload } from "react-icons/md";
import { BiRename } from "react-icons/bi";
import { FaListUl, FaRegPaste } from "react-icons/fa6";
-import { createFolderTree } from "../../utils/createFolderTree";
import ToggleView from "./ToggleView";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
+import { useSelection } from "../../contexts/SelectionContext";
+import { useClipBoard } from "../../contexts/ClipboardContext";
+import "./Toolbar.scss";
+import { useLayout } from "../../contexts/LayoutContext";
const Toolbar = ({
allowCreateFolder = true,
allowUploadFile = true,
- handleRefresh,
- isItemSelection,
- setIsItemSelection,
- currentPath,
- selectedFile,
- setSelectedFile,
- files,
- clipBoard,
- setClipBoard,
- handlePaste,
- triggerAction,
+ onPaste,
onLayoutChange,
- activeLayout,
- setActiveLayout,
+ onRefresh,
+ triggerAction,
}) => {
const [showToggleViewMenu, setShowToggleViewMenu] = useState(false);
+ const { currentFolder } = useFileNavigation();
+ const { isItemSelection, selectedFile, setSelectedFile } = useSelection();
+ const { clipBoard, setClipBoard } = useClipBoard();
+ const { activeLayout } = useLayout();
+
// Toolbar Items
- const [toolbarLeftItems, setToolbarLeftItems] = useState([
+ const toolbarLeftItems = [
{
icon: ,
text: "New Folder",
@@ -43,10 +42,10 @@ const Toolbar = ({
{
icon: ,
text: "Paste",
- permission: false,
- onClick: () => {},
+ permission: !!clipBoard,
+ onClick: handlePasting,
},
- ]);
+ ];
const toolbarRightItems = [
{
@@ -58,61 +57,36 @@ const Toolbar = ({
icon: ,
title: "Refresh",
onClick: () => {
- handleRefresh();
+ onRefresh();
setClipBoard(null);
},
},
];
- // Handle Pasting
- const handlePasting = (files, pastePath, clipBoard) => {
- const selectedCopiedFile = clipBoard.files[0];
- const copiedFiles = files.filter((f) => {
- const folderToCopy = f.path === selectedCopiedFile.path && f.name === selectedCopiedFile.name;
- const folderChildren = f.path.startsWith(
- selectedCopiedFile.path + "/" + selectedCopiedFile.name
- );
- return folderToCopy || folderChildren;
- });
-
- const destinationFolder = files.find((file) => file.path === pastePath);
- const operationType = clipBoard.isMoving ? "move" : "copy";
-
- handlePaste(selectedCopiedFile, destinationFolder, operationType);
- clipBoard.isMoving && setClipBoard(null);
- setIsItemSelection(false);
- setSelectedFile(null);
- };
-
- useEffect(() => {
- setToolbarLeftItems((prev) => {
- return prev.map((item) => {
- if (item.text === "Paste") {
- return {
- ...item,
- permission: !!clipBoard,
- onClick: () => handlePasting(files, currentPath, clipBoard),
- };
- } else {
- return item;
- }
- });
- });
- }, [clipBoard, currentPath, files]);
-
// Handle Cut / Copy
const handleCutCopy = (isMoving) => {
setClipBoard({
- files: [{ ...createFolderTree(selectedFile, files) }],
+ files: [selectedFile],
isMoving: isMoving,
});
};
//
+ // Handle Pasting
+ // Todo: Show error if destination folder already has file(s) with the same name
+ function handlePasting() {
+ const selectedCopiedFile = clipBoard.files[0];
+ const destinationFolder = isItemSelection ? selectedFile : currentFolder;
+ const operationType = clipBoard.isMoving ? "move" : "copy";
+
+ onPaste(selectedCopiedFile, destinationFolder, operationType);
+
+ clipBoard.isMoving && setClipBoard(null);
+ setSelectedFile(null);
+ }
+
// Selected File/Folder Actions
if (isItemSelection) {
- const pastePath = selectedFile.path;
-
return (
@@ -125,10 +99,10 @@ const Toolbar = ({
Copy
- {selectedFile.isDirectory ? (
+ {selectedFile?.isDirectory ? (
handlePasting(files, pastePath, clipBoard)}
+ onClick={handlePasting}
disabled={!clipBoard}
>
@@ -152,7 +126,7 @@ const Toolbar = ({
Delete
-
setIsItemSelection(false)}>
+ setSelectedFile(null)}>
Clear Selection
@@ -187,8 +161,6 @@ const Toolbar = ({
{showToggleViewMenu && (
diff --git a/frontend/src/File Manager/Toolbar/Toolbar.scss b/frontend/src/File Manager/Toolbar/Toolbar.scss
new file mode 100644
index 0000000..8527fa4
--- /dev/null
+++ b/frontend/src/File Manager/Toolbar/Toolbar.scss
@@ -0,0 +1,129 @@
+@import "../../styles/variables";
+
+.toolbar {
+ height: calc(7.6% - 13px);
+ min-height: 35px;
+ border-bottom: 1px solid #dddddd;
+ padding: 6px 10px;
+
+ .file-action-container {
+ display: flex;
+ justify-content: space-between;
+
+ > div {
+ display: flex;
+ }
+
+ .file-action {
+ background-color: transparent;
+ gap: 5px;
+
+ &:hover:not(:disabled) {
+ cursor: pointer;
+ background-color: rgb(0, 0, 0, 0.55) !important;
+ border-radius: 3px;
+ color: white;
+ text-shadow: 0px 0px 1px white;
+ }
+
+ &:hover:disabled {
+ cursor: default;
+ background-color: transparent !important;
+ color: #b0b0b0;
+ text-shadow: none;
+ }
+ }
+ }
+
+ .fm-toolbar {
+ display: flex;
+ justify-content: space-between;
+
+ > div {
+ display: flex;
+ position: relative;
+ }
+
+ .toolbar-left-items {
+ display: flex;
+ }
+
+ .toggle-view {
+ position: absolute;
+ z-index: 3;
+ top: 105%;
+ right: 22%;
+ background-color: white;
+ margin: 0;
+ border: 1px solid #c4c4c4;
+ border-radius: 5px;
+ ul {
+ list-style: none;
+ padding-left: 0;
+ margin: 0.4em 0;
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+
+ li {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 5px 20px 5px 10px;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgba(0, 0, 0, 0.075);
+ }
+
+ span:nth-child(1) {
+ width: 13px;
+ }
+ }
+ }
+ }
+ }
+
+ .item-action {
+ background-color: white;
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ padding: 8px 12px;
+ font-size: 14px;
+ width: fit-content;
+ border: none;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgb(0 0 0 / 12%) !important;
+ border-radius: 3px;
+ color: $secondary-color-lighter;
+ }
+
+ .toggle-view-icon {
+ background-color: transparent;
+ border: none;
+
+ &:hover {
+ cursor: pointer;
+ color: $secondary-color-lighter;
+ }
+ }
+ }
+
+ .icon-only {
+ padding: 8px !important;
+
+ &:focus {
+ background-color: rgb(0 0 0 / 12%);
+ }
+ }
+
+ .item-separator {
+ height: 36px;
+ background: #dee1e3;
+ width: 1px;
+ margin: 0 5px;
+ }
+}
diff --git a/frontend/src/Mock APIs/api.js b/frontend/src/api/api.js
similarity index 100%
rename from frontend/src/Mock APIs/api.js
rename to frontend/src/api/api.js
diff --git a/frontend/src/Mock APIs/createFolderAPI.js b/frontend/src/api/createFolderAPI.js
similarity index 100%
rename from frontend/src/Mock APIs/createFolderAPI.js
rename to frontend/src/api/createFolderAPI.js
diff --git a/frontend/src/Mock APIs/deleteAPI.js b/frontend/src/api/deleteAPI.js
similarity index 100%
rename from frontend/src/Mock APIs/deleteAPI.js
rename to frontend/src/api/deleteAPI.js
diff --git a/frontend/src/Mock APIs/fileTransferAPI.js b/frontend/src/api/fileTransferAPI.js
similarity index 100%
rename from frontend/src/Mock APIs/fileTransferAPI.js
rename to frontend/src/api/fileTransferAPI.js
diff --git a/frontend/src/Mock APIs/getAllFilesAPI.js b/frontend/src/api/getAllFilesAPI.js
similarity index 100%
rename from frontend/src/Mock APIs/getAllFilesAPI.js
rename to frontend/src/api/getAllFilesAPI.js
diff --git a/frontend/src/Mock APIs/renameAPI.js b/frontend/src/api/renameAPI.js
similarity index 100%
rename from frontend/src/Mock APIs/renameAPI.js
rename to frontend/src/api/renameAPI.js
diff --git a/frontend/src/components/Error Tooltip/ErrorTooltip.jsx b/frontend/src/components/Error Tooltip/ErrorTooltip.jsx
new file mode 100644
index 0000000..7c41408
--- /dev/null
+++ b/frontend/src/components/Error Tooltip/ErrorTooltip.jsx
@@ -0,0 +1,7 @@
+import "./ErrorTooltip.scss";
+
+const ErrorTooltip = ({ message, xPlacement, yPlacement }) => {
+ return {message}
;
+};
+
+export default ErrorTooltip;
diff --git a/frontend/src/components/Error Tooltip/ErrorTooltip.scss b/frontend/src/components/Error Tooltip/ErrorTooltip.scss
new file mode 100644
index 0000000..8550e3a
--- /dev/null
+++ b/frontend/src/components/Error Tooltip/ErrorTooltip.scss
@@ -0,0 +1,65 @@
+.error-tooltip {
+ position: absolute;
+ z-index: 1;
+ bottom: -68px;
+ left: 16px;
+ padding: 8px;
+ width: 292px;
+ border-radius: 5px;
+ background-color: #696969;
+ text-align: left;
+ margin: 0;
+ font-size: 0.9em;
+
+ &::before {
+ content: "";
+ position: absolute;
+ top: -20%;
+ rotate: -45deg;
+ border: 15px solid #696969;
+ border-color: transparent #696969 transparent transparent;
+ }
+}
+
+.error-tooltip.right {
+ left: 16px;
+
+ &::before {
+ left: 11%;
+ }
+}
+
+.error-tooltip.left {
+ left: -180px;
+
+ &::before {
+ left: 76%;
+ transform: rotate(90deg) scaleX(-1);
+ }
+}
+
+.error-tooltip.top {
+ bottom: unset !important;
+ top: -68px;
+
+ &::before {
+ content: none;
+ }
+
+ &:after {
+ content: "";
+ position: absolute;
+ bottom: -20%;
+ left: 11%;
+ rotate: -45deg;
+ border: 15px solid #696969;
+ border-color: transparent transparent #696969 transparent;
+ }
+}
+
+.error-tooltip.top.left {
+ &::after {
+ left: 76%;
+ transform: rotate(90deg) scaleX(-1);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/Name Input/NameInput.jsx b/frontend/src/components/Name Input/NameInput.jsx
new file mode 100644
index 0000000..0d8130a
--- /dev/null
+++ b/frontend/src/components/Name Input/NameInput.jsx
@@ -0,0 +1,18 @@
+import "./NameInput.scss";
+
+const NameInput = ({ nameInputRef, maxLength, value, onChange, onKeyDown, onClick, rows }) => {
+ return (
+
+ );
+};
+
+export default NameInput;
diff --git a/frontend/src/components/Name Input/NameInput.scss b/frontend/src/components/Name Input/NameInput.scss
new file mode 100644
index 0000000..31d4d5b
--- /dev/null
+++ b/frontend/src/components/Name Input/NameInput.scss
@@ -0,0 +1,20 @@
+ @import "../../styles/variables";
+
+ .rename-file {
+ position: relative;
+ min-width: 69%;
+ max-width: -webkit-fill-available;
+ text-align: center;
+ border: 4px solid $primary-color;
+ border-radius: 10px;
+ font-size: 15px;
+ font-family: "Nunito Sans", sans-serif;
+ padding: 2px 7px;
+ resize: none;
+ field-sizing: content;
+ overflow-y: hidden;
+
+ &:focus {
+ outline: none;
+ }
+ }
\ No newline at end of file
diff --git a/frontend/src/contexts/ClipboardContext.jsx b/frontend/src/contexts/ClipboardContext.jsx
new file mode 100644
index 0000000..d02ac5e
--- /dev/null
+++ b/frontend/src/contexts/ClipboardContext.jsx
@@ -0,0 +1,15 @@
+import { createContext, useContext, useState } from "react";
+
+const ClipBoardContext = createContext();
+
+export const ClipBoardProvider = ({ children }) => {
+ const [clipBoard, setClipBoard] = useState(null);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useClipBoard = () => useContext(ClipBoardContext);
diff --git a/frontend/src/contexts/FileNavigationContext.jsx b/frontend/src/contexts/FileNavigationContext.jsx
new file mode 100644
index 0000000..e5066f5
--- /dev/null
+++ b/frontend/src/contexts/FileNavigationContext.jsx
@@ -0,0 +1,40 @@
+import { createContext, useContext, useEffect, useState } from "react";
+import { useFiles } from "./FilesContext";
+
+const FileNavigationContext = createContext();
+
+export const FileNavigationProvider = ({ children }) => {
+ const [currentPath, setCurrentPath] = useState("");
+ const [currentFolder, setCurrentFolder] = useState(null);
+ const [currentPathFiles, setCurrentPathFiles] = useState([]);
+ const { files } = useFiles();
+
+ useEffect(() => {
+ if (Array.isArray(files) && files.length > 0) {
+ setCurrentPathFiles(() => {
+ return files.filter((file) => file.path === `${currentPath}/${file.name}`);
+ });
+
+ setCurrentFolder(() => {
+ return files.find((file) => file.path === currentPath) ?? null;
+ });
+ }
+ }, [files, currentPath]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFileNavigation = () => useContext(FileNavigationContext);
diff --git a/frontend/src/contexts/FilesContext.jsx b/frontend/src/contexts/FilesContext.jsx
new file mode 100644
index 0000000..73b974f
--- /dev/null
+++ b/frontend/src/contexts/FilesContext.jsx
@@ -0,0 +1,25 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+const FilesContext = createContext();
+
+export const FilesProvider = ({ children, filesData }) => {
+ const [files, setFiles] = useState([]);
+
+ useEffect(() => {
+ setFiles(filesData);
+ }, [filesData]);
+
+ const getChildren = (file) => {
+ if (!file.isDirectory) return [];
+
+ return files.filter((child) => child.path === `${file.path}/${child.name}`);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFiles = () => useContext(FilesContext);
diff --git a/frontend/src/contexts/LayoutContext.jsx b/frontend/src/contexts/LayoutContext.jsx
new file mode 100644
index 0000000..1646e1a
--- /dev/null
+++ b/frontend/src/contexts/LayoutContext.jsx
@@ -0,0 +1,15 @@
+import { createContext, useContext, useState } from "react";
+
+const LayoutContext = createContext();
+
+export const LayoutProvider = ({ children }) => {
+ const [activeLayout, setActiveLayout] = useState("grid");
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useLayout = () => useContext(LayoutContext);
diff --git a/frontend/src/contexts/SelectionContext.jsx b/frontend/src/contexts/SelectionContext.jsx
new file mode 100644
index 0000000..3386684
--- /dev/null
+++ b/frontend/src/contexts/SelectionContext.jsx
@@ -0,0 +1,22 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+const SelectionContext = createContext();
+
+export const SelectionProvider = ({ children }) => {
+ const [isItemSelection, setIsItemSelection] = useState(false);
+ const [selectedFile, setSelectedFile] = useState(null); // This will be selectedFiles as an array for multiple selection in future
+
+ useEffect(() => {
+ setIsItemSelection(!!selectedFile);
+ }, [selectedFile]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useSelection = () => useContext(SelectionContext);
diff --git a/frontend/src/hooks/useColumnResize.js b/frontend/src/hooks/useColumnResize.js
new file mode 100644
index 0000000..3e9e8b1
--- /dev/null
+++ b/frontend/src/hooks/useColumnResize.js
@@ -0,0 +1,41 @@
+import { useRef, useState } from "react";
+
+export const useColumnResize = (col1Size, col2Size) => {
+ const [colSizes, setColSizes] = useState({ col1: col1Size, col2: col2Size });
+ const [isDragging, setIsDragging] = useState(false);
+ const containerRef = useRef(null);
+
+ const handleMouseDown = () => {
+ setIsDragging(true);
+ };
+
+ const handleMouseUp = () => {
+ setIsDragging(false);
+ };
+
+ const handleMouseMove = (e) => {
+ if (!isDragging) return;
+ // Prevent text selection during drag
+ e.preventDefault();
+
+ // Calculate new sizes based on mouse movement
+ const container = containerRef.current;
+ const containerRect = container.getBoundingClientRect();
+ const newCol1Size = ((e.clientX - containerRect.left) / containerRect.width) * 100;
+
+ // Limiting the resizing to 15% to 60% for better UX
+ if (newCol1Size >= 15 && newCol1Size <= 60) {
+ setColSizes({ col1: newCol1Size, col2: 100 - newCol1Size });
+ }
+ };
+
+ return {
+ containerRef,
+ colSizes,
+ setColSizes,
+ isDragging,
+ handleMouseDown,
+ handleMouseUp,
+ handleMouseMove,
+ };
+};
diff --git a/frontend/src/styles/_variables.scss b/frontend/src/styles/_variables.scss
new file mode 100644
index 0000000..a6cd46e
--- /dev/null
+++ b/frontend/src/styles/_variables.scss
@@ -0,0 +1,28 @@
+// App Colors
+$secondary-color: #6155b4;
+$primary-color: #6155b4;
+$secondary-color-lighter: #5549a3;
+//
+
+// Resuable Styles
+@mixin flex-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+@mixin overflow-y-scroll {
+ overflow-y: auto !important;
+
+ &::-webkit-scrollbar {
+ width: 5px;
+ height: 8px;
+ padding-top: 2px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: $primary-color !important;
+ border-radius: 8px;
+ }
+}
+//