diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 306aab0..ea37c48 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -26,7 +26,7 @@ import Header from "./components/Header"; import NavSidebar from "./components/NavSidebar"; import Sidebar from "./components/Sidebar"; import Feed from "./components/Feed"; -import Channels, { type ChannelStatusFilter } from "./components/Channels"; +import Channels, { type ChannelStatusFilter, type ChannelsView } from "./components/Channels"; import Playlists from "./components/Playlists"; import Stats from "./components/Stats"; import Scheduler from "./components/Scheduler"; @@ -136,11 +136,24 @@ export default function App() { const [aboutOpen, setAboutOpen] = useState(false); const [notesOpen, setNotesOpen] = useState(false); const [notesHighlight, setNotesHighlight] = useState(undefined); + // The Channel manager's active tab (subscriptions vs playlist discovery). Lifted here and + // persisted so navigation intents that target the subscriptions table — the header's + // "without full history" link, focus-channel — can force it back to "subscribed" instead + // of dumping the user on whatever tab they last left open. + const CHANNELS_VIEW_KEY = "siftlode.channelsView"; + const [channelsView, setChannelsViewState] = useState(() => + localStorage.getItem(CHANNELS_VIEW_KEY) === "discovery" ? "discovery" : "subscribed" + ); + const setChannelsView = (v: ChannelsView) => { + setChannelsViewState(v); + localStorage.setItem(CHANNELS_VIEW_KEY, v); + }; // "Focus this channel in the manager": jump to the Channels page and seed its name filter // so the channel is isolated. Cleared when leaving the page so it doesn't re-apply later. const [focusChannelName, setFocusChannelName] = useState(null); const focusChannel = (name: string) => { setFocusChannelName(name); + setChannelsView("subscribed"); // the name filter applies to the subscriptions table setPage("channels"); }; useEffect(() => { @@ -409,6 +422,7 @@ export default function App() { page={page} onGoToFullHistory={() => { setChannelFilter("needs_full"); + setChannelsView("subscribed"); // the status filter applies to the subscriptions tab setPage("channels"); }} /> @@ -433,6 +447,8 @@ export default function App() { onFocusChannel={focusChannel} statusFilter={channelFilter} setStatusFilter={setChannelFilter} + view={channelsView} + setView={setChannelsView} onOpenWizard={() => setWizardOpen(true)} onViewChannel={(id, name) => { setFilters({ ...filters, channelId: id, channelName: name, show: "all" }); diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index fc1c817..daf1caa 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -23,7 +23,7 @@ import ChannelDiscovery from "./ChannelDiscovery"; import TagManager from "./TagManager"; import { useConfirm } from "./ConfirmProvider"; -type ChannelsView = "subscribed" | "discovery"; +export type ChannelsView = "subscribed" | "discovery"; export type ChannelStatusFilter = "all" | "needs_full" | "fully_synced" | "hidden"; @@ -50,6 +50,8 @@ export default function Channels({ focusChannelName, statusFilter, setStatusFilter, + view, + setView, onOpenWizard, }: { canWrite: boolean; @@ -60,23 +62,16 @@ export default function Channels({ focusChannelName: string | null; statusFilter: ChannelStatusFilter; setStatusFilter: (f: ChannelStatusFilter) => void; + // The active tab lives in App so navigation intents that target the subscriptions table + // (the header's "without full history" link, focus-channel) can force it back here. + view: ChannelsView; + setView: (v: ChannelsView) => void; onOpenWizard: () => void; }) { const { t } = useTranslation(); const qc = useQueryClient(); const confirm = useConfirm(); - // Which tab is showing: the user's subscriptions, or channels discovered from their - // playlists. Persisted so a reload keeps the active tab (see other siftlode.* keys). - const [view, setView] = useState(() => - localStorage.getItem("siftlode.channelsView") === "discovery" - ? "discovery" - : "subscribed" - ); - useEffect(() => { - localStorage.setItem("siftlode.channelsView", view); - }, [view]); - // A YouTube-gated action (sync, backfill, unsubscribe) that 403s means the user hasn't // granted the needed scope — surface that with a "Connect" action instead of a vague fail. const notifyActionError = (err: unknown, fallbackKey: string) => {