fix(channels): land on the subscriptions tab for channel-targeted navigation
The Channel manager's tab (subscriptions vs playlist discovery) was persisted locally, so the header's "N without full history" link and the focus-channel jump (subscribe notice, tag manager) could dump the user on the Discovery tab while quietly applying a status/name filter that only affects the subscriptions table — the targeted channel was there, just on the hidden tab. Lift the tab state to App alongside the status filter it pairs with, and have those navigation intents switch it back to "subscribed".
This commit is contained in:
parent
5f6ff7653b
commit
39cd025e79
2 changed files with 24 additions and 13 deletions
|
|
@ -26,7 +26,7 @@ import Header from "./components/Header";
|
||||||
import NavSidebar from "./components/NavSidebar";
|
import NavSidebar from "./components/NavSidebar";
|
||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
import Feed from "./components/Feed";
|
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 Playlists from "./components/Playlists";
|
||||||
import Stats from "./components/Stats";
|
import Stats from "./components/Stats";
|
||||||
import Scheduler from "./components/Scheduler";
|
import Scheduler from "./components/Scheduler";
|
||||||
|
|
@ -136,11 +136,24 @@ export default function App() {
|
||||||
const [aboutOpen, setAboutOpen] = useState(false);
|
const [aboutOpen, setAboutOpen] = useState(false);
|
||||||
const [notesOpen, setNotesOpen] = useState(false);
|
const [notesOpen, setNotesOpen] = useState(false);
|
||||||
const [notesHighlight, setNotesHighlight] = useState<string | undefined>(undefined);
|
const [notesHighlight, setNotesHighlight] = useState<string | undefined>(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<ChannelsView>(() =>
|
||||||
|
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
|
// "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.
|
// so the channel is isolated. Cleared when leaving the page so it doesn't re-apply later.
|
||||||
const [focusChannelName, setFocusChannelName] = useState<string | null>(null);
|
const [focusChannelName, setFocusChannelName] = useState<string | null>(null);
|
||||||
const focusChannel = (name: string) => {
|
const focusChannel = (name: string) => {
|
||||||
setFocusChannelName(name);
|
setFocusChannelName(name);
|
||||||
|
setChannelsView("subscribed"); // the name filter applies to the subscriptions table
|
||||||
setPage("channels");
|
setPage("channels");
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -409,6 +422,7 @@ export default function App() {
|
||||||
page={page}
|
page={page}
|
||||||
onGoToFullHistory={() => {
|
onGoToFullHistory={() => {
|
||||||
setChannelFilter("needs_full");
|
setChannelFilter("needs_full");
|
||||||
|
setChannelsView("subscribed"); // the status filter applies to the subscriptions tab
|
||||||
setPage("channels");
|
setPage("channels");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -433,6 +447,8 @@ export default function App() {
|
||||||
onFocusChannel={focusChannel}
|
onFocusChannel={focusChannel}
|
||||||
statusFilter={channelFilter}
|
statusFilter={channelFilter}
|
||||||
setStatusFilter={setChannelFilter}
|
setStatusFilter={setChannelFilter}
|
||||||
|
view={channelsView}
|
||||||
|
setView={setChannelsView}
|
||||||
onOpenWizard={() => setWizardOpen(true)}
|
onOpenWizard={() => setWizardOpen(true)}
|
||||||
onViewChannel={(id, name) => {
|
onViewChannel={(id, name) => {
|
||||||
setFilters({ ...filters, channelId: id, channelName: name, show: "all" });
|
setFilters({ ...filters, channelId: id, channelName: name, show: "all" });
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import ChannelDiscovery from "./ChannelDiscovery";
|
||||||
import TagManager from "./TagManager";
|
import TagManager from "./TagManager";
|
||||||
import { useConfirm } from "./ConfirmProvider";
|
import { useConfirm } from "./ConfirmProvider";
|
||||||
|
|
||||||
type ChannelsView = "subscribed" | "discovery";
|
export type ChannelsView = "subscribed" | "discovery";
|
||||||
|
|
||||||
export type ChannelStatusFilter = "all" | "needs_full" | "fully_synced" | "hidden";
|
export type ChannelStatusFilter = "all" | "needs_full" | "fully_synced" | "hidden";
|
||||||
|
|
||||||
|
|
@ -50,6 +50,8 @@ export default function Channels({
|
||||||
focusChannelName,
|
focusChannelName,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
|
view,
|
||||||
|
setView,
|
||||||
onOpenWizard,
|
onOpenWizard,
|
||||||
}: {
|
}: {
|
||||||
canWrite: boolean;
|
canWrite: boolean;
|
||||||
|
|
@ -60,23 +62,16 @@ export default function Channels({
|
||||||
focusChannelName: string | null;
|
focusChannelName: string | null;
|
||||||
statusFilter: ChannelStatusFilter;
|
statusFilter: ChannelStatusFilter;
|
||||||
setStatusFilter: (f: ChannelStatusFilter) => void;
|
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;
|
onOpenWizard: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
const confirm = useConfirm();
|
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<ChannelsView>(() =>
|
|
||||||
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
|
// 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.
|
// granted the needed scope — surface that with a "Connect" action instead of a vague fail.
|
||||||
const notifyActionError = (err: unknown, fallbackKey: string) => {
|
const notifyActionError = (err: unknown, fallbackKey: string) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue