import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { Link } from "react-router-dom"; import { Button, EmptyState, Spinner, inputClass } from "@/components/ui"; import { api, apiUrl, errorMessage } from "@/lib/api"; import { cn } from "@/lib/cn"; import { formatBytes } from "@/lib/format"; import type { MediaItem, Paginated, StorageInfo } from "@/lib/types"; import { confirmAction } from "@/store/confirm"; import { toast } from "@/store/toasts"; function parseJIds(text: string): string[] { return text .split(/[\s,]+/) .map((value) => value.trim().toUpperCase()) .filter((value) => /^J-\d+$/.test(value)); } export default function DeletePage() { const queryClient = useQueryClient(); const [jIdsInput, setJIdsInput] = useState(""); const [loadedIds, setLoadedIds] = useState([]); const [selected, setSelected] = useState([]); const storageQuery = useQuery({ queryKey: ["storage"], queryFn: () => api("/api/storage/"), }); const filesQuery = useQuery({ queryKey: ["delete-files", loadedIds], queryFn: () => api>(`/api/files/?j_ids=${loadedIds.join(",")}`), enabled: loadedIds.length > 0, }); const deleteMutation = useMutation({ mutationFn: (jIds: string[]) => api<{ deleted: string[]; errors: { j_id?: string; error: string }[] }>( "/api/delete/", { method: "POST", json: { j_ids: jIds } }, ), onSuccess: (result) => { setSelected([]); setLoadedIds([]); toast.ok(`Deleted ${result.deleted.length} item(s).`); if (result.errors.length > 0) { toast.error( `${result.errors.length} item(s) could not be deleted: ` + result.errors.map((entry) => entry.error).join(", "), ); } void queryClient.invalidateQueries({ queryKey: ["storage"] }); void queryClient.invalidateQueries({ queryKey: ["files"] }); void queryClient.invalidateQueries({ queryKey: ["duplicates-md5"] }); }, onError: (error) => toast.error(errorMessage(error)), }); const tempMutation = useMutation({ mutationFn: () => api<{ removed: number }>("/api/temp/clear/", { method: "POST" }), onSuccess: (result) => { toast.ok(`Removed ${result.removed} temp file(s).`); void queryClient.invalidateQueries({ queryKey: ["storage"] }); }, onError: (error) => toast.error(errorMessage(error)), }); async function handleDeleteSelected() { const confirmed = await confirmAction({ title: `Delete ${selected.length} item(s)?`, description: "They are removed from the library along with all of their copies.", confirmLabel: "Delete", danger: true, }); if (confirmed) deleteMutation.mutate(selected); } async function handleClearTemp() { const confirmed = await confirmAction({ title: "Clear the temp folder?", description: "Staged uploads still sitting in it will be removed.", confirmLabel: "Clear", danger: true, }); if (confirmed) tempMutation.mutate(); } function toggleSelected(jId: string) { setSelected((current) => current.includes(jId) ? current.filter((value) => value !== jId) : [...current, jId], ); } const storage = storageQuery.data; return (

Delete & storage

Inspect disk usage and remove files from the library.

{storageQuery.isPending ? ( ) : storage ? ( <>

Library size

{formatBytes(storage.watched_folder.library_size)}

{storage.watched_folder.path}

{storage.library_items} items

Disk

{formatBytes(storage.watched_folder.used)} /{" "} {formatBytes(storage.watched_folder.total)}

= 95 ? "bg-ctp-red" : storage.watched_folder.percent_used >= 80 ? "bg-ctp-peach" : "bg-ctp-blue", )} style={{ width: `${storage.watched_folder.percent_used}%` }} />

{storage.watched_folder.percent_used}% used ·{" "} {formatBytes(storage.watched_folder.free)} free

Media folder

{formatBytes(storage.media.size)}

{storage.media.path}

Temp uploads

{formatBytes(storage.temp.size)} · {storage.temp.files} file(s)

) : ( )}

Delete files by J-ID