Files
soulseek/web/SoulseekView.tsx
T
officer c11f45c033 soulseek, extracted from the platform as a plugin
Seventh plugin out, and the first that was already dead. Soulseek had been
switched off since 2026-08-13: hono.ts had the router import and its mount
commented out, so createSidecarProxy never ran, registerProxiedPrefix was never
called, the slskd:server announcement fired into a void, and officer-slskd was
not in the ecosystem at all. The permission entry had been reduced to api: [],
which is why assertPermissionTotality was satisfied and the server booted
clean. The UI rendered perfectly and every request 404'd.

So this is a resurrection as well as a move.

The mount is now /api/soulseek, derived via mountPrefix from the appName — all
47 frontend call sites repointed from /slskd/. The credential key in
service_connections stays 'slskd': it names the daemon, not the route. Those
two strings differ on purpose and conflating them reads the wrong row.

The connection stays in core service_connections, as transmission does, so
'slskd' remains in a platform union this plugin cannot extend. Recorded in
PLUGIN.md. Unlike transmission this plugin does own a schema: favourites and
the two halves of the cached browse tree, which slskd has no concept of. Those
three tables had been dropped from the drizzle barrel, so bun db:push would
have deleted them on any server holding data.

313 palette classes converted to the semantic tone set, including a shared
PHASE_TONE the three transfer views previously each copied.
2026-08-19 02:55:02 +00:00

67 lines
2.4 KiB
TypeScript

import { Construction } from 'lucide-react';
import { SOULSEEK_SECTIONS, useSoulseekSection, type SoulseekSectionId } from './shared';
import { SearchView } from './SearchView';
import { SoulseekTransfers } from './SoulseekTransfers';
import { SoulseekUploads } from './SoulseekUploads';
import { SoulseekRooms } from './SoulseekRooms';
import { SoulseekDashboard } from './SoulseekDashboard';
import { SoulseekChat } from './SoulseekChat';
import { SoulseekUsers } from './SoulseekUsers';
import { SoulseekSystem } from './SoulseekSystem';
import { SoulseekConnection } from './SoulseekConnection';
import { useServiceConnection } from 'officerdev';
// Right panel of the /soulseek workspace — renders the UI for the section the URL names. Content
// zoom used to live here; it is now a framework-level per-panel control in PanelSlot.
const Placeholder = ({ id }: { id: SoulseekSectionId }) => {
const label = SOULSEEK_SECTIONS.find((s) => s.id === id)?.label ?? id;
return (
<div className="flex h-full flex-col items-center justify-center gap-3 text-center">
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
<Construction className="h-6 w-6" />
</div>
<div>
<div className="text-base font-semibold">{label}</div>
<div className="text-sm text-muted-foreground">Coming soon</div>
</div>
</div>
);
};
const sectionView = (section: SoulseekSectionId) => {
switch (section) {
case 'dashboard':
return <SoulseekDashboard />;
case 'search':
return <SearchView />;
case 'downloads':
return <SoulseekTransfers />;
case 'uploads':
return <SoulseekUploads />;
case 'rooms':
return <SoulseekRooms />;
case 'chat':
return <SoulseekChat />;
case 'users':
return <SoulseekUsers />;
case 'system':
return <SoulseekSystem />;
case 'connection':
return <SoulseekConnection />;
default:
return <Placeholder id={section} />;
}
};
export const SoulseekView = () => {
const section = useSoulseekSection();
const { data: connection, isLoading } = useServiceConnection('soulseek');
// Nothing connected yet: the setup form takes over every section, because none of them can do anything
// without a daemon.
if (!isLoading && !connection?.configured) return <SoulseekConnection />;
return <div className="h-full w-full">{sectionView(section)}</div>;
};