Files
jellyfin/web/JellyfinView.tsx
T
officer a7f1a71785 jellyfin, extracted from the platform as a plugin
Eighth plugin out, and the second resurrection: like soulseek, hono.ts carried
the router import and its mount commented out since 2026-08-13, with the
permission reduced to api: [], so the server booted clean while every panel
request 404'd. jellyfin_servers was also absent from the drizzle barrel, so
bun db:push would have dropped it.

Easier than soulseek in one specific way: mountPrefix derives /jellyfin from
the appName, which is exactly what the router already hardcoded, so no frontend
call site needed repointing. Soulseek needed 47 because its appName and its old
prefix differed. The frontend also already routed through one helper, so there
were five call sites rather than forty-seven.

Self-contained, unlike transmission and soulseek: jellyfin_servers is a
registry this plugin owns outright — label, URL, encrypted token, one active —
because more than one Jellyfin is a thing someone has, and more than one
Transmission is not.

Carries its own .prettierignore for the vendored hls.mjs, rather than leaving a
stale rule behind in the platform naming a path that no longer exists.
2026-08-19 03:17:32 +00:00

58 lines
2.5 KiB
TypeScript

import { Link, useSearchParams } from 'react-router';
import { ConnectionSection } from './ConnectionSection';
import { HomeSection } from './HomeSection';
import { ItemDetail } from './ItemDetail';
import { LibrarySection } from './LibrarySection';
import { SearchSection } from './SearchSection';
import { VideoPlayer } from './VideoPlayer';
import { ITEM_PARAM, jellyfinSectionPath, PLAY_PARAM } from './shared';
import { useJellyfinHealth } from './useJellyfinData';
import { useJellyfinSection } from './useJellyfinSection';
// Right panel of the /jellyfin workspace — renders the section named by the URL, with the detail sheet and
// the player as overlays on top of it.
//
// 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 server
// that is failing keeps its own message and a way back to the connection screen, since replacing a working
// sign-in by accident is worse than a wall of text.
//
// The player wins over the detail sheet when both parameters are set, and `itemHref` never sets both — but
// the precedence is written down rather than left to whichever renders last.
export const JellyfinView = () => {
const section = useJellyfinSection();
const [params] = useSearchParams();
const { data: health, isLoading } = useJellyfinHealth();
const playId = params.get(PLAY_PARAM);
const itemId = params.get(ITEM_PARAM);
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">Jellyfin is not answering</p>
<p className="max-w-sm text-xs text-muted-foreground">
{health.error ?? 'The jellyfin sidecar could not reach the configured server.'}
</p>
<Link to={jellyfinSectionPath('settings')} className="mt-2 text-xs font-medium text-primary hover:underline">
Check the connection
</Link>
</div>
);
}
const body = section === 'library' ? <LibrarySection /> : section === 'search' ? <SearchSection /> : <HomeSection />;
return (
<div className="relative h-full">
{body}
{playId ? <VideoPlayer id={playId} /> : itemId ? <ItemDetail id={itemId} /> : null}
</div>
);
};