fix: bulk label apply, instant archive removal, per-photo state, drop sidebar counts

Issue 1 — colors/labels not applying in bulk:
- sidecar validColors only accepted 4 of the 8 UI swatches, so teal/blue/
  purple/pink returned "invalid color" and rolled back the whole bulk txn.
  Add teal, blue, purple, pink to validColors.
- Add invalidateFacets() and call it on the success path of bulk marks,
  patchTargets, and single-photo edits so the Colors/Ratings/Notes facet
  sections refresh immediately instead of waiting out staleTime.

Issue 2 — archived photos linger in the grid:
- Add a UI-only removedIds set to the bulkAction store; archive/delete/
  restore/keep call markRemoved() on success so tiles vanish instantly,
  cleared once the server-reconcile refetch lands (no cache eviction).

Issue 3 — per-photo progress state:
- Wire startBulk/doneBulk/failBulk into all metadata applies, bulk
  (BulkMetadataSidebar) and single (RightSidebar), so colors/ratings/
  notes/dates/keywords show the spinner -> check -> X overlay.

Issue 4 — remove Left-sidebar count badges:
- Drop count badges from root folder, Archive, heaps, Notes, and the
  folder tree, plus the now-dead count queries and unused imports. Facet
  drill-panel counts are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 00:21:04 +02:00
parent a13e171295
commit ccf2c6b7c7
8 changed files with 161 additions and 251 deletions

View File

@@ -37,6 +37,8 @@
type PhotoMarksMap,
type UpdatePhotoBody
} from '$lib/services/photoprism';
import { invalidateFacets } from '$lib/services/bulk';
import { startBulk, doneBulk, failBulk } from '$lib/stores/bulkAction.svelte';
import { isAuthenticated } from '$lib/stores/session.svelte';
import { push as pushUndo } from '$lib/stores/undo.svelte';
import { getMetadataSectionOpen, setMetadataSection } from '$lib/stores/view.svelte';
@@ -91,12 +93,20 @@
const fresh = qc.getQueryData<PpPhoto>(['photo', photo.UID]) ?? photo;
return updatePhoto(fresh, patch);
},
onMutate: () => {
startBulk('Saving…', [photo.UID]);
},
onSuccess: (data) => {
qc.setQueryData(['photo', data.UID], data);
void qc.invalidateQueries({ queryKey: ['photos'] });
// Keep the keyword / notes facet panels in sync with the edit.
invalidateFacets();
doneBulk('Saved', [photo.UID]);
},
onError: (err) =>
toast.error(err instanceof Error ? err.message : 'Save failed')
onError: (err) => {
failBulk([photo.UID]);
toast.error(err instanceof Error ? err.message : 'Save failed');
}
}));
function commit(patch: UpdatePhotoBody) {
@@ -234,12 +244,17 @@
if (!optimistic.rating) delete optimistic.rating;
if (!optimistic.color) delete optimistic.color;
patchMarksCache(photo.UID, optimistic);
startBulk('Saving…', [photo.UID]);
try {
const saved = await setMark(photo.UID, patch);
patchMarksCache(photo.UID, saved);
// Refresh the Colors / Ratings facet panels off the sidecar truth.
invalidateFacets();
doneBulk('Saved', [photo.UID]);
} catch (err) {
// Rollback on failure.
patchMarksCache(photo.UID, prev);
failBulk([photo.UID]);
toast.error(err instanceof Error ? err.message : 'Save failed');
}
}