/* * Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors. * SPDX-License-Identifier: GPL-2.0-or-later */ import { Fragment, useRef, useState, useMemo } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Menu, Switch, Transition } from "@headlessui/react"; import { toast } from "react-hot-toast"; import { ArrowsRightLeftIcon, DocumentTextIcon, EllipsisHorizontalIcon, PencilSquareIcon, TrashIcon } from "@heroicons/react/24/outline"; import { APIClient } from "@api/APIClient"; import { useToggle } from "@hooks/hooks"; import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "@utils"; import Toast from "@components/notifications/Toast"; import { DeleteModal } from "@components/modals"; import { FeedUpdateForm } from "@forms/settings/FeedForms"; import { EmptySimple } from "@components/emptystates"; import { ImplementationBadges } from "./Indexer"; export const feedKeys = { all: ["feeds"] as const, lists: () => [...feedKeys.all, "list"] as const, // list: (indexers: string[], sortOrder: string) => [...feedKeys.lists(), { indexers, sortOrder }] as const, details: () => [...feedKeys.all, "detail"] as const, detail: (id: number) => [...feedKeys.details(), id] as const }; interface SortConfig { key: keyof ListItemProps["feed"] | "enabled"; direction: "ascending" | "descending"; } function useSort(items: ListItemProps["feed"][], config?: SortConfig) { const [sortConfig, setSortConfig] = useState(config); const sortedItems = useMemo(() => { if (!sortConfig) { return items; } const sortableItems = [...items]; sortableItems.sort((a, b) => { const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string; const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string; if (aValue < bValue) { return sortConfig.direction === "ascending" ? -1 : 1; } if (aValue > bValue) { return sortConfig.direction === "ascending" ? 1 : -1; } return 0; }); return sortableItems; }, [items, sortConfig]); const requestSort = (key: keyof ListItemProps["feed"] | "enabled") => { let direction: "ascending" | "descending" = "ascending"; if ( sortConfig && sortConfig.key === key && sortConfig.direction === "ascending" ) { direction = "descending"; } setSortConfig({ key, direction }); }; const getSortIndicator = (key: keyof ListItemProps["feed"]) => { if (!sortConfig || sortConfig.key !== key) { return ""; } return sortConfig.direction === "ascending" ? "↑" : "↓"; }; return { items: sortedItems, requestSort, sortConfig, getSortIndicator }; } function FeedSettings() { const { data } = useQuery({ queryKey: feedKeys.lists(), queryFn: APIClient.feeds.find, refetchOnWindowFocus: false }); const sortedFeeds = useSort(data || []); return (

Feeds

Manage RSS, Newznab, and Torznab feeds.

{data && data.length > 0 ?
  1. sortedFeeds.requestSort("enabled")}> Enabled {sortedFeeds.getSortIndicator("enabled")}
    sortedFeeds.requestSort("name")}> Name {sortedFeeds.getSortIndicator("name")}
    sortedFeeds.requestSort("type")}> Type {sortedFeeds.getSortIndicator("type")}
    sortedFeeds.requestSort("last_run")}> Last run {sortedFeeds.getSortIndicator("last_run")}
  2. {sortedFeeds.items.map((feed) => ( ))}
: }
); } interface ListItemProps { feed: Feed; } function ListItem({ feed }: ListItemProps) { const [updateFormIsOpen, toggleUpdateForm] = useToggle(false); const [enabled, setEnabled] = useState(feed.enabled); const queryClient = useQueryClient(); const updateMutation = useMutation({ mutationFn: (status: boolean) => APIClient.feeds.toggleEnable(feed.id, status), onSuccess: () => { queryClient.invalidateQueries({ queryKey: feedKeys.lists() }); queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) }); toast.custom((t) => ); } }); const toggleActive = (status: boolean) => { setEnabled(status); updateMutation.mutate(status); }; return (
  • Use setting
    {feed.name} {feed.indexer}
    {ImplementationBadges[feed.type.toLowerCase()]}
    {IsEmptyDate(feed.last_run)}
  • ); } interface FeedItemDropdownProps { feed: Feed; onToggle: (newState: boolean) => void; toggleUpdate: () => void; } const FeedItemDropdown = ({ feed, onToggle, toggleUpdate }: FeedItemDropdownProps) => { const cancelModalButtonRef = useRef(null); const queryClient = useQueryClient(); const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); const deleteMutation = useMutation({ mutationFn: (id: number) => APIClient.feeds.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: feedKeys.lists() }); queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) }); toast.custom((t) => ); } }); return ( { deleteMutation.mutate(feed.id); toggleDeleteModal(); }} title={`Remove feed: ${feed.name}`} text="Are you sure you want to remove this feed? This action cannot be undone." />
    {({ active }) => ( )} {({ active }) => ( )}
    {({ active }) => ( )}
    {({ active }) => ( )}
    ); }; export default FeedSettings;