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:
@@ -0,0 +1,70 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { qk } from "../../lib/queryKeys";
|
||||
import { invalidateSubscription } from "../../lib/subscriptionCache";
|
||||
import { api, type WatchedChannel } from "../../lib/api";
|
||||
import { notify } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import { useConfirm } from "../../components/ui/confirmContext";
|
||||
|
||||
/** Follow and take-off-the-list for a watched channel: the tab's rows and its videos dialog share
|
||||
* them, so the confirm, the toasts and the cache refresh are written once. */
|
||||
export function useWatchedChannelActions(onOpenWizard: () => void) {
|
||||
const { t } = useTranslation();
|
||||
const qc = useQueryClient();
|
||||
const confirm = useConfirm();
|
||||
const nameOf = (c: WatchedChannel) => c.title ?? c.id;
|
||||
|
||||
const subscribe = useMutation({
|
||||
mutationFn: (c: WatchedChannel) => api.subscribeChannel(c.id),
|
||||
onSuccess: (_data, c) => {
|
||||
invalidateSubscription(qc, c.id);
|
||||
notify({
|
||||
level: "success",
|
||||
title: t("channels.discovery.subscribedTitle"),
|
||||
message: t("channels.discovery.subscribedBody", { name: nameOf(c) }),
|
||||
meta: { kind: "channel-subscribed", channelId: c.id, channelName: nameOf(c) },
|
||||
});
|
||||
},
|
||||
onError: (err: unknown) =>
|
||||
notifyYouTubeActionError(err, t("channels.discovery.subscribeFailed"), onOpenWizard),
|
||||
});
|
||||
const restore = useMutation({
|
||||
mutationFn: (c: WatchedChannel) => api.restoreWatchedChannel(c.id),
|
||||
meta: { failTitle: "errors.writeFailed.restoreWatchedChannel" },
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: qk.watchedChannels() }),
|
||||
});
|
||||
// Taken off the list at once, with the toast's Undo as the way back (the same bargain as hiding a
|
||||
// channel): the next channel steps up into its place.
|
||||
const dismiss = useMutation({
|
||||
mutationFn: (c: WatchedChannel) => api.dismissWatchedChannel(c.id),
|
||||
meta: { failTitle: "errors.writeFailed.dismissWatchedChannel" },
|
||||
onSuccess: (_data, c) => {
|
||||
void qc.invalidateQueries({ queryKey: qk.watchedChannels() });
|
||||
notify({
|
||||
level: "success",
|
||||
message: t("channels.watched.dismissed", { name: nameOf(c) }),
|
||||
action: { label: t("common.undo"), onClick: () => restore.mutate(c) },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Subscribing changes the user's real YouTube account and spends a little quota — confirmed, as
|
||||
// on the Discovery tab. Resolves whether it went ahead.
|
||||
const onSubscribe = async (c: WatchedChannel): Promise<boolean> => {
|
||||
const ok = await confirm({
|
||||
title: t("channels.discovery.subscribe"),
|
||||
message: t("channels.discovery.confirmSubscribe", { name: nameOf(c) }),
|
||||
confirmLabel: t("channels.discovery.subscribe"),
|
||||
});
|
||||
if (ok) subscribe.mutate(c);
|
||||
return ok;
|
||||
};
|
||||
return {
|
||||
onSubscribe,
|
||||
subscribing: subscribe.isPending,
|
||||
dismiss: (c: WatchedChannel) => dismiss.mutate(c),
|
||||
};
|
||||
}
|
||||
|
||||
export type WatchedChannelActions = ReturnType<typeof useWatchedChannelActions>;
|
||||
Reference in New Issue
Block a user