revert evictFromCache, add back delay + marks invalidation
Remove evictFromCache entirely - the cache-manipulation approach was brittle and broke both archive (photos not removed) and tags (colors/ratings showing empty). Replace with: 200ms delay before invalidation to let PhotoPrism's indexer process the change, plus invalidateQueries(['marks']) so tag caches (colors, ratings, notes) refresh alongside the photo timeline.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { batchEdit } from '$lib/services/batch';
|
import { batchEdit } from '$lib/services/batch';
|
||||||
import { invalidatePhotos, evictFromCache } from '$lib/services/bulk';
|
import { invalidatePhotos } from '$lib/services/bulk';
|
||||||
import {
|
import {
|
||||||
addToHeap,
|
addToHeap,
|
||||||
approvePhoto,
|
approvePhoto,
|
||||||
@@ -194,10 +194,10 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
doneBulk(doneLabel, ids);
|
doneBulk(doneLabel, ids);
|
||||||
evictFromCache(ids);
|
|
||||||
focusAfter(ids);
|
focusAfter(ids);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
invalidatePhotos(ids);
|
invalidatePhotos(ids);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['marks'] });
|
||||||
toast.success(doneLabel, { id: tid });
|
toast.success(doneLabel, { id: tid });
|
||||||
pushUndo(doneLabel, async () => {
|
pushUndo(doneLabel, async () => {
|
||||||
if (target) await batchRestore(ids);
|
if (target) await batchRestore(ids);
|
||||||
@@ -234,10 +234,10 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
doneBulk(`Deleted ${ids.length}`, ids);
|
doneBulk(`Deleted ${ids.length}`, ids);
|
||||||
evictFromCache(ids);
|
|
||||||
focusAfter(ids);
|
focusAfter(ids);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
invalidatePhotos(ids);
|
invalidatePhotos(ids);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['marks'] });
|
||||||
toast.success(`Deleted ${ids.length}`, { id: tid });
|
toast.success(`Deleted ${ids.length}`, { id: tid });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +272,6 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
|||||||
doneBulk(`Kept ${ids.length}`, ids);
|
doneBulk(`Kept ${ids.length}`, ids);
|
||||||
toast.success(`Kept ${ids.length}`, { id: tid });
|
toast.success(`Kept ${ids.length}`, { id: tid });
|
||||||
}
|
}
|
||||||
evictFromCache(ids);
|
|
||||||
focusAfter(ids);
|
focusAfter(ids);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
invalidatePhotos(ids);
|
invalidatePhotos(ids);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
import { useQueryClient } from '@tanstack/svelte-query';
|
import { useQueryClient } from '@tanstack/svelte-query';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { batchArchive } from '$lib/services/photoprism';
|
import { batchArchive } from '$lib/services/photoprism';
|
||||||
import { evictFromCache } from '$lib/services/bulk';
|
|
||||||
import { startBulk, doneBulk, failBulk } from '$lib/stores/bulkAction.svelte';
|
import { startBulk, doneBulk, failBulk } from '$lib/stores/bulkAction.svelte';
|
||||||
import PhotoGrid from '$lib/components/timeline/PhotoGrid.svelte';
|
import PhotoGrid from '$lib/components/timeline/PhotoGrid.svelte';
|
||||||
import { type ReviewGroup } from '$lib/services/adapters/review';
|
import { type ReviewGroup } from '$lib/services/adapters/review';
|
||||||
@@ -36,11 +35,11 @@
|
|||||||
startBulk(`Archiving…`, uids);
|
startBulk(`Archiving…`, uids);
|
||||||
try {
|
try {
|
||||||
await batchArchive(uids);
|
await batchArchive(uids);
|
||||||
evictFromCache(uids);
|
|
||||||
doneBulk(`Archived ${uids.length}`, uids);
|
doneBulk(`Archived ${uids.length}`, uids);
|
||||||
toast.success(`Archived ${uids.length}`, { id: tid });
|
toast.success(`Archived ${uids.length}`, { id: tid });
|
||||||
void qc.invalidateQueries({ queryKey: ['review-groups'] });
|
void qc.invalidateQueries({ queryKey: ['review-groups'] });
|
||||||
void qc.invalidateQueries({ queryKey: ['photos'] });
|
void qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
|
void qc.invalidateQueries({ queryKey: ['marks'] });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failBulk(uids);
|
failBulk(uids);
|
||||||
toast.error(err instanceof Error ? err.message : 'Archive all failed', { id: tid });
|
toast.error(err instanceof Error ? err.message : 'Archive all failed', { id: tid });
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
} from '$lib/services/photoprism';
|
} from '$lib/services/photoprism';
|
||||||
import { batchEdit } from '$lib/services/batch';
|
import { batchEdit } from '$lib/services/batch';
|
||||||
import { acceptDateAndKeep, cachedPhoto } from '$lib/services/photoActions';
|
import { acceptDateAndKeep, cachedPhoto } from '$lib/services/photoActions';
|
||||||
import { evictFromCache } from '$lib/services/bulk';
|
|
||||||
import { suggestDateFromPath } from '$lib/utils/suggestDateFromPath';
|
import { suggestDateFromPath } from '$lib/utils/suggestDateFromPath';
|
||||||
import { photoNameAndDir } from '$lib/types/photoprism';
|
import { photoNameAndDir } from '$lib/types/photoprism';
|
||||||
import {
|
import {
|
||||||
@@ -130,6 +129,7 @@
|
|||||||
const result = await fn();
|
const result = await fn();
|
||||||
if (bulk) {
|
if (bulk) {
|
||||||
doneBulk(bulk.doneLabel, bulk.ids);
|
doneBulk(bulk.doneLabel, bulk.ids);
|
||||||
|
await delay(200);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -138,6 +138,8 @@
|
|||||||
} finally {
|
} finally {
|
||||||
busy = false;
|
busy = false;
|
||||||
void qc.invalidateQueries({ queryKey: ['photos'] });
|
void qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
|
void qc.invalidateQueries({ queryKey: ['marks'] });
|
||||||
|
void qc.invalidateQueries({ queryKey: ['review-groups'] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +154,6 @@
|
|||||||
setDetail(p?.FileName ?? completedId);
|
setDetail(p?.FileName ?? completedId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
evictFromCache(ids);
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
toast.error(`Kept ${updated.length}; ${errors.length} failed`, { id: tid });
|
toast.error(`Kept ${updated.length}; ${errors.length} failed`, { id: tid });
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +181,6 @@
|
|||||||
await withBusy(async () => {
|
await withBusy(async () => {
|
||||||
try {
|
try {
|
||||||
await batchArchive(ids);
|
await batchArchive(ids);
|
||||||
evictFromCache(ids);
|
|
||||||
pushUndo(`Archived ${ids.length}`, async () => {
|
pushUndo(`Archived ${ids.length}`, async () => {
|
||||||
await batchRestore(ids);
|
await batchRestore(ids);
|
||||||
void qc.invalidateQueries({ queryKey: ['photos'] });
|
void qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
@@ -206,7 +206,6 @@
|
|||||||
await withBusy(async () => {
|
await withBusy(async () => {
|
||||||
try {
|
try {
|
||||||
await batchDelete(ids);
|
await batchDelete(ids);
|
||||||
evictFromCache(ids);
|
|
||||||
focusAfter(ids);
|
focusAfter(ids);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
toast.success(`Deleted ${ids.length}`, { id: tid });
|
toast.success(`Deleted ${ids.length}`, { id: tid });
|
||||||
@@ -223,7 +222,6 @@
|
|||||||
await withBusy(async () => {
|
await withBusy(async () => {
|
||||||
try {
|
try {
|
||||||
await batchRestore(ids);
|
await batchRestore(ids);
|
||||||
evictFromCache(ids);
|
|
||||||
pushUndo(`Restored ${ids.length}`, async () => {
|
pushUndo(`Restored ${ids.length}`, async () => {
|
||||||
await batchArchive(ids);
|
await batchArchive(ids);
|
||||||
void qc.invalidateQueries({ queryKey: ['photos'] });
|
void qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
|
|||||||
@@ -32,36 +32,12 @@ export function invalidatePhotos(uids: string[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function invalidateAllPhotoCaches(): void {
|
||||||
* Remove uids from every cached infinite photo-list query so the grid
|
void queryClient.invalidateQueries({ queryKey: ['photos'] });
|
||||||
* updates instantly instead of waiting for a refetch round-trip. Call
|
void queryClient.invalidateQueries({ queryKey: ['marks'] });
|
||||||
* after the API confirms the mutation, then still invalidate for eventual
|
void queryClient.invalidateQueries({ queryKey: ['labels'] });
|
||||||
* sync. Only targets infinite queries (those with a `pages` array) —
|
void queryClient.invalidateQueries({ queryKey: ['review-groups'] });
|
||||||
* flat list caches like marks-pool, with-notes, keywords, etc. are left
|
void queryClient.invalidateQueries({ queryKey: ['heaps'] });
|
||||||
* intact so tag/category drill pages don't lose referenced photos.
|
|
||||||
*/
|
|
||||||
export function evictFromCache(uids: string[]): void {
|
|
||||||
const uidSet = new Set(uids);
|
|
||||||
const lists = queryClient.getQueriesData<
|
|
||||||
{ pages?: PpPhoto[][] } | { pages?: unknown[] }
|
|
||||||
>({
|
|
||||||
queryKey: ['photos']
|
|
||||||
});
|
|
||||||
for (const [key, data] of lists) {
|
|
||||||
if (!data) continue;
|
|
||||||
if ('pages' in data && Array.isArray((data as { pages?: unknown[] }).pages)) {
|
|
||||||
const pages = (data as { pages: PpPhoto[][] }).pages;
|
|
||||||
let changed = false;
|
|
||||||
const filtered = pages.map((page: PpPhoto[]) => {
|
|
||||||
const f = page.filter((p) => !uidSet.has(p.UID));
|
|
||||||
if (f.length < page.length) changed = true;
|
|
||||||
return f;
|
|
||||||
});
|
|
||||||
if (changed) {
|
|
||||||
queryClient.setQueryData(key, { ...data, pages: filtered });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { batchEdit } from './batch';
|
import { batchEdit } from './batch';
|
||||||
import { invalidatePhotos, evictFromCache } from './bulk';
|
import { invalidatePhotos } from './bulk';
|
||||||
import {
|
import {
|
||||||
approvePhoto,
|
approvePhoto,
|
||||||
batchArchive,
|
batchArchive,
|
||||||
@@ -68,7 +68,6 @@ export async function dismissPhotos(uids: string[]): Promise<void> {
|
|||||||
if (uids.length === 0) return;
|
if (uids.length === 0) return;
|
||||||
const tid = toast.loading(`Dismissing ${uids.length}…`);
|
const tid = toast.loading(`Dismissing ${uids.length}…`);
|
||||||
const { updated, errors } = await batchEdit(uids, (id) => approvePhoto(id));
|
const { updated, errors } = await batchEdit(uids, (id) => approvePhoto(id));
|
||||||
evictFromCache(uids);
|
|
||||||
focusAfter(uids);
|
focusAfter(uids);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
invalidatePhotos(uids);
|
invalidatePhotos(uids);
|
||||||
@@ -108,7 +107,6 @@ export async function acceptDateAndKeep(uids: string[]): Promise<void> {
|
|||||||
await approvePhoto(id);
|
await approvePhoto(id);
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
evictFromCache(uids);
|
|
||||||
focusAfter(uids);
|
focusAfter(uids);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
invalidatePhotos(uids);
|
invalidatePhotos(uids);
|
||||||
@@ -135,7 +133,6 @@ export async function archivePhotos(uids: string[]): Promise<void> {
|
|||||||
toast.error(err instanceof Error ? err.message : 'Archive failed', { id: tid });
|
toast.error(err instanceof Error ? err.message : 'Archive failed', { id: tid });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
evictFromCache(uids);
|
|
||||||
pushUndo(`Archived ${uids.length}`, async () => {
|
pushUndo(`Archived ${uids.length}`, async () => {
|
||||||
await batchRestore(uids);
|
await batchRestore(uids);
|
||||||
invalidatePhotos(uids);
|
invalidatePhotos(uids);
|
||||||
|
|||||||
Reference in New Issue
Block a user