import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; import { CircleCheck } from "lucide-react"; import { api, HttpError, refusalReason, type RelatedRegion, type Video, type VideoStatus, } from "../../lib/api"; import { qk } from "../../lib/queryKeys"; import { nextPageOnClick } from "../../lib/nextPage"; import { usePref, type UsePrefOptions } from "../../lib/usePref"; import { markedRows, stampFetch, useVideoMarks } from "../../lib/progressCache"; import { notifyActionError } from "../../lib/actionErrors"; import { Button } from "../../components/ui/Button"; import SegmentedToggle from "../../components/ui/SegmentedToggle"; import { LoadingState, StateMessage } from "../../components/ui/QueryState"; import { useContextMenu } from "../../components/ui/useContextMenu"; import NavGrid from "../../components/ui/NavGrid"; import Tooltip from "../../components/ui/Tooltip"; import VideoCard from "../video/VideoCard"; import { VideoMenu } from "../video/VideoMenu"; import { announceVideoState } from "../video/stateNotice"; import { writeVideoState, writeWatchLater } from "../video/videoWrites"; import { msg } from "../../lib/notifications"; /** Which list YouTube fills in — on the video's topic or with what trends in Hungary — is a mood, * decided at the list, and the account keeps the last one (the owner's pick). */ const RELATED_REGION_PREF = "relatedRegion"; const RELATED_REGION_OPTIONS: UsePrefOptions = { parse: (raw) => (raw === "hu" ? "hu" : "topical"), }; // The list leaves out what the user has seen (the server does, as it fetches); a video marked here // leaves it the same way, at once. const isSeen = (v: Video) => v.status === "watched" || v.status === "hidden"; /** The refusals a retry cannot answer: the day's cap (tomorrow does), the feature turned off on * this instance and the shared budget spent (the admin, or tomorrow). */ const FINAL_REASONS = new Set(["related_daily_limit", "related_disabled", "quota"]); const isFinal = (e: unknown) => FINAL_REASONS.has(refusalReason(e) ?? ""); // A write not yet confirmed: its change shows at once, and leaves when the server answers. type Pending = { seq: number; id: string; change: Partial