diff --git a/backend/controllers/folder.ts b/backend/controllers/folder.ts index 163cf40..e6d8a69 100644 --- a/backend/controllers/folder.ts +++ b/backend/controllers/folder.ts @@ -26,15 +26,17 @@ class FolderController { this.chunkService = chunkService; } - uploadFolder = async (req: RequestType, res: Response) => { + createFolder = async (req: RequestType, res: Response) => { if (!req.user) { return; } try { - const data = req.body; + const userID = req.user._id; + const name = req.body.name; + const parent = req.body.parent; - const folder = await folderService.uploadFolder(data); + const folder = await folderService.createFolder(userID, name, parent); res.send(folder); } catch (e: unknown) { diff --git a/backend/express-routers/folder.ts b/backend/express-routers/folder.ts index e56dd95..3de4d6c 100644 --- a/backend/express-routers/folder.ts +++ b/backend/express-routers/folder.ts @@ -17,7 +17,7 @@ if (env.dbType === "fs") { const router = Router(); -router.post("/folder-service/upload", auth, folderController.uploadFolder); +router.post("/folder-service/create", auth, folderController.createFolder); router.delete("/folder-service/remove", auth, folderController.deleteFolder); diff --git a/backend/models/folder.ts b/backend/models/folder.ts index ac74874..5c48817 100644 --- a/backend/models/folder.ts +++ b/backend/models/folder.ts @@ -1,43 +1,45 @@ -import mongoose, {Document} from "mongoose"; +import mongoose, { Document } from "mongoose"; -const folderSchema = new mongoose.Schema({ - +const folderSchema = new mongoose.Schema( + { name: { - type: String, - required: true, + type: String, + required: true, }, - + parent: { - type: String, - required: true, + type: String, + required: true, }, - + owner: { - type: String, - required: true + type: String, + required: true, }, parentList: { - type: Array, - required:true + type: Array, + required: true, }, - personalFolder: Boolean - -}, { - timestamps: true -}) + personalFolder: Boolean, + }, + { + timestamps: true, + } +); -export interface FolderInterface extends Document { - name: string, - parent: string, - owner: string, - createdAt: Date, - updatedAt: Date, - parentList: string[], - _doc?: any, - personalFolder?: boolean +export interface FolderInterface + extends mongoose.Document { + name: string; + parent: string; + owner: string; + createdAt: Date; + updatedAt: Date; + parentList: string[]; + _doc?: any; + personalFolder?: boolean; } const Folder = mongoose.model("Folder", folderSchema); -export default Folder; \ No newline at end of file +export default Folder; diff --git a/backend/services/ChunkService/FileSystemService.ts b/backend/services/ChunkService/FileSystemService.ts index 1d11f59..d4b0c16 100644 --- a/backend/services/ChunkService/FileSystemService.ts +++ b/backend/services/ChunkService/FileSystemService.ts @@ -37,7 +37,7 @@ class FileSystemService implements ChunkInterface { constructor() {} uploadFile = async (user: UserInterface, busboy: any, req: Request) => { - console.log("upeload file"); + console.log("upeload file2"); const password = user.getEncryptionKey(); if (!password) throw new ForbiddenError("Invalid Encryption Key"); @@ -51,23 +51,35 @@ class FileSystemService implements ChunkInterface { const { file, filename: fileInfo, formData } = await getBusboyData(busboy); const filename = fileInfo.filename; - - console.log("1filename", filename); - + console.log("1"); const parent = formData.get("parent") || "/"; - const parentList = formData.get("parentList") || "/"; const size = formData.get("size") || ""; const personalFile = formData.get("personal-file") ? true : false; let hasThumbnail = false; let thumbnailID = ""; const isVideo = videoChecker(filename); + console.log("2", parent, typeof parent); + + const parentList = []; + + if (parent !== "/") { + const parentFolder = await dbUtilsFolder.getFolderInfo( + parent, + user._id.toString() + ); + parentList.push(...parentFolder.parentList, parentFolder._id); + } else { + parentList.push("/"); + } + + console.log("parent list", parentList); const systemFileName = uuid.v4(); const metadata = { owner: user._id, parent, - parentList, + parentList: parentList.toString(), hasThumbnail, thumbnailID, isVideo, @@ -174,8 +186,12 @@ class FileSystemService implements ChunkInterface { const filePath = currentFile.metadata.filePath!; - const IV = currentFile.metadata.IV; - console.log("iv", IV); + // TODO: I believe this has to do with using conn.db instead of the mongoose driver + // We should switch to the mongoose driver + let IV = currentFile.metadata.IV; + if (!(IV instanceof Buffer)) { + IV = IV.buffer; + } const readStream = fs.createReadStream(filePath); @@ -233,7 +249,11 @@ class FileSystemService implements ChunkInterface { throw new ForbiddenError("Thumbnail Unauthorized Error"); } - const iv = thumbnail.IV!; + // TODO: Fix this type + let iv = thumbnail.IV as any; + if (!(iv instanceof Buffer)) { + iv = iv.buffer; + } const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); diff --git a/backend/services/ChunkService/utils/getBusboyData.ts b/backend/services/ChunkService/utils/getBusboyData.ts index 598d2e6..bd6a2fd 100644 --- a/backend/services/ChunkService/utils/getBusboyData.ts +++ b/backend/services/ChunkService/utils/getBusboyData.ts @@ -13,9 +13,11 @@ const getBusboyData = (busboy: any) => { const formData = new Map(); busboy.on("field", (field: any, val: any) => { - console.log("field", field, val); - - formData.set(field, val); + console.log("field", field, val, typeof val); + // ????? why does it make undefined a string? + if (typeof val !== "string" || val !== "undefined") { + formData.set(field, val); + } }); busboy.on( diff --git a/backend/services/FolderService/index.ts b/backend/services/FolderService/index.ts index 5dfa7a3..a65d6d0 100644 --- a/backend/services/FolderService/index.ts +++ b/backend/services/FolderService/index.ts @@ -1,4 +1,4 @@ -import Folder from "../../models/folder"; +import Folder, { FolderInterface } from "../../models/folder"; import InternalServerError from "../../utils/InternalServerError"; import NotFoundError from "../../utils/NotFoundError"; import UtilsFile from "../../db/utils/fileUtils"; @@ -17,8 +17,27 @@ const utilsFile = new UtilsFile(); const utilsFolder = new UtilsFolder(); class FolderService { - uploadFolder = async (data: any) => { - const folder = new Folder(data); + createFolder = async (userID: string, name: string, parent: string) => { + const newFolderParentList = []; + if (parent && parent !== "/") { + const parentFolder = ( + await utilsFolder.getFolderInfo(parent, userID) + ).toObject() as FolderInterface | null; + if (!parentFolder) throw new Error("Parent not found"); + newFolderParentList.push( + ...parentFolder.parentList, + parentFolder._id?.toString() + ); + } else { + newFolderParentList.push("/"); + } + const newFolderData = { + name, + parent: parent || "/", + parentList: newFolderParentList, + owner: userID, + }; + const folder = new Folder(newFolderData); await folder.save(); diff --git a/src/actions/files.js b/src/actions/files.js index 476754a..3957b76 100644 --- a/src/actions/files.js +++ b/src/actions/files.js @@ -12,6 +12,7 @@ import { setFolders } from "./folders"; import reduceQuickItemList from "../utils/reduceQuickItemList"; import http from "http"; import https from "https"; +import { addFileUploadCancelToken } from "../utils/cancelTokenManager"; let cachedResults = {}; @@ -362,26 +363,8 @@ export const addFile = (file) => ({ file, }); -export const startAddFile = ( - uploadInput, - parent, - parentList, - storageSwitcherType -) => { +export const startAddFile = (uploadInput, parent) => { return (dispatch, getState) => { - if (env.uploadMode === "") { - console.log("No Storage Accounts!"); - Swal.fire({ - icon: "error", - title: "No Storage Accounts Active", - text: "Go to settings to add a storage account", - }); - return; - } - - // Store the parent, incase it changes. - const prevParent = getState().parent.parent; - for (let i = 0; i < uploadInput.files.length; i++) { const currentFile = uploadInput.files[i]; const currentID = uuid(); @@ -406,13 +389,15 @@ export const startAddFile = ( cancelToken: source.token, }; + addFileUploadCancelToken(currentID, source); + dispatch( addUpload({ id: currentID, progress: 0, name: currentFile.name, completed: false, - source, + // source, // TODO: Fix canceling uploads canceled: false, size: currentFile.size, }) @@ -424,7 +409,6 @@ export const startAddFile = ( data.append("filename", currentFile.name); data.append("parent", parent); - data.append("parentList", parentList); data.append("currentID", currentID); data.append("size", currentFile.size); if (storageType === "s3") data.append("personal-file", true); @@ -440,21 +424,16 @@ export const startAddFile = ( axios .post(url, data, config) .then(function (response) { - const currentParent = getState().parent.parent; - // This can change by the time the file uploads - if (prevParent === currentParent) { - dispatch(addFile(response.data)); - } - - dispatch(addQuickFile(response.data)); + console.log("uploaded"); + // dispatch(addQuickFile(response.data)); dispatch(editUpload(currentID, 100, true)); - dispatch(resetSelected()); - dispatch(startSetStorage()); + // dispatch(resetSelected()); + // dispatch(startSetStorage()); - cachedResults = {}; + // cachedResults = {}; }) .catch(function (error) { - console.log(error); + console.log("uploadError", error); dispatch(cancelUpload(currentID)); }); } diff --git a/src/api/filesAPI.ts b/src/api/filesAPI.ts index 6401166..968055f 100644 --- a/src/api/filesAPI.ts +++ b/src/api/filesAPI.ts @@ -14,7 +14,7 @@ interface QueryKeyParams { // GET -export const getFilesList = async ({ +export const getFilesListAPI = async ({ queryKey, pageParam, }: QueryFunctionContext<[string, QueryKeyParams]>) => { @@ -42,7 +42,7 @@ export const getFilesList = async ({ return response.data; }; -export const getQuickFilesList = async () => { +export const getQuickFilesListAPI = async () => { const response = await axios.get(`/file-service/quick-list`, { params: { limit: 20, @@ -51,7 +51,7 @@ export const getQuickFilesList = async () => { return response.data; }; -export const downloadFile = async (fileID: string) => { +export const downloadFileAPI = async (fileID: string) => { await getUserToken(); // TODO: Change this @@ -65,7 +65,7 @@ export const downloadFile = async (fileID: string) => { link.click(); }; -export const getFileThumbnail = async (thumbnailID: string) => { +export const getFileThumbnailAPI = async (thumbnailID: string) => { // TODO: Change this const url = `http://localhost:5173/api/file-service/thumbnail/${thumbnailID}`; const config = { @@ -81,7 +81,7 @@ export const getFileThumbnail = async (thumbnailID: string) => { }; // PATCH -export const renameFile = async (fileID: string, name: string) => { +export const renameFileAPI = async (fileID: string, name: string) => { const response = await axios.patch(`/file-service/rename`, { id: fileID, title: name, @@ -90,7 +90,7 @@ export const renameFile = async (fileID: string, name: string) => { }; // DELETE -export const deleteFile = async (fileID: string) => { +export const deleteFileAPI = async (fileID: string) => { const response = await axios.delete(`/file-service/remove`, { data: { id: fileID, diff --git a/src/api/foldersAPI.ts b/src/api/foldersAPI.ts index baa9cde..13bf966 100644 --- a/src/api/foldersAPI.ts +++ b/src/api/foldersAPI.ts @@ -10,7 +10,7 @@ interface QueryKeyParams { // GET -export const getFoldersList = async ({ +export const getFoldersListAPI = async ({ queryKey, }: QueryFunctionContext<[string, QueryKeyParams]>) => { const [_key, { parent, search, sortBy, limit }] = queryKey; @@ -25,7 +25,7 @@ export const getFoldersList = async ({ return response.data; }; -export const getFolderInfo = async ({ +export const getFolderInfoAPI = async ({ queryKey, }: QueryFunctionContext<[string, { id: string | undefined }]>) => { const [_key, { id }] = queryKey; @@ -34,6 +34,16 @@ export const getFolderInfo = async ({ return response.data; }; +// POST + +export const createFolderAPI = async (name: string, parent?: string) => { + const response = await axios.post("/folder-service/create", { + name, + parent, + }); + return response.data; +}; + // PATCH export const renameFolder = async (folderID: string, name: string) => { diff --git a/src/components/AddNewDropdown/index.tsx b/src/components/AddNewDropdown/index.tsx new file mode 100644 index 0000000..2e794cd --- /dev/null +++ b/src/components/AddNewDropdown/index.tsx @@ -0,0 +1,87 @@ +import { useParams } from "react-router-dom"; +import { createFolderAPI } from "../../api/foldersAPI"; +import { useFoldersClient } from "../../hooks/folders"; +import { useClickOutOfBounds } from "../../hooks/utils"; +import { showCreateFolderPopup } from "../../popups/folder"; +import { useAppDispatch } from "../../hooks/store"; +import { startAddFile } from "../../actions/files"; +import { useRef } from "react"; + +const AddNewDropdown = (props: any) => { + const params = useParams(); + const { invalidateFoldersCache } = useFoldersClient(); + const { wrapperRef } = useClickOutOfBounds(props.closeDropdown); + const uploadRef = useRef(""); + const dispatch = useAppDispatch(); + + const createFolder = async () => { + props.closeDropdown(); + const folderName = await showCreateFolderPopup(); + + if (folderName === undefined || folderName === null) { + return; + } + + await createFolderAPI(folderName, params.id); + invalidateFoldersCache(); + }; + + const handleUpload = (e: any) => { + e.preventDefault(); + props.closeDropdown(); + console.log("handle upload"); + + dispatch(startAddFile(uploadRef.current, params.id)); + if (uploadRef && uploadRef.current) { + uploadRef.current = ""; + } + }; + + return ( +
+ +
+ ); +}; + +export default AddNewDropdown; diff --git a/src/components/ContextMenu/index.jsx b/src/components/ContextMenu/index.jsx index 714d34a..e4b4b35 100644 --- a/src/components/ContextMenu/index.jsx +++ b/src/components/ContextMenu/index.jsx @@ -1,20 +1,25 @@ import classNames from "classnames"; import React, { useEffect, useRef } from "react"; import Swal from "sweetalert2"; -import { deleteFile, renameFile, downloadFile } from "../../api/filesAPI"; +import { + deleteFileAPI, + renameFileAPI, + downloadFileAPI, +} from "../../api/filesAPI"; import { useFilesClient, useQuickFilesClient } from "../../hooks/files"; import { useFoldersClient } from "../../hooks/folders"; import { useDispatch } from "react-redux"; import { setMoverID } from "../../actions/mover"; import { setShareSelected } from "../../actions/selectedItem"; import { deleteFolder, renameFolder } from "../../api/foldersAPI"; +import { useClickOutOfBounds } from "../../hooks/utils"; const ContextMenu = (props) => { const { invalidateFilesCache } = useFilesClient(); const { invalidateFoldersCache } = useFoldersClient(); const { invalidateQuickFilesCache } = useQuickFilesClient(); + const { wrapperRef } = useClickOutOfBounds(props.closeContext); const dispatch = useDispatch(); - const wrapperRef = useRef(); 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"; const spanClassname = "inline-flex mr-[18px]"; @@ -33,7 +38,7 @@ const ContextMenu = (props) => { } }, }); - await renameFile(props.file._id, filename); + await renameFileAPI(props.file._id, filename); invalidateFilesCache(); invalidateQuickFilesCache(); } else { @@ -72,7 +77,7 @@ const ContextMenu = (props) => { }); if (result.value) { - await deleteFile(props.file._id); + await deleteFileAPI(props.file._id); invalidateFilesCache(); invalidateQuickFilesCache(); } @@ -109,35 +114,15 @@ const ContextMenu = (props) => { const downloadItem = () => { props.closeContext(); - downloadFile(props.file._id); + downloadFileAPI(props.file._id); }; - // TODO: Decide if we want it to close right on click or not - const outOfBoundsClickCheck = (e) => { - if (wrapperRef && !wrapperRef.current.contains(e.target)) { - props.closeContext(); - } - // setTimeout(() => { - // props.closeContext(); - // }, 150); - }; - - useEffect(() => { - document.addEventListener("mousedown", outOfBoundsClickCheck); - document.addEventListener("touchstart", outOfBoundsClickCheck); - - return () => { - document.removeEventListener("mousedown", outOfBoundsClickCheck); - document.removeEventListener("touchstart", outOfBoundsClickCheck); - }; - }, []); - return (
{ lastSelected.current = Date.now(); }, [ - dispatch, startSetSelectedItem, mobilecheck, navigate, diff --git a/src/components/Folders/index.jsx b/src/components/Folders/index.jsx index 3e9a31e..4873722 100644 --- a/src/components/Folders/index.jsx +++ b/src/components/Folders/index.jsx @@ -39,7 +39,7 @@ const Folders = memo(() => { } dispatch(setSortBy(newSortBy)); - }, [setSortBy, dispatch, sortBy]); + }, [sortBy]); const switchTypeOrderBy = useCallback( (e) => { @@ -63,7 +63,7 @@ const Folders = memo(() => { dispatch(setSortBy(newSortBy)); }, - [setSortBy, dispatch, sortBy] + [sortBy] ); return ( diff --git a/src/components/Homepage/Homepage2.txt b/src/components/Homepage/Homepage2.txt index 8557f32..305676e 100644 --- a/src/components/Homepage/Homepage2.txt +++ b/src/components/Homepage/Homepage2.txt @@ -11,8 +11,8 @@ import ShareModelWrapper from "../ShareModelWrapper"; import { useLocation, useNavigate, useParams } from "react-router-dom"; import { useSelector } from "react-redux"; import { useInfiniteQuery, useQuery } from "react-query"; -import { getFilesList } from "../../api/filesAPI"; -import { getFoldersList } from "../../api/foldersAPI"; +import { getFilesListAPI } from "../../api/filesAPI"; +import { getFoldersListAPI } from "../../api/foldersAPI"; import { useFiles } from "../../hooks/files"; const Homepage2 = () => { @@ -30,7 +30,7 @@ const Homepage2 = () => { limit: undefined, }, ], - queryFn: getFilesList, + queryFn: getFilesListAPI, initialPageParam: { startAtDate: undefined, startAtName: undefined, @@ -53,7 +53,7 @@ const Homepage2 = () => { limit: undefined, }, ], - getFoldersList + getFoldersListAPI ); const goHome = () => { diff --git a/src/components/LeftSection/LeftSection.jsx b/src/components/LeftSection/LeftSection.jsx index d316b22..e005ef9 100644 --- a/src/components/LeftSection/LeftSection.jsx +++ b/src/components/LeftSection/LeftSection.jsx @@ -1,118 +1,80 @@ -import FolderTree from "../FolderTree"; -import React from "react"; -import UploadStorageSwitcher from "../UploadStorageSwitcher"; -import mobilecheck from "../../utils/mobileCheck"; +import React, { useCallback, useRef, useState } from "react"; +import { 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 AddNewDropdown from "../AddNewDropdown"; -class LeftSection extends React.Component { - constructor(props) { - super(props); +const LeftSection = (props) => { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const addNewDisabled = useRef(false); - this.isMobile = mobilecheck(); - this.nonDropMode = window.localStorage.getItem("non_drop-mode"); - } + const openDropdown = useCallback(() => { + if (addNewDisabled.current) return; + setIsDropdownOpen(true); + }, []); - render() { - return ( -
-
-
- -

ADD NEW

- - dropselect - -
-
- -
-
-
- -
- + const closeDropdown = useCallback(() => { + addNewDisabled.current = true; + setIsDropdownOpen(false); + // Clicking out of bounds on the add new button will cause it to reopen + setTimeout(() => (addNewDisabled.current = false), 300); + }, []); + + return ( +
+
+
+ +

ADD NEW

+ + dropselect + +
+ {/* TODO: Remove this props */} + {isDropdownOpen && ( + + )} +
+
+ +
+
- ); - } -} +
+ ); +}; export default LeftSection; diff --git a/src/components/LeftSection/index.jsx b/src/components/LeftSection/index.jsx index f82b5bc..5ba00a0 100644 --- a/src/components/LeftSection/index.jsx +++ b/src/components/LeftSection/index.jsx @@ -1,139 +1,145 @@ import LeftSection from "./LeftSection"; -import {showAddOptions} from "../../actions/addOptions"; -import {startAddFile} from "../../actions/files"; -import {startAddFolder} from "../../actions/folders" +import { showAddOptions } from "../../actions/addOptions"; +import { startAddFile } from "../../actions/files"; +import { startAddFolder } from "../../actions/folders"; import Swal from "sweetalert2"; -import {connect} from "react-redux"; +import { connect } from "react-redux"; import React from "react"; import { openUploadOverlay, setLeftSectionMode } from "../../actions/main"; class LeftSectionContainer extends React.Component { + constructor(props) { + super(props); - constructor(props) { - super(props); + this.wrapperRef = React.createRef(); + this.uploadInput = React.createRef(); - this.wrapperRef = React.createRef(); - this.uploadInput = React.createRef(); + this.leftSectionRef = React.createRef(); - this.leftSectionRef = React.createRef() + this.state = { + open: false, + hideFolderTree: false, + }; + } - this.state = { - open: false, - hideFolderTree: false, + createFolder = async (e) => { + let inputValue = ""; + + const { value: folderName } = await Swal.fire({ + title: "Enter Folder Name", + input: "text", + inputValue: inputValue, + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return "Please Enter a Name"; } + }, + }); + + if (folderName === undefined || folderName === null) { + return; } - createFolder = async(e) => { + const parent = this.props.parent; + const owner = this.props.auth.id; + const parentList = this.props.parentList; + const isGoogle = this.props.isGoogle; - let inputValue = "" + this.props.dispatch( + startAddFolder(folderName, owner, parent, parentList, isGoogle) + ); + this.showDropDown(); + }; - const { value: folderName} = await Swal.fire({ - title: 'Enter Folder Name', - input: 'text', - inputValue: inputValue, - showCancelButton: true, - inputValidator: (value) => { - if (!value) { - return 'Please Enter a Name' - } - } - }) - - if (folderName === undefined || folderName === null) { - - return; - } - - const parent = this.props.parent; - const owner = this.props.auth.id; - const parentList = this.props.parentList; - const isGoogle = this.props.isGoogle; - - this.props.dispatch(startAddFolder(folderName, owner, parent, parentList, isGoogle)); - this.showDropDown(); + handleClickOutside = (e) => { + if ( + this.leftSectionRef && + !this.leftSectionRef.current.contains(event.target) + ) { + if (this.props.leftSectionMode === "open") { + this.props.dispatch(setLeftSectionMode("close")); + } } + }; - handleClickOutside = (e) => { + componentDidMount = () => { + document.addEventListener("mousedown", this.handleClickOutside); - if (this.leftSectionRef && !this.leftSectionRef.current.contains(event.target)) { - - if (this.props.leftSectionMode === 'open') { - this.props.dispatch(setLeftSectionMode('close')) - } - } + const hideFolderTree = localStorage.getItem("hide-folder-tree"); + + if (hideFolderTree) { + this.setState(() => ({ + hideFolderTree, + })); } + }; - componentDidMount = () => { - document.addEventListener('mousedown', this.handleClickOutside); + componentWillUnmount = () => { + document.removeEventListener("mousedown", this.handleClickOutside); + }; - const hideFolderTree = localStorage.getItem("hide-folder-tree"); + addButtonEvent = () => { + const currentAddOptions = !this.props.showAddOptions; + this.props.dispatch(showAddOptions(currentAddOptions)); + }; - if (hideFolderTree) { + handleUpload = (e) => { + e.preventDefault(); + console.log("handle upload"); - this.setState(() => ({ - hideFolderTree - })) - } - } + this.props.dispatch( + startAddFile( + this.uploadInput.current, + this.props.parent, + this.props.parentList, + this.props.storageSwitcher + ) + ); + this.uploadInput.current.value = ""; + }; - componentWillUnmount = () => { - document.removeEventListener('mousedown', this.handleClickOutside); - } + showDropDown = () => { + this.setState(() => { + return { + ...this.state, + open: !this.state.open, + }; + }); + }; - addButtonEvent = () => { - - const currentAddOptions = !this.props.showAddOptions - this.props.dispatch(showAddOptions(currentAddOptions)) - } + showUploadOverlay = () => { + this.showDropDown(); + this.props.dispatch(openUploadOverlay()); + }; - handleUpload = (e) => { - e.preventDefault(); - - this.props.dispatch(startAddFile(this.uploadInput.current, this.props.parent, this.props.parentList, this.props.storageSwitcher)) - this.uploadInput.current.value = "" - } - - showDropDown = () => { - - this.setState(() => { - return { - ...this.state, - open: !this.state.open - } - }) - } - - showUploadOverlay = () => { - - this.showDropDown(); - this.props.dispatch(openUploadOverlay()); - } - - render() { - - return - } + render() { + return ( + + ); + } } const connectPropToState = (state) => ({ - auth: state.auth, - parent: state.parent.parent, - parentList: state.parent.parentList, - storage: state.storage, - showAddOptions: state.addOptions.showAddOptions, - isGoogle: state.filter.isGoogle, - storageSwitcher: state.storageSwitcher.selected, - leftSectionMode: state.main.leftSectionMode -}) + auth: state.auth, + parent: state.parent.parent, + parentList: state.parent.parentList, + storage: state.storage, + showAddOptions: state.addOptions.showAddOptions, + isGoogle: state.filter.isGoogle, + storageSwitcher: state.storageSwitcher.selected, + leftSectionMode: state.main.leftSectionMode, +}); -export default connect(connectPropToState)(LeftSectionContainer); \ No newline at end of file +export default connect(connectPropToState)(LeftSectionContainer); diff --git a/src/components/UploadItem/UploadItem.jsx b/src/components/UploadItem/UploadItem.jsx index 91f8d1d..c3564ea 100644 --- a/src/components/UploadItem/UploadItem.jsx +++ b/src/components/UploadItem/UploadItem.jsx @@ -1,12 +1,25 @@ import { connect } from "react-redux"; -import React from "react"; +import React, { useEffect, useRef } from "react"; import bytes from "bytes"; +import { useFilesClient, useQuickFilesClient } from "../../hooks/files"; +import { getCancelToken } from "../../utils/cancelTokenManager"; const UploadItem = (props) => { + const filesRefreshed = useRef(false); + const { invalidateFilesCache } = useFilesClient(); + const { invalidateQuickFilesCache } = useQuickFilesClient(); const completed = props.completed; const uploadImage = props.getUploadImage(); const canceled = props.canceled; + // TODO: Add ability to cancel indivdual uploads + useEffect(() => { + if (completed && !filesRefreshed.current) { + invalidateFilesCache(); + invalidateQuickFilesCache(); + } + }, [completed]); + if (!completed && !canceled) { return (
diff --git a/src/hooks/files.ts b/src/hooks/files.ts index 52bdcda..d936f48 100644 --- a/src/hooks/files.ts +++ b/src/hooks/files.ts @@ -1,9 +1,9 @@ import { useInfiniteQuery, useQuery, useQueryClient } from "react-query"; import { useParams } from "react-router-dom"; import { - getFileThumbnail, - getFilesList, - getQuickFilesList, + getFileThumbnailAPI, + getFilesListAPI, + getQuickFilesListAPI, } from "../api/filesAPI"; import { useCallback, useEffect, useState } from "react"; import { useSelector } from "react-redux"; @@ -22,7 +22,7 @@ export const useFiles = () => { limit: undefined, }, ], - getFilesList, + getFilesListAPI, { getNextPageParam: (lastPage, pages) => { const lastElement = lastPage[lastPage.length - 1]; @@ -66,7 +66,7 @@ export const useFilesClient = () => { }; export const useQuickFiles = () => { - const quickFilesQuery = useQuery("quickFiles", getQuickFilesList); + const quickFilesQuery = useQuery("quickFiles", getQuickFilesListAPI); return { ...quickFilesQuery }; }; @@ -99,11 +99,11 @@ export const useThumbnail = (hasThumbnail: boolean, thumbnailID?: string) => { hasThumbnail: false, image: undefined, }); - }, [setState]); + }, []); const getThumbnail = useCallback(async () => { try { if (!thumbnailID) return; - const thumbnailData = await getFileThumbnail(thumbnailID); + const thumbnailData = await getFileThumbnailAPI(thumbnailID); setState({ hasThumbnail: true, image: thumbnailData, @@ -112,7 +112,7 @@ export const useThumbnail = (hasThumbnail: boolean, thumbnailID?: string) => { console.log("error getting thumbnail data", e); imageOnError(); } - }, [thumbnailID, getFileThumbnail, setState, imageOnError]); + }, [thumbnailID, getFileThumbnailAPI, imageOnError]); useEffect(() => { if (!hasThumbnail || !thumbnailID) return; diff --git a/src/hooks/folders.ts b/src/hooks/folders.ts index ca82701..c1d6c5e 100644 --- a/src/hooks/folders.ts +++ b/src/hooks/folders.ts @@ -1,6 +1,6 @@ import { useQuery, useQueryClient } from "react-query"; import { useParams } from "react-router-dom"; -import { getFolderInfo, getFoldersList } from "../api/foldersAPI"; +import { getFolderInfoAPI, getFoldersListAPI } from "../api/foldersAPI"; import { useSelector } from "react-redux"; export const useFolders = () => { @@ -16,7 +16,7 @@ export const useFolders = () => { limit: undefined, }, ], - getFoldersList + getFoldersListAPI ); return { ...foldersReactQuery }; @@ -55,7 +55,7 @@ export const useFolder = () => { id: params.id, }, ], - getFolderInfo + getFolderInfoAPI ); return { ...folderQuery }; diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index 33cf3d0..f9be222 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; import { useParams } from "react-router-dom"; export const useUtils = () => { @@ -10,3 +10,32 @@ export const useUtils = () => { return { isHome }; }; + +export const useClickOutOfBounds = (outOfBoundsCallback: () => any) => { + console.log("out"); + const wrapperRef = useRef(null); + // TODO: Remove this any + const outOfBoundsClickCheck = useCallback( + (e: any) => { + if (wrapperRef && !wrapperRef.current?.contains(e.target as Node)) { + outOfBoundsCallback(); + } + }, + [outOfBoundsCallback] + ); + + useEffect(() => { + document.addEventListener("mousedown", outOfBoundsClickCheck); + document.addEventListener("touchstart", outOfBoundsClickCheck); + + return () => { + console.log("remove listener"); + document.removeEventListener("mousedown", outOfBoundsClickCheck); + document.removeEventListener("touchstart", outOfBoundsClickCheck); + }; + }, [outOfBoundsCallback]); + + return { + wrapperRef, + }; +}; diff --git a/src/popups/folder.ts b/src/popups/folder.ts new file mode 100644 index 0000000..9943e19 --- /dev/null +++ b/src/popups/folder.ts @@ -0,0 +1,16 @@ +import Swal from "sweetalert2"; + +export const showCreateFolderPopup = async (defaultName = "") => { + const { value: folderName } = await Swal.fire({ + title: "Enter Folder Name", + input: "text", + inputValue: defaultName, + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return "Please Enter a Name"; + } + }, + }); + return folderName; +}; diff --git a/src/reducers/uploads.js b/src/reducers/uploads.js index 7be37b2..8344f8e 100644 --- a/src/reducers/uploads.js +++ b/src/reducers/uploads.js @@ -1,49 +1,39 @@ -const defaultState = [] +const defaultState = []; export default (state = defaultState, action) => { + switch (action.type) { + case "ADD_UPLOAD": + return [action.upload, ...state]; - switch(action.type) { + case "EDIT_UPLOAD": + return state.map((upload) => { + if (upload.id === action.id) { + return { + ...upload, + progress: action.progress, + completed: action.completed, + }; + } - case "ADD_UPLOAD": + return upload; + }); - return [action.upload, ...state] + case "CANCEL_UPLOAD": + return state.map((upload) => { + if (upload.id === action.id) { + return { + ...upload, + canceled: true, + }; + } - case "EDIT_UPLOAD": + return upload; + }); - return state.map((upload) => { + case "RESET_UPLOADS": + return []; - //console.log("edit upload", upload.id, action.id) - - if (upload.id === action.id) { - - //console.log("edit match", action.progress) - upload.progress = action.progress - upload.completed = action.completed - } - - return upload - - }) - - case "CANCEL_UPLOAD": - - return state.map((upload) => { - - if (upload.id === action.id) { - - upload.canceled = true; - } - - return upload - - }) - - case "RESET_UPLOADS": - - return [] - - default: - - return state - } -} \ No newline at end of file + default: + return state; + } +}; diff --git a/src/utils/cancelTokenManager.ts b/src/utils/cancelTokenManager.ts new file mode 100644 index 0000000..3c72821 --- /dev/null +++ b/src/utils/cancelTokenManager.ts @@ -0,0 +1,30 @@ +import axios from "../axiosInterceptor"; + +interface MapType { + [key: string]: { + token: any; + cancel: () => {}; + }; +} + +const cancelTokens: MapType = {}; + +// TODO: Fix any +export const addFileUploadCancelToken = (id: string, cancelToken: any) => { + cancelTokens[id] = cancelToken; +}; + +export const getCancelToken = (id: string) => { + console.log("cancel tokens", cancelTokens); + return cancelTokens[id]; +}; + +export const cancelAllFileUploads = () => { + for (const key in cancelTokens) { + cancelTokens[key].cancel(); + } +}; + +// export const cancelFileUpload = (fileId) => { + +// };