diff --git a/web/package.json b/web/package.json
index d2ebc52..d833903 100644
--- a/web/package.json
+++ b/web/package.json
@@ -69,7 +69,6 @@
"react-select": "^5.7.4",
"react-table": "^7.8.0",
"react-textarea-autosize": "^8.5.3",
- "react-tooltip": "^5.21.1",
"stacktracey": "^2.1.8",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 5c00f97..ec00457 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -128,9 +128,6 @@ dependencies:
react-textarea-autosize:
specifier: ^8.5.3
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:
specifier: ^2.1.8
version: 2.1.8
@@ -2873,10 +2870,6 @@ packages:
fsevents: 2.3.3
dev: false
- /classnames@2.3.2:
- resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
- dev: false
-
/client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
@@ -4763,7 +4756,7 @@ packages:
react: ^18.2.0
react-dom: '>=16.6.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.22.11
'@popperjs/core': 2.11.8
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -4869,18 +4862,6 @@ packages:
- '@types/react'
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):
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
diff --git a/web/src/components/ExternalLink.tsx b/web/src/components/ExternalLink.tsx
new file mode 100644
index 0000000..86691b9
--- /dev/null
+++ b/web/src/components/ExternalLink.tsx
@@ -0,0 +1,20 @@
+type ExternalLinkProps = {
+ href: string;
+ className?: string;
+ children?: React.ReactNode;
+};
+
+export const ExternalLink = ({ href, className, children }: ExternalLinkProps) => (
+
+ {children}
+
+);
+
+export const DocsLink = ({ href }: { href: string; }) => (
+ {href}
+);
diff --git a/web/src/components/alerts/ErrorPage.tsx b/web/src/components/alerts/ErrorPage.tsx
index bcd5690..fe375ec 100644
--- a/web/src/components/alerts/ErrorPage.tsx
+++ b/web/src/components/alerts/ErrorPage.tsx
@@ -6,6 +6,7 @@
import StackTracey from "stacktracey";
import type { FallbackProps } from "react-error-boundary";
import { ArrowPathIcon } from "@heroicons/react/24/solid";
+import { ExternalLink } from "@components/ExternalLink";
export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
const stack = new StackTracey(error);
@@ -35,23 +36,19 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
{
return (
@@ -21,23 +22,19 @@ export const NotFound = () => {
diff --git a/web/src/components/data-table/Cells.tsx b/web/src/components/data-table/Cells.tsx
index cb31cf4..1a54ad1 100644
--- a/web/src/components/data-table/Cells.tsx
+++ b/web/src/components/data-table/Cells.tsx
@@ -4,18 +4,18 @@
*/
import * as React from "react";
+import { toast } from "react-hot-toast";
import { formatDistanceToNowStrict } from "date-fns";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid";
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 { classNames, simplifyDate } from "@utils";
import { filterKeys } from "@screens/filters/List";
-import { toast } from "react-hot-toast";
import Toast from "@components/notifications/Toast";
import { RingResizeSpinner } from "@components/Icons";
+import { Tooltip } from "@components/tooltips/Tooltip";
interface CellProps {
value: string;
@@ -35,6 +35,7 @@ export const IndexerCell = ({ value }: CellProps) => (
)}
>
@@ -53,6 +54,7 @@ export const TitleCell = ({ value }: CellProps) => (
)}
>
@@ -221,6 +223,7 @@ export const ReleaseStatusCell = ({ value }: ReleaseStatusCellProps) => (
)}
>
diff --git a/web/src/components/header/Header.tsx b/web/src/components/header/Header.tsx
index 56ba4aa..df7bc0e 100644
--- a/web/src/components/header/Header.tsx
+++ b/web/src/components/header/Header.tsx
@@ -15,6 +15,7 @@ import Toast from "@components/notifications/Toast";
import { LeftNav } from "./LeftNav";
import { RightNav } from "./RightNav";
import { MobileNav } from "./MobileNav";
+import { ExternalLink } from "@components/ExternalLink";
export const Header = () => {
const { data: config } = useQuery({
@@ -77,13 +78,13 @@ export const Header = () => {
{data?.html_url && (
-
+
New update available!
{data?.name}
-
+
)}
diff --git a/web/src/components/header/LeftNav.tsx b/web/src/components/header/LeftNav.tsx
index 53b86c5..1b2d6ce 100644
--- a/web/src/components/header/LeftNav.tsx
+++ b/web/src/components/header/LeftNav.tsx
@@ -10,6 +10,7 @@ import { classNames } from "@utils";
import { ReactComponent as Logo } from "@app/logo.svg";
import { NAV_ROUTES } from "./_shared";
+import { ExternalLink } from "@components/ExternalLink";
export const LeftNav = () => (
diff --git a/web/src/components/inputs/common.tsx b/web/src/components/inputs/common.tsx
index 8a24d4e..adb22db 100644
--- a/web/src/components/inputs/common.tsx
+++ b/web/src/components/inputs/common.tsx
@@ -5,7 +5,7 @@
import { Field, FieldProps } from "formik";
import { classNames } from "@utils";
-import { CustomTooltip } from "@components/tooltips/CustomTooltip";
+import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface ErrorFieldProps {
name: string;
@@ -63,10 +63,9 @@ const CheckboxField = ({
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
{sublabel}
@@ -74,4 +73,4 @@ const CheckboxField = ({
);
-export { ErrorField, RequiredField, CheckboxField };
\ No newline at end of file
+export { ErrorField, RequiredField, CheckboxField };
diff --git a/web/src/components/inputs/input.tsx b/web/src/components/inputs/input.tsx
index cf795fe..d013a2b 100644
--- a/web/src/components/inputs/input.tsx
+++ b/web/src/components/inputs/input.tsx
@@ -9,7 +9,7 @@ import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/
import TextareaAutosize from "react-textarea-autosize";
import { useToggle } from "@hooks/hooks";
-import { CustomTooltip } from "@components/tooltips/CustomTooltip";
+import { DocsTooltip } from "@components/tooltips/DocsTooltip";
import { classNames } from "@utils";
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
@@ -46,10 +46,9 @@ export const TextField = ({
{label && (
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : 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"
>
- {label}
- {tooltip && {tooltip} }
+ {tooltip ? (
+ {tooltip}
+ ) : label}
)}
@@ -324,9 +324,10 @@ export const RegexTextAreaField = ({
htmlFor={name}
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
>
-
- {label}
-
{tooltip && {tooltip} }
+
+ {tooltip ? (
+ {tooltip}
+ ) : label}
)}
@@ -412,10 +413,9 @@ export const TextArea = ({
{label && (
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
)}
@@ -484,10 +484,9 @@ export const TextAreaAutoResize = ({
{label && (
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : 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"
>
- {label}
- {tooltip && {tooltip} }
+ {tooltip ? (
+ {tooltip}
+ ) : label}
diff --git a/web/src/components/inputs/input_wide.tsx b/web/src/components/inputs/input_wide.tsx
index 669d130..33dacfb 100644
--- a/web/src/components/inputs/input_wide.tsx
+++ b/web/src/components/inputs/input_wide.tsx
@@ -12,7 +12,7 @@ import { Switch } from "@headlessui/react";
import { ErrorField, RequiredField } from "./common";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { SelectFieldProps } from "./select";
-import { CustomTooltip } from "@components/tooltips/CustomTooltip";
+import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface TextFieldWideProps {
name: string;
@@ -43,7 +43,9 @@ export const TextFieldWide = ({
- {label} {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
@@ -108,7 +110,9 @@ export const PasswordFieldWide = ({
- {label} {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : 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"
>
- {label} {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
@@ -232,10 +238,11 @@ export const SwitchGroupWide = ({
-
+
- {label} {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
{description && (
@@ -396,8 +403,9 @@ export const SelectFieldWide = ({
className="flex text-sm font-medium text-gray-900 dark:text-white"
>
- {label}
- {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
diff --git a/web/src/components/inputs/select.tsx b/web/src/components/inputs/select.tsx
index 355e674..e6475db 100644
--- a/web/src/components/inputs/select.tsx
+++ b/web/src/components/inputs/select.tsx
@@ -11,7 +11,7 @@ import { MultiSelect as RMSC } from "react-multi-select-component";
import { classNames, COL_WIDTHS } from "@utils";
import { SettingsContext } from "@utils/Context";
-import { CustomTooltip } from "@components/tooltips/CustomTooltip";
+import { DocsTooltip } from "@components/tooltips/DocsTooltip";
export interface MultiSelectOption {
value: string | number;
@@ -56,10 +56,9 @@ export const MultiSelect = ({
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
@@ -297,10 +296,9 @@ export const Select = ({
<>
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
diff --git a/web/src/components/inputs/select_wide.tsx b/web/src/components/inputs/select_wide.tsx
index d3d57ae..2846e01 100644
--- a/web/src/components/inputs/select_wide.tsx
+++ b/web/src/components/inputs/select_wide.tsx
@@ -8,7 +8,7 @@ import { Field } from "formik";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { OptionBasicTyped } from "@domain/constants";
import CreatableSelect from "react-select/creatable";
-import { CustomTooltip } from "@components/tooltips/CustomTooltip";
+import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface SelectFieldProps
{
name: string;
@@ -29,7 +29,9 @@ export function SelectFieldCreatable({ name, label, help, placeholder, toolti
className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2"
>
- {label} {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
@@ -200,7 +202,9 @@ export function SelectFieldBasic({ name, label, help, placeholder, tooltip, d
className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2"
>
- {label} {tooltip && ({tooltip} )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
diff --git a/web/src/components/inputs/switch.tsx b/web/src/components/inputs/switch.tsx
index a2b69b6..6ff3335 100644
--- a/web/src/components/inputs/switch.tsx
+++ b/web/src/components/inputs/switch.tsx
@@ -9,7 +9,7 @@ import { Field } from "formik";
import { Switch as HeadlessSwitch } from "@headlessui/react";
import { classNames } from "@utils";
-import { CustomTooltip } from "@components/tooltips/CustomTooltip";
+import { DocsTooltip } from "@components/tooltips/DocsTooltip";
type SwitchProps
= {
label?: string
@@ -88,13 +88,18 @@ const SwitchGroup = ({
}: SwitchGroupProps) => (
{label &&
-
+
- {label}
- {tooltip && (
- {tooltip}
- )}
+ {tooltip ? (
+ {tooltip}
+ ) : label}
{description && (
diff --git a/web/src/components/tooltips/CustomTooltip.css b/web/src/components/tooltips/CustomTooltip.css
deleted file mode 100644
index 3fc83de..0000000
--- a/web/src/components/tooltips/CustomTooltip.css
+++ /dev/null
@@ -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;
-}
diff --git a/web/src/components/tooltips/CustomTooltip.tsx b/web/src/components/tooltips/CustomTooltip.tsx
deleted file mode 100644
index 51058c6..0000000
--- a/web/src/components/tooltips/CustomTooltip.tsx
+++ /dev/null
@@ -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 (
-
- );
-};
diff --git a/web/src/components/tooltips/DocsTooltip.tsx b/web/src/components/tooltips/DocsTooltip.tsx
new file mode 100644
index 0000000..48f6870
--- /dev/null
+++ b/web/src/components/tooltips/DocsTooltip.tsx
@@ -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) => (
+
+ {label ?? null}
+
+
+ }
+ >
+ {children}
+
+);
+
diff --git a/web/src/components/tooltips/Tooltip.tsx b/web/src/components/tooltips/Tooltip.tsx
index 1a5c091..5058b72 100644
--- a/web/src/components/tooltips/Tooltip.tsx
+++ b/web/src/components/tooltips/Tooltip.tsx
@@ -3,22 +3,30 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
-import * as React from "react";
import type { ReactNode } from "react";
+import { Transition } from "@headlessui/react";
import { usePopperTooltip } from "react-popper-tooltip";
+
import { classNames } from "@utils";
interface TooltipProps {
label: ReactNode;
+ onLabelClick?: (e: React.MouseEvent) => void;
title?: ReactNode;
maxWidth?: string;
+ requiresClick?: boolean;
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 = ({
label,
+ onLabelClick,
title,
children,
+ requiresClick,
maxWidth = "max-w-sm"
}: TooltipProps) => {
const {
@@ -28,22 +36,46 @@ export const Tooltip = ({
setTriggerRef,
visible
} = usePopperTooltip({
- trigger: ["click"],
- interactive: false
+ trigger: requiresClick ? ["click"] : ["click", "hover"],
+ interactive: !requiresClick,
+ delayHide: 200
});
+ if (!children || Array.isArray(children) && !children.length) {
+ return null;
+ }
+
return (
<>
-
+
{
+ e.preventDefault();
+ e.stopPropagation();
+ e.nativeEvent.stopImmediatePropagation();
+
+ onLabelClick?.(e);
+ }}
+ >
{label}
- {visible && (
+
@@ -55,13 +87,13 @@ export const Tooltip = ({
{children}
- )}
+
>
);
-};
\ No newline at end of file
+};
diff --git a/web/src/forms/settings/DownloadClientForms.tsx b/web/src/forms/settings/DownloadClientForms.tsx
index deadc8c..c06d479 100644
--- a/web/src/forms/settings/DownloadClientForms.tsx
+++ b/web/src/forms/settings/DownloadClientForms.tsx
@@ -26,6 +26,7 @@ import {
} from "@components/inputs";
import { clientKeys } from "@screens/settings/DownloadClient";
import { SelectFieldWide } from "@components/inputs/input_wide";
+import { DocsLink, ExternalLink } from "@components/ExternalLink";
interface InitialValuesSettings {
basic?: {
@@ -54,7 +55,6 @@ interface InitialValues {
settings: InitialValuesSettings;
}
-
function FormFieldsDeluge() {
const {
values: { tls }
@@ -63,11 +63,20 @@ function FormFieldsDeluge() {
return (
}
- required={true}
+ tooltip={
+
+
See guides for how to connect to Deluge for various server types in our docs.
+
+
Dedicated servers:
+
+
Shared seedbox providers:
+
+
+ }
/>
See guides for how to connect to the *arr suite for various server types in our docs.
Dedicated servers:
https://autobrr.com/configuration/download-clients/dedicated/ Shared seedbox providers:
https://autobrr.com/configuration/download-clients/shared-seedboxes }
- required={true}
+ tooltip={
+
+
See guides for how to connect to the *arr suite for various server types in our docs.
+
+
Dedicated servers:
+
+
Shared seedbox providers:
+
+
+ }
/>
-
+
@@ -130,11 +148,20 @@ function FormFieldsQbit() {
return (
}
- required={true}
+ tooltip={
+
+
See guides for how to connect to qBittorrent for various server types in our docs.
+
+
Dedicated servers:
+
+
Shared seedbox providers:
+
+
+ }
/>
{port > 0 && (
@@ -177,16 +204,15 @@ function FormFieldsPorla() {
return (
}
- required={true}
+ tooltip={
+
+
See guides for how to connect to rTorrent for various server types in our docs.
+
+
Dedicated servers:
+
+
Shared seedbox providers:
+
+
+ }
/>
@@ -251,11 +286,20 @@ function FormFieldsTransmission() {
return (
}
- required={true}
+ tooltip={
+
+
See guides for how to connect to Transmission for various server types in our docs.
+
+
Dedicated servers:
+
+
Shared seedbox providers:
+
+
+ }
/>
@@ -286,7 +330,16 @@ function FormFieldsSabnzbd() {
name="host"
label="Host"
help="Eg. http://ip:port or https://url.com/sabnzbd"
- // tooltip={}
+ tooltip={
+
+
See our guides on how to connect to qBittorrent for various server types in our docs.
+
+
Dedicated servers:
+
+
Shared seedbox providers:
+
+
+ }
/>
{port > 0 && (
@@ -357,10 +410,22 @@ function FormFieldsRulesBasic() {
-
+
{settings && settings.rules?.enabled === true && (
-
Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.
https://autobrr.com/configuration/download-clients/dedicated#deluge-rules See recommendations for various server types here:
https://autobrr.com/filters/examples#build-buffer } />
+
+ Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.
+
+
+ See recommendations for various server types here:
+
+
+ }
+ />
)}
);
@@ -389,7 +454,16 @@ function FormFieldsRulesQbit() {
Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.
https://autobrr.com/configuration/download-clients/dedicated#qbittorrent-rules See recommendations for various server types here:
https://autobrr.com/filters/examples#build-buffer >} />
+ tooltip={
+ <>
+ Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.
+
+
+ See recommendations for various server types here:
+
+ >
+ }
+ />
Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.
https://autobrr.com/configuration/download-clients/dedicated#transmission-rules See recommendations for various server types here:
https://autobrr.com/filters/examples#build-buffer >}
+ tooltip={
+ <>
+ Limit the amount of active downloads (0 is unlimited), to give the maximum amount of bandwidth and disk for the downloads.
+
+
+ See recommendations for various server types here:
+
+ >
+ }
/>
>
)}
@@ -456,11 +538,11 @@ function FormFieldsRulesTransmission() {
}
export const rulesComponentMap: componentMapType = {
- DELUGE_V1: ,
- DELUGE_V2: ,
- QBITTORRENT: ,
- PORLA: ,
- TRANSMISSION:
+ DELUGE_V1: ,
+ DELUGE_V2: ,
+ QBITTORRENT: ,
+ PORLA: ,
+ TRANSMISSION:
};
interface formButtonsProps {
@@ -582,12 +664,12 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
mutationFn: (client: DownloadClient) => APIClient.download_clients.create(client),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
- toast.custom((t) => );
+ toast.custom((t) => );
toggle();
},
onError: () => {
- toast.custom((t) => );
+ toast.custom((t) => );
}
});
@@ -647,7 +729,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
onClose={toggle}
>
-
+
-
-
+
+
-
+
)}
@@ -756,7 +838,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) });
- toast.custom((t) => );
+ toast.custom((t) => );
toggle();
}
});
@@ -769,7 +851,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
queryClient.invalidateQueries({ queryKey: clientKeys.lists() });
queryClient.invalidateQueries({ queryKey: clientKeys.detail(client.id) });
- toast.custom((t) => );
+ toast.custom((t) => );
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."
/>
-
+
-
-
+
+
-
+
);
}}
diff --git a/web/src/forms/settings/IndexerForms.tsx b/web/src/forms/settings/IndexerForms.tsx
index 363d999..879196a 100644
--- a/web/src/forms/settings/IndexerForms.tsx
+++ b/web/src/forms/settings/IndexerForms.tsx
@@ -22,6 +22,7 @@ import { SelectFieldBasic, SelectFieldCreatable } from "@components/inputs/selec
import { FeedDownloadTypeOptions } from "@domain/constants";
import { feedKeys } from "@screens/settings/Feed";
import { indexerKeys } from "@screens/settings/Indexer";
+import { DocsLink } from "@components/ExternalLink";
const Input = (props: InputProps) => (
{
{ind.irc.settings.map((f: IndexerSetting, idx: number) => {
switch (f.type) {
- case "text":
- return Please read our IRC guide if you are unfamiliar with IRC.
https://autobrr.com/configuration/irc } />;
- case "secret":
- if (f.name === "invite_command") {
- return ;
- }
- return ;
+ case "text":
+ return (
+
+ Please read our IRC guide if you are unfamiliar with IRC.
+
+
+ }
+ />
+ );
+ case "secret":
+ if (f.name === "invite_command") {
+ return
;
+ }
+ return
;
}
return null;
})}
@@ -224,7 +241,21 @@ const SettingFields = (ind: IndexerDefinition, indexer: string) => {
);
case "secret":
return (
-
This field does not take a full URL. Only use alphanumeric strings like uqcdi67cibkx3an8cmdm
.
https://autobrr.com/faqs#common-action-rejections } />
+
+ This field does not take a full URL. Only use alphanumeric strings like uqcdi67cibkx3an8cmdm
.
+
+
+
+ }
+ />
);
}
return null;
@@ -752,14 +783,26 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
{settings.map((f: IndexerSetting, idx: number) => {
switch (f.type) {
- case "text":
- return (
-
- );
- case "secret":
- return (
-
This field does not take a full URL. Only use alphanumeric strings like uqcdi67cibkx3an8cmdm
.
https://autobrr.com/faqs#common-action-rejections } />
- );
+ case "text":
+ return (
+
+ );
+ case "secret":
+ return (
+
+ This field does not take a full URL. Only use alphanumeric strings like uqcdi67cibkx3an8cmdm
.
+
+
+
+ }
+ />
+ );
}
return null;
})}
diff --git a/web/src/forms/settings/NotificationForms.tsx b/web/src/forms/settings/NotificationForms.tsx
index 7f3edec..c47c124 100644
--- a/web/src/forms/settings/NotificationForms.tsx
+++ b/web/src/forms/settings/NotificationForms.tsx
@@ -20,6 +20,7 @@ import Toast from "@components/notifications/Toast";
import { SlideOver } from "@components/panels";
import { componentMapType } from "./DownloadClientForms";
import { notificationKeys } from "@screens/settings/Notifications";
+import { ExternalLink } from "@components/ExternalLink";
const Input = (props: InputProps) => {
return (
@@ -68,7 +69,14 @@ function FormFieldsDiscord() {
Settings
- Create a webhook integration in your server.
+ {"Create a "}
+
+ webhook integration
+
+ {" in your server."}
@@ -107,7 +115,14 @@ function FormFieldsTelegram() {
Settings
- Read how to create a bot .
+ {"Read how to "}
+
+ create a bot
+
+ {"."}
@@ -136,7 +151,14 @@ function FormFieldsPushover() {
Settings
- Register a new application and add its API Token here.
+ {"Register a new "}
+
+ application
+
+ {" and add its API Token here."}
diff --git a/web/src/index.tsx b/web/src/index.tsx
index 2794033..97ab311 100644
--- a/web/src/index.tsx
+++ b/web/src/index.tsx
@@ -8,7 +8,6 @@ import { createRoot } from "react-dom/client";
import { Buffer } from "buffer";
import "./index.css";
-import "react-tooltip/dist/react-tooltip.css";
import { App } from "./App";
import { InitializeGlobalContext } from "./utils/Context";
diff --git a/web/src/screens/Releases.tsx b/web/src/screens/Releases.tsx
index 894b9d8..f929181 100644
--- a/web/src/screens/Releases.tsx
+++ b/web/src/screens/Releases.tsx
@@ -7,6 +7,7 @@ import { useState } from "react";
import { ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/24/solid";
import { ReleaseTable } from "./releases/ReleaseTable";
+import { ExternalLink } from "@components/ExternalLink";
const Code = ({ children }: { children: React.ReactNode }) => (
@@ -45,7 +46,7 @@ export const Releases = () => {
Search tips
-
+
You can use
2 special
wildcard characters for the purpose of pattern matching.
- Percent (
%
) - for matching any
sequence of characters (equivalent to
*
in Regex)
@@ -75,14 +76,14 @@ export const Releases = () => {
- As always, please refer to our
Search function usage
- documentation page to keep up with the latest examples and information.
+
+ {" documentation page to keep up with the latest examples and information."}
) : null}
diff --git a/web/src/screens/auth/Login.tsx b/web/src/screens/auth/Login.tsx
index 5cf8046..d2b4a4e 100644
--- a/web/src/screens/auth/Login.tsx
+++ b/web/src/screens/auth/Login.tsx
@@ -8,13 +8,13 @@ import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { useMutation } from "@tanstack/react-query";
import toast from "react-hot-toast";
-import { Tooltip } from "react-tooltip";
import { ReactComponent as Logo } from "@app/logo.svg";
import { APIClient } from "@api/APIClient";
import { AuthContext } from "@utils/Context";
-import { PasswordInput, TextInput } from "@components/inputs/text";
import Toast from "@components/notifications/Toast";
+import { Tooltip } from "@components/tooltips/Tooltip";
+import { PasswordInput, TextInput } from "@components/inputs/text";
type LoginFormFields = {
username: string;
@@ -103,8 +103,15 @@ export const Login = () => {
- Forgot?
-
+
+ Forgot?
+
+ }
+ >
+ If you forget your password you can reset it via the terminal: autobrrctl --config /home/username/.config/autobrr change-password $USERNAME
+
diff --git a/web/src/screens/filters/Action.tsx b/web/src/screens/filters/Action.tsx
index dab86a7..8025663 100644
--- a/web/src/screens/filters/Action.tsx
+++ b/web/src/screens/filters/Action.tsx
@@ -28,6 +28,7 @@ import { DeleteModal } from "@components/modals";
import { CollapsableSection } from "./Details";
import { TextArea } from "@components/inputs/input";
import Toast from "@components/notifications/Toast";
+import { DocsLink } from "@components/ExternalLink";
interface FilterActionsProps {
filter: Filter;
@@ -224,7 +225,15 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
label="Save path"
columns={6}
placeholder="eg. /full/path/to/download_folder"
- tooltip={Set a custom save path for this action. Automatic Torrent Management will take care of this if using qBittorrent with categories.
The field can use macros to transform/add values from metadata:
https://autobrr.com/filters/actions#macros } />
+ tooltip={
+
+
Set a custom save path for this action. Automatic Torrent Management will take care of this if using qBittorrent with categories.
+
+
The field can use macros to transform/add values from metadata:
+
+
+ }
+ />
@@ -234,13 +243,25 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
label="Category"
columns={6}
placeholder="eg. category"
- tooltip={} />
+ tooltip={
+
+
The field can use macros to transform/add values from metadata:
+
+
+ }
+ />
The field can use macros to transform/add values from metadata:
https://autobrr.com/filters/actions#macros } />
+ tooltip={
+
+
The field can use macros to transform/add values from metadata:
+
+
+ }
+ />
@@ -282,7 +303,14 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
Choose to ignore rules set in Client Settings.
} />
+ tooltip={
+
+
+ Choose to ignore rules set in Client Settings.
+
+
+ }
+ />
} />
- Number of seconds to wait before running actions.
https://autobrr.com/filters#rules } />
- Filters are checked in order of priority. Higher number = higher priority.
https://autobrr.com/filters#rules } />
- Number of max downloads as specified by the respective unit.
https://autobrr.com/filters#rules } />
- The unit of time for counting the maximum downloads per filter.
https://autobrr.com/filters#rules } />
+
+ Supports units such as MB, MiB, GB, etc.
+
+
+ }
+ />
+
+ Supports units such as MB, MiB, GB, etc.
+
+
+ }
+ />
+
+ Number of seconds to wait before running actions.
+
+
+ }
+ />
+
+ Filters are checked in order of priority. Higher number = higher priority.
+
+
+ }
+ />
+
+ Number of max downloads as specified by the respective unit.
+
+
+ }
+ />
+
+ The unit of time for counting the maximum downloads per filter.
+
+
+ }
+ />
@@ -478,43 +542,194 @@ export function MoviesTv() {
return (
} />
+
+ You can use basic filtering like wildcards *
or replace single characters with ?
+
+
+ }
+ />
+
+ This field takes a range of years and/or comma separated single years.
+
+
+ }
+ />
} />
+
+ See docs for information about how to only grab season packs:
+
+
+ }
+ />
+
+ See docs for information about how to only grab episodes:
+
+
+ }
+ />
- {/*Do not match older or already existing episodes.*/}
+ {" "}
+ {/*Do not match older or already existing episodes.*/}
} />
+
+ Will match releases which contain any of the selected resolutions.
+
+
+ }
+ />
+
+ Will match releases which contain any of the selected sources.
+
+
+ }
+ />
} />
- Will match releases which contain any of the selected containers.
https://autobrr.com/filters#quality } />
+
+ Will match releases which contain any of the selected codecs.
+
+
+ }
+ />
+
+ Will match releases which contain any of the selected containers.
+
+
+ }
+ />
} />
- Won't match releases which contain any of the selected HDR designations (takes priority over Match HDR).
https://autobrr.com/filters#quality } />
+
+ Will match releases which contain any of the selected HDR designations.
+
+
+ }
+ />
+
+ Won't match releases which contain any of the selected HDR designations (takes priority over Match HDR).
+
+
+ }
+ />
} />
- Won't match releases which contain any of the selected Other designations (takes priority over Match Other).
https://autobrr.com/filters#quality } />
+
+ Will match releases which contain any of the selected designations.
+
+
+ }
+ />
+
+ Won't match releases which contain any of the selected Other designations (takes priority over Match Other).
+
+
+ }
+ />
@@ -525,47 +740,147 @@ export function Music({ values }: AdvancedProps) {
return (
} />
- This field takes a range of years and/or comma separated single years.
https://autobrr.com/filters#music } />
+
+ You can use basic filtering like wildcards *
or replace single characters with ?
+
+
+ }
+ />
+
+ You can use basic filtering like wildcards *
or replace single characters with ?
+
+
+ }
+ />
+
+ This field takes a range of years and/or comma separated single years.
+
+
+ }
+ />
} />
-
-
- } />
- Will only match releases with any of the selected types.
https://autobrr.com/filters#quality-1 } />
-
-
- } />
-
-
-
-
-
-
-
-
- {/*
*/}
-
+
+ Will only match releases with any of the selected formats. This is overridden by Perfect FLAC.
+
-
+ }
+ />
+
+ Will only match releases with any of the selected qualities. This is overridden by Perfect FLAC.
+
+
+ }
+ />
+
+
+
+
+ Will only match releases with any of the selected sources. This is overridden by Perfect FLAC.
+
+
+ }
+ />
+
+ Will only match releases with any of the selected types.
+
+
+ }
+ />
+
+
+
+
+ Log scores go from 0 to 100. This is overridden by Perfect FLAC.
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+ Override all options about quality, source, format, and cue/log/log score.
+
+
+ }
+ />
@@ -580,18 +895,54 @@ interface AdvancedProps {
export function Advanced({ values }: AdvancedProps) {
return (
} />
+
+ This field has full regex support (Golang flavour).
+
+
+
+ Remember to tick Use Regex below if using more than *
and ?
.
+
+ }
+ />
+
+ This field has full regex support (Golang flavour).
+
+
+
+ Remember to tick Use Regex below if using more than *
and ?
.
+
+ }
+ />
{values.match_releases ? (
Do you have a good reason to use Match releases instead of one of the other tabs?>
+ <>
+ Do you have a good reason to use Match releases instead of one of the other tabs?
+ >
}
colors="text-cyan-700 bg-cyan-100 dark:bg-cyan-200 dark:text-cyan-800"
/>
@@ -600,7 +951,9 @@ export function Advanced({ values }: AdvancedProps) {
Do you have a good reason to use Except releases instead of one of the other tabs?>
+ <>
+ Do you have a good reason to use Except releases instead of one of the other tabs?
+ >
}
colors="text-fuchsia-700 bg-fuchsia-100 dark:bg-fuchsia-200 dark:text-fuchsia-800"
/>
@@ -611,42 +964,215 @@ export function Advanced({ values }: AdvancedProps) {
-
- Comma separated list of release groups to match.
https://autobrr.com/filters#advanced } />
- Comma separated list of release groups to ignore (takes priority over Match releases).
https://autobrr.com/filters#advanced } />
+
+
+ Comma separated list of release groups to match.
+
+
+ }
+ />
+
+ Comma separated list of release groups to ignore (takes priority over Match releases).
+
+
+ }
+ />
-
- Comma separated list of categories to match.
https://autobrr.com/filters/categories } />
- Comma separated list of categories to ignore (takes priority over Match releases).
https://autobrr.com/filters/categories } />
+
+
+ Comma separated list of categories to match.
+
+
+ }
+ />
+
+ Comma separated list of categories to ignore (takes priority over Match releases).
+
+
+ }
+ />
- Comma separated list of tags to match.
https://autobrr.com/filters#advanced } />
- Logic used to match filter tags.
https://autobrr.com/filters#advanced } />
- Comma separated list of tags to ignore (takes priority over Match releases).
hhttps://autobrr.com/filters#advanced } />
- Logic used to match except tags.
https://autobrr.com/filters#advanced } />
+
+ Comma separated list of tags to match.
+
+
+ }
+ />
+
+ Logic used to match filter tags.
+
+
+ }
+ />
+
+ Comma separated list of tags to ignore (takes priority over Match releases).
+
+
+ }
+ />
+
+ Logic used to match except tags.
+
+
+ }
+ />
-
- Comma separated list of uploaders to match.
https://autobrr.com/filters#advanced } />
- Comma separated list of uploaders to ignore (takes priority over Match releases).
https://autobrr.com/filters#advanced } />
+
+
+ Comma separated list of uploaders to match.
+
+
+ }
+ />
+
+ Comma separated list of uploaders to ignore (takes priority over Match releases).
+
+
+
+ }
+ />
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
-
+
+
@@ -654,10 +1180,44 @@ export function Advanced({ values }: AdvancedProps) {
-
+
{/*} />
- This field has full regex support (Golang flavour).
https://autobrr.com/filters#advanced Remember to tick Use Regex below if using more than *
and ?
.
} />
+
+ This field has full regex support (Golang flavour).
+
+
+
+ Remember to tick Use Regex below if using more than *
and ?
.
+
+ }
+ />
+
+ This field has full regex support (Golang flavour).
+
+
+
+ Remember to tick Use Regex below if using more than *
and ?
.
+
+ }
+ />
{/**/}
@@ -665,12 +1225,53 @@ export function Advanced({ values }: AdvancedProps) {
-
+
-
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.
See who uses what in the documentation: https://autobrr.com/filters/freeleech
} />
+
+
+ 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.
+
+
+
+ See who uses what in the documentation:{" "}
+
+
+
+ }
+ />
- 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.
See who uses what in the documentation: https://autobrr.com/filters/freeleech
} columns={6} placeholder="eg. 50,75-100" />
+
+
+ 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.
+
+
+
+ See who uses what in the documentation:{" "}
+
+
+
+ }
+ columns={6}
+ placeholder="eg. 50,75-100"
+ />
);
diff --git a/web/src/screens/filters/External.tsx b/web/src/screens/filters/External.tsx
index efc455c..7198cd7 100644
--- a/web/src/screens/filters/External.tsx
+++ b/web/src/screens/filters/External.tsx
@@ -19,6 +19,7 @@ import {
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import { DeleteModal } from "@components/modals";
import { ArrowDownIcon, ArrowUpIcon } from "@heroicons/react/24/outline";
+import { DocsLink } from "@components/ExternalLink";
export function External() {
const { values } = useFormikContext();
@@ -261,11 +262,7 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
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:
- https://autobrr.com/filters/actions#custom-commands--exec
-
+
}
/>
diff --git a/web/src/screens/filters/List.tsx b/web/src/screens/filters/List.tsx
index 39dd1b7..83d559c 100644
--- a/web/src/screens/filters/List.tsx
+++ b/web/src/screens/filters/List.tsx
@@ -10,7 +10,6 @@ import { Listbox, Menu, Switch, Transition } from "@headlessui/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { FormikValues } from "formik";
import { useCallback } from "react";
-import { Tooltip } from "react-tooltip";
import {
ArrowsRightLeftIcon,
CheckIcon,
@@ -35,6 +34,7 @@ import { EmptyListState } from "@components/emptystates";
import { DeleteModal } from "@components/modals";
import { Importer } from "./Importer";
+import { Tooltip } from "@components/tooltips/Tooltip";
export const filterKeys = {
all: ["filters"] as const,
@@ -96,7 +96,7 @@ export function Filters() {
setIsOpen={setShowImportModal}
/>
-
+
Filters
{({ open }) => (
@@ -635,49 +635,30 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
Priority: {filter.priority}
-
-
-
-
+
+
Actions: {filter.actions_count}
-
- {filter.actions_count === 0 && (
- <>
+ {!filter.actions_count && (
-
-
- You need to setup an action in the filter otherwise you will not get any snatches.
-
-
- >
- )}
-
-
+ )}
+
+ }
+ >
+ {!filter.actions_count ? (
+ <>{"You need to setup an action in the filter otherwise you will not get any snatches."}>
+ ) : null}
+
diff --git a/web/src/screens/releases/Filters.tsx b/web/src/screens/releases/Filters.tsx
index 0ae51ec..c9bb192 100644
--- a/web/src/screens/releases/Filters.tsx
+++ b/web/src/screens/releases/Filters.tsx
@@ -140,7 +140,8 @@ export const PushStatusSelectColumnFilter = ({
))}
- );};
+ );
+}
export const SearchColumnFilter = ({
column: { filterValue, setFilter, id }
@@ -161,4 +162,5 @@ export const SearchColumnFilter = ({
placeholder="Search releases..."
/>
- );};
+ );
+}
diff --git a/web/src/screens/releases/ReleaseTable.tsx b/web/src/screens/releases/ReleaseTable.tsx
index 09ed3bb..3089611 100644
--- a/web/src/screens/releases/ReleaseTable.tsx
+++ b/web/src/screens/releases/ReleaseTable.tsx
@@ -23,6 +23,7 @@ import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFi
import { classNames } from "@utils";
import { ArrowTopRightOnSquareIcon, ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import { Tooltip } from "@components/tooltips/Tooltip";
+import { ExternalLink } from "@components/ExternalLink";
export const releaseKeys = {
all: ["releases"] as const,
@@ -103,24 +104,17 @@ export const ReleaseTable = () => {
{props.row.original.download_url && (
-
-
+
)}
{props.row.original.info_url && (
-
+
-
+
)}
diff --git a/web/src/screens/settings/Application.tsx b/web/src/screens/settings/Application.tsx
index 04cadaf..9ddc135 100644
--- a/web/src/screens/settings/Application.tsx
+++ b/web/src/screens/settings/Application.tsx
@@ -11,6 +11,7 @@ import { Checkbox } from "@components/Checkbox";
import { SettingsContext } from "@utils/Context";
import { GithubRelease } from "@app/types/Update";
import Toast from "@components/notifications/Toast";
+import { ExternalLink } from "@components/ExternalLink";
interface RowItemProps {
label: string;
@@ -63,9 +64,12 @@ const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => {
{value}
{newUpdate && newUpdate.html_url && (
-
- {newUpdate.name} available!
-
+
+ {newUpdate.name} available!
+
)}
@@ -184,10 +188,12 @@ function ApplicationSettings() {
setSettings({
- ...settings,
- debug: newValue
- })}
+ setValue={
+ (newValue: boolean) => setSettings((prevState) => ({
+ ...prevState,
+ debug: newValue
+ }))
+ }
/>
@@ -205,15 +211,16 @@ function ApplicationSettings() {
label="Dark theme"
description="Switch between dark and light theme."
value={settings.darkTheme}
- setValue={(newValue: boolean) => setSettings({
- ...settings,
- darkTheme: newValue
- })}
+ setValue={
+ (newValue: boolean) => setSettings((prevState) => ({
+ ...prevState,
+ darkTheme: newValue
+ }))
+ }
/>
-
);
}
diff --git a/web/src/screens/settings/Feed.tsx b/web/src/screens/settings/Feed.tsx
index 061f429..d8d626c 100644
--- a/web/src/screens/settings/Feed.tsx
+++ b/web/src/screens/settings/Feed.tsx
@@ -24,6 +24,7 @@ import { FeedUpdateForm } from "@forms/settings/FeedForms";
import { EmptySimple } from "@components/emptystates";
import { ImplementationBadges } from "./Indexer";
import { ArrowPathIcon } from "@heroicons/react/24/solid";
+import { ExternalLink } from "@components/ExternalLink";
export const feedKeys = {
all: ["feeds"] as const,
@@ -354,10 +355,8 @@ const FeedItemDropdown = ({
{({ active }) => (
-
View latest run
-
+
)}