The fifth plugin out, and the cheapest so far — offscale's shape rather than
music's or vault's. Nothing stayed behind and nothing had to be redesigned,
for three structural reasons recorded in PLUGIN.md:
• No reverse dependency. Nothing in src/ imported anything of photos'.
• Configuration was already right. The URL and API key live in photos_config,
a per-user registry encrypted at rest and driven from the app. Vault had to
move VAULTWARDEN_URL out of .env and invent a settings panel; here the
sidecar already read the database, and its own comment already said why
nothing reads process.env.
• The frontend was already panels. The screen was a WorkspaceView around
exactly photos-nav and photos-view, so web/panels.ts and web/layout.ts are
the screen's contents with the wrapper removed.
The router's prefix is now derived from mountPrefix rather than the literal
'/api/photos', which was correct only by coincidence — a third-party publisher
mounts at /api/p/<publisher>/photos and the literal would forward the wrong
path to the sidecar.
Palette debt paid down as the runbook requires: 6 classes (it says 47; someone
had already done most of it), all of them state colours wearing palette
numbers, now `success` and `warning` from the closed tone set so they follow
the theme.
Immich itself is still provisioned by the app store rather than by this plugin
— the `provisioned` half of its old catalogue entry has no home until Docker
provisioning for plugins exists. Point it at an instance you already run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import { Link } from 'react-router';
|
|
import { AlbumsSection } from './AlbumsSection';
|
|
import { ConnectionSection } from './ConnectionSection';
|
|
import { ExploreSection } from './ExploreSection';
|
|
import { MapSection } from './MapSection';
|
|
import { PeopleSection } from './PeopleSection';
|
|
import { SearchSection } from './SearchSection';
|
|
import { SharingSection } from './SharingSection';
|
|
import { TimelineSection } from './TimelineSection';
|
|
import { photosSectionPath } from './shared';
|
|
import { usePhotosHealth } from './usePhotosData';
|
|
import { usePhotosSection } from './usePhotosSection';
|
|
|
|
// Right panel of the /photos workspace — renders the section named by the URL.
|
|
//
|
|
// Timeline, favorites, archive and trash are one component with a filter; the rest are their own.
|
|
//
|
|
// Health gates all of them, and its two failure modes are answered differently: nothing configured yet is
|
|
// the setup form, whatever section was asked for, because there is nothing else useful to show; a stored
|
|
// instance that is failing keeps its own message and a way back to the connection screen, since replacing a
|
|
// working key by accident is worse than a wall of text.
|
|
|
|
export const PhotosView = () => {
|
|
const section = usePhotosSection();
|
|
const { data: health, isLoading } = usePhotosHealth();
|
|
|
|
if (section === 'settings') return <ConnectionSection />;
|
|
|
|
if (!isLoading && health && !health.ok) {
|
|
if (health.configured === false) return <ConnectionSection />;
|
|
|
|
return (
|
|
<div className="flex h-full flex-col items-center justify-center gap-1 p-6 text-center">
|
|
<p className="text-sm font-medium">Photos is not answering</p>
|
|
<p className="max-w-sm text-xs text-muted-foreground">
|
|
{health.error ?? 'The photos sidecar could not reach the configured Immich instance.'}
|
|
</p>
|
|
<Link to={photosSectionPath('settings')} className="mt-2 text-xs font-medium text-primary hover:underline">
|
|
Check the connection
|
|
</Link>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
switch (section) {
|
|
case 'explore':
|
|
return <ExploreSection />;
|
|
case 'map':
|
|
return <MapSection />;
|
|
case 'search':
|
|
return <SearchSection />;
|
|
case 'albums':
|
|
return <AlbumsSection />;
|
|
case 'people':
|
|
return <PeopleSection />;
|
|
case 'sharing':
|
|
return <SharingSection />;
|
|
case 'favorites':
|
|
case 'archive':
|
|
case 'trash':
|
|
case 'timeline':
|
|
default:
|
|
return <TimelineSection section={section} />;
|
|
}
|
|
};
|