import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ArrowDown, ArrowUp, Eye, EyeOff, History, Plus, RefreshCw, Search, UserMinus, X, } from "lucide-react"; import { api, type ManagedChannel, type Tag } from "../lib/api"; import { formatEta } from "../lib/format"; import { notify } from "../lib/notifications"; import Tooltip from "./Tooltip"; export default function Channels({ canWrite, onViewChannel, }: { canWrite: boolean; onViewChannel: (id: string, name: string) => void; }) { const qc = useQueryClient(); const channelsQuery = useQuery({ queryKey: ["channels"], queryFn: api.channels }); const tagsQuery = useQuery({ queryKey: ["tags"], queryFn: api.tags }); const statusQuery = useQuery({ queryKey: ["my-status"], queryFn: api.myStatus }); const [q, setQ] = useState(""); const [newTag, setNewTag] = useState(""); const invalidate = () => { qc.invalidateQueries({ queryKey: ["channels"] }); qc.invalidateQueries({ queryKey: ["tags"] }); qc.invalidateQueries({ queryKey: ["my-status"] }); }; const userTags = (tagsQuery.data ?? []).filter((t) => !t.system); const patch = useMutation({ mutationFn: (v: { id: string; body: { priority?: number; hidden?: boolean; deep_requested?: boolean }; }) => api.updateChannel(v.id, v.body), // Requesting full history may have just pulled in a channel's recent uploads, so // refresh the per-user status and feed too — not only the channel list. onSuccess: () => { qc.invalidateQueries({ queryKey: ["channels"] }); qc.invalidateQueries({ queryKey: ["my-status"] }); qc.invalidateQueries({ queryKey: ["feed"] }); qc.invalidateQueries({ queryKey: ["feed-count"] }); }, }); const attach = useMutation({ mutationFn: (v: { id: string; tagId: number }) => api.attachChannelTag(v.id, v.tagId), onSuccess: () => qc.invalidateQueries({ queryKey: ["channels"] }), }); const detach = useMutation({ mutationFn: (v: { id: string; tagId: number }) => api.detachChannelTag(v.id, v.tagId), onSuccess: () => qc.invalidateQueries({ queryKey: ["channels"] }), }); const syncSubs = useMutation({ mutationFn: () => api.syncSubscriptions(), onSuccess: (r: { subscriptions?: number }) => { invalidate(); notify({ level: "success", message: `Synced ${r.subscriptions ?? 0} subscriptions` }); }, onError: () => notify({ level: "error", message: "Subscription sync failed" }), }); const createTag = useMutation({ mutationFn: (name: string) => api.createTag({ name, category: "other" }), onSuccess: () => { setNewTag(""); qc.invalidateQueries({ queryKey: ["tags"] }); }, }); const deleteTag = useMutation({ mutationFn: (id: number) => api.deleteTag(id), onSuccess: () => invalidate(), }); const unsubscribe = useMutation({ mutationFn: (id: string) => api.unsubscribeChannel(id), onSuccess: () => { qc.invalidateQueries({ queryKey: ["channels"] }); qc.invalidateQueries({ queryKey: ["my-status"] }); notify({ level: "success", message: "Unsubscribed on YouTube" }); }, onError: () => notify({ level: "error", message: "Unsubscribe failed" }), }); const deepAll = useMutation({ mutationFn: () => api.deepAll(true), onSuccess: (r: { updated?: number }) => { qc.invalidateQueries({ queryKey: ["channels"] }); qc.invalidateQueries({ queryKey: ["my-status"] }); notify({ level: "success", message: `Full history requested for ${r.updated ?? 0} channels`, }); }, onError: () => notify({ level: "error", message: "Couldn't request full history" }), }); const channels = (channelsQuery.data ?? []).filter( (c) => !q || (c.title ?? "").toLowerCase().includes(q.toLowerCase()) ); const s = statusQuery.data; return (
Set a channel's priority to push its videos up when you sort by “Channel priority”, attach your own tags to filter the feed, or hide a channel to drop it from the feed without unsubscribing.
{/* Your tags */}