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) => {

Please consider reporting this error to our {" "} - GitHub page - + {" or to "} - our official Discord channel - + .

{ return ( @@ -21,23 +22,19 @@ export const NotFound = () => {

feel free to report this to our {" "} - GitHub page - + {" or to "} - our official Discord channel - + .

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 = () => (
@@ -38,9 +39,7 @@ export const LeftNav = () => ( {item.name} ))} - ( className="inline ml-1 h-5 w-5" aria-hidden="true" /> - +
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 = ({

{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 && ( )} @@ -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 && ( )} @@ -484,10 +484,9 @@ export const TextAreaAutoResize = ({ {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 = ({
@@ -108,7 +110,9 @@ export const PasswordFieldWide = ({
@@ -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 = ({ @@ -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 ( -
- - - {children} - -
- ); -}; 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 (

See guides for how to connect to Deluge for various server types in our docs.


Dedicated servers:

https://autobrr.com/configuration/download-clients/dedicated#deluge

Shared seedbox providers:

https://autobrr.com/configuration/download-clients/shared-seedboxes#deluge
} - 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 (

See guides for how to connect to qBittorrent for various server types in our docs.


Dedicated servers:

https://autobrr.com/configuration/download-clients/dedicated#qbittorrent

Shared seedbox providers:

https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent
} - 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 (
- - + {tls && (

See guides for how to connect to rTorrent for various server types in our docs.


Dedicated servers:

https://autobrr.com/configuration/download-clients/dedicated#rtorrent--rutorrent

Shared seedbox providers:

https://autobrr.com/configuration/download-clients/shared-seedboxes#rtorrent
} - 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 (

See guides for how to connect to Transmission for various server types in our docs.


Dedicated servers:

https://autobrr.com/configuration/download-clients/dedicated#transmission

Shared seedbox providers:

https://autobrr.com/configuration/download-clients/shared-seedboxes#transmisison
} - 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={

See guides for how to connect to qBittorrent for various server types in our docs.


Dedicated servers:

https://autobrr.com/configuration/download-clients/dedicated#qbittorrent

Shared seedbox providers:

https://autobrr.com/configuration/download-clients/shared-seedboxes#qbittorrent
} + 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={

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:

+ +
+ } + />

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. +

+
+ } + />

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.

+ + + } + /> +

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 filter tags.

+ + + } + /> + +

Comma separated list of tags to ignore (takes priority over Match releases).

+ + + } + /> +