feat(web): header pill showing PhotoPrism indexer status
Subscribes to PhotoPrism's /api/v1/ws channel on login and surfaces index.indexing / index.updating / index.completed events as a small status pill in the header (next to the AnimatedMule wordmark). - Shows "Indexing" + the current filename (basename, monospace) during the scan pass, "Finalizing — <step>" during faces/counts/folders/ purge/moments, and "Indexed in Ns" for ~4s after completion before fading. - Per-file events arrive many per second on large libraries — throttled to 150 ms with a trailing-edge update so the pill stays calm and always lands on the most recent filename. Step and completion events bypass the throttle. - Filename slot is fixed at 24ch so the pill width stays constant through a run (no horizontal jitter as filenames change length); the full path is exposed via the parent's `title` for hover. - WS reconnect uses exponential backoff capped at 30 s, and the store tears down cleanly on logout so we don't leak sockets across identities. Defensive parsing throughout: PhotoPrism's WS protocol isn't a stable contract, so unknown event shapes are ignored rather than thrown — worst-case the pill stays idle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
import { resizable } from '$lib/actions/resizable';
|
||||
import { queryClient } from '$lib/queryClient';
|
||||
import { preview } from '$lib/stores/preview.svelte';
|
||||
import { startIndexerWatch, stopIndexerWatch } from '$lib/stores/indexer.svelte';
|
||||
import IndexerStatusPill from '$lib/components/layout/IndexerStatusPill.svelte';
|
||||
import LeftSidebar from '$lib/components/layout/LeftSidebar.svelte';
|
||||
import AnimatedMule from '$lib/components/mule/AnimatedMule.svelte';
|
||||
|
||||
@@ -44,6 +46,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Indexer status feed. The store opens a WS to PhotoPrism's /api/v1/ws
|
||||
// channel once we're signed in and tears it down on logout. Kept here
|
||||
// (not in the store's top-level) so SvelteKit's SSR pass never touches
|
||||
// `WebSocket` and so logout can short-circuit reconnect attempts.
|
||||
$effect(() => {
|
||||
if (!browser || !bootstrapped) return;
|
||||
if (isAuthenticated()) startIndexerWatch();
|
||||
else stopIndexerWatch();
|
||||
});
|
||||
|
||||
// PreviewOverlay is the full-screen lightbox — keyboard nav, map
|
||||
// pane, exif sidebar. Users who never click into a photo never need
|
||||
// it, so we lazy-import the first time `preview.uid` flips non-null
|
||||
@@ -75,7 +87,9 @@
|
||||
region inside each page scrolls. The mule header, toolbar, and
|
||||
sidebars stay fixed regardless of how far you scroll the grid. -->
|
||||
<div class="flex h-screen flex-col overflow-hidden">
|
||||
<AnimatedMule />
|
||||
<AnimatedMule>
|
||||
<IndexerStatusPill />
|
||||
</AnimatedMule>
|
||||
<div class="flex min-h-0 flex-1">
|
||||
{#if !view.leftSidebarCollapsed}
|
||||
<aside
|
||||
|
||||
Reference in New Issue
Block a user