started mover modal
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
];
|
||||
@@ -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 = ["/"];
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user