fix: tile overlay reactivity + facets showing all marked/noted photos
Problem 1 — per-photo progress overlay never rendered on the grid:
- bulkPhotoStates was `$state(new Map())`; a `.get(uid)` read in PhotoTile
didn't reliably re-run when the entry flipped, so the spinner/check/X
overlay never appeared. Switch to SvelteMap (svelte/reactivity).
Problem 2 — Notes / Colors / Ratings only showed the newest ~1000 photos:
- All three derived from `listPhotos({ count: 1000 })`, silently hiding
older marked/noted photos.
- listPhotosWithNotes now pages the whole library.
- Add listPhotosByUids() and resolve the Colors/Ratings marks-pool from the
complete marked-UID set (from getAllMarks) instead of the newest slice;
wire it into the TagsBrowserSidebar panel and the tag drill page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -186,6 +186,26 @@ export async function listPhotos(params: ListPhotosParams = {}): Promise<PpPhoto
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve photos for an explicit set of UIDs. Used by the Colors / Ratings
|
||||
* facets, whose member set comes from the mule-sidecar marks store and is NOT
|
||||
* bounded to the newest N photos — a marked photo anywhere in the library must
|
||||
* resolve. Fetches per-UID (concurrency-bounded) via the same `/photos/:uid`
|
||||
* endpoint the metadata panel uses, so it can't drift from PhotoPrism's search
|
||||
* DSL. Missing UIDs (deleted since marked) are skipped.
|
||||
*/
|
||||
export async function listPhotosByUids(uids: string[]): Promise<PpPhoto[]> {
|
||||
if (uids.length === 0) return [];
|
||||
const out: PpPhoto[] = [];
|
||||
const concurrency = 8;
|
||||
for (let i = 0; i < uids.length; i += concurrency) {
|
||||
const slice = uids.slice(i, i + concurrency);
|
||||
const fetched = await Promise.all(slice.map((uid) => getPhoto(uid).catch(() => null)));
|
||||
for (const p of fetched) if (p) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a page of photos *anchored at* a specific TakenAt — `before`
|
||||
* older photos preceded by `after` newer ones, merged newest-first.
|
||||
@@ -619,17 +639,25 @@ export interface PhotoWithNote {
|
||||
}
|
||||
|
||||
export async function listPhotosWithNotes(): Promise<PhotoWithNote[]> {
|
||||
const list = await listPhotos({ count: 1000, order: 'newest', merged: true });
|
||||
// PhotoPrism has no "caption is not empty" search filter, so we page the
|
||||
// whole library and keep the captioned rows. Paging (rather than a single
|
||||
// count:1000 fetch) means notes on photos older than the newest 1000 still
|
||||
// surface — the previous cap silently hid them.
|
||||
const out: PhotoWithNote[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const p of list) {
|
||||
// `merged: true` can repeat a photo across file-rows; dedupe by UID
|
||||
// so the same tile doesn't render twice.
|
||||
if (seen.has(p.UID)) continue;
|
||||
seen.add(p.UID);
|
||||
const note = p.Caption?.trim();
|
||||
if (!note) continue;
|
||||
out.push({ photo: p, note });
|
||||
const PAGE = 1000;
|
||||
for (let offset = 0; ; offset += PAGE) {
|
||||
const list = await listPhotos({ count: PAGE, offset, order: 'newest', merged: true });
|
||||
for (const p of list) {
|
||||
// `merged: true` can repeat a photo across file-rows; dedupe by UID
|
||||
// so the same tile doesn't render twice.
|
||||
if (seen.has(p.UID)) continue;
|
||||
seen.add(p.UID);
|
||||
const note = p.Caption?.trim();
|
||||
if (!note) continue;
|
||||
out.push({ photo: p, note });
|
||||
}
|
||||
if (list.length < PAGE) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user