import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router'; import * as Authentication from './Screens/Authentication'; import * as Dashboard from './Screens/Dashboard'; import { useAuth } from 'hooks/useAuth'; import { useServerSettings } from 'state/useServerSettings'; import { useServerEnvironment } from 'state/useServerEnvironment'; import { useInitialData } from '@/state/useInitialData'; // `installedPlugins`, not `plugins`: App.tsx already destructures a `plugins` from useServerSettings(), // which is the DEAD plugin system — /server-settings/plugins scans src/workspaces/plugins/, a directory // that does not exist, so it is always []. Different thing entirely; see plugins/offscale/PLUGIN.md. import { plugins as installedPlugins } from './Plugins.gen'; /** * `/plugins` → `/marketplace`, KEEPING the query string. * * `` drops it, and that was not theoretical: every row in the plugin list * linked to `/plugins?selected=`, so clicking one redirected to a bare `/marketplace` and the detail * pane sat on "Select a plugin." The screen looked broken while both halves were working — the link set a * param the redirect then threw away. * * Rows now point at `/marketplace` directly, so this only serves old links and bookmarks. It preserves * the search anyway, because that is what made the redirect wrong in the first place. */ const PluginsRedirect = () => { const { search } = useLocation(); return ; }; export function App() { const { isLoading, isAuthenticated } = useAuth(); const { plugins, isLoading: isServerSettingsLoading } = useServerSettings(); useServerEnvironment(); useInitialData(); if (isLoading || isServerSettingsLoading) return null; return ( {!isAuthenticated && ( } /> } /> } /> } /> )} {isAuthenticated && ( } /> {/* Route pairs, like /command-hq and /soulseek below: the bare page route exists so the existing links into it keep working, and its `SettingsRoute` guard redirects to the first section. The section is the URL rather than a `*_SELECTED` global, so a settings sub-page is linkable, bookmarkable and cmd-clickable. */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* A project group is a working directory, so it is spelled as a path, not ?cwd= — see apps/ChatHistory/chat-routes.ts. The splat has to be last in a pattern, which is why "new" comes before the group rather than after it. A session carries no group: the transcript records its own cwd and the server resolves it by id. */} } /> } /> } /> } /> } /> {/* Splat, because the folder is the URL now: `/files/Tests/test folder`. The bare `/files` still matches with an empty splat and means home, so every existing link and the dock entry keep working. Only DIRECTORIES live here — an open file stays `?view=`, since it is what is on top of you rather than where you are. */} } /> } /> } /> } /> } /> } /> } /> {/* The screen was `/plugins` until marketplace.officer.dev existed. Redirected rather than dropped: the word appears in the dock, in docs, and in whatever anyone bookmarked, and a 404 on a route that worked yesterday reads as the feature having been removed. */} } /> {/* Installed plugins. Core routes above stay hand-written; everything below is generated from what is installed, because a bundler cannot follow a runtime import specifier. The wildcard hands the whole subtree to the plugin's own router, which react-router nests natively. */} {installedPlugins.flatMap((plugin) => [ } />, // The section pair, exactly as the core screens do it (`/soulseek/:section`): the plugin's // panels read `useParams` themselves, so which section is open is the URL rather than state // passed between them. } />, ])} } /> } /> {/* TEMPORARY / EXPERIMENTAL — offline file transfer over animated QR codes */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> )} ); }