mirror of
https://github.com/usememos/memos.git
synced 2025-12-11 14:46:03 +08:00
feat(web): add ability to delete unused attachments (#5272)
This commit is contained in:
parent
60d977c0bf
commit
455eef9fa3
2 changed files with 59 additions and 4 deletions
|
|
@ -231,6 +231,8 @@
|
|||
"delete-selected-resources": "Delete Selected Resources",
|
||||
"delete-all-unused": "Delete all unused",
|
||||
"delete-all-unused-confirm": "Are you sure you want to delete all unused resources? THIS ACTION IS IRREVERSIBLE",
|
||||
"delete-all-unused-success": "Resources deleted successfully",
|
||||
"delete-all-unused-error": "Failed to delete unused resources",
|
||||
"fetching-data": "Fetching data…",
|
||||
"file-drag-drop-prompt": "Drag and drop your file here to upload file",
|
||||
"linked-amount": "Linked amount",
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
import dayjs from "dayjs";
|
||||
import { includes } from "lodash-es";
|
||||
import { PaperclipIcon, SearchIcon } from "lucide-react";
|
||||
import { PaperclipIcon, SearchIcon, Trash } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import AttachmentIcon from "@/components/AttachmentIcon";
|
||||
import ConfirmDialog from "@/components/ConfirmDialog";
|
||||
import Empty from "@/components/Empty";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { attachmentServiceClient } from "@/grpcweb";
|
||||
import useDialog from "@/hooks/useDialog";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import i18n from "@/i18n";
|
||||
import { attachmentStore } from "@/store";
|
||||
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
|
|
@ -39,6 +42,7 @@ const Attachments = observer(() => {
|
|||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const loadingState = useLoading();
|
||||
const deleteUnusedAttachmentsDialog = useDialog();
|
||||
const [state, setState] = useState<State>({
|
||||
searchQuery: "",
|
||||
});
|
||||
|
|
@ -88,6 +92,37 @@ const Attachments = observer(() => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRefetch = async () => {
|
||||
try {
|
||||
loadingState.setLoading();
|
||||
const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({
|
||||
pageSize: 50,
|
||||
});
|
||||
setAttachments(fetchedAttachments);
|
||||
setNextPageToken(nextPageToken ?? "");
|
||||
loadingState.setFinish();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
loadingState.setError();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUnusedAttachments = async () => {
|
||||
try {
|
||||
await Promise.all(
|
||||
unusedAttachments.map((attachment) => {
|
||||
return attachmentStore.deleteAttachment(attachment.name);
|
||||
}),
|
||||
);
|
||||
toast.success(t("resource.delete-all-unused-success"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(t("resource.delete-all-unused-error"));
|
||||
} finally {
|
||||
void handleRefetch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
|
||||
{!md && <MobileHeader />}
|
||||
|
|
@ -158,9 +193,17 @@ const Attachments = observer(() => {
|
|||
<div className="w-full flex flex-row justify-start items-start">
|
||||
<div className="w-16 sm:w-24 sm:pl-4 flex flex-col justify-start items-start"></div>
|
||||
<div className="w-full max-w-[calc(100%-4rem)] sm:max-w-[calc(100%-6rem)] flex flex-row justify-start items-start gap-4 flex-wrap">
|
||||
<div className="w-full flex flex-row justify-start items-center gap-2">
|
||||
<span className="text-muted-foreground">{t("resource.unused-resources")}</span>
|
||||
<span className="text-muted-foreground opacity-80">({unusedAttachments.length})</span>
|
||||
<div className="w-full flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<span className="text-muted-foreground">{t("resource.unused-resources")}</span>
|
||||
<span className="text-muted-foreground opacity-80">({unusedAttachments.length})</span>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="destructive" onClick={() => deleteUnusedAttachmentsDialog.open()} size="sm">
|
||||
<Trash />
|
||||
{t("resource.delete-all-unused")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{unusedAttachments.map((attachment) => {
|
||||
return (
|
||||
|
|
@ -193,6 +236,16 @@ const Attachments = observer(() => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
open={deleteUnusedAttachmentsDialog.isOpen}
|
||||
onOpenChange={deleteUnusedAttachmentsDialog.setOpen}
|
||||
title={t("resource.delete-all-unused-confirm")}
|
||||
confirmLabel={t("common.delete")}
|
||||
cancelLabel={t("common.cancel")}
|
||||
onConfirm={handleDeleteUnusedAttachments}
|
||||
confirmVariant="destructive"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue