From 4cccd43677c28fd4ae539cee1ef77cc5b7d2d64b Mon Sep 17 00:00:00 2001 From: subnub Date: Sat, 22 Jun 2024 03:46:42 -0400 Subject: [PATCH] added more hooks --- backend/controllers/file.ts | 2 + .../ChunkService/FileSystemService.ts | 5 +- src/api/filesAPI.ts | 35 ++++ src/components/Dataform/DataForm.jsx | 13 +- .../NewContextMenu/NewContextMenu.jsx | 195 ++++++++++-------- src/components/NewContextMenu/index.jsx | 114 +++++----- .../QuickAccessItem/QuickAccessItem.jsx | 120 ++++------- src/components/QuickAccessItem/index.jsx | 133 +----------- src/hooks/contextMenu.ts | 33 +++ src/hooks/files.ts | 78 ++++++- src/utils/files.ts | 59 ++++++ 11 files changed, 430 insertions(+), 357 deletions(-) create mode 100644 src/hooks/contextMenu.ts create mode 100644 src/utils/files.ts diff --git a/backend/controllers/file.ts b/backend/controllers/file.ts index d880942..6a42d5b 100644 --- a/backend/controllers/file.ts +++ b/backend/controllers/file.ts @@ -495,6 +495,8 @@ class FileController { const userID = req.user._id; const fileID = req.body.id; + console.log("id", fileID); + await this.chunkService.deleteFile(userID, fileID); res.send(); diff --git a/backend/services/ChunkService/FileSystemService.ts b/backend/services/ChunkService/FileSystemService.ts index 01c5a36..91990e8 100644 --- a/backend/services/ChunkService/FileSystemService.ts +++ b/backend/services/ChunkService/FileSystemService.ts @@ -172,7 +172,8 @@ class FileSystemService implements ChunkInterface { const filePath = currentFile.metadata.filePath!; - const IV = currentFile.metadata.IV.buffer as Buffer; + const IV = currentFile.metadata.IV; + console.log("iv", IV); const readStream = fs.createReadStream(filePath); @@ -260,7 +261,7 @@ class FileSystemService implements ChunkInterface { if (!file) throw new NotFoundError("File Thumbnail Not Found"); const password = user.getEncryptionKey(); - const IV = file.metadata.IV.buffer as Buffer; + const IV = file.metadata.IV; if (!password) throw new ForbiddenError("Invalid Encryption Key"); diff --git a/src/api/filesAPI.ts b/src/api/filesAPI.ts index e753cce..5b229dc 100644 --- a/src/api/filesAPI.ts +++ b/src/api/filesAPI.ts @@ -11,6 +11,8 @@ interface QueryKeyParams { startAt?: boolean; } +// GET + export const getFilesList = async ({ queryKey, pageParam, @@ -47,3 +49,36 @@ export const getQuickFilesList = async () => { }); return response.data; }; + +export const getFileThumbnail = async (thumbnailID: string) => { + const url = `http://localhost:5173/api/file-service/thumbnail/${thumbnailID}`; + const config = { + responseType: "arraybuffer", + }; + + const response = await axios.get(url, config); + + const imgFile = new Blob([response.data]); + const imgUrl = URL.createObjectURL(imgFile); + + return imgUrl; +}; + +// PATCH +export const renameFile = async (fileID: string, name: string) => { + const response = await axios.patch(`/file-service/rename`, { + id: fileID, + title: name, + }); + return response.data; +}; + +// DELETE +export const deleteFile = async (fileID: string) => { + const response = await axios.delete(`/file-service/remove`, { + data: { + id: fileID, + }, + }); + return response.data; +}; diff --git a/src/components/Dataform/DataForm.jsx b/src/components/Dataform/DataForm.jsx index 18e23f4..913c7f0 100644 --- a/src/components/Dataform/DataForm.jsx +++ b/src/components/Dataform/DataForm.jsx @@ -13,8 +13,11 @@ import { useFiles } from "../../hooks/files"; import { useFolders } from "../../hooks/folders"; const DataForm = (props) => { - const params = useParams(); - const { data: files, fetchNextPage: filesFetchNextPage } = useFiles(); + const { + data: files, + fetchNextPage: filesFetchNextPage, + invalidateFilesCache, + } = useFiles(); const { data: folders } = useFolders(); return ( @@ -39,6 +42,12 @@ const DataForm = (props) => { > Next page +
{ + const { invalidateFilesCache } = useFiles(); + const { invalidateQuickFilesCache } = useQuickFiles(); + const liClassname = + "flex w-full px-[20px] py-[12px] items-center font-normal justify-start no-underline transition-all duration-400 ease-in-out hover:bg-[#f6f5fd] hover:text-[#3c85ee] hover:font-medium"; + const spanClassname = "inline-flex mr-[18px]"; - render() { - return ( - // settings__folder__margin -
- +
+ ); +}; export default NewContextMenu; diff --git a/src/components/NewContextMenu/index.jsx b/src/components/NewContextMenu/index.jsx index 8508844..b442ade 100644 --- a/src/components/NewContextMenu/index.jsx +++ b/src/components/NewContextMenu/index.jsx @@ -1,73 +1,73 @@ import React from "react"; -import {connect} from "react-redux"; +import { connect } from "react-redux"; import { setShareSelected } from "../../actions/selectedItem"; import NewContextMenu from "./NewContextMenu"; class NewContextMenuContainer extends React.Component { + constructor(props) { + super(props); - constructor(props) { - super(props); + this.wrapperRef = React.createRef(); + } - this.wrapperRef = React.createRef(); + componentDidMount = () => { + document.addEventListener("mousedown", this.handleClickOutside); + }; + + componentWillUnmount = () => { + document.removeEventListener("mousedown", this.handleClickOutside); + }; + + componentDidUpdate = () => {}; + + handleClickOutside = () => { + if ( + this.props.contextSelected.selected && + this.wrapperRef && + !this.wrapperRef.current.contains(event.target) + ) { + this.props.closeContext(); } + }; - componentDidMount = () => { + startFileDownload = () => { + console.log("start download file", this.props.file._id); + this.props.downloadFile(this.props.file._id, this.props.file); + }; - document.addEventListener("mousedown", this.handleClickOutside) - } + startRenameFile = async (e) => { + console.log("rename file click"); + this.props.changeEditNameMode(); + }; - componentWillUnmount = () => { + startDeleteFile = async () => { + this.props.changeDeleteMode(); + }; - document.removeEventListener("mousedown", this.handleClickOutside) - } + startShareFile = () => { + this.props.dispatch(setShareSelected({ ...this.props.file })); + }; - componentDidUpdate = () => { - } - - handleClickOutside = () => { - if (this.props.contextSelected && this.wrapperRef && !this.wrapperRef.current.contains(event.target)) { - this.props.closeContext(); - } - } - - startFileDownload = () => { - console.log("start download file", this.props.file._id); - this.props.downloadFile(this.props.file._id, this.props.file) - } - - startRenameFile = async(e) => { - console.log("rename file click") - this.props.changeEditNameMode(); - } - - startDeleteFile = async() => { - this.props.changeDeleteMode() - } - - startShareFile = () => { - this.props.dispatch(setShareSelected({...this.props.file})) - } - - - stopPropagation = (e) => { - e.stopPropagation() - if (this.props.quickItemMode || this.props.gridMode) this.props.closeContext(); - } - - render() { - - return - } + stopPropagation = (e) => { + e.stopPropagation(); + if (this.props.quickItemMode || this.props.gridMode) + this.props.closeContext(); + }; + render() { + return ( + + ); + } } -export default connect()(NewContextMenuContainer); \ No newline at end of file +export default connect()(NewContextMenuContainer); diff --git a/src/components/QuickAccessItem/QuickAccessItem.jsx b/src/components/QuickAccessItem/QuickAccessItem.jsx index 3cab419..92718f5 100644 --- a/src/components/QuickAccessItem/QuickAccessItem.jsx +++ b/src/components/QuickAccessItem/QuickAccessItem.jsx @@ -1,117 +1,79 @@ import capitalize from "../../utils/capitalize"; import moment from "moment"; -import React, { useMemo } from "react"; +import React, { useMemo, useState } from "react"; import NewContextMenu from "../NewContextMenu"; import classNames from "classnames"; +import { getFileColor, getFileExtension } from "../../utils/files"; +import { useThumbnail } from "../../hooks/files"; +import { useContextMenu } from "../../hooks/contextMenu"; const QuickAccessItem = (props) => { - const fileExtension = useMemo(() => { - const filenameSplit = props.filename.split("."); + const { image, hasThumbnail, imageOnError } = useThumbnail( + props.metadata.hasThumbnail, + props.metadata.thumbnailID + ); - if (filenameSplit.length > 1) { - let extension = filenameSplit[filenameSplit.length - 1]; + const { onContextMenu, closeContextMenu, ...contextMenuState } = + useContextMenu(); - if (extension.length > 4) - extension = - extension.substring(0, 3) + - extension.substring(extension.length - 1, extension.length); + const fileExtension = useMemo( + () => getFileExtension(props.filename), + [props.filename] + ); - return extension.toUpperCase(); - } else { - return "UNK"; - } - }, [props.filename]); - - const imageColor = useMemo(() => { - const letter = fileExtension.substring(0, 1).toUpperCase(); - - const colorObj = { - A: "#e53935", - B: "#d81b60", - C: "#8e24aa", - D: "#5e35b1", - E: "#3949ab", - F: "#1e88e5", - G: "#039be5", - H: "#00acc1", - I: "#00897b", - J: "#43a047", - K: "#fdd835", - L: "#ffb300", - M: "#fb8c00", - N: "#f4511e", - O: "#d32f2f", - P: "#c2185b", - Q: "#7b1fa2", - R: "#512da8", - S: "#303f9f", - T: "#1976d2", - U: "#0288d1", - V: "#0097a7", - W: "#0097a7", - X: "#00796b", - Y: "#388e3c", - Z: "#fbc02d", - }; - - if (colorObj[letter]) { - return colorObj[letter]; - } else { - return "#03a9f4"; - } - }, [props.filename]); + const imageColor = useMemo( + () => getFileColor(props.filename), + [props.filename] + ); const elementSelected = useMemo( () => `quick-${props._id}` === props.selected, [props._id, props.selected] ); - console.log("thumbnail", props.state); - return (
{ props.fileClick(props._id, props, true); }} - onContextMenu={props.selectContext} + onContextMenu={onContextMenu} onTouchStart={props.onTouchStart} onTouchEnd={props.onTouchEnd} onTouchMove={props.onTouchMove} > -
- -
+ {contextMenuState.selected && ( +
+ +
+ )}
- {props.state.hasThumbnail ? ( + {hasThumbnail ? ( ) : ( { /> )} - {!props.state.hasThumbnail && ( + {!hasThumbnail && (

{fileExtension}

diff --git a/src/components/QuickAccessItem/index.jsx b/src/components/QuickAccessItem/index.jsx index 54b4360..ff3072c 100644 --- a/src/components/QuickAccessItem/index.jsx +++ b/src/components/QuickAccessItem/index.jsx @@ -25,7 +25,6 @@ class QuickAccessItemContainer extends React.Component { this.lastTouch = 0; this.state = { - contextMenuPos: {}, image: "/images/file-svg.svg", imageClassname: "noSelect file__item-no-thumbnail", contextSelected: false, @@ -33,99 +32,6 @@ class QuickAccessItemContainer extends React.Component { }; } - componentDidMount = () => { - const hasThumbnail = this.props.metadata.hasThumbnail; - console.log("has", hasThumbnail); - - if (hasThumbnail && !this.failedToLoad) { - this.getThumbnail(); - } - }; - - closeContext = () => { - this.setState(() => { - return { - ...this.state, - contextSelected: false, - }; - }); - }; - - selectContext = (e) => { - if (e) e.stopPropagation(); - if (e) e.preventDefault(); - - if (mobilecheck()) { - this.props.dispatch(setMobileContextMenu(true, this.props)); - return; - } - - this.setState(() => { - return { - ...this.state, - contextSelected: { - width: e.clientX, - height: e.clientY, - }, - }; - }); - }; - - getThumbnail = async () => { - const thumbnailID = this.props.metadata.thumbnailID; - const imageClassname = "noSelect"; - - // GOOGLE DRIVE IMAGE - if (this.props.metadata.drive) { - return await this.setState(() => ({ - ...this.state, - image: this.props.metadata.thumbnailID, - imageClassname: imageClassname, - })); - } - - const config = { - responseType: "arraybuffer", - }; - - await this.setState(() => ({ - ...this.state, - image: "/images/file-svg.svg", - imageClassname: "noSelect file__item-no-thumbnail", - })); - - const url = `http://localhost:5173/api/file-service/thumbnail/${thumbnailID}`; - - axios - .get(url, config) - .then((results) => { - const imgFile = new Blob([results.data]); - const imgUrl = URL.createObjectURL(imgFile); - console.log("got image"); - - this.setState(() => ({ - ...this.state, - image: imgUrl, - imageClassname: imageClassname, - hasThumbnail: true, - })); - }) - .catch((err) => { - console.log(err); - }); - }; - - thumbnailOnError = (e) => { - console.log("thumbnail on error", e); - - this.setState(() => ({ - ...this.state, - image: "/images/file-svg.svg", - imageClassname: "noSelect file__item-no-thumbnail", - hasThumbnail: false, - })); - }; - onTouchStart = () => { const date = new Date(); this.lastTouch = date.getTime(); @@ -146,43 +52,10 @@ class QuickAccessItemContainer extends React.Component { this.lastTouch = 0; if (difference > 500) { - this.selectContext(); + // this.selectContext(); } }; - getContextMenu = (e) => { - if (e) e.preventDefault(); - - const isMobile = mobileCheck(); - - const windowX = window.innerWidth; - const windowY = window.innerHeight; - - let styleObj = { right: 0, left: 0, top: "-3px", bottom: 0 }; - - if (isMobile) { - styleObj = { bottom: 0, left: "2px" }; - } else { - const clientY = e.nativeEvent.clientY; - const clientX = e.nativeEvent.clientX; - - if (clientX > windowX / 2) { - styleObj = { ...styleObj, left: "unset", right: 0 }; - } else { - styleObj = { ...styleObj, left: 0, right: "unset" }; - } - } - - this.setState(() => ({ - ...this.state, - contextMenuPos: styleObj, - })); - - this.props.dispatch(setSelected("quick-" + this.props._id)); - this.props.dispatch(setRightSelected("quick-" + this.props._id)); - this.props.dispatch(setLastSelected(0)); - }; - changeEditNameMode = async () => { let inputValue = this.props.filename; @@ -257,17 +130,13 @@ class QuickAccessItemContainer extends React.Component { render() { return ( diff --git a/src/hooks/contextMenu.ts b/src/hooks/contextMenu.ts new file mode 100644 index 0000000..8197633 --- /dev/null +++ b/src/hooks/contextMenu.ts @@ -0,0 +1,33 @@ +import { useState } from "react"; + +export const useContextMenu = () => { + const [contextData, setContextData] = useState({ + selected: false, + X: 0, + Y: 0, + lastTouched: 0, + }); + + const onContextMenu = (e: React.MouseEvent) => { + if (e) e.stopPropagation(); + if (e) e.preventDefault(); + setContextData({ + ...contextData, + selected: true, + X: e.clientX, + Y: e.clientY, + }); + }; + + const closeContextMenu = () => { + setContextData({ + ...contextData, + selected: false, + X: 0, + Y: 0, + lastTouched: 0, + }); + }; + + return { ...contextData, onContextMenu, closeContextMenu }; +}; diff --git a/src/hooks/files.ts b/src/hooks/files.ts index 27ff580..b9c42d0 100644 --- a/src/hooks/files.ts +++ b/src/hooks/files.ts @@ -1,6 +1,11 @@ -import { useInfiniteQuery, useQuery } from "react-query"; +import { useInfiniteQuery, useQuery, useQueryClient } from "react-query"; import { useParams } from "react-router-dom"; -import { getFilesList, getQuickFilesList } from "../api/filesAPI"; +import { + getFileThumbnail, + getFilesList, + getQuickFilesList, +} from "../api/filesAPI"; +import { useCallback, useEffect, useState } from "react"; export const useFiles = () => { const params = useParams(); @@ -17,6 +22,7 @@ export const useFiles = () => { getFilesList, { getNextPageParam: (lastPage, pages) => { + console.log("pages", pages); const lastElement = lastPage[lastPage.length - 1]; if (!lastElement) return undefined; return { @@ -27,15 +33,79 @@ export const useFiles = () => { } ); + const filesReactClientQuery = useQueryClient(); + + const invalidateFilesCache = () => { + filesReactClientQuery.invalidateQueries({ + queryKey: [ + "files", + { + parent: params.id || "/", + search: "", + sortBy: undefined, + limit: undefined, + }, + ], + }); + }; + const testFunction = () => { console.log("this is a test function"); }; - return { ...filesReactQuery, testFunction }; + return { ...filesReactQuery, testFunction, invalidateFilesCache }; }; export const useQuickFiles = () => { const quickFilesQuery = useQuery("quickFiles", getQuickFilesList); - return quickFilesQuery; + const quickFilesReactClientQuery = useQueryClient(); + + const invalidateQuickFilesCache = () => { + quickFilesReactClientQuery.invalidateQueries({ + queryKey: "quickFiles", + }); + }; + + return { ...quickFilesQuery, invalidateQuickFilesCache }; +}; + +interface thumbnailState { + hasThumbnail: boolean; + image: null | string; +} + +export const useThumbnail = (hasThumbnail: boolean, thumbnailID: string) => { + const [state, setState] = useState({ + hasThumbnail: false, + image: null, + }); + + const getThumbnail = useCallback(async () => { + try { + const thumbnailData = await getFileThumbnail(thumbnailID); + setState({ + hasThumbnail: true, + image: thumbnailData, + }); + } catch (e) { + console.log("error getting thumbnail data", e); + imageOnError(); + } + }, [thumbnailID]); + + const imageOnError = () => { + setState({ + hasThumbnail: false, + image: null, + }); + }; + + useEffect(() => { + if (!hasThumbnail) return; + + getThumbnail(); + }, [hasThumbnail, getThumbnail]); + + return { ...state, imageOnError }; }; diff --git a/src/utils/files.ts b/src/utils/files.ts new file mode 100644 index 0000000..46043e4 --- /dev/null +++ b/src/utils/files.ts @@ -0,0 +1,59 @@ +export const getFileExtension = (filename: string) => { + const filenameSplit = filename.split("."); + + if (filenameSplit.length > 1) { + let extension = filenameSplit[filenameSplit.length - 1]; + + if (extension.length > 4) + extension = + extension.substring(0, 3) + + extension.substring(extension.length - 1, extension.length); + + return extension.toUpperCase(); + } else { + return "UNK"; + } +}; + +type ColorMap = { + [key: string]: string; +}; + +export const getFileColor = (filename: string) => { + const letter = getFileExtension(filename).substring(0, 1).toUpperCase(); + + const colorMap: ColorMap = { + A: "#e53935", + B: "#d81b60", + C: "#8e24aa", + D: "#5e35b1", + E: "#3949ab", + F: "#1e88e5", + G: "#039be5", + H: "#00acc1", + I: "#00897b", + J: "#43a047", + K: "#fdd835", + L: "#ffb300", + M: "#fb8c00", + N: "#f4511e", + O: "#d32f2f", + P: "#c2185b", + Q: "#7b1fa2", + R: "#512da8", + S: "#303f9f", + T: "#1976d2", + U: "#0288d1", + V: "#0097a7", + W: "#0097a7", + X: "#00796b", + Y: "#388e3c", + Z: "#fbc02d", + }; + + if (colorMap[letter]) { + return colorMap[letter]; + } else { + return "#03a9f4"; + } +};