diff --git a/backend/controllers/file.ts b/backend/controllers/file.ts index eeec46b..c79f9cf 100644 --- a/backend/controllers/file.ts +++ b/backend/controllers/file.ts @@ -8,6 +8,7 @@ import { createStreamVideoCookie, removeStreamVideoCookie, } from "../cookies/createCookies"; +import ChunkService from "../services/ChunkService"; const fileService = new FileService(); @@ -34,8 +35,8 @@ type ChunkServiceType = FileSystemService | S3Service; class FileController { chunkService: ChunkServiceType; - constructor(chunkService: ChunkServiceType) { - this.chunkService = chunkService; + constructor() { + this.chunkService = new ChunkService(); } getThumbnail = async (req: RequestTypeFullUser, res: Response) => { diff --git a/backend/express-routers/file.ts b/backend/express-routers/file.ts index 344c062..e064ac4 100644 --- a/backend/express-routers/file.ts +++ b/backend/express-routers/file.ts @@ -1,25 +1,10 @@ import { Router } from "express"; import auth from "../middleware/auth"; import FileController from "../controllers/file"; -import env from "../enviroment/env"; -import FileSystemService from "../services/ChunkService/FileSystemService"; -import S3Service from "../services/ChunkService/S3Service"; -import ChunkInterface from "../services/ChunkService/utils/ChunkInterface"; import authFullUser from "../middleware/authFullUser"; import authStreamVideo from "../middleware/authStreamVideo"; -let fileController: FileController; -let chunkService: ChunkInterface; - -if (env.dbType === "fs") { - const fileSystemService = new FileSystemService(); - chunkService = fileSystemService; - fileController = new FileController(fileSystemService); -} else { - const s3Service = new S3Service(); - chunkService = s3Service; - fileController = new FileController(s3Service); -} +const fileController = new FileController(); const router = Router(); diff --git a/backend/services/ChunkService/FileSystemActions.ts b/backend/services/ChunkService/FileSystemActions.ts new file mode 100644 index 0000000..15743e5 --- /dev/null +++ b/backend/services/ChunkService/FileSystemActions.ts @@ -0,0 +1,17 @@ +import fs from "fs"; +import { UserInterface } from "../../models/user"; +import { AuthParams, IStorageActions } from "./StoreTypes"; + +class FilesystemActions implements IStorageActions { + async getAuth() { + return {}; + } + + createReadStream(params: AuthParams): NodeJS.ReadableStream { + if (!params.filePath) throw new Error("File path not configured"); + const fsReadableStream = fs.createReadStream(params.filePath); + return fsReadableStream; + } +} + +export { FilesystemActions }; diff --git a/backend/services/ChunkService/S3Actions.ts b/backend/services/ChunkService/S3Actions.ts new file mode 100644 index 0000000..c5f187c --- /dev/null +++ b/backend/services/ChunkService/S3Actions.ts @@ -0,0 +1,22 @@ +import { UserInterface } from "../../models/user"; +import s3 from "../../db/s3"; +import env from "../../enviroment/env"; +import { AuthParams, IStorageActions } from "./StoreTypes"; +import internal from "stream"; + +class S3Actions implements IStorageActions { + getAuth() { + return { s3Storage: s3, bucket: env.s3Bucket! }; + } + + createReadStream(params: AuthParams): internal.Readable { + if (!params.Key) throw new Error("S3 not configured"); + const { s3Storage, bucket } = this.getAuth(); + const s3ReadableStream = s3Storage + .getObject({ Key: params.Key, Bucket: bucket }) + .createReadStream(); + return s3ReadableStream; + } +} + +export { S3Actions }; diff --git a/backend/services/ChunkService/StoreTypes.ts b/backend/services/ChunkService/StoreTypes.ts new file mode 100644 index 0000000..406dcc7 --- /dev/null +++ b/backend/services/ChunkService/StoreTypes.ts @@ -0,0 +1,15 @@ +import internal from "stream"; + +export interface AuthParams { + Key?: string; + Bucket?: string; + filePath?: string; + [key: string]: any; +} + +export interface IStorageActions { + getAuth(): Object; + createReadStream( + params: AuthParams + ): NodeJS.ReadableStream | internal.Readable; +} diff --git a/backend/services/ChunkService/index.ts b/backend/services/ChunkService/index.ts new file mode 100644 index 0000000..5bb0ebb --- /dev/null +++ b/backend/services/ChunkService/index.ts @@ -0,0 +1,679 @@ +import { Response, Request } from "express"; +import ChunkInterface from "./utils/ChunkInterface"; +import { UserInterface } from "../../models/user"; +import NotAuthorizedError from "../../utils/NotAuthorizedError"; +import NotFoundError from "../../utils/NotFoundError"; +import crypto from "crypto"; +import getBusboyData from "./utils/getBusboyData"; +import videoChecker from "../../utils/videoChecker"; +import fs from "fs"; +import uuid from "uuid"; +import awaitUploadStreamFS from "./utils/awaitUploadStreamFS"; +import File, { FileInterface } from "../../models/file"; +import getFileSize from "./utils/getFileSize"; +import DbUtilFile from "../../db/utils/fileUtils/index"; +import DbUtilFolder from "../../db/utils/folderUtils/index"; +import awaitStream from "./utils/awaitStream"; +import createThumbnailAny from "./utils/createThumbailAny"; +import imageChecker from "../../utils/imageChecker"; +import Thumbnail, { ThumbnailInterface } from "../../models/thumbnail"; +import streamToBuffer from "../../utils/streamToBuffer"; +import User from "../../models/user"; +import env from "../../enviroment/env"; +import removeChunksFS from "./utils/removeChunksFS"; +import getPrevIVFS from "./utils/getPrevIVFS"; +import awaitStreamVideo from "./utils/awaitStreamVideo"; +import fixStartChunkLength from "./utils/fixStartChunkLength"; +import Folder, { FolderInterface } from "../../models/folder"; +import addToStoageSize from "./utils/addToStorageSize"; +import subtractFromStorageSize from "./utils/subtractFromStorageSize"; +import ForbiddenError from "../../utils/ForbiddenError"; +import { ObjectId } from "mongodb"; +import FolderService from "../FolderService"; +import MongoFileService from "../FileService"; +import createVideoThumbnailFS from "./utils/createVideoThumbnailFS"; +import { S3Actions } from "./S3Actions"; +import { FilesystemActions } from "./FileSystemActions"; + +const dbUtilsFile = new DbUtilFile(); +const dbUtilsFolder = new DbUtilFolder(); +const folderService = new FolderService(); +const fileService = new MongoFileService(); + +const storageActions = + env.dbType === "s3" ? new S3Actions() : new FilesystemActions(); + +class StorageService implements ChunkInterface { + constructor() {} + + uploadFile = async (user: UserInterface, busboy: any, req: Request) => { + console.log("upeload file2"); + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const cipher = crypto.createCipheriv("aes256", CIPHER_KEY, initVect); + + const { file, filename: fileInfo, formData } = await getBusboyData(busboy); + + const filename = fileInfo.filename; + console.log("1"); + const parent = formData.get("parent") || "/"; + 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.toString(), + hasThumbnail, + thumbnailID, + isVideo, + size, + IV: initVect, + filePath: env.fsDirectory + systemFileName, + }; + + const fileWriteStream = fs.createWriteStream(metadata.filePath); + + const totalStreamsToErrorCatch = [file, cipher, fileWriteStream]; + + await awaitUploadStreamFS( + file.pipe(cipher), + fileWriteStream, + req, + metadata.filePath, + totalStreamsToErrorCatch + ); + + const date = new Date(); + const encryptedFileSize = await getFileSize(metadata.filePath); + + const currentFile = new File({ + filename, + uploadDate: date.toISOString(), + length: encryptedFileSize, + metadata, + }); + + await currentFile.save(); + + await addToStoageSize(user, size, personalFile); + + const imageCheck = imageChecker(currentFile.filename); + const videoCheck = videoChecker(currentFile.filename); + + if (videoCheck) { + console.log("is vidoe"); + const updatedFile = await createVideoThumbnailFS( + currentFile, + filename, + user + ); + + return updatedFile; + } else if (currentFile.length < 15728640 && imageCheck) { + const updatedFile = await createThumbnailAny(currentFile, filename, user); + + return updatedFile; + } else { + return currentFile; + } + }; + + getFileWriteStream = async ( + user: UserInterface, + file: FileInterface, + parentFolder: FolderInterface + ) => { + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const cipher = crypto.createCipheriv("aes256", CIPHER_KEY, initVect); + + const filename = file.filename; + const parent = parentFolder._id; + const parentList = [...parentFolder.parentList, parentFolder._id]; + const size = file.metadata.size; + const personalFile = file.metadata.personalFile ? true : false; + let hasThumbnail = file.metadata.hasThumbnail; + let thumbnailID = file.metadata.thumbnailID; + const isVideo = file.metadata.isVideo; + + const systemFileName = uuid.v4(); + + const metadata = { + owner: user._id, + parent, + parentList, + hasThumbnail, + thumbnailID, + isVideo, + size, + IV: file.metadata.IV, + filePath: env.fsDirectory + systemFileName, + }; + + const fileWriteStream = fs.createWriteStream(metadata.filePath); + + return fileWriteStream; + }; + + downloadFile = async (user: UserInterface, fileID: string, res: Response) => { + console.log("DEP INJECT :)"); + const currentFile = await dbUtilsFile.getFileInfo( + fileID, + user._id.toString() + ); + console.log(fileID, user._id); + if (!currentFile) throw new NotFoundError("Download File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + // 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 readStreamParams = {} as any; + if (env.dbType === "fs") { + readStreamParams.filePath = currentFile.metadata.filePath!; + } else { + readStreamParams.Key = currentFile.metadata.s3ID!; + } + + const readStream = storageActions.createReadStream(readStreamParams); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV); + + res.set("Content-Type", "binary/octet-stream"); + res.set( + "Content-Disposition", + 'attachment; filename="' + currentFile.filename + '"' + ); + res.set("Content-Length", currentFile.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + }; + + getFileReadStream = async (user: UserInterface, fileID: string) => { + const currentFile = await dbUtilsFile.getFileInfo( + fileID, + user._id.toString() + ); + + if (!currentFile) throw new NotFoundError("Download File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const filePath = currentFile.metadata.filePath!; + + const IV = currentFile.metadata.IV.buffer as Buffer; + + const readStream = fs.createReadStream(filePath); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV); + + return readStream; + }; + + getThumbnail = async (user: UserInterface, id: string) => { + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const thumbnail = (await Thumbnail.findById( + new ObjectId(id) + )) as ThumbnailInterface; + + if (thumbnail.owner !== user._id.toString()) { + throw new ForbiddenError("Thumbnail Unauthorized Error"); + } + + // 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(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv); + + const readStreamParams = {} as any; + + const readStream = fs.createReadStream(thumbnail.path!); + + const allStreamsToErrorCatch = [readStream, decipher]; + + const bufferData = await streamToBuffer( + readStream.pipe(decipher), + allStreamsToErrorCatch + ); + + return bufferData; + }; + + getFullThumbnail = async ( + user: UserInterface, + fileID: string, + res: Response + ) => { + const userID = user._id; + + const file = await dbUtilsFile.getFileInfo(fileID, userID.toString()); + + if (!file) throw new NotFoundError("File Thumbnail Not Found"); + + const password = user.getEncryptionKey(); + const IV = file.metadata.IV; + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const readStream = fs.createReadStream(file.metadata.filePath!); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV); + + res.set("Content-Type", "binary/octet-stream"); + res.set( + "Content-Disposition", + 'attachment; filename="' + file.filename + '"' + ); + res.set("Content-Length", file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + }; + + streamVideo = async ( + user: UserInterface, + fileID: string, + headers: any, + res: Response, + req: Request + ) => { + // To get this all working correctly with encryption and across + // All browsers took many days, tears, and some of my sanity. + // Shoutout to Tyzoid for helping me with the decryption + // And and helping me understand how the IVs work. + + // P.S I hate safari >:( + // Why do yall have to be weird with video streaming + // 90% of the issues with this are only in Safari + // Is safari going to be the next internet explorer? + + const userID = user._id; + const currentFile = await dbUtilsFile.getFileInfo( + fileID, + userID.toString() + ); + + if (!currentFile) throw new NotFoundError("Video File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const fileSize = currentFile.metadata.size; + + const range = headers.range; + const parts = range.replace(/bytes=/, "").split("-"); + let start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; + const IV = currentFile.metadata.IV.buffer as Buffer; + const chunksize = end - start + 1; + + let head = { + "Content-Range": "bytes " + start + "-" + end + "/" + fileSize, + "Accept-Ranges": "bytes", + "Content-Length": chunksize, + "Content-Type": "video/mp4", + }; + + let currentIV = IV; + + let fixedStart = 0; + let fixedEnd = currentFile.length; + + if (start === 0 && end === 1) { + // This is for Safari/iOS, Safari will request the first + // Byte before actually playing the video. Needs to be + // 16 bytes. + + fixedStart = 0; + fixedEnd = 15; + } else { + // If you're a normal browser, or this isn't Safari's first request + // We need to make it so start is divisible by 16, since AES256 + // Has a block size of 16 bytes. + + fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + } + + if (+start === 0) { + // This math will not work if the start is 0 + // So if it is we just change fixed start back + // To 0. + + fixedStart = 0; + } + + // We also need to calculate the difference between the start and the + // Fixed start position. Since there will be an offset if the original + // Request is not divisible by 16, it will not return the right part + // Of the file, you will see how we do this in the awaitStreamVideo + // code. + + const differenceStart = start - fixedStart; + + if (fixedStart !== 0 && start !== 0) { + // If this isn't the first request, the way AES256 works is when you try to + // Decrypt a certain part of the file that isn't the start, the IV will + // Actually be the 16 bytes ahead of where you are trying to + // Start the decryption. + + currentIV = (await getPrevIVFS( + fixedStart - 16, + currentFile.metadata.filePath! + )) as Buffer; + } + + const readStream = fs.createReadStream(currentFile.metadata.filePath!, { + start: fixedStart, + end: fixedEnd, + }); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, currentIV); + + decipher.setAutoPadding(false); + + res.writeHead(206, head); + + const allStreamsToErrorCatch = [readStream, decipher]; + + readStream.pipe(decipher); + + await awaitStreamVideo( + start, + end, + differenceStart, + decipher, + res, + req, + allStreamsToErrorCatch, + readStream + ); + + readStream.destroy(); + }; + + getPublicDownload = async (fileID: string, tempToken: any, res: Response) => { + const file: FileInterface = await dbUtilsFile.getPublicFile(fileID); + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + throw new NotAuthorizedError("File Not Public"); + } + + const user = (await User.findById(file.metadata.owner)) as UserInterface; + + const password = user.getEncryptionKey(); + + if (!password) throw new ForbiddenError("Invalid Encryption Key"); + + const IV = file.metadata.IV.buffer as Buffer; + + const readStream = fs.createReadStream(file.metadata.filePath!); + + const CIPHER_KEY = crypto.createHash("sha256").update(password).digest(); + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV); + + res.set("Content-Type", "binary/octet-stream"); + res.set( + "Content-Disposition", + 'attachment; filename="' + file.filename + '"' + ); + res.set("Content-Length", file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + + if (file.metadata.linkType === "one") { + await dbUtilsFile.removeOneTimePublicLink(fileID); + } + }; + + trashMulti = async ( + userID: string, + items: { + type: "file" | "folder" | "quick-item"; + id: string; + file?: FileInterface; + folder?: FolderInterface; + }[] + ) => { + const fileList = items.filter( + (item) => item.type === "file" || item.type === "quick-item" + ); + const folderList = items + .filter((item) => item.type === "folder") + .sort((a, b) => { + if (!a.folder || !b.folder) return 0; + return b.folder.parentList.length - a.folder.parentList.length; + }); + + for (const file of fileList) { + await fileService.trashFile(userID, file.id); + } + for (const folder of folderList) { + await folderService.trashFolder(userID, folder.id); + } + }; + + restoreMulti = async ( + userID: string, + items: { + type: "file" | "folder" | "quick-item"; + id: string; + file?: FileInterface; + folder?: FolderInterface; + }[] + ) => { + const fileList = items.filter( + (item) => item.type === "file" || item.type === "quick-item" + ); + const folderList = items + .filter((item) => item.type === "folder") + .sort((a, b) => { + if (!a.folder || !b.folder) return 0; + return b.folder.parentList.length - a.folder.parentList.length; + }); + + for (const file of fileList) { + await fileService.restoreFile(userID, file.id); + } + for (const folder of folderList) { + await folderService.restoreFolder(userID, folder.id); + } + }; + + deleteMulti = async ( + userID: string, + items: { + type: "file" | "folder" | "quick-item"; + id: string; + file?: FileInterface; + folder?: FolderInterface; + }[] + ) => { + const fileList = items.filter( + (item) => item.type === "file" || item.type === "quick-item" + ); + const folderList = items + .filter((item) => item.type === "folder") + .sort((a, b) => { + if (!a.folder || !b.folder) return 0; + return b.folder.parentList.length - a.folder.parentList.length; + }); + + for (const file of fileList) { + await this.deleteFile(userID, file.id); + } + for (const folder of folderList) { + await this.deleteFolder(userID, folder.id); + } + }; + + deleteFile = async (userID: string, fileID: string) => { + const file = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("Delete File Not Found Error"); + + if (file.metadata.thumbnailID) { + const thumbnail = (await Thumbnail.findById( + file.metadata.thumbnailID + )) as ThumbnailInterface; + const thumbnailPath = thumbnail.path!; + await removeChunksFS(thumbnailPath); + + await Thumbnail.deleteOne({ _id: file.metadata.thumbnailID }); + } + + await removeChunksFS(file.metadata.filePath!); + await File.deleteOne({ _id: file._id }); + await subtractFromStorageSize( + userID, + file.length, + file.metadata.personalFile! + ); + }; + + deleteFolder = async (userID: string, folderID: string) => { + const folder = await dbUtilsFolder.getFolderInfo(folderID, userID); + + if (!folder) throw new NotFoundError("Delete Folder Not Found Error"); + + const parentList = [...folder.parentList, folder._id]; + + await Folder.deleteMany({ + owner: userID, + parentList: { $all: parentList }, + }); + await Folder.deleteMany({ owner: userID, _id: folderID }); + + const fileList = await dbUtilsFile.getFileListByParent( + userID, + parentList.toString() + ); + + if (!fileList) throw new NotFoundError("Delete File List Not Found"); + + for (let i = 0; i < fileList.length; i++) { + const currentFile = fileList[i]; + + try { + if (currentFile.metadata.thumbnailID) { + const thumbnail = (await Thumbnail.findById( + currentFile.metadata.thumbnailID + )) as ThumbnailInterface; + const thumbnailPath = thumbnail.path!; + await removeChunksFS(thumbnailPath); + + await Thumbnail.deleteOne({ _id: currentFile.metadata.thumbnailID }); + } + + await removeChunksFS(currentFile.metadata.filePath!); + await File.deleteOne({ _id: currentFile._id }); + } catch (e) { + console.log( + "Could not delete file", + currentFile.filename, + currentFile._id + ); + } + } + }; + + deleteAll = async (userID: string) => { + await Folder.deleteMany({ owner: userID }); + + const fileList = await dbUtilsFile.getFileListByOwner(userID); + + if (!fileList) + throw new NotFoundError("Delete All File List Not Found Error"); + + for (let i = 0; i < fileList.length; i++) { + const currentFile = fileList[i]; + + try { + if (currentFile.metadata.thumbnailID) { + const thumbnail = (await Thumbnail.findById( + currentFile.metadata.thumbnailID + )) as ThumbnailInterface; + const thumbnailPath = thumbnail.path!; + await removeChunksFS(thumbnailPath); + + await Thumbnail.deleteOne({ _id: currentFile.metadata.thumbnailID }); + } + + await removeChunksFS(currentFile.metadata.filePath!); + await File.deleteOne({ _id: currentFile._id }); + } catch (e) { + console.log( + "Could Not Remove File", + currentFile.filename, + currentFile._id + ); + } + } + }; +} + +export default StorageService; diff --git a/src/components/FileItem/index.jsx b/src/components/FileItem/index.jsx index dc4e831..aba5df5 100644 --- a/src/components/FileItem/index.jsx +++ b/src/components/FileItem/index.jsx @@ -187,7 +187,7 @@ const FileItem = React.memo((props) => { return (
{ >

{ -

+

Name

diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 8dd246f..2f64cd1 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -4,9 +4,11 @@ import MenuIcon from "../../icons/MenuIcon"; import { useAppDispatch, useAppSelector } from "../../hooks/store"; import { useCallback, useMemo } from "react"; import { toggleDrawer } from "../../reducers/leftSection"; +import { useUtils } from "../../hooks/utils"; const Header = () => { const drawerOpen = useAppSelector((state) => state.leftSection.drawOpen); + const { isSettings } = useUtils(); const dispatch = useAppDispatch(); const navigate = useNavigate(); @@ -28,15 +30,17 @@ const Header = () => { logo
-
- - - -
+ {!isSettings && ( +
+ + + +
+ )}
diff --git a/src/components/LeftSection/index.jsx b/src/components/LeftSection/index.jsx index 572f886..c2fd3ec 100644 --- a/src/components/LeftSection/index.jsx +++ b/src/components/LeftSection/index.jsx @@ -71,7 +71,7 @@ const LeftSection = (props) => { [closeDrawer, leftSectionOpen] ); - const { wrapperRef } = useClickOutOfBounds(closeDrawerEvent); + const { wrapperRef } = useClickOutOfBounds(closeDrawerEvent, leftSectionOpen); return (
{ ? "justify-center xs:justify-normal" : "justify-normal", { - "max-h-32 sm:max-h-40": !quickAccessExpanded, - "max-h-[700px] sm:max-h-[665px] quickAccessOne:max-h-[1000px] quickAccessTwo:max-h-[660px] quickAccessThree:max-h-[490px]": + "max-h-36 sm:max-h-40": !quickAccessExpanded, + "max-h-[720px] sm:max-h-[665px] quickAccessOne:max-h-[1000px] quickAccessTwo:max-h-[660px] quickAccessThree:max-h-[490px]": quickAccessExpanded, } )} diff --git a/src/components/QuickAccessItem/index.tsx b/src/components/QuickAccessItem/index.tsx index 11c4517..62b204e 100644 --- a/src/components/QuickAccessItem/index.tsx +++ b/src/components/QuickAccessItem/index.tsx @@ -98,7 +98,7 @@ const QuickAccessItem = memo((props: QuickAccessItemProps) => { return (
{ >

{ if (completed && !filesRefreshed.current) { invalidateFilesCache(); invalidateQuickFilesCache(); + filesRefreshed.current = true; } }, [completed]); diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index 172f8a5..23e20dc 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -30,7 +30,10 @@ export const useUtils = () => { return { isHome, isTrash, isMedia, isSettings }; }; -export const useClickOutOfBounds = (outOfBoundsCallback: (e: any) => any) => { +export const useClickOutOfBounds = ( + outOfBoundsCallback: (e: any) => any, + shouldCheck = true +) => { const wrapperRef = useRef(null); // TODO: Remove this any const outOfBoundsClickCheck = useCallback( @@ -44,15 +47,20 @@ export const useClickOutOfBounds = (outOfBoundsCallback: (e: any) => any) => { useEffect(() => { console.log("useeffect"); - document.addEventListener("mousedown", outOfBoundsClickCheck); - document.addEventListener("touchstart", outOfBoundsClickCheck); - - return () => { - console.log("remove listener"); + if (shouldCheck) { + document.addEventListener("mousedown", outOfBoundsClickCheck); + document.addEventListener("touchstart", outOfBoundsClickCheck); + } else { document.removeEventListener("mousedown", outOfBoundsClickCheck); document.removeEventListener("touchstart", outOfBoundsClickCheck); + } + return () => { + if (shouldCheck) { + document.removeEventListener("mousedown", outOfBoundsClickCheck); + document.removeEventListener("touchstart", outOfBoundsClickCheck); + } }; - }, [outOfBoundsCallback, outOfBoundsClickCheck]); + }, [outOfBoundsCallback, outOfBoundsClickCheck, shouldCheck]); return { wrapperRef,