feat(channels): a "Discover from your history" tab for the channels watched but not followed
The Channel manager lists the channels the Takeout history shows watched often but not followed, the most watched first: twenty at a time, the channel's About on hover, its name to its page, Follow, and Not interested with an Undo. A row's count opens the videos it counts, ready to play one or all. Before a history is uploaded it points to the upload; while the import still asks YouTube it says more channels may join.
This commit is contained in:
@@ -57,6 +57,7 @@ import { menuGroups } from "../../components/ui/menuGroups";
|
||||
import { choiceSubmenu, sortSubmenu } from "../../components/ui/pageMenuItems";
|
||||
import { optionsFromColumns } from "../../lib/sortOptions";
|
||||
import ChannelDiscovery from "./ChannelDiscovery";
|
||||
import WatchedChannels from "./WatchedChannels";
|
||||
import TagManager from "../../features/channel/TagManager";
|
||||
import {
|
||||
useChannelLayout,
|
||||
@@ -183,6 +184,8 @@ export default function Channels() {
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: qk.blockedChannels() });
|
||||
qc.invalidateQueries({ queryKey: qk.feed() });
|
||||
// A blocked channel is off the watched-channels list; unblocked, it may be back on it.
|
||||
qc.invalidateQueries({ queryKey: qk.watchedChannels() });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -530,7 +533,7 @@ export default function Channels() {
|
||||
<ActionMenu ariaLabel={t("channels.actionsLabel")} items={catalogActions} />
|
||||
);
|
||||
|
||||
// Table / cards density — shared by the subscribed + discovery tables (blocked is a chip list, no
|
||||
// Table / cards density — shared by the subscribed + both discovery tables (blocked is a chip list, no
|
||||
// layout to switch). Rides the same generic ViewSwitcher the feed uses.
|
||||
const layoutOptions: ViewOption<ChannelLayout>[] = CHANNEL_LAYOUTS.map((id) => ({
|
||||
id,
|
||||
@@ -546,7 +549,7 @@ export default function Channels() {
|
||||
labelClassName="hidden lg:inline"
|
||||
/>
|
||||
);
|
||||
const hasLayout = view === "subscribed" || view === "discovery";
|
||||
const hasLayout = view === "subscribed" || view === "discovery" || view === "watched";
|
||||
// In cards there are no column headers to sort by, so a dropdown drives the SAME shared sort state
|
||||
// the table header uses (table ↔ cards stay in sync). Table mode keeps the header clicks.
|
||||
const sortControl = (
|
||||
@@ -612,7 +615,7 @@ export default function Channels() {
|
||||
|
||||
const viewTabs: TabDef[] = useMemo(
|
||||
() =>
|
||||
(["subscribed", "discovery", "hidden", "blocked"] as ChannelsView[]).map((v) => ({
|
||||
(["subscribed", "discovery", "watched", "hidden", "blocked"] as ChannelsView[]).map((v) => ({
|
||||
id: v,
|
||||
label: t(`channels.tabs.${v}`),
|
||||
})),
|
||||
@@ -778,6 +781,13 @@ export default function Channels() {
|
||||
onOpenWizard={onOpenWizard}
|
||||
onViewChannel={onViewChannel}
|
||||
/>
|
||||
) : view === "watched" ? (
|
||||
<WatchedChannels
|
||||
canWrite={canWrite}
|
||||
search={search}
|
||||
onOpenWizard={onOpenWizard}
|
||||
onViewChannel={onViewChannel}
|
||||
/>
|
||||
) : view === "blocked" ? (
|
||||
/* Blocked channels — videos from these are hidden everywhere and dropped from live search. */
|
||||
<div className="px-4 pb-4 pt-3 max-w-384 mx-auto">
|
||||
|
||||
Reference in New Issue
Block a user