refactor(web): rename custom components (#1581)

Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
This commit is contained in:
martylukyy 2024-08-12 20:44:57 +02:00 committed by GitHub
parent 7d7bf9ed4c
commit e8e45c664d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 459 additions and 430 deletions

View file

@ -13,7 +13,7 @@ interface ButtonProps {
onClick?: () => void; onClick?: () => void;
} }
export const Button = ({ children, className, disabled, onClick }: ButtonProps) => ( export const TableButton = ({ children, className, disabled, onClick }: ButtonProps) => (
<button <button
type="button" type="button"
className={classNames( className={classNames(
@ -27,7 +27,7 @@ export const Button = ({ children, className, disabled, onClick }: ButtonProps)
</button> </button>
); );
export const PageButton = ({ children, className, disabled, onClick }: ButtonProps) => ( export const TablePageButton = ({ children, className, disabled, onClick }: ButtonProps) => (
<button <button
type="button" type="button"
className={classNames( className={classNames(

View file

@ -3,5 +3,5 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
export { Button, PageButton } from "./Buttons"; export { TableButton, TablePageButton } from "./Buttons";
export { AgeCell, IndexerCell, NameCell, TitleCell, ReleaseStatusCell, LinksCell } from "./Cells"; export { AgeCell, IndexerCell, NameCell, TitleCell, ReleaseStatusCell, LinksCell } from "./Cells";

View file

@ -13,10 +13,18 @@ import { useToggle } from "@hooks/hooks";
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
import { SelectFieldProps } from "./select"; import { SelectFieldProps } from "./select";
import * as common from "./common";
import { DocsTooltip } from "@components/tooltips/DocsTooltip"; import { DocsTooltip } from "@components/tooltips/DocsTooltip";
import { Checkbox } from "@components/Checkbox"; import { Checkbox } from "@components/Checkbox";
import {
DropdownIndicator,
ErrorField, IndicatorSeparator,
RequiredField,
SelectControl,
SelectInput,
SelectMenu,
SelectOption
} from "@components/inputs/common.tsx";
interface TextFieldWideProps { interface TextFieldWideProps {
name: string; name: string;
@ -50,7 +58,7 @@ export const TextFieldWide = ({
{tooltip ? ( {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip> <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label} ) : label}
<common.RequiredField required={required} /> <RequiredField required={required} />
</div> </div>
</label> </label>
</div> </div>
@ -85,7 +93,7 @@ export const TextFieldWide = ({
{help && ( {help && (
<p className="mt-2 text-sm text-gray-500" id={`${name}-description`}>{help}</p> <p className="mt-2 text-sm text-gray-500" id={`${name}-description`}>{help}</p>
)} )}
<common.ErrorField name={name} classNames="block text-red-500 mt-2" /> <ErrorField name={name} classNames="block text-red-500 mt-2" />
</div> </div>
</div> </div>
); );
@ -125,7 +133,7 @@ export const PasswordFieldWide = ({
{tooltip ? ( {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip> <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label} ) : label}
<common.RequiredField required={required} /> <RequiredField required={required} />
</div> </div>
</label> </label>
</div> </div>
@ -163,7 +171,7 @@ export const PasswordFieldWide = ({
{help && ( {help && (
<p className="mt-2 text-sm text-gray-500" id={`${name}-description`}>{help}</p> <p className="mt-2 text-sm text-gray-500" id={`${name}-description`}>{help}</p>
)} )}
<common.ErrorField name={name} classNames="block text-red-500 mt-2" /> <ErrorField name={name} classNames="block text-red-500 mt-2" />
</div> </div>
</div> </div>
); );
@ -198,7 +206,7 @@ export const NumberFieldWide = ({
{tooltip ? ( {tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip> <DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label} ) : label}
<common.RequiredField required={required} /> <RequiredField required={required} />
</div> </div>
</label> </label>
</div> </div>
@ -233,7 +241,7 @@ export const NumberFieldWide = ({
{help && ( {help && (
<p className="mt-2 text-sm text-gray-500 dark:text-gray-500" id={`${name}-description`}>{help}</p> <p className="mt-2 text-sm text-gray-500 dark:text-gray-500" id={`${name}-description`}>{help}</p>
)} )}
<common.ErrorField name={name} classNames="block text-red-500 mt-2" /> <ErrorField name={name} classNames="block text-red-500 mt-2" />
</div> </div>
</div> </div>
); );
@ -325,12 +333,12 @@ export const SelectFieldWide = ({
isClearable={true} isClearable={true}
isSearchable={true} isSearchable={true}
components={{ components={{
Input: common.SelectInput, Input: SelectInput,
Control: common.SelectControl, Control: SelectControl,
Menu: common.SelectMenu, Menu: SelectMenu,
Option: common.SelectOption, Option: SelectOption,
IndicatorSeparator: common.IndicatorSeparator, IndicatorSeparator: IndicatorSeparator,
DropdownIndicator: common.DropdownIndicator DropdownIndicator: DropdownIndicator
}} }}
placeholder={optionDefaultText} placeholder={optionDefaultText}
styles={{ styles={{

View file

@ -23,9 +23,17 @@ import Toast from "@components/notifications/Toast";
import { Checkbox } from "@components/Checkbox"; import { Checkbox } from "@components/Checkbox";
import { TitleSubtitle } from "@components/headings"; import { TitleSubtitle } from "@components/headings";
import * as FilterSection from "./_components";
import * as FilterActions from "./action_components";
import { DownloadClientsQueryOptions } from "@api/queries"; import { DownloadClientsQueryOptions } from "@api/queries";
import { FilterHalfRow, FilterLayout, FilterPage, FilterSection } from "@screens/filters/sections/_components.tsx";
import {
Arr,
Deluge, Exec,
Porla,
QBittorrent,
RTorrent,
SABnzbd, Test,
Transmission, WatchFolder, WebHook
} from "@screens/filters/sections/action_components";
// interface FilterActionsProps { // interface FilterActionsProps {
// filter: Filter; // filter: Filter;
@ -141,35 +149,35 @@ const TypeForm = (props: ClientActionProps) => {
switch (action.type) { switch (action.type) {
// torrent clients // torrent clients
case "QBITTORRENT": case "QBITTORRENT":
return <FilterActions.QBittorrent {...props} />; return <QBittorrent {...props} />;
case "DELUGE_V1": case "DELUGE_V1":
case "DELUGE_V2": case "DELUGE_V2":
return <FilterActions.Deluge {...props} />; return <Deluge {...props} />;
case "RTORRENT": case "RTORRENT":
return <FilterActions.RTorrent {...props} />; return <RTorrent {...props} />;
case "TRANSMISSION": case "TRANSMISSION":
return <FilterActions.Transmission {...props} />; return <Transmission {...props} />;
case "PORLA": case "PORLA":
return <FilterActions.Porla {...props} />; return <Porla {...props} />;
// arrs // arrs
case "RADARR": case "RADARR":
case "SONARR": case "SONARR":
case "LIDARR": case "LIDARR":
case "WHISPARR": case "WHISPARR":
case "READARR": case "READARR":
return <FilterActions.Arr {...props} />; return <Arr {...props} />;
// nzb // nzb
case "SABNZBD": case "SABNZBD":
return <FilterActions.SABnzbd {...props} />; return <SABnzbd {...props} />;
// autobrr actions // autobrr actions
case "TEST": case "TEST":
return <FilterActions.Test />; return <Test />;
case "EXEC": case "EXEC":
return <FilterActions.Exec {...props} />; return <Exec {...props} />;
case "WATCH_FOLDER": case "WATCH_FOLDER":
return <FilterActions.WatchFolder {...props} />; return <WatchFolder {...props} />;
case "WEBHOOK": case "WEBHOOK":
return <FilterActions.WebHook {...props} />; return <WebHook {...props} />;
default: default:
// TODO(stacksmash76): Indicate error // TODO(stacksmash76): Indicate error
return null; return null;
@ -268,13 +276,13 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
text="Are you sure you want to remove this action? This action cannot be undone." text="Are you sure you want to remove this action? This action cannot be undone."
/> />
<FilterSection.Page gap="sm:gap-y-6"> <FilterPage gap="sm:gap-y-6">
<FilterSection.Section <FilterSection
title="Action" title="Action"
subtitle="Define the download client for your action and its name" subtitle="Define the download client for your action and its name"
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Select <Select
name={`actions.${idx}.type`} name={`actions.${idx}.type`}
label="Action type" label="Action type"
@ -282,13 +290,13 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
options={ActionTypeOptions} options={ActionTypeOptions}
tooltip={<div><p>Select the action type for this action.</p></div>} tooltip={<div><p>Select the action type for this action.</p></div>}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<TextField name={`actions.${idx}.name`} label="Name" /> <TextField name={`actions.${idx}.name`} label="Name" />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
<TypeForm action={action} clients={clients} idx={idx} /> <TypeForm action={action} clients={clients} idx={idx} />
@ -309,7 +317,7 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
Close Close
</button> </button>
</div> </div>
</FilterSection.Page> </FilterPage>
</div> </div>
)} )}
</li> </li>

View file

@ -7,14 +7,25 @@ import { useFormikContext } from "formik";
import { DocsLink } from "@components/ExternalLink"; import { DocsLink } from "@components/ExternalLink";
import { WarningAlert } from "@components/alerts"; import { WarningAlert } from "@components/alerts";
import {
import * as Input from "@components/inputs"; CollapsibleSection,
import * as CONSTS from "@domain/constants"; FilterHalfRow,
FilterLayout,
import { CollapsibleSection } from "./_components"; FilterLayoutClass,
import * as Components from "./_components"; FilterTightGridGapClass
} from "./_components";
import { classNames } from "@utils"; import { classNames } from "@utils";
import * as CONSTS from "@domain/constants";
import {
MultiSelect, NumberField, RegexField,
RegexTextAreaField,
Select,
SwitchGroup,
TextAreaAutoResize,
TextField
} from "@components/inputs";
// type ValueConsumer = { // type ValueConsumer = {
// values: FormikValues; // values: FormikValues;
// }; // };
@ -29,15 +40,15 @@ const Releases = () => {
title="Release Names" title="Release Names"
subtitle="Match only certain release names and/or ignore other release names." subtitle="Match only certain release names and/or ignore other release names."
> >
<Components.Layout> <FilterLayout>
<Components.HalfRow> <FilterHalfRow>
<Input.SwitchGroup name="use_regex" label="Use Regex" className="pt-2" /> <SwitchGroup name="use_regex" label="Use Regex" className="pt-2" />
</Components.HalfRow> </FilterHalfRow>
</Components.Layout> </FilterLayout>
<Components.Layout> <FilterLayout>
<Components.HalfRow> <FilterHalfRow>
<Input.RegexTextAreaField <RegexTextAreaField
name="match_releases" name="match_releases"
label="Match releases" label="Match releases"
useRegex={values.use_regex} useRegex={values.use_regex}
@ -53,10 +64,10 @@ const Releases = () => {
</div> </div>
} }
/> />
</Components.HalfRow> </FilterHalfRow>
<Components.HalfRow> <FilterHalfRow>
<Input.RegexTextAreaField <RegexTextAreaField
name="except_releases" name="except_releases"
label="Except releases" label="Except releases"
useRegex={values.use_regex} useRegex={values.use_regex}
@ -72,9 +83,9 @@ const Releases = () => {
</div> </div>
} }
/> />
</Components.HalfRow> </FilterHalfRow>
</Components.Layout> </FilterLayout>
{values.match_releases ? ( {values.match_releases ? (
<WarningAlert <WarningAlert
@ -112,7 +123,7 @@ const Groups = () => {
title="Groups" title="Groups"
subtitle="Match only certain groups and/or ignore other groups." subtitle="Match only certain groups and/or ignore other groups."
> >
<Input.TextAreaAutoResize <TextAreaAutoResize
name="match_release_groups" name="match_release_groups"
label="Match release groups" label="Match release groups"
columns={6} columns={6}
@ -124,7 +135,7 @@ const Groups = () => {
</div> </div>
} }
/> />
<Input.TextAreaAutoResize <TextAreaAutoResize
name="except_release_groups" name="except_release_groups"
label="Except release groups" label="Except release groups"
columns={6} columns={6}
@ -150,7 +161,7 @@ const Categories = () => {
title="Categories" title="Categories"
subtitle="Match or exclude categories (if announced)" subtitle="Match or exclude categories (if announced)"
> >
<Input.TextAreaAutoResize <TextAreaAutoResize
name="match_categories" name="match_categories"
label="Match categories" label="Match categories"
columns={6} columns={6}
@ -162,7 +173,7 @@ const Categories = () => {
</div> </div>
} }
/> />
<Input.TextAreaAutoResize <TextAreaAutoResize
name="except_categories" name="except_categories"
label="Except categories" label="Except categories"
columns={6} columns={6}
@ -188,8 +199,8 @@ const Tags = () => {
title="Tags" title="Tags"
subtitle="Match or exclude tags (if announced)" subtitle="Match or exclude tags (if announced)"
> >
<div className={classNames("sm:col-span-6", Components.LayoutClass, Components.TightGridGapClass)}> <div className={classNames("sm:col-span-6", FilterLayoutClass, FilterTightGridGapClass)}>
<Input.TextAreaAutoResize <TextAreaAutoResize
name="tags" name="tags"
label="Match tags" label="Match tags"
columns={8} columns={8}
@ -201,7 +212,7 @@ const Tags = () => {
</div> </div>
} }
/> />
<Input.Select <Select
name="tags_match_logic" name="tags_match_logic"
label="Match logic" label="Match logic"
columns={4} columns={4}
@ -215,8 +226,8 @@ const Tags = () => {
} }
/> />
</div> </div>
<div className={classNames("sm:col-span-6", Components.LayoutClass, Components.TightGridGapClass)}> <div className={classNames("sm:col-span-6", FilterLayoutClass, FilterTightGridGapClass)}>
<Input.TextAreaAutoResize <TextAreaAutoResize
name="except_tags" name="except_tags"
label="Except tags" label="Except tags"
columns={8} columns={8}
@ -228,7 +239,7 @@ const Tags = () => {
</div> </div>
} }
/> />
<Input.Select <Select
name="except_tags_match_logic" name="except_tags_match_logic"
label="Except logic" label="Except logic"
columns={4} columns={4}
@ -256,7 +267,7 @@ const Uploaders = () => {
title="Uploaders" title="Uploaders"
subtitle="Match or ignore uploaders (if announced)" subtitle="Match or ignore uploaders (if announced)"
> >
<Input.TextAreaAutoResize <TextAreaAutoResize
name="match_uploaders" name="match_uploaders"
label="Match uploaders" label="Match uploaders"
columns={6} columns={6}
@ -268,7 +279,7 @@ const Uploaders = () => {
</div> </div>
} }
/> />
<Input.TextAreaAutoResize <TextAreaAutoResize
name="except_uploaders" name="except_uploaders"
label="Except uploaders" label="Except uploaders"
columns={6} columns={6}
@ -295,13 +306,13 @@ const Language = () => {
title="Language" title="Language"
subtitle="Match or ignore languages (if announced)" subtitle="Match or ignore languages (if announced)"
> >
<Input.MultiSelect <MultiSelect
name="match_language" name="match_language"
options={CONSTS.LANGUAGE_OPTIONS} options={CONSTS.LANGUAGE_OPTIONS}
label="Match Language" label="Match Language"
columns={6} columns={6}
/> />
<Input.MultiSelect <MultiSelect
name="except_language" name="except_language"
options={CONSTS.LANGUAGE_OPTIONS} options={CONSTS.LANGUAGE_OPTIONS}
label="Except Language" label="Except Language"
@ -321,13 +332,13 @@ const Origins = () => {
title="Origins" title="Origins"
subtitle="Match Internals, Scene, P2P, etc. (if announced)" subtitle="Match Internals, Scene, P2P, etc. (if announced)"
> >
<Input.MultiSelect <MultiSelect
name="origins" name="origins"
options={CONSTS.ORIGIN_OPTIONS} options={CONSTS.ORIGIN_OPTIONS}
label="Match Origins" label="Match Origins"
columns={6} columns={6}
/> />
<Input.MultiSelect <MultiSelect
name="except_origins" name="except_origins"
options={CONSTS.ORIGIN_OPTIONS} options={CONSTS.ORIGIN_OPTIONS}
label="Except Origins" label="Except Origins"
@ -347,7 +358,7 @@ const Freeleech = () => {
title="Freeleech" title="Freeleech"
subtitle="Match based off freeleech (if announced)" subtitle="Match based off freeleech (if announced)"
> >
<Input.TextField <TextField
name="freeleech_percent" name="freeleech_percent"
label="Freeleech percent" label="Freeleech percent"
disabled={values.freeleech} disabled={values.freeleech}
@ -368,8 +379,8 @@ const Freeleech = () => {
columns={6} columns={6}
placeholder="eg. 50,75-100" placeholder="eg. 50,75-100"
/> />
<Components.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name="freeleech" name="freeleech"
label="Freeleech" label="Freeleech"
className="py-0" className="py-0"
@ -389,7 +400,7 @@ const Freeleech = () => {
</div> </div>
} }
/> />
</Components.HalfRow> </FilterHalfRow>
</CollapsibleSection> </CollapsibleSection>
); );
} }
@ -414,15 +425,15 @@ const FeedSpecific = () => {
<>These options are <span className="font-bold">only</span> for Feeds such as RSS, Torznab and Newznab</> <>These options are <span className="font-bold">only</span> for Feeds such as RSS, Torznab and Newznab</>
} }
> >
<Components.Layout> <FilterLayout>
<Input.SwitchGroup <SwitchGroup
name="use_regex_description" name="use_regex_description"
label="Use Regex" label="Use Regex"
className="col-span-12 sm:col-span-6" className="col-span-12 sm:col-span-6"
/> />
</Components.Layout> </FilterLayout>
<Input.RegexTextAreaField <RegexTextAreaField
name="match_description" name="match_description"
label="Match description" label="Match description"
useRegex={values.use_regex_description} useRegex={values.use_regex_description}
@ -438,7 +449,7 @@ const FeedSpecific = () => {
</div> </div>
} }
/> />
<Input.RegexTextAreaField <RegexTextAreaField
name="except_description" name="except_description"
label="Except description" label="Except description"
useRegex={values.use_regex_description} useRegex={values.use_regex_description}
@ -454,7 +465,7 @@ const FeedSpecific = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="min_seeders" name="min_seeders"
label="Min Seeders" label="Min Seeders"
placeholder="Takes any number (0 is infinite)" placeholder="Takes any number (0 is infinite)"
@ -465,7 +476,7 @@ const FeedSpecific = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="max_seeders" name="max_seeders"
label="Max Seeders" label="Max Seeders"
placeholder="Takes any number (0 is infinite)" placeholder="Takes any number (0 is infinite)"
@ -476,7 +487,7 @@ const FeedSpecific = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="min_leechers" name="min_leechers"
label="Min Leechers" label="Min Leechers"
placeholder="Takes any number (0 is infinite)" placeholder="Takes any number (0 is infinite)"
@ -487,7 +498,7 @@ const FeedSpecific = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="max_leechers" name="max_leechers"
label="Max Leechers" label="Max Leechers"
placeholder="Takes any number (0 is infinite)" placeholder="Takes any number (0 is infinite)"
@ -522,22 +533,22 @@ const RawReleaseTags = () => {
} }
/> />
<Components.Layout> <FilterLayout>
<Input.SwitchGroup <SwitchGroup
name="use_regex_release_tags" name="use_regex_release_tags"
label="Use Regex" label="Use Regex"
className="col-span-12 sm:col-span-6" className="col-span-12 sm:col-span-6"
/> />
</Components.Layout> </FilterLayout>
<Input.RegexField <RegexField
name="match_release_tags" name="match_release_tags"
label="Match release tags" label="Match release tags"
useRegex={values.use_regex_release_tags} useRegex={values.use_regex_release_tags}
columns={6} columns={6}
placeholder="eg. *mkv*,*foreign*" placeholder="eg. *mkv*,*foreign*"
/> />
<Input.RegexField <RegexField
name="except_release_tags" name="except_release_tags"
label="Except release tags" label="Except release tags"
useRegex={values.use_regex_release_tags} useRegex={values.use_regex_release_tags}

View file

@ -23,8 +23,7 @@ import { DeleteModal } from "@components/modals";
import { DocsLink } from "@components/ExternalLink"; import { DocsLink } from "@components/ExternalLink";
import { Checkbox } from "@components/Checkbox"; import { Checkbox } from "@components/Checkbox";
import { TitleSubtitle } from "@components/headings"; import { TitleSubtitle } from "@components/headings";
import { FilterHalfRow, FilterLayout, FilterPage, FilterSection } from "@screens/filters/sections/_components.tsx";
import * as FilterSection from "./_components";
export function External() { export function External() {
const { values } = useFormikContext<Filter>(); const { values } = useFormikContext<Filter>();
@ -197,13 +196,13 @@ function FilterExternalItem({ idx, external, initialEdit, remove, move }: Filter
text="Are you sure you want to remove this external filter? This action cannot be undone." text="Are you sure you want to remove this external filter? This action cannot be undone."
/> />
<FilterSection.Page gap="sm:gap-y-6"> <FilterPage gap="sm:gap-y-6">
<FilterSection.Section <FilterSection
title="External Filter" title="External Filter"
subtitle="Define the type of your filter and its name" subtitle="Define the type of your filter and its name"
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Select <Select
name={`external.${idx}.type`} name={`external.${idx}.type`}
label="Type" label="Type"
@ -211,13 +210,13 @@ function FilterExternalItem({ idx, external, initialEdit, remove, move }: Filter
options={ExternalFilterTypeOptions} options={ExternalFilterTypeOptions}
tooltip={<div><p>Select the type for this external filter.</p></div>} tooltip={<div><p>Select the type for this external filter.</p></div>}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<TextField name={`external.${idx}.name`} label="Name" /> <TextField name={`external.${idx}.name`} label="Name" />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
<TypeForm external={external} idx={idx} /> <TypeForm external={external} idx={idx} />
@ -238,7 +237,7 @@ function FilterExternalItem({ idx, external, initialEdit, remove, move }: Filter
Close Close
</button> </button>
</div> </div>
</FilterSection.Page> </FilterPage>
</div> </div>
)} )}
</li> </li>
@ -255,11 +254,11 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
switch (external.type) { switch (external.type) {
case "EXEC": { case "EXEC": {
return ( return (
<FilterSection.Section <FilterSection
title="Execute" title="Execute"
subtitle="Specify the executable, the argument and the expected exit status to run as a pre-filter" subtitle="Specify the executable, the argument and the expected exit status to run as a pre-filter"
> >
<FilterSection.Layout> <FilterLayout>
<TextAreaAutoResize <TextAreaAutoResize
name={`external.${idx}.exec_cmd`} name={`external.${idx}.exec_cmd`}
label="Path to Executable" label="Path to Executable"
@ -288,18 +287,18 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
placeholder="0" placeholder="0"
/> />
</div> </div>
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
); );
} }
case "WEBHOOK": { case "WEBHOOK": {
return ( return (
<> <>
<FilterSection.Section <FilterSection
title="Request" title="Request"
subtitle="Specify your request destination endpoint, headers and expected return status" subtitle="Specify your request destination endpoint, headers and expected return status"
> >
<FilterSection.Layout> <FilterLayout>
<TextField <TextField
name={`external.${idx}.webhook_host`} name={`external.${idx}.webhook_host`}
label="Endpoint" label="Endpoint"
@ -325,13 +324,13 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
label="Expected HTTP status code" label="Expected HTTP status code"
placeholder="200" placeholder="200"
/> />
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
<FilterSection.Section <FilterSection
title="Retry" title="Retry"
subtitle="Retry behavior on request failure" subtitle="Retry behavior on request failure"
> >
<FilterSection.Layout> <FilterLayout>
<TextField <TextField
name={`external.${idx}.webhook_retry_status`} name={`external.${idx}.webhook_retry_status`}
label="Retry http status code(s)" label="Retry http status code(s)"
@ -348,20 +347,20 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
label="Retry delay in seconds" label="Retry delay in seconds"
placeholder="1" placeholder="1"
/> />
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
<FilterSection.Section <FilterSection
title="Payload" title="Payload"
subtitle="Specify your JSON payload" subtitle="Specify your JSON payload"
> >
<FilterSection.Layout> <FilterLayout>
<TextAreaAutoResize <TextAreaAutoResize
name={`external.${idx}.webhook_data`} name={`external.${idx}.webhook_data`}
label="Data (json)" label="Data (json)"
placeholder={"Request data: { \"key\": \"value\" }"} placeholder={"Request data: { \"key\": \"value\" }"}
/> />
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
</> </>
); );
} }

View file

@ -9,13 +9,12 @@ import { downloadsPerUnitOptions } from "@domain/constants";
import { IndexersOptionsQueryOptions } from "@api/queries"; import { IndexersOptionsQueryOptions } from "@api/queries";
import { DocsLink } from "@components/ExternalLink"; import { DocsLink } from "@components/ExternalLink";
import { FilterLayout, FilterPage, FilterSection } from "./_components";
import * as Input from "@components/inputs"; import { IndexerMultiSelect, MultiSelectOption, NumberField, Select, SwitchGroup, TextField } from "@components/inputs";
import * as Components from "./_components";
const MapIndexer = (indexer: Indexer) => ( const MapIndexer = (indexer: Indexer) => (
{ label: indexer.name, value: indexer.id } as Input.MultiSelectOption { label: indexer.name, value: indexer.id } as MultiSelectOption
); );
export const General = () => { export const General = () => {
@ -25,23 +24,23 @@ export const General = () => {
// const indexerOptions = data?.map(MapIndexer) ?? []; // const indexerOptions = data?.map(MapIndexer) ?? [];
return ( return (
<Components.Page> <FilterPage>
<Components.Section> <FilterSection>
<Components.Layout> <FilterLayout>
<Input.TextField name="name" label="Filter name" columns={6} placeholder="eg. Filter 1" /> <TextField name="name" label="Filter name" columns={6} placeholder="eg. Filter 1" />
{/*{!isLoading && (*/} {/*{!isLoading && (*/}
<Input.IndexerMultiSelect name="indexers" options={indexerOptions} label="Indexers" columns={6} /> <IndexerMultiSelect name="indexers" options={indexerOptions} label="Indexers" columns={6} />
{/*)}*/} {/*)}*/}
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
<Components.Section <FilterSection
title="Rules" title="Rules"
subtitle="Specify rules on how torrents should be handled/selected." subtitle="Specify rules on how torrents should be handled/selected."
> >
<Components.Layout> <FilterLayout>
<Input.TextField <TextField
name="min_size" name="min_size"
label="Min size" label="Min size"
columns={6} columns={6}
@ -53,7 +52,7 @@ export const General = () => {
</div> </div>
} }
/> />
<Input.TextField <TextField
name="max_size" name="max_size"
label="Max size" label="Max size"
columns={6} columns={6}
@ -65,7 +64,7 @@ export const General = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="delay" name="delay"
label="Delay" label="Delay"
placeholder="Number of seconds to delay actions" placeholder="Number of seconds to delay actions"
@ -76,7 +75,7 @@ export const General = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="priority" name="priority"
label="Priority" label="Priority"
placeholder="Higher number = higher priority" placeholder="Higher number = higher priority"
@ -87,7 +86,7 @@ export const General = () => {
</div> </div>
} }
/> />
<Input.NumberField <NumberField
name="max_downloads" name="max_downloads"
label="Max downloads" label="Max downloads"
placeholder="Takes any number (0 is infinite)" placeholder="Takes any number (0 is infinite)"
@ -98,7 +97,7 @@ export const General = () => {
</div> </div>
} }
/> />
<Input.Select <Select
name="max_downloads_unit" name="max_downloads_unit"
label="Max downloads per" label="Max downloads per"
options={downloadsPerUnitOptions} options={downloadsPerUnitOptions}
@ -110,17 +109,17 @@ export const General = () => {
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
<Components.Layout> <FilterLayout>
<Input.SwitchGroup <SwitchGroup
name="enabled" name="enabled"
label="Enabled" label="Enabled"
description="Enable or disable this filter." description="Enable or disable this filter."
className="pb-2 col-span-12 sm:col-span-6" className="pb-2 col-span-12 sm:col-span-6"
/> />
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
</Components.Page> </FilterPage>
); );
}; };

View file

@ -8,14 +8,19 @@ import { TextAreaAutoResize } from "@components/inputs/input";
import { MultiSelect, SwitchGroup, TextField } from "@components/inputs"; import { MultiSelect, SwitchGroup, TextField } from "@components/inputs";
import * as CONSTS from "@domain/constants"; import * as CONSTS from "@domain/constants";
import * as Components from "./_components"; import {
FilterLayout,
FilterPage,
FilterSection,
FilterWideGridGapClass
} from "@screens/filters/sections/_components.tsx";
const SeasonsAndEpisodes = () => ( const SeasonsAndEpisodes = () => (
<Components.Section <FilterSection
title="Seasons, Episodes and Date" title="Seasons, Episodes and Date"
subtitle="Set season, episode, year, months and day match constraints." subtitle="Set season, episode, year, months and day match constraints."
> >
<Components.Layout> <FilterLayout>
<TextField <TextField
name="seasons" name="seasons"
label="Seasons" label="Seasons"
@ -84,16 +89,16 @@ const SeasonsAndEpisodes = () => (
description="Do not match episodes older than the last one matched." description="Do not match episodes older than the last one matched."
/> />
</div> </div>
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
); );
const Quality = () => ( const Quality = () => (
<Components.Section <FilterSection
title="Quality" title="Quality"
subtitle="Set resolution, source, codec and related match constraints." subtitle="Set resolution, source, codec and related match constraints."
> >
<Components.Layout gap={Components.WideGridGapClass}> <FilterLayout gap={FilterWideGridGapClass}>
<MultiSelect <MultiSelect
name="resolutions" name="resolutions"
options={CONSTS.RESOLUTION_OPTIONS} options={CONSTS.RESOLUTION_OPTIONS}
@ -118,9 +123,9 @@ const Quality = () => (
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
<Components.Layout gap={Components.WideGridGapClass}> <FilterLayout gap={FilterWideGridGapClass}>
<MultiSelect <MultiSelect
name="codecs" name="codecs"
options={CONSTS.CODECS_OPTIONS} options={CONSTS.CODECS_OPTIONS}
@ -145,9 +150,9 @@ const Quality = () => (
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
<Components.Layout gap={Components.WideGridGapClass}> <FilterLayout gap={FilterWideGridGapClass}>
<MultiSelect <MultiSelect
name="match_hdr" name="match_hdr"
options={CONSTS.HDR_OPTIONS} options={CONSTS.HDR_OPTIONS}
@ -172,9 +177,9 @@ const Quality = () => (
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
<Components.Layout gap={Components.WideGridGapClass}> <FilterLayout gap={FilterWideGridGapClass}>
<MultiSelect <MultiSelect
name="match_other" name="match_other"
options={CONSTS.OTHER_OPTIONS} options={CONSTS.OTHER_OPTIONS}
@ -199,14 +204,14 @@ const Quality = () => (
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
); );
export const MoviesTv = () => ( export const MoviesTv = () => (
<Components.Page> <FilterPage>
<Components.Section> <FilterSection>
<Components.Layout> <FilterLayout>
<TextAreaAutoResize <TextAreaAutoResize
name="shows" name="shows"
label="Movies / Shows" label="Movies / Shows"
@ -231,10 +236,10 @@ export const MoviesTv = () => (
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
<SeasonsAndEpisodes /> <SeasonsAndEpisodes />
<Quality /> <Quality />
</Components.Page> </FilterPage>
); );

View file

@ -6,19 +6,20 @@
import { useFormikContext } from "formik"; import { useFormikContext } from "formik";
import { DocsLink } from "@components/ExternalLink"; import { DocsLink } from "@components/ExternalLink";
import * as Input from "@components/inputs"; import { FilterLayout, FilterPage, FilterRow, FilterSection } from "./_components";
import { MultiSelect, NumberField, SwitchGroup, TextAreaAutoResize, TextField } from "@components/inputs";
import * as CONSTS from "@domain/constants"; import * as CONSTS from "@domain/constants";
import * as Components from "./_components";
export const Music = () => { export const Music = () => {
const { values } = useFormikContext<Filter>(); const { values } = useFormikContext<Filter>();
return ( return (
<Components.Page> <FilterPage>
<Components.Section> <FilterSection>
<Components.Layout> <FilterLayout>
<Input.TextAreaAutoResize <TextAreaAutoResize
name="artists" name="artists"
label="Artists" label="Artists"
columns={6} columns={6}
@ -30,7 +31,7 @@ export const Music = () => {
</div> </div>
} }
/> />
<Input.TextAreaAutoResize <TextAreaAutoResize
name="albums" name="albums"
label="Albums" label="Albums"
columns={6} columns={6}
@ -42,15 +43,15 @@ export const Music = () => {
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
<Components.Section <FilterSection
title="Release details" title="Release details"
subtitle="Type (Album, Single, EP, etc.) and year of release (if announced)" subtitle="Type (Album, Single, EP, etc.) and year of release (if announced)"
> >
<Components.Layout> <FilterLayout>
<Input.MultiSelect <MultiSelect
name="match_release_types" name="match_release_types"
options={CONSTS.RELEASE_TYPE_MUSIC_OPTIONS} options={CONSTS.RELEASE_TYPE_MUSIC_OPTIONS}
label="Music Type" label="Music Type"
@ -62,7 +63,7 @@ export const Music = () => {
</div> </div>
} }
/> />
<Input.TextField <TextField
name="years" name="years"
label="Years" label="Years"
columns={6} columns={6}
@ -74,16 +75,16 @@ export const Music = () => {
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
<Components.Section <FilterSection
title="Quality" title="Quality"
subtitle="Format, source, log, etc." subtitle="Format, source, log, etc."
> >
<Components.Layout> <FilterLayout>
<Components.Layout> <FilterLayout>
<Input.MultiSelect <MultiSelect
name="formats" name="formats"
options={CONSTS.FORMATS_OPTIONS} options={CONSTS.FORMATS_OPTIONS}
label="Format" label="Format"
@ -96,7 +97,7 @@ export const Music = () => {
</div> </div>
} }
/> />
<Input.MultiSelect <MultiSelect
name="quality" name="quality"
options={CONSTS.QUALITY_MUSIC_OPTIONS} options={CONSTS.QUALITY_MUSIC_OPTIONS}
label="Quality" label="Quality"
@ -109,7 +110,7 @@ export const Music = () => {
</div> </div>
} }
/> />
<Input.MultiSelect <MultiSelect
name="media" name="media"
options={CONSTS.SOURCES_MUSIC_OPTIONS} options={CONSTS.SOURCES_MUSIC_OPTIONS}
label="Media" label="Media"
@ -122,31 +123,31 @@ export const Music = () => {
</div> </div>
} }
/> />
</Components.Layout> </FilterLayout>
<Components.Layout className="items-end sm:!gap-x-6"> <FilterLayout className="items-end sm:!gap-x-6">
<Components.Row className="sm:col-span-4"> <FilterRow className="sm:col-span-4">
<Input.SwitchGroup <SwitchGroup
name="cue" name="cue"
label="Cue" label="Cue"
description="Must include CUE info" description="Must include CUE info"
disabled={values.perfect_flac} disabled={values.perfect_flac}
className="sm:col-span-4" className="sm:col-span-4"
/> />
</Components.Row> </FilterRow>
<Components.Row className="sm:col-span-4"> <FilterRow className="sm:col-span-4">
<Input.SwitchGroup <SwitchGroup
name="log" name="log"
label="Log" label="Log"
description="Must include LOG info" description="Must include LOG info"
disabled={values.perfect_flac} disabled={values.perfect_flac}
className="sm:col-span-4" className="sm:col-span-4"
/> />
</Components.Row> </FilterRow>
<Components.Row className="sm:col-span-4"> <FilterRow className="sm:col-span-4">
<Input.NumberField <NumberField
name="log_score" name="log_score"
label="Log score" label="Log score"
placeholder="eg. 100" placeholder="eg. 100"
@ -160,9 +161,9 @@ export const Music = () => {
</div> </div>
} }
/> />
</Components.Row> </FilterRow>
</Components.Layout> </FilterLayout>
</Components.Layout> </FilterLayout>
<div className="col-span-12 flex items-center justify-center"> <div className="col-span-12 flex items-center justify-center">
<span className="border-b border-gray-150 dark:border-gray-750 w-full" /> <span className="border-b border-gray-150 dark:border-gray-750 w-full" />
@ -172,8 +173,8 @@ export const Music = () => {
<span className="border-b border-gray-150 dark:border-gray-750 w-full" /> <span className="border-b border-gray-150 dark:border-gray-750 w-full" />
</div> </div>
<Components.Layout className="sm:!gap-x-6"> <FilterLayout className="sm:!gap-x-6">
<Input.SwitchGroup <SwitchGroup
name="perfect_flac" name="perfect_flac"
label="Perfect FLAC" label="Perfect FLAC"
description="Override all options about quality, source, format, and cue/log/log score." description="Override all options about quality, source, format, and cue/log/log score."
@ -189,8 +190,8 @@ export const Music = () => {
<span className="col-span-12 sm:col-span-6 self-center ml-0 text-center sm:text-left text-sm text-gray-500 dark:text-gray-425 underline underline-offset-2"> <span className="col-span-12 sm:col-span-6 self-center ml-0 text-center sm:text-left text-sm text-gray-500 dark:text-gray-425 underline underline-offset-2">
This is what you want in 90% of cases (instead of options above). This is what you want in 90% of cases (instead of options above).
</span> </span>
</Components.Layout> </FilterLayout>
</Components.Section> </FilterSection>
</Components.Page> </FilterPage>
); );
} }

View file

@ -24,41 +24,41 @@ type OwningComponent = {
const VerticalGap = "gap-y-6 sm:gap-y-4"; const VerticalGap = "gap-y-6 sm:gap-y-4";
export const NormalGridGapClass = `gap-x-0.5 sm:gap-x-3 ${VerticalGap}`; export const FilterNormalGridGapClass = `gap-x-0.5 sm:gap-x-3 ${VerticalGap}`;
export const TightGridGapClass = `gap-x-0.5 sm:gap-x-1.5 ${VerticalGap}`; export const FilterTightGridGapClass = `gap-x-0.5 sm:gap-x-1.5 ${VerticalGap}`;
export const WideGridGapClass = `gap-x-0.5 sm:gap-x-6 ${VerticalGap}`; export const FilterWideGridGapClass = `gap-x-0.5 sm:gap-x-6 ${VerticalGap}`;
export const LayoutClass = "grid grid-cols-12 col-span-12"; export const FilterLayoutClass = "grid grid-cols-12 col-span-12";
export const Layout = ({ export const FilterLayout = ({
children, children,
className = "", className = "",
gap = NormalGridGapClass gap = FilterNormalGridGapClass
}: OwningComponent) => ( }: OwningComponent) => (
<div className={classNames(className, LayoutClass, gap)}>{children}</div> <div className={classNames(className, FilterLayoutClass, gap)}>{children}</div>
); );
export const Row = ({ export const FilterRow = ({
children, children,
className = "", className = "",
gap = NormalGridGapClass gap = FilterNormalGridGapClass
}: OwningComponent) => ( }: OwningComponent) => (
<div className={classNames(className, gap, "col-span-12")}>{children}</div> <div className={classNames(className, gap, "col-span-12")}>{children}</div>
); );
export const HalfRow = ({ export const FilterHalfRow = ({
children, children,
className = "", className = "",
gap = NormalGridGapClass gap = FilterNormalGridGapClass
}: OwningComponent) => ( }: OwningComponent) => (
<div className={classNames(className, gap, "col-span-12 sm:col-span-6")}>{children}</div> <div className={classNames(className, gap, "col-span-12 sm:col-span-6")}>{children}</div>
); );
export const Section = ({ export const FilterSection = ({
title, title,
subtitle, subtitle,
children, children,
gap = NormalGridGapClass gap = FilterNormalGridGapClass
}: FilterSectionProps) => ( }: FilterSectionProps) => (
<div <div
className={classNames( className={classNames(
@ -79,7 +79,7 @@ type FilterPageProps = {
children: React.ReactNode; children: React.ReactNode;
}; };
export const Page = ({ export const FilterPage = ({
gap = VerticalGap, gap = VerticalGap,
children children
}: FilterPageProps) => ( }: FilterPageProps) => (
@ -108,7 +108,7 @@ export const CollapsibleSection = ({
children, children,
defaultOpen = false, defaultOpen = false,
noBottomBorder = false, noBottomBorder = false,
childClassName = NormalGridGapClass childClassName = FilterNormalGridGapClass
}: CollapsibleSectionProps) => { }: CollapsibleSectionProps) => {
const [isOpen, toggleOpen] = useToggle(defaultOpen); const [isOpen, toggleOpen] = useToggle(defaultOpen);

View file

@ -3,77 +3,75 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
import * as Input from "@components/inputs"; import { CollapsibleSection, FilterHalfRow, FilterLayout, FilterSection } from "../_components";
import { DownloadClientSelect, NumberField, SwitchGroup, TextAreaAutoResize, TextField } from "@components/inputs";
import { CollapsibleSection } from "../_components";
import * as FilterSection from "../_components";
export const Deluge = ({ idx, action, clients }: ClientActionProps) => ( export const Deluge = ({ idx, action, clients }: ClientActionProps) => (
<> <>
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.TextField <TextField
name={`actions.${idx}.label`} name={`actions.${idx}.label`}
label="Label" label="Label"
columns={6} columns={6}
placeholder="eg. label1 (must exist in Deluge to work)" placeholder="eg. label1 (must exist in Deluge to work)"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.save_path`} name={`actions.${idx}.save_path`}
label="Save path" label="Save path"
placeholder="eg. /full/path/to/download_folder" placeholder="eg. /full/path/to/download_folder"
/> />
</FilterSection.Layout> </FilterLayout>
<FilterSection.Layout className="pb-6"> <FilterLayout className="pb-6">
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.paused`} name={`actions.${idx}.paused`}
label="Add paused" label="Add paused"
description="Add torrent as paused" description="Add torrent as paused"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.skip_hash_check`} name={`actions.${idx}.skip_hash_check`}
label="Skip hash check" label="Skip hash check"
description="Add torrent and skip hash check" description="Add torrent and skip hash check"
tooltip={<div>This will only work on Deluge v2.</div>} tooltip={<div>This will only work on Deluge v2.</div>}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
<CollapsibleSection <CollapsibleSection
noBottomBorder noBottomBorder
title="Limits" title="Limits"
subtitle="Configure your speed/ratio/seed time limits" subtitle="Configure your speed/ratio/seed time limits"
> >
<Input.NumberField <NumberField
name={`actions.${idx}.limit_download_speed`} name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KB/s)" label="Limit download speed (KB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.limit_upload_speed`} name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KB/s)" label="Limit upload speed (KB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</CollapsibleSection> </CollapsibleSection>
</FilterSection.Section> </FilterSection>
</> </>
); );

View file

@ -3,29 +3,27 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
import * as Input from "@components/inputs"; import { CollapsibleSection, FilterHalfRow, FilterLayout, FilterSection } from "../_components";
import { DownloadClientSelect, NumberField, TextAreaAutoResize, TextField } from "@components/inputs";
import { CollapsibleSection } from "../_components";
import * as FilterSection from "../_components";
export const Porla = ({ idx, action, clients }: ClientActionProps) => ( export const Porla = ({ idx, action, clients }: ClientActionProps) => (
<> <>
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.TextField <TextField
name={`actions.${idx}.label`} name={`actions.${idx}.label`}
label="Preset" label="Preset"
placeholder="eg. default" placeholder="eg. default"
@ -33,10 +31,10 @@ export const Porla = ({ idx, action, clients }: ClientActionProps) => (
<div>A case-sensitive preset name as configured in Porla.</div> <div>A case-sensitive preset name as configured in Porla.</div>
} }
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.save_path`} name={`actions.${idx}.save_path`}
label="Save path" label="Save path"
placeholder="eg. /full/path/to/torrent/data" placeholder="eg. /full/path/to/torrent/data"
@ -48,21 +46,21 @@ export const Porla = ({ idx, action, clients }: ClientActionProps) => (
title="Limits" title="Limits"
subtitle="Configure your speed/ratio/seed time limits" subtitle="Configure your speed/ratio/seed time limits"
> >
<FilterSection.HalfRow> <FilterHalfRow>
<Input.NumberField <NumberField
name={`actions.${idx}.limit_download_speed`} name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KiB/s)" label="Limit download speed (KiB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.NumberField <NumberField
name={`actions.${idx}.limit_upload_speed`} name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KiB/s)" label="Limit upload speed (KiB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</CollapsibleSection> </CollapsibleSection>
</FilterSection.Section> </FilterSection>
</> </>
); );

View file

@ -7,31 +7,37 @@ import { Link } from "@tanstack/react-router";
import { DocsLink } from "@components/ExternalLink"; import { DocsLink } from "@components/ExternalLink";
import { ActionContentLayoutOptions, ActionPriorityOptions } from "@domain/constants"; import { ActionContentLayoutOptions, ActionPriorityOptions } from "@domain/constants";
import * as Input from "@components/inputs";
import { CollapsibleSection } from "../_components"; import { CollapsibleSection, FilterHalfRow, FilterLayout, FilterSection, FilterWideGridGapClass } from "../_components";
import * as FilterSection from "../_components"; import {
DownloadClientSelect,
NumberField,
Select,
SwitchGroup,
TextAreaAutoResize,
TextField
} from "@components/inputs";
export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => ( export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => (
<> <>
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
<FilterSection.Layout> <FilterLayout>
<Input.TextField <TextField
name={`actions.${idx}.category`} name={`actions.${idx}.category`}
label="Category" label="Category"
columns={6} columns={6}
@ -44,7 +50,7 @@ export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => (
} }
/> />
<Input.TextField <TextField
name={`actions.${idx}.tags`} name={`actions.${idx}.tags`}
label="Tags" label="Tags"
columns={6} columns={6}
@ -56,10 +62,10 @@ export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => (
</div> </div>
} }
/> />
</FilterSection.Layout> </FilterLayout>
<FilterSection.Layout className="pb-6"> <FilterLayout className="pb-6">
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.save_path`} name={`actions.${idx}.save_path`}
label="Save path" label="Save path"
placeholder="eg. /full/path/to/download_folder" placeholder="eg. /full/path/to/download_folder"
@ -72,15 +78,15 @@ export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => (
</div> </div>
} }
/> />
</FilterSection.Layout> </FilterLayout>
<CollapsibleSection <CollapsibleSection
title="Rules" title="Rules"
subtitle="Configure your torrent client rules" subtitle="Configure your torrent client rules"
childClassName={FilterSection.WideGridGapClass} childClassName={FilterWideGridGapClass}
> >
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.ignore_rules`} name={`actions.${idx}.ignore_rules`}
label="Ignore existing client rules" label="Ignore existing client rules"
description={ description={
@ -90,14 +96,14 @@ export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => (
} }
className="py-2 pb-4" className="py-2 pb-4"
/> />
<Input.Select <Select
name={`actions.${idx}.content_layout`} name={`actions.${idx}.content_layout`}
label="Content Layout" label="Content Layout"
optionDefaultText="Select content layout" optionDefaultText="Select content layout"
options={ActionContentLayoutOptions} options={ActionContentLayoutOptions}
className="py-2 pb-4" className="py-2 pb-4"
/> />
<Input.Select <Select
name={`actions.${idx}.priority`} name={`actions.${idx}.priority`}
label="Priority" label="Priority"
optionDefaultText="Disabled" optionDefaultText="Disabled"
@ -108,94 +114,94 @@ export const QBittorrent = ({ idx, action, clients }: ClientActionProps) => (
</div> </div>
} }
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.paused`} name={`actions.${idx}.paused`}
label="Add paused" label="Add paused"
description="Add torrent as paused" description="Add torrent as paused"
/> />
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.skip_hash_check`} name={`actions.${idx}.skip_hash_check`}
label="Skip hash check" label="Skip hash check"
description="Add torrent and skip hash check" description="Add torrent and skip hash check"
className="pt-4 sm:pt-4" className="pt-4 sm:pt-4"
/> />
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.first_last_piece_prio`} name={`actions.${idx}.first_last_piece_prio`}
label="Download first and last pieces first" label="Download first and last pieces first"
description="Add torrent and download first and last pieces first" description="Add torrent and download first and last pieces first"
className="pt-6 sm:pt-10" className="pt-6 sm:pt-10"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
title="Limits" title="Limits"
subtitle="Configure your speed/ratio/seed time limits" subtitle="Configure your speed/ratio/seed time limits"
> >
<FilterSection.Layout> <FilterLayout>
<Input.NumberField <NumberField
name={`actions.${idx}.limit_download_speed`} name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KiB/s)" label="Limit download speed (KiB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.limit_upload_speed`} name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KiB/s)" label="Limit upload speed (KiB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</FilterSection.Layout> </FilterLayout>
<FilterSection.Layout> <FilterLayout>
<Input.NumberField <NumberField
name={`actions.${idx}.limit_ratio`} name={`actions.${idx}.limit_ratio`}
label="Ratio limit" label="Ratio limit"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
step={0.25} step={0.25}
isDecimal isDecimal
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.limit_seed_time`} name={`actions.${idx}.limit_seed_time`}
label="Seed time limit (minutes)" label="Seed time limit (minutes)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</FilterSection.Layout> </FilterLayout>
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
noBottomBorder noBottomBorder
title="Announce" title="Announce"
subtitle="Set number of reannounces (if needed), delete after Y announce failures, etc." subtitle="Set number of reannounces (if needed), delete after Y announce failures, etc."
childClassName={FilterSection.WideGridGapClass} childClassName={FilterWideGridGapClass}
> >
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.reannounce_skip`} name={`actions.${idx}.reannounce_skip`}
label="Skip reannounce" label="Skip reannounce"
description="If reannounce is not needed, skip it completely" description="If reannounce is not needed, skip it completely"
className="pt-2 pb-4" className="pt-2 pb-4"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.reannounce_interval`} name={`actions.${idx}.reannounce_interval`}
label="Reannounce interval. Run every X seconds" label="Reannounce interval. Run every X seconds"
placeholder="7 is default and recommended" placeholder="7 is default and recommended"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.reannounce_delete`} name={`actions.${idx}.reannounce_delete`}
label="Delete stalled" label="Delete stalled"
description="Delete stalled torrents after Y attempts" description="Delete stalled torrents after Y attempts"
className="pt-2 pb-4" className="pt-2 pb-4"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.reannounce_max_attempts`} name={`actions.${idx}.reannounce_max_attempts`}
label="Run reannounce Y times" label="Run reannounce Y times"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</CollapsibleSection> </CollapsibleSection>
</FilterSection.Section> </FilterSection>
</> </>
); );

View file

@ -4,59 +4,59 @@
*/ */
import { ActionRtorrentRenameOptions } from "@domain/constants"; import { ActionRtorrentRenameOptions } from "@domain/constants";
import * as Input from "@components/inputs"; import { FilterHalfRow, FilterLayout, FilterSection } from "@screens/filters/sections/_components.tsx";
import { DownloadClientSelect, Select, SwitchGroup, TextAreaAutoResize, TextField } from "@components/inputs";
import * as FilterSection from "../_components";
export const RTorrent = ({ idx, action, clients }: ClientActionProps) => ( export const RTorrent = ({ idx, action, clients }: ClientActionProps) => (
<> <>
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.TextField <TextField
name={`actions.${idx}.label`} name={`actions.${idx}.label`}
label="Label" label="Label"
columns={6} columns={6}
placeholder="eg. label1,label2" placeholder="eg. label1,label2"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.save_path`} name={`actions.${idx}.save_path`}
label="Save path" label="Save path"
placeholder="eg. /full/path/to/download_folder" placeholder="eg. /full/path/to/download_folder"
/> />
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.paused`} name={`actions.${idx}.paused`}
label="Add paused" label="Add paused"
description="Add torrent as paused" description="Add torrent as paused"
className="pt-2 pb-4" className="pt-2 pb-4"
/> />
<Input.Select <Select
name={`actions.${idx}.content_layout`} name={`actions.${idx}.content_layout`}
label="Do not add torrent name to path" label="Do not add torrent name to path"
optionDefaultText="No" optionDefaultText="No"
options={ActionRtorrentRenameOptions} options={ActionRtorrentRenameOptions}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
</> </>
); );

View file

@ -3,119 +3,117 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
import * as Input from "@components/inputs"; import { CollapsibleSection, FilterHalfRow, FilterLayout, FilterSection, FilterWideGridGapClass } from "../_components";
import { DownloadClientSelect, NumberField, SwitchGroup, TextAreaAutoResize, TextField } from "@components/inputs";
import { CollapsibleSection } from "../_components";
import * as FilterSection from "../_components";
export const Transmission = ({ idx, action, clients }: ClientActionProps) => ( export const Transmission = ({ idx, action, clients }: ClientActionProps) => (
<> <>
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.TextField <TextField
name={`actions.${idx}.label`} name={`actions.${idx}.label`}
label="Torrent Label" label="Torrent Label"
columns={6} columns={6}
placeholder="eg. label1" placeholder="eg. label1"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.save_path`} name={`actions.${idx}.save_path`}
label="Save path" label="Save path"
columns={6} columns={6}
placeholder="eg. /full/path/to/download_folder" placeholder="eg. /full/path/to/download_folder"
/> />
<FilterSection.Layout className="pb-6"> <FilterLayout className="pb-6">
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.paused`} name={`actions.${idx}.paused`}
label="Add paused" label="Add paused"
description="Add torrent as paused" description="Add torrent as paused"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
<CollapsibleSection <CollapsibleSection
title="Limits" title="Limits"
subtitle="Configure your speed/ratio/seed time limits" subtitle="Configure your speed/ratio/seed time limits"
> >
<FilterSection.Layout> <FilterLayout>
<Input.NumberField <NumberField
name={`actions.${idx}.limit_download_speed`} name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KiB/s)" label="Limit download speed (KiB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.limit_upload_speed`} name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KiB/s)" label="Limit upload speed (KiB/s)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</FilterSection.Layout> </FilterLayout>
<FilterSection.Layout> <FilterLayout>
<Input.NumberField <NumberField
name={`actions.${idx}.limit_ratio`} name={`actions.${idx}.limit_ratio`}
label="Ratio limit" label="Ratio limit"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
step={0.25} step={0.25}
isDecimal isDecimal
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.limit_seed_time`} name={`actions.${idx}.limit_seed_time`}
label="Seed time limit (minutes)" label="Seed time limit (minutes)"
placeholder="Takes any number (0 is no limit)" placeholder="Takes any number (0 is no limit)"
/> />
</FilterSection.Layout> </FilterLayout>
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
noBottomBorder noBottomBorder
title="Announce" title="Announce"
subtitle="Set number of reannounces (if needed), delete after Y announce failures, etc." subtitle="Set number of reannounces (if needed), delete after Y announce failures, etc."
childClassName={FilterSection.WideGridGapClass} childClassName={FilterWideGridGapClass}
> >
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.reannounce_skip`} name={`actions.${idx}.reannounce_skip`}
label="Skip reannounce" label="Skip reannounce"
description="If reannounce is not needed, skip it completely" description="If reannounce is not needed, skip it completely"
className="pt-2 pb-4" className="pt-2 pb-4"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.reannounce_interval`} name={`actions.${idx}.reannounce_interval`}
label="Reannounce interval. Run every X seconds" label="Reannounce interval. Run every X seconds"
placeholder="7 is default and recommended" placeholder="7 is default and recommended"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.SwitchGroup <SwitchGroup
name={`actions.${idx}.reannounce_delete`} name={`actions.${idx}.reannounce_delete`}
label="Delete stalled" label="Delete stalled"
description="Delete stalled torrents after Y attempts" description="Delete stalled torrents after Y attempts"
className="pt-2 pb-4" className="pt-2 pb-4"
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.reannounce_max_attempts`} name={`actions.${idx}.reannounce_max_attempts`}
label="Run reannounce Y times" label="Run reannounce Y times"
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</CollapsibleSection> </CollapsibleSection>
</FilterSection.Section> </FilterSection>
</> </>
); );

View file

@ -4,36 +4,36 @@
*/ */
import { WarningAlert } from "@components/alerts"; import { WarningAlert } from "@components/alerts";
import * as Input from "@components/inputs"; import { FilterHalfRow, FilterLayout, FilterSection } from "@screens/filters/sections/_components.tsx";
import { DownloadClientSelect, NumberField, TextAreaAutoResize, TextField } from "@components/inputs";
import * as FilterSection from "../_components";
export const SABnzbd = ({ idx, action, clients }: ClientActionProps) => ( export const SABnzbd = ({ idx, action, clients }: ClientActionProps) => (
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.TextField <TextField
name={`actions.${idx}.category`} name={`actions.${idx}.category`}
label="Category" label="Category"
columns={6} columns={6}
placeholder="eg. category" placeholder="eg. category"
tooltip={<p>Category must exist already.</p>} tooltip={<p>Category must exist already.</p>}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
); );
export const Test = () => ( export const Test = () => (
@ -46,49 +46,49 @@ export const Test = () => (
); );
export const Exec = ({ idx }: ClientActionProps) => ( export const Exec = ({ idx }: ClientActionProps) => (
<FilterSection.Section <FilterSection
title="Exec Arguments" title="Exec Arguments"
subtitle="Specify the executable and its arguments to be executed upon filter match. Use an absolute path." subtitle="Specify the executable and its arguments to be executed upon filter match. Use an absolute path."
> >
<FilterSection.Layout> <FilterLayout>
<Input.TextField <TextField
name={`actions.${idx}.exec_cmd`} name={`actions.${idx}.exec_cmd`}
label="Path to Executable" label="Path to Executable"
placeholder="Path to program eg. /bin/test" placeholder="Path to program eg. /bin/test"
/> />
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.exec_args`} name={`actions.${idx}.exec_args`}
label="Arguments" label="Arguments"
placeholder="Arguments eg. --test" placeholder="Arguments eg. --test"
/> />
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
); );
export const WatchFolder = ({ idx }: ClientActionProps) => ( export const WatchFolder = ({ idx }: ClientActionProps) => (
<FilterSection.Section <FilterSection
title="Watch Folder Arguments" title="Watch Folder Arguments"
subtitle="Point to where autobrr should save the files it fetches. Use an absolute path." subtitle="Point to where autobrr should save the files it fetches. Use an absolute path."
> >
<FilterSection.Layout> <FilterLayout>
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.watch_folder`} name={`actions.${idx}.watch_folder`}
label="Watch directory" label="Watch directory"
placeholder="Watch directory eg. /home/user/rwatch" placeholder="Watch directory eg. /home/user/rwatch"
/> />
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
); );
export const WebHook = ({ idx }: ClientActionProps) => ( export const WebHook = ({ idx }: ClientActionProps) => (
<FilterSection.Section <FilterSection
title="Webhook Arguments" title="Webhook Arguments"
subtitle="Specify the payload to be sent to the desired endpoint upon filter match." subtitle="Specify the payload to be sent to the desired endpoint upon filter match."
> >
<FilterSection.Layout> <FilterLayout>
<Input.TextField <TextField
name={`actions.${idx}.webhook_host`} name={`actions.${idx}.webhook_host`}
label="Endpoint" label="Endpoint"
columns={6} columns={6}
@ -97,34 +97,34 @@ export const WebHook = ({ idx }: ClientActionProps) => (
<p>URL or IP to your API. Pass params and set API tokens etc.</p> <p>URL or IP to your API. Pass params and set API tokens etc.</p>
} }
/> />
</FilterSection.Layout> </FilterLayout>
<Input.TextAreaAutoResize <TextAreaAutoResize
name={`actions.${idx}.webhook_data`} name={`actions.${idx}.webhook_data`}
label="Payload (json)" label="Payload (json)"
placeholder={"Request data: { \"key\": \"value\" }"} placeholder={"Request data: { \"key\": \"value\" }"}
/> />
</FilterSection.Section> </FilterSection>
); );
export const Arr = ({ idx, action, clients }: ClientActionProps) => ( export const Arr = ({ idx, action, clients }: ClientActionProps) => (
<FilterSection.Section <FilterSection
title="Instance" title="Instance"
subtitle={ subtitle={
<>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</> <>Select the <span className="font-bold">specific instance</span> which you want to handle this release filter.</>
} }
> >
<FilterSection.Layout> <FilterLayout>
<FilterSection.HalfRow> <FilterHalfRow>
<Input.DownloadClientSelect <DownloadClientSelect
name={`actions.${idx}.client_id`} name={`actions.${idx}.client_id`}
action={action} action={action}
clients={clients} clients={clients}
/> />
</FilterSection.HalfRow> </FilterHalfRow>
<FilterSection.HalfRow> <FilterHalfRow>
<div className=""> <div className="">
<Input.TextField <TextField
name={`actions.${idx}.external_download_client`} name={`actions.${idx}.external_download_client`}
label="Override download client name for arr" label="Override download client name for arr"
tooltip={ tooltip={
@ -134,7 +134,7 @@ export const Arr = ({ idx, action, clients }: ClientActionProps) => (
</p> </p>
} }
/> />
<Input.NumberField <NumberField
name={`actions.${idx}.external_download_client_id`} name={`actions.${idx}.external_download_client_id`}
label="Override download client id for arr DEPRECATED" label="Override download client id for arr DEPRECATED"
className="mt-4" className="mt-4"
@ -146,7 +146,7 @@ export const Arr = ({ idx, action, clients }: ClientActionProps) => (
} }
/> />
</div> </div>
</FilterSection.HalfRow> </FilterHalfRow>
</FilterSection.Layout> </FilterLayout>
</FilterSection.Section> </FilterSection>
); );

View file

@ -19,13 +19,11 @@ import {
import { ReleasesRoute } from "@app/routes"; import { ReleasesRoute } from "@app/routes";
import { ReleasesListQueryOptions } from "@api/queries"; import { ReleasesListQueryOptions } from "@api/queries";
import { RandomLinuxIsos } from "@utils"; import { RandomLinuxIsos } from "@utils";
import { RingResizeSpinner, SortDownIcon, SortIcon, SortUpIcon } from "@components/Icons";
import * as Icons from "@components/Icons";
import { RingResizeSpinner } from "@components/Icons";
import * as DataTable from "@components/data-table";
import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./ReleaseFilters"; import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./ReleaseFilters";
import { EmptyListState } from "@components/emptystates"; import { EmptyListState } from "@components/emptystates";
import { TableButton, TablePageButton } from "@components/data-table/Buttons.tsx";
import { AgeCell, IndexerCell, LinksCell, NameCell, ReleaseStatusCell } from "@components/data-table";
type TableState = { type TableState = {
queryPageIndex: number; queryPageIndex: number;
@ -100,30 +98,30 @@ export const ReleaseTable = () => {
{ {
Header: "Age", Header: "Age",
accessor: "timestamp", accessor: "timestamp",
Cell: DataTable.AgeCell Cell: AgeCell
}, },
{ {
Header: "Release", Header: "Release",
accessor: "name", accessor: "name",
Cell: DataTable.NameCell, Cell: NameCell,
Filter: SearchColumnFilter Filter: SearchColumnFilter
}, },
{ {
Header: "Links", Header: "Links",
accessor: (row) => ({ download_url: row.download_url, info_url: row.info_url }), accessor: (row) => ({ download_url: row.download_url, info_url: row.info_url }),
id: "links", id: "links",
Cell: DataTable.LinksCell Cell: LinksCell
}, },
{ {
Header: "Actions", Header: "Actions",
accessor: "action_status", accessor: "action_status",
Cell: DataTable.ReleaseStatusCell, Cell: ReleaseStatusCell,
Filter: PushStatusSelectColumnFilter Filter: PushStatusSelectColumnFilter
}, },
{ {
Header: "Indexer", Header: "Indexer",
accessor: "indexer.identifier", accessor: "indexer.identifier",
Cell: DataTable.IndexerCell, Cell: IndexerCell,
Filter: IndexerSelectColumnFilter, Filter: IndexerSelectColumnFilter,
filter: "equal" filter: "equal"
} }
@ -303,12 +301,12 @@ export const ReleaseTable = () => {
<span> <span>
{column.isSorted ? ( {column.isSorted ? (
column.isSortedDesc ? ( column.isSortedDesc ? (
<Icons.SortDownIcon className="w-4 h-4 text-gray-400"/> <SortDownIcon className="w-4 h-4 text-gray-400"/>
) : ( ) : (
<Icons.SortUpIcon className="w-4 h-4 text-gray-400"/> <SortUpIcon className="w-4 h-4 text-gray-400"/>
) )
) : ( ) : (
<Icons.SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100"/> <SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100"/>
)} )}
</span> </span>
</div> </div>
@ -350,8 +348,8 @@ export const ReleaseTable = () => {
{/* Pagination */} {/* Pagination */}
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700"> <div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
<div className="flex justify-between flex-1 sm:hidden"> <div className="flex justify-between flex-1 sm:hidden">
<DataTable.Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</DataTable.Button> <TableButton onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</TableButton>
<DataTable.Button onClick={() => nextPage()} disabled={!canNextPage}>Next</DataTable.Button> <TableButton onClick={() => nextPage()} disabled={!canNextPage}>Next</TableButton>
</div> </div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> <div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div className="flex items-baseline gap-x-2"> <div className="flex items-baseline gap-x-2">
@ -378,37 +376,37 @@ export const ReleaseTable = () => {
</div> </div>
<div> <div>
<nav className="inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination"> <nav className="inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
<DataTable.PageButton <TablePageButton
className="rounded-l-md" className="rounded-l-md"
onClick={() => gotoPage(0)} onClick={() => gotoPage(0)}
disabled={!canPreviousPage} disabled={!canPreviousPage}
> >
<span className="sr-only">First</span> <span className="sr-only">First</span>
<ChevronDoubleLeftIcon className="w-4 h-4" aria-hidden="true"/> <ChevronDoubleLeftIcon className="w-4 h-4" aria-hidden="true"/>
</DataTable.PageButton> </TablePageButton>
<DataTable.PageButton <TablePageButton
className="pl-1 pr-2" className="pl-1 pr-2"
onClick={() => previousPage()} onClick={() => previousPage()}
disabled={!canPreviousPage} disabled={!canPreviousPage}
> >
<ChevronLeftIcon className="w-4 h-4 mr-1" aria-hidden="true"/> <ChevronLeftIcon className="w-4 h-4 mr-1" aria-hidden="true"/>
<span>Prev</span> <span>Prev</span>
</DataTable.PageButton> </TablePageButton>
<DataTable.PageButton <TablePageButton
className="pl-2 pr-1" className="pl-2 pr-1"
onClick={() => nextPage()} onClick={() => nextPage()}
disabled={!canNextPage}> disabled={!canNextPage}>
<span>Next</span> <span>Next</span>
<ChevronRightIcon className="w-4 h-4 ml-1" aria-hidden="true"/> <ChevronRightIcon className="w-4 h-4 ml-1" aria-hidden="true"/>
</DataTable.PageButton> </TablePageButton>
<DataTable.PageButton <TablePageButton
className="rounded-r-md" className="rounded-r-md"
onClick={() => gotoPage(pageCount - 1)} onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage} disabled={!canNextPage}
> >
<ChevronDoubleRightIcon className="w-4 h-4" aria-hidden="true"/> <ChevronDoubleRightIcon className="w-4 h-4" aria-hidden="true"/>
<span className="sr-only">Last</span> <span className="sr-only">Last</span>
</DataTable.PageButton> </TablePageButton>
</nav> </nav>
</div> </div>
</div> </div>