diff --git a/backend/controllers/file.ts b/backend/controllers/file.ts index d518645..a87a219 100644 --- a/backend/controllers/file.ts +++ b/backend/controllers/file.ts @@ -401,10 +401,12 @@ class FileController { try { const userID = req.user._id; let searchQuery = req.query.search || ""; + const trashMode = req.query.trashMode === "true"; const { fileList, folderList } = await fileService.getSuggestedList( userID, - searchQuery + searchQuery, + trashMode ); return res.send({ folderList, fileList }); diff --git a/backend/db/utils/fileUtils/index.ts b/backend/db/utils/fileUtils/index.ts index 683907c..0c2413b 100644 --- a/backend/db/utils/fileUtils/index.ts +++ b/backend/db/utils/fileUtils/index.ts @@ -133,10 +133,15 @@ class DbUtil { return user; }; - getFileSearchList = async (userID: string, searchQuery: RegExp) => { + getFileSearchList = async ( + userID: string, + searchQuery: RegExp, + trashMode: boolean + ) => { let query: any = { "metadata.owner": userID, filename: searchQuery, + "metadata.trashed": trashMode ? true : null, }; const fileList = (await conn.db diff --git a/backend/db/utils/folderUtils/index.ts b/backend/db/utils/folderUtils/index.ts index 8ae52ef..73c5674 100644 --- a/backend/db/utils/folderUtils/index.ts +++ b/backend/db/utils/folderUtils/index.ts @@ -4,8 +4,16 @@ import { ObjectId } from "mongodb"; class DbUtil { constructor() {} - getFolderSearchList = async (userID: string, searchQuery: RegExp) => { - let query: any = { owner: userID, name: searchQuery }; + getFolderSearchList = async ( + userID: string, + searchQuery: RegExp, + trashMode: boolean + ) => { + let query: any = { + owner: userID, + name: searchQuery, + trashed: trashMode ? true : null, + }; const folderList = (await Folder.find(query).limit( 10 @@ -75,7 +83,8 @@ class DbUtil { storageType: string, folderSearch: boolean, itemType: string, - s3Enabled: boolean + s3Enabled: boolean, + trashMode: boolean ) => { let query: any = { name: searchQuery, owner: userID }; @@ -105,6 +114,12 @@ class DbUtil { query = { ...query, personalFolder: null }; } + if (trashMode) { + query = { ...query, trashed: true }; + } else { + query = { ...query, trashed: null }; + } + const folderList = (await Folder.find(query).sort( sortBy )) as FolderInterface[]; diff --git a/backend/services/FileService/index.ts b/backend/services/FileService/index.ts index a515243..2030001 100644 --- a/backend/services/FileService/index.ts +++ b/backend/services/FileService/index.ts @@ -203,13 +203,22 @@ class MongoFileService { await removedTokenUser.save(); }; - getSuggestedList = async (userID: string, searchQuery: any) => { + getSuggestedList = async ( + userID: string, + searchQuery: any, + trashMode: boolean + ) => { searchQuery = new RegExp(searchQuery, "i"); - const fileList = await dbUtilsFile.getFileSearchList(userID, searchQuery); + const fileList = await dbUtilsFile.getFileSearchList( + userID, + searchQuery, + trashMode + ); const folderList = await dbUtilsFolder.getFolderSearchList( userID, - searchQuery + searchQuery, + trashMode ); if (!fileList || !folderList) diff --git a/backend/services/FolderService/index.ts b/backend/services/FolderService/index.ts index f7b851f..436a8bc 100644 --- a/backend/services/FolderService/index.ts +++ b/backend/services/FolderService/index.ts @@ -153,7 +153,8 @@ class FolderService { storageType, folderSearch, itemType, - s3Enabled + s3Enabled, + trashMode ); if (!folderList) throw new NotFoundError("Folder List Not Found Error"); diff --git a/public/assets/homea.svg b/public/assets/homea.svg index 53cf3b1..99793cd 100644 --- a/public/assets/homea.svg +++ b/public/assets/homea.svg @@ -1,10 +1,10 @@ - - - - - - + + + + + + diff --git a/src/api/filesAPI.ts b/src/api/filesAPI.ts index 86c2c0d..6259e59 100644 --- a/src/api/filesAPI.ts +++ b/src/api/filesAPI.ts @@ -84,11 +84,14 @@ export const getFileThumbnailAPI = async (thumbnailID: string) => { export const getSuggestedListAPI = async ({ queryKey, -}: QueryFunctionContext<[string, { searchText: string }]>) => { - const [_key, { searchText }] = queryKey; +}: QueryFunctionContext< + [string, { searchText: string; trashMode: boolean }] +>) => { + const [_key, { searchText, trashMode }] = queryKey; const response = await axios.get(`/file-service/suggested-list`, { params: { search: searchText, + trashMode, }, }); return response.data; diff --git a/src/components/ContextMenu/index.jsx b/src/components/ContextMenu/index.jsx index b7e4f6c..cce1d60 100644 --- a/src/components/ContextMenu/index.jsx +++ b/src/components/ContextMenu/index.jsx @@ -17,15 +17,23 @@ import { renameFolder, trashFolderAPI, } from "../../api/foldersAPI"; -import { useClickOutOfBounds } from "../../hooks/utils"; +import { useClickOutOfBounds, useUtils } from "../../hooks/utils"; import { useAppDispatch } from "../../hooks/store"; import { setMultiSelectMode } from "../../reducers/selected"; +import TrashIcon from "../../icons/TrashIcon"; +import MultiSelectIcon from "../../icons/MultiSelectIcon"; +import RenameIcon from "../../icons/RenameIcon"; +import ShareIcon from "../../icons/ShareIcon"; +import DownloadIcon from "../../icons/DownloadIcon"; +import MoveIcon from "../../icons/MoveIcon"; +import RestoreIcon from "../../icons/RestoreIcon"; const ContextMenu = (props) => { const { invalidateFilesCache } = useFilesClient(); const { invalidateFoldersCache } = useFoldersClient(); const { invalidateQuickFilesCache } = useQuickFilesClient(); const { wrapperRef } = useClickOutOfBounds(props.closeContext); + const { isTrash } = useUtils(); const dispatch = useAppDispatch(); const liClassname = "flex w-full px-[20px] py-[12px] items-center font-normal text-[#637381] justify-start no-underline transition-all duration-400 ease-in-out text- hover:bg-[#f6f5fd] hover:text-[#3c85ee] hover:font-medium"; @@ -70,7 +78,7 @@ const ContextMenu = (props) => { } }; - const deleteItem = async () => { + const trashItem = async () => { props.closeContext(); if (!props.folderMode) { const result = await Swal.fire({ @@ -105,6 +113,41 @@ const ContextMenu = (props) => { } }; + const deleteItem = async () => { + props.closeContext(); + if (!props.folderMode) { + const result = await Swal.fire({ + title: "Delete file?", + text: "You will not be able to recover this file.", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes", + }); + + if (result.value) { + await deleteFileAPI(props.file._id); + invalidateFilesCache(); + invalidateQuickFilesCache(); + } + } else { + const result = await Swal.fire({ + title: "Delete folder?", + text: "You will not be able to recover this folder.", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes", + }); + if (result.value) { + await deleteFolder(props.folder._id); + invalidateFoldersCache(); + } + } + }; + const openMoveItemModal = async () => { props.closeContext(); if (!props.folderMode) { @@ -169,62 +212,84 @@ const ContextMenu = (props) => {
  • - multiselect + Multi select
  • -
  • - - - setting - - Rename - -
  • - {!props.folderMode ? ( + {!isTrash && ( +
  • + + + + + Rename + +
  • + )} + {!props.folderMode && !isTrash ? (
  • - - setting + + Share
  • ) : undefined} - {!props.folderMode ? ( + {!props.folderMode && !isTrash ? (
  • - setting + Download
  • ) : undefined} -
  • - - - setting - {" "} - Move - -
  • -
  • - - - setting - - Trash - -
  • + {!isTrash && ( +
  • + + + + {" "} + Move + +
  • + )} + {!isTrash && ( +
  • + + + + + Trash + +
  • + )} + {isTrash && ( +
  • + + + + + Restore + +
  • + )} + {isTrash && ( +
  • + + + + + Delete + +
  • + )} ); diff --git a/src/components/FolderItem/index.jsx b/src/components/FolderItem/index.jsx index 3971256..612a6fb 100644 --- a/src/components/FolderItem/index.jsx +++ b/src/components/FolderItem/index.jsx @@ -9,6 +9,7 @@ import mobilecheck from "../../utils/mobileCheck"; import moment from "moment"; import { useAppDispatch, useAppSelector } from "../../hooks/store"; import { setMainSelect, setMultiSelectMode } from "../../reducers/selected"; +import { useUtils } from "../../hooks/utils"; const FolderItem = React.memo((props) => { const { folder } = props; @@ -23,6 +24,7 @@ const FolderItem = React.memo((props) => { const multiSelectMode = useAppSelector( (state) => state.selected.multiSelectMode ); + const { isTrash } = useUtils(); const lastSelected = useRef(0); const navigate = useNavigate(); const dispatch = useAppDispatch(); @@ -66,7 +68,11 @@ const FolderItem = React.memo((props) => { const isMobile = mobilecheck(); if (isMobile || currentDate - lastSelected.current < 1500) { - navigate(`/folder/${folder._id}`); + if (isTrash) { + navigate(`/folder-trash/${folder._id}`); + } else { + navigate(`/folder/${folder._id}`); + } } lastSelected.current = Date.now(); diff --git a/src/components/LeftSection/LeftSection.jsx b/src/components/LeftSection/LeftSection.jsx index e005ef9..9ec65c5 100644 --- a/src/components/LeftSection/LeftSection.jsx +++ b/src/components/LeftSection/LeftSection.jsx @@ -1,13 +1,18 @@ import React, { useCallback, useRef, useState } from "react"; -import { useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { createFolderAPI } from "../../api/foldersAPI"; import { useFoldersClient } from "../../hooks/folders"; import { showCreateFolderPopup } from "../../popups/folder"; -import { useClickOutOfBounds } from "../../hooks/utils"; +import { useClickOutOfBounds, useUtils } from "../../hooks/utils"; import AddNewDropdown from "../AddNewDropdown"; +import HomeListIcon from "../../icons/HomeListIcon"; +import TrashIcon from "../../icons/TrashIcon"; +import classNames from "classnames"; const LeftSection = (props) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const { isHome, isTrash } = useUtils(); + const navigate = useNavigate(); const addNewDisabled = useRef(false); const openDropdown = useCallback(() => { @@ -22,6 +27,14 @@ const LeftSection = (props) => { setTimeout(() => (addNewDisabled.current = false), 300); }, []); + const goHome = () => { + navigate("/home"); + }; + + const goTrash = () => { + navigate("/trash"); + }; + return (
    { : { left: "-290px" } } > -
    -
    - -

    ADD NEW

    - - dropselect - -
    - {/* TODO: Remove this props */} - {isDropdownOpen && ( - - )} +
    +
    +
    + +

    ADD NEW

    + + dropselect + +
    + {/* TODO: Remove this props */} + {isDropdownOpen && ( + + )} +
    +
    + +
    -
    ); diff --git a/src/components/MultiSelectBar/index.tsx b/src/components/MultiSelectBar/index.tsx index a95256d..315736a 100644 --- a/src/components/MultiSelectBar/index.tsx +++ b/src/components/MultiSelectBar/index.tsx @@ -49,68 +49,70 @@ const MultiSelectBar = () => { if (!multiSelectMode) return
    ; return ( -
    -
    -
    - -

    {multiSelectCount} selected

    -
    +
    +
    +
    +
    + +

    {multiSelectCount} selected

    +
    -
    - - +
    + + + + + + + + + - - - - - - - - + +
    diff --git a/src/components/ParentBar/ParentBar.jsx b/src/components/ParentBar/ParentBar.jsx index f009d66..37a7b19 100644 --- a/src/components/ParentBar/ParentBar.jsx +++ b/src/components/ParentBar/ParentBar.jsx @@ -8,14 +8,18 @@ const ParentBar = memo(() => { const { data: folder, isLoading } = useFolder(); console.log("folder", folder); const navigate = useNavigate(); - const { isHome } = useUtils(); + const { isHome, isTrash } = useUtils(); if (isHome || !folder) { return
    ; } - const goHome = () => { - navigate("/home"); + const goHomeOrTrash = () => { + if (!isTrash) { + navigate("/home"); + } else { + navigate("/trash"); + } }; const goToFolder = () => { @@ -34,9 +38,9 @@ const ParentBar = memo(() => {
    - Home + {!isTrash ? "Home" : "Trash"} spacer diff --git a/src/components/RightSection/index.tsx b/src/components/RightSection/index.tsx index d7fa46b..529dee8 100644 --- a/src/components/RightSection/index.tsx +++ b/src/components/RightSection/index.tsx @@ -11,9 +11,11 @@ import { setPopupFile } from "../../actions/popupFile"; import { useNavigate } from "react-router-dom"; import { useAppSelector } from "../../hooks/store"; import { resetSelected } from "../../reducers/selected"; +import { useUtils } from "../../hooks/utils"; const RightSection = memo(() => { const selectedItem = useAppSelector((state) => state.selected.mainSection); + const { isTrash } = useUtils(); const navigate = useNavigate(); const dispatch = useDispatch(); @@ -71,8 +73,10 @@ const RightSection = memo(() => { const openItem = () => { if (selectedItem.file) { dispatch(setPopupFile({ showPopup: true, ...selectedItem.file })); - } else { + } else if (!isTrash) { navigate(`/folder/${selectedItem.id}`); + } else { + navigate(`/folder-trash/${selectedItem.id}`); } }; return ( diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index 42a0577..30cf960 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from "../../hooks/store"; import { useSearchSuggestions } from "../../hooks/files"; import debounce from "lodash/debounce"; import { useNavigate } from "react-router-dom"; -import { useClickOutOfBounds } from "../../hooks/utils"; +import { useClickOutOfBounds, useUtils } from "../../hooks/utils"; import SearchBarItem from "../SearchBarItem"; import { FolderInterface } from "../../types/folders"; import { FileInterface } from "../../types/file"; @@ -18,6 +18,7 @@ const SearchBar = memo(() => { const { data: searchSuggestions, isLoading: isLoadingSearchSuggestions } = useSearchSuggestions(debouncedSearchText); const navigate = useNavigate(); + const { isTrash } = useUtils(); const debouncedSetSearchText = useMemo( () => debounce(setDebouncedSearchText, 500), @@ -43,10 +44,18 @@ const SearchBar = memo(() => { e.preventDefault(); setSearchText(""); setDebouncedSearchText(""); - if (searchText.length) { - navigate(`/search/${searchText}`); + if (!isTrash) { + if (searchText.length) { + navigate(`/search/${searchText}`); + } else { + navigate("/home"); + } } else { - navigate("/home"); + if (searchText.length) { + navigate(`/search-trash/${searchText}`); + } else { + navigate("/trash"); + } } }; @@ -60,7 +69,12 @@ const SearchBar = memo(() => { }; const folderClick = (folder: FolderInterface) => { - navigate(`/folder/${folder?._id}`); + if (!isTrash) { + navigate(`/folder/${folder?._id}`); + } else { + navigate(`/folder-trash/${folder?._id}`); + } + resetState(); }; @@ -88,7 +102,7 @@ const SearchBar = memo(() => { type="text" onChange={onChangeSearch} value={searchText} - placeholder="Search" + placeholder={!isTrash ? "Search" : "Search Trash"} className="w-full min-h-[42px] border border-[#BEC9D3] pl-[45px] pr-[15px] text-[16px] text-black rounded-[5px]" />
    { const params = useParams(); // TODO: Remove any const sortBy = useSelector((state: any) => state.filter.sortBy); - const trashMode = false; + const { isTrash } = useUtils(); const filesReactQuery = useInfiniteQuery( [ "files", @@ -29,7 +29,7 @@ export const useFiles = () => { search: params.query || "", sortBy, limit: undefined, - trashMode, + trashMode: isTrash, }, ], getFilesListAPI, @@ -57,7 +57,7 @@ export const useFilesClient = () => { // TODO: Remove any const sortBy = useSelector((state: any) => state.filter.sortBy); const filesReactClientQuery = useQueryClient(); - const trashMode = false; + const { isTrash } = useUtils(); const invalidateFilesCache = () => { filesReactClientQuery.invalidateQueries({ @@ -65,10 +65,10 @@ export const useFilesClient = () => { "files", { parent: params.id || "/", - search: "", + search: params.query || "", sortBy, limit: undefined, - trashMode, + trashMode: isTrash, }, ], }); @@ -153,11 +153,13 @@ export const useThumbnail = ( }; export const useSearchSuggestions = (searchText: string) => { + const { isTrash } = useUtils(); const searchQuery = useQuery( [ "search", { searchText, + trashMode: isTrash, }, ], getSuggestedListAPI, diff --git a/src/hooks/folders.ts b/src/hooks/folders.ts index 98ad97c..284a032 100644 --- a/src/hooks/folders.ts +++ b/src/hooks/folders.ts @@ -2,11 +2,12 @@ import { useQuery, useQueryClient } from "react-query"; import { useParams } from "react-router-dom"; import { getFolderInfoAPI, getFoldersListAPI } from "../api/foldersAPI"; import { useSelector } from "react-redux"; +import { useUtils } from "./utils"; export const useFolders = () => { const params = useParams(); const sortBy = useSelector((state: any) => state.filter.sortBy); - const trashMode = false; + const { isTrash } = useUtils(); const foldersReactQuery = useQuery( [ "folders", @@ -15,7 +16,7 @@ export const useFolders = () => { search: params.query || "", sortBy, limit: undefined, - trashMode, + trashMode: isTrash, }, ], getFoldersListAPI @@ -28,7 +29,7 @@ export const useFoldersClient = () => { const params = useParams(); const sortBy = useSelector((state: any) => state.filter.sortBy); const foldersReactClientQuery = useQueryClient(); - const trashMode = false; + const { isTrash } = useUtils(); const invalidateFoldersCache = () => { foldersReactClientQuery.invalidateQueries({ @@ -36,10 +37,10 @@ export const useFoldersClient = () => { "folders", { parent: params.id || "/", - search: "", + search: params.query || "", sortBy, limit: undefined, - trashMode, + trashMode: isTrash, }, ], }); diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index 9e6d43d..8d30043 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -1,14 +1,23 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useParams } from "react-router-dom"; +import { useLocation, useParams } from "react-router-dom"; export const useUtils = () => { - const params = useParams(); + const location = useLocation(); const isHome = useMemo(() => { - return !params.id && !params.query; - }, [params.id, params.query]); + return location.pathname === "/home"; + }, [location.pathname]); - return { isHome }; + const isTrash = useMemo(() => { + console.log("location", location.pathname); + return ( + location.pathname === "/trash" || + location.pathname.includes("/folder-trash") || + location.pathname.includes("/search-trash") + ); + }, [location.pathname]); + + return { isHome, isTrash }; }; export const useClickOutOfBounds = (outOfBoundsCallback: () => any) => { diff --git a/src/icons/DownloadIcon.tsx b/src/icons/DownloadIcon.tsx new file mode 100644 index 0000000..d34da92 --- /dev/null +++ b/src/icons/DownloadIcon.tsx @@ -0,0 +1,28 @@ +interface DownloadIconProps { + className?: string; +} + +const DownloadIcon = (props: DownloadIconProps) => { + return ( + + + + + + ); +}; + +export default DownloadIcon; diff --git a/src/icons/HomeListIcon.tsx b/src/icons/HomeListIcon.tsx new file mode 100644 index 0000000..0cd32fd --- /dev/null +++ b/src/icons/HomeListIcon.tsx @@ -0,0 +1,63 @@ +export interface HomeListIconProps { + className?: string; +} + +const HomeListIcon = (props: HomeListIconProps) => { + return ( + + + + + + + + + + + ); +}; + +export default HomeListIcon; diff --git a/src/icons/MoveIcon.tsx b/src/icons/MoveIcon.tsx new file mode 100644 index 0000000..c64e697 --- /dev/null +++ b/src/icons/MoveIcon.tsx @@ -0,0 +1,26 @@ +interface MoveiconProps { + className?: string; +} + +const Moveicon = (props: MoveiconProps) => { + return ( + + + + ); +}; + +export default Moveicon; diff --git a/src/icons/MultiSelectIcon.tsx b/src/icons/MultiSelectIcon.tsx new file mode 100644 index 0000000..0342b64 --- /dev/null +++ b/src/icons/MultiSelectIcon.tsx @@ -0,0 +1,21 @@ +interface MultiSelectIconProps { + className?: string; +} + +const MultiSelectIcon = (props: MultiSelectIconProps) => { + return ( + + + + ); +}; + +export default MultiSelectIcon; diff --git a/src/icons/RenameIcon.tsx b/src/icons/RenameIcon.tsx new file mode 100644 index 0000000..a5096c2 --- /dev/null +++ b/src/icons/RenameIcon.tsx @@ -0,0 +1,26 @@ +interface RenameIconProps { + className?: string; +} + +const RenameIcon = (props: RenameIconProps) => { + return ( + + + + ); +}; + +export default RenameIcon; diff --git a/src/icons/RestoreIcon.tsx b/src/icons/RestoreIcon.tsx new file mode 100644 index 0000000..eef7f8e --- /dev/null +++ b/src/icons/RestoreIcon.tsx @@ -0,0 +1,21 @@ +interface RestoreIconProps { + className?: string; +} + +const RestoreIcon = (props: any) => { + return ( + + restore + + + ); +}; + +export default RestoreIcon; diff --git a/src/icons/ShareIcon.tsx b/src/icons/ShareIcon.tsx new file mode 100644 index 0000000..f9847a2 --- /dev/null +++ b/src/icons/ShareIcon.tsx @@ -0,0 +1,28 @@ +interface ShareIconProps { + className?: string; +} + +const ShareIcon = (props: ShareIconProps) => { + return ( + + + + + + ); +}; + +export default ShareIcon; diff --git a/src/icons/TrashIcon.tsx b/src/icons/TrashIcon.tsx new file mode 100644 index 0000000..b0ead4f --- /dev/null +++ b/src/icons/TrashIcon.tsx @@ -0,0 +1,43 @@ +interface TrashIconProps { + className?: string; +} + +const TrashIcon = (props: TrashIconProps) => { + return ( + + + + + + + + + ); +}; + +export default TrashIcon; diff --git a/src/routers/AppRouter.jsx b/src/routers/AppRouter.jsx index e5d8f4d..81995b2 100755 --- a/src/routers/AppRouter.jsx +++ b/src/routers/AppRouter.jsx @@ -57,6 +57,33 @@ const AppRouter = () => { } /> + + + + } + /> + + + + } + /> + + + + } + /> } /> } /> } />