started mover modal

This commit is contained in:
subnub
2024-08-04 02:46:09 -04:00
parent 1757bed5af
commit c648b9bfc6
15 changed files with 424 additions and 3 deletions
+30
View File
@@ -254,6 +254,36 @@ class FolderController {
res.status(500).send("Server error renaming folder");
}
};
getMoveFolderList = async (req: RequestType, res: Response) => {
if (!req.user) {
return;
}
try {
const userID = req.user._id;
const parent = (req.query.parent as string) || undefined;
const search = (req.query.search as string) || undefined;
const folderID = (req.query.folderID as string) || undefined;
console.log("folderID", folderID);
const folderList = await folderService.getMoveFolderList(
userID,
parent,
search,
folderID
);
res.send(folderList);
} catch (e: unknown) {
if (e instanceof Error) {
console.log("\nGet Move Folder List Error Folder Route:", e.message);
}
res.status(500).send("Server error getting move folder list");
}
};
}
export default FolderController;
+30
View File
@@ -182,6 +182,36 @@ class DbUtil {
);
return result;
};
getMoveFolderList = async (
userID: string,
parent = "/",
search?: string,
folderID?: string
) => {
let query: any = {
owner: userID,
};
if (folderID) {
query.parentList = { $ne: folderID };
query._id = { $ne: folderID };
}
if (!search || search === "") {
query.parent = parent;
}
if (search && search !== "") {
query.name = new RegExp(search, "i");
}
console.log("query", query);
const result = await Folder.find(query);
return result;
};
}
export default DbUtil;
+8
View File
@@ -4,6 +4,7 @@ import env from "../enviroment/env";
import FileSystemService from "../services/ChunkService/FileSystemService";
import S3Service from "../services/ChunkService/S3Service";
import FolderController from "../controllers/folder";
import { moveFolderValidationRules } from "../middleware/Folders/FolderMiddleware";
let folderController: FolderController;
@@ -47,4 +48,11 @@ router.get(
folderController.getSubfolderFullList
);
router.get(
"/folder-service/move-folder-list",
auth,
moveFolderValidationRules,
folderController.getMoveFolderList
);
export default router;
@@ -0,0 +1,18 @@
import { query, validationResult } from "express-validator";
import { Request, Response, NextFunction } from "express";
export const moveFolderValidationRules = [
query("parent").optional().isString().withMessage("Parent must be a string"),
query("search").optional().isString().withMessage("Search must be a string"),
query("folderID")
.optional()
.isString()
.withMessage("FolderID must be a string"),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
];
+16
View File
@@ -229,6 +229,22 @@ class FolderService {
await folder.save();
};
getMoveFolderList = async (
userID: string,
parent?: string,
search?: string,
folderID?: string
) => {
const folderList = await utilsFolder.getMoveFolderList(
userID,
parent,
search,
folderID
);
return folderList;
};
moveFolder = async (userID: string, folderID: string, parentID: string) => {
let parentList = ["/"];
+1
View File
@@ -84,6 +84,7 @@
"dotenv": "^8.2.0",
"eslint-plugin-react-hooks": "^4.6.2",
"express": "^4.19.2",
"express-validator": "^7.1.0",
"fluent-ffmpeg": "^2.1.3",
"font-awesome": "^4.7.0",
"helmet": "^3.21.2",
+16
View File
@@ -36,6 +36,22 @@ export const getFolderInfoAPI = async ({
return response.data;
};
export const getMoveFolderListAPI = async ({
queryKey,
}: QueryFunctionContext<
[string, { parent: string; search: string; folderID?: string }]
>) => {
const [_key, { parent, search, folderID }] = queryKey;
const response = await axios.get(`/folder-service/move-folder-list`, {
params: {
parent,
search,
folderID,
},
});
return response.data;
};
// POST
export const createFolderAPI = async (name: string, parent?: string) => {
+6 -2
View File
@@ -22,6 +22,7 @@ import { useClickOutOfBounds, useUtils } from "../../hooks/utils";
import { useAppDispatch } from "../../hooks/store";
import {
resetSelected,
setMoveModal,
setMultiSelectMode,
setShareModal,
} from "../../reducers/selected";
@@ -240,9 +241,12 @@ const ContextMenu: React.FC<ContextMenuProps> = memo((props) => {
const openMoveItemModal = async () => {
closeContext();
if (!folderMode && file) {
dispatch(setMoverID(file._id, file.metadata.parent, true));
// dispatch(setMoverID(file._id, file.metadata.parent, true));
dispatch(setMoveModal({ type: "file", file, folder: null }));
} else if (folderMode && folder) {
dispatch(setMoverID(folder._id, folder.parent, false));
// dispatch(setMoverID(folder._id, folder.parent, false));
// }
dispatch(setMoveModal({ type: "folder", file: null, folder }));
}
};
+6
View File
@@ -8,6 +8,7 @@ import { useAppSelector } from "../../hooks/store";
import PhotoViewerPopup from "../PhotoViewerPopup";
import FileInfoPopup from "../FileInfoPopup";
import SharePopup from "../SharePopup";
import MoverPopup from "../MoverPopup/MoverPopup";
const MainSection = memo(() => {
const popupModalItem = useAppSelector(
@@ -16,6 +17,9 @@ const MainSection = memo(() => {
const shareModalItem = useAppSelector(
(state) => state.selected.shareModal.file
);
const moveModalItemType = useAppSelector(
(state) => state.selected.moveModal.type
);
const isMediaSelected =
popupModalItem?.metadata.isVideo || popupModalItem?.metadata.hasThumbnail;
@@ -33,6 +37,8 @@ const MainSection = memo(() => {
{shareModalItem && <SharePopup />}
{moveModalItemType && <MoverPopup />}
<div className="flex flex-row h-screen w-screen pt-16">
<LeftSection />
+173
View File
@@ -0,0 +1,173 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../hooks/store";
import { useMoveFolders } from "../../hooks/folders";
import { FolderInterface } from "../../types/folders";
import CloseIcon from "../../icons/CloseIcon";
import { resetMoveModal } from "../../reducers/selected";
import { debounce } from "lodash";
import Spinner from "../Spinner";
import HomeIconOutline from "../../icons/HomeIconOutline";
import ArrowBackIcon from "../../icons/ArrowBackIcon";
import classNames from "classnames";
import FolderIcon from "../../icons/FolderIcon";
const MoverPopup = () => {
const [search, setSearch] = useState("");
const [debouncedSearch, setDebouncedSearch] = useState("");
const [parent, setParent] = useState("/");
const [parentList, setParentList] = useState(["/"]);
const [selectedFolderID, setSelectedFolderID] = useState("");
const file = useAppSelector((state) => state.selected.moveModal.file);
const folder = useAppSelector((state) => state.selected.moveModal.folder);
const dispatch = useAppDispatch();
const lastSelected = useRef({
timestamp: 0,
folderID: "",
});
const { data: folderList, isLoading: isLoadingFolders } = useMoveFolders(
parent,
debouncedSearch,
folder?._id
);
const debouncedSetSearchText = useMemo(
() => debounce(setDebouncedSearch, 500),
[]
);
useEffect(() => {
debouncedSetSearchText(search);
return () => {
debouncedSetSearchText.cancel();
};
}, [search, debouncedSetSearchText]);
const onFolderClick = (folderID: string) => {
const currentDate = Date.now();
if (
lastSelected.current.folderID === folderID &&
currentDate - lastSelected.current.timestamp < 1500
) {
setSearch("");
setDebouncedSearch("");
setParentList([...parentList, folderID]);
setParent(folderID);
setSelectedFolderID("");
} else {
setSelectedFolderID(folderID);
}
lastSelected.current.timestamp = Date.now();
lastSelected.current.folderID = folderID;
};
const onBackClick = () => {
setSearch("");
setDebouncedSearch("");
const newParentList = parentList.slice(0, parentList.length - 1);
if (newParentList.length <= 1) {
setParent("/");
setParentList(["/"]);
} else {
setParentList(newParentList);
setParent(newParentList[newParentList.length - 1]);
}
};
const moveText = useMemo(() => {
if (selectedFolderID) {
return "Move to selected folder";
} else if (parent === "/") {
return "Move home";
} else {
return "Move here";
}
}, [selectedFolderID, parent]);
const onHomeClick = () => {
setSearch("");
setDebouncedSearch("");
setParent("/");
setParentList(["/"]);
setSelectedFolderID("");
};
return (
<div className="w-screen h-screen bg-black bg-opacity-80 absolute top-0 left-0 right-0 bottom-0 z-50 flex justify-center items-center flex-col">
<div className="absolute top-[20px] flex justify-between w-full">
<div>
<CloseIcon
className="w-[25px] h-[25px] cursor-pointer"
onClick={() => dispatch(resetMoveModal())}
/>
</div>
</div>
<div className="bg-white w-full max-w-[500px] p-4 rounded-md">
<div className="flex flex-row items-center">
<ArrowBackIcon
className="w-7 h-7 cursor-pointer mr-2"
onClick={onBackClick}
/>
<input
className="w-full py-2 px-3 text-black border border-gray-primary rounded-md text-sm outline-none"
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<HomeIconOutline
className="w-7 h-7 cursor-pointer ml-2"
onClick={onHomeClick}
/>
</div>
<div className="flex flex-col overflow-y-scroll h-[230px]">
{!isLoadingFolders && (
<React.Fragment>
{folderList?.map((folder: FolderInterface) => (
<div
className={classNames(
"p-2 border-b border-[#ebe9f9] rounded-md mt-1 flex flex-row items-center",
{
"bg-primary text-white hover:bg-primary-hover":
selectedFolderID === folder._id,
"hover:bg-white-hover": selectedFolderID !== folder._id,
}
)}
key={folder._id}
onClick={() => onFolderClick(folder._id)}
>
<FolderIcon
className={classNames("w-5 h-5 mr-2 select-none", {
"text-white": selectedFolderID === folder._id,
"text-primary": selectedFolderID !== folder._id,
})}
/>
<p className="max-w-[75%] text-ellipsis overflow-hidden select-none">
{folder.name}
</p>
</div>
))}
</React.Fragment>
)}
{isLoadingFolders && (
<div className="flex justify-center items-center h-full">
<Spinner />
</div>
)}
</div>
<div className="flex justify-end mt-4">
<button
className={classNames(
"bg-primary hover:bg-primary-hover text-white px-4 py-2 rounded-md"
)}
>
{moveText}
</button>
</div>
</div>
</div>
);
};
export default MoverPopup;
+25 -1
View File
@@ -1,6 +1,10 @@
import { UseQueryResult, useQuery, useQueryClient } from "react-query";
import { useParams } from "react-router-dom";
import { getFolderInfoAPI, getFoldersListAPI } from "../api/foldersAPI";
import {
getFolderInfoAPI,
getFoldersListAPI,
getMoveFolderListAPI,
} from "../api/foldersAPI";
import { useSelector } from "react-redux";
import { useUtils } from "./utils";
import { FolderInterface } from "../types/folders";
@@ -67,3 +71,23 @@ export const useFolder = () => {
return { ...folderQuery };
};
export const useMoveFolders = (
parent: string,
search: string,
folderID?: string
) => {
const moveFoldersQuery = useQuery(
[
"move-folder-list",
{
parent,
search,
folderID,
},
],
getMoveFolderListAPI
);
return { ...moveFoldersQuery };
};
+15
View File
@@ -0,0 +1,15 @@
type ArrowBackIconType = React.SVGAttributes<SVGSVGElement>;
const ArrowBackIcon: React.FC<ArrowBackIconType> = (props) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}>
<title>arrow-left</title>
<path
d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"
fill="currentColor"
/>
</svg>
);
};
export default ArrowBackIcon;
+14
View File
@@ -0,0 +1,14 @@
type FolderIconType = React.SVGAttributes<SVGSVGElement>;
const FolderIcon: React.FC<FolderIconType> = (props) => {
return (
<svg viewBox="0 0 512 512" {...props}>
<path
d="M464 128H272l-64-64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V176c0-26.51-21.49-48-48-48z"
fill="currentColor"
></path>
</svg>
);
};
export default FolderIcon;
+42
View File
@@ -0,0 +1,42 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { FileInterface } from "../types/file";
import { FolderInterface } from "../types/folders";
export type SelectedStateType = {
type: "" | "file" | "folder";
file: FileInterface | null;
folder: FolderInterface | null;
};
type MoverStateType = {
selectedItem: SelectedStateType;
};
const initialState: MoverStateType = {
selectedItem: {
type: "",
file: null,
folder: null,
},
};
const moveSlice = createSlice({
name: "selected",
initialState,
reducers: {
setMoverItem: (state, action: PayloadAction<SelectedStateType>) => {
state.selectedItem = action.payload;
},
resetMoverItem: (state) => {
state.selectedItem = {
type: "",
file: null,
folder: null,
};
},
},
});
export const {} = moveSlice.actions;
export default moveSlice.reducer;
+24
View File
@@ -9,6 +9,12 @@ interface MainSecionType {
folder: FolderInterface | null;
}
type MoveStateType = {
type: "" | "file" | "folder";
file: FileInterface | null;
folder: FolderInterface | null;
};
export interface SelectedStateType {
mainSection: MainSecionType;
popupModal: {
@@ -23,6 +29,7 @@ export interface SelectedStateType {
shareModal: {
file: FileInterface | null;
};
moveModal: MoveStateType;
}
const initialState: SelectedStateType = {
@@ -42,6 +49,11 @@ const initialState: SelectedStateType = {
shareModal: {
file: null,
},
moveModal: {
type: "",
file: null,
folder: null,
},
};
const selectedSlice = createSlice({
@@ -110,6 +122,16 @@ const selectedSlice = createSlice({
file: null,
};
},
setMoveModal: (state, action: PayloadAction<MoveStateType>) => {
state.moveModal = action.payload;
},
resetMoveModal: (state) => {
state.moveModal = {
type: "",
file: null,
folder: null,
};
},
},
});
@@ -122,6 +144,8 @@ export const {
resetPopupSelect,
setShareModal,
resetShareModal,
setMoveModal,
resetMoveModal,
} = selectedSlice.actions;
export default selectedSlice.reducer;