fix(web): tooltips (#1154)

* fix broken tooltips, replace react-tooltip with react-popper-tooltip

* make tooltips use ExternalLink component

* fix import

* get rid of unused import

* fix(tooltip): set delayHide

* removed unecessary comment

* fix tooltip on mobile

* stop tooltip label propagation (mainly for mobile devices)

* added onClick convenience prop for label component wrapper (since onClick isn't propagated down)

---------

Co-authored-by: soup <soup@r4tio.dev>
This commit is contained in:
stacksmash76 2023-10-01 13:16:05 +00:00 committed by GitHub
parent 98df0c9040
commit 3e3454724b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1171 additions and 396 deletions

View file

@ -69,7 +69,6 @@
"react-select": "^5.7.4", "react-select": "^5.7.4",
"react-table": "^7.8.0", "react-table": "^7.8.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"react-tooltip": "^5.21.1",
"stacktracey": "^2.1.8", "stacktracey": "^2.1.8",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "^5.2.2", "typescript": "^5.2.2",

21
web/pnpm-lock.yaml generated
View file

@ -128,9 +128,6 @@ dependencies:
react-textarea-autosize: react-textarea-autosize:
specifier: ^8.5.3 specifier: ^8.5.3
version: 8.5.3(@types/react@18.2.21)(react@18.2.0) version: 8.5.3(@types/react@18.2.21)(react@18.2.0)
react-tooltip:
specifier: ^5.21.1
version: 5.21.1(react-dom@18.2.0)(react@18.2.0)
stacktracey: stacktracey:
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
@ -2873,10 +2870,6 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: false dev: false
/classnames@2.3.2:
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
dev: false
/client-only@0.0.1: /client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false dev: false
@ -4763,7 +4756,7 @@ packages:
react: ^18.2.0 react: ^18.2.0
react-dom: '>=16.6.0' react-dom: '>=16.6.0'
dependencies: dependencies:
'@babel/runtime': 7.22.10 '@babel/runtime': 7.22.11
'@popperjs/core': 2.11.8 '@popperjs/core': 2.11.8
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
@ -4869,18 +4862,6 @@ packages:
- '@types/react' - '@types/react'
dev: false dev: false
/react-tooltip@5.21.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-wJqF/yzK1wuJuy5/zAkVErFA609fVv1ZukhGjw44PcMvg9wL0jomnpQyz3qH1H7TWjz/wqO/OMc3ipQNjZ8zYg==}
peerDependencies:
react: ^18.2.0
react-dom: '>=16.14.0'
dependencies:
'@floating-ui/dom': 1.5.1
classnames: 2.3.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies: peerDependencies:

View file

@ -0,0 +1,20 @@
type ExternalLinkProps = {
href: string;
className?: string;
children?: React.ReactNode;
};
export const ExternalLink = ({ href, className, children }: ExternalLinkProps) => (
<a
rel="noopener noreferrer"
target="_blank"
href={href}
className={className}
>
{children}
</a>
);
export const DocsLink = ({ href }: { href: string; }) => (
<ExternalLink href={href} className="text-blue-400 visited:text-blue-400">{href}</ExternalLink>
);

View file

@ -6,6 +6,7 @@
import StackTracey from "stacktracey"; import StackTracey from "stacktracey";
import type { FallbackProps } from "react-error-boundary"; import type { FallbackProps } from "react-error-boundary";
import { ArrowPathIcon } from "@heroicons/react/24/solid"; import { ArrowPathIcon } from "@heroicons/react/24/solid";
import { ExternalLink } from "@components/ExternalLink";
export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => { export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
const stack = new StackTracey(error); const stack = new StackTracey(error);
@ -35,23 +36,19 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
<h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4"> <h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4">
Please consider reporting this error to our Please consider reporting this error to our
{" "} {" "}
<a <ExternalLink
rel="noopener noreferrer"
target="_blank"
href="https://github.com/autobrr/autobrr" href="https://github.com/autobrr/autobrr"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-sky-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100" className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-sky-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
> >
GitHub page GitHub page
</a> </ExternalLink>
{" or to "} {" or to "}
<a <ExternalLink
rel="noopener noreferrer"
target="_blank"
href="https://discord.gg/WQ2eUycxyT" href="https://discord.gg/WQ2eUycxyT"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100" className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
> >
our official Discord channel our official Discord channel
</a> </ExternalLink>
. .
</h3> </h3>
<div <div

View file

@ -5,6 +5,7 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { ReactComponent as Logo } from "@app/logo.svg"; import { ReactComponent as Logo } from "@app/logo.svg";
import { ExternalLink } from "@components/ExternalLink";
export const NotFound = () => { export const NotFound = () => {
return ( return (
@ -21,23 +22,19 @@ export const NotFound = () => {
<h3 className="text-xl text-center text-gray-700 dark:text-gray-400 mb-1 px-2"> <h3 className="text-xl text-center text-gray-700 dark:text-gray-400 mb-1 px-2">
feel free to report this to our feel free to report this to our
{" "} {" "}
<a <ExternalLink
rel="noopener noreferrer"
target="_blank"
href="https://github.com/autobrr/autobrr" href="https://github.com/autobrr/autobrr"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-sky-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100" className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-sky-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
> >
GitHub page GitHub page
</a> </ExternalLink>
{" or to "} {" or to "}
<a <ExternalLink
rel="noopener noreferrer"
target="_blank"
href="https://discord.gg/WQ2eUycxyT" href="https://discord.gg/WQ2eUycxyT"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100" className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
> >
our official Discord channel our official Discord channel
</a> </ExternalLink>
. .
</h3> </h3>
<h3 className="text-xl text-center leading-6 text-gray-700 dark:text-gray-400 mb-8 px-2"> <h3 className="text-xl text-center leading-6 text-gray-700 dark:text-gray-400 mb-8 px-2">

View file

@ -4,18 +4,18 @@
*/ */
import * as React from "react"; import * as React from "react";
import { toast } from "react-hot-toast";
import { formatDistanceToNowStrict } from "date-fns"; import { formatDistanceToNowStrict } from "date-fns";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid"; import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid";
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline"; import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
import { classNames, simplifyDate } from "@utils";
import { Tooltip } from "@components/tooltips/Tooltip";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { APIClient } from "@api/APIClient"; import { APIClient } from "@api/APIClient";
import { classNames, simplifyDate } from "@utils";
import { filterKeys } from "@screens/filters/List"; import { filterKeys } from "@screens/filters/List";
import { toast } from "react-hot-toast";
import Toast from "@components/notifications/Toast"; import Toast from "@components/notifications/Toast";
import { RingResizeSpinner } from "@components/Icons"; import { RingResizeSpinner } from "@components/Icons";
import { Tooltip } from "@components/tooltips/Tooltip";
interface CellProps { interface CellProps {
value: string; value: string;
@ -35,6 +35,7 @@ export const IndexerCell = ({ value }: CellProps) => (
)} )}
> >
<Tooltip <Tooltip
requiresClick
label={value} label={value}
maxWidth="max-w-[90vw]" maxWidth="max-w-[90vw]"
> >
@ -53,6 +54,7 @@ export const TitleCell = ({ value }: CellProps) => (
)} )}
> >
<Tooltip <Tooltip
requiresClick
label={value} label={value}
maxWidth="max-w-[90vw]" maxWidth="max-w-[90vw]"
> >
@ -221,6 +223,7 @@ export const ReleaseStatusCell = ({ value }: ReleaseStatusCellProps) => (
)} )}
> >
<Tooltip <Tooltip
requiresClick
label={StatusCellMap[v.status].icon} label={StatusCellMap[v.status].icon}
title={StatusCellMap[v.status].textFormatter(v)} title={StatusCellMap[v.status].textFormatter(v)}
> >

View file

