From e36aeaa6aa539272cfa1725819793a26124fa08e Mon Sep 17 00:00:00 2001 From: subnub Date: Mon, 1 Jul 2024 02:51:23 -0400 Subject: [PATCH] added media mode and linter --- backend/controllers/file.ts | 4 +- backend/db/utils/fileUtils/index.ts | 5 +- backend/services/FileService/index.ts | 23 +++- backend/utils/createQuery.ts | 8 +- eslint.config.mjs | 26 +++++ package.json | 16 +-- src/api/filesAPI.ts | 16 ++- src/components/ContextMenu/index.jsx | 8 +- src/components/Dataform/index.jsx | 4 +- src/components/FileItem/index.jsx | 6 +- src/components/Files/index.jsx | 2 +- src/components/FolderItem/index.jsx | 92 ++++++++-------- src/components/Folders/index.jsx | 2 +- src/components/LeftSection/LeftSection.jsx | 25 ++++- src/components/MainSection/index.jsx | 94 ++-------------- src/components/MediaItem/index.tsx | 119 +++++++++++++++++++++ src/components/Medias/index.tsx | 52 +++++++++ src/components/MultiSelectBar/index.tsx | 37 +++++-- src/components/QuickAccess/index.jsx | 2 +- src/components/QuickAccessItem/index.tsx | 7 +- src/components/SearchBar/index.tsx | 29 +++-- src/hooks/files.ts | 30 ++++-- src/hooks/utils.ts | 35 +++--- src/icons/PhotoIcon.tsx | 13 +++ src/routers/AppRouter.jsx | 18 ++++ 25 files changed, 473 insertions(+), 200 deletions(-) create mode 100644 eslint.config.mjs create mode 100644 src/components/MediaItem/index.tsx create mode 100644 src/components/Medias/index.tsx create mode 100644 src/icons/PhotoIcon.tsx diff --git a/backend/controllers/file.ts b/backend/controllers/file.ts index 2eb591b..eeec46b 100644 --- a/backend/controllers/file.ts +++ b/backend/controllers/file.ts @@ -402,11 +402,13 @@ class FileController { const userID = req.user._id; let searchQuery = req.query.search || ""; const trashMode = req.query.trashMode === "true"; + const mediaMode = req.query.mediaMode === "true"; const { fileList, folderList } = await fileService.getSuggestedList( userID, searchQuery, - trashMode + trashMode, + mediaMode ); return res.send({ folderList, fileList }); diff --git a/backend/db/utils/fileUtils/index.ts b/backend/db/utils/fileUtils/index.ts index 496aafd..eaddd07 100644 --- a/backend/db/utils/fileUtils/index.ts +++ b/backend/db/utils/fileUtils/index.ts @@ -148,7 +148,8 @@ class DbUtil { getFileSearchList = async ( userID: string, searchQuery: RegExp, - trashMode: boolean + trashMode: boolean, + mediaMode: boolean ) => { let query: any = { "metadata.owner": userID, @@ -156,6 +157,8 @@ class DbUtil { "metadata.trashed": trashMode ? true : null, }; + if (mediaMode) query = { ...query, "metadata.hasThumbnail": true }; + const fileList = (await conn.db .collection("fs.files") .find(query) diff --git a/backend/services/FileService/index.ts b/backend/services/FileService/index.ts index 2b17e49..70a2596 100644 --- a/backend/services/FileService/index.ts +++ b/backend/services/FileService/index.ts @@ -128,6 +128,7 @@ class MongoFileService { const storageType = query.storageType || undefined; const folderSearch = query.folder_search || undefined; const trashMode = query.trashMode === "true"; + const mediaMode = query.mediaMode === "true"; sortBy = sortBySwitch(sortBy); limit = parseInt(limit); console.log("sortBy", sortBy, query.sortBy); @@ -145,7 +146,8 @@ class MongoFileService { startAtName, storageType, folderSearch, - trashMode + trashMode, + mediaMode ); const fileList = await dbUtilsFile.getList(queryObj, sortBy, limit); @@ -206,23 +208,34 @@ class MongoFileService { getSuggestedList = async ( userID: string, searchQuery: any, - trashMode: boolean + trashMode: boolean, + mediaMode: boolean ) => { searchQuery = new RegExp(searchQuery, "i"); const fileList = await dbUtilsFile.getFileSearchList( userID, searchQuery, - trashMode + trashMode, + mediaMode ); + + if (!fileList) throw new NotFoundError("Suggested List Not Found Error"); + + if (mediaMode) { + return { + fileList, + folderList: [], + }; + } + const folderList = await dbUtilsFolder.getFolderSearchList( userID, searchQuery, trashMode ); - if (!fileList || !folderList) - throw new NotFoundError("Suggested List Not Found Error"); + if (!folderList) throw new NotFoundError("Suggested List Not Found Error"); return { fileList, diff --git a/backend/utils/createQuery.ts b/backend/utils/createQuery.ts index d6acc31..a560681 100644 --- a/backend/utils/createQuery.ts +++ b/backend/utils/createQuery.ts @@ -17,6 +17,7 @@ export interface QueryInterface { }; "metadata.personalFile"?: boolean | null; "metadata.trashed"?: boolean | null; + "metadata.hasThumbnail"?: boolean | null; } const createQuery = ( @@ -30,7 +31,8 @@ const createQuery = ( startAtName: string, storageType: string, folderSearch: boolean, - trashMode: boolean + trashMode: boolean, + mediaMode: boolean ) => { let query: QueryInterface = { "metadata.owner": owner }; @@ -71,6 +73,10 @@ const createQuery = ( query = { ...query, "metadata.trashed": null }; } + if (mediaMode) { + query = { ...query, "metadata.hasThumbnail": true }; + } + // if (storageType === "s3") { // query = {...query, "metadata.personalFile": true} // } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..552444d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,26 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; +import pluginReactConfig from "eslint-plugin-react/configs/recommended.js"; +import pluginReactHooks from "eslint-plugin-react-hooks"; +import pluginReact from "eslint-plugin-react"; + +export default [ + { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, + { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } }, + { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + { + ...pluginReactConfig, + plugins: { + react: pluginReact, + "react-hooks": pluginReactHooks, + }, + rules: { + "react/react-in-jsx-scope": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "error", + }, + }, +]; diff --git a/package.json b/package.json index d0d1220..9c136bf 100755 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "dev-test": "concurrently \"vite\" \"nodemon server.js\"", "dev-hot": "concurrently \"vite\" \"tsc -w -p ./backend/tsconfig.json\" \"npm run dev:backend\"", "dev:backend": "nodemon dist/server/serverStart.js", - "vite-build": "vite build" + "vite-build": "vite build", + "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'" }, "dependencies": { "@babel/core": "^7.8.4", @@ -82,6 +83,7 @@ "core-js": "^3.6.4", "diskusage": "^1.1.3", "dotenv": "^8.2.0", + "eslint-plugin-react-hooks": "^4.6.2", "express": "^4.19.2", "fluent-ffmpeg": "^2.1.3", "font-awesome": "^4.7.0", @@ -117,22 +119,22 @@ "vite": "^5.2.13" }, "devDependencies": { + "@eslint/js": "^9.6.0", "@types/fluent-ffmpeg": "^2.1.24", "@types/lodash": "^4.17.5", - "@typescript-eslint/eslint-plugin": "^2.20.0", - "@typescript-eslint/parser": "^2.20.0", "concurrently": "^8.2.2", "cross-env": "^6.0.3", "dart-sass": "^1.25.0", "env-cmd": "^10.1.0", - "eslint": "^6.8.0", - "eslint-plugin-import": "^2.20.1", - "eslint-plugin-react": "^7.18.3", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.3", + "globals": "^15.7.0", "jest": "^29.7.0", "nodemon": "^3.1.3", "sass": "^1.77.4", "superagent-binary-parser": "^1.0.1", "supertest": "^6.0.1", - "tailwindcss": "^3.4.4" + "tailwindcss": "^3.4.4", + "typescript-eslint": "^7.14.1" } } diff --git a/src/api/filesAPI.ts b/src/api/filesAPI.ts index 1335016..a70e717 100644 --- a/src/api/filesAPI.ts +++ b/src/api/filesAPI.ts @@ -11,6 +11,7 @@ interface QueryKeyParams { startAtName?: string; startAt?: boolean; trashMode?: boolean; + mediaMode?: boolean; } // GET @@ -21,7 +22,14 @@ export const getFilesListAPI = async ({ }: QueryFunctionContext<[string, QueryKeyParams]>) => { const [ _key, - { parent = "/", search = "", sortBy = "date_desc", limit = 50, trashMode }, + { + parent = "/", + search = "", + sortBy = "date_desc", + limit = 50, + trashMode, + mediaMode, + }, ] = queryKey; const queryParams: QueryKeyParams = { @@ -30,6 +38,7 @@ export const getFilesListAPI = async ({ sortBy, limit, trashMode, + mediaMode, }; if (pageParam?.startAtDate && pageParam?.startAtName) { @@ -85,13 +94,14 @@ export const getFileThumbnailAPI = async (thumbnailID: string) => { export const getSuggestedListAPI = async ({ queryKey, }: QueryFunctionContext< - [string, { searchText: string; trashMode: boolean }] + [string, { searchText: string; trashMode: boolean; mediaMode: boolean }] >) => { - const [_key, { searchText, trashMode }] = queryKey; + const [_key, { searchText, trashMode, mediaMode }] = queryKey; const response = await axios.get(`/file-service/suggested-list`, { params: { search: searchText, trashMode, + mediaMode, }, }); return response.data; diff --git a/src/components/ContextMenu/index.jsx b/src/components/ContextMenu/index.jsx index bc939a4..7d89ebb 100644 --- a/src/components/ContextMenu/index.jsx +++ b/src/components/ContextMenu/index.jsx @@ -36,7 +36,7 @@ const ContextMenu = (props) => { const { invalidateFoldersCache } = useFoldersClient(); const { invalidateQuickFilesCache } = useQuickFilesClient(); const { wrapperRef } = useClickOutOfBounds(props.closeContext); - const { isTrash } = useUtils(); + const { isTrash, isMedia } = useUtils(); const dispatch = useAppDispatch(); const liClassname = "flex w-full px-[20px] py-[12px] items-center font-normal text-[#637381] justify-start no-underline animate hover:bg-[#f6f5fd] hover:text-[#3c85ee] hover:font-medium"; @@ -230,10 +230,10 @@ const ContextMenu = (props) => { - Multi select + Multi-select - {!isTrash && ( + {!isTrash && !isMedia && (
  • @@ -263,7 +263,7 @@ const ContextMenu = (props) => {
  • ) : undefined} - {!isTrash && ( + {!isTrash && !isMedia && (
  • diff --git a/src/components/Dataform/index.jsx b/src/components/Dataform/index.jsx index cf9d68c..e1102cc 100644 --- a/src/components/Dataform/index.jsx +++ b/src/components/Dataform/index.jsx @@ -73,7 +73,9 @@ const DataForm = memo(() => { > {!isLoading && (
    - +
    + +
    diff --git a/src/components/FileItem/index.jsx b/src/components/FileItem/index.jsx index cad4404..5bcdd08 100644 --- a/src/components/FileItem/index.jsx +++ b/src/components/FileItem/index.jsx @@ -66,8 +66,10 @@ const FileItem = React.memo((props) => { ); // TODO: See if we can memoize this - const fileClick = () => { - if (multiSelectMode) { + const fileClick = (e) => { + const multiSelectKey = e.metaKey || e.ctrlKey; + + if (multiSelectMode | multiSelectKey) { dispatch( setMultiSelectMode({ type: "file", diff --git a/src/components/Files/index.jsx b/src/components/Files/index.jsx index 423325e..08b3e63 100644 --- a/src/components/Files/index.jsx +++ b/src/components/Files/index.jsx @@ -103,7 +103,7 @@ const Files = memo(() => { {!listView ? (
    1 ? "justify-center xs:justify-normal" : "justify-normal" diff --git a/src/components/FolderItem/index.jsx b/src/components/FolderItem/index.jsx index 612a6fb..4febcb2 100644 --- a/src/components/FolderItem/index.jsx +++ b/src/components/FolderItem/index.jsx @@ -38,52 +38,56 @@ const FolderItem = React.memo((props) => { ...contextMenuState } = useContextMenu(); - const folderClick = useCallback(() => { - if (multiSelectMode) { - dispatch( - setMultiSelectMode({ - type: "folder", - id: folder._id, - file: null, - folder: folder, - }) - ); - return; - } - const currentDate = Date.now(); - - if (!elementSelected) { - dispatch( - setMainSelect({ - file: null, - id: folder._id, - type: "folder", - folder: folder, - }) - ); - lastSelected.current = Date.now(); - return; - } - - const isMobile = mobilecheck(); - - if (isMobile || currentDate - lastSelected.current < 1500) { - if (isTrash) { - navigate(`/folder-trash/${folder._id}`); - } else { - navigate(`/folder/${folder._id}`); + const folderClick = useCallback( + (e) => { + const multiSelectKey = e.metaKey || e.ctrlKey; + if (multiSelectMode || multiSelectKey) { + dispatch( + setMultiSelectMode({ + type: "folder", + id: folder._id, + file: null, + folder: folder, + }) + ); + return; } - } + const currentDate = Date.now(); - lastSelected.current = Date.now(); - }, [ - startSetSelectedItem, - mobilecheck, - navigate, - folder._id, - elementSelected, - multiSelectMode, - ]); + if (!elementSelected) { + dispatch( + setMainSelect({ + file: null, + id: folder._id, + type: "folder", + folder: folder, + }) + ); + lastSelected.current = Date.now(); + return; + } + + const isMobile = mobilecheck(); + + if (isMobile || currentDate - lastSelected.current < 1500) { + if (isTrash) { + navigate(`/folder-trash/${folder._id}`); + } else { + navigate(`/folder/${folder._id}`); + } + } + + lastSelected.current = Date.now(); + }, + [ + startSetSelectedItem, + mobilecheck, + navigate, + folder._id, + elementSelected, + multiSelectMode, + ] + ); return (
    {
    1 ? "justify-center xs:justify-normal" : "justify-normal" diff --git a/src/components/LeftSection/LeftSection.jsx b/src/components/LeftSection/LeftSection.jsx index 88833c8..45bf9ab 100644 --- a/src/components/LeftSection/LeftSection.jsx +++ b/src/components/LeftSection/LeftSection.jsx @@ -8,10 +8,11 @@ import AddNewDropdown from "../AddNewDropdown"; import HomeListIcon from "../../icons/HomeListIcon"; import TrashIcon from "../../icons/TrashIcon"; import classNames from "classnames"; +import PhotoIcon from "../../icons/PhotoIcon"; const LeftSection = (props) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const { isHome, isTrash } = useUtils(); + const { isHome, isTrash, isMedia } = useUtils(); const navigate = useNavigate(); const addNewDisabled = useRef(false); @@ -35,6 +36,10 @@ const LeftSection = (props) => { navigate("/trash"); }; + const goMedia = () => { + navigate("/media"); + }; + return (
    {
    +
    • diff --git a/src/components/MainSection/index.jsx b/src/components/MainSection/index.jsx index b33cdf7..b8202ce 100644 --- a/src/components/MainSection/index.jsx +++ b/src/components/MainSection/index.jsx @@ -5,106 +5,24 @@ import PopupWindow from "../PopupWindow"; import React, { memo } from "react"; import LeftSection from "../LeftSection"; import { useSelector } from "react-redux"; +import { useUtils } from "../../hooks/utils"; +import Medias from "../Medias"; const MainSection = memo(() => { const moverID = useSelector((state) => state.mover.id); const showPopup = useSelector((state) => state.popupFile.showPopup); + const { isMedia } = useUtils(); return (
      - {/*
      - */} -
      - {/* {true ? undefined : ( -
      -
      -
      - get -
      -
      All your files in one place
      -

      Drag and drop a file to get started

      -
      -
      - )} */} - - {/* {props.routeType === "search" ? ( -
      -
      -

      - - {props.files.length + props.folders.length >= 50 - ? "50+" - : props.files.length + props.folders.length} - {" "} - results for{" "} - - {props.cachedSearch} - -

      -

      - You are searching in{" "} - - {props.parent === "/" - ? "Home" - : props.parentNameList.length !== 0 - ? props.parentNameList[props.parentNameList.length - 1] - : "Unknown"} - {" "} - - spacer - - {" "} - - Show results from everywhere - -

      -
      -
      - ) : undefined} */} - +
      {showPopup ? : undefined} {moverID.length === 0 ? undefined : }
      - {}} /> + - + {!isMedia ? : }
      diff --git a/src/components/MediaItem/index.tsx b/src/components/MediaItem/index.tsx new file mode 100644 index 0000000..c491c74 --- /dev/null +++ b/src/components/MediaItem/index.tsx @@ -0,0 +1,119 @@ +import React, { useRef } from "react"; +import { FileInterface } from "../../types/file"; +import { useThumbnail } from "../../hooks/files"; +import { useAppDispatch, useAppSelector } from "../../hooks/store"; +import { setMainSelect, setMultiSelectMode } from "../../reducers/selected"; +import mobilecheck from "../../utils/mobileCheck"; +import { setPopupFile } from "../../actions/popupFile"; +import classNames from "classnames"; +import { useContextMenu } from "../../hooks/contextMenu"; +import ContextMenu from "../ContextMenu"; +import PlayButtonIcon from "../../icons/PlayIcon"; + +type MediaItemType = { + file: FileInterface; +}; + +const MediaItem: React.FC = ({ file }) => { + const elementSelected = useAppSelector((state) => { + if (state.selected.mainSection.type !== "file") return false; + return state.selected.mainSection.id === file._id; + }); + const elementMultiSelected = useAppSelector((state) => { + if (!state.selected.multiSelectMode) return false; + const selected = state.selected.multiSelectMap[file._id]; + return selected && selected.type === "file"; + }); + const multiSelectMode = useAppSelector( + (state) => state.selected.multiSelectMode + ); + const { + onContextMenu, + closeContextMenu, + onTouchStart, + onTouchMove, + onTouchEnd, + clickStopPropagation, + ...contextMenuState + } = useContextMenu(); + const lastSelected = useRef(0); + const dispatch = useAppDispatch(); + const { image, imageOnError } = useThumbnail( + file.metadata.hasThumbnail, + file.metadata.thumbnailID + ); + + // TODO: See if we can memoize this and remove any + const mediaItemClick = (e: any) => { + const multiSelectKey = e.metaKey || e.ctrlKey; + if (multiSelectMode || multiSelectKey) { + dispatch( + setMultiSelectMode({ + type: "file", + id: file._id, + file: file, + folder: null, + }) + ); + return; + } + const currentDate = Date.now(); + + if (!elementSelected) { + // dispatch(startSetSelectedItem(file._id, true, true)); + dispatch( + setMainSelect({ file, id: file._id, type: "file", folder: null }) + ); + lastSelected.current = Date.now(); + return; + } + + const isMobile = mobilecheck(); + + if (isMobile || currentDate - lastSelected.current < 1500) { + dispatch(setPopupFile({ showPopup: true, ...file })); + } + + lastSelected.current = Date.now(); + }; + + return ( +
      + {contextMenuState.selected && ( +
      + +
      + )} + {file.metadata.isVideo && ( +
      + +
      + )} + +
      + ); +}; + +export default MediaItem; diff --git a/src/components/Medias/index.tsx b/src/components/Medias/index.tsx new file mode 100644 index 0000000..824a712 --- /dev/null +++ b/src/components/Medias/index.tsx @@ -0,0 +1,52 @@ +import classNames from "classnames"; +import React, { useEffect, useMemo, useState } from "react"; +import MediaItem from "../MediaItem"; +import { useFiles, useThumbnail } from "../../hooks/files"; +import MultiSelectBar from "../MultiSelectBar"; +import { useInfiniteScroll } from "../../hooks/infiniteScroll"; + +const Medias = () => { + const { + data: files, + isFetchingNextPage, + fetchNextPage: filesFetchNextPage, + } = useFiles(); + const [initialLoad, setInitialLoad] = useState(true); + const { sentinelRef, reachedIntersect } = useInfiniteScroll(); + + useEffect(() => { + if (initialLoad) { + setInitialLoad(false); + return; + } else if (!files) { + return; + } + if (reachedIntersect && !isFetchingNextPage) { + filesFetchNextPage(); + } + }, [reachedIntersect, initialLoad, isFetchingNextPage]); + + return ( +
      +
      + +
      +
      + {files?.pages.map((filePage, index) => ( + + {filePage.map((file) => ( + + ))} + + ))} +
      +
      +
      + ); +}; + +export default Medias; diff --git a/src/components/MultiSelectBar/index.tsx b/src/components/MultiSelectBar/index.tsx index 5775a59..bdc3119 100644 --- a/src/components/MultiSelectBar/index.tsx +++ b/src/components/MultiSelectBar/index.tsx @@ -1,7 +1,6 @@ -import React, { useEffect, useMemo, useRef } from "react"; +import React, { useCallback, useEffect, useMemo, useRef } from "react"; import { useAppDispatch, useAppSelector } from "../../hooks/store"; import { resetMultiSelect } from "../../reducers/selected"; -import Swal from "sweetalert2"; import { deleteMultiAPI, restoreMultiAPI, @@ -19,7 +18,7 @@ import { import RestoreIcon from "../../icons/RestoreIcon"; import { useUtils } from "../../hooks/utils"; -const MultiSelectBar = () => { +const MultiSelectBar: React.FC = () => { const dispatch = useAppDispatch(); const ignoreFirstMount = useRef(true); const multiSelectMode = useAppSelector( @@ -35,7 +34,7 @@ const MultiSelectBar = () => { const { invalidateFoldersCache } = useFoldersClient(); const { invalidateQuickFilesCache } = useQuickFilesClient(); - const { isTrash } = useUtils(); + const { isTrash, isMedia } = useUtils(); useEffect(() => { if (ignoreFirstMount.current) { @@ -45,9 +44,26 @@ const MultiSelectBar = () => { } }, [isTrash]); - const closeMultiSelect = () => { + const closeMultiSelect = useCallback(() => { dispatch(resetMultiSelect()); - }; + }, []); + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Escape" || e.key === "Esc") { + closeMultiSelect(); + } + }, + [closeMultiSelect] + ); + + useEffect(() => { + document.addEventListener("keydown", handleKeyDown); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); const trashItems = async () => { const result = await trashItemsPopup(); @@ -91,7 +107,7 @@ const MultiSelectBar = () => { return (
      -
      +
      { className="ml-4 cursor-pointer" onClick={trashItems} /> - {}} /> + {!isMedia && ( + {}} + /> + )} )} {isTrash && ( diff --git a/src/components/QuickAccess/index.jsx b/src/components/QuickAccess/index.jsx index 7e5bb11..0701602 100644 --- a/src/components/QuickAccess/index.jsx +++ b/src/components/QuickAccess/index.jsx @@ -39,7 +39,7 @@ const QuickAccess = memo(() => {
      1 ? "justify-center xs:justify-normal" : "justify-normal", diff --git a/src/components/QuickAccessItem/index.tsx b/src/components/QuickAccessItem/index.tsx index 7a2eae4..11c4517 100644 --- a/src/components/QuickAccessItem/index.tsx +++ b/src/components/QuickAccessItem/index.tsx @@ -61,9 +61,10 @@ const QuickAccessItem = memo((props: QuickAccessItemProps) => { [file.filename] ); - // TODO: See if we can memoize this - const quickItemClick = () => { - if (multiSelectMode) { + // TODO: See if we can memoize this and remove any + const quickItemClick = (e: any) => { + const multiSelectKey = e.metaKey || e.ctrlKey; + if (multiSelectMode || multiSelectKey) { dispatch( setMultiSelectMode({ type: "quick-item", diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index 30cf960..46ee0c7 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -10,6 +10,7 @@ import { FileInterface } from "../../types/file"; import { setPopupFile } from "../../actions/popupFile"; import Spinner from "../Spinner"; import classNames from "classnames"; +import { is } from "@babel/types"; const SearchBar = memo(() => { const [searchText, setSearchText] = useState(""); @@ -18,7 +19,7 @@ const SearchBar = memo(() => { const { data: searchSuggestions, isLoading: isLoadingSearchSuggestions } = useSearchSuggestions(debouncedSearchText); const navigate = useNavigate(); - const { isTrash } = useUtils(); + const { isTrash, isMedia } = useUtils(); const debouncedSetSearchText = useMemo( () => debounce(setDebouncedSearchText, 500), @@ -44,18 +45,24 @@ const SearchBar = memo(() => { e.preventDefault(); setSearchText(""); setDebouncedSearchText(""); - if (!isTrash) { + if (isMedia) { if (searchText.length) { - navigate(`/search/${searchText}`); + navigate(`/search-media/${searchText}`); } else { - navigate("/home"); + navigate("/media"); } - } else { + } else if (isTrash) { if (searchText.length) { navigate(`/search-trash/${searchText}`); } else { navigate("/trash"); } + } else { + if (searchText.length) { + navigate(`/search/${searchText}`); + } else { + navigate("/home"); + } } }; @@ -78,6 +85,16 @@ const SearchBar = memo(() => { resetState(); }; + const searchTextPlaceholder = useMemo(() => { + if (isMedia) { + return "Search Media"; + } else if (isTrash) { + return "Search Trash"; + } else { + return "Search"; + } + }, [isMedia, isTrash]); + return (
      { type="text" onChange={onChangeSearch} value={searchText} - placeholder={!isTrash ? "Search" : "Search Trash"} + placeholder={searchTextPlaceholder} 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 { isTrash } = useUtils(); + const { isTrash, isMedia } = useUtils(); + const limit = isMedia ? 100 : 50; const filesReactQuery = useInfiniteQuery( [ "files", @@ -28,8 +29,9 @@ export const useFiles = (enabled = true) => { parent: params.id || "/", search: params.query || "", sortBy, - limit: undefined, + limit, trashMode: isTrash, + mediaMode: isMedia, }, ], getFilesListAPI, @@ -58,7 +60,8 @@ export const useFilesClient = () => { // TODO: Remove any const sortBy = useSelector((state: any) => state.filter.sortBy); const filesReactClientQuery = useQueryClient(); - const { isTrash } = useUtils(); + const { isTrash, isMedia } = useUtils(); + const limit = isMedia ? 100 : 50; const invalidateFilesCache = () => { filesReactClientQuery.invalidateQueries({ @@ -68,8 +71,9 @@ export const useFilesClient = () => { parent: params.id || "/", search: params.query || "", sortBy, - limit: undefined, + limit, trashMode: isTrash, + mediaMode: isMedia, }, ], }); @@ -113,7 +117,7 @@ export const useThumbnail = ( hasThumbnail: false, image: undefined, }); - const { isHome } = useUtils(); + const { isHome, isMedia } = useUtils(); const listView = useAppSelector((state) => state.filter.listView); const imageOnError = useCallback(() => { @@ -123,11 +127,11 @@ export const useThumbnail = ( }); }, []); const getThumbnail = useCallback(async () => { + console.log("getting thumbnail", thumbnailID); try { if (!thumbnailID || requestedThumbnail.current) return; if (isQuickFile && !isHome) return; - if (!isQuickFile && listView) return; - console.log("getting thumbnail", thumbnailID); + if (!isQuickFile && listView && !isMedia) return; requestedThumbnail.current = true; const thumbnailData = await getFileThumbnailAPI(thumbnailID); setState({ @@ -143,26 +147,32 @@ export const useThumbnail = ( getFileThumbnailAPI, imageOnError, listView, - requestedThumbnail.current, + isHome, + isQuickFile, ]); useEffect(() => { if (!hasThumbnail || !thumbnailID) return; getThumbnail(); - }, [hasThumbnail, getThumbnail]); + + return () => { + requestedThumbnail.current = false; + }; + }, [hasThumbnail, getThumbnail, thumbnailID]); return { ...state, imageOnError }; }; export const useSearchSuggestions = (searchText: string) => { - const { isTrash } = useUtils(); + const { isTrash, isMedia } = useUtils(); const searchQuery = useQuery( [ "search", { searchText, trashMode: isTrash, + mediaMode: isMedia, }, ], getSuggestedListAPI, diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index 8d30043..99dcd0e 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useLocation, useParams } from "react-router-dom"; +import { useLocation } from "react-router-dom"; export const useUtils = () => { const location = useLocation(); @@ -17,7 +17,15 @@ export const useUtils = () => { ); }, [location.pathname]); - return { isHome, isTrash }; + const isMedia = useMemo(() => { + console.log("location", location.pathname); + return ( + location.pathname === "/media" || + location.pathname.includes("/search-media") + ); + }, [location.pathname]); + + return { isHome, isTrash, isMedia }; }; export const useClickOutOfBounds = (outOfBoundsCallback: () => any) => { @@ -42,7 +50,7 @@ export const useClickOutOfBounds = (outOfBoundsCallback: () => any) => { document.removeEventListener("mousedown", outOfBoundsClickCheck); document.removeEventListener("touchstart", outOfBoundsClickCheck); }; - }, [outOfBoundsCallback]); + }, [outOfBoundsCallback, outOfBoundsClickCheck]); return { wrapperRef, @@ -53,16 +61,19 @@ export const useDragAndDrop = (fileDroppedCallback: (file: any) => any) => { const [isDraggingFile, setIsDraggingFile] = useState(false); const isDraggingFileRef = useRef(false); - const onDragDropEvent = useCallback((e: any) => { - e.preventDefault(); - e.stopPropagation(); - isDraggingFileRef.current = false; - setIsDraggingFile(false); + const onDragDropEvent = useCallback( + (e: any) => { + e.preventDefault(); + e.stopPropagation(); + isDraggingFileRef.current = false; + setIsDraggingFile(false); - const fileInput = e.dataTransfer; + const fileInput = e.dataTransfer; - fileDroppedCallback(fileInput); - }, []); + fileDroppedCallback(fileInput); + }, + [fileDroppedCallback] + ); const onDragEvent = useCallback((e: any) => { e.preventDefault(); e.stopPropagation(); @@ -92,7 +103,7 @@ export const useDragAndDrop = (fileDroppedCallback: (file: any) => any) => { window.removeEventListener("dragover", stopDrag); window.removeEventListener("focus", stopDrag); }; - }, []); + }, [stopDrag]); return { isDraggingFile, diff --git a/src/icons/PhotoIcon.tsx b/src/icons/PhotoIcon.tsx new file mode 100644 index 0000000..c8c61ef --- /dev/null +++ b/src/icons/PhotoIcon.tsx @@ -0,0 +1,13 @@ +const PhotoIcon: React.FC> = (props) => { + return ( + + image + + + ); +}; + +export default PhotoIcon; diff --git a/src/routers/AppRouter.jsx b/src/routers/AppRouter.jsx index 81995b2..0b9d448 100755 --- a/src/routers/AppRouter.jsx +++ b/src/routers/AppRouter.jsx @@ -66,6 +66,24 @@ const AppRouter = () => { } /> + + + + } + /> + + + + } + />