added restore logic and fixed delete and added multi delete

This commit is contained in:
subnub
2024-06-30 13:17:04 -04:00
parent e3970ad394
commit 9f1ab10f40
25 changed files with 633 additions and 278 deletions
+17 -1
View File
@@ -6,6 +6,7 @@ import {
renameFileAPI,
downloadFileAPI,
trashFileAPI,
restoreFileAPI,
} from "../../api/filesAPI";
import { useFilesClient, useQuickFilesClient } from "../../hooks/files";
import { useFoldersClient } from "../../hooks/folders";
@@ -15,6 +16,7 @@ import { setShareSelected } from "../../actions/selectedItem";
import {
deleteFolder,
renameFolder,
restoreFolderAPI,
trashFolderAPI,
} from "../../api/foldersAPI";
import { useClickOutOfBounds, useUtils } from "../../hooks/utils";
@@ -27,6 +29,7 @@ import ShareIcon from "../../icons/ShareIcon";
import DownloadIcon from "../../icons/DownloadIcon";
import MoveIcon from "../../icons/MoveIcon";
import RestoreIcon from "../../icons/RestoreIcon";
import { restoreItemPopup } from "../../popups/file";
const ContextMenu = (props) => {
const { invalidateFilesCache } = useFilesClient();
@@ -148,6 +151,19 @@ const ContextMenu = (props) => {
}
};
const restoreItem = async () => {
props.closeContext();
const result = await restoreItemPopup();
if (!result) return;
if (!props.folderMode) {
await restoreFileAPI(props.file._id);
invalidateFilesCache();
} else {
await restoreFolderAPI(props.folder._id);
invalidateFoldersCache();
}
};
const openMoveItemModal = async () => {
props.closeContext();
if (!props.folderMode) {
@@ -268,7 +284,7 @@ const ContextMenu = (props) => {
</li>
)}
{isTrash && (
<li onClick={deleteItem} className={liClassname}>
<li onClick={restoreItem} className={liClassname}>
<a className="flex">
<span className={spanClassname}>
<RestoreIcon className="w-[19px] h-[20px]" />
+13 -5
View File
@@ -13,6 +13,7 @@ import { setPopupFile } from "../../actions/popupFile";
import bytes from "bytes";
import { useAppDispatch, useAppSelector } from "../../hooks/store";
import { setMainSelect, setMultiSelectMode } from "../../reducers/selected";
import PlayButtonIcon from "../../icons/PlayIcon";
const FileItem = React.memo((props) => {
const { file } = props;
@@ -215,11 +216,18 @@ const FileItem = React.memo((props) => {
)}
>
{hasThumbnail ? (
<img
className="w-full min-h-[88px] max-h-[88px] h-full flex object-cover"
src={image}
onError={imageOnError}
/>
<div className="w-full min-h-[88px] max-h-[88px] h-full flex">
<img
className=" object-cover"
src={image}
onError={imageOnError}
/>
{file.metadata.isVideo && (
<div className="w-full h-full absolute flex justify-center items-center text-white">
<PlayButtonIcon className="w-[50px] h-[50px]" />
</div>
)}
</div>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
+57 -52
View File
@@ -1,10 +1,19 @@
import { useMemo } from "react";
import React, { useMemo } from "react";
import { useAppDispatch, useAppSelector } from "../../hooks/store";
import { resetMultiSelect } from "../../reducers/selected";
import Swal from "sweetalert2";
import { trashMultiAPI } from "../../api/filesAPI";
import {
deleteMultiAPI,
restoreMultiAPI,
trashMultiAPI,
} from "../../api/filesAPI";
import { useFilesClient, useQuickFilesClient } from "../../hooks/files";
import { useFoldersClient } from "../../hooks/folders";
import TrashIcon from "../../icons/TrashIcon";
import Moveicon from "../../icons/MoveIcon";
import { deleteItemsPopup, restoreItemsPopup } from "../../popups/file";
import RestoreIcon from "../../icons/RestoreIcon";
import { useUtils } from "../../hooks/utils";
const MultiSelectBar = () => {
const dispatch = useAppDispatch();
@@ -21,11 +30,13 @@ const MultiSelectBar = () => {
const { invalidateFoldersCache } = useFoldersClient();
const { invalidateQuickFilesCache } = useQuickFilesClient();
const { isTrash } = useUtils();
const closeMultiSelect = () => {
dispatch(resetMultiSelect());
};
const deleteItems = async () => {
const trashItems = async () => {
const result = await Swal.fire({
title: "Move to trash?",
text: "Items in the trash will eventually be deleted.",
@@ -36,7 +47,7 @@ const MultiSelectBar = () => {
confirmButtonText: "Yes",
});
if (result.value) {
if (result) {
const itemsToTrash = Object.values(multiSelectMap);
await trashMultiAPI(itemsToTrash);
invalidateFilesCache();
@@ -46,6 +57,31 @@ const MultiSelectBar = () => {
}
};
const deleteItems = async () => {
const result = await deleteItemsPopup();
if (result) {
const itemsToTrash = Object.values(multiSelectMap);
await deleteMultiAPI(itemsToTrash);
invalidateFilesCache();
invalidateFoldersCache();
invalidateQuickFilesCache();
closeMultiSelect();
}
};
const restoreItems = async () => {
const result = await restoreItemsPopup();
if (result) {
const itemsToTrash = Object.values(multiSelectMap);
await restoreMultiAPI(itemsToTrash);
invalidateFilesCache();
invalidateFoldersCache();
invalidateQuickFilesCache();
closeMultiSelect();
}
};
if (!multiSelectMode) return <div></div>;
return (
@@ -62,56 +98,25 @@ const MultiSelectBar = () => {
</div>
<div className="flex flex-row items-center">
<svg
width="17"
height="18"
viewBox="0 0 17 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="ml-4 cursor-pointer"
onClick={deleteItems}
>
<g id="trash">
<path
id="Shape"
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.0694 2.57072H11.5707V1.92803C11.5707 0.863209 10.7075 0 9.64265 0H7.07192C6.0071 0 5.14389 0.863209 5.14389 1.92803V2.57072H0.645132C0.290178 2.57072 0.00244141 2.85846 0.00244141 3.21341C0.00244141 3.56837 0.290215 3.85607 0.645132 3.85607H1.34371L2.57317 17.4108C2.60348 17.7427 2.88255 17.9963 3.21586 17.995H13.4987C13.832 17.9964 14.1111 17.7427 14.1414 17.4108L15.3708 3.85607H16.0694C16.4244 3.85607 16.7121 3.56833 16.7121 3.21338C16.7121 2.85842 16.4244 2.57072 16.0694 2.57072ZM6.42923 1.92803C6.42923 1.57308 6.71697 1.28534 7.07192 1.28534H9.64265C9.9976 1.28534 10.2853 1.57308 10.2853 1.92803V2.57072H6.42927V1.92803H6.42923ZM3.80263 16.7096H12.9119L14.0803 3.85607H5.78658H2.63745L3.80263 16.7096Z"
fill="currentColor"
{!isTrash && (
<TrashIcon className="ml-4 cursor-pointer" onClick={trashItems} />
)}
{isTrash && (
<React.Fragment>
<RestoreIcon
className="ml-4 cursor-pointer h-[20px] w-[20px]"
onClick={restoreItems}
/>
<path
id="Path"
d="M6.42938 14.7385C6.4293 14.7376 6.42926 14.7367 6.42919 14.7358L5.7865 5.73834C5.76131 5.38339 5.45312 5.1161 5.09821 5.14129C4.74325 5.16649 4.47596 5.47467 4.50116 5.82959L5.14385 14.8271C5.16783 15.1641 5.44868 15.425 5.78654 15.4241H5.83282C6.1869 15.3995 6.45401 15.0925 6.42938 14.7385Z"
fill="currentColor"
<TrashIcon
className="ml-4 cursor-pointer text-red-500"
onClick={deleteItems}
/>
<path
id="Path_2"
d="M8.35729 5.1416C8.00234 5.1416 7.7146 5.42934 7.7146 5.78429V14.7818C7.7146 15.1367 8.00234 15.4245 8.35729 15.4245C8.71224 15.4245 8.99998 15.1367 8.99998 14.7818V5.78429C8.99998 5.42934 8.71224 5.1416 8.35729 5.1416Z"
fill="currentColor"
/>
<path
id="Path_3"
d="M11.6164 5.14129C11.2615 5.1161 10.9533 5.38339 10.9281 5.73834L10.2854 14.7358C10.2594 15.0898 10.5253 15.3979 10.8793 15.4239C10.8804 15.424 10.8814 15.424 10.8825 15.4241H10.9281C11.266 15.425 11.5468 15.1641 11.5708 14.8271L12.2135 5.82959C12.2387 5.47467 11.9714 5.16652 11.6164 5.14129Z"
fill="currentColor"
/>
</g>
</svg>
<svg
width="19"
height="16"
viewBox="0 0 19 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="ml-4 cursor-pointer"
>
<path
id="Combined Shape"
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.63107 0C7.83994 0 8.03661 0.0983374 8.16193 0.265429L9.95357 2.65429H17.9164C18.2829 2.65429 18.58 2.95138 18.58 3.31786V15.2621C18.58 15.6286 18.2829 15.9257 17.9164 15.9257H0.663571C0.297091 15.9257 0 15.6286 0 15.2621V0.663571C0 0.297091 0.297091 0 0.663571 0H7.63107ZM7.212 1.5H1.5V14.425H17.08V4.154L9.20357 4.15429L7.212 1.5ZM9.96369 9.51348C10.0439 9.43969 10.0892 9.33783 10.0892 9.23078C10.0892 9.12373 10.0439 9.02188 9.96369 8.9486L7.2712 6.4802C7.15381 6.37263 6.98149 6.34301 6.8334 6.40433C6.68532 6.46565 6.58893 6.60648 6.58893 6.76238V7.93162H4.30032C3.92929 7.93162 3.62719 8.22315 3.62719 8.5812V9.88036C3.62719 10.2384 3.92929 10.5299 4.30032 10.5299H6.58893V11.6992C6.58893 11.8551 6.68478 11.9964 6.8334 12.0577C6.98203 12.1191 7.15435 12.0889 7.2712 11.9819L9.96369 9.51348Z"
fill="currentColor"
/>
</svg>
</React.Fragment>
)}
{!isTrash && (
<Moveicon className="ml-4 cursor-pointer" onClick={() => {}} />
)}
</div>
</div>
</div>
+1 -1
View File
@@ -58,7 +58,7 @@ class PopupWindow extends React.Component {
</div>
) : (
<video
className="popup-window__video"
className="popup-window__video !max-h-[70vh]"
src={this.props.state.video}
ref={this.props.video}
type="video/mp4"
+202 -203
View File
@@ -1,244 +1,243 @@
import PopupWindow from "./PopupWindow";
import {hidePopup} from "../../actions/popupFile"
import { hidePopup } from "../../actions/popupFile";
import axios from "../../axiosInterceptor";
import env from "../../enviroment/envFrontEnd";
import {connect} from "react-redux";
import { connect } from "react-redux";
import React from "react";
import { setPhotoID } from "../../actions/photoViewer";
import axiosNonInterceptor from "axios";
class PopupWindowContainer extends React.Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.wrapperRef = React.createRef();
this.video = React.createRef();
this.imageData = "";
this.tempToken = "";
this.wrapperRef = React.createRef();
this.video = React.createRef();
this.imageData = ""
this.tempToken = ""
this.state = {
image: "/images/cloud-svg.svg",
imageClassname: this.props.popupFile.metadata.hasThumbnail
? "popup-window__image popup-window popup-window--gone"
: "popup-window__image",
video: "",
spinnerClassname: "popup-window__spinner__wrapper",
};
}
this.state = {
image: "/images/cloud-svg.svg",
imageClassname: this.props.popupFile.metadata.hasThumbnail ? "popup-window__image popup-window popup-window--gone" : "popup-window__image",
video: "",
spinnerClassname: "popup-window__spinner__wrapper"
}
getFileExtension = (filename) => {
const filenameSplit = filename.split(".");
if (filenameSplit.length > 1) {
const extension = filenameSplit[filenameSplit.length - 1];
return extension.toUpperCase();
} else {
return "Unknown";
}
};
getFileExtension = (filename) => {
const filenameSplit = filename.split(".");
if (filenameSplit.length > 1) {
const extension = filenameSplit[filenameSplit.length - 1]
return extension.toUpperCase();
} else {
return "Unknown"
}
}
getThumbnail = () => {
if (this.getFileExtension(this.props.popupFile.filename).toLowerCase() === "svg") {
this.props.popupFile.metadata.hasThumbnail = false;
return this.setState(() => {
return {
...this.state,
image: "/images/cloud-svg.svg",
imageClassname: "popup-window__image"
}
})
}
const thumbnailID = this.props.popupFile.metadata.drive ? this.props.popupFile._id : this.props.popupFile.metadata.thumbnailID
const config = {
responseType: 'arraybuffer'
getThumbnail = () => {
if (
this.getFileExtension(this.props.popupFile.filename).toLowerCase() ===
"svg"
) {
this.props.popupFile.metadata.hasThumbnail = false;
return this.setState(() => {
return {
...this.state,
image: "/images/cloud-svg.svg",
imageClassname: "popup-window__image",
};
const isDrive = this.props.popupFile.metadata.drive;
const isPersonal = this.props.popupFile.metadata.personalFile;
const url = isDrive ? `/file-service-google/thumbnail/${thumbnailID}`
: !isPersonal ? `/file-service/thumbnail/${thumbnailID}` : `/file-service-personal/thumbnail/${thumbnailID}`;
axios.get(url, config).then((results) => {
const imgFile = new Blob([results.data]);
const imgUrl = URL.createObjectURL(imgFile);
this.setState(() => ({
...this.state,
image: imgUrl,
imageClassname: "popup-window__image popup-window__image--loaded",
spinnerClassname: "popup-window__spinner__wrapper popup-window--gone"
}))
}).catch((err) => {
console.log(err)
})
});
}
thumbnailOnError = () => {
const thumbnailID = this.props.popupFile.metadata.drive
? this.props.popupFile._id
: this.props.popupFile.metadata.thumbnailID;
const config = {
responseType: "arraybuffer",
};
const isDrive = this.props.popupFile.metadata.drive;
const isPersonal = this.props.popupFile.metadata.personalFile;
const url = isDrive
? `/file-service-google/thumbnail/${thumbnailID}`
: !isPersonal
? `/file-service/thumbnail/${thumbnailID}`
: `/file-service-personal/thumbnail/${thumbnailID}`;
axios
.get(url, config)
.then((results) => {
const imgFile = new Blob([results.data]);
const imgUrl = URL.createObjectURL(imgFile);
this.setState(() => ({
...this.state,
imageClassname: "popup-window__image",
spinnerClassname: "popup-window__spinner__wrapper popup-window--gone",
image: "/images/cloud-svg.svg"
}))
...this.state,
image: imgUrl,
imageClassname: "popup-window__image popup-window__image--loaded",
spinnerClassname: "popup-window__spinner__wrapper popup-window--gone",
}));
})
.catch((err) => {
console.log(err);
});
};
thumbnailOnError = () => {
this.setState(() => ({
...this.state,
imageClassname: "popup-window__image",
spinnerClassname: "popup-window__spinner__wrapper popup-window--gone",
image: "/images/cloud-svg.svg",
}));
};
handleClickOutside = (e) => {
if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) {
this.props.dispatch(hidePopup());
}
};
handleClickOutside = (e) => {
getVideo = () => {
// const config = {
// headers: {
// uuid: window.sessionStorage.getItem("uuid")
// }
if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) {
this.props.dispatch(hidePopup())
}
}
// };
getVideo = () => {
console.log("gettings stream video token");
// const config = {
// headers: {
// uuid: window.sessionStorage.getItem("uuid")
// }
// };
axios
.get("/file-service/download/access-token-stream-video")
.then(() => {
console.log("stream video got token");
console.log("gettings stream video token")
axios.get("/file-service/download/access-token-stream-video").then(() => {
// TODO: Fix this
const finalUrl = `http://localhost:5173/api/file-service/stream-video/${this.props.popupFile._id}`;
console.log("stream video got token")
this.setState(() => ({
...this.state,
video: finalUrl,
}));
})
.catch((e) => {
console.log("Stream Video Error", e.message);
});
const isDrive = this.props.popupFile.metadata.drive;
const isPersonal = this.props.popupFile.metadata.personalFile;
const finalUrl = isDrive ?
`/file-service-google/stream-video/${this.props.popupFile._id}`
: !isPersonal ? `/file-service/stream-video/${this.props.popupFile._id}`
: `/file-service-personal/stream-video/${this.props.popupFile._id}`
this.setState(() => ({
...this.state,
video: finalUrl
}))
// axios.get(currentURL +'/file-service/download/get-token-video',config)
// .then((response) => {
}).catch((e) => {
console.log("Stream Video Error", e.message);
// this.tempToken = response.data.tempToken;
// const uuidID = window.sessionStorage.getItem("uuid");
// const isDrive = this.props.popupFile.metadata.drive;
// const isPersonal = this.props.popupFile.metadata.personalFile;
// const finalUrl = isDrive ?
// currentURL + `/file-service-google/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}`
// : !isPersonal ? currentURL + `/file-service/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}`
// : currentURL + `/file-service-personal/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}`
// this.setState(() => ({
// ...this.state,
// video: finalUrl
// }))
// }).catch((err) => {
// console.log(err)
// })
};
componentWillUnmount = () => {
document.removeEventListener("mousedown", this.handleClickOutside);
if (this.props.popupFile.metadata.isVideo) {
axios
.delete(`/file-service/remove-stream-video-token`)
.then(() => {
console.log("removed video access token");
})
.catch((err) => {
console.log(err);
});
// axios.get(currentURL +'/file-service/download/get-token-video',config)
// .then((response) => {
// this.tempToken = response.data.tempToken;
this.video.current.pause();
// const uuidID = window.sessionStorage.getItem("uuid");
// const isDrive = this.props.popupFile.metadata.drive;
// const isPersonal = this.props.popupFile.metadata.personalFile;
// const finalUrl = isDrive ?
// currentURL + `/file-service-google/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}`
// : !isPersonal ? currentURL + `/file-service/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}`
// : currentURL + `/file-service-personal/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}`
// this.setState(() => ({
// ...this.state,
// video: finalUrl
// }))
// }).catch((err) => {
// console.log(err)
// })
this.setState(() => ({
...this.state,
video: "",
}));
}
};
componentWillUnmount = () => {
componentDidMount = () => {
document.addEventListener("mousedown", this.handleClickOutside);
document.removeEventListener('mousedown', this.handleClickOutside);
if (this.props.popupFile.metadata.isVideo) {
axios.delete(`/file-service/remove-stream-video-token`).then(() => {
console.log("removed video access token");
}).catch((err) => {
console.log(err);
})
this.video.current.pause();
this.setState(() => ({
...this.state,
video: ""
}))
}
if (
this.props.popupFile.metadata.hasThumbnail &&
!this.props.popupFile.metadata.isVideo &&
!this.props.popupFile.metadata.drive
) {
this.getThumbnail();
} else if (
this.props.popupFile.metadata.drive &&
this.props.popupFile.metadata.hasThumbnail &&
!this.props.popupFile.metadata.googleDoc &&
!this.props.popupFile.metadata.isVideo
) {
this.getThumbnail();
} else if (
this.props.popupFile.metadata.drive &&
this.props.popupFile.metadata.hasThumbnail &&
!this.props.popupFile.metadata.isVideo
) {
this.setState(() => ({
...this.state,
imageClassname: "popup-window__image",
spinnerClassname: "popup-window__spinner__wrapper popup-window--gone",
image: "/images/cloud-svg.svg",
}));
} else if (this.props.popupFile.metadata.isVideo) {
this.getVideo();
}
};
componentDidMount = () => {
hidePopupWindow = () => {
this.props.dispatch(hidePopup());
};
document.addEventListener('mousedown', this.handleClickOutside);
if (this.props.popupFile.metadata.hasThumbnail && !this.props.popupFile.metadata.isVideo && !this.props.popupFile.metadata.drive) {
this.getThumbnail()
} else if (this.props.popupFile.metadata.drive && this.props.popupFile.metadata.hasThumbnail && !this.props.popupFile.metadata.googleDoc && !this.props.popupFile.metadata.isVideo) {
this.getThumbnail();
} else if (this.props.popupFile.metadata.drive && this.props.popupFile.metadata.hasThumbnail && !this.props.popupFile.metadata.isVideo) {
this.setState(() => ({
...this.state,
imageClassname: "popup-window__image",
spinnerClassname: "popup-window__spinner__wrapper popup-window--gone",
image: "/images/cloud-svg.svg"
}))
}else if (this.props.popupFile.metadata.isVideo) {
this.getVideo();
}
}
hidePopupWindow = () => {
this.props.dispatch(hidePopup());
}
setPhotoViewerWindow = () => {
const isGoogle = this.props.popupFile.metadata.drive
const isPersonal = this.props.popupFile.metadata.personalFile;
this.props.dispatch(setPhotoID(this.props.popupFile._id, isGoogle, isPersonal))
}
render() {
return <PopupWindow
wrapperRef={this.wrapperRef}
video={this.video}
hidePopupWindow={this.hidePopupWindow}
state={this.state}
setPhotoViewerWindow={this.setPhotoViewerWindow}
thumbnailOnError={this.thumbnailOnError}
{...this.props}
/>
}
setPhotoViewerWindow = () => {
const isGoogle = this.props.popupFile.metadata.drive;
const isPersonal = this.props.popupFile.metadata.personalFile;
this.props.dispatch(
setPhotoID(this.props.popupFile._id, isGoogle, isPersonal)
);
};
render() {
return (
<PopupWindow
wrapperRef={this.wrapperRef}
video={this.video}
hidePopupWindow={this.hidePopupWindow}
state={this.state}
setPhotoViewerWindow={this.setPhotoViewerWindow}
thumbnailOnError={this.thumbnailOnError}
{...this.props}
/>
);
}
}
const connectPropToState = (state) => ({
popupFile: state.popupFile,
})
popupFile: state.popupFile,
});
export default connect(connectPropToState)(PopupWindowContainer)
export default connect(connectPropToState)(PopupWindowContainer);
+9 -5
View File
@@ -13,6 +13,7 @@ import { setPopupFile } from "../../actions/popupFile";
import { FileInterface } from "../../types/file";
import { useAppDispatch, useAppSelector } from "../../hooks/store";
import { setMainSelect, setMultiSelectMode } from "../../reducers/selected";
import PlayButtonIcon from "../../icons/PlayIcon";
interface QuickAccessItemProps {
file: FileInterface;
@@ -127,11 +128,14 @@ const QuickAccessItem = memo((props: QuickAccessItemProps) => {
)}
>
{hasThumbnail ? (
<img
className="w-full min-h-[88px] max-h-[88px] h-full flex object-cover"
src={image}
onError={imageOnError}
/>
<div className="w-full min-h-[88px] max-h-[88px] h-full flex">
<img className=" object-cover" src={image} onError={imageOnError} />
{file.metadata.isVideo && (
<div className="w-full h-full absolute flex justify-center items-center text-white">
<PlayButtonIcon className="w-[50px] h-[50px]" />
</div>
)}
</div>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"