@ -15,6 +15,7 @@ import Toast from "@components/notifications/Toast";
import { LeftNav } from "./LeftNav"; import { LeftNav } from "./LeftNav";
import { RightNav } from "./RightNav"; import { RightNav } from "./RightNav";
import { MobileNav } from "./MobileNav"; import { MobileNav } from "./MobileNav";
import { ExternalLink } from "@components/ExternalLink";
export const Header = () => { export const Header = () => {
const { data: config } = useQuery({ const { data: config } = useQuery({
@ -77,13 +78,13 @@ export const Header = () => {
</div> </div>
{data?.html_url && ( {data?.html_url && (
<a href={data.html_url} target="_blank" rel="noopener noreferrer"> <ExternalLink href={data.html_url}>
<div className="flex mt-4 py-2 bg-blue-500 rounded justify-center"> <div className="flex mt-4 py-2 bg-blue-500 rounded justify-center">
<MegaphoneIcon className="h-6 w-6 text-blue-100" /> <MegaphoneIcon className="h-6 w-6 text-blue-100" />
<span className="text-blue-100 font-medium mx-3">New update available!</span> <span className="text-blue-100 font-medium mx-3">New update available!</span>
<span className="inline-flex items-center rounded-md bg-blue-100 px-2.5 py-0.5 text-sm font-medium text-blue-800">{data?.name}</span> <span className="inline-flex items-center rounded-md bg-blue-100 px-2.5 py-0.5 text-sm font-medium text-blue-800">{data?.name}</span>
</div> </div>
</a> </ExternalLink>
)} )}
</div> </div>

View file

@ -10,6 +10,7 @@ import { classNames } from "@utils";
import { ReactComponent as Logo } from "@app/logo.svg"; import { ReactComponent as Logo } from "@app/logo.svg";
import { NAV_ROUTES } from "./_shared"; import { NAV_ROUTES } from "./_shared";
import { ExternalLink } from "@components/ExternalLink";
export const LeftNav = () => ( export const LeftNav = () => (
<div className="flex items-center"> <div className="flex items-center">
@ -38,9 +39,7 @@ export const LeftNav = () => (
{item.name} {item.name}
</NavLink> </NavLink>
))} ))}
<a <ExternalLink
rel="noopener noreferrer"
target="_blank"
href="https://autobrr.com" href="https://autobrr.com"
className={classNames( className={classNames(
"text-gray-600 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium", "text-gray-600 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium",
@ -52,7 +51,7 @@ export const LeftNav = () => (
className="inline ml-1 h-5 w-5" className="inline ml-1 h-5 w-5"
aria-hidden="true" aria-hidden="true"
/> />
</a> </ExternalLink>
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,7 +5,7 @@
import { Field, FieldProps } from "formik"; import { Field, FieldProps } from "formik";
import { classNames } from "@utils"; import { classNames } from "@utils";
import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface ErrorFieldProps { interface ErrorFieldProps {
name: string; name: string;
@ -63,10 +63,9 @@ const CheckboxField = ({
<div className="ml-3 text-sm"> <div className="ml-3 text-sm">
<label htmlFor={name} className="flex mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"> <label htmlFor={name} className="flex mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && ( <DocsTooltip label={label}>{tooltip}</DocsTooltip>
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> ) : label}
)}
</div> </div>
</label> </label>
<p className="text-gray-500">{sublabel}</p> <p className="text-gray-500">{sublabel}</p>

View file

@ -9,7 +9,7 @@ import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/
import TextareaAutosize from "react-textarea-autosize"; import TextareaAutosize from "react-textarea-autosize";
import { useToggle } from "@hooks/hooks"; import { useToggle } from "@hooks/hooks";
import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
import { classNames } from "@utils"; import { classNames } from "@utils";
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
@ -46,10 +46,9 @@ export const TextField = ({
{label && ( {label && (
<label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"> <label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && ( <DocsTooltip label={label}>{tooltip}</DocsTooltip>
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> ) : label}
)}
</div> </div>
</label> </label>
)} )}
@ -185,8 +184,9 @@ export const RegexField = ({
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide" className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
> >
<div className="flex"> <div className="flex">
{label} {tooltip ? (
<span className="z-10">{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>}</span> <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</label> </label>
)} )}
@ -324,9 +324,10 @@ export const RegexTextAreaField = ({
htmlFor={name} htmlFor={name}
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide" className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
> >
<div className="flex"> <div className="flex z-10">
{label} {tooltip ? (
<span className="z-10">{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>}</span> <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</label> </label>
)} )}
@ -412,10 +413,9 @@ export const TextArea = ({
{label && ( {label && (
<label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"> <label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && ( <DocsTooltip label={label}>{tooltip}</DocsTooltip>
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> ) : label}
)}
</div> </div>
</label> </label>
)} )}
@ -484,10 +484,9 @@ export const TextAreaAutoResize = ({
{label && ( {label && (
<label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"> <label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && ( <DocsTooltip label={label}>{tooltip}</DocsTooltip>
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> ) : label}
)}
</div> </div>
</label> </label>
)} )}
@ -630,8 +629,9 @@ export const NumberField = ({
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide" className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
> >
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>} <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</label> </label>

View file

@ -12,7 +12,7 @@ import { Switch } from "@headlessui/react";
import { ErrorField, RequiredField } from "./common"; import { ErrorField, RequiredField } from "./common";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select"; import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { SelectFieldProps } from "./select"; import { SelectFieldProps } from "./select";
import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface TextFieldWideProps { interface TextFieldWideProps {
name: string; name: string;
@ -43,7 +43,9 @@ export const TextFieldWide = ({
<div> <div>
<label htmlFor={name} className="flex text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"> <label htmlFor={name} className="flex text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
<div className="flex"> <div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
<RequiredField required={required} /> <RequiredField required={required} />
</div> </div>
</label> </label>
@ -108,7 +110,9 @@ export const PasswordFieldWide = ({
<div> <div>
<label htmlFor={name} className="flex text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"> <label htmlFor={name} className="flex text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
<div className="flex"> <div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
<RequiredField required={required} /> <RequiredField required={required} />
</div> </div>
</label> </label>
@ -172,7 +176,9 @@ export const NumberFieldWide = ({
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2" className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
> >
<div className="flex"> <div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
<RequiredField required={required} /> <RequiredField required={required} />
</div> </div>
</label> </label>
@ -232,10 +238,11 @@ export const SwitchGroupWide = ({
<ul className="px-4 divide-y divide-gray-200 dark:divide-gray-700"> <ul className="px-4 divide-y divide-gray-200 dark:divide-gray-700">
<Switch.Group as="li" className="py-4 flex items-center justify-between"> <Switch.Group as="li" className="py-4 flex items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col">
<Switch.Label as="div" className="text-sm font-medium text-gray-900 dark:text-white" <Switch.Label as="div" passive className="text-sm font-medium text-gray-900 dark:text-white">
passive>
<div className="flex"> <div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</Switch.Label> </Switch.Label>
{description && ( {description && (
@ -396,8 +403,9 @@ export const SelectFieldWide = ({
className="flex text-sm font-medium text-gray-900 dark:text-white" className="flex text-sm font-medium text-gray-900 dark:text-white"
> >
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</label> </label>
</div> </div>

View file

@ -11,7 +11,7 @@ import { MultiSelect as RMSC } from "react-multi-select-component";
import { classNames, COL_WIDTHS } from "@utils"; import { classNames, COL_WIDTHS } from "@utils";
import { SettingsContext } from "@utils/Context"; import { SettingsContext } from "@utils/Context";
import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
export interface MultiSelectOption { export interface MultiSelectOption {
value: string | number; value: string | number;
@ -56,10 +56,9 @@ export const MultiSelect = ({
<label <label
htmlFor={label} className="flex mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200"> htmlFor={label} className="flex mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200">
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && ( <DocsTooltip label={label}>{tooltip}</DocsTooltip>
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> ) : label}
)}
</div> </div>
</label> </label>
@ -297,10 +296,9 @@ export const Select = ({
<> <>
<Listbox.Label className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"> <Listbox.Label className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex"> <div className="flex">
{label} {tooltip ? (
{tooltip && ( <DocsTooltip label={label}>{tooltip}</DocsTooltip>
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> ) : label}
)}
</div> </div>
</Listbox.Label> </Listbox.Label>
<div className="mt-2 relative"> <div className="mt-2 relative">

View file

@ -8,7 +8,7 @@ import { Field } from "formik";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select"; import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { OptionBasicTyped } from "@domain/constants"; import { OptionBasicTyped } from "@domain/constants";
import CreatableSelect from "react-select/creatable"; import CreatableSelect from "react-select/creatable";
import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface SelectFieldProps<T> { interface SelectFieldProps<T> {
name: string; name: string;
@ -29,7 +29,9 @@ export function SelectFieldCreatable<T>({ name, label, help, placeholder, toolti
className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2" className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2"
> >
<div className="flex"> <div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</label> </label>
</div> </div>
@ -200,7 +202,9 @@ export function SelectFieldBasic<T>({ name, label, help, placeholder, tooltip, d
className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2" className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2"
> >
<div className="flex"> <div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)} {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</label> </label>
</div> </div>

View file

@ -9,7 +9,7 @@ import { Field } from "formik";
import { Switch as HeadlessSwitch } from "@headlessui/react"; import { Switch as HeadlessSwitch } from "@headlessui/react";
import { classNames } from "@utils"; import { classNames } from "@utils";
import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
type SwitchProps<V = unknown> = { type SwitchProps<V = unknown> = {
label?: string label?: string
@ -88,13 +88,18 @@ const SwitchGroup = ({
}: SwitchGroupProps) => ( }: SwitchGroupProps) => (
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between"> <HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
{label && <div className="flex flex-col"> {label && <div className="flex flex-col">
<HeadlessSwitch.Label as={heading ? "h2" : "span"} className={classNames("flex float-left cursor-default mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide", heading ? "text-lg" : "text-sm")} <HeadlessSwitch.Label
passive> passive
<div className="flex"> as={heading ? "h2" : "span"}
{label} className={classNames(
{tooltip && ( "flex float-left cursor-default mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide",
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip> heading ? "text-lg" : "text-sm"
)} )}
>
<div className="flex">
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div> </div>
</HeadlessSwitch.Label> </HeadlessSwitch.Label>
{description && ( {description && (

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
.react-tooltip code {
background-color: rgb(63, 71, 94);
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
}
.react-tooltip a:hover {
text-decoration-line: underline;
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { PlacesType, Tooltip } from "react-tooltip";
import "./CustomTooltip.css";
interface CustomTooltipProps {
anchorId: string;
children?: React.ReactNode;
clickable?: boolean;
place?: PlacesType;
}
export const CustomTooltip = ({
anchorId,
children,
clickable = true,
place = "top"
}: CustomTooltipProps) => {
const id = `${anchorId}-tooltip`;
return (
<div className="flex items-center">
<svg id={id} className="ml-1 w-4 h-4 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10"/></svg>
<Tooltip
style={{ maxWidth: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff" }}
delayShow={100}
delayHide={150}
place={place}
anchorSelect={id}
data-html={true}
clickable={clickable}
>
{children}
</Tooltip>
</div>
);
};

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { Tooltip } from "./Tooltip";
interface DocsTooltipProps {
label?: React.ReactNode;
children?: React.ReactNode;
}
export const DocsTooltip = ({ label, children }: DocsTooltipProps) => (
<Tooltip
label={
<div className="flex flex-row items-center">
{label ?? null}
<svg className="ml-1 w-4 h-4 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10" /></svg>
</div>
}
>
{children}
</Tooltip>
);

View file

@ -3,22 +3,30 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
import * as React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Transition } from "@headlessui/react";
import { usePopperTooltip } from "react-popper-tooltip"; import { usePopperTooltip } from "react-popper-tooltip";
import { classNames } from "@utils"; import { classNames } from "@utils";
interface TooltipProps { interface TooltipProps {
label: ReactNode; label: ReactNode;
onLabelClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
title?: ReactNode; title?: ReactNode;
maxWidth?: string; maxWidth?: string;
requiresClick?: boolean;
children: ReactNode; children: ReactNode;
} }
// NOTE(stacksmash76): onClick is not propagated
// to the label (always-visible) component, so you will have
// to use the `onLabelClick` prop in this case.
export const Tooltip = ({ export const Tooltip = ({
label, label,
onLabelClick,
title, title,
children, children,
requiresClick,
maxWidth = "max-w-sm" maxWidth = "max-w-sm"
}: TooltipProps) => { }: TooltipProps) => {
const { const {
@ -28,22 +36,46 @@ export const Tooltip = ({
setTriggerRef, setTriggerRef,
visible visible
} = usePopperTooltip({ } = usePopperTooltip({
trigger: ["click"], trigger: requiresClick ? ["click"] : ["click", "hover"],
interactive: false interactive: !requiresClick,
delayHide: 200
}); });
if (!children || Array.isArray(children) && !children.length) {
return null;
}
return ( return (
<> <>
<div ref={setTriggerRef} className="truncate"> <div
ref={setTriggerRef}
className="truncate"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
onLabelClick?.(e);
}}
>
{label} {label}
</div> </div>
{visible && ( <Transition
show={visible}
className="z-10"
enter="transition duration-200 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition duration-200 ease-in"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div <div
ref={setTooltipRef} ref={setTooltipRef}
{...getTooltipProps({ {...getTooltipProps({
className: classNames( className: classNames(
maxWidth, maxWidth,
"rounded-md border border-gray-300 text-black text-xs shadow-lg dark:text-white dark:border-gray-700 dark:shadow-2xl" "rounded-md border border-gray-300 text-black text-xs normal-case tracking-normal font-normal shadow-lg dark:text-white dark:border-gray-700 dark:shadow-2xl"
) )
})} })}
> >
@ -55,13 +87,13 @@ export const Tooltip = ({
<div <div
className={classNames( className={classNames(
title ? "" : "rounded-t-md", title ? "" : "rounded-t-md",
"py-1 px-2 rounded-b-md bg-white dark:bg-gray-900" "whitespace-normal break-words py-1 px-2 rounded-b-md bg-white dark:bg-gray-900"
)} )}
> >
{children} {children}
</div> </div>
</div> </div>
)} </Transition>
</> </>
); );
}; };

View file

@ -26,6 +26,7 @@ import {
} from "@components/inputs"; } from "@components/inputs";
import { clientKeys } from "@screens/settings/DownloadClient"; import { clientKeys } from "@screens/settings/DownloadClient";
import { SelectFieldWide } from "@components/inputs/input_wide"; import { SelectFieldWide } from "@components/inputs/input_wide";
import { DocsLink, ExternalLink } from "@components/ExternalLink";
interface InitialValuesSettings { interface InitialValuesSettings {
basic?: { basic?: {
@ -54,7 +55,6 @@ interface InitialValues {
settings: InitialValuesSettings; settings: InitialValuesSettings;
} }
function FormFieldsDeluge() { function FormFieldsDeluge() {
const { const {
values: { tls } values: { tls }
@ -63,11 +63,20 @@ function FormFieldsDeluge() {
return ( return (
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide <TextFieldWide
required
name="host" name="host"
label="Host" label="Host"
help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd:port" help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd:port"
tooltip={<div><p>See guides for how to connect to Deluge for various server types in our docs.</p><br /><p>Dedicated servers:</p><a href='https://autobrr.com/configuration/download-clients/dedicated#deluge' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#deluge</a><p>Shared seedbox providers:</p><a href='https://autobrr.com/configuration/download-clients/shared-seedboxes#deluge' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/shared-seedboxes#deluge</a></div>} tooltip={
required={true} <div>
<p>See guides for how to connect to Deluge for various server types in our docs.</p>
<br />
<p>Dedicated servers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#deluge" />
<p>Shared seedbox providers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/shared-seedboxes#deluge" />
</div>
}
/> />
<NumberFieldWide <NumberFieldWide
@ -99,14 +108,23 @@ function FormFieldsArr() {
return ( return (
<div className="flex flex-col space-y-4 px-1 mb-4 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 mb-4 sm:py-0 sm:space-y-0">
<TextFieldWide <TextFieldWide
required
name="host" name="host"
label="Host" label="Host"
help="Full url http(s)://domain.ltd and/or subdomain/subfolder" help="Full url http(s)://domain.ltd and/or subdomain/subfolder"
tooltip={<div><p>See guides for how to connect to the *arr suite for various server types in our docs.</p><br /><p>Dedicated servers:</p><a href='https://autobrr.com/configuration/download-clients/dedicated/#sonarr' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated/</a><p>Shared seedbox providers:</p><a href='https://autobrr.com/configuration/download-clients/shared-seedboxes#sonarr' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/shared-seedboxes</a></div>} tooltip={
required={true} <div>
<p>See guides for how to connect to the *arr suite for various server types in our docs.</p>
<br />
<p>Dedicated servers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated/#sonarr" />
<p>Shared seedbox providers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/shared-seedboxes#sonarr" />
</div>
}
/> />
<PasswordFieldWide name="settings.apikey" label="API key" required={true}/> <PasswordFieldWide required name="settings.apikey" label="API key" />
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" /> <SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
@ -130,11 +148,20 @@ function FormFieldsQbit() {
return ( return (
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide <TextFieldWide
required
name="host" name="host"
label="Host" label="Host"
help="Eg. http(s)://client.domain.ltd, http(s)://domain.ltd/qbittorrent, http://domain.ltd:port" help="Eg. http(s)://client.domain.ltd, http(s)://domain.ltd/qbittorrent, http://domain.ltd:port"
tooltip={<div><p>See guides for how to connect to qBittorrent for various server types in our docs.</p><br /><p>Dedicated servers:</p><a href='https://autobrr.com/configuration/download-clients/dedicated#qbittorrent' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#qbittorrent</a><p>Shared seedbox providers:</p><a href='https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent</a></div>} tooltip={
required={true} <div>
<p>See guides for how to connect to qBittorrent for various server types in our docs.</p>
<br />
<p>Dedicated servers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#qbittorrent" />
<p>Shared seedbox providers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent" />
</div>
}
/> />
{port > 0 && ( {port > 0 && (
@ -177,16 +204,15 @@ function FormFieldsPorla() {
return ( return (
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide <TextFieldWide
required
name="host" name="host"
label="Host" label="Host"
help="Eg. http(s)://client.domain.ltd, http(s)://domain.ltd/porla, http://domain.ltd:port" help="Eg. http(s)://client.domain.ltd, http(s)://domain.ltd/porla, http://domain.ltd:port"
required={true}
/> />
<SwitchGroupWide name="tls" label="TLS" /> <SwitchGroupWide name="tls" label="TLS" />
<PasswordFieldWide name="settings.apikey" label="Auth token" required={true}/> <PasswordFieldWide required name="settings.apikey" label="Auth token" />
{tls && ( {tls && (
<SwitchGroupWide <SwitchGroupWide
@ -215,11 +241,20 @@ function FormFieldsRTorrent() {
return ( return (
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide <TextFieldWide
required
name="host" name="host"
label="Host" label="Host"
help="Eg. http(s)://client.domain.ltd/RPC2, http(s)://domain.ltd/client, http(s)://domain.ltd/RPC2" help="Eg. http(s)://client.domain.ltd/RPC2, http(s)://domain.ltd/client, http(s)://domain.ltd/RPC2"
tooltip={<div><p>See guides for how to connect to rTorrent for various server types in our docs.</p><br /><p>Dedicated servers:</p><a href='https://autobrr.com/configuration/download-clients/dedicated#rtorrent--rutorrent' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#rtorrent--rutorrent</a><p>Shared seedbox providers:</p><a href='https://autobrr.com/configuration/download-clients/shared-seedboxes#rtorrent' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/shared-seedboxes#rtorrent</a></div>} tooltip={
required={true} <div>
<p>See guides for how to connect to rTorrent for various server types in our docs.</p>
<br />
<p>Dedicated servers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#rtorrent--rutorrent" />
<p>Shared seedbox providers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/shared-seedboxes#rtorrent" />
</div>
}
/> />
<SwitchGroupWide name="tls" label="TLS" /> <SwitchGroupWide name="tls" label="TLS" />
@ -251,11 +286,20 @@ function FormFieldsTransmission() {
return ( return (
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide <TextFieldWide
required
name="host" name="host"
label="Host" label="Host"
help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd" help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd"
tooltip={<div><p>See guides for how to connect to Transmission for various server types in our docs.</p><br /><p>Dedicated servers:</p><a href='https://autobrr.com/configuration/download-clients/dedicated#transmission' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#transmission</a><p>Shared seedbox providers:</p><a href='https://autobrr.com/configuration/download-clients/shared-seedboxes#transmisison' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/shared-seedboxes#transmisison</a></div>} tooltip={
required={true} <div>
<p>See guides for how to connect to Transmission for various server types in our docs.</p>
<br />
<p>Dedicated servers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#transmission" />
<p>Shared seedbox providers:</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/shared-seedboxes#transmisison" />
</div>
}
/> />
<NumberFieldWide name="port" label="Port" help="Port for Transmission" /> <NumberFieldWide name="port" label="Port" help="Port for Transmission" />
@ -286,7 +330,16 @@ function FormFieldsSabnzbd() {
name="host" name="host"
label="Host" label="Host"
help="Eg. http://ip:port or https://url.com/sabnzbd" help="Eg. http://ip:port or https://url.com/sabnzbd"
// tooltip={<div><p>See guides for how to connect to qBittorrent for various server types in our docs.</p><br /><p>Dedicated servers:</p><a href='https://autobrr.com/configuration/download-clients/dedicated#qbittorrent' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#qbittorrent</a><p>Shared seedbox providers:</p><a href='https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent</a></div>} tooltip={
<div>
<p>See our guides on how to connect to qBittorrent for various server types in our docs.</p>
<br />
<p>Dedicated servers:</p>
<ExternalLink href="https://autobrr.com/configuration/download-clients/dedicated#qbittorrent" />
<p>Shared seedbox providers:</p>
<ExternalLink href="https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent" />
</div>
}
/> />
{port > 0 && ( {port > 0 && (
@ -357,10 +410,22 @@ function FormFieldsRulesBasic() {
</p> </p>
</div> </div>
<SwitchGroupWide name="settings.rules.enabled" label="Enabled"/> <SwitchGroupWide name="settings.rules.enabled" label="Enabled" />
{settings && settings.rules?.enabled === true && ( {settings && settings.rules?.enabled === true && (
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads" tooltip={<span><p>Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.</p><a href='https://autobrr.com/configuration/download-clients/dedicated#deluge-rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#deluge-rules</a><br /><br /><p>See recommendations for various server types here:</p><a href='https://autobrr.com/filters/examples#build-buffer' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/examples#build-buffer</a></span>} /> <NumberFieldWide
name="settings.rules.max_active_downloads"
label="Max active downloads"
tooltip={
<span>
<p>Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#deluge-rules" />
<br /><br />
<p>See recommendations for various server types here:</p>
<DocsLink href='https://autobrr.com/filters/examples#build-buffer' />
</span>
}
/>
)} )}
</div> </div>
); );
@ -389,7 +454,16 @@ function FormFieldsRulesQbit() {
<NumberFieldWide <NumberFieldWide
name="settings.rules.max_active_downloads" name="settings.rules.max_active_downloads"
label="Max active downloads" label="Max active downloads"
tooltip={<><p>Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.</p><a href='https://autobrr.com/configuration/download-clients/dedicated#qbittorrent-rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#qbittorrent-rules</a><br /><br /><p>See recommendations for various server types here:</p><a href='https://autobrr.com/filters/examples#build-buffer' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/examples#build-buffer</a></>} /> tooltip={
<>
<p>Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#qbittorrent-rules" />
<br /><br />
<p>See recommendations for various server types here:</p>
<DocsLink href="https://autobrr.com/filters/examples#build-buffer" />
</>
}
/>
<SwitchGroupWide <SwitchGroupWide
name="settings.rules.ignore_slow_torrents" name="settings.rules.ignore_slow_torrents"
label="Ignore slow torrents" label="Ignore slow torrents"
@ -447,7 +521,15 @@ function FormFieldsRulesTransmission() {
<NumberFieldWide <NumberFieldWide
name="settings.rules.max_active_downloads" name="settings.rules.max_active_downloads"
label="Max active downloads" label="Max active downloads"
tooltip={<><p>Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.</p><a href='https://autobrr.com/configuration/download-clients/dedicated#transmission-rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/download-clients/dedicated#transmission-rules</a><br /><br /><p>See recommendations for various server types here:</p><a href='https://autobrr.com/filters/examples#build-buffer' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/examples#build-buffer</a></>} tooltip={
<>
<p>Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.</p>
<DocsLink href="https://autobrr.com/configuration/download-clients/dedicated#transmission-rules" />
<br /><br />
<p>See recommendations for various server types here:</p>
<DocsLink href="https://autobrr.com/filters/examples#build-buffer" />
</>
}
/> />
</> </>
)} )}
@ -456,11 +538,11 @@ function FormFieldsRulesTransmission() {
} }
export const rulesComponentMap: componentMapType = { export const rulesComponentMap: componentMapType = {
DELUGE_V1: <FormFieldsRulesBasic/>, DELUGE_V1: <FormFieldsRulesBasic />,
DELUGE_V2: <FormFieldsRulesBasic/>, DELUGE_V2: <FormFieldsRulesBasic />,
QBITTORRENT: <FormFieldsRulesQbit/>, QBITTORRENT: <FormFieldsRulesQbit />,
PORLA: <FormFieldsRulesBasic/>, PORLA: <FormFieldsRulesBasic />,
TRANSMISSION: <FormFieldsRulesTransmission/> TRANSMISSION: <FormFieldsRulesTransmission />
}; };
interface formButtonsProps { interface formButtonsProps {
@ -582,12 +664,12 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
mutationFn: (client: DownloadClient) => APIClient.download_clients.create(client), mutationFn: (client: DownloadClient) => APIClient.download_clients.create(client),
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: clientKeys.lists() }); queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
toast.custom((t) => <Toast type="success" body="Client was added" t={t}/>); toast.custom((t) => <Toast type="success" body="Client was added" t={t} />);
toggle(); toggle();
}, },
onError: () => { onError: () => {
toast.custom((t) => <Toast type="error" body="Client could not be added" t={t}/>); toast.custom((t) => <Toast type="error" body="Client could not be added" t={t} />);
} }
}); });
@ -647,7 +729,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
onClose={toggle} onClose={toggle}
> >
<div className="absolute inset-0 overflow-hidden"> <div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/> <Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16"> <div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<Transition.Child <Transition.Child
@ -697,8 +779,8 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
</div> </div>
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0"> <div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide name="name" label="Name" required={true}/> <TextFieldWide required name="name" label="Name" />
<SwitchGroupWide name="enabled" label="Enabled"/> <SwitchGroupWide name="enabled" label="Enabled" />
<RadioFieldsetWide <RadioFieldsetWide
name="type" name="type"
legend="Type" legend="Type"
@ -720,7 +802,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
values={values} values={values}
/> />
<DEBUG values={values}/> <DEBUG values={values} />
</Form> </Form>
)} )}
</Formik> </Formik>
@ -756,7 +838,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
queryClient.invalidateQueries({ queryKey: clientKeys.lists() }); queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) }); queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) });
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t}/>); toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t} />);
toggle(); toggle();
} }
}); });
@ -769,7 +851,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
queryClient.invalidateQueries({ queryKey: clientKeys.lists() }); queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) }); queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) });
toast.custom((t) => <Toast type="success" body={`${client.name} was deleted.`} t={t}/>); toast.custom((t) => <Toast type="success" body={`${client.name} was deleted.`} t={t} />);
toggleDeleteModal(); toggleDeleteModal();
} }
}); });
@ -841,7 +923,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
text="Are you sure you want to remove this download client? This action cannot be undone." text="Are you sure you want to remove this download client? This action cannot be undone."
/> />
<div className="absolute inset-0 overflow-hidden"> <div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/> <Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16"> <div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<Transition.Child <Transition.Child
@ -892,8 +974,8 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
</div> </div>
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700"> <div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<TextFieldWide name="name" label="Name" required={true}/> <TextFieldWide required name="name" label="Name" />
<SwitchGroupWide name="enabled" label="Enabled"/> <SwitchGroupWide name="enabled" label="Enabled" />
<RadioFieldsetWide <RadioFieldsetWide
name="type" name="type"
legend="Type" legend="Type"
@ -916,7 +998,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
values={values} values={values}
/> />
<DEBUG values={values}/> <DEBUG values={values} />
</Form> </Form>
); );
}} }}

View file

@ -22,6 +22,7 @@ import { SelectFieldBasic, SelectFieldCreatable } from "@components/inputs/selec
import { FeedDownloadTypeOptions } from "@domain/constants"; import { FeedDownloadTypeOptions } from "@domain/constants";
import { feedKeys } from "@screens/settings/Feed"; import { feedKeys } from "@screens/settings/Feed";
import { indexerKeys } from "@screens/settings/Indexer"; import { indexerKeys } from "@screens/settings/Indexer";
import { DocsLink } from "@components/ExternalLink";
const Input = (props: InputProps) => ( const Input = (props: InputProps) => (
<components.Input <components.Input
@ -87,10 +88,26 @@ const IrcSettingFields = (ind: IndexerDefinition, indexer: string) => {
{ind.irc.settings.map((f: IndexerSetting, idx: number) => { {ind.irc.settings.map((f: IndexerSetting, idx: number) => {
switch (f.type) { switch (f.type) {
case "text": case "text":
return <TextFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} autoComplete="off" validate={validateField(f)} tooltip={<div><p>Please read our IRC guide if you are unfamiliar with IRC.</p><a href='https://autobrr.com/configuration/irc' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/configuration/irc</a></div>} />; return (
<TextFieldWide
key={idx}
name={`irc.${f.name}`}
label={f.label}
required={f.required}
help={f.help}
autoComplete="off"
validate={validateField(f)}
tooltip={
<div>
<p>Please read our IRC guide if you are unfamiliar with IRC.</p>
<DocsLink href="https://autobrr.com/configuration/irc" />
</div>
}
/>
);
case "secret": case "secret":
if (f.name === "invite_command") { if (f.name === "invite_command") {
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultVisible={true} defaultValue={f.default} validate={validateField(f)} />; return <PasswordFieldWide defaultVisible name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
} }
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />; return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} validate={validateField(f)} />;
} }
@ -224,7 +241,21 @@ const SettingFields = (ind: IndexerDefinition, indexer: string) => {
); );
case "secret": case "secret":
return ( return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} validate={validateField(f)} tooltip={<div><p>This field does not take a full URL. Only use alphanumeric strings like <code>uqcdi67cibkx3an8cmdm</code>.</p><br /><a href='https://autobrr.com/faqs#common-action-rejections' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/faqs#common-action-rejections</a></div>} /> <PasswordFieldWide
name={`settings.${f.name}`}
label={f.label}
required={f.required}
key={idx}
help={f.help}
validate={validateField(f)}
tooltip={
<div>
<p>This field does not take a full URL. Only use alphanumeric strings like <code>uqcdi67cibkx3an8cmdm</code>.</p>
<br />
<DocsLink href="https://autobrr.com/faqs#common-action-rejections" />
</div>
}
/>
); );
} }
return null; return null;
@ -758,7 +789,19 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
); );
case "secret": case "secret":
return ( return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} tooltip={<div><p>This field does not take a full URL. Only use alphanumeric strings like <code>uqcdi67cibkx3an8cmdm</code>.</p><br /><a href='https://autobrr.com/faqs#common-action-rejections' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/faqs#common-action-rejections</a></div>} /> <PasswordFieldWide
key={idx}
name={`settings.${f.name}`}
label={f.label}
help={f.help}
tooltip={
<div>
<p>This field does not take a full URL. Only use alphanumeric strings like <code>uqcdi67cibkx3an8cmdm</code>.</p>
<br />
<DocsLink href="https://autobrr.com/faqs#common-action-rejections" />
</div>
}
/>
); );
} }
return null; return null;

View file

@ -20,6 +20,7 @@ import Toast from "@components/notifications/Toast";
import { SlideOver } from "@components/panels"; import { SlideOver } from "@components/panels";
import { componentMapType } from "./DownloadClientForms"; import { componentMapType } from "./DownloadClientForms";
import { notificationKeys } from "@screens/settings/Notifications"; import { notificationKeys } from "@screens/settings/Notifications";
import { ExternalLink } from "@components/ExternalLink";
const Input = (props: InputProps) => { const Input = (props: InputProps) => {
return ( return (
@ -68,7 +69,14 @@ function FormFieldsDiscord() {
<div className="px-4 space-y-1"> <div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title> <Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
Create a <a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400">webhook integration</a> in your server. {"Create a "}
<ExternalLink
href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"
className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400"
>
webhook integration
</ExternalLink>
{" in your server."}
</p> </p>
</div> </div>
@ -107,7 +115,14 @@ function FormFieldsTelegram() {
<div className="px-4 space-y-1"> <div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title> <Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
Read how to <a href="https://core.telegram.org/bots#3-how-do-i-create-a-bot" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400">create a bot</a>. {"Read how to "}
<ExternalLink
href="https://core.telegram.org/bots#3-how-do-i-create-a-bot"
className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400"
>
create a bot
</ExternalLink>
{"."}
</p> </p>
</div> </div>
@ -136,7 +151,14 @@ function FormFieldsPushover() {
<div className="px-4 space-y-1"> <div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title> <Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
Register a new <a href="https://support.pushover.net/i175-how-do-i-get-an-api-or-application-token" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400">application</a> and add its API Token here. {"Register a new "}
<ExternalLink
href="https://support.pushover.net/i175-how-do-i-get-an-api-or-application-token"
className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400"
>
application
</ExternalLink>
{" and add its API Token here."}
</p> </p>
</div> </div>

View file

@ -8,7 +8,6 @@ import { createRoot } from "react-dom/client";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import "./index.css"; import "./index.css";
import "react-tooltip/dist/react-tooltip.css";
import { App } from "./App"; import { App } from "./App";
import { InitializeGlobalContext } from "./utils/Context"; import { InitializeGlobalContext } from "./utils/Context";

View file

@ -7,6 +7,7 @@ import { useState } from "react";
import { ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/24/solid"; import { ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/24/solid";
import { ReleaseTable } from "./releases/ReleaseTable"; import { ReleaseTable } from "./releases/ReleaseTable";
import { ExternalLink } from "@components/ExternalLink";
const Code = ({ children }: { children: React.ReactNode }) => ( const Code = ({ children }: { children: React.ReactNode }) => (
<code className="rounded-md inline-block mb-1 px-1 py-0.5 border bg-gray-100 border-gray-300 dark:bg-gray-800 dark:border-gray-700"> <code className="rounded-md inline-block mb-1 px-1 py-0.5 border bg-gray-100 border-gray-300 dark:bg-gray-800 dark:border-gray-700">
@ -45,7 +46,7 @@ export const Releases = () => {
<div className="flex justify-between items-center text-base font-medium pl-2 py-1 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md"> <div className="flex justify-between items-center text-base font-medium pl-2 py-1 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md">
Search tips Search tips
</div> </div>
<div className={"rounded-t-md py-1 px-2 rounded-b-md bg-white dark:bg-gray-900"}> <div className="rounded-t-md py-1 px-2 rounded-b-md bg-white dark:bg-gray-900">
You can use <b>2</b> special <span className="underline decoration-2 underline-offset-2 decoration-amber-500">wildcard characters</span> for the purpose of pattern matching. You can use <b>2</b> special <span className="underline decoration-2 underline-offset-2 decoration-amber-500">wildcard characters</span> for the purpose of pattern matching.
<br /> <br />
- Percent (<Code>%</Code>) - for matching any <i>sequence</i> of characters (equivalent to <Code>*</Code> in Regex) - Percent (<Code>%</Code>) - for matching any <i>sequence</i> of characters (equivalent to <Code>*</Code> in Regex)
@ -75,14 +76,14 @@ export const Releases = () => {
<br /><br /> <br /><br />
As always, please refer to our <a {"As always, please refer to our "}
rel="noopener noreferrer" <ExternalLink
target="_blank"
href="https://autobrr.com/usage/search/" href="https://autobrr.com/usage/search/"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 decoration-2 hover:text-black hover:dark:text-gray-100" className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 decoration-2 hover:text-black hover:dark:text-gray-100"
> >
Search function usage Search function usage
</a> documentation page to keep up with the latest examples and information. </ExternalLink>
{" documentation page to keep up with the latest examples and information."}
</div> </div>
</div> </div>
) : null} ) : null}

View file

@ -8,13 +8,13 @@ import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Tooltip } from "react-tooltip";
import { ReactComponent as Logo } from "@app/logo.svg"; import { ReactComponent as Logo } from "@app/logo.svg";
import { APIClient } from "@api/APIClient"; import { APIClient } from "@api/APIClient";
import { AuthContext } from "@utils/Context"; import { AuthContext } from "@utils/Context";
import { PasswordInput, TextInput } from "@components/inputs/text";
import Toast from "@components/notifications/Toast"; import Toast from "@components/notifications/Toast";
import { Tooltip } from "@components/tooltips/Tooltip";
import { PasswordInput, TextInput } from "@components/inputs/text";
type LoginFormFields = { type LoginFormFields = {
username: string; username: string;
@ -103,8 +103,15 @@ export const Login = () => {
</button> </button>
<div> <div>
<span className="flex float-right items-center mt-3 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide cursor-pointer" id="forgot"> <span className="flex float-right items-center mt-3 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide cursor-pointer" id="forgot">
Forgot?<svg className="ml-1 w-3 h-3 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10"/></svg> <Tooltip
<Tooltip style={{ maxWidth: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", opacity: "1" }} place="bottom" delayShow={100} delayHide={150} anchorId="forgot" html="<p style='padding-top: 2px'>If you forget your password you can reset it via the terminal: <code>autobrrctl --config /home/username/.config/autobrr change-password <USERNAME></code></p>" clickable={true}/> label={
<div className="flex flex-row items-center">
Forgot? <svg className="ml-1 w-3 h-3 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10"/></svg>
</div>
}
>
<p className="py-1">If you forget your password you can reset it via the terminal: <code>autobrrctl --config /home/username/.config/autobrr change-password $USERNAME</code></p>
</Tooltip>
</span> </span>
</div> </div>
</div> </div>

View file

@ -28,6 +28,7 @@ import { DeleteModal } from "@components/modals";
import { CollapsableSection } from "./Details"; import { CollapsableSection } from "./Details";
import { TextArea } from "@components/inputs/input"; import { TextArea } from "@components/inputs/input";
import Toast from "@components/notifications/Toast"; import Toast from "@components/notifications/Toast";
import { DocsLink } from "@components/ExternalLink";
interface FilterActionsProps { interface FilterActionsProps {
filter: Filter; filter: Filter;
@ -224,7 +225,15 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
label="Save path" label="Save path"
columns={6} columns={6}
placeholder="eg. /full/path/to/download_folder" placeholder="eg. /full/path/to/download_folder"
tooltip={<div><p>Set a custom save path for this action. Automatic Torrent Management will take care of this if using qBittorrent with categories.</p><br /><p>The field can use macros to transform/add values from metadata:</p><a href='https://autobrr.com/filters/actions#macros' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/actions#macros</a></div>} /> tooltip={
<div>
<p>Set a custom save path for this action. Automatic Torrent Management will take care of this if using qBittorrent with categories.</p>
<br />
<p>The field can use macros to transform/add values from metadata:</p>
<DocsLink href="https://autobrr.com/filters/actions#macros" />
</div>
}
/>
</div> </div>
</div> </div>
@ -234,13 +243,25 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
label="Category" label="Category"
columns={6} columns={6}
placeholder="eg. category" placeholder="eg. category"
tooltip={<div><p>The field can use macros to transform/add values from metadata:</p><a href='https://autobrr.com/filters/actions#macros' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/actions#macros</a></div>} /> tooltip={
<div>
<p>The field can use macros to transform/add values from metadata:</p>
<DocsLink href="https://autobrr.com/filters/actions#macros" />
</div>
}
/>
<TextField <TextField
name={`actions.${idx}.tags`} name={`actions.${idx}.tags`}
label="Tags" label="Tags"
columns={6} columns={6}
placeholder="eg. tag1,tag2" placeholder="eg. tag1,tag2"
tooltip={<div><p>The field can use macros to transform/add values from metadata:</p><a href='https://autobrr.com/filters/actions#macros' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/actions#macros</a></div>} /> tooltip={
<div>
<p>The field can use macros to transform/add values from metadata:</p>
<DocsLink href="https://autobrr.com/filters/actions#macros" />
</div>
}
/>
</div> </div>
<CollapsableSection title="Rules" subtitle="client options"> <CollapsableSection title="Rules" subtitle="client options">
@ -282,7 +303,14 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
<SwitchGroup <SwitchGroup
name={`actions.${idx}.ignore_rules`} name={`actions.${idx}.ignore_rules`}
label="Ignore client rules" label="Ignore client rules"
tooltip={<div><p>Choose to ignore rules set in <Link className='text-blue-400 visited:text-blue-400' to="/settings/clients">Client Settings</Link>.</p></div>} /> tooltip={
<div>
<p>
Choose to ignore rules set in <Link className="text-blue-400 visited:text-blue-400" to="/settings/clients">Client Settings</Link>.
</p>
</div>
}
/>
</div> </div>
<div className="col-span-6"> <div className="col-span-6">
<Select <Select

View file

@ -51,6 +51,7 @@ import { FilterActions } from "./Action";
import { filterKeys } from "./List"; import { filterKeys } from "./List";
import { External } from "@screens/filters/External"; import { External } from "@screens/filters/External";
import { SectionLoader } from "@components/SectionLoader"; import { SectionLoader } from "@components/SectionLoader";
import { DocsLink } from "@components/ExternalLink";
interface tabType { interface tabType {
name: string; name: string;
@ -458,12 +459,75 @@ export function General() {
<TitleSubtitle title="Rules" subtitle="Specify rules on how torrents should be handled/selected." /> <TitleSubtitle title="Rules" subtitle="Specify rules on how torrents should be handled/selected." />
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<TextField name="min_size" label="Min size" columns={6} placeholder="eg. 100MiB, 80GB" tooltip={<div><p>Supports units such as MB, MiB, GB, etc.</p><a href='https://autobrr.com/filters#rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#rules</a></div>} /> <TextField
<TextField name="max_size" label="Max size" columns={6} placeholder="eg. 100MiB, 80GB" tooltip={<div><p>Supports units such as MB, MiB, GB, etc.</p><a href='https://autobrr.com/filters#rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#rules</a></div>} /> name="min_size"
<NumberField name="delay" label="Delay" placeholder="Number of seconds to delay actions" tooltip={<div><p>Number of seconds to wait before running actions.</p><a href='https://autobrr.com/filters#rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#rules</a></div>} /> label="Min size"
<NumberField name="priority" label="Priority" placeholder="Higher number = higher prio" tooltip={<div><p>Filters are checked in order of priority. Higher number = higher priority.</p><a href='https://autobrr.com/filters#rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#rules</a></div>} /> columns={6}
<NumberField name="max_downloads" label="Max downloads" placeholder="Takes any number (0 is infinite)" tooltip={<div><p>Number of max downloads as specified by the respective unit.</p><a href='https://autobrr.com/filters#rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#rules</a></div>} /> placeholder="eg. 100MiB, 80GB"
<Select name="max_downloads_unit" label="Max downloads per" options={downloadsPerUnitOptions} optionDefaultText="Select unit" tooltip={<div><p>The unit of time for counting the maximum downloads per filter.</p><a href='https://autobrr.com/filters#rules' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#rules</a></div>} /> tooltip={
<div>
<p>Supports units such as MB, MiB, GB, etc.</p>
<DocsLink href="https://autobrr.com/filters#rules" />
</div>
}
/>
<TextField
name="max_size"
label="Max size"
columns={6}
placeholder="eg. 100MiB, 80GB"
tooltip={
<div>
<p>Supports units such as MB, MiB, GB, etc.</p>
<DocsLink href="https://autobrr.com/filters#rules" />
</div>
}
/>
<NumberField
name="delay"
label="Delay"
placeholder="Number of seconds to delay actions"
tooltip={
<div>
<p>Number of seconds to wait before running actions.</p>
<DocsLink href="https://autobrr.com/filters#rules" />
</div>
}
/>
<NumberField
name="priority"
label="Priority"
placeholder="Higher number = higher priority"
tooltip={
<div>
<p>Filters are checked in order of priority. Higher number = higher priority.</p>
<DocsLink href="https://autobrr.com/filters#rules" />
</div>
}
/>
<NumberField
name="max_downloads"
label="Max downloads"
placeholder="Takes any number (0 is infinite)"
tooltip={
<div>
<p>Number of max downloads as specified by the respective unit.</p>
<DocsLink href="https://autobrr.com/filters#rules" />
</div>
}
/>
<Select
name="max_downloads_unit"
label="Max downloads per"
options={downloadsPerUnitOptions}
optionDefaultText="Select unit"
tooltip={
<div>
<p>The unit of time for counting the maximum downloads per filter.</p>
<DocsLink href="https://autobrr.com/filters#rules" />
</div>
}
/>
</div> </div>
</div> </div>
@ -478,43 +542,194 @@ export function MoviesTv() {
return ( return (
<div> <div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<TextAreaAutoResize name="shows" label="Movies / Shows" columns={8} placeholder="eg. Movie,Show 1,Show?2" tooltip={<div><p>You can use basic filtering like wildcards <code>*</code> or replace single characters with <code>?</code></p><a href='https://autobrr.com/filters#tvmovies' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#tvmovies</a></div>} /> <TextAreaAutoResize
<TextField name="years" label="Years" columns={4} placeholder="eg. 2018,2019-2021" tooltip={<div><p>This field takes a range of years and/or comma separated single years.</p><a href='https://autobrr.com/filters#tvmovies' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#tvmovies</a></div>} /> name="shows"
label="Movies / Shows"
columns={8}
placeholder="eg. Movie,Show 1,Show?2"
tooltip={
<div>
<p>You can use basic filtering like wildcards <code>*</code> or replace single characters with <code>?</code></p>
<DocsLink href="https://autobrr.com/filters#tvmovies" />
</div>
}
/>
<TextField
name="years"
label="Years"
columns={4}
placeholder="eg. 2018,2019-2021"
tooltip={
<div>
<p>This field takes a range of years and/or comma separated single years.</p>
<DocsLink href="https://autobrr.com/filters#tvmovies" />
</div>
}
/>
</div> </div>
<div className="mt-6 lg:pb-8"> <div className="mt-6 lg:pb-8">
<TitleSubtitle title="Seasons and Episodes" subtitle="Set season and episode match constraints." /> <TitleSubtitle
title="Seasons and Episodes"
subtitle="Set season and episode match constraints."
/>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<TextField name="seasons" label="Seasons" columns={8} placeholder="eg. 1,3,2-6" tooltip={<div><p>See docs for information about how to <b>only</b> grab season packs:</p><a href='https://autobrr.com/filters/examples#only-season-packs' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/examples#only-season-packs</a></div>} /> <TextField
<TextField name="episodes" label="Episodes" columns={4} placeholder="eg. 2,4,10-20" tooltip={<div><p>See docs for information about how to <b>only</b> grab episodes:</p><a href='https://autobrr.com/filters/examples/#skip-season-packs' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/examples/#skip-season-packs</a></div>} /> name="seasons"
label="Seasons"
columns={8}
placeholder="eg. 1,3,2-6"
tooltip={
<div>
<p>See docs for information about how to <b>only</b> grab season packs:</p>
<DocsLink href="https://autobrr.com/filters/examples#only-season-packs" />
</div>
}
/>
<TextField
name="episodes"
label="Episodes"
columns={4}
placeholder="eg. 2,4,10-20"
tooltip={
<div>
<p>See docs for information about how to <b>only</b> grab episodes:</p>
<DocsLink href="https://autobrr.com/filters/examples/#skip-season-packs" />
</div>
}
/>
</div> </div>
<div className="mt-6"> <div className="mt-6">
<CheckboxField name="smart_episode" label="Smart Episode" sublabel="Do not match episodes older than the last one matched." /> {/*Do not match older or already existing episodes.*/} <CheckboxField
name="smart_episode"
label="Smart Episode"
sublabel="Do not match episodes older than the last one matched."
/>{" "}
{/*Do not match older or already existing episodes.*/}
</div> </div>
</div> </div>
<div className="mt-6 lg:pb-8"> <div className="mt-6 lg:pb-8">
<TitleSubtitle title="Quality" subtitle="Set resolution, source, codec and related match constraints." /> <TitleSubtitle
title="Quality"
subtitle="Set resolution, source, codec and related match constraints."
/>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="resolutions" options={RESOLUTION_OPTIONS} label="resolutions" columns={6} creatable={true} tooltip={<div><p>Will match releases which contain any of the selected resolutions.</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> <MultiSelect
<MultiSelect name="sources" options={SOURCES_OPTIONS} label="sources" columns={6} creatable={true} tooltip={<div><p>Will match releases which contain any of the selected sources.</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> name="resolutions"
options={RESOLUTION_OPTIONS}
label="resolutions"
columns={6}
creatable={true}
tooltip={
<div>
<p>Will match releases which contain any of the selected resolutions.</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
<MultiSelect
name="sources"
options={SOURCES_OPTIONS}
label="sources"
columns={6}
creatable={true}
tooltip={
<div>
<p>Will match releases which contain any of the selected sources.</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
</div> </div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="codecs" options={CODECS_OPTIONS} label="codecs" columns={6} creatable={true} tooltip={<div><p>Will match releases which contain any of the selected codecs.</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> <MultiSelect
<MultiSelect name="containers" options={CONTAINER_OPTIONS} label="containers" columns={6} creatable={true} tooltip={<div><p>Will match releases which contain any of the selected containers.</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> name="codecs"
options={CODECS_OPTIONS}
label="codecs"
columns={6}
creatable={true}
tooltip={
<div>
<p>Will match releases which contain any of the selected codecs.</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
<MultiSelect
name="containers"
options={CONTAINER_OPTIONS}
label="containers"
columns={6}
creatable={true}
tooltip={
<div>
<p>Will match releases which contain any of the selected containers.</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
</div> </div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="match_hdr" options={HDR_OPTIONS} label="Match HDR" columns={6} creatable={true} tooltip={<div><p>Will match releases which contain any of the selected HDR designations.</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> <MultiSelect
<MultiSelect name="except_hdr" options={HDR_OPTIONS} label="Except HDR" columns={6} creatable={true} tooltip={<div><p>Won't match releases which contain any of the selected HDR designations (takes priority over Match HDR).</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> name="match_hdr"
options={HDR_OPTIONS}
label="Match HDR"
columns={6}
creatable={true}
tooltip={
<div>
<p>Will match releases which contain any of the selected HDR designations.</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
<MultiSelect
name="except_hdr"
options={HDR_OPTIONS}
label="Except HDR"
columns={6}
creatable={true}
tooltip={
<div>
<p>Won't match releases which contain any of the selected HDR designations (takes priority over Match HDR).</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
</div> </div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="match_other" options={OTHER_OPTIONS} label="Match Other" columns={6} creatable={true} tooltip={<div><p>Will match releases which contain any of the selected designations.</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> <MultiSelect
<MultiSelect name="except_other" options={OTHER_OPTIONS} label="Except Other" columns={6} creatable={true} tooltip={<div><p>Won't match releases which contain any of the selected Other designations (takes priority over Match Other).</p><a href='https://autobrr.com/filters#quality' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality</a></div>} /> name="match_other"
options={OTHER_OPTIONS}
label="Match Other"
columns={6}
creatable={true}
tooltip={
<div>
<p>Will match releases which contain any of the selected designations.</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
<MultiSelect
name="except_other"
options={OTHER_OPTIONS}
label="Except Other"
columns={6}
creatable={true}
tooltip={
<div>
<p>Won't match releases which contain any of the selected Other designations (takes priority over Match Other).</p>
<DocsLink href="https://autobrr.com/filters#quality" />
</div>
}
/>
</div> </div>
</div> </div>
</div> </div>
@ -525,47 +740,147 @@ export function Music({ values }: AdvancedProps) {
return ( return (
<div> <div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<TextAreaAutoResize name="artists" label="Artists" columns={4} placeholder="eg. Artist One" tooltip={<div><p>You can use basic filtering like wildcards <code>*</code> or replace single characters with <code>?</code></p><a href='https://autobrr.com/filters#music' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#music</a></div>} /> <TextAreaAutoResize
<TextAreaAutoResize name="albums" label="Albums" columns={4} placeholder="eg. That Album" tooltip={<div><p>You can use basic filtering like wildcards <code>*</code> or replace single characters with <code>?</code></p><a href='https://autobrr.com/filters#music' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#music</a></div>} /> name="artists"
<TextField name="years" label="Years" columns={4} placeholder="eg. 2018,2019-2021" tooltip={<div><p>This field takes a range of years and/or comma separated single years.</p><a href='https://autobrr.com/filters#music' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#music</a></div>} /> label="Artists"
columns={4}
placeholder="eg. Artist One"
tooltip={
<div>
<p>You can use basic filtering like wildcards <code>*</code> or replace single characters with <code>?</code></p>
<DocsLink href="https://autobrr.com/filters#music" />
</div>
}
/>
<TextAreaAutoResize
name="albums"
label="Albums"
columns={4}
placeholder="eg. That Album"
tooltip={
<div>
<p>You can use basic filtering like wildcards <code>*</code> or replace single characters with <code>?</code></p>
<DocsLink href="https://autobrr.com/filters#music" />
</div>
}
/>
<TextField
name="years"
label="Years"
columns={4}
placeholder="eg. 2018,2019-2021"
tooltip={
<div>
<p>This field takes a range of years and/or comma separated single years.</p>
<DocsLink href="https://autobrr.com/filters#music" />
</div>
}
/>
</div> </div>
<div className="mt-6 lg:pb-8"> <div className="mt-6 lg:pb-8">
<TitleSubtitle title="Quality" subtitle="Format, source, log etc." /> <TitleSubtitle title="Quality" subtitle="Format, source, log etc." />
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="formats" options={FORMATS_OPTIONS} label="Format" columns={6} disabled={values.perfect_flac} tooltip={<div><p> Will only match releases with any of the selected formats. This is overridden by Perfect FLAC.</p><a href='https://autobrr.com/filters#quality-1' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality-1</a></div>} /> <MultiSelect
<MultiSelect name="quality" options={QUALITY_MUSIC_OPTIONS} label="Quality" columns={6} disabled={values.perfect_flac} tooltip={<div><p> Will only match releases with any of the selected qualities. This is overridden by Perfect FLAC.</p><a href='https://autobrr.com/filters#quality-1' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality-1</a></div>} /> name="formats"
options={FORMATS_OPTIONS}
label="Format"
columns={6}
disabled={values.perfect_flac}
tooltip={
<div>
<p>Will only match releases with any of the selected formats. This is overridden by Perfect FLAC.</p>
<DocsLink href="https://autobrr.com/filters#quality-1" />
</div>
}
/>
<MultiSelect
name="quality"
options={QUALITY_MUSIC_OPTIONS}
label="Quality"
columns={6}
disabled={values.perfect_flac}
tooltip={
<div>
<p>Will only match releases with any of the selected qualities. This is overridden by Perfect FLAC.</p>
<DocsLink href="https://autobrr.com/filters#quality-1" />
</div>
}
/>
</div> </div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="media" options={SOURCES_MUSIC_OPTIONS} label="Media" columns={6} disabled={values.perfect_flac} tooltip={<div><p> Will only match releases with any of the selected sources. This is overridden by Perfect FLAC.</p><a href='https://autobrr.com/filters#quality-1' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality-1</a></div>} /> <MultiSelect
<MultiSelect name="match_release_types" options={RELEASE_TYPE_MUSIC_OPTIONS} label="Type" columns={6} tooltip={<div><p> Will only match releases with any of the selected types.</p><a href='https://autobrr.com/filters#quality-1' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality-1</a></div>} /> name="media"
options={SOURCES_MUSIC_OPTIONS}
label="Media"
columns={6}
disabled={values.perfect_flac}
tooltip={
<div>
<p>Will only match releases with any of the selected sources. This is overridden by Perfect FLAC.</p>
<DocsLink href="https://autobrr.com/filters#quality-1" />
</div>
}
/>
<MultiSelect
name="match_release_types"
options={RELEASE_TYPE_MUSIC_OPTIONS}
label="Type"
columns={6}
tooltip={
<div>
<p>Will only match releases with any of the selected types.</p>
<DocsLink href="https://autobrr.com/filters#quality-1" />
</div>
}
/>
</div> </div>
<div className="mt-6 grid grid-cols-12 gap-6"> <div className="mt-6 grid grid-cols-12 gap-6">
<NumberField name="log_score" label="Log score" placeholder="eg. 100" min={0} max={100} disabled={values.perfect_flac} tooltip={<div><p> Log scores go from 0 to 100. This is overridden by Perfect FLAC.</p><a href='https://autobrr.com/filters#quality-1' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality-1</a></div>} /> <NumberField
name="log_score"
label="Log score"
placeholder="eg. 100"
min={0}
max={100}
disabled={values.perfect_flac}
tooltip={
<div>
<p>Log scores go from 0 to 100. This is overridden by Perfect FLAC.</p>
<DocsLink href="https://autobrr.com/filters#quality-1" />
</div>
}
/>
</div> </div>
</div> </div>
<div className="space-y-6 sm:space-y-5 divide-y divide-gray-200">
<div className="pt-6 sm:pt-5">
<div role="group" aria-labelledby="label-email">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
{/* <div>
<div className="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700" >
Extra
</div>
</div> */}
<div className="mt-4 sm:mt-0 sm:col-span-2"> <div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg space-y-4"> <div className="max-w-lg space-y-4">
<CheckboxField name="log" label="Log" sublabel="Must include Log." disabled={values.perfect_flac} /> <CheckboxField
<CheckboxField name="cue" label="Cue" sublabel="Must include Cue." disabled={values.perfect_flac} /> name="log"
<CheckboxField name="perfect_flac" label="Perfect FLAC" sublabel="Override all options about quality, source, format, and cue/log/log score." tooltip={<div><p>Override all options about quality, source, format, and cue/log/log score.</p><a href='https://autobrr.com/filters#quality-1' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#quality-1</a></div>} /> label="Log"
</div> sublabel="Must include Log."
</div> disabled={values.perfect_flac}
/>
<CheckboxField
name="cue"
label="Cue"
sublabel="Must include Cue."
disabled={values.perfect_flac}
/>
<CheckboxField
name="perfect_flac"
label="Perfect FLAC"
sublabel="Override all options about quality, source, format, and cue/log/log score."
tooltip={
<div>
<p>Override all options about quality, source, format, and cue/log/log score.</p>
<DocsLink href="https://autobrr.com/filters#quality-1" />
</div> </div>
}
/>
</div> </div>
</div> </div>
</div> </div>
@ -580,18 +895,54 @@ interface AdvancedProps {
export function Advanced({ values }: AdvancedProps) { export function Advanced({ values }: AdvancedProps) {
return ( return (
<div> <div>
<CollapsableSection defaultOpen={true} title="Releases" subtitle="Match only certain release names and/or ignore other release names."> <CollapsableSection
defaultOpen
title="Releases"
subtitle="Match only certain release names and/or ignore other release names."
>
<div className="grid col-span-12 gap-6"> <div className="grid col-span-12 gap-6">
<WarningAlert text="autobrr has extensive filtering built-in - only use this if nothing else works. If you need help, please ask." /> <WarningAlert text="autobrr has extensive filtering built-in - only use this if nothing else works. If you need help, please ask." />
<RegexTextAreaField name="match_releases" label="Match releases" useRegex={values.use_regex} columns={6} placeholder="eg. *some?movie*,*some?show*s01*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br /><br /><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} /> <RegexTextAreaField
<RegexTextAreaField name="except_releases" label="Except releases" useRegex={values.use_regex} columns={6} placeholder="eg. *bad?movie*,*bad?show*s03*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br /><br /><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} /> name="match_releases"
label="Match releases"
useRegex={values.use_regex}
columns={6}
placeholder="eg. *some?movie*,*some?show*s01*"
tooltip={
<div>
<p>This field has full regex support (Golang flavour).</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
<br />
<br />
<p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p>
</div>
}
/>
<RegexTextAreaField
name="except_releases"
label="Except releases"
useRegex={values.use_regex}
columns={6}
placeholder="eg. *bad?movie*,*bad?show*s03*"
tooltip={
<div>
<p>This field has full regex support (Golang flavour).</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
<br />
<br />
<p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p>
</div>
}
/>
{values.match_releases ? ( {values.match_releases ? (
<WarningAlert <WarningAlert
alert="Ask yourself:" alert="Ask yourself:"
text={ text={
<>Do you have a good reason to use <strong>Match releases</strong> instead of one of the other tabs?</> <>
Do you have a good reason to use <strong>Match releases</strong> instead of one of the other tabs?
</>
} }
colors="text-cyan-700 bg-cyan-100 dark:bg-cyan-200 dark:text-cyan-800" colors="text-cyan-700 bg-cyan-100 dark:bg-cyan-200 dark:text-cyan-800"
/> />
@ -600,7 +951,9 @@ export function Advanced({ values }: AdvancedProps) {
<WarningAlert <WarningAlert
alert="Ask yourself:" alert="Ask yourself:"
text={ text={
<>Do you have a good reason to use <strong>Except releases</strong> instead of one of the other tabs?</> <>
Do you have a good reason to use <strong>Except releases</strong> instead of one of the other tabs?
</>
} }
colors="text-fuchsia-700 bg-fuchsia-100 dark:bg-fuchsia-200 dark:text-fuchsia-800" colors="text-fuchsia-700 bg-fuchsia-100 dark:bg-fuchsia-200 dark:text-fuchsia-800"
/> />
@ -611,42 +964,215 @@ export function Advanced({ values }: AdvancedProps) {
</div> </div>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Groups" subtitle="Match only certain groups and/or ignore other groups."> <CollapsableSection
<TextAreaAutoResize name="match_release_groups" label="Match release groups" columns={6} placeholder="eg. group1,group2" tooltip={<div><p>Comma separated list of release groups to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> defaultOpen={true}
<TextAreaAutoResize name="except_release_groups" label="Except release groups" columns={6} placeholder="eg. badgroup1,badgroup2" tooltip={<div><p>Comma separated list of release groups to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> title="Groups"
subtitle="Match only certain groups and/or ignore other groups."
>
<TextAreaAutoResize
name="match_release_groups"
label="Match release groups"
columns={6}
placeholder="eg. group1,group2"
tooltip={
<div>
<p>Comma separated list of release groups to match.</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
<TextAreaAutoResize
name="except_release_groups"
label="Except release groups"
columns={6}
placeholder="eg. badgroup1,badgroup2"
tooltip={
<div>
<p>Comma separated list of release groups to ignore (takes priority over Match releases).</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Categories and tags" subtitle="Match or ignore categories or tags."> <CollapsableSection
<TextAreaAutoResize name="match_categories" label="Match categories" columns={6} placeholder="eg. *category*,category1" tooltip={<div><p>Comma separated list of categories to match.</p><a href='https://autobrr.com/filters/categories' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/categories</a></div>} /> defaultOpen={true}
<TextAreaAutoResize name="except_categories" label="Except categories" columns={6} placeholder="eg. *category*" tooltip={<div><p>Comma separated list of categories to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters/categories' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/categories</a></div>} /> title="Categories and tags"
subtitle="Match or ignore categories or tags."
>
<TextAreaAutoResize
name="match_categories"
label="Match categories"
columns={6}
placeholder="eg. *category*,category1"
tooltip={
<div>
<p>Comma separated list of categories to match.</p>
<DocsLink href="https://autobrr.com/filters/categories" />
</div>
}
/>
<TextAreaAutoResize
name="except_categories"
label="Except categories"
columns={6}
placeholder="eg. *category*"
tooltip={
<div>
<p>Comma separated list of categories to ignore (takes priority over Match releases).</p>
<DocsLink href="https://autobrr.com/filters/categories" />
</div>
}
/>
<TextAreaAutoResize name="tags" label="Match tags" columns={4} placeholder="eg. tag1,tag2" tooltip={<div><p>Comma separated list of tags to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> <TextAreaAutoResize
<Select name="tags_match_logic" label="Tags logic" columns={2} options={tagsMatchLogicOptions} optionDefaultText="any" tooltip={<div><p>Logic used to match filter tags.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> name="tags"
<TextAreaAutoResize name="except_tags" label="Except tags" columns={4} placeholder="eg. tag1,tag2" tooltip={<div><p>Comma separated list of tags to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>hhttps://autobrr.com/filters#advanced</a></div>} /> label="Match tags"
<Select name="except_tags_match_logic" label="Except tags logic" columns={2} options={tagsMatchLogicOptions} optionDefaultText="any" tooltip={<div><p>Logic used to match except tags.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> columns={4}
placeholder="eg. tag1,tag2"
tooltip={
<div>
<p>Comma separated list of tags to match.</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
<Select
name="tags_match_logic"
label="Tags logic"
columns={2}
options={tagsMatchLogicOptions}
optionDefaultText="any"
tooltip={
<div>
<p>Logic used to match filter tags.</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
<TextAreaAutoResize
name="except_tags"
label="Except tags"
columns={4}
placeholder="eg. tag1,tag2"
tooltip={
<div>
<p>Comma separated list of tags to ignore (takes priority over Match releases).</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
<Select
name="except_tags_match_logic"
label="Except tags logic"
columns={2}
options={tagsMatchLogicOptions}
optionDefaultText="any"
tooltip={
<div>
<p>Logic used to match except tags.</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Uploaders" subtitle="Match or ignore uploaders."> <CollapsableSection
<TextAreaAutoResize name="match_uploaders" label="Match uploaders" columns={6} placeholder="eg. uploader1,uploader2" tooltip={<div><p>Comma separated list of uploaders to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> defaultOpen={true}
<TextAreaAutoResize name="except_uploaders" label="Except uploaders" columns={6} placeholder="eg. anonymous1,anonymous2" tooltip={<div><p>Comma separated list of uploaders to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} /> title="Uploaders"
subtitle="Match or ignore uploaders."
>
<TextAreaAutoResize
name="match_uploaders"
label="Match uploaders"
columns={6}
placeholder="eg. uploader1,uploader2"
tooltip={
<div>
<p>Comma separated list of uploaders to match.</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
<TextAreaAutoResize
name="except_uploaders"
label="Except uploaders"
columns={6}
placeholder="eg. anonymous1,anonymous2"
tooltip={
<div>
<p>Comma separated list of uploaders to ignore (takes priority over Match releases).
</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
</div>
}
/>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Language" subtitle="Match or ignore languages."> <CollapsableSection
<MultiSelect name="match_language" options={LANGUAGE_OPTIONS} label="Match Language" columns={6} creatable={true} /> defaultOpen={true}
<MultiSelect name="except_language" options={LANGUAGE_OPTIONS} label="Except Language" columns={6} creatable={true} /> title="Language"
subtitle="Match or ignore languages."
>
<MultiSelect
name="match_language"
options={LANGUAGE_OPTIONS}
label="Match Language"
columns={6}
creatable={true}
/>
<MultiSelect
name="except_language"
options={LANGUAGE_OPTIONS}
label="Except Language"
columns={6}
creatable={true}
/>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Origins" subtitle="Match Internals, scene, p2p etc. if announced."> <CollapsableSection
<MultiSelect name="origins" options={ORIGIN_OPTIONS} label="Match Origins" columns={6} creatable={true} /> defaultOpen={true}
<MultiSelect name="except_origins" options={ORIGIN_OPTIONS} label="Except Origins" columns={6} creatable={true} /> title="Origins"
subtitle="Match Internals, scene, p2p etc. if announced."
>
<MultiSelect
name="origins"
options={ORIGIN_OPTIONS}
label="Match Origins"
columns={6}
creatable={true}
/>
<MultiSelect
name="except_origins"
options={ORIGIN_OPTIONS}
label="Except Origins"
columns={6}
creatable={true}
/>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Release Tags" subtitle="This is the non-parsed releaseTags string from the announce."> <CollapsableSection
defaultOpen={true}
title="Release Tags"
subtitle="This is the non-parsed releaseTags string from the announce."
>
<div className="grid col-span-12 gap-6"> <div className="grid col-span-12 gap-6">
<WarningAlert text="These might not be what you think they are. For advanced users who know how things are parsed." /> <WarningAlert text="These might not be what you think they are. For advanced users who know how things are parsed." />
<RegexField name="match_release_tags" label="Match release tags" useRegex={values.use_regex_release_tags} columns={6} placeholder="eg. *mkv*,*foreign*" /> <RegexField
<RegexField name="except_release_tags" label="Except release tags" useRegex={values.use_regex_release_tags} columns={6} placeholder="eg. *mkv*,*foreign*" /> name="match_release_tags"
label="Match release tags"
useRegex={values.use_regex_release_tags}
columns={6}
placeholder="eg. *mkv*,*foreign*"
/>
<RegexField
name="except_release_tags"
label="Except release tags"
useRegex={values.use_regex_release_tags}
columns={6}
placeholder="eg. *mkv*,*foreign*"
/>
<div className="col-span-6"> <div className="col-span-6">
<SwitchGroup name="use_regex_release_tags" label="Use Regex" /> <SwitchGroup name="use_regex_release_tags" label="Use Regex" />
@ -654,10 +1180,44 @@ export function Advanced({ values }: AdvancedProps) {
</div> </div>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Feeds" subtitle="These options are only for Feeds such as RSS, Torznab and Newznab"> <CollapsableSection
defaultOpen={true}
title="Feeds"
subtitle="These options are only for Feeds such as RSS, Torznab and Newznab"
>
{/*<div className="grid col-span-12 gap-6">*/} {/*<div className="grid col-span-12 gap-6">*/}
<RegexTextAreaField name="match_description" label="Match description" useRegex={values.use_regex_description} columns={6} placeholder="eg. *some?movie*,*some?show*s01*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br /><br /><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} /> <RegexTextAreaField
<RegexTextAreaField name="except_description" label="Except description" useRegex={values.use_regex_description} columns={6} placeholder="eg. *bad?movie*,*bad?show*s03*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br /><br /><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} /> name="match_description"
label="Match description"
useRegex={values.use_regex_description}
columns={6}
placeholder="eg. *some?movie*,*some?show*s01*"
tooltip={
<div>
<p>This field has full regex support (Golang flavour).</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
<br />
<br />
<p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p>
</div>
}
/>
<RegexTextAreaField
name="except_description"
label="Except description"
useRegex={values.use_regex_description}
columns={6}
placeholder="eg. *bad?movie*,*bad?show*s03*"
tooltip={
<div>
<p>This field has full regex support (Golang flavour).</p>
<DocsLink href="https://autobrr.com/filters#advanced" />
<br />
<br />
<p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p>
</div>
}
/>
{/*</div>*/} {/*</div>*/}
<div className="col-span-6"> <div className="col-span-6">
@ -665,12 +1225,53 @@ export function Advanced({ values }: AdvancedProps) {
</div> </div>
</CollapsableSection> </CollapsableSection>
<CollapsableSection defaultOpen={true} title="Freeleech" subtitle="Match only freeleech and freeleech percent."> <CollapsableSection
defaultOpen={true}
title="Freeleech"
subtitle="Match only freeleech and freeleech percent."
>
<div className="col-span-6"> <div className="col-span-6">
<SwitchGroup name="freeleech" label="Freeleech" tooltip={<div><p>Freeleech may be announced as a binary true/false value or as a percentage, depending on the indexer. Use either or both, depending on the indexers you use.</p><br /><p>See who uses what in the documentation: <a href='https://autobrr.com/filters/freeleech' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/freeleech</a></p></div>} /> <SwitchGroup
name="freeleech"
label="Freeleech"
tooltip={
<div>
<p>
Freeleech may be announced as a binary true/false value or as
a percentage, depending on the indexer. Use either or both,
depending on the indexers you use.
</p>
<br />
<p>
See who uses what in the documentation:{" "}
<DocsLink href="https://autobrr.com/filters/freeleech" />
</p>
</div>
}
/>
</div> </div>
<TextField name="freeleech_percent" label="Freeleech percent" disabled={values.freeleech} tooltip={<div><p>Freeleech may be announced as a binary true/false value or as a percentage, depending on the indexer. Use either or both, depending on the indexers you use.</p><br /><p>See who uses what in the documentation: <a href='https://autobrr.com/filters/freeleech' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/freeleech</a></p></div>} columns={6} placeholder="eg. 50,75-100" /> <TextField
name="freeleech_percent"
label="Freeleech percent"
disabled={values.freeleech}
tooltip={
<div>
<p>
Freeleech may be announced as a binary true/false value or as a
percentage, depending on the indexer. Use either or both,
depending on the indexers you use.
</p>
<br />
<p>
See who uses what in the documentation:{" "}
<DocsLink href="https://autobrr.com/filters/freeleech" />
</p>
</div>
}
columns={6}
placeholder="eg. 50,75-100"
/>
</CollapsableSection> </CollapsableSection>
</div> </div>
); );

View file

@ -19,6 +19,7 @@ import {
import { ChevronRightIcon } from "@heroicons/react/24/solid"; import { ChevronRightIcon } from "@heroicons/react/24/solid";
import { DeleteModal } from "@components/modals"; import { DeleteModal } from "@components/modals";
import { ArrowDownIcon, ArrowUpIcon } from "@heroicons/react/24/outline"; import { ArrowDownIcon, ArrowUpIcon } from "@heroicons/react/24/outline";
import { DocsLink } from "@components/ExternalLink";
export function External() { export function External() {
const { values } = useFormikContext<Filter>(); const { values } = useFormikContext<Filter>();
@ -261,11 +262,7 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
For custom commands you should specify the full path to the binary/program For custom commands you should specify the full path to the binary/program
you want to run. And you can include your own static variables: you want to run. And you can include your own static variables:
</p> </p>
<a <DocsLink href="https://autobrr.com/filters/actions#custom-commands--exec" />
href="https://autobrr.com/filters/actions#custom-commands--exec"
className="text-blue-400 visited:text-blue-400"
target="_blank">https://autobrr.com/filters/actions#custom-commands--exec
</a>
</div> </div>
} }
/> />

View file

@ -10,7 +10,6 @@ import { Listbox, Menu, Switch, Transition } from "@headlessui/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { FormikValues } from "formik"; import { FormikValues } from "formik";
import { useCallback } from "react"; import { useCallback } from "react";
import { Tooltip } from "react-tooltip";
import { import {
ArrowsRightLeftIcon, ArrowsRightLeftIcon,
CheckIcon, CheckIcon,
@ -35,6 +34,7 @@ import { EmptyListState } from "@components/emptystates";
import { DeleteModal } from "@components/modals"; import { DeleteModal } from "@components/modals";
import { Importer } from "./Importer"; import { Importer } from "./Importer";
import { Tooltip } from "@components/tooltips/Tooltip";
export const filterKeys = { export const filterKeys = {
all: ["filters"] as const, all: ["filters"] as const,
@ -96,7 +96,7 @@ export function Filters() {
setIsOpen={setShowImportModal} setIsOpen={setShowImportModal}
/> />
<div className="flex justify-between items-center flex-col sm:flex-row my-6 max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="flex justify-between items-center flex-row flex-wrap my-6 max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold text-black dark:text-white">Filters</h1> <h1 className="text-3xl font-bold text-black dark:text-white">Filters</h1>
<Menu as="div" className="relative"> <Menu as="div" className="relative">
{({ open }) => ( {({ open }) => (
@ -635,49 +635,30 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
Priority: {filter.priority} Priority: {filter.priority}
</span> </span>
<span className="whitespace-nowrap text-xs font-medium text-gray-600 dark:text-gray-400"> <span className="whitespace-nowrap text-xs font-medium text-gray-600 dark:text-gray-400">
<Tooltip
label={
<Link <Link
to={`${filter.id.toString()}/actions`} to={`${filter.id.toString()}/actions`}
className="hover:text-black dark:hover:text-gray-300" className="flex items-center cursor-pointer hover:text-black dark:hover:text-gray-300"
>
<span
id={`tooltip-actions-${filter.id}`}
className="flex items-center hover:cursor-pointer"
>
<span className={classNames(filter.actions_count == 0 ? "text-red-500" : "")}>
<span
className={
classNames(
filter.actions_count == 0 ? "hover:text-red-400 dark:hover:text-red-400" : ""
)
}
> >
<span className={classNames(!filter.actions_count ? "text-red-500 hover:text-red-400 dark:hover:text-red-400" : "")}>
Actions: {filter.actions_count} Actions: {filter.actions_count}
</span> </span>
</span> {!filter.actions_count && (
{filter.actions_count === 0 && (
<>
<span className="mr-2 ml-2 flex h-3 w-3 relative"> <span className="mr-2 ml-2 flex h-3 w-3 relative">
<span className="animate-ping inline-flex h-full w-full rounded-full dark:bg-red-500 bg-red-400 opacity-75" /> <span className="animate-ping inline-flex h-full w-full rounded-full dark:bg-red-500 bg-red-400 opacity-75" />
<span <span
className="inline-flex absolute rounded-full h-3 w-3 dark:bg-red-500 bg-red-400" className="inline-flex absolute rounded-full h-3 w-3 dark:bg-red-500 bg-red-400"
/> />
</span> </span>
<span className="text-sm text-gray-800 dark:text-gray-500">
<Tooltip
style={{ width: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", whiteSpace: "pre-wrap", overflow: "hidden", textOverflow: "ellipsis" }}
delayShow={100}
delayHide={150}
data-html={true}
place="right"
data-tooltip-id={`tooltip-actions-${filter.id}`}
>
<p>You need to setup an action in the filter otherwise you will not get any snatches.</p>
</Tooltip>
</span>
</>
)} )}
</span>
</Link> </Link>
}
>
{!filter.actions_count ? (
<>{"You need to setup an action in the filter otherwise you will not get any snatches."}</>
) : null}
</Tooltip>
</span> </span>
</div> </div>
</div> </div>

View file

@ -140,7 +140,8 @@ export const PushStatusSelectColumnFilter = ({
))} ))}
</ListboxFilter> </ListboxFilter>
</div> </div>
);}; );
}
export const SearchColumnFilter = ({ export const SearchColumnFilter = ({
column: { filterValue, setFilter, id } column: { filterValue, setFilter, id }
@ -161,4 +162,5 @@ export const SearchColumnFilter = ({
placeholder="Search releases..." placeholder="Search releases..."
/> />
</div> </div>
);}; );
}

View file

@ -23,6 +23,7 @@ import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFi
import { classNames } from "@utils"; import { classNames } from "@utils";
import { ArrowTopRightOnSquareIcon, ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { ArrowTopRightOnSquareIcon, ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import { Tooltip } from "@components/tooltips/Tooltip"; import { Tooltip } from "@components/tooltips/Tooltip";
import { ExternalLink } from "@components/ExternalLink";
export const releaseKeys = { export const releaseKeys = {
all: ["releases"] as const, all: ["releases"] as const,
@ -103,24 +104,17 @@ export const ReleaseTable = () => {
</Tooltip> </Tooltip>
<div className="flex mr-0"> <div className="flex mr-0">
{props.row.original.download_url && ( {props.row.original.download_url && (
<a <ExternalLink
rel="noopener noreferrer"
target="_blank"
href={props.row.original.download_url} href={props.row.original.download_url}
className="max-w-[90vw] px-2" className="px-2"
> >
<ArrowDownTrayIcon className="h-5 w-5 text-blue-400 hover:text-blue-500 dark:text-blue-500 dark:hover:text-blue-600" aria-hidden="true" /> <ArrowDownTrayIcon className="h-5 w-5 text-blue-400 hover:text-blue-500 dark:text-blue-500 dark:hover:text-blue-600" aria-hidden="true" />
</a> </ExternalLink>
)} )}
{props.row.original.info_url && ( {props.row.original.info_url && (
<a <ExternalLink href={props.row.original.info_url}>
rel="noopener noreferrer"
target="_blank"
href={props.row.original.info_url}
className="max-w-[90vw]"
>
<ArrowTopRightOnSquareIcon className="h-5 w-5 text-blue-400 hover:text-blue-500 dark:text-blue-500 dark:hover:text-blue-600" aria-hidden="true" /> <ArrowTopRightOnSquareIcon className="h-5 w-5 text-blue-400 hover:text-blue-500 dark:text-blue-500 dark:hover:text-blue-600" aria-hidden="true" />
</a> </ExternalLink>
)} )}
</div> </div>
</div> </div>

View file

@ -11,6 +11,7 @@ import { Checkbox } from "@components/Checkbox";
import { SettingsContext } from "@utils/Context"; import { SettingsContext } from "@utils/Context";
import { GithubRelease } from "@app/types/Update"; import { GithubRelease } from "@app/types/Update";
import Toast from "@components/notifications/Toast"; import Toast from "@components/notifications/Toast";
import { ExternalLink } from "@components/ExternalLink";
interface RowItemProps { interface RowItemProps {
label: string; label: string;
@ -63,9 +64,12 @@ const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => {
<dd className="mt-1 text-gray-900 dark:text-gray-300 text-sm sm:mt-0 sm:col-span-2 break-all truncate"> <dd className="mt-1 text-gray-900 dark:text-gray-300 text-sm sm:mt-0 sm:col-span-2 break-all truncate">
<span className="px-1.5 py-1 bg-gray-200 dark:bg-gray-700 rounded shadow">{value}</span> <span className="px-1.5 py-1 bg-gray-200 dark:bg-gray-700 rounded shadow">{value}</span>
{newUpdate && newUpdate.html_url && ( {newUpdate && newUpdate.html_url && (
<span> <ExternalLink
<a href={newUpdate.html_url} target="_blank" rel="noopener noreferrer"><span className="ml-2 inline-flex items-center rounded-md bg-green-100 px-2.5 py-0.5 text-sm font-medium text-green-800">{newUpdate.name} available!</span></a> href={newUpdate.html_url}
</span> className="ml-2 inline-flex items-center rounded-md bg-green-100 px-2.5 py-0.5 text-sm font-medium text-green-800"
>
{newUpdate.name} available!
</ExternalLink>
)} )}
</dd> </dd>
</div> </div>
@ -184,10 +188,12 @@ function ApplicationSettings() {
<Checkbox <Checkbox
label="WebUI Debug mode" label="WebUI Debug mode"
value={settings.debug} value={settings.debug}
setValue={(newValue: boolean) => setSettings({ setValue={
...settings, (newValue: boolean) => setSettings((prevState) => ({
...prevState,
debug: newValue debug: newValue
})} }))
}
/> />
</div> </div>
<div className="px-4 sm:px-6 py-1"> <div className="px-4 sm:px-6 py-1">
@ -205,15 +211,16 @@ function ApplicationSettings() {
label="Dark theme" label="Dark theme"
description="Switch between dark and light theme." description="Switch between dark and light theme."
value={settings.darkTheme} value={settings.darkTheme}
setValue={(newValue: boolean) => setSettings({ setValue={
...settings, (newValue: boolean) => setSettings((prevState) => ({
...prevState,
darkTheme: newValue darkTheme: newValue
})} }))
}
/> />
</div> </div>
</ul> </ul>
</div> </div>
</div> </div>
); );
} }

View file

@ -24,6 +24,7 @@ import { FeedUpdateForm } from "@forms/settings/FeedForms";
import { EmptySimple } from "@components/emptystates"; import { EmptySimple } from "@components/emptystates";
import { ImplementationBadges } from "./Indexer"; import { ImplementationBadges } from "./Indexer";
import { ArrowPathIcon } from "@heroicons/react/24/solid"; import { ArrowPathIcon } from "@heroicons/react/24/solid";
import { ExternalLink } from "@components/ExternalLink";
export const feedKeys = { export const feedKeys = {
all: ["feeds"] as const, all: ["feeds"] as const,
@ -354,10 +355,8 @@ const FeedItemDropdown = ({
<div> <div>
<Menu.Item> <Menu.Item>
{({ active }) => ( {({ active }) => (
<a <ExternalLink
href={`${baseUrl()}api/feeds/${feed.id}/latest`} href={`${baseUrl()}api/feeds/${feed.id}/latest`}
target="_blank"
rel="noopener noreferrer"
className={classNames( className={classNames(
active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300", active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300",
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm" "font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
@ -371,7 +370,7 @@ const FeedItemDropdown = ({
aria-hidden="true" aria-hidden="true"
/> />
View latest run View latest run
</a> </ExternalLink>
)} )}
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>