Skip to content

Commit 4829aaf

Browse files
Merge pull request #111 from Saifullah-dev/feature/context-menu
109: [FEAT] Context Menu - Make Global Context Menu Functional
2 parents b756620 + a14284a commit 4829aaf

File tree

10 files changed

+481
-248
lines changed

10 files changed

+481
-248
lines changed

frontend/src/FileManager/FileList/FileList.jsx

Lines changed: 30 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,212 +1,49 @@
1-
import { useEffect, useRef, useState } from "react";
1+
import { useRef } from "react";
22
import FileItem from "./FileItem";
3-
import { duplicateNameHandler } from "../../utils/duplicateNameHandler";
43
import { useFileNavigation } from "../../contexts/FileNavigationContext";
5-
import { useSelection } from "../../contexts/SelectionContext";
64
import { useLayout } from "../../contexts/LayoutContext";
75
import ContextMenu from "../../components/ContextMenu/ContextMenu";
86
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
9-
import { BsCopy, BsFolderPlus, BsScissors } from "react-icons/bs";
10-
import { MdOutlineDelete, MdOutlineFileDownload, MdOutlineFileUpload } from "react-icons/md";
11-
import { FiRefreshCw } from "react-icons/fi";
7+
import useFileList from "./useFileList";
8+
import FilesHeader from "./FilesHeader";
129
import "./FileList.scss";
13-
import { PiFolderOpen } from "react-icons/pi";
14-
import { FaRegFile, FaRegPaste } from "react-icons/fa6";
15-
import { BiRename } from "react-icons/bi";
16-
import { useClipBoard } from "../../contexts/ClipboardContext";
1710

