web(review): YYYY/MM path fallback + suggestion row below date + tighten 'a' gate
- suggestDateFromPath: when only year+month appear in the path (e.g. 2024/01/), synthesize day=01 so date-only foldering yields a usable suggestion instead of null. - RightSidebar: move the suggestion row below the Taken-at input. - BulkActionBar + gridKeyNav: show the "Accept date & Keep" button and fire the bare 'a' shortcut only when EVERY targeted photo has a path-derivable date — no more silent approve-without-fix for mixed selections. - gridKeyNav: drop local cachedPhoto duplicate, use the shared one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,8 @@ import {
|
||||
removeFromHeap,
|
||||
type PpAlbum
|
||||
} from '$lib/services/photoprism';
|
||||
import { acceptDateAndKeep } from '$lib/services/photoActions';
|
||||
import { acceptDateAndKeep, cachedPhoto } from '$lib/services/photoActions';
|
||||
import { suggestDateFromPath } from '$lib/utils/suggestDateFromPath';
|
||||
import { queryClient } from '$lib/queryClient';
|
||||
import { filters } from '$lib/stores/filters.svelte';
|
||||
import {
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
} from '$lib/stores/selection.svelte';
|
||||
import { popAndRun, push as pushUndo } from '$lib/stores/undo.svelte';
|
||||
import { openPreview, toggleLeftSidebar, toggleRightSidebar, view } from '$lib/stores/view.svelte';
|
||||
import type { PpPhoto } from '$lib/types/photoprism';
|
||||
|
||||
/**
|
||||
* Optional parameters the host passes via `use:gridKeyNav={...}`.
|
||||
@@ -161,35 +161,6 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Look up a photo's current cached state without forcing a refetch.
|
||||
* Walks every `['photos', …]` cache entry first, then the per-photo
|
||||
* cache. Lets `x` decide "archive vs restore" based on the actual current
|
||||
* state instead of always sending Archived=true.
|
||||
*
|
||||
* The `['photos', …]` namespace holds two shapes: a flat `PpPhoto[]`
|
||||
* (e.g. ratings/colors pools) and TanStack's `InfiniteData` envelope
|
||||
* (`{pages: PpPhoto[][], pageParams}`) used by the timeline's infinite
|
||||
* scroll. Walk both — assuming a flat array on the timeline cache used
|
||||
* to throw `list.find is not a function` and abort the F/X handlers. */
|
||||
function cachedPhoto(uid: string): PpPhoto | undefined {
|
||||
const lists = queryClient.getQueriesData({ queryKey: ['photos'] });
|
||||
for (const [, data] of lists) {
|
||||
if (!data) continue;
|
||||
if (Array.isArray(data)) {
|
||||
const hit = (data as PpPhoto[]).find((p) => p.UID === uid);
|
||||
if (hit) return hit;
|
||||
continue;
|
||||
}
|
||||
const pages = (data as { pages?: PpPhoto[][] }).pages;
|
||||
if (!Array.isArray(pages)) continue;
|
||||
for (const page of pages) {
|
||||
const hit = page?.find?.((p) => p.UID === uid);
|
||||
if (hit) return hit;
|
||||
}
|
||||
}
|
||||
return queryClient.getQueryData<PpPhoto>(['photo', uid]);
|
||||
}
|
||||
|
||||
async function toggleArchive(direction: 'archive' | 'restore' | 'toggle') {
|
||||
const ids = cullTargets();
|
||||
if (ids.length === 0) {
|
||||
@@ -503,25 +474,21 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
||||
}
|
||||
if (shift) return;
|
||||
// Bare `a` on the EXIF Stripped review tab fires the same
|
||||
// "Accept date & Keep" flow the bar button uses. Scoped to
|
||||
// that tab so the key stays free everywhere else (where a
|
||||
// path-derived date wouldn't make sense as a one-shot
|
||||
// shortcut). Reads location directly because actions sit
|
||||
// outside the component tree where `$app/state` is
|
||||
// idiomatic — same approach used by `filters.section`
|
||||
// elsewhere in this file.
|
||||
// "Accept date & Keep" flow as the bar button. Mirrors the
|
||||
// bar's all-targets-have-a-suggestion gate so the shortcut
|
||||
// can't silently approve photos without a date fix.
|
||||
if (
|
||||
filters.section === 'review' &&
|
||||
new URL(window.location.href).searchParams.get('tab') === 'stripped_exif'
|
||||
) {
|
||||
e.preventDefault();
|
||||
const ids = cullTargets();
|
||||
if (ids.length === 0) {
|
||||
toast.message('Nothing to keep', {
|
||||
description: 'Click a photo or select some first'
|
||||
});
|
||||
return;
|
||||
if (ids.length === 0) return;
|
||||
for (const id of ids) {
|
||||
const p = cachedPhoto(id);
|
||||
if (!p) return;
|
||||
if (!suggestDateFromPath({ fileName: p.FileName, path: p.Path })) return;
|
||||
}
|
||||
e.preventDefault();
|
||||
void acceptDateAndKeep(ids);
|
||||
}
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user