feat: notification batching, per-folder mute, and global pause
Debounce embedding/tagging completion notifications per folder (6s window) so rapid file additions from downloaders fire one notification instead of one per file. Add per-folder mute via the sidebar context menu and a global pause toggle in Settings > General, both persisted across restarts.
This commit is contained in:
@@ -162,6 +162,8 @@ export function SettingsModal() {
|
||||
const clearTaggingJobs = useGalleryStore((state) => state.clearTaggingJobs);
|
||||
const clearTaggingJobsForFolders = useGalleryStore((state) => state.clearTaggingJobsForFolders);
|
||||
const openAppDataFolder = useGalleryStore((state) => state.openAppDataFolder);
|
||||
const notificationsPaused = useGalleryStore((state) => state.notificationsPaused);
|
||||
const setNotificationsPaused = useGalleryStore((state) => state.setNotificationsPaused);
|
||||
const getDatabaseInfo = useGalleryStore((state) => state.getDatabaseInfo);
|
||||
const vacuumDatabase = useGalleryStore((state) => state.vacuumDatabase);
|
||||
const getOrphanedThumbnailsInfo = useGalleryStore((state) => state.getOrphanedThumbnailsInfo);
|
||||
@@ -601,6 +603,26 @@ export function SettingsModal() {
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
title="Notifications"
|
||||
description="Notifications are batched per folder — a single alert fires once activity settles. Mute individual folders from their right-click menu."
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 rounded-xl border border-white/[0.07] bg-black/20 p-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">Pause all notifications</p>
|
||||
<p className="mt-0.5 text-xs text-gray-500">Suppress all indexing notifications until re-enabled.</p>
|
||||
</div>
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={notificationsPaused}
|
||||
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${notificationsPaused ? "bg-sky-500" : "bg-white/15"}`}
|
||||
onClick={() => setNotificationsPaused(!notificationsPaused)}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${notificationsPaused ? "translate-x-4" : "translate-x-0"}`} />
|
||||
</button>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
title="Compact database"
|
||||
description="Reclaims wasted space left behind when images or tags are deleted. Safe to run at any time."
|
||||
|
||||
@@ -11,18 +11,22 @@ interface ContextMenuState {
|
||||
function FolderContextMenu({
|
||||
menu,
|
||||
folder,
|
||||
isMuted,
|
||||
onClose,
|
||||
onRename,
|
||||
onReindex,
|
||||
onLocate,
|
||||
onToggleMute,
|
||||
onRemove,
|
||||
}: {
|
||||
menu: ContextMenuState;
|
||||
folder: Folder;
|
||||
isMuted: boolean;
|
||||
onClose: () => void;
|
||||
onRename: () => void;
|
||||
onReindex: () => void;
|
||||
onLocate: () => void;
|
||||
onToggleMute: () => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -61,6 +65,7 @@ function FolderContextMenu({
|
||||
>
|
||||
{item("Reindex", onReindex)}
|
||||
{item("Rename", onRename)}
|
||||
{item(isMuted ? "Unmute notifications" : "Mute notifications", onToggleMute)}
|
||||
{folder.scan_error && item("Locate Folder", onLocate)}
|
||||
<div className="my-1 border-t border-white/[0.06]" />
|
||||
{item("Remove from app", onRemove, true)}
|
||||
@@ -77,7 +82,9 @@ function FolderItem({
|
||||
selected: boolean;
|
||||
progress: IndexProgress | undefined;
|
||||
}) {
|
||||
const { selectFolder, removeFolder, reindexFolder, renameFolder, updateFolderPath } = useGalleryStore();
|
||||
const { selectFolder, removeFolder, reindexFolder, renameFolder, updateFolderPath, toggleMutedFolder } = useGalleryStore();
|
||||
const mutedFolderIds = useGalleryStore((state) => state.mutedFolderIds);
|
||||
const isMuted = mutedFolderIds.includes(folder.id);
|
||||
const isIndexing = progress && !progress.done;
|
||||
const isMissing = !!folder.scan_error && !isIndexing;
|
||||
|
||||
@@ -250,10 +257,12 @@ function FolderItem({
|
||||
<FolderContextMenu
|
||||
menu={contextMenu}
|
||||
folder={folder}
|
||||
isMuted={isMuted}
|
||||
onClose={() => setContextMenu(null)}
|
||||
onRename={() => setRenaming(true)}
|
||||
onReindex={() => void reindexFolder(folder.id)}
|
||||
onLocate={() => void handleLocateFolder()}
|
||||
onToggleMute={() => toggleMutedFolder(folder.id)}
|
||||
onRemove={() => setConfirmingRemoval(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user