Skip to content

Commit

Permalink
Merge pull request #111 from Saifullah-dev/feature/context-menu
Browse files Browse the repository at this point in the history
109: [FEAT] Context Menu - Make Global Context Menu Functional
  • Loading branch information
Saifullah-dev authored Nov 21, 2024
2 parents b756620 + a14284a commit 4829aaf
Show file tree
Hide file tree
Showing 10 changed files with 481 additions and 248 deletions.
223 changes: 30 additions & 193 deletions frontend/src/FileManager/FileList/FileList.jsx
Original file line number Diff line number Diff line change
@@ -1,212 +1,49 @@
import { useEffect, useRef, useState } from "react";
import { useRef } from "react";
import FileItem from "./FileItem";
import { duplicateNameHandler } from "../../utils/duplicateNameHandler";
import { useFileNavigation } from "../../contexts/FileNavigationContext";
import { useSelection } from "../../contexts/SelectionContext";
import { useLayout } from "../../contexts/LayoutContext";
import ContextMenu from "../../components/ContextMenu/ContextMenu";
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
import { BsCopy, BsFolderPlus, BsScissors } from "react-icons/bs";
import { MdOutlineDelete, MdOutlineFileDownload, MdOutlineFileUpload } from "react-icons/md";
import { FiRefreshCw } from "react-icons/fi";
import useFileList from "./useFileList";
import FilesHeader from "./FilesHeader";
import "./FileList.scss";
import { PiFolderOpen } from "react-icons/pi";
import { FaRegFile, FaRegPaste } from "react-icons/fa6";
import { BiRename } from "react-icons/bi";
import { useClipBoard } from "../../contexts/ClipboardContext";

const FileList = ({ onCreateFolder, onRename, onFileOpen, enableFilePreview, triggerAction }) => {
const [selectedFileIndexes, setSelectedFileIndexes] = useState([]);
const [visible, setVisible] = useState(false);
const [isSelectionCtx, setIsSelectionCtx] = useState(false);
const [clickPosition, setClickPosition] = useState({ clickX: 0, clickY: 0 });
const [lastSelectedFile, setLastSelectedFile] = useState(null);

const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles } =
useFileNavigation();
const FileList = ({
onCreateFolder,
onRename,
onFileOpen,
onRefresh,
enableFilePreview,
triggerAction,
}) => {
const { currentPathFiles } = useFileNavigation();
const filesViewRef = useRef(null);
const { selectedFiles, setSelectedFiles, handleDownload } = useSelection();
const { clipBoard, handleCutCopy, handlePasting } = useClipBoard();
const { activeLayout } = useLayout();
const contextMenuRef = useDetectOutsideClick(() => setVisible(false));

const emptySelecCtxItems = [
{
title: "Refresh",
icon: <FiRefreshCw size={18} />,
onClick: () => {},
},
{
title: "New Folder",
icon: <BsFolderPlus size={18} />,
onClick: () => {},
},
{
title: "Upload",
icon: <MdOutlineFileUpload size={18} />,
onClick: () => {},
},
];

const selecCtxItems = [
{
title: "Open",
icon: lastSelectedFile?.isDirectory ? <PiFolderOpen size={20} /> : <FaRegFile size={16} />,
onClick: handleFileOpen,
},
{
title: "Cut",
icon: <BsScissors size={19} />,
onClick: () => handleMoveOrCopyItems(true),
},
{
title: "Copy",
icon: <BsCopy strokeWidth={0.1} size={17} />,
onClick: () => handleMoveOrCopyItems(false),
},
{
title: "Paste",
icon: <FaRegPaste size={18} />,
onClick: handleFilePasting,
className: `${clipBoard ? "" : "disable-paste"}`,
hidden: !lastSelectedFile?.isDirectory,
},
{
title: "Rename",
icon: <BiRename size={19} />,
onClick: handleRenaming,
hidden: selectedFiles.length > 1,
},
{
title: "Download",
icon: <MdOutlineFileDownload size={18} />,
onClick: handleDownloadItems,
hidden: lastSelectedFile?.isDirectory,
},
{
title: "Delete",
icon: <MdOutlineDelete size={19} />,
onClick: handleDelete,
},
];

function handleFileOpen() {
if (lastSelectedFile.isDirectory) {
setCurrentPath(lastSelectedFile.path);
setSelectedFileIndexes([]);
setSelectedFiles([]);
} else {
enableFilePreview && triggerAction.show("previewFile");
}
setVisible(false);
}

function handleMoveOrCopyItems(isMoving) {
handleCutCopy(isMoving);
setVisible(false);
}

function handleFilePasting() {
handlePasting(lastSelectedFile);
setVisible(false);
}

function handleRenaming() {
setVisible(false);
triggerAction.show("rename");
}

function handleDownloadItems() {
handleDownload();
setVisible(false);
}

function handleDelete() {
setVisible(false);
triggerAction.show("delete");
}

const handleFolderCreating = () => {
setCurrentPathFiles((prev) => {
return [
...prev,
{
name: duplicateNameHandler("New Folder", true, prev),
isDirectory: true,
path: currentPath,
isEditing: true,
key: new Date().valueOf(),
},
];
});
};
const {
emptySelecCtxItems,
selecCtxItems,
handleContextMenu,
unselectFiles,
visible,
setVisible,
setLastSelectedFile,
selectedFileIndexes,
clickPosition,
isSelectionCtx,
} = useFileList(onRefresh, enableFilePreview, triggerAction);

const handleItemRenaming = () => {
setCurrentPathFiles((prev) => {
if (prev[selectedFileIndexes.at(-1)]) {
prev[selectedFileIndexes.at(-1)].isEditing = true;
}
return prev;
});

setSelectedFileIndexes([]);
setSelectedFiles([]);
};

const handleContextMenu = (e, isSelection) => {
e.preventDefault();
setClickPosition({ clickX: e.clientX, clickY: e.clientY });
setIsSelectionCtx(isSelection);
setVisible(true);
};

useEffect(() => {
if (triggerAction.isActive) {
switch (triggerAction.actionType) {
case "createFolder":
handleFolderCreating();
break;
case "rename":
handleItemRenaming();
break;
}
}
}, [triggerAction.isActive]);

useEffect(() => {
setSelectedFileIndexes([]);
setSelectedFiles([]);
}, [currentPath]);

useEffect(() => {
if (selectedFiles.length > 0) {
setSelectedFileIndexes(() => {
return selectedFiles.map((selectedFile) => {
return currentPathFiles.findIndex((f) => f.path === selectedFile.path);
});
});
} else {
setSelectedFileIndexes([]);
}
}, [selectedFiles, currentPathFiles]);
const contextMenuRef = useDetectOutsideClick(() => setVisible(false));

return (
<div
ref={filesViewRef}
className={`files ${activeLayout}`}
onContextMenu={(e) => handleContextMenu(e, false)}
onClick={() => {
setSelectedFileIndexes([]);
setSelectedFiles((prev) => (prev.length > 0 ? [] : prev));
}}
onContextMenu={handleContextMenu}
onClick={unselectFiles}
>
{activeLayout === "list" && (
<div className="files-header">
<div className="file-name">Name</div>
<div className="file-date">Modified</div>
<div className="file-size">Size</div>
</div>
)}
{activeLayout === "list" && <FilesHeader unselectFiles={unselectFiles} />}

{currentPathFiles?.length > 0 ? (
<>
{currentPathFiles.map((file, index) => (
Expand All @@ -218,9 +55,9 @@ const FileList = ({ onCreateFolder, onRename, onFileOpen, enableFilePreview, tri
onRename={onRename}
onFileOpen={onFileOpen}
enableFilePreview={enableFilePreview}
triggerAction={triggerAction}
filesViewRef={filesViewRef}
selectedFileIndexes={selectedFileIndexes}
triggerAction={triggerAction}
handleContextMenu={handleContextMenu}
setVisible={setVisible}
setLastSelectedFile={setLastSelectedFile}
Expand Down
47 changes: 11 additions & 36 deletions frontend/src/FileManager/FileList/FileList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@

.rename-file-container.list {
top: 6px;
left: 48px;
left: 58px;
text-align: left;

.rename-file {
Expand All @@ -101,7 +101,7 @@
top: 5px;
white-space: nowrap;
overflow-x: hidden;
max-width: calc(100% - 48px);
max-width: calc(100% - 62px);
}

.folder-name-error.right {
Expand All @@ -127,37 +127,6 @@
.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 {
Expand All @@ -173,15 +142,20 @@
display: flex;
gap: 5px;
border-bottom: 1px solid #dddddd;
padding: 4px 0;
padding: 4px 0 4px 5px;
position: sticky;
top: 0;
background-color: #f5f5f5;
z-index: 1;

.file-select-all {
width: 5%;
height: 0.83em;
}

.file-name {
width: calc(70% - 65px);
padding-left: 65px;
width: calc(65% - 35px);
padding-left: 35px;
}

.file-date {
Expand All @@ -199,6 +173,7 @@
display: flex;
width: 100%;
margin: 0;
border-radius: 0;

&:hover {
background-color: rgb(0, 0, 0, 0.04);
Expand Down
43 changes: 43 additions & 0 deletions frontend/src/FileManager/FileList/FilesHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMemo, useState } from "react";
import Checkbox from "../../components/Checkbox/Checkbox";
import { useSelection } from "../../contexts/SelectionContext";
import { useFileNavigation } from "../../contexts/FileNavigationContext";

const FilesHeader = ({ unselectFiles }) => {
const [showSelectAll, setShowSelectAll] = useState(false);

const { selectedFiles, setSelectedFiles } = useSelection();
const { currentPathFiles } = useFileNavigation();

const allFilesSelected = useMemo(() => {
return selectedFiles.length === currentPathFiles.length;
}, [selectedFiles, currentPathFiles]);

const handleSelectAll = (e) => {
if (e.target.checked) {
setSelectedFiles(currentPathFiles);
setShowSelectAll(true);
} else {
unselectFiles();
}
};

return (
<div
className="files-header"
onMouseOver={() => setShowSelectAll(true)}
onMouseLeave={() => setShowSelectAll(false)}
>
<div className="file-select-all">
{(showSelectAll || allFilesSelected) && (
<Checkbox checked={allFilesSelected} onChange={handleSelectAll} title="Select all" />
)}
</div>
<div className="file-name">Name</div>
<div className="file-date">Modified</div>
<div className="file-size">Size</div>
</div>
);
};

export default FilesHeader;
Loading

0 comments on commit 4829aaf

Please sign in to comment.