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.
67 lines
2.4 KiB
TypeScript
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>;
|
|
};
|