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:
@@ -17,7 +17,8 @@
|
||||
type PhotoMarksMap,
|
||||
type UpdatePhotoBody
|
||||
} from '$lib/services/photoprism';
|
||||
import { patchTargets } from '$lib/services/bulk';
|
||||
import { patchTargets, invalidateFacets } from '$lib/services/bulk';
|
||||
import { startBulk, doneBulk, failBulk } from '$lib/stores/bulkAction.svelte';
|
||||
import { COLOR_SWATCHES } from '$lib/utils/tagGroups';
|
||||
|
||||
const qc = useQueryClient();
|
||||
@@ -35,10 +36,19 @@
|
||||
let colorDraft = $state<string | null>(null);
|
||||
let busy = $state(false);
|
||||
|
||||
async function withBusy<T>(fn: () => Promise<T>): Promise<T> {
|
||||
// `label` drives the per-photo tile overlay (pending → done / error) via the
|
||||
// shared bulkAction store, so metadata applies show the same progress state
|
||||
// as the archive/keep actions in BulkActionBar.
|
||||
async function withBusy<T>(fn: () => Promise<T>, label?: string): Promise<T> {
|
||||
busy = true;
|
||||
if (label) startBulk(`${label}…`, ids);
|
||||
try {
|
||||
return await fn();
|
||||
const result = await fn();
|
||||
if (label) doneBulk(label, ids);
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (label) failBulk(ids);
|
||||
throw e;
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
@@ -47,13 +57,16 @@
|
||||
async function applyNote() {
|
||||
if (busy) return;
|
||||
const value = noteDraft;
|
||||
await withBusy(() =>
|
||||
patchTargets(
|
||||
ids,
|
||||
{ Caption: value, CaptionSrc: 'manual' },
|
||||
value ? `Note → ${ids.length}` : `Cleared note on ${ids.length}`,
|
||||
(p) => ({ Caption: p.Caption ?? '', CaptionSrc: 'manual' })
|
||||
)
|
||||
const label = value ? `Note → ${ids.length}` : `Cleared note on ${ids.length}`;
|
||||
await withBusy(
|
||||
() =>
|
||||
patchTargets(
|
||||
ids,
|
||||
{ Caption: value, CaptionSrc: 'manual' },
|
||||
label,
|
||||
(p) => ({ Caption: p.Caption ?? '', CaptionSrc: 'manual' })
|
||||
),
|
||||
label
|
||||
);
|
||||
noteDraft = '';
|
||||
}
|
||||
@@ -64,16 +77,19 @@
|
||||
// Date-only input — stamp midnight UTC and let PhotoPrism's backwrite
|
||||
// fill the local timezone field downstream.
|
||||
const iso = `${dateDraft}T00:00:00Z`;
|
||||
await withBusy(() =>
|
||||
patchTargets(
|
||||
ids,
|
||||
buildTakenAtPatch(iso),
|
||||
`Date → ${ids.length}`,
|
||||
(p) =>
|
||||
p.TakenAt
|
||||
? buildTakenAtPatch(p.TakenAt)
|
||||
: ({ TakenSrc: '' } as UpdatePhotoBody)
|
||||
)
|
||||
const label = `Date → ${ids.length}`;
|
||||
await withBusy(
|
||||
() =>
|
||||
patchTargets(
|
||||
ids,
|
||||
buildTakenAtPatch(iso),
|
||||
label,
|
||||
(p) =>
|
||||
p.TakenAt
|
||||
? buildTakenAtPatch(p.TakenAt)
|
||||
: ({ TakenSrc: '' } as UpdatePhotoBody)
|
||||
),
|
||||
label
|
||||
);
|
||||
dateDraft = '';
|
||||
}
|
||||
@@ -81,6 +97,7 @@
|
||||
async function applyMarks(patch: PhotoMark, label: string) {
|
||||
if (busy) return;
|
||||
const tid = toast.loading(`${label}…`);
|
||||
startBulk(`${label}…`, ids);
|
||||
await withBusy(async () => {
|
||||
qc.setQueryData<PhotoMarksMap>(['marks'], (prev) => {
|
||||
const map = { ...(prev ?? {}) };
|
||||
@@ -95,8 +112,13 @@
|
||||
});
|
||||
try {
|
||||
await bulkSetMarks(ids, patch);
|
||||
doneBulk(label, ids);
|
||||
// Refresh the Colors / Ratings facet panels — they sit on
|
||||
// `['marks']` + `['photos','marks-pool']`, not the optimistic write above.
|
||||
invalidateFacets();
|
||||
toast.success(`${label} · ${ids.length}`, { id: tid });
|
||||
} catch (err) {
|
||||
failBulk(ids);
|
||||
toast.error(err instanceof Error ? err.message : 'Save failed', { id: tid });
|
||||
void qc.invalidateQueries({ queryKey: ['marks'] });
|
||||
}
|
||||
@@ -122,23 +144,26 @@
|
||||
const kw = keywordDraft.trim().replace(/,/g, '');
|
||||
if (!kw) return;
|
||||
keywordDraft = '';
|
||||
await withBusy(() =>
|
||||
patchTargets(
|
||||
ids,
|
||||
(p) => {
|
||||
const cur = (p.Details?.Keywords ?? '')
|
||||
.split(',')
|
||||
.map((k) => k.trim())
|
||||
.filter(Boolean);
|
||||
if (cur.includes(kw)) return {};
|
||||
const next = [...cur, kw].join(', ');
|
||||
return { Details: { Keywords: next, KeywordsSrc: 'manual' } };
|
||||
},
|
||||
`Tagged "${kw}" → ${ids.length}`,
|
||||
(p) => ({
|
||||
Details: { Keywords: p.Details?.Keywords ?? '', KeywordsSrc: 'manual' }
|
||||
})
|
||||
)
|
||||
const label = `Tagged "${kw}" → ${ids.length}`;
|
||||
await withBusy(
|
||||
() =>
|
||||
patchTargets(
|
||||
ids,
|
||||
(p) => {
|
||||
const cur = (p.Details?.Keywords ?? '')
|
||||
.split(',')
|
||||
.map((k) => k.trim())
|
||||
.filter(Boolean);
|
||||
if (cur.includes(kw)) return {};
|
||||
const next = [...cur, kw].join(', ');
|
||||
return { Details: { Keywords: next, KeywordsSrc: 'manual' } };
|
||||
},
|
||||
label,
|
||||
(p) => ({
|
||||
Details: { Keywords: p.Details?.Keywords ?? '', KeywordsSrc: 'manual' }
|
||||
})
|
||||
),
|
||||
label
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user