diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 2147e949..7fa5c706 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -79,7 +79,9 @@ "no-unused-resources": "No unused resources", "name": "Name", "delete-selected-resources": "Delete Selected Resources", - "no-files-selected": "No files selected❗" + "no-files-selected": "No files selected❗", + "upload-successfully": "Upload successfully", + "file-drag-drop-prompt": "Drag and drop your file here to upload file" }, "archived": { "archived-memos": "Archived Memos", diff --git a/web/src/locales/zh-Hant.json b/web/src/locales/zh-Hant.json index 7ed26d9f..0b0b6365 100644 --- a/web/src/locales/zh-Hant.json +++ b/web/src/locales/zh-Hant.json @@ -79,7 +79,9 @@ "no-unused-resources": "無可刪除的資源", "name": "資源名稱", "delete-selected-resources": "刪除選中資源", - "no-files-selected": "沒有文件被選中❗" + "no-files-selected": "沒有文件被選中❗", + "upload-successfully": "上傳成功", + "file-drag-drop-prompt": "將您的文件拖放到此處以上傳文件" }, "archived": { "archived-memos": "已封存的 Memo", diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 41ce9297..e87df674 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -79,7 +79,9 @@ "no-unused-resources": "无可删除的资源", "name": "资源名称", "delete-selected-resources": "删除选中资源", - "no-files-selected": "没有文件被选中❗" + "no-files-selected": "没有文件被选中❗", + "upload-successfully": "上传成功", + "file-drag-drop-prompt": "将您的文件拖放到此处以上传文件" }, "archived": { "archived-memos": "已归档的 Memo", diff --git a/web/src/pages/ResourcesDashboard.tsx b/web/src/pages/ResourcesDashboard.tsx index 702e211f..d80a1393 100644 --- a/web/src/pages/ResourcesDashboard.tsx +++ b/web/src/pages/ResourcesDashboard.tsx @@ -20,6 +20,7 @@ const ResourcesDashboard = () => { const [selectedList, setSelectedList] = useState>([]); const [isVisible, setIsVisible] = useState(false); const [queryText, setQueryText] = useState(""); + const [dragActive, setDragActive] = useState(false); useEffect(() => { resourceStore @@ -94,70 +95,114 @@ const ResourcesDashboard = () => { } }; + const handleDrag = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") { + setDragActive(true); + } else if (e.type === "dragleave") { + setDragActive(false); + } + }; + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + await resourceStore.createResourcesWithBlob(e.dataTransfer.files).then( + (res) => { + for (const resource of res) { + toast.success(`${resource.filename} ${t("resources.upload-successfully")}`); + } + }, + (reason) => { + toast.error(reason); + } + ); + } + }; + return (
-
-
-

- {t("common.resources")} -

- -
-
- {isVisible && ( - - )} - - - - - } - actions={ - <> - - - } - /> -
-
- {loadingState.isLoading ? ( -
-

{t("resources.fetching-data")}

+
+ {dragActive && ( +
+
+

{t("resources.file-drag-drop-prompt")}

- ) : ( -
- {resources.length === 0 ? ( -

{t("resources.no-resources")}

- ) : ( - resources - .filter((res: Resource) => (queryText === "" ? true : res.filename.toLowerCase().includes(queryText.toLowerCase()))) - .map((resource) => ( - handleCheckBtnClick(resource.id)} - handleUncheckClick={() => handleUncheckBtnClick(resource.id)} - > - )) - )} -
- )} +
+ )} + +
+
+

+ {t("common.resources")} +

+ +
+
+ {isVisible && ( + + )} + + + + + } + actions={ + <> + + + } + /> +
+
+ {loadingState.isLoading ? ( +
+

{t("resources.fetching-data")}

+
+ ) : ( +
+ {resources.length === 0 ? ( +

{t("resources.no-resources")}

+ ) : ( + resources + .filter((res: Resource) => (queryText === "" ? true : res.filename.toLowerCase().includes(queryText.toLowerCase()))) + .map((resource) => ( + handleCheckBtnClick(resource.id)} + handleUncheckClick={() => handleUncheckBtnClick(resource.id)} + > + )) + )} +
+ )} +
diff --git a/web/src/store/module/resource.ts b/web/src/store/module/resource.ts index f9307dbd..8b6c712d 100644 --- a/web/src/store/module/resource.ts +++ b/web/src/store/module/resource.ts @@ -47,6 +47,24 @@ export const useResourceStore = () => { store.dispatch(setResources([resource, ...resourceList])); return resource; }, + async createResourcesWithBlob(files: FileList): Promise> { + let newResourceList: Array = []; + for (const file of files) { + const { name: filename, size } = file; + if (size > MAX_FILE_SIZE) { + return Promise.reject(`${filename} overload max size: 32MB`); + } + + const formData = new FormData(); + formData.append("file", file, filename); + const { data } = (await api.createResourceWithBlob(formData)).data; + const resource = convertResponseModelResource(data); + newResourceList = [resource, ...newResourceList]; + } + const resourceList = state.resources; + store.dispatch(setResources([...newResourceList, ...resourceList])); + return newResourceList; + }, async deleteResourceById(id: ResourceId) { await api.deleteResourceById(id); store.dispatch(deleteResource(id));