mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00

* delete manifest (vite-plugin-pwa generates it) * fix upper case letter on screen components * fix imports of screens components missing upper case * remove default export from Base.tsx * move RegexPlayground to settings import * replace some relative path imports * remove React and ununsed imports * small alignments on vite.config.ts * move Dashboard and Releases to screens * move filters/index.tsx to filters/index.ts * remove default export from APIKeyAddForm * remove default export from FilterAddForm * organize imports and exports for the router * add .vscode workspace to gitignore * some touchs on .gitignore file * fix some eslint rules
247 lines
9.3 KiB
TypeScript
247 lines
9.3 KiB
TypeScript
/*
|
|
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
import { useState, useMemo } from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { Switch } from "@headlessui/react";
|
|
|
|
import { IndexerAddForm, IndexerUpdateForm } from "@forms";
|
|
import { useToggle } from "@hooks/hooks";
|
|
import { classNames } from "@utils";
|
|
import { EmptySimple } from "@components/emptystates";
|
|
import { APIClient } from "@api/APIClient";
|
|
import { componentMapType } from "@forms/settings/DownloadClientForms";
|
|
|
|
export const indexerKeys = {
|
|
all: ["indexers"] as const,
|
|
lists: () => [...indexerKeys.all, "list"] as const,
|
|
// list: (indexers: string[], sortOrder: string) => [...indexerKeys.lists(), { indexers, sortOrder }] as const,
|
|
details: () => [...indexerKeys.all, "detail"] as const,
|
|
detail: (id: number) => [...indexerKeys.details(), id] as const
|
|
};
|
|
|
|
interface SortConfig {
|
|
key: keyof ListItemProps["indexer"] | "enabled";
|
|
direction: "ascending" | "descending";
|
|
}
|
|
|
|
function useSort(items: ListItemProps["indexer"][], 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["indexer"]) => {
|
|
let direction: "ascending" | "descending" = "ascending";
|
|
if (
|
|
sortConfig &&
|
|
sortConfig.key === key &&
|
|
sortConfig.direction === "ascending"
|
|
) {
|
|
direction = "descending";
|
|
}
|
|
setSortConfig({ key, direction });
|
|
};
|
|
|
|
const getSortIndicator = (key: keyof ListItemProps["indexer"]) => {
|
|
if (!sortConfig || sortConfig.key !== key) {
|
|
return "";
|
|
}
|
|
|
|
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
|
};
|
|
|
|
return { items: sortedItems, requestSort, sortConfig, getSortIndicator };
|
|
}
|
|
|
|
const ImplementationBadgeIRC = () => (
|
|
<span className="mr-2 inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-green-200 dark:bg-green-400 text-green-800 dark:text-green-800">
|
|
IRC
|
|
</span>
|
|
);
|
|
|
|
const ImplementationBadgeTorznab = () => (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-orange-200 dark:bg-orange-400 text-orange-800 dark:text-orange-800">
|
|
Torznab
|
|
</span>
|
|
);
|
|
|
|
const ImplementationBadgeNewznab = () => (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-blue-200 dark:bg-blue-400 text-blue-800 dark:text-blue-800">
|
|
Newznab
|
|
</span>
|
|
);
|
|
|
|
const ImplementationBadgeRSS = () => (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-amber-200 dark:bg-amber-400 text-amber-800 dark:text-amber-800">
|
|
RSS
|
|
</span>
|
|
);
|
|
|
|
export const ImplementationBadges: componentMapType = {
|
|
irc: <ImplementationBadgeIRC />,
|
|
torznab: <ImplementationBadgeTorznab />,
|
|
newznab: <ImplementationBadgeNewznab />,
|
|
rss: <ImplementationBadgeRSS />
|
|
};
|
|
|
|
interface ListItemProps {
|
|
indexer: IndexerDefinition;
|
|
}
|
|
|
|
const ListItem = ({ indexer }: ListItemProps) => {
|
|
const [updateIsOpen, toggleUpdate] = useToggle(false);
|
|
|
|
return (
|
|
<li key={indexer.name}>
|
|
<div className="grid grid-cols-12 items-center py-1.5">
|
|
<IndexerUpdateForm
|
|
isOpen={updateIsOpen}
|
|
toggle={toggleUpdate}
|
|
indexer={indexer}
|
|
/>
|
|
<div className="col-span-2 sm:col-span-1 flex px-6 items-center sm:px-6">
|
|
<Switch
|
|
checked={indexer.enabled ?? false}
|
|
onChange={toggleUpdate}
|
|
className={classNames(
|
|
indexer.enabled ? "bg-blue-500" : "bg-gray-200 dark:bg-gray-600",
|
|
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
)}
|
|
>
|
|
<span className="sr-only">Enable</span>
|
|
<span
|
|
aria-hidden="true"
|
|
className={classNames(
|
|
indexer.enabled ? "translate-x-5" : "translate-x-0",
|
|
"inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
|
|
)}
|
|
/>
|
|
</Switch>
|
|
</div>
|
|
<div className="col-span-7 sm:col-span-8 pl-12 sm:pr-6 py-3 block flex-col text-sm font-medium text-gray-900 dark:text-white truncate">
|
|
{indexer.name}
|
|
</div>
|
|
<div className="hidden md:block col-span-2 pr-6 py-3 text-left items-center whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 truncate">
|
|
{ImplementationBadges[indexer.implementation]}
|
|
</div>
|
|
<div className="col-span-1 flex first-letter:px-6 py-3 whitespace-nowrap text-right text-sm font-medium">
|
|
<span
|
|
className="col-span-1 px-6 text-blue-600 dark:text-gray-300 hover:text-blue-900 dark:hover:text-blue-500 cursor-pointer"
|
|
onClick={toggleUpdate}
|
|
>
|
|
Edit
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
};
|
|
|
|
function IndexerSettings() {
|
|
const [addIndexerIsOpen, toggleAddIndexer] = useToggle(false);
|
|
|
|
const { error, data } = useQuery({
|
|
queryKey: indexerKeys.lists(),
|
|
queryFn: APIClient.indexers.getAll,
|
|
refetchOnWindowFocus: false
|
|
});
|
|
|
|
const sortedIndexers = useSort(data || []);
|
|
|
|
if (error) {
|
|
return (<p>An error has occurred</p>);
|
|
}
|
|
|
|
return (
|
|
<div className="lg:col-span-9">
|
|
<IndexerAddForm isOpen={addIndexerIsOpen} toggle={toggleAddIndexer} />
|
|
|
|
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
|
<div className="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
|
|
<div className="ml-4 mt-4">
|
|
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
|
Indexers
|
|
</h3>
|
|
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
Indexer settings for IRC, RSS, Newznab, and Torznab based indexers.<br />
|
|
Generic feeds can be added here by selecting the Generic indexer.
|
|
</p>
|
|
</div>
|
|
<div className="ml-4 mt-4 flex-shrink-0">
|
|
<button
|
|
type="button"
|
|
onClick={toggleAddIndexer}
|
|
className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500"
|
|
>
|
|
Add new
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col mt-6">
|
|
{data && data.length > 0 ? (
|
|
<section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-md">
|
|
<ol className="min-w-full relative">
|
|
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
|
<div
|
|
className="flex col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
|
onClick={() => sortedIndexers.requestSort("enabled")}
|
|
>
|
|
Enabled <span className="sort-indicator">{sortedIndexers.getSortIndicator("enabled")}</span>
|
|
</div>
|
|
<div
|
|
className="col-span-7 sm:col-span-8 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
|
onClick={() => sortedIndexers.requestSort("name")}
|
|
>
|
|
Name <span className="sort-indicator">{sortedIndexers.getSortIndicator("name")}</span>
|
|
</div>
|
|
<div
|
|
className="hidden md:flex col-span-1 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
|
onClick={() => sortedIndexers.requestSort("implementation")}
|
|
>
|
|
Implementation <span className="sort-indicator">{sortedIndexers.getSortIndicator("implementation")}</span>
|
|
</div>
|
|
</li>
|
|
{sortedIndexers.items.map((indexer, idx) => (
|
|
<ListItem indexer={indexer} key={idx} />
|
|
))}
|
|
</ol>
|
|
</section>
|
|
) : (
|
|
<EmptySimple
|
|
title="No indexers"
|
|
subtitle=""
|
|
buttonText="Add new indexer"
|
|
buttonAction={toggleAddIndexer}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default IndexerSettings;
|