mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: upload file by drag and drop (#1388)
* stash: file upload * feat: support file upload by drag * feat: beautify the ui * feat: support file upload * stash * fix: the resource is incorrectly when upload multiple files * feat: beautify the ui * chore: reduce unused line * stash * chore: deleted unused line * chore: deleted unused line * chore * chore: change the function declare * feat: support to prompt file is too large * feat:drop prompt to cover all element * fix: eslint * fix: the name of i18n * chore: refactor the import deps * feat: beautify the ui * Update web/src/locales/en.json Co-authored-by: boojack <stevenlgtm@gmail.com> * chore: the import of deps * fix: the window size of fecting data --------- Co-authored-by: boojack <stevenlgtm@gmail.com>
This commit is contained in:
@@ -79,7 +79,9 @@
|
|||||||
"no-unused-resources": "No unused resources",
|
"no-unused-resources": "No unused resources",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"delete-selected-resources": "Delete Selected Resources",
|
"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": {
|
||||||
"archived-memos": "Archived Memos",
|
"archived-memos": "Archived Memos",
|
||||||
|
@@ -79,7 +79,9 @@
|
|||||||
"no-unused-resources": "無可刪除的資源",
|
"no-unused-resources": "無可刪除的資源",
|
||||||
"name": "資源名稱",
|
"name": "資源名稱",
|
||||||
"delete-selected-resources": "刪除選中資源",
|
"delete-selected-resources": "刪除選中資源",
|
||||||
"no-files-selected": "沒有文件被選中❗"
|
"no-files-selected": "沒有文件被選中❗",
|
||||||
|
"upload-successfully": "上傳成功",
|
||||||
|
"file-drag-drop-prompt": "將您的文件拖放到此處以上傳文件"
|
||||||
},
|
},
|
||||||
"archived": {
|
"archived": {
|
||||||
"archived-memos": "已封存的 Memo",
|
"archived-memos": "已封存的 Memo",
|
||||||
|
@@ -79,7 +79,9 @@
|
|||||||
"no-unused-resources": "无可删除的资源",
|
"no-unused-resources": "无可删除的资源",
|
||||||
"name": "资源名称",
|
"name": "资源名称",
|
||||||
"delete-selected-resources": "删除选中资源",
|
"delete-selected-resources": "删除选中资源",
|
||||||
"no-files-selected": "没有文件被选中❗"
|
"no-files-selected": "没有文件被选中❗",
|
||||||
|
"upload-successfully": "上传成功",
|
||||||
|
"file-drag-drop-prompt": "将您的文件拖放到此处以上传文件"
|
||||||
},
|
},
|
||||||
"archived": {
|
"archived": {
|
||||||
"archived-memos": "已归档的 Memo",
|
"archived-memos": "已归档的 Memo",
|
||||||
|
@@ -20,6 +20,7 @@ const ResourcesDashboard = () => {
|
|||||||
const [selectedList, setSelectedList] = useState<Array<ResourceId>>([]);
|
const [selectedList, setSelectedList] = useState<Array<ResourceId>>([]);
|
||||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||||
const [queryText, setQueryText] = useState<string>("");
|
const [queryText, setQueryText] = useState<string>("");
|
||||||
|
const [dragActive, setDragActive] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resourceStore
|
resourceStore
|
||||||
@@ -94,9 +95,52 @@ const ResourcesDashboard = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDrag = (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
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<HTMLDivElement>) => {
|
||||||
|
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 (
|
return (
|
||||||
<section className="w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800">
|
<section className="w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800">
|
||||||
<MobileHeader showSearch={false} />
|
<MobileHeader showSearch={false} />
|
||||||
|
<div className="w-full relative" onDragEnter={handleDrag}>
|
||||||
|
{dragActive && (
|
||||||
|
<div
|
||||||
|
className="absolute h-full w-full rounded-xl bg-zinc-800 dark:bg-white opacity-60 z-10"
|
||||||
|
onDragEnter={handleDrag}
|
||||||
|
onDragLeave={handleDrag}
|
||||||
|
onDragOver={handleDrag}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
<div className="flex h-full w-full">
|
||||||
|
<p className="m-auto text-2xl text-white dark:text-black">{t("resources.file-drag-drop-prompt")}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700 text-black dark:text-gray-300">
|
<div className="w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700 text-black dark:text-gray-300">
|
||||||
<div className="relative w-full flex flex-row justify-between items-center">
|
<div className="relative w-full flex flex-row justify-between items-center">
|
||||||
<p className="flex flex-row justify-start items-center select-none rounded">
|
<p className="flex flex-row justify-start items-center select-none rounded">
|
||||||
@@ -160,6 +204,7 @@ const ResourcesDashboard = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -47,6 +47,24 @@ export const useResourceStore = () => {
|
|||||||
store.dispatch(setResources([resource, ...resourceList]));
|
store.dispatch(setResources([resource, ...resourceList]));
|
||||||
return resource;
|
return resource;
|
||||||
},
|
},
|
||||||
|
async createResourcesWithBlob(files: FileList): Promise<Array<Resource>> {
|
||||||
|
let newResourceList: Array<Resource> = [];
|
||||||
|
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) {
|
async deleteResourceById(id: ResourceId) {
|
||||||
await api.deleteResourceById(id);
|
await api.deleteResourceById(id);
|
||||||
store.dispatch(deleteResource(id));
|
store.dispatch(deleteResource(id));
|
||||||
|
Reference in New Issue
Block a user