diff --git a/web/public/locales/en/translation.json b/web/public/locales/en/translation.json index 51fb2b84ff..d28efdc8bc 100644 --- a/web/public/locales/en/translation.json +++ b/web/public/locales/en/translation.json @@ -287,7 +287,9 @@ "DomainUpdateSuccess": "Domain Update Success", "DomainDeleteSuccess": "Domain Delete Success", "CustomApplicationDomain": "Custom Application Domain", - "BucketNameisRequired": "Bucket name is required" + "BucketNameisRequired": "Bucket name is required", + "Fail": "upload failed", + "UploadFailTip": "{{number}} files upload failed" }, "TriggerPanel": { "AddTrigger": "Add Trigger", @@ -681,4 +683,4 @@ "Byte": "byte", "Second": "second", "Optional": "optional" -} \ No newline at end of file +} diff --git a/web/public/locales/zh-CN/translation.json b/web/public/locales/zh-CN/translation.json index 714f1a0d1f..4ada5e8ff2 100644 --- a/web/public/locales/zh-CN/translation.json +++ b/web/public/locales/zh-CN/translation.json @@ -287,7 +287,9 @@ "DomainUpdateSuccess": "域名修改成功", "DomainDeleteSuccess": "域名删除成功", "CustomApplicationDomain": "自定义应用域名", - "BucketNameisRequired": "请输入 Bucket 名称" + "BucketNameisRequired": "请输入 Bucket 名称", + "Fail": "上传失败", + "UploadFailTip": "{{number}} 个文件上传失败" }, "TriggerPanel": { "AddTrigger": "新建触发器", @@ -681,4 +683,4 @@ "Byte": "字节", "Second": "秒", "Optional": "可选" -} \ No newline at end of file +} diff --git a/web/public/locales/zh/translation.json b/web/public/locales/zh/translation.json index 2be9f5d5f0..3b69a5a293 100644 --- a/web/public/locales/zh/translation.json +++ b/web/public/locales/zh/translation.json @@ -287,7 +287,9 @@ "DomainUpdateSuccess": "域名修改成功", "DomainDeleteSuccess": "域名删除成功", "CustomApplicationDomain": "自定义应用域名", - "BucketNameisRequired": "请输入 Bucket 名称" + "BucketNameisRequired": "请输入 Bucket 名称", + "Fail": "上传失败", + "UploadFailTip": "{{number}} 个文件上传失败" }, "TriggerPanel": { "AddTrigger": "新建触发器", @@ -681,4 +683,4 @@ "Byte": "字节", "Second": "秒", "Optional": "可选" -} \ No newline at end of file +} diff --git a/web/src/components/FileUpload/index.tsx b/web/src/components/FileUpload/index.tsx index ff4c45f50a..fcf63bf30d 100644 --- a/web/src/components/FileUpload/index.tsx +++ b/web/src/components/FileUpload/index.tsx @@ -63,7 +63,7 @@ function FileUpload(props: { onUpload: (files: any) => void; darkMode: boolean } }); } else { const dirReader = file.createReader(); - dirReader.readEntries(function (entries: any) { + readAllEntries(dirReader, []).then((entries: any) => { const promises = []; for (let i = 0; i < entries.length; i++) { const entry = entries[i]; @@ -76,6 +76,19 @@ function FileUpload(props: { onUpload: (files: any) => void; darkMode: boolean } }); }; + function readAllEntries(dirReader: any, entries: any) { + return new Promise((resolve, reject) => { + dirReader.readEntries(function (newEntries: any) { + if (newEntries.length === 0) { + resolve(entries); + } else { + entries = entries.concat(newEntries); + readAllEntries(dirReader, entries).then(resolve).catch(reject); + } + }); + }); + } + // triggers when file is selected with click const handleChange = function (e: React.ChangeEvent) { e.preventDefault(); diff --git a/web/src/pages/app/storages/mods/UploadButton/index.tsx b/web/src/pages/app/storages/mods/UploadButton/index.tsx index b4a0a1a826..34ae05fcb7 100644 --- a/web/src/pages/app/storages/mods/UploadButton/index.tsx +++ b/web/src/pages/app/storages/mods/UploadButton/index.tsx @@ -1,7 +1,8 @@ import React from "react"; -import { useTranslation } from "react-i18next"; -import { CheckCircleIcon } from "@chakra-ui/icons"; +import { Trans, useTranslation } from "react-i18next"; +import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons"; import { + Button, Modal, ModalCloseButton, ModalContent, @@ -21,23 +22,85 @@ import useAwsS3 from "@/hooks/useAwsS3"; import useGlobalStore from "@/pages/globalStore"; export type TFileItem = { - status: boolean; + status: string; fileName: string; }; function UploadButton(props: { onUploadSuccess: Function; children: React.ReactElement }) { const { isOpen, onOpen, onClose } = useDisclosure(); const { currentStorage, prefix } = useStorageStore(); - const { showSuccess } = useGlobalStore(); + const { showSuccess, showError } = useGlobalStore(); const { uploadFile } = useAwsS3(); const [fileList, setFileList] = React.useState([]); const { t } = useTranslation(); const darkMode = useColorMode().colorMode === "dark"; const { onUploadSuccess, children } = props; + const [failedFileList, setFailedFileList] = React.useState([]); + + const handleUpload = async (files: any) => { + setFailedFileList([]); + const newFileList = Array.from(files).map((item: any) => ({ + fileName: + files[0] instanceof File + ? item.webkitRelativePath + ? item.webkitRelativePath.replace(/^[^/]*\//, "") + : item.name + : item.webkitRelativePath + ? item.webkitRelativePath + : item.file.name, + status: "pending", + })); + setFileList(newFileList); + const tasks = []; + const failedFiles: any[] = []; + for (let i = 0; i < files.length; i++) { + const file = files[0] instanceof File ? files[i] : files[i].file; + const fileName = + files[0] instanceof File + ? file.webkitRelativePath + ? file.webkitRelativePath.replace(/^[^/]*\//, "") + : file.name + : files[i].webkitRelativePath + ? files[i].webkitRelativePath + : file.name; + const task = uploadFile(currentStorage?.name!, prefix + fileName, file, { + contentType: file.type, + }) + .then(() => { + setFileList((pre) => { + const newList = [...pre]; + newList[i].status = "success"; + return newList; + }); + return true; + }) + .catch(() => { + setFileList((pre) => { + const newList = [...pre]; + newList[i].status = "fail"; + failedFiles.push(files[i]); + return newList; + }); + return false; + }); + tasks.push(task); + } + const res = await Promise.all(tasks); + onUploadSuccess(); + if (!res.includes(false)) { + onClose(); + showSuccess(t("StoragePanel.Success")); + } else { + setFailedFileList(failedFiles); + showError(t("StoragePanel.Fail")); + } + }; + return (
{React.cloneElement(children, { onClick: () => { setFileList([]); + setFailedFileList([]); onOpen(); }, })} @@ -48,45 +111,27 @@ function UploadButton(props: { onUploadSuccess: Function; children: React.ReactE {t("StoragePanel.UploadFile")}
- { - const newFileList = Array.from(files).map((item: any) => ({ - fileName: - files[0] instanceof File - ? item.webkitRelativePath - ? item.webkitRelativePath.replace(/^[^/]*\//, "") - : item.name - : item.webkitRelativePath - ? item.webkitRelativePath - : item.file.name, - status: false, - })); - setFileList(newFileList); - for (let i = 0; i < files.length; i++) { - const file = files[0] instanceof File ? files[i] : files[i].file; - const fileName = - files[0] instanceof File - ? file.webkitRelativePath - ? file.webkitRelativePath.replace(/^[^/]*\//, "") - : file.name - : files[i].webkitRelativePath - ? files[i].webkitRelativePath - : file.name; - await uploadFile(currentStorage?.name!, prefix + fileName, file, { - contentType: file.type, - }); - setFileList((pre) => { - const newList = [...pre]; - newList[i].status = true; - return newList; - }); - } - onUploadSuccess(); - onClose(); - showSuccess(t("StoragePanel.Success")); - }} - darkMode={darkMode} - /> + + {!!failedFileList.length && ( +
+ + +
+ )}
{fileList.map((item) => { return ( @@ -98,10 +143,12 @@ function UploadButton(props: { onUploadSuccess: Function; children: React.ReactE )} > {item.fileName} - {item.status ? ( + {item.status === "success" && ( - ) : ( - + )} + {item.status === "pending" && } + {item.status === "fail" && ( + )}
);