added upload changes
This commit is contained in:
@@ -2,6 +2,7 @@ import { QueryFunctionContext } from "react-query";
|
||||
import axios from "../axiosInterceptor";
|
||||
import { getUserToken } from "./user";
|
||||
import { FileInterface } from "../types/file";
|
||||
import { AxiosRequestConfig } from "axios";
|
||||
|
||||
interface QueryKeyParams {
|
||||
parent: string;
|
||||
@@ -124,6 +125,33 @@ export const getSuggestedListAPI = async ({
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getPublicFileInfoAPI = async (
|
||||
fileID: string,
|
||||
tempToken: string
|
||||
) => {
|
||||
const response = await axios.get(
|
||||
`/file-service/public/info/${fileID}/${tempToken}`
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const downloadPublicFileAPI = async (
|
||||
fileID: string,
|
||||
tempToken: string
|
||||
) => {
|
||||
await getUserToken();
|
||||
|
||||
// TODO: Change this
|
||||
const url = `http://localhost:5173/api/file-service/public/download/${fileID}/${tempToken}`;
|
||||
|
||||
const link = document.createElement("a");
|
||||
document.body.appendChild(link);
|
||||
link.href = url;
|
||||
link.setAttribute("type", "hidden");
|
||||
link.setAttribute("download", "true");
|
||||
link.click();
|
||||
};
|
||||
|
||||
// PATCH
|
||||
|
||||
export const trashFileAPI = async (fileID: string) => {
|
||||
@@ -202,3 +230,10 @@ export const deleteVideoTokenAPI = async () => {
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// POST
|
||||
export const uploadFileAPI = async (data: FormData, config: any) => {
|
||||
const url = "/file-service/upload";
|
||||
const response = await axios.post(url, data, config);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,10 @@ import { useClickOutOfBounds } from "../../hooks/utils";
|
||||
import { showCreateFolderPopup } from "../../popups/folder";
|
||||
import { useAppDispatch } from "../../hooks/store";
|
||||
import { startAddFile } from "../../actions/files";
|
||||
import { useRef } from "react";
|
||||
import { RefObject, useRef } from "react";
|
||||
import axiosNonInterceptor from "axios";
|
||||
import uuid from "uuid";
|
||||
import { useUploader } from "../../hooks/files";
|
||||
|
||||
interface AddNewDropdownProps {
|
||||
closeDropdown: () => void;
|
||||
@@ -15,7 +18,8 @@ const AddNewDropdown: React.FC<AddNewDropdownProps> = (props) => {
|
||||
const params = useParams();
|
||||
const { invalidateFoldersCache } = useFoldersClient();
|
||||
const { wrapperRef } = useClickOutOfBounds(props.closeDropdown);
|
||||
const uploadRef = useRef("");
|
||||
const uploadRef: RefObject<HTMLInputElement> = useRef(null);
|
||||
const { uploadFiles } = useUploader();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const createFolder = async () => {
|
||||
@@ -35,10 +39,10 @@ const AddNewDropdown: React.FC<AddNewDropdownProps> = (props) => {
|
||||
props.closeDropdown();
|
||||
console.log("handle upload");
|
||||
|
||||
dispatch(startAddFile(uploadRef.current, params.id));
|
||||
if (uploadRef && uploadRef.current) {
|
||||
uploadRef.current = "";
|
||||
}
|
||||
const files = uploadRef.current?.files;
|
||||
if (!files) return;
|
||||
|
||||
uploadFiles(files);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import Spinner from "../SpinnerLogin";
|
||||
import React from "react";
|
||||
|
||||
const DownloadPage = ({ download, state }) => {
|
||||
if (state.size === "" && !state.error) {
|
||||
return (
|
||||
<div className="downloadpage__box">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="downloadpage">
|
||||
{!state.error ? (
|
||||
<div className="downloadpage__box">
|
||||
<p className="downloadpage__box__title">{state.title}</p>
|
||||
<p className="downloadpage__box__subtitle">Type: {state.type}</p>
|
||||
<p className="downloadpage__box__subtitle">Size: {state.size}</p>
|
||||
<button className="button popup-window__button" onClick={download}>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="downloadpage__box">
|
||||
<p>Unauthorized/Not Found Download</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadPage;
|
||||
@@ -1,85 +0,0 @@
|
||||
import DownloadPage from "./DownloadPage";
|
||||
import capitalize from "../../utils/capitalize";
|
||||
import axios from "../../axiosInterceptor";
|
||||
import bytes from "bytes";
|
||||
import React from "react";
|
||||
|
||||
class DownloadPageContainer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: false,
|
||||
title: "",
|
||||
type: "",
|
||||
size: "",
|
||||
}
|
||||
|
||||
this.isPersonalFile = false;
|
||||
}
|
||||
|
||||
getFileExtension = (filename) => {
|
||||
|
||||
const filenameSplit = filename.split(".");
|
||||
|
||||
if (filenameSplit.length > 1) {
|
||||
|
||||
const extension = filenameSplit[filenameSplit.length - 1]
|
||||
|
||||
return extension.toUpperCase();
|
||||
|
||||
} else {
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
|
||||
const _id = this.props.match.params.id;
|
||||
const tempToken = this.props.match.params.tempToken
|
||||
|
||||
axios.get(`/file-service/public/info/${_id}/${tempToken}`).then((results) => {
|
||||
|
||||
const data = results.data;
|
||||
|
||||
const title = capitalize(data.filename);
|
||||
const size = bytes(data.length);
|
||||
const type = this.getFileExtension(title);
|
||||
this.isPersonalFile = results.data.metadata.personalFile;
|
||||
|
||||
this.setState(() => ({
|
||||
...this.state,
|
||||
title,
|
||||
type,
|
||||
size
|
||||
}))
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.setState(() => ({...this.state, error: true}))
|
||||
})
|
||||
}
|
||||
|
||||
download = () => {
|
||||
|
||||
const _id = this.props.match.params.id;
|
||||
const tempToken = this.props.match.params.tempToken
|
||||
const finalUrl = !this.isPersonalFile ? `/file-service/public/download/${_id}/${tempToken}` : `/file-service-personal/public/download/${_id}/${tempToken}`;
|
||||
|
||||
const link = document.createElement('a');
|
||||
document.body.appendChild(link);
|
||||
link.href = finalUrl;
|
||||
link.setAttribute('type', 'hidden');
|
||||
link.click();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return <DownloadPage state={this.state} download={this.download} {...this.props}/>
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadPageContainer;
|
||||
@@ -0,0 +1,129 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import axios from "../../axiosInterceptor";
|
||||
import {
|
||||
downloadPublicFileAPI,
|
||||
getPublicFileInfoAPI,
|
||||
} from "../../api/filesAPI";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import SpinnerPage from "../SpinnerPage";
|
||||
import moment from "moment";
|
||||
import { getFileColor, getFileExtension } from "../../utils/files";
|
||||
import { FileInterface } from "../../types/file";
|
||||
import bytes from "bytes";
|
||||
|
||||
const PublicDownloadPage = () => {
|
||||
const [file, setFile] = useState<FileInterface | null>(null);
|
||||
const params = useParams();
|
||||
|
||||
const getFile = useCallback(async () => {
|
||||
try {
|
||||
const id = params.id!;
|
||||
const tempToken = params.tempToken!;
|
||||
const fileResponse = await getPublicFileInfoAPI(id, tempToken);
|
||||
setFile(fileResponse);
|
||||
} catch (e) {
|
||||
console.log("Error getting publicfile info", e);
|
||||
toast.error("Error getting public file");
|
||||
}
|
||||
}, [params.id, params.tempToken, getPublicFileInfoAPI]);
|
||||
|
||||
const downloadItem = useCallback(() => {
|
||||
const id = params.id!;
|
||||
const tempToken = params.tempToken!;
|
||||
downloadPublicFileAPI(id, tempToken);
|
||||
}, [params.id, params.tempToken, downloadPublicFileAPI]);
|
||||
|
||||
useEffect(() => {
|
||||
getFile();
|
||||
}, [getFile]);
|
||||
|
||||
if (!file) {
|
||||
return (
|
||||
<div className="w-screen h-screen flex justify-center items-center">
|
||||
<SpinnerPage />
|
||||
<ToastContainer position="bottom-right" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const fileExtension = getFileExtension(file.filename, 3);
|
||||
|
||||
const imageColor = getFileColor(file.filename);
|
||||
|
||||
const formattedDate = moment(file.uploadDate).format("L");
|
||||
|
||||
const fileSize = bytes(file.metadata.size);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-center items-center w-screen h-screen bg-black bg-opacity-90">
|
||||
<div
|
||||
className="absolute top-[20px] flex justify-between w-full"
|
||||
id="actions-wrapper"
|
||||
>
|
||||
<div className="ml-4 flex items-center">
|
||||
<span className="inline-flex items-center mr-[15px] max-w-[27px] min-w-[27px] min-h-[27px] max-h-[27px]">
|
||||
<div
|
||||
className="h-[27px] w-[27px] bg-red-500 rounded-[3px] flex flex-row justify-center items-center"
|
||||
style={{ background: imageColor }}
|
||||
>
|
||||
<span className="font-semibold text-[9.5px] text-white">
|
||||
{fileExtension}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<p className="text-md text-white text-ellipsis overflow-hidden max-w-[200px] md:max-w-[600px] whitespace-nowrap">
|
||||
{file.filename}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[300px] p-4 bg-white rounded-md border shadow-lg">
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Type
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{fileExtension}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Size
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{fileSize}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Created
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{formattedDate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Access
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{file.metadata.link ? "Public" : "Private"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-[15px] flex justify-center">
|
||||
<a
|
||||
onClick={downloadItem}
|
||||
className="w-[80px] h-[40px] inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate mr-4 cursor-pointer hover:bg-[#f6f5fd]"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ToastContainer position="bottom-right" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicDownloadPage;
|
||||
@@ -9,6 +9,10 @@ import { useAppSelector } from "../../hooks/store";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
const Homepage = () => {
|
||||
const showUploader = useAppSelector(
|
||||
(state) => state.uploader.uploads.length !== 0
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HomepageSpinner />
|
||||
@@ -17,7 +21,7 @@ const Homepage = () => {
|
||||
<Header />
|
||||
<div className="flex space-between">
|
||||
<MainSection />
|
||||
<Uploader />
|
||||
{showUploader && <Uploader />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -298,7 +298,10 @@ const PhotoViewerPopup = memo(() => {
|
||||
className="max-w-[80vw] max-h-[80vh] flex justify-center items-center"
|
||||
>
|
||||
{!file.metadata.isVideo && (
|
||||
<img src={image} className="max-w-full max-h-full object-contain" />
|
||||
<img
|
||||
src={image}
|
||||
className="max-w-full max-h-full object-contain select-none"
|
||||
/>
|
||||
)}
|
||||
{file.metadata.isVideo && (
|
||||
<video
|
||||
|
||||
@@ -151,7 +151,7 @@ const SharePopup = memo(() => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!file.metadata.link) return;
|
||||
const url = `${window.location.origin}/download-page/${file._id}/${shareLink}`;
|
||||
const url = `${window.location.origin}/public-download/${file._id}/${file.metadata.link}`;
|
||||
console.log("url", url);
|
||||
setShareLink(url);
|
||||
}, [file.metadata.link]);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { connect } from "react-redux";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import bytes from "bytes";
|
||||
import { useFilesClient, useQuickFilesClient } from "../../hooks/files";
|
||||
import { getCancelToken } from "../../utils/cancelTokenManager";
|
||||
import CloseIcon from "../../icons/CloseIcon";
|
||||
import CheckCircleIcon from "../../icons/CheckCircleIcon";
|
||||
import AlertIcon from "../../icons/AlertIcon";
|
||||
|
||||
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;
|
||||
const { completed, canceled, progress, name, id } = props;
|
||||
const cancelToken = getCancelToken(id);
|
||||
|
||||
// TODO: Add ability to cancel indivdual uploads
|
||||
useEffect(() => {
|
||||
if (completed && !filesRefreshed.current) {
|
||||
invalidateFilesCache();
|
||||
@@ -21,97 +21,49 @@ const UploadItem = (props) => {
|
||||
}
|
||||
}, [completed]);
|
||||
|
||||
if (!completed && !canceled) {
|
||||
return (
|
||||
<div className="elem__upload uploading__now">
|
||||
<div className="upload__elem--status">
|
||||
<span>
|
||||
<img src="/assets/upload_now.svg" alt="upload" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="upload__info">
|
||||
<div className="top__upload">
|
||||
<div className="upload__text">
|
||||
<p>{props.name}</p>
|
||||
</div>
|
||||
<div className="upload__size">
|
||||
<div className="stop__download">
|
||||
<span>
|
||||
<img
|
||||
onClick={props.cancelUploadEvent}
|
||||
src="/assets/cancel.svg"
|
||||
alt="cancel"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<span>{bytes(props.size)}</span>
|
||||
</div>
|
||||
const cancelUpload = () => {
|
||||
cancelToken.cancel();
|
||||
};
|
||||
|
||||
const ProgressIcon = () => {
|
||||
if (completed) {
|
||||
return <CheckCircleIcon className="w-[20px] h-[20px] text-green-600" />;
|
||||
} else if (canceled) {
|
||||
return <AlertIcon className="w-[20px] h-[20px] text-red-600" />;
|
||||
} else {
|
||||
return (
|
||||
<CloseIcon
|
||||
className="w-[20px] h-[20px] cursor-pointer"
|
||||
onClick={cancelUpload}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative p-[20px] flex justify-between items-start hover:bg-[#f6f5fd]">
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between items-center mb-[15px]">
|
||||
<div className="mr-[30px]">
|
||||
<p className="text-[15px] leading-[18px] font-medium max-w-[160px] overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bottom__upload">
|
||||
<div className="progress__upload">
|
||||
<div
|
||||
className="active__progress"
|
||||
style={{ width: `${props.progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<ProgressIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-full bg-[#e0dcf3] rounded-[1.5px] h-[3px] relative">
|
||||
<div
|
||||
className="h-[3px] bg-[#3c85ee] rounded-[1.5px]"
|
||||
style={{ width: `${progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (completed) {
|
||||
return (
|
||||
<div className="elem__upload uploaded__already">
|
||||
<div className="upload__elem--status">
|
||||
<span>
|
||||
<img src="/assets/uploaded__success.svg" alt="upload" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="upload__info">
|
||||
<div className="top__upload">
|
||||
<div className="upload__text">
|
||||
<p>{props.name}</p>
|
||||
</div>
|
||||
<div className="upload__size">
|
||||
<span>{bytes(props.size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bottom__upload">
|
||||
<div className="progress__upload">
|
||||
<div
|
||||
className="active__progress"
|
||||
style={{ width: `${props.progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="elem__upload uploaded__cancelled">
|
||||
<div className="upload__elem--status">
|
||||
<span>
|
||||
<img src="/assets/uploaded__failed.svg" alt="upload" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="upload__info">
|
||||
<div className="top__upload">
|
||||
<div className="upload__text">
|
||||
<p>{props.name}</p>
|
||||
</div>
|
||||
<div className="retry__download">
|
||||
<a href="#">Retry</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bottom__upload">
|
||||
<div className="failed__info">
|
||||
<span>Upload failed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect()(UploadItem);
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
import { useAppSelector } from "../../hooks/store";
|
||||
import UploadItem from "../UploadItem";
|
||||
import React from "react";
|
||||
|
||||
const Uploader = React.forwardRef((props, ref) => (
|
||||
<div
|
||||
className="upload__status"
|
||||
style={
|
||||
props.uploads.length !== 0 ? { display: "block" } : { display: "none" }
|
||||
}
|
||||
>
|
||||
<div className="head__upload">
|
||||
<p>
|
||||
Uploading {props.uploads.length}{" "}
|
||||
{props.uploads.length === 1 ? "file" : "files"}
|
||||
</p>
|
||||
<div className="hide__upload">
|
||||
<a onClick={() => props.minimizeUploader()}>
|
||||
<img src="/assets/upload_hide.svg" alt="upload__hide" />
|
||||
</a>
|
||||
<a onClick={() => props.cancelAllUploadsEvent()}>
|
||||
<img
|
||||
src="/assets/close-white.svg"
|
||||
style={{ height: "24px" }}
|
||||
alt="upload__hide"
|
||||
/>
|
||||
</a>
|
||||
const Uploader = React.forwardRef((props, ref) => {
|
||||
const uploads = useAppSelector((state) => state.uploader.uploads);
|
||||
|
||||
return (
|
||||
<div className="upload__status">
|
||||
<div className="head__upload">
|
||||
<p>
|
||||
Uploading {uploads.length} {uploads.length === 1 ? "file" : "files"}
|
||||
</p>
|
||||
<div className="hide__upload">
|
||||
<a onClick={() => props.minimizeUploader()}>
|
||||
<img src="/assets/upload_hide.svg" alt="upload__hide" />
|
||||
</a>
|
||||
<a onClick={() => props.cancelAllUploadsEvent()}>
|
||||
<img
|
||||
src="/assets/close-white.svg"
|
||||
style={{ height: "24px" }}
|
||||
alt="upload__hide"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content__upload">
|
||||
{props.uploaderShow
|
||||
? uploads.map((upload) => {
|
||||
return <UploadItem key={upload.id} {...upload} />;
|
||||
})
|
||||
: undefined}
|
||||
</div>
|
||||
</div>
|
||||
<div className="content__upload">
|
||||
{props.uploaderShow
|
||||
? props.uploads.map((upload) => {
|
||||
return <UploadItem key={upload.id} {...upload} />;
|
||||
})
|
||||
: undefined}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
export default Uploader;
|
||||
|
||||
+89
-1
@@ -12,12 +12,18 @@ import {
|
||||
getFilesListAPI,
|
||||
getQuickFilesListAPI,
|
||||
getSuggestedListAPI,
|
||||
uploadFileAPI,
|
||||
} from "../api/filesAPI";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useAppSelector } from "./store";
|
||||
import { useAppDispatch, useAppSelector } from "./store";
|
||||
import { useUtils } from "./utils";
|
||||
import { FileInterface } from "../types/file";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import axiosNonInterceptor from "axios";
|
||||
import { addFileUploadCancelToken } from "../utils/cancelTokenManager";
|
||||
import { debounce } from "lodash";
|
||||
import { addUpload, editUpload } from "../reducers/uploader";
|
||||
|
||||
export const useFiles = (enabled = true) => {
|
||||
const params = useParams();
|
||||
@@ -196,3 +202,85 @@ export const useSearchSuggestions = (searchText: string) => {
|
||||
|
||||
return { ...searchQuery };
|
||||
};
|
||||
|
||||
export const useUploader = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const params = useParams();
|
||||
|
||||
const debounceDispatch = debounce(dispatch, 200);
|
||||
|
||||
const uploadFiles = async (files: FileList) => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const parent = params.id || "/";
|
||||
|
||||
const currentFile = files[i];
|
||||
const currentID = uuid();
|
||||
|
||||
const CancelToken = axiosNonInterceptor.CancelToken;
|
||||
const source = CancelToken.source();
|
||||
|
||||
addFileUploadCancelToken(currentID, source);
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
"Transfere-Encoding": "chunked",
|
||||
},
|
||||
onUploadProgress: (progressEvent: ProgressEvent<EventTarget>) => {
|
||||
const currentProgress = Math.round(
|
||||
(progressEvent.loaded / progressEvent.total) * 100
|
||||
);
|
||||
|
||||
if (currentProgress !== 100) {
|
||||
debounceDispatch(
|
||||
editUpload({
|
||||
id: currentID,
|
||||
updateData: { progress: currentProgress },
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
cancelToken: source.token,
|
||||
};
|
||||
|
||||
dispatch(
|
||||
addUpload({
|
||||
id: currentID,
|
||||
progress: 0,
|
||||
name: currentFile.name,
|
||||
completed: false,
|
||||
canceled: false,
|
||||
size: currentFile.size,
|
||||
})
|
||||
);
|
||||
|
||||
const data = new FormData();
|
||||
|
||||
data.append("filename", currentFile.name);
|
||||
data.append("parent", parent);
|
||||
data.append("currentID", currentID);
|
||||
data.append("size", currentFile.size.toString());
|
||||
data.append("file", currentFile);
|
||||
|
||||
try {
|
||||
await uploadFileAPI(data, config);
|
||||
dispatch(
|
||||
editUpload({
|
||||
id: currentID,
|
||||
updateData: { completed: true, progress: 100 },
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("Error uploading file", e);
|
||||
dispatch(
|
||||
editUpload({
|
||||
id: currentID,
|
||||
updateData: { canceled: true },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { uploadFiles };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
type CheckCircleIconType = React.SVGAttributes<SVGSVGElement>;
|
||||
|
||||
const CheckCircleIcon: React.FC<CheckCircleIconType> = (props) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}>
|
||||
<title>check-circle</title>
|
||||
<path
|
||||
d="M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckCircleIcon;
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface UploadItemType {
|
||||
id: string;
|
||||
progress: number;
|
||||
name: string;
|
||||
completed: boolean;
|
||||
canceled: boolean;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface UploaderStateType {
|
||||
uploads: UploadItemType[];
|
||||
}
|
||||
|
||||
const initialState: UploaderStateType = {
|
||||
uploads: [],
|
||||
};
|
||||
|
||||
const uploaderSlice = createSlice({
|
||||
name: "uploader",
|
||||
initialState,
|
||||
reducers: {
|
||||
addUpload(state, action: PayloadAction<UploadItemType>) {
|
||||
state.uploads.push(action.payload);
|
||||
},
|
||||
editUpload(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
updateData: {
|
||||
progress?: number;
|
||||
completed?: boolean;
|
||||
canceled?: boolean;
|
||||
};
|
||||
}>
|
||||
) {
|
||||
const uploads = state.uploads.map((upload) => {
|
||||
if (upload.id === action.payload.id) {
|
||||
return {
|
||||
...upload,
|
||||
...action.payload.updateData,
|
||||
};
|
||||
} else {
|
||||
return upload;
|
||||
}
|
||||
});
|
||||
|
||||
state.uploads = uploads;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { addUpload, editUpload } = uploaderSlice.actions;
|
||||
|
||||
export default uploaderSlice.reducer;
|
||||
@@ -34,7 +34,7 @@ const AppRouter = () => {
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/download-page/:id/:tempToken"
|
||||
path="/public-download/:id/:tempToken"
|
||||
element={<DownloadPage />}
|
||||
/>
|
||||
<Route
|
||||
|
||||
@@ -21,6 +21,7 @@ import mobileContextMenuReducer from "../reducers/mobileContextMenu";
|
||||
import selectedReducer from "../reducers/selected";
|
||||
import leftSectionReducer from "../reducers/leftSection";
|
||||
import userReducer from "../reducers/user";
|
||||
import uploaderReducer from "../reducers/uploader";
|
||||
|
||||
//const composeEnchancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
@@ -36,6 +37,7 @@ const store = configureStore({
|
||||
// selectedItem: selectedItemReducer,
|
||||
uploads: uploadsReducer,
|
||||
user: userReducer,
|
||||
uploader: uploaderReducer,
|
||||
// storage: storageReducer,
|
||||
// quickFiles: quickFilesReducer,
|
||||
// popupFile: popupFilesReducer,
|
||||
|
||||
@@ -15,7 +15,6 @@ export const addFileUploadCancelToken = (id: string, cancelToken: any) => {
|
||||
};
|
||||
|
||||
export const getCancelToken = (id: string) => {
|
||||
console.log("cancel tokens", cancelTokens);
|
||||
return cancelTokens[id];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user