18-
const FileList = ({ onCreateFolder, onRename, onFileOpen, enableFilePreview, triggerAction }) => {
19-
const [selectedFileIndexes, setSelectedFileIndexes] = useState([]);
20-
const [visible, setVisible] = useState(false);
21-
const [isSelectionCtx, setIsSelectionCtx] = useState(false);
22-
const [clickPosition, setClickPosition] = useState({ clickX: 0, clickY: 0 });
23-
const [lastSelectedFile, setLastSelectedFile] = useState(null);
24-
25-
const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles } =
26-
useFileNavigation();
11+
const FileList = ({
12+
onCreateFolder,
13+
onRename,
14+
onFileOpen,
15+
onRefresh,
16+
enableFilePreview,
17+
triggerAction,
18+
}) => {
19+
const { currentPathFiles } = useFileNavigation();
2720
const filesViewRef = useRef(null);
28-
const { selectedFiles, setSelectedFiles, handleDownload } = useSelection();
29-
const { clipBoard, handleCutCopy, handlePasting } = useClipBoard();
3021
const { activeLayout } = useLayout();
31-
const contextMenuRef = useDetectOutsideClick(() => setVisible(false));
32-
33-
const emptySelecCtxItems = [
34-
{
35-
title: "Refresh",
36-
icon: <FiRefreshCw size={18} />,
37-
onClick: () => {},
38-
},
39-
{
40-
title: "New Folder",
41-
icon: <BsFolderPlus size={18} />,
42-
onClick: () => {},
43-
},
44-
{
45-
title: "Upload",
46-
icon: <MdOutlineFileUpload size={18} />,
47-
onClick: () => {},
48-
},
49-
];
50-
51-
const selecCtxItems = [
52-
{
53-
title: "Open",
54-
icon: lastSelectedFile?.isDirectory ? <PiFolderOpen size={20} /> : <FaRegFile size={16} />,
55-
onClick: handleFileOpen,
56-
},
57-
{
58-
title: "Cut",
59-
icon: <BsScissors size={19} />,
60-
onClick: () => handleMoveOrCopyItems(true),
61-
},
62-
{
63-
title: "Copy",
64-
icon: <BsCopy strokeWidth={0.1} size={17} />,
65-
onClick: () => handleMoveOrCopyItems(false),
66-
},
67-
{
68-
title: "Paste",
69-
icon: <FaRegPaste size={18} />,
70-
onClick: handleFilePasting,
71-
className: `${clipBoard ? "" : "disable-paste"}`,
72-
hidden: !lastSelectedFile?.isDirectory,
73-
},
74-
{
75-
title: "Rename",
76-
icon: <BiRename size={19} />,
77-
onClick: handleRenaming,
78-
hidden: selectedFiles.length > 1,
79-
},
80-
{
81-
title: "Download",
82-
icon: <MdOutlineFileDownload size={18} />,
83-
onClick: handleDownloadItems,
84-
hidden: lastSelectedFile?.isDirectory,
85-
},
86-
{
87-
title: "Delete",
88-
icon: <MdOutlineDelete size={19} />,
89-
onClick: handleDelete,
90-
},
91-
];
92-
93-
function handleFileOpen() {
94-
if (lastSelectedFile.isDirectory) {
95-
setCurrentPath(lastSelectedFile.path);
96-
setSelectedFileIndexes([]);
97-
setSelectedFiles([]);
98-
} else {
99-
enableFilePreview && triggerAction.show("previewFile");
100-
}
101-
setVisible(false);
102-
}
103-
104-
function handleMoveOrCopyItems(isMoving) {
105-
handleCutCopy(isMoving);
106-
setVisible(false);
107-
}
108-
109-
function handleFilePasting() {
110-
handlePasting(lastSelectedFile);
111-
setVisible(false);
112-
}
113-
114-
function handleRenaming() {
115-
setVisible(false);
116-
triggerAction.show("rename");
117-
}
118-
119-
function handleDownloadItems() {
120-
handleDownload();
121-
setVisible(false);
122-
}
123-
124-
function handleDelete() {
125-
setVisible(false);
126-
triggerAction.show("delete");
127-
}
12822

129-
const handleFolderCreating = () => {
130-
setCurrentPathFiles((prev) => {
131-
return [
132-
...prev,
133-
{
134-
name: duplicateNameHandler("New Folder", true, prev),
135-
isDirectory: true,
136-
path: currentPath,
137-
isEditing: true,
138-
key: new Date().valueOf(),
139-
},
140-
];
141-
});
142-
};
23+
const {
24+
emptySelecCtxItems,
25+
selecCtxItems,
26+
handleContextMenu,
27+
unselectFiles,
28+
visible,
29+
setVisible,
30+
setLastSelectedFile,
31+
selectedFileIndexes,
32+
clickPosition,
33+
isSelectionCtx,
34+
} = useFileList(onRefresh, enableFilePreview, triggerAction);
14335

144-
const handleItemRenaming = () => {
145-
setCurrentPathFiles((prev) => {
146-
if (prev[selectedFileIndexes.at(-1)]) {
147-
prev[selectedFileIndexes.at(-1)].isEditing = true;
148-
}
149-
return prev;
150-
});
151-
152-
setSelectedFileIndexes([]);
153-
setSelectedFiles([]);
154-
};
155-
156-
const handleContextMenu = (e, isSelection) => {
157-
e.preventDefault();
158-
setClickPosition({ clickX: e.clientX, clickY: e.clientY });
159-
setIsSelectionCtx(isSelection);
160-
setVisible(true);
161-
};
162-
163-
useEffect(() => {
164-
if (triggerAction.isActive) {
165-
switch (triggerAction.actionType) {
166-
case "createFolder":
167-
handleFolderCreating();
168-
break;
169-
case "rename":
170-
handleItemRenaming();
171-
break;
172-
}
173-
}
174-
}, [triggerAction.isActive]);
175-
176-
useEffect(() => {
177-
setSelectedFileIndexes([]);
178-
setSelectedFiles([]);
179-
}, [currentPath]);
180-
181-
useEffect(() => {
182-
if (selectedFiles.length > 0) {
183-
setSelectedFileIndexes(() => {
184-
return selectedFiles.map((selectedFile) => {
185-
return currentPathFiles.findIndex((f) => f.path === selectedFile.path);
186-
});
187-
});
188-
} else {
189-
setSelectedFileIndexes([]);
190-
}
191-
}, [selectedFiles, currentPathFiles]);
36+
const contextMenuRef = useDetectOutsideClick(() => setVisible(false));
19237

19338
return (
19439
<div
19540
ref={filesViewRef}
19641
className={`files ${activeLayout}`}
197-
onContextMenu={(e) => handleContextMenu(e, false)}
198-
onClick={() => {
199-
setSelectedFileIndexes([]);
200-
setSelectedFiles((prev) => (prev.length > 0 ? [] : prev));
201-
}}
42+
onContextMenu={handleContextMenu}
43+
onClick={unselectFiles}
20244
>
203-
{activeLayout === "list" && (
204-
<div className="files-header">
205-
<div className="file-name">Name</div>
206-
<div className="file-date">Modified</div>
207-
<div className="file-size">Size</div>
208-
</div>
209-
)}
45+
{activeLayout === "list" && <FilesHeader unselectFiles={unselectFiles} />}
46+
21047
{currentPathFiles?.length > 0 ? (
21148
<>
21249
{currentPathFiles.map((file, index) => (
@@ -218,9 +55,9 @@ const FileList = ({ onCreateFolder, onRename, onFileOpen, enableFilePreview, tri
21855
onRename={onRename}
21956
onFileOpen={onFileOpen}
22057
enableFilePreview={enableFilePreview}
58+
triggerAction={triggerAction}
22159
filesViewRef={filesViewRef}
22260
selectedFileIndexes={selectedFileIndexes}
223-
triggerAction={triggerAction}
22461
handleContextMenu={handleContextMenu}
22562
setVisible={setVisible}
22663
setLastSelectedFile={setLastSelectedFile}

frontend/src/FileManager/FileList/FileList.scss

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090

9191
.rename-file-container.list {
9292
top: 6px;
93-
left: 48px;
93+
left: 58px;
9494
text-align: left;
9595

9696
.rename-file {
@@ -101,7 +101,7 @@
101101
top: 5px;
102102
white-space: nowrap;
103103
overflow-x: hidden;
104-
max-width: calc(100% - 48px);
104+
max-width: calc(100% - 62px);
105105
}
106106

107107
.folder-name-error.right {
@@ -127,37 +127,6 @@
127127
.file-moving {
128128
opacity: 0.5;
129129
}
130-
131-
.file-context-menu-list {
132-
font-size: 1.1em;
133-
134-
ul {
135-
list-style-type: none;
136-
padding-left: 0;
137-
margin: 0;
138-
139-
li {
140-
display: flex;
141-
gap: 6px;
142-
align-items: center;
143-
padding: 6px 22px 6px 14px;
144-
145-
&:hover {
146-
cursor: pointer;
147-
background-color: rgb(0, 0, 0, 0.04);
148-
}
149-
}
150-
151-
li.disable-paste {
152-
opacity: 0.5;
153-
154-
&:hover {
155-
cursor: default;
156-
background-color: transparent;
157-
}
158-
}
159-
}
160-
}
161130
}
162131

163132
.files.list {
@@ -173,15 +142,20 @@
173142
display: flex;
174143
gap: 5px;
175144
border-bottom: 1px solid #dddddd;
176-
padding: 4px 0;
145+
padding: 4px 0 4px 5px;
177146
position: sticky;
178147
top: 0;
179148
background-color: #f5f5f5;
180149
z-index: 1;
181150

151+
.file-select-all {
152+
width: 5%;
153+
height: 0.83em;
154+
}
155+
182156
.file-name {
183-
width: calc(70% - 65px);
184-
padding-left: 65px;
157+
width: calc(65% - 35px);
158+
padding-left: 35px;
185159
}
186160

187161
.file-date {
@@ -199,6 +173,7 @@
199173
display: flex;
200174
width: 100%;
201175
margin: 0;
176+
border-radius: 0;
202177

203178
&:hover {
204179
background-color: rgb(0, 0, 0, 0.04);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useMemo, useState } from "react";
2+
import Checkbox from "../../components/Checkbox/Checkbox";
3+
import { useSelection } from "../../contexts/SelectionContext";
4+
import { useFileNavigation } from "../../contexts/FileNavigationContext";
5+
6+
const FilesHeader = ({ unselectFiles }) => {
7+
const [showSelectAll, setShowSelectAll] = useState(false);
8+
9+
const { selectedFiles, setSelectedFiles } = useSelection();
10+
const { currentPathFiles } = useFileNavigation();
11+
12+
const allFilesSelected = useMemo(() => {
13+
return selectedFiles.length === currentPathFiles.length;
14+
}, [selectedFiles, currentPathFiles]);
15+
16+
const handleSelectAll = (e) => {
17+
if (e.target.checked) {
18+
setSelectedFiles(currentPathFiles);
19+
setShowSelectAll(true);
20+
} else {
21+
unselectFiles();
22+
}
23+
};
24+
25+
return (
26+
<div
27+
className="files-header"
28+
onMouseOver={() => setShowSelectAll(true)}
29+
onMouseLeave={() => setShowSelectAll(false)}
30+
>
31+
<div className="file-select-all">
32+
{(showSelectAll || allFilesSelected) && (
33+
<Checkbox checked={allFilesSelected} onChange={handleSelectAll} title="Select all" />
34+
)}
35+
</div>
36+
<div className="file-name">Name</div>
37+
<div className="file-date">Modified</div>
38+
<div className="file-size">Size</div>
39+
</div>
40+
);
41+
};
42+
43+
export default FilesHeader;

0 commit comments

Comments
 (0)