feat(sidebar): pause/resume all background work from folder context menu

The onboarding tour tells users they can right-click a folder to pause its
background work, but only the per-worker buttons in the BackgroundTasks bar
existed. Add a 'Pause/Resume background work' context-menu item that toggles
all four workers (thumbnail/metadata/embedding/tagging) for the folder.

To keep the bar and the menu in sync, worker-pause state moves from
BackgroundTasks' local state into the store (the canonical owner of app
state), so pausing from either surface reflects in both. Reuses the existing
set_worker_paused / get_worker_states commands; no backend change.
This commit is contained in:
2026-06-13 09:42:51 +01:00
parent 2f66b0bdb8
commit 075c7e4cfb
3 changed files with 96 additions and 52 deletions
+75
View File
@@ -266,6 +266,18 @@ export type SortOrder =
export type UpdateStatus = "idle" | "checking" | "upToDate" | "available" | "downloading" | "installing" | "error";
export type WorkerKey = "thumbnail" | "metadata" | "embedding" | "tagging";
const WORKER_KEYS: WorkerKey[] = ["thumbnail", "metadata", "embedding", "tagging"];
interface FolderWorkerStates {
folder_id: number;
thumbnail_paused: boolean;
metadata_paused: boolean;
embedding_paused: boolean;
tagging_paused: boolean;
}
export type FfmpegStatus = "unknown" | "starting" | "downloading" | "unpacking" | "installed" | "error";
interface FfmpegProgressEvent {
@@ -329,6 +341,9 @@ interface GalleryState {
taggingQueueFolderIds: number[];
mutedFolderIds: number[];
notificationsPaused: boolean;
// Per-folder background-worker pause flags, shared by the BackgroundTasks
// bar and the sidebar folder context menu.
workerPaused: Record<number, Record<WorkerKey, boolean>>;
appVersion: string | null;
updateStatus: UpdateStatus;
@@ -421,6 +436,9 @@ interface GalleryState {
toggleMutedFolder: (folderId: number) => void;
loadNotificationsPaused: () => Promise<void>;
setNotificationsPaused: (paused: boolean) => void;
loadWorkerStates: () => Promise<void>;
setWorkerPaused: (folderId: number, worker: WorkerKey, paused: boolean) => void;
setAllWorkersPaused: (folderId: number, paused: boolean) => void;
loadAppVersion: () => Promise<void>;
checkForUpdates: (options?: { quiet?: boolean }) => Promise<void>;
installUpdate: () => Promise<void>;
@@ -708,6 +726,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
taggingQueueFolderIds: [],
mutedFolderIds: [],
notificationsPaused: false,
workerPaused: {},
appVersion: null,
updateStatus: "idle",
@@ -1526,6 +1545,62 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
void invoke("set_notifications_paused", { paused }).catch(() => {});
},
loadWorkerStates: async () => {
const folderIds = get().folders.map((folder) => folder.id);
if (folderIds.length === 0) {
set({ workerPaused: {} });
return;
}
try {
const states = await invoke<FolderWorkerStates[]>("get_worker_states", { folderIds });
set({
workerPaused: Object.fromEntries(
states.map((state) => [
state.folder_id,
{
thumbnail: state.thumbnail_paused,
metadata: state.metadata_paused,
embedding: state.embedding_paused,
tagging: state.tagging_paused,
},
]),
),
});
} catch {
// leave the existing snapshot in place
}
},
setWorkerPaused: (folderId, worker, paused) => {
set((state) => {
const current = state.workerPaused[folderId] ?? {
thumbnail: false,
metadata: false,
embedding: false,
tagging: false,
};
return {
workerPaused: {
...state.workerPaused,
[folderId]: { ...current, [worker]: paused },
},
};
});
void invoke("set_worker_paused", { worker, folderId, paused }).catch(() => {});
},
setAllWorkersPaused: (folderId, paused) => {
set((state) => ({
workerPaused: {
...state.workerPaused,
[folderId]: { thumbnail: paused, metadata: paused, embedding: paused, tagging: paused },
},
}));
for (const worker of WORKER_KEYS) {
void invoke("set_worker_paused", { worker, folderId, paused }).catch(() => {});
}
},
loadAppVersion: async () => {
try {
set({ appVersion: await getVersion() });