Skip to content

Commit 5a07ec9

Browse files
Merge pull request #91 from Saifullah-dev/feature/keyboard-shortcuts
Feature/keyboard shortcuts
2 parents bfc4177 + 62bfba1 commit 5a07ec9

File tree

20 files changed

+306
-97
lines changed

20 files changed

+306
-97
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ An open-source React.js package for easy integration of a file manager into appl
1717
- **Navigation**: Use the breadcrumb trail and sidebar navigation pane for quick directory traversal.
1818
- **Toolbar & Context Menu**: Access all common actions (upload, download, delete, copy, move, rename, etc.) via the toolbar or right-click for the same options in the context menu.
1919
- **Multi-Selection**: Select multiple files and folders at once to perform bulk actions like delete, copy, move, or download.
20+
- **Keyboard Shortcuts**: Quickly perform file operations like copy, paste, delete, and more using intuitive keyboard shortcuts.
2021

2122
![React File Manager](https://github.com/user-attachments/assets/e68f750b-86bf-450d-b27e-fd3dedebf1bd)
2223

@@ -110,6 +111,26 @@ type File = {
110111
| `onRename` | (file: [File](#-file-structure), newName: string) => void | A callback function triggered when a file or folder is renamed. |
111112
| `width` | string \| number | The width of the component `default: 100%`. Can be a string (e.g., `'100%'`, `'10rem'`) or a number (in pixels). |
112113

114+
## ⌨️ Keyboard Shortcuts
115+
116+
| **Action** | **Shortcut** |
117+
| ------------------------------ | ------------------ |
118+
| New Folder | `Alt + Shift + N` |
119+
| Upload Files | `CTRL + U` |
120+
| Cut | `CTRL + X` |
121+
| Copy | `CTRL + C` |
122+
| Paste | `CTRL + V` |
123+
| Rename | `F2` |
124+
| Download | `CTRL + D` |
125+
| Delete | `DEL` |
126+
| Select All Files | `CTRL + A` |
127+
| Jump to First File in the List | `Home` |
128+
| Jump to Last File in the List | `End` |
129+
| Switch to List Layout | `CTRL + Shift + 1` |
130+
| Switch to Grid Layout | `CTRL + Shift + 2` |
131+
| Refresh File List | `F5` |
132+
| Clear Selection | `Esc` |
133+
113134
## 🤝 Contributing
114135

115136
Contributions are welcome! To contribute:

backend/Readme.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ The backend supports the following file system operations:
6565

6666
- **📁 Create a Folder**: `/folder`
6767
- **⬆️ Upload a File**: `/upload`
68-
- **📋 Copy a File/Folder**: `/copy`
68+
- **📋 Copy File(s) or Folder(s)**: `/copy`
6969
- **📂 Get All Files/Folders**: `/`
70-
- **⬇️ Download a File**: `/download/:id`
71-
- **📤 Move a File/Folder**: `/move`
72-
- **✏️ Rename a File/Folder**: `/rename`
73-
- **🗑️ Delete a File/Folder**: `/:id`
70+
- **⬇️ Download File(s) or Folder(s)**: `/download`
71+
- **📤 Move File(s) or Folder(s)**: `/move`
72+
- **✏️ Rename a File or Folder**: `/rename`
73+
- **🗑️ Delete File(s) or Folder(s)**: `/`
7474

7575
Refer to the [Swagger Documentation](http://localhost:3000/api-docs/) for detailed request/response formats.
7676

backend/app/controllers/deleteItem.controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const deleteRecursive = async (item) => {
1414
};
1515

1616
const deleteItem = async (req, res) => {
17-
// #swagger.summary = 'Deletes a file/folder(s).'
17+
// #swagger.summary = 'Deletes file/folder(s).'
1818
/* #swagger.parameters['body'] = {
1919
in: 'body',
2020
required: true,

backend/app/controllers/downloadFile.controller.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ const mongoose = require("mongoose");
55
const archiver = require("archiver");
66

77
const downloadFile = async (req, res) => {
8-
// #swagger.summary = 'Downloads a file.'
8+
// Todo: Update download request query swagger docs.
9+
// #swagger.summary = 'Downloads file/folder(s).'
910
/* #swagger.parameters['filePath'] = {
1011
in: 'query',
1112
type: 'string',

frontend/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ An open-source React.js package for easy integration of a file manager into appl
1717
- **Navigation**: Use the breadcrumb trail and sidebar navigation pane for quick directory traversal.
1818
- **Toolbar & Context Menu**: Access all common actions (upload, download, delete, copy, move, rename, etc.) via the toolbar or right-click for the same options in the context menu.
1919
- **Multi-Selection**: Select multiple files and folders at once to perform bulk actions like delete, copy, move, or download.
20+
- **Keyboard Shortcuts**: Quickly perform file operations like copy, paste, delete, and more using intuitive keyboard shortcuts.
2021

2122
![React File Manager](https://github.com/user-attachments/assets/e68f750b-86bf-450d-b27e-fd3dedebf1bd)
2223

@@ -110,6 +111,26 @@ type File = {
110111
| `onRename` | (file: [File](#-file-structure), newName: string) => void | A callback function triggered when a file or folder is renamed. |
111112
| `width` | string \| number | The width of the component `default: 100%`. Can be a string (e.g., `'100%'`, `'10rem'`) or a number (in pixels). |
112113

114+
## ⌨️ Keyboard Shortcuts
115+
116+
| **Action** | **Shortcut** |
117+
| ------------------------------ | ------------------ |
118+
| New Folder | `Alt + Shift + N` |
119+
| Upload Files | `CTRL + U` |
120+
| Cut | `CTRL + X` |
121+
| Copy | `CTRL + C` |
122+
| Paste | `CTRL + V` |
123+
| Rename | `F2` |
124+
| Download | `CTRL + D` |
125+
| Delete | `DEL` |
126+
| Select All Files | `CTRL + A` |
127+
| Jump to First File in the List | `Home` |
128+
| Jump to Last File in the List | `End` |
129+
| Switch to List Layout | `CTRL + Shift + 1` |
130+
| Switch to Grid Layout | `CTRL + Shift + 2` |
131+
| Refresh File List | `F5` |
132+
| Clear Selection | `Esc` |
133+
113134
## 🤝 Contributing
114135

115136
Contributions are welcome! To contribute:

frontend/src/FileManager/Actions/Actions.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import DeleteAction from "./Delete/Delete.action";
44
import UploadFileAction from "./UploadFile/UploadFile.action";
55
import PreviewFileAction from "./PreviewFile/PreviewFile.action";
66
import { useSelection } from "../../contexts/SelectionContext";
7+
import { useShortcutHandler } from "../../hooks/useShortcutHandler";
78

89
const Actions = ({
910
fileUploadConfig,
1011
onFileUploading,
1112
onFileUploaded,
1213
onDelete,
14+
onRefresh,
1315
maxFileSize,
1416
filePreviewPath,
1517
acceptedFileTypes,
@@ -18,6 +20,9 @@ const Actions = ({
1820
const [activeAction, setActiveAction] = useState(null);
1921
const { selectedFiles } = useSelection();
2022

23+
// Triggers all the keyboard shortcuts based actions
24+
useShortcutHandler(triggerAction, onRefresh);
25+
2126
const actionTypes = {
2227
uploadFile: {
2328
title: "Upload",

frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction
3030

3131
// Validate folder name and call "onCreateFolder" function
3232
const handleValidateFolderName = (e) => {
33+
e.stopPropagation();
3334
if (e.key === "Enter") {
3435
e.preventDefault();
35-
e.stopPropagation();
3636
handleFolderCreating();
3737
return;
3838
}

frontend/src/FileManager/Actions/Rename/Rename.action.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ const RenameAction = ({ filesViewRef, file, onRename, triggerAction }) => {
3030
});
3131

3232
const handleValidateFolderRename = (e) => {
33+
e.stopPropagation();
3334
if (e.key === "Enter") {
3435
e.preventDefault();
35-
e.stopPropagation();
3636
outsideClick.setIsClicked(true);
3737
return;
3838
}

frontend/src/FileManager/Actions/UploadFile/UploadFile.action.jsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useRef, useState } from "react";
22
import Button from "../../../components/Button/Button";
33
import { AiOutlineCloudUpload } from "react-icons/ai";
44
import UploadItem from "./UploadItem";
@@ -21,6 +21,14 @@ const UploadFileAction = ({
2121
const [isUploading, setIsUploading] = useState({});
2222
const { currentFolder, currentPathFiles } = useFileNavigation();
2323
const { onError } = useFiles();
24+
const fileInputRef = useRef(null);
25+
26+
// To open choose file if the "Choose File" button is focused and Enter key is pressed
27+
const handleChooseFileKeyDown = (e) => {
28+
if (e.key === "Enter") {
29+
fileInputRef.current.click();
30+
}
31+
};
2432

2533
const checkFileError = (file) => {
2634
const extError = !acceptedFileTypes.includes(getFileExtension(file.name));
@@ -104,9 +112,10 @@ const UploadFileAction = ({
104112
</div>
105113
</div>
106114
<div className="btn-choose-file">
107-
<Button padding="0">
115+
<Button padding="0" onKeyDown={handleChooseFileKeyDown}>
108116
<label htmlFor="chooseFile">Choose File</label>
109117
<input
118+
ref={fileInputRef}
110119
type="file"
111120
id="chooseFile"
112121
className="choose-file-input"

frontend/src/FileManager/FileList/FileItem.jsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const FileItem = ({
2020
onFileOpen,
2121
filesViewRef,
2222
selectedFileIndexes,
23-
setSelectedFileIndexes,
2423
triggerAction,
2524
handleContextMenu,
2625
setLastSelectedFile,
@@ -44,7 +43,6 @@ const FileItem = ({
4443
onFileOpen(file);
4544
if (file.isDirectory) {
4645
setCurrentPath(file.path);
47-
setSelectedFileIndexes([]);
4846
setSelectedFiles([]);
4947
} else {
5048
enableFilePreview && triggerAction.show("previewFile");
@@ -56,7 +54,6 @@ const FileItem = ({
5654
if (file.isEditing) return;
5755

5856
setSelectedFiles([file]);
59-
setSelectedFileIndexes([index]);
6057
const currentTime = new Date().getTime();
6158
if (currentTime - lastClickTime < 300) {
6259
handleFileAccess();
@@ -66,10 +63,9 @@ const FileItem = ({
6663
};
6764

6865
const handleOnKeyDown = (e) => {
69-
e.stopPropagation();
7066
if (e.key === "Enter") {
67+
e.stopPropagation();
7168
setSelectedFiles([file]);
72-
setSelectedFileIndexes([index]);
7369
handleFileAccess();
7470
}
7571
};
@@ -82,7 +78,6 @@ const FileItem = ({
8278

8379
if (!fileSelected) {
8480
setSelectedFiles([file]);
85-
setSelectedFileIndexes([index]);
8681
}
8782

8883
setLastSelectedFile(file);
@@ -101,10 +96,8 @@ const FileItem = ({
10196
const handleCheckboxChange = (e) => {
10297
if (e.target.checked) {
10398
setSelectedFiles((prev) => [...prev, file]);
104-
setSelectedFileIndexes((prev) => [...prev, index]);
10599
} else {
106100
setSelectedFiles((prev) => prev.filter((f) => f.name !== file.name && f.path !== file.path));
107-
setSelectedFileIndexes((prev) => prev.filter((i) => i !== index));
108101
}
109102

110103
setFileSelected(e.target.checked);

0 commit comments

Comments
 (0)