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;
}
export const Button = ({ children, className, disabled, onClick }: ButtonProps) => (
export const TableButton = ({ children, className, disabled, onClick }: ButtonProps) => (
<button
type="button"
className={classNames(
@ -27,7 +27,7 @@ export const Button = ({ children, className, disabled, onClick }: ButtonProps)
</button>
);
export const PageButton = ({ children, className, disabled, onClick }: ButtonProps) => (
export const TablePageButton = ({ children, className, disabled, onClick }: ButtonProps) => (
<button
type="button"
className={classNames(

View file

@ -3,5 +3,5 @@
* 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";

View file

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

View file

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

View file

@ -7,14 +7,25 @@ import { useFormikContext } from "formik";
import { DocsLink } from "@components/ExternalLink";
import { WarningAlert } from "@components/alerts";
import * as Input from "@components/inputs";
import * as CONSTS from "@domain/constants";
import { CollapsibleSection } from "./_components";
import * as Components from "./_components";
import {
CollapsibleSection,
FilterHalfRow,
FilterLayout,
FilterLayoutClass,
FilterTightGridGapClass
} from "./_components";
import { classNames } from "@utils";
import * as CONSTS from "@domain/constants";
import {
MultiSelect, NumberField, RegexField,
RegexTextAreaField,
Select,
SwitchGroup,
TextAreaAutoResize,
TextField
} from "@components/inputs";
// type ValueConsumer = {
// values: FormikValues;
// };
@ -29,15 +40,15 @@ const Releases = () => {
title="Release Names"
subtitle="Match only certain release names and/or ignore other release names."
>
<Components.Layout>
<Components.HalfRow>
<Input.SwitchGroup name="use_regex" label="Use Regex" className="pt-2" />
</Components.HalfRow>
</Components.Layout>
<FilterLayout>
<FilterHalfRow>
<SwitchGroup name="use_regex" label="Use Regex" className="pt-2" />
</FilterHalfRow>
</FilterLayout>
<Components.Layout>
<Components.HalfRow>
<Input.RegexTextAreaField
<FilterLayout>
<FilterHalfRow>
<RegexTextAreaField
name="match_releases"
label="Match releases"
useRegex={values.use_regex}
@ -53,10 +64,10 @@ const Releases = () => {
</div>
}
/>
</Components.HalfRow>
</FilterHalfRow>
<Components.HalfRow>
<Input.RegexTextAreaField
<FilterHalfRow>
<RegexTextAreaField
name="except_releases"
label="Except releases"
useRegex={values.use_regex}
@ -72,9 +83,9 @@ const Releases = () => {
</div>
}
/>
</Components.HalfRow>
</FilterHalfRow>
</Components.Layout>
</FilterLayout>
{values.match_releases ? (
<WarningAlert
@ -112,7 +123,7 @@ const Groups = () => {
title="Groups"
subtitle="Match only certain groups and/or ignore other groups."
>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="match_release_groups"
label="Match release groups"
columns={6}
@ -124,7 +135,7 @@ const Groups = () => {
</div>
}
/>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="except_release_groups"
label="Except release groups"
columns={6}
@ -150,7 +161,7 @@ const Categories = () => {
title="Categories"
subtitle="Match or exclude categories (if announced)"
>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="match_categories"
label="Match categories"
columns={6}
@ -162,7 +173,7 @@ const Categories = () => {
</div>
}
/>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="except_categories"
label="Except categories"
columns={6}
@ -188,8 +199,8 @@ const Tags = () => {
title="Tags"
subtitle="Match or exclude tags (if announced)"
>
<div className={classNames("sm:col-span-6", Components.LayoutClass, Components.TightGridGapClass)}>
<Input.TextAreaAutoResize
<div className={classNames("sm:col-span-6", FilterLayoutClass, FilterTightGridGapClass)}>
<TextAreaAutoResize
name="tags"
label="Match tags"
columns={8}
@ -201,7 +212,7 @@ const Tags = () => {
</div>
}
/>
<Input.Select
<Select
name="tags_match_logic"
label="Match logic"
columns={4}
@ -215,8 +226,8 @@ const Tags = () => {
}
/>
</div>
<div className={classNames("sm:col-span-6", Components.LayoutClass, Components.TightGridGapClass)}>
<Input.TextAreaAutoResize
<div className={classNames("sm:col-span-6", FilterLayoutClass, FilterTightGridGapClass)}>
<TextAreaAutoResize
name="except_tags"
label="Except tags"
columns={8}
@ -228,7 +239,7 @@ const Tags = () => {
</div>
}
/>
<Input.Select
<Select
name="except_tags_match_logic"
label="Except logic"
columns={4}
@ -256,7 +267,7 @@ const Uploaders = () => {
title="Uploaders"
subtitle="Match or ignore uploaders (if announced)"
>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="match_uploaders"
label="Match uploaders"
columns={6}
@ -268,7 +279,7 @@ const Uploaders = () => {
</div>
}
/>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="except_uploaders"
label="Except uploaders"
columns={6}
@ -295,13 +306,13 @@ const Language = () => {
title="Language"
subtitle="Match or ignore languages (if announced)"
>
<Input.MultiSelect
<MultiSelect
name="match_language"
options={CONSTS.LANGUAGE_OPTIONS}
label="Match Language"
columns={6}
/>
<Input.MultiSelect
<MultiSelect
name="except_language"
options={CONSTS.LANGUAGE_OPTIONS}
label="Except Language"
@ -321,13 +332,13 @@ const Origins = () => {
title="Origins"
subtitle="Match Internals, Scene, P2P, etc. (if announced)"
>
<Input.MultiSelect
<MultiSelect
name="origins"
options={CONSTS.ORIGIN_OPTIONS}
label="Match Origins"
columns={6}
/>
<Input.MultiSelect
<MultiSelect
name="except_origins"
options={CONSTS.ORIGIN_OPTIONS}
label="Except Origins"
@ -347,7 +358,7 @@ const Freeleech = () => {
title="Freeleech"
subtitle="Match based off freeleech (if announced)"
>
<Input.TextField
<TextField
name="freeleech_percent"
label="Freeleech percent"
disabled={values.freeleech}
@ -368,8 +379,8 @@ const Freeleech = () => {
columns={6}
placeholder="eg. 50,75-100"
/>
<Components.HalfRow>
<Input.SwitchGroup
<FilterHalfRow>
<SwitchGroup
name="freeleech"
label="Freeleech"
className="py-0"
@ -389,7 +400,7 @@ const Freeleech = () => {
</div>
}
/>
</Components.HalfRow>
</FilterHalfRow>
</CollapsibleSection>
);
}
@ -414,15 +425,15 @@ const FeedSpecific = () => {
<>These options are <span className="font-bold">only</span> for Feeds such as RSS, Torznab and Newznab</>
}
>
<Components.Layout>
<Input.SwitchGroup
<FilterLayout>
<SwitchGroup
name="use_regex_description"
label="Use Regex"
className="col-span-12 sm:col-span-6"
/>
</Components.Layout>
</FilterLayout>
<Input.RegexTextAreaField
<RegexTextAreaField
name="match_description"
label="Match description"
useRegex={values.use_regex_description}
@ -438,7 +449,7 @@ const FeedSpecific = () => {
</div>
}
/>
<Input.RegexTextAreaField
<RegexTextAreaField
name="except_description"
label="Except description"
useRegex={values.use_regex_description}
@ -454,7 +465,7 @@ const FeedSpecific = () => {
</div>
}
/>
<Input.NumberField
<NumberField
name="min_seeders"
label="Min Seeders"
placeholder="Takes any number (0 is infinite)"
@ -465,7 +476,7 @@ const FeedSpecific = () => {
</div>
}
/>
<Input.NumberField
<NumberField
name="max_seeders"
label="Max Seeders"
placeholder="Takes any number (0 is infinite)"
@ -476,7 +487,7 @@ const FeedSpecific = () => {
</div>
}
/>
<Input.NumberField
<NumberField
name="min_leechers"
label="Min Leechers"
placeholder="Takes any number (0 is infinite)"
@ -487,7 +498,7 @@ const FeedSpecific = () => {
</div>
}
/>
<Input.NumberField
<NumberField
name="max_leechers"
label="Max Leechers"
placeholder="Takes any number (0 is infinite)"
@ -522,22 +533,22 @@ const RawReleaseTags = () => {
}
/>
<Components.Layout>
<Input.SwitchGroup
<FilterLayout>
<SwitchGroup
name="use_regex_release_tags"
label="Use Regex"
className="col-span-12 sm:col-span-6"
/>
</Components.Layout>
</FilterLayout>
<Input.RegexField
<RegexField
name="match_release_tags"
label="Match release tags"
useRegex={values.use_regex_release_tags}
columns={6}
placeholder="eg. *mkv*,*foreign*"
/>
<Input.RegexField
<RegexField
name="except_release_tags"
label="Except 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 { Checkbox } from "@components/Checkbox";
import { TitleSubtitle } from "@components/headings";
import * as FilterSection from "./_components";
import { FilterHalfRow, FilterLayout, FilterPage, FilterSection } from "@screens/filters/sections/_components.tsx";
export function External() {
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."
/>
<FilterSection.Page gap="sm:gap-y-6">
<FilterSection.Section
<FilterPage gap="sm:gap-y-6">
<FilterSection
title="External Filter"
subtitle="Define the type of your filter and its name"
>
<FilterSection.Layout>
<FilterSection.HalfRow>
<FilterLayout>
<FilterHalfRow>
<Select
name={`external.${idx}.type`}
label="Type"
@ -211,13 +210,13 @@ function FilterExternalItem({ idx, external, initialEdit, remove, move }: Filter
options={ExternalFilterTypeOptions}
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" />
</FilterSection.HalfRow>
</FilterSection.Layout>
</FilterSection.Section>
</FilterHalfRow>
</FilterLayout>
</FilterSection>
<TypeForm external={external} idx={idx} />
@ -238,7 +237,7 @@ function FilterExternalItem({ idx, external, initialEdit, remove, move }: Filter
Close
</button>
</div>
</FilterSection.Page>
</FilterPage>
</div>
)}
</li>
@ -255,11 +254,11 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
switch (external.type) {
case "EXEC": {
return (
<FilterSection.Section
<FilterSection
title="Execute"
subtitle="Specify the executable, the argument and the expected exit status to run as a pre-filter"
>
<FilterSection.Layout>
<FilterLayout>
<TextAreaAutoResize
name={`external.${idx}.exec_cmd`}
label="Path to Executable"
@ -288,18 +287,18 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
placeholder="0"
/>
</div>
</FilterSection.Layout>
</FilterSection.Section>
</FilterLayout>
</FilterSection>
);
}
case "WEBHOOK": {
return (
<>
<FilterSection.Section
<FilterSection
title="Request"
subtitle="Specify your request destination endpoint, headers and expected return status"
>
<FilterSection.Layout>
<FilterLayout>
<TextField
name={`external.${idx}.webhook_host`}
label="Endpoint"
@ -325,13 +324,13 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
label="Expected HTTP status code"
placeholder="200"
/>
</FilterSection.Layout>
</FilterSection.Section>
<FilterSection.Section
</FilterLayout>
</FilterSection>
<FilterSection
title="Retry"
subtitle="Retry behavior on request failure"
>
<FilterSection.Layout>
<FilterLayout>
<TextField
name={`external.${idx}.webhook_retry_status`}
label="Retry http status code(s)"
@ -348,20 +347,20 @@ const TypeForm = ({ external, idx }: TypeFormProps) => {
label="Retry delay in seconds"
placeholder="1"
/>
</FilterSection.Layout>
</FilterSection.Section>
<FilterSection.Section
</FilterLayout>
</FilterSection>
<FilterSection
title="Payload"
subtitle="Specify your JSON payload"
>
<FilterSection.Layout>
<FilterLayout>
<TextAreaAutoResize
name={`external.${idx}.webhook_data`}
label="Data (json)"
placeholder={"Request data: { \"key\": \"value\" }"}
/>
</FilterSection.Layout>
</FilterSection.Section>
</FilterLayout>
</FilterSection>
</>
);
}

View file

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

View file

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

View file

@ -6,19 +6,20 @@
import { useFormikContext } from "formik";
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 Components from "./_components";
export const Music = () => {
const { values } = useFormikContext<Filter>();
return (
<Components.Page>
<Components.Section>
<Components.Layout>
<Input.TextAreaAutoResize
<FilterPage>
<FilterSection>
<FilterLayout>
<TextAreaAutoResize
name="artists"
label="Artists"
columns={6}
@ -30,7 +31,7 @@ export const Music = () => {
</div>
}
/>
<Input.TextAreaAutoResize
<TextAreaAutoResize
name="albums"
label="Albums"
columns={6}
@ -42,15 +43,15 @@ export const Music = () => {
</div>
}
/>
</Components.Layout>
</Components.Section>
</FilterLayout>
</FilterSection>
<Components.Section
<FilterSection
title="Release details"
subtitle="Type (Album, Single, EP, etc.) and year of release (if announced)"
>
<Components.Layout>
<Input.MultiSelect
<FilterLayout>
<MultiSelect
name="match_release_types"
options={CONSTS.RELEASE_TYPE_MUSIC_OPTIONS}
label="Music Type"
@ -62,7 +63,7 @@ export const Music = () => {
</div>
}
/>
<Input.TextField
<TextField
name="years"
label="Years"
columns={6}
@ -74,16 +75,16 @@ export const Music = () => {
</div>
}
/>
</Components.Layout>
</Components.Section>
</FilterLayout>
</FilterSection>
<Components.Section
<FilterSection
title="Quality"
subtitle="Format, source, log, etc."
>
<Components.Layout>
<Components.Layout>
<Input.MultiSelect
<FilterLayout>
<FilterLayout>
<MultiSelect
name="formats"
options={CONSTS.FORMATS_OPTIONS}
label="Format"
@ -96,7 +97,7 @@ export const Music = () => {
</div>
}
/>
<Input.MultiSelect
<MultiSelect
name="quality"
options={CONSTS.QUALITY_MUSIC_OPTIONS}
label="Quality"
@ -109,7 +110,7 @@ export const Music = () => {
</div>
}
/>
<Input.MultiSelect
<MultiSelect
name="media"
options={CONSTS.SOURCES_MUSIC_OPTIONS}
label="Media"
@ -122,31 +123,31 @@ export const Music = () => {
</div>
}
/>
</Components.Layout>
</FilterLayout>
<Components.Layout className="items-end sm:!gap-x-6">
<Components.Row className="sm:col-span-4">
<Input.SwitchGroup
<FilterLayout className="items-end sm:!gap-x-6">
<FilterRow className="sm:col-span-4">
<SwitchGroup
name="cue"
label="Cue"
description="Must include CUE info"
disabled={values.perfect_flac}
className="sm:col-span-4"
/>
</Components.Row>
</FilterRow>
<Components.Row className="sm:col-span-4">
<Input.SwitchGroup
<FilterRow className="sm:col-span-4">
<SwitchGroup
name="log"
label="Log"
description="Must include LOG info"
disabled={values.perfect_flac}
className="sm:col-span-4"
/>
</Components.Row>
</FilterRow>
<Components.Row className="sm:col-span-4">
<Input.NumberField
<FilterRow className="sm:col-span-4">
<NumberField
name="log_score"
label="Log score"
placeholder="eg. 100"
@ -160,9 +161,9 @@ export const Music = () => {
</div>
}
/>
</Components.Row>
</Components.Layout>
</Components.Layout>
</FilterRow>
</FilterLayout>
</FilterLayout>
<div className="col-span-12 flex items-center justify-center">
<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" />
</div>
<Components.Layout className="sm:!gap-x-6">
<Input.SwitchGroup
<FilterLayout className="sm:!gap-x-6">
<SwitchGroup
name="perfect_flac"
label="Perfect FLAC"
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">
This is what you want in 90% of cases (instead of options above).
</span>
</Components.Layout>
</Components.Section>
</Components.Page>
</FilterLayout>
</FilterSection>
</FilterPage>
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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