feat(web): native favorites, lightbox zoom, EXIF jump links + perf fix
- Favorites use PhotoPrism's own like/unlike endpoint (not a mule-only mark) so they sync to third-party gallery apps, with heart controls in the tile, sidebar, and an `f` shortcut. - Lightbox: wheel-zoom around cursor, double-click to 2.5x, drag-to-pan, auto-upgrades to the fit_2048 tile past 1.25x zoom. - Sidebar: copy-EXIF button, clickable Camera/Lens values that jump to a filtered timeline (camera:/lens: DSL), matching the existing Country link. - Fix filtersToQ() quoting the entire search string whenever it contained a colon, which silently turned any raw DSL operator (camera:, taken:2024, etc.) into a literal phrase search — discovered while verifying the new jump-links against production. - Disable TanStack Query's refetchOnWindowFocus: the indexer WebSocket already invalidates photo queries on real changes, so the focus refetch was just a redundant full-timeline re-render on tab-switch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,8 @@ import {
|
||||
batchArchive,
|
||||
batchRestore,
|
||||
buildTakenAtPatch,
|
||||
likePhoto,
|
||||
unlikePhoto,
|
||||
updatePhoto
|
||||
} from './photoprism';
|
||||
import { queryClient } from '$lib/queryClient';
|
||||
@@ -121,6 +123,70 @@ export async function acceptDateAndKeep(uids: string[]): Promise<void> {
|
||||
toast.success(`Kept ${uids.length}`, { id: tid });
|
||||
}
|
||||
|
||||
/** Patch `Favorite` on every cached copy of the uids (timeline pages,
|
||||
* per-photo detail) so hearts flip instantly without a refetch. */
|
||||
function patchFavoriteCaches(uids: string[], value: boolean): void {
|
||||
const target = new Set(uids);
|
||||
const lists = queryClient.getQueriesData({ queryKey: ['photos'] });
|
||||
for (const [key, data] of lists) {
|
||||
if (!data) continue;
|
||||
if (Array.isArray(data)) {
|
||||
queryClient.setQueryData(
|
||||
key,
|
||||
(data as PpPhoto[]).map((p) => (target.has(p.UID) ? { ...p, Favorite: value } : p))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const pages = (data as { pages?: PpPhoto[][] }).pages;
|
||||
if (!Array.isArray(pages)) continue;
|
||||
queryClient.setQueryData(key, {
|
||||
...(data as object),
|
||||
pages: pages.map((pg) =>
|
||||
pg.map((p) => (target.has(p.UID) ? { ...p, Favorite: value } : p))
|
||||
)
|
||||
});
|
||||
}
|
||||
for (const uid of uids) {
|
||||
const p = queryClient.getQueryData<PpPhoto>(['photo', uid]);
|
||||
if (p) queryClient.setQueryData(['photo', uid], { ...p, Favorite: value });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle PhotoPrism's native favorite flag on a set of photos. Target
|
||||
* state comes from the first uid (mixed selections converge). Optimistic
|
||||
* cache flip with rollback; undo re-toggles.
|
||||
*/
|
||||
export async function toggleFavorite(uids: string[]): Promise<void> {
|
||||
if (uids.length === 0) {
|
||||
toast.message('Nothing to favorite', {
|
||||
description: 'Click a photo or select some first'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const value = !(cachedPhoto(uids[0])?.Favorite ?? false);
|
||||
patchFavoriteCaches(uids, value);
|
||||
const { errors } = await batchEdit(uids, (id) => (value ? likePhoto(id) : unlikePhoto(id)));
|
||||
if (errors.length) {
|
||||
patchFavoriteCaches(uids, !value);
|
||||
toast.error(`Favorite failed on ${errors.length}`, { description: errors[0].message });
|
||||
return;
|
||||
}
|
||||
toast.success(
|
||||
value
|
||||
? uids.length === 1
|
||||
? 'Added to favorites'
|
||||
: `Favorited ${uids.length}`
|
||||
: uids.length === 1
|
||||
? 'Removed from favorites'
|
||||
: `Unfavorited ${uids.length}`
|
||||
);
|
||||
pushUndo(value ? `Favorited ${uids.length}` : `Unfavorited ${uids.length}`, async () => {
|
||||
patchFavoriteCaches(uids, !value);
|
||||
await batchEdit(uids, (id) => (value ? unlikePhoto(id) : likePhoto(id)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive photos. Reversible via the undo stack (Restore on ⌘Z).
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user