lots of fixes

This commit is contained in:
subnub
2025-01-10 09:55:34 -05:00
parent f3b45a532f
commit 00124cf542
12 changed files with 299 additions and 126 deletions
+22
View File
@@ -501,6 +501,28 @@ class FileController {
}
};
moveMultiFile = async (
req: RequestType,
res: Response,
next: NextFunction
) => {
if (!req.user) {
return;
}
try {
const userID = req.user._id;
const items = req.body.items;
const parentID = (req.body.parentID as string) || "/";
await fileService.moveMultiFiles(userID, items, parentID);
res.send();
} catch (e) {
next(e);
}
};
trashFile = async (req: RequestType, res: Response, next: NextFunction) => {
if (!req.user) {
return;
+8
View File
@@ -16,6 +16,7 @@ import {
makePrivateValidationRules,
makePublicValidationRules,
moveFileValidationRules,
moveMultiValidationRules,
removeVideoStreamTokenValidationRules,
renameFileValidationRules,
restoreFileValidationRules,
@@ -141,6 +142,13 @@ router.patch(
fileController.moveFile
);
router.patch(
"/file-service/move-multi",
auth,
moveMultiValidationRules,
fileController.moveMultiFile
);
router.patch(
"/file-service/trash",
auth,
@@ -101,6 +101,12 @@ export const moveFileValidationRules = [
middlewareValidationFunction,
];
export const moveMultiValidationRules = [
body("items").isArray().withMessage("Items must be an array"),
body("parentID").isString().optional().withMessage("Parent must be a string"),
middlewareValidationFunction,
];
export const trashFileValidationRules = [
body("id").isString().withMessage("ID must be a string"),
middlewareValidationFunction,
@@ -331,6 +331,34 @@ class MongoFileService {
return updatedFile;
};
moveMultiFiles = async (
userID: string,
items: {
type: "file" | "folder" | "quick-item";
id: string;
file?: FileInterface;
folder?: FolderInterface;
}[],
parentID: string
) => {
const fileList = items.filter(
(item) => item.type === "file" || item.type === "quick-item"
);
const folderList = items
.filter((item) => item.type === "folder")
.sort((a, b) => {
if (!a.folder || !b.folder) return 0;
return b.folder.parentList.length - a.folder.parentList.length;
});
for (const file of fileList) {
await this.moveFile(userID, file.id, parentID);
}
for (const folder of folderList) {
await folderService.moveFolder(userID, folder.id, parentID);
}
};
}
export default MongoFileService;
@@ -132,6 +132,16 @@ class FolderService {
startParentList.push("/");
}
await Promise.all([
folderDB.moveFolder(folderID, userID, parentID, startParentList),
fileDB.moveMultipleFiles(
userID,
folderID,
folderID,
[...startParentList, folderID].toString()
),
]);
for (let i = 0; i < foldersByIncludedParent.length; i++) {
const currentFolder = foldersByIncludedParent[i];
@@ -141,16 +151,22 @@ class FolderService {
newParentList.push(
...startParentList,
folderID,
...currentFolder.parentList.slice(currentParentIndex + 1)
);
await Promise.all([
folderDB.moveFolder(folderID, userID, parentID, newParentList),
folderDB.moveFolder(
currentFolder._id.toString(),
userID,
currentFolder.parent,
newParentList
),
fileDB.moveMultipleFiles(
userID,
currentFolder._id.toString(),
parentID,
newParentList.toString()
currentFolder._id.toString(),
[...newParentList, currentFolder._id.toString()].toString()
),
]);
}
+8
View File
@@ -190,6 +190,14 @@ export const moveFileAPI = async (fileID: string, parentID: string) => {
return response.data;
};
export const moveMultiAPI = async (items: any, parentID: string) => {
const response = await axios.patch(`/file-service/move-multi`, {
items,
parentID,
});
return response.data;
};
// DELETE
export const deleteFileAPI = async (fileID: string) => {
+86 -84
View File
@@ -288,91 +288,93 @@ const ContextMenu: React.FC<ContextMenuProps> = memo((props) => {
};
return (
<div
onClick={stopPropagation}
ref={wrapperRef}
className="fixed min-w-[215px] bg-white shadow-lg rounded-md z-50"
style={
fixedCoords.set
? {
display: "block",
left: `${fixedCoords.X}px`,
top: `${fixedCoords.Y}px`,
}
: { opacity: 0 }
}
>
<div>
<div
onClick={selectItemMultiSelect}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary rounded-t-md"
>
<MultiSelectIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Multi-select</p>
<div className="w-screen dynamic-height absolute top-0 left-0 right-0 bottom-0 z-50 flex justify-center items-center flex-col">
<div
onClick={stopPropagation}
ref={wrapperRef}
className="fixed min-w-[215px] bg-white shadow-lg rounded-md z-50"
style={
fixedCoords.set
? {
display: "block",
left: `${fixedCoords.X}px`,
top: `${fixedCoords.Y}px`,
}
: { opacity: 0 }
}
>
<div>
<div
onClick={selectItemMultiSelect}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary rounded-t-md"
>
<MultiSelectIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Multi-select</p>
</div>
{!isTrash && !isMedia && (
<div
onClick={renameItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<RenameIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Rename</p>
</div>
)}
{!folderMode && !isTrash && (
<div
onClick={openShareItemModal}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<ShareIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Share</p>
</div>
)}
{!isTrash && (
<div
onClick={downloadItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<DownloadIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Download</p>
</div>
)}
{!isTrash && !isMedia && (
<div
onClick={openMoveItemModal}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<MoveIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Move</p>
</div>
)}
{!isTrash && (
<div
onClick={trashItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary rounded-b-md"
>
<TrashIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Trash</p>
</div>
)}
{isTrash && (
<div
onClick={restoreItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<RestoreIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Restore</p>
</div>
)}
{isTrash && (
<div
onClick={deleteItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-red-500 rounded-b-md"
>
<TrashIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Delete</p>
</div>
)}
</div>
{!isTrash && !isMedia && (
<div
onClick={renameItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<RenameIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Rename</p>
</div>
)}
{!folderMode && !isTrash && (
<div
onClick={openShareItemModal}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<ShareIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Share</p>
</div>
)}
{!isTrash && (
<div
onClick={downloadItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<DownloadIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Download</p>
</div>
)}
{!isTrash && !isMedia && (
<div
onClick={openMoveItemModal}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<MoveIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Move</p>
</div>
)}
{!isTrash && (
<div
onClick={trashItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary rounded-b-md"
>
<TrashIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Trash</p>
</div>
)}
{isTrash && (
<div
onClick={restoreItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-primary"
>
<RestoreIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Restore</p>
</div>
)}
{isTrash && (
<div
onClick={deleteItem}
className="text-gray-primary flex flex-row p-4 hover:bg-white-hover hover:text-red-500 rounded-b-md"
>
<TrashIcon className="w-5 h-5" />
<p className="ml-2.5 text-sm">Delete</p>
</div>
)}
</div>
</div>
);
+3 -10
View File
@@ -2,7 +2,6 @@ import { useFiles } from "../../hooks/files";
import { useUtils } from "../../hooks/utils";
import React, { memo } from "react";
import FileItem from "../FileItem/FileItem";
import ParentBar from "../ParentBar/ParentBar";
import classNames from "classnames";
import { useAppDispatch, useAppSelector } from "../../hooks/store";
import { toggleListView } from "../../reducers/general";
@@ -22,15 +21,9 @@ const Files = memo(() => {
<div className="mt-8 select-none">
<div>
<div className="flex justify-between items-center mb-5">
{isHome && <h2 className="m-0 text-xl font-medium">Home Files</h2>}
{!isHome && (
<React.Fragment>
<div className="block">
<ParentBar />
</div>
</React.Fragment>
)}
<h2 className="m-0 text-xl font-medium">
{isHome ? "Home" : ""} Files
</h2>
<div>
<ul className="flex items-center list-none m-0 p-0">
<li className="mr-4" onClick={changeListViewMode}>
+15 -3
View File
@@ -1,15 +1,16 @@
import { useFolders } from "../../hooks/folders";
import FolderItem from "../FolderItem/FolderItem";
import { memo } from "react";
import React, { memo } from "react";
import classNames from "classnames";
import { useUtils } from "../../hooks/utils";
import { useAppDispatch, useAppSelector } from "../../hooks/store";
import { setSortBy } from "../../reducers/filter";
import ParentBar from "../ParentBar/ParentBar";
const Folders = memo(
({ scrollDivRef }: { scrollDivRef: React.RefObject<HTMLDivElement> }) => {
const { data: folders } = useFolders(false);
const { isTrash, isSearch } = useUtils();
const { isTrash, isSearch, isHome } = useUtils();
const sortBy = useAppSelector((state) => state.filter.sortBy);
const dispatch = useAppDispatch();
@@ -68,13 +69,24 @@ const Folders = memo(
return "Trash";
} else if (isSearch) {
return "Search";
} else if (folders?.length === 0) {
return "No Folders";
} else if (isHome) {
return "Home Folders";
} else {
return folders?.length === 0 ? "No Folders" : "Folders";
return "Folders";
}
})();
return (
<div className="mt-8 select-none">
{!isHome && (
<React.Fragment>
<div className="block mb-6">
<ParentBar />
</div>
</React.Fragment>
)}
<div className="flex flex-row mb-5 justify-between items-center">
<h2
className={classNames(
+13 -1
View File
@@ -11,7 +11,7 @@ import ArrowBackIcon from "../../icons/ArrowBackIcon";
import classNames from "classnames";
import FolderIcon from "../../icons/FolderIcon";
import { toast } from "react-toastify";
import { moveFileAPI } from "../../api/filesAPI";
import { moveFileAPI, moveMultiAPI } from "../../api/filesAPI";
import { useFiles } from "../../hooks/files";
import { moveFolderAPI } from "../../api/foldersAPI";
@@ -26,6 +26,9 @@ const MoverPopup = () => {
const multiSelectMode = useAppSelector(
(state) => state.selected.multiSelectMode
);
const multiSelectMap = useAppSelector(
(state) => state.selected.multiSelectMap
);
const [isLoadingMove, setIsLoadingMove] = useState(false);
const file = useAppSelector((state) => state.selected.moveModal.file);
const folder = useAppSelector((state) => state.selected.moveModal.folder);
@@ -131,6 +134,15 @@ const MoverPopup = () => {
: parent?._id || "/";
try {
if (multiSelectMode) {
const itemsToMove = Object.values(multiSelectMap);
await toast.promise(moveMultiAPI(itemsToMove, moveTo), {
pending: "Moving items...",
success: "Items Moved",
error: "Error Moving Items",
});
refetchFiles();
refetchFolders();
dispatch(resetMoveModal());
} else if (file) {
await toast.promise(moveFileAPI(file._id, moveTo), {
pending: "Moving File...",
+18 -10
View File
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
import { useUtils } from "../../hooks/utils";
import { useFolder } from "../../hooks/folders";
import SpacerIcon from "../../icons/SpacerIcon";
import ArrowBackIcon from "../../icons/ArrowBackIcon";
const ParentBar = memo(() => {
const { data: folder } = useFolder(false);
@@ -25,26 +26,33 @@ const ParentBar = memo(() => {
navigate(`/folder/${folder?._id}`);
};
// TODO: Decide how to handle loading
// if (!isLoading) {
// return <div className="flex">
// </div>
// }
const goBackAFolder = () => {
if (folder?.parent === "/") {
navigate("/home");
} else {
navigate(`/folder/${folder.parent}`);
}
};
return (
<div className="w-full items-center flex">
<div className="w-full items-center flex border border-gray-third rounded-md">
<div className="flex items-center">
<div className="flex items-center justify-center h-full border-r p-2 mr-2 hover:bg-gray-third">
<ArrowBackIcon
className="w-5 h-5 cursor-pointer"
onClick={goBackAFolder}
/>
</div>
<a
className="text-[#637381] text-[18px] leading-[21px] font-medium m-0 no-underline animate cursor-pointer"
className="text-[#637381] text-md leading-[21px] font-medium m-0 no-underline animate cursor-pointer rounded-md p-1 hover:bg-gray-third"
onClick={goHomeOrTrash}
>
{!isTrash ? "Home" : "Trash"}
</a>
<SpacerIcon className="text-black mx-2" />
<SpacerIcon className="text-black mx-2 w-2.5 h-2.5" />
<p
onClick={goToFolder}
className="text-[#212b36] text-[18px] leading-[21px] font-medium m-0 whitespace-nowrap max-w-[170px] sm:max-w-[300px] overflow-hidden text-ellipsis cursor-pointer"
className="text-primary text-md leading-[21px] font-medium m-0 whitespace-nowrap max-w-[170px] sm:max-w-[300px] overflow-hidden text-ellipsis cursor-pointer rounded-md p-1 hover:bg-gray-third "
>
{folder.name}
</p>
@@ -5,7 +5,11 @@ import CloseIcon from "../../icons/CloseIcon";
import ActionsIcon from "../../icons/ActionsIcon";
import { useContextMenu } from "../../hooks/contextMenu";
import ContextMenu from "../ContextMenu/ContextMenu";
import { resetPopupSelect, setPopupSelect } from "../../reducers/selected";
import {
resetPopupSelect,
setMainSelect,
setPopupSelect,
} from "../../reducers/selected";
import CircleLeftIcon from "../../icons/CircleLeftIcon";
import CircleRightIcon from "../../icons/CircleRightIcon";
import { useFiles, useQuickFiles } from "../../hooks/files";
@@ -122,13 +126,28 @@ const PhotoViewerPopup: React.FC<PhotoViewerPopupProps> = memo((props) => {
const prevItem = filteredQuickFiles[index - 1];
if (prevItem) {
dispatch(setPopupSelect({ type: "quick-item", file: prevItem }));
dispatch(
setMainSelect({
file: prevItem,
id: prevItem._id,
type: "file",
folder: null,
})
);
}
} else {
if (!files?.pages) return 0;
const prevItem = findPrevFilesItem();
console.log("prev item", prevItem);
if (prevItem) {
dispatch(setPopupSelect({ type: "file", file: prevItem }));
dispatch(
setMainSelect({
file: prevItem,
id: prevItem._id,
type: "file",
folder: null,
})
);
}
// TODO: Perhaps implement this if needed in the future
// else {
@@ -186,23 +205,51 @@ const PhotoViewerPopup: React.FC<PhotoViewerPopupProps> = memo((props) => {
const nextItem = filteredQuickFiles[index + 1];
if (nextItem) {
dispatch(setPopupSelect({ type: "quick-item", file: nextItem }));
dispatch(
setMainSelect({
file: nextItem,
id: nextItem._id,
type: "file",
folder: null,
})
);
}
} else {
if (!files?.pages) return;
const nextItem = findNextFilesItem();
if (nextItem) {
dispatch(setPopupSelect({ type: "file", file: nextItem }));
dispatch(
setMainSelect({
file: nextItem,
id: nextItem._id,
type: "file",
folder: null,
})
);
} else if (!finalLastPageLoaded.current && !loadingNextPage.current) {
loadingNextPage.current = true;
console.log("fetch next page");
const newFilesResponse = await fetchNextPage();
console.log("new files response", newFilesResponse);
if (!newFilesResponse.data?.pages) return;
const fetchedNextItem = findNextFilesItem(newFilesResponse.data);
console.log("fetched next item", fetchedNextItem);
if (fetchedNextItem) {
dispatch(setPopupSelect({ type: "file", file: fetchedNextItem }));
dispatch(
setMainSelect({
file: fetchedNextItem,
id: fetchedNextItem._id,
type: "file",
folder: null,
})
);
} else {
finalLastPageLoaded.current = true;
}
loadingNextPage.current = false;
console.log("end");
}
}
};
@@ -211,7 +258,16 @@ const PhotoViewerPopup: React.FC<PhotoViewerPopupProps> = memo((props) => {
dispatch(resetPopupSelect());
};
const outterWrapperClick = (e: React.MouseEvent<HTMLDivElement>) => {
console.log("outter wrapper click", e.target);
if ((e.target as HTMLDivElement).id !== "outer-wrapper") {
return;
}
closePhotoViewer();
};
useEffect(() => {
console.log("useeffect");
if (file.metadata.isVideo) {
getVideo();
}
@@ -222,7 +278,11 @@ const PhotoViewerPopup: React.FC<PhotoViewerPopupProps> = memo((props) => {
}, [file.metadata.isVideo, getVideo, cleanUpVideo]);
return (
<div className="w-screen dynamic-height bg-black bg-opacity-80 absolute top-0 left-0 right-0 bottom-0 z-50 flex justify-center items-center flex-col">
<div
id="outer-wrapper"
className="w-screen dynamic-height bg-black bg-opacity-80 absolute top-0 left-0 right-0 bottom-0 z-50 flex justify-center items-center flex-col"
onClick={outterWrapperClick}
>
{contextMenuState.selected && (
<div onClick={clickStopPropagation}>
<ContextMenu
@@ -244,12 +304,12 @@ const PhotoViewerPopup: React.FC<PhotoViewerPopupProps> = memo((props) => {
className="h-[27px] w-[27px] bg-red-500 rounded-[3px] flex flex-row justify-center items-center"
style={{ background: imageColor }}
>
<span className="font-semibold text-[9.5px] text-white">
<span className="font-semibold text-[9.5px] text-white select-none">
{fileExtension}
</span>
</div>
</span>
<p className="text-md text-white text-ellipsis overflow-hidden max-w-[200px] md:max-w-[600px] whitespace-nowrap">
<p className="text-md text-white text-ellipsis overflow-hidden max-w-[200px] md:max-w-[600px] whitespace-nowrap select-none">
{file.filename}
</p>
</div>
@@ -269,16 +329,14 @@ const PhotoViewerPopup: React.FC<PhotoViewerPopupProps> = memo((props) => {
</div>
</div>
</div>
<div className="flex absolute pb-[70px] desktopMode:pb-0 top-[50px] bottom-0 w-full h-full justify-between items-end desktopMode:items-center p-4">
<CircleLeftIcon
onClick={goToPreviousItem}
className="pointer text-white w-[45px] h-[45px] desktopMode:w-[30px] desktopMode:h-[30px] select-none cursor-pointer hover:text-white-hover"
/>
<CircleRightIcon
onClick={goToNextItem}
className="pointer text-white w-[45px] h-[45px] desktopMode:w-[30px] desktopMode:h-[30px] select-none cursor-pointer hover:text-white-hover"
/>
</div>
<CircleLeftIcon
onClick={goToPreviousItem}
className="fixed left-2 pointer text-white w-[45px] h-[45px] desktopMode:w-[30px] desktopMode:h-[30px] select-none cursor-pointer hover:text-white-hover"
/>
<CircleRightIcon
onClick={goToNextItem}
className="fixed right-2 pointer text-white w-[45px] h-[45px] desktopMode:w-[30px] desktopMode:h-[30px] select-none cursor-pointer hover:text-white-hover"
/>
<div className="max-w-[80vw] max-h-[80vh] flex justify-center items-center z-10">
{isThumbnailLoading && !thumbnailError && <Spinner />}
{!file.metadata.isVideo && (