Archive/delete now flash a red cross then drop tiles from the grid immediately, instead of a green check that lingered until the slow server-reconcile refetch landed. Keyboard `x` archive previously never called markRemoved, so tiles only vanished on refetch — that lag is gone. - Add 'removed' bulk state + removedBulk() helper (red cross overlay) - gridKeyNav archive/delete: removedBulk -> 500ms flash -> markRemoved, clearRemoved once refetch settles; restore stays green check - BulkActionBar: BulkConfig.removing routes archive/delete through the red flash; approve/restore/label/note unchanged Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
/**
|
|
* Bulk-action status, written by BulkActionBar and read by the header
|
|
* StatusPill and individual PhotoTile overlays.
|
|
*
|
|
* State lifecycle:
|
|
* startBulk → pill spins, all target tiles go "pending"
|
|
* setDetail → pill shows the filename currently being processed (fan-out ops)
|
|
* doneBulk → pill shows completion label, tiles flash green, auto-clears after 3 s
|
|
* removedBulk→ destructive completion (archive / delete): tiles flash a red cross,
|
|
* then the caller hides them via markRemoved; map auto-clears after 3 s
|
|
* failBulk → tiles flash red, auto-clears after 2 s
|
|
*/
|
|
|
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
|
|
interface BulkActionState {
|
|
active: boolean;
|
|
label: string;
|
|
detail?: string;
|
|
}
|
|
|
|
export const bulkAction = $state<BulkActionState>({ active: false, label: '' });
|
|
// SvelteMap (not `$state(new Map())`) so a `.get(uid)` read in a PhotoTile
|
|
// reliably re-runs when the entry flips — the plain-Map proxy form wasn't
|
|
// re-rendering the timeline tiles' overlay.
|
|
export const bulkPhotoStates = new SvelteMap<string, 'pending' | 'done' | 'error' | 'removed'>();
|
|
|
|
/**
|
|
* UIDs hidden from the timeline grid the instant a removing action (archive /
|
|
* delete / restore) succeeds, so tiles vanish without waiting on the ~1s
|
|
* server-reconcile refetch. The caller clears each id once the refetch lands.
|
|
* This is a pure UI overlay — it never touches the query cache, so it can't
|
|
* corrupt the facet/drill caches the way a direct cache eviction did.
|
|
*/
|
|
export const removedIds = $state(new SvelteSet<string>());
|
|
|
|
export function markRemoved(ids: string[]): void {
|
|
for (const id of ids) removedIds.add(id);
|
|
}
|
|
|
|
export function clearRemoved(ids: string[]): void {
|
|
for (const id of ids) removedIds.delete(id);
|
|
}
|
|
|
|
let doneTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
export function startBulk(label: string, ids: string[]): void {
|
|
if (doneTimer !== null) {
|
|
clearTimeout(doneTimer);
|
|
doneTimer = null;
|
|
}
|
|
bulkPhotoStates.clear();
|
|
for (const id of ids) bulkPhotoStates.set(id, 'pending');
|
|
bulkAction.active = true;
|
|
bulkAction.label = label;
|
|
bulkAction.detail = undefined;
|
|
}
|
|
|
|
export function setDetail(path: string): void {
|
|
bulkAction.detail = path;
|
|
}
|
|
|
|
export function doneBulk(label: string, ids: string[]): void {
|
|
for (const id of ids) bulkPhotoStates.set(id, 'done');
|
|
bulkAction.active = false;
|
|
bulkAction.label = label;
|
|
bulkAction.detail = undefined;
|
|
if (doneTimer !== null) clearTimeout(doneTimer);
|
|
doneTimer = setTimeout(() => {
|
|
bulkAction.label = '';
|
|
bulkPhotoStates.clear();
|
|
doneTimer = null;
|
|
}, 3000);
|
|
}
|
|
|
|
/**
|
|
* Destructive completion (archive / permanent delete): flash a red cross on the
|
|
* target tiles instead of the green check. The caller hides the tiles via
|
|
* markRemoved shortly after the flash; this timer only cleans up the state map.
|
|
*/
|
|
export function removedBulk(label: string, ids: string[]): void {
|
|
for (const id of ids) bulkPhotoStates.set(id, 'removed');
|
|
bulkAction.active = false;
|
|
bulkAction.label = label;
|
|
bulkAction.detail = undefined;
|
|
if (doneTimer !== null) clearTimeout(doneTimer);
|
|
doneTimer = setTimeout(() => {
|
|
bulkAction.label = '';
|
|
bulkPhotoStates.clear();
|
|
doneTimer = null;
|
|
}, 3000);
|
|
}
|
|
|
|
export function failBulk(ids: string[]): void {
|
|
for (const id of ids) bulkPhotoStates.set(id, 'error');
|
|
bulkAction.active = false;
|
|
bulkAction.label = '';
|
|
bulkAction.detail = undefined;
|
|
setTimeout(() => {
|
|
for (const id of ids) bulkPhotoStates.delete(id);
|
|
}, 2000);
|
|
}
|