added media mode and linter
This commit is contained in:
+13
-3
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
<span className={spanClassname}>
|
||||
<MultiSelectIcon className="w-[19px] h-[20px]" />
|
||||
</span>
|
||||
Multi select
|
||||
Multi-select
|
||||
</a>
|
||||
</li>
|
||||
{!isTrash && (
|
||||
{!isTrash && !isMedia && (
|
||||
<li onClick={renameItem} className={liClassname}>
|
||||
<a className="flex">
|
||||
<span className={spanClassname}>
|
||||
@@ -263,7 +263,7 @@ const ContextMenu = (props) => {
|
||||
</a>
|
||||
</li>
|
||||
) : undefined}
|
||||
{!isTrash && (
|
||||
{!isTrash && !isMedia && (
|
||||
<li onClick={openMoveItemModal} className={liClassname}>
|
||||
<a className="flex" data-modal="destination__modal">
|
||||
<span className={spanClassname}>
|
||||
|
||||
@@ -73,7 +73,9 @@ const DataForm = memo(() => {
|
||||
>
|
||||
{!isLoading && (
|
||||
<div>
|
||||
<MultiSelectBar />
|
||||
<div className="fixed bottom-0 flex justify-center items-center right-0 left-0">
|
||||
<MultiSelectBar />
|
||||
</div>
|
||||
|
||||
<QuickAccess />
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -103,7 +103,7 @@ const Files = memo(() => {
|
||||
{!listView ? (
|
||||
<div
|
||||
className={classNames(
|
||||
"grid grid-cols-[repeat(auto-fit,minmax(40%,45%))] xs:grid-cols-[repeat(auto-fit,minmax(185px,185px))] gap-[20px]",
|
||||
"grid grid-cols-[repeat(auto-fit,minmax(40%,45%))] xs:grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-[16px]",
|
||||
files?.pages[0]?.length > 1
|
||||
? "justify-center xs:justify-normal"
|
||||
: "justify-normal"
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
|
||||
@@ -116,7 +116,7 @@ const Folders = memo(() => {
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
"grid grid-cols-[repeat(auto-fit,minmax(40%,45%))] xs:grid-cols-[repeat(auto-fit,minmax(185px,185px))] gap-[20px]",
|
||||
"grid grid-cols-[repeat(auto-fit,minmax(40%,45%))] xs:grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-[16px]",
|
||||
folders?.length > 1
|
||||
? "justify-center xs:justify-normal"
|
||||
: "justify-normal"
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className="menu__block p-6 hidden mobileMode:block border-r w-[270px] min-w-[270px]"
|
||||
@@ -80,6 +85,24 @@ const LeftSection = (props) => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-[#E8EEF2] pr-[20px] pt-3 pb-3">
|
||||
<ul className="m-0 list-none p-0 cursor-pointer ">
|
||||
<li>
|
||||
<a
|
||||
onClick={goMedia}
|
||||
className={classNames(
|
||||
"flex items-center text-[#3c85ee] font-medium no-underline animate",
|
||||
isMedia ? "text-[#3c85ee]" : "text-[#637381]"
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<PhotoIcon className="w-[20px] h-[20px]" />
|
||||
</span>
|
||||
<p className="ml-3">Media</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="border-t border-[#E8EEF2] pr-[20px] pt-4">
|
||||
<ul className="m-0 list-none p-0 cursor-pointer ">
|
||||
<li>
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
{/* <div
|
||||
className="overlay"
|
||||
style={
|
||||
props.leftSectionMode === "open" || props.rightSectionMode === "open"
|
||||
? { display: "block" }
|
||||
: { display: "none" }
|
||||
}
|
||||
></div>
|
||||
<div className="small__switcher--content">
|
||||
<a onClick={props.switchLeftSectionMode} className="menu__button">
|
||||
<i className="fas fa-bars"></i>
|
||||
</a>
|
||||
<a onClick={props.switchRightSectionMode} className="image__viewer">
|
||||
<i className="fas fa-images"></i>
|
||||
</a>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex h-full"
|
||||
// style={
|
||||
// props.routeType === "search"
|
||||
// ? { flexDirection: "column" }
|
||||
// : { flexDirection: "row" }
|
||||
// }
|
||||
>
|
||||
{/* {true ? undefined : (
|
||||
<div className="file__control--panel empty__control--panel">
|
||||
<div className="file__get--started">
|
||||
<div className="get__started--image">
|
||||
<img src="/assets/get_startedfile.svg" alt="get" />
|
||||
</div>
|
||||
<h6>All your files in one place</h6>
|
||||
<p>Drag and drop a file to get started</p>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
{/* {props.routeType === "search" ? (
|
||||
<div
|
||||
className="file__control--panel folder__view"
|
||||
style={{ paddingBottom: "0", marginBottom: "-50px" }}
|
||||
>
|
||||
<div className="results__files">
|
||||
<h2>
|
||||
<span className="counter__result">
|
||||
{props.files.length + props.folders.length >= 50
|
||||
? "50+"
|
||||
: props.files.length + props.folders.length}
|
||||
</span>{" "}
|
||||
<span className="result__word">results</span> for{" "}
|
||||
<span className="result__search--word">
|
||||
{props.cachedSearch}
|
||||
</span>
|
||||
</h2>
|
||||
<p className="searching__result">
|
||||
You are searching in{" "}
|
||||
<span className="root__parent">
|
||||
{props.parent === "/"
|
||||
? "Home"
|
||||
: props.parentNameList.length !== 0
|
||||
? props.parentNameList[props.parentNameList.length - 1]
|
||||
: "Unknown"}
|
||||
</span>{" "}
|
||||
<span className="spacer">
|
||||
<img
|
||||
style={{
|
||||
height: "11px",
|
||||
marginTop: "2px",
|
||||
display: "none",
|
||||
}}
|
||||
src="/assets/smallspacer.svg"
|
||||
alt="spacer"
|
||||
/>
|
||||
</span>
|
||||
<span className="current__folder"></span>{" "}
|
||||
<a
|
||||
href="#"
|
||||
style={{ display: "none" }}
|
||||
className="search__filter--global"
|
||||
>
|
||||
Show results from everywhere
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : undefined} */}
|
||||
|
||||
<div className="flex h-full">
|
||||
{showPopup ? <PopupWindow /> : undefined}
|
||||
|
||||
{moverID.length === 0 ? undefined : <MoverMenu />}
|
||||
|
||||
<div className="flex flex-row h-screen w-screen pt-16">
|
||||
<LeftSection goHome={() => {}} />
|
||||
<LeftSection />
|
||||
|
||||
<DataForm />
|
||||
{!isMedia ? <DataForm /> : <Medias />}
|
||||
|
||||
<RightSection />
|
||||
</div>
|
||||
|
||||
@@ -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<MediaItemType> = ({ 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 (
|
||||
<div
|
||||
className={classNames(
|
||||
"h-[100px] bg-black overflow-hidden rounded-md cursor-pointer relative",
|
||||
{
|
||||
"border-4 border-[#3c85ee]": elementSelected || elementMultiSelected,
|
||||
}
|
||||
)}
|
||||
onClick={mediaItemClick}
|
||||
onContextMenu={onContextMenu}
|
||||
onTouchStart={onTouchStart}
|
||||
onTouchMove={onTouchMove}
|
||||
onTouchEnd={onTouchEnd}
|
||||
>
|
||||
{contextMenuState.selected && (
|
||||
<div onClick={clickStopPropagation}>
|
||||
<ContextMenu
|
||||
gridMode={true}
|
||||
quickItemMode={false}
|
||||
contextSelected={contextMenuState}
|
||||
closeContext={closeContextMenu}
|
||||
file={file}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{file.metadata.isVideo && (
|
||||
<div className="w-full h-full absolute flex justify-center items-center text-white">
|
||||
<PlayButtonIcon className="w-[50px] h-[50px]" />
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
className="object-cover h-full w-full"
|
||||
src={image}
|
||||
onError={imageOnError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MediaItem;
|
||||
@@ -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 (
|
||||
<div className="w-full p-[17px_15px] mobileMode:p-[17px_40px] overflow-y-scroll">
|
||||
<div className="fixed bottom-0 flex justify-center items-center right-0 left-0">
|
||||
<MultiSelectBar />
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"grid grid-cols-[repeat(auto-fill,minmax(100px,1fr))] gap-[10px]"
|
||||
)}
|
||||
>
|
||||
{files?.pages.map((filePage, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{filePage.map((file) => (
|
||||
<MediaItem file={file} key={file._id} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div ref={sentinelRef} className="h-1"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Medias;
|
||||
@@ -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 (
|
||||
<div className="flex justify-center items-center">
|
||||
<div className="border border-[#ebe9f9] bg-[#ebe9f9] rounded-full p-2 px-5 text-black text-sm mb-4 max-w-[600px] w-full mt-4">
|
||||
<div className="border border-[#ebe9f9] bg-[#ebe9f9] rounded-full p-2 px-5 text-black text-sm mb-4 max-w-[600px] w-full mt-4 min-w-[300px] shadow-lg">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img
|
||||
@@ -109,7 +125,12 @@ const MultiSelectBar = () => {
|
||||
className="ml-4 cursor-pointer"
|
||||
onClick={trashItems}
|
||||
/>
|
||||
<Moveicon className="ml-4 cursor-pointer" onClick={() => {}} />
|
||||
{!isMedia && (
|
||||
<Moveicon
|
||||
className="ml-4 cursor-pointer"
|
||||
onClick={() => {}}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{isTrash && (
|
||||
|
||||
@@ -39,7 +39,7 @@ const QuickAccess = memo(() => {
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
"grid animate-movement grid-cols-[repeat(auto-fit,minmax(40%,45%))] xs:grid-cols-[repeat(auto-fit,minmax(185px,185px))] gap-[20px]",
|
||||
"grid animate-movement grid-cols-[repeat(auto-fit,minmax(40%,45%))] xs:grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-[16px]",
|
||||
quickfilesList?.length > 1
|
||||
? "justify-center xs:justify-normal"
|
||||
: "justify-normal",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<form
|
||||
onSubmit={onSearch}
|
||||
@@ -102,7 +119,7 @@ const SearchBar = memo(() => {
|
||||
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]"
|
||||
/>
|
||||
<div
|
||||
|
||||
+20
-10
@@ -20,7 +20,8 @@ export const useFiles = (enabled = true) => {
|
||||
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,
|
||||
|
||||
+23
-12
@@ -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,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const PhotoIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}>
|
||||
<title>image</title>
|
||||
<path
|
||||
d="M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhotoIcon;
|
||||
@@ -66,6 +66,24 @@ const AppRouter = () => {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
key={1}
|
||||
path="/media"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Homepage2 />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
key={1}
|
||||
path="/search-media/:query"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Homepage2 />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
key={1}
|
||||
path="/trash"
|
||||
|
||||
Reference in New Issue
Block a user