Initial commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
import { useTransition } from "react";
|
||||
import { useSelection } from "./SelectionProvider";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Trash2, X, ListChecks } from "lucide-react";
|
||||
import { deleteImages, bulkSetWatched, bulkSetMark } from "@/app/actions/bulk";
|
||||
import { useUndoDeleteToast } from "@/components/select/UndoDeleteToast";
|
||||
import { useSettings } from "@/components/settings/SettingsProvider";
|
||||
import { MarkAsMenu } from "./MarkAsMenu";
|
||||
import { useWatchQueue } from "@/components/queue/WatchQueueProvider";
|
||||
import { dispatchQueueRemove } from "@/components/queue/watchQueueEvents";
|
||||
import { ListVideo } from "lucide-react";
|
||||
|
||||
export function SelectionBar() {
|
||||
const { ids, clear, visibleIds, selectMany } = useSelection();
|
||||
const { settings } = useSettings();
|
||||
const { show: showUndo } = useUndoDeleteToast();
|
||||
const [pending, start] = useTransition();
|
||||
const router = useRouter();
|
||||
const queue = useWatchQueue();
|
||||
|
||||
if (ids.size === 0) return null;
|
||||
const count = ids.size;
|
||||
const allVisibleSelected = visibleIds.length > 0 && visibleIds.every((id) => ids.has(id));
|
||||
|
||||
const onDelete = (e: React.MouseEvent) => {
|
||||
const permanent = e.shiftKey || !settings.useRecycleBin;
|
||||
if (permanent) {
|
||||
if (!confirm(`Permanently delete ${count} cover${count === 1 ? "" : "s"}? Cannot be undone.`)) return;
|
||||
}
|
||||
const targetIds = Array.from(ids);
|
||||
start(async () => {
|
||||
await deleteImages(targetIds, permanent ? { permanent: true } : undefined);
|
||||
clear();
|
||||
router.refresh();
|
||||
if (!permanent) showUndo(targetIds);
|
||||
});
|
||||
};
|
||||
|
||||
const onSelectAllToggle = () => {
|
||||
if (allVisibleSelected) clear();
|
||||
else selectMany(visibleIds);
|
||||
};
|
||||
|
||||
const onMarkAs = (action: "watched" | "unwatched" | "vip" | "favorite" | "unmark") => {
|
||||
const targetIds = Array.from(ids);
|
||||
start(async () => {
|
||||
try {
|
||||
if (action === "watched") { await bulkSetWatched(targetIds, true); dispatchQueueRemove(targetIds); }
|
||||
else if (action === "unwatched") await bulkSetWatched(targetIds, false);
|
||||
else if (action === "vip") await bulkSetMark(targetIds, "vip");
|
||||
else if (action === "favorite") await bulkSetMark(targetIds, "favorite");
|
||||
else if (action === "unmark") await bulkSetMark(targetIds, "unmarked");
|
||||
router.refresh();
|
||||
} catch (err) {
|
||||
console.error(`[bulk ${action}] failed:`, err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-[80px] left-1/2 -translate-x-1/2 z-50">
|
||||
<div
|
||||
className="rounded-2xl shadow-2xl px-4 py-2.5 flex items-center gap-3 border border-[var(--color-glass-border-strong)] backdrop-blur-2xl"
|
||||
style={{ background: "color-mix(in oklch, var(--color-bg-0) 85%, transparent)" }}
|
||||
>
|
||||
<span className="text-sm font-mono tabular-nums">
|
||||
<span className="text-[var(--color-cyan)] font-semibold">{count}</span>
|
||||
<span className="text-[var(--color-fg-dim)]"> selected</span>
|
||||
</span>
|
||||
<div className="w-px h-5 bg-[var(--color-glass-border)]" />
|
||||
{visibleIds.length > 0 && (
|
||||
<button
|
||||
onClick={onSelectAllToggle}
|
||||
className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-lg glass hover:text-[var(--color-fg)]"
|
||||
>
|
||||
<ListChecks className="w-3.5 h-3.5" />
|
||||
{allVisibleSelected ? "Deselect All" : `All (${visibleIds.length})`}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => { queue.addMany(Array.from(ids)); }}
|
||||
title="Add to watch queue"
|
||||
className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-lg glass hover:text-[var(--color-fg)]"
|
||||
>
|
||||
<ListVideo className="w-3.5 h-3.5" /> Queue
|
||||
</button>
|
||||
<MarkAsMenu onAction={onMarkAs} disabled={pending} />
|
||||
<button
|
||||
onClick={onDelete}
|
||||
disabled={pending}
|
||||
title={settings.useRecycleBin ? "Send to trash · Shift-click for permanent delete" : "Delete permanently"}
|
||||
className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-lg bg-[var(--color-coral)]/15 text-[var(--color-coral)] border border-[var(--color-coral)]/40 hover:bg-[var(--color-coral)]/25 disabled:opacity-40"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" /> {pending ? "Deleting…" : "Delete"}
|
||||
</button>
|
||||
<button
|
||||
onClick={clear}
|
||||
className="flex items-center gap-1.5 text-xs px-2 py-1.5 rounded-lg text-[var(--color-fg-dim)] hover:text-[var(--color-fg)]"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" /> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user