mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(filters): regex validation for supported fields (#773)
* initial commit * minor visual improvements * lock validation to golang flavor * made a new field called regexfield reverted old textfield to be up2date with develop * added Formik Validation * removed yup as its not needed * improvements * refactor: simplify and make more generic --------- Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
parent
59f41be3ef
commit
8a0c6e8180
5 changed files with 130 additions and 18 deletions
|
@ -1,5 +1,5 @@
|
||||||
export { ErrorField, CheckboxField } from "./common";
|
export { ErrorField, CheckboxField } from "./common";
|
||||||
export { TextField, NumberField, PasswordField } from "./input";
|
export { TextField, NumberField, PasswordField, RegexField } from "./input";
|
||||||
export { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, SwitchGroupWideRed, TextFieldWide } from "./input_wide";
|
export { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, SwitchGroupWideRed, TextFieldWide } from "./input_wide";
|
||||||
export { RadioFieldsetWide } from "./radio";
|
export { RadioFieldsetWide } from "./radio";
|
||||||
export { MultiSelect, Select, SelectWide, DownloadClientSelect, IndexerMultiSelect } from "./select";
|
export { MultiSelect, Select, SelectWide, DownloadClientSelect, IndexerMultiSelect } from "./select";
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { Field, FieldProps } from "formik";
|
import { Field, FieldProps } from "formik";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
|
||||||
import { useToggle } from "../../hooks/hooks";
|
import { useToggle } from "../../hooks/hooks";
|
||||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||||
|
|
||||||
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||||
|
|
||||||
interface TextFieldProps {
|
interface TextFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
columns?: COL_WIDTHS;
|
columns?: COL_WIDTHS;
|
||||||
autoComplete?: string;
|
autoComplete?: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tooltip?: JSX.Element;
|
tooltip?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextField = ({
|
export const TextField = ({
|
||||||
|
@ -75,6 +75,113 @@ export const TextField = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface RegexFieldProps {
|
||||||
|
name: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
columns?: COL_WIDTHS;
|
||||||
|
autoComplete?: string;
|
||||||
|
useRegex?: boolean;
|
||||||
|
hidden?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegexField = ({
|
||||||
|
name,
|
||||||
|
defaultValue,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
columns,
|
||||||
|
autoComplete,
|
||||||
|
useRegex,
|
||||||
|
hidden,
|
||||||
|
tooltip,
|
||||||
|
disabled
|
||||||
|
}: RegexFieldProps) => {
|
||||||
|
const golangRegex = /^((\\\*|\\\?|\\[^\s\\])+|\(\?i\))(\|((\\\*|\\\?|\\[^\s\\])+|\(\?i\)))*$/;
|
||||||
|
|
||||||
|
const validRegex = (pattern: string) => {
|
||||||
|
try {
|
||||||
|
new RegExp(golangRegex.source + pattern);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateRegexp = (val: string) => {
|
||||||
|
let error = "";
|
||||||
|
|
||||||
|
if (!validRegex(val)) {
|
||||||
|
error = "Invalid regex";
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
hidden ? "hidden" : "",
|
||||||
|
columns ? `col-span-${columns}` : "col-span-12"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label && (
|
||||||
|
<label
|
||||||
|
htmlFor={name}
|
||||||
|
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
|
||||||
|
>
|
||||||
|
<div className="flex">
|
||||||
|
{label}
|
||||||
|
{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<Field
|
||||||
|
name={name}
|
||||||
|
validate={useRegex && validateRegexp}
|
||||||
|
>
|
||||||
|
{({ field, meta }: FieldProps) => (
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
{...field}
|
||||||
|
name={name}
|
||||||
|
type="text"
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
autoComplete={autoComplete}
|
||||||
|
className={classNames(
|
||||||
|
meta.touched && meta.error
|
||||||
|
? "focus:ring-red-500 focus:border-red-500 border-red-500"
|
||||||
|
: "focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
|
||||||
|
disabled
|
||||||
|
? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed"
|
||||||
|
: "dark:bg-gray-800",
|
||||||
|
"mt-2 block w-full dark:text-gray-100 rounded-md"
|
||||||
|
)}
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
{useRegex && (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="flex float-right items-center">
|
||||||
|
{meta.touched && !meta.error ? (
|
||||||
|
<CheckCircleIcon className="dark:bg-gray-800 bg-white h-8 w-8 mb-2.5 pl-1 text-green-500 right-2 absolute transform -translate-y-1/2" aria-hidden="true" style={{ overflow: "hidden" }} />
|
||||||
|
) : (
|
||||||
|
<XCircleIcon className="dark:bg-gray-800 bg-white h-8 w-8 mb-2.5 pl-1 text-red-500 right-2 absolute transform -translate-y-1/2" aria-hidden="true" style={{ overflow: "hidden" }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface TextAreaProps {
|
interface TextAreaProps {
|
||||||
name: string;
|
name: string;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const CustomTooltip = ({
|
||||||
const id = `${anchorId}-tooltip`;
|
const id = `${anchorId}-tooltip`;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<svg id={id} className="ml-1 w-4 h-4 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10"/></svg>
|
<svg id={id} className="z-10 ml-1 w-4 h-4 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10"/></svg>
|
||||||
<Tooltip style= {{ maxWidth: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", opacity: "1" }} delayShow={100} delayHide={150} place={place} anchorId={id} data-html={true} clickable={clickable}>
|
<Tooltip style= {{ maxWidth: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", opacity: "1" }} delayShow={100} delayHide={150} place={place} anchorId={id} data-html={true} clickable={clickable}>
|
||||||
{children}
|
{children}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -32,7 +32,8 @@ import {
|
||||||
NumberField,
|
NumberField,
|
||||||
Select,
|
Select,
|
||||||
SwitchGroup,
|
SwitchGroup,
|
||||||
TextField
|
TextField,
|
||||||
|
RegexField
|
||||||
} from "../../components/inputs";
|
} from "../../components/inputs";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "../../components/notifications/Toast";
|
||||||
|
@ -472,8 +473,9 @@ export function Advanced({ values }: AdvancedProps) {
|
||||||
<div className="grid col-span-12 gap-6">
|
<div className="grid col-span-12 gap-6">
|
||||||
<WarningAlert text="autobrr has extensive filtering built-in - only use this if nothing else works. If you need help please ask." />
|
<WarningAlert text="autobrr has extensive filtering built-in - only use this if nothing else works. If you need help please ask." />
|
||||||
|
|
||||||
<TextField name="match_releases" label="Match releases" columns={6} placeholder="eg. *some?movie*,*some?show*s01*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br/><br/><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} />
|
<RegexField name="match_releases" label="Match releases" useRegex={values.use_regex} columns={6} placeholder="eg. *some?movie*,*some?show*s01*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br/><br/><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} />
|
||||||
<TextField name="except_releases" label="Except releases" columns={6} placeholder="eg. *bad?movie*,*bad?show*s03*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br/><br/><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} />
|
<RegexField name="except_releases" label="Except releases" useRegex={values.use_regex} columns={6} placeholder="eg. *bad?movie*,*bad?show*s03*" tooltip={<div><p>This field has full regex support (Golang flavour).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a><br/><br/><p>Remember to tick <b>Use Regex</b> below if using more than <code>*</code> and <code>?</code>.</p></div>} />
|
||||||
|
|
||||||
{values.match_releases ? (
|
{values.match_releases ? (
|
||||||
<WarningAlert
|
<WarningAlert
|
||||||
alert="Ask yourself:"
|
alert="Ask yourself:"
|
||||||
|
@ -498,7 +500,6 @@ export function Advanced({ values }: AdvancedProps) {
|
||||||
</div>
|
</div>
|
||||||
</CollapsableSection>
|
</CollapsableSection>
|
||||||
|
|
||||||
|
|
||||||
<CollapsableSection defaultOpen={true} title="Groups" subtitle="Match only certain groups and/or ignore other groups.">
|
<CollapsableSection defaultOpen={true} title="Groups" subtitle="Match only certain groups and/or ignore other groups.">
|
||||||
<TextField name="match_release_groups" label="Match release groups" columns={6} placeholder="eg. group1,group2" tooltip={<div><p>Comma separated list of release groups to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
<TextField name="match_release_groups" label="Match release groups" columns={6} placeholder="eg. group1,group2" tooltip={<div><p>Comma separated list of release groups to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
||||||
<TextField name="except_release_groups" label="Except release groups" columns={6} placeholder="eg. badgroup1,badgroup2" tooltip={<div><p>Comma separated list of release groups to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
<TextField name="except_release_groups" label="Except release groups" columns={6} placeholder="eg. badgroup1,badgroup2" tooltip={<div><p>Comma separated list of release groups to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
||||||
|
@ -531,8 +532,9 @@ export function Advanced({ values }: AdvancedProps) {
|
||||||
<div className="grid col-span-12 gap-6">
|
<div className="grid col-span-12 gap-6">
|
||||||
<WarningAlert text="These might not be what you think they are. For advanced users who know how things are parsed." />
|
<WarningAlert text="These might not be what you think they are. For advanced users who know how things are parsed." />
|
||||||
|
|
||||||
<TextField name="match_release_tags" label="Match release tags" columns={6} placeholder="eg. *mkv*,*foreign*" />
|
<RegexField name="match_release_tags" label="Match release tags" useRegex={values.use_regex_release_tags} columns={6} placeholder="eg. *mkv*,*foreign*" />
|
||||||
<TextField name="except_release_tags" label="Except release tags" columns={6} placeholder="eg. *mkv*,*foreign*" />
|
<RegexField name="except_release_tags" label="Except release tags" useRegex={values.use_regex_release_tags} columns={6} placeholder="eg. *mkv*,*foreign*" />
|
||||||
|
|
||||||
<div className="col-span-6">
|
<div className="col-span-6">
|
||||||
<SwitchGroup name="use_regex_release_tags" label="Use Regex" />
|
<SwitchGroup name="use_regex_release_tags" label="Use Regex" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,9 @@ module.exports = {
|
||||||
colors: {
|
colors: {
|
||||||
gray: colors.zinc,
|
gray: colors.zinc,
|
||||||
},
|
},
|
||||||
|
margin: { // for the checkmarks used for regex validation in Filters/Advanced
|
||||||
|
"2.5": "0.625rem", // 10px, between mb-2 (8px) and mb-3 (12px)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue