Filter criterion fixes (#4090)

* Reorder
* Remove PhashDuplicateCriterion
* Improve DurationInput
* Register abloop outside of player init function
* Remove none criterion
* Typing improvements
* Move makeCriteria to ListFilterModel
* Separate PathCriterionOption
* Add makeCriterion arg to StringCriterionOption
* Remove unused options args
* Add DurationCriterionOption
* Use createNumberCriterionOption
* Add StringBooleanCriterion
This commit is contained in:
DingDongSoLong4
2023-09-12 02:53:32 +02:00
committed by GitHub
parent 0d13eec9a2
commit 9f4d0af886
49 changed files with 676 additions and 767 deletions

View File

@@ -30,7 +30,7 @@ export const GalleryAddPanel: React.FC<IGalleryAddProps> = ({
// if galleries is already present, then we modify it, otherwise add // if galleries is already present, then we modify it, otherwise add
let galleryCriterion = filter.criteria.find((c) => { let galleryCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "galleries"; return c.criterionOption.type === "galleries";
}) as GalleriesCriterion; }) as GalleriesCriterion | undefined;
if ( if (
galleryCriterion && galleryCriterion &&

View File

@@ -33,7 +33,7 @@ export const GalleryImagesPanel: React.FC<IGalleryDetailsProps> = ({
// if galleries is already present, then we modify it, otherwise add // if galleries is already present, then we modify it, otherwise add
let galleryCriterion = filter.criteria.find((c) => { let galleryCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "galleries"; return c.criterionOption.type === "galleries";
}) as GalleriesCriterion; }) as GalleriesCriterion | undefined;
if ( if (
galleryCriterion && galleryCriterion &&

View File

@@ -12,7 +12,6 @@ import {
DateCriterion, DateCriterion,
TimestampCriterion, TimestampCriterion,
BooleanCriterion, BooleanCriterion,
PathCriterionOption,
} from "src/models/list-filter/criteria/criterion"; } from "src/models/list-filter/criteria/criterion";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { import {
@@ -47,6 +46,7 @@ import TagsFilter from "./Filters/TagsFilter";
import { PhashCriterion } from "src/models/list-filter/criteria/phash"; import { PhashCriterion } from "src/models/list-filter/criteria/phash";
import { PhashFilter } from "./Filters/PhashFilter"; import { PhashFilter } from "./Filters/PhashFilter";
import cx from "classnames"; import cx from "classnames";
import { PathCriterion } from "src/models/list-filter/criteria/path";
interface IGenericCriterionEditor { interface IGenericCriterionEditor {
criterion: Criterion<CriterionValue>; criterion: Criterion<CriterionValue>;
@@ -175,7 +175,7 @@ const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
); );
} }
} }
if (criterion.criterionOption instanceof PathCriterionOption) { if (criterion instanceof PathCriterion) {
return ( return (
<PathFilter criterion={criterion} onValueChanged={onValueChanged} /> <PathFilter criterion={criterion} onValueChanged={onValueChanged} />
); );

View File

@@ -14,7 +14,6 @@ import {
Criterion, Criterion,
CriterionOption, CriterionOption,
} from "src/models/list-filter/criteria/criterion"; } from "src/models/list-filter/criteria/criterion";
import { makeCriteria } from "src/models/list-filter/criteria/factory";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { ConfigurationContext } from "src/hooks/Config"; import { ConfigurationContext } from "src/hooks/Config";
import { ListFilterModel } from "src/models/list-filter/filter"; import { ListFilterModel } from "src/models/list-filter/filter";
@@ -243,17 +242,11 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
}, [currentFilter.mode]); }, [currentFilter.mode]);
const criterionOptions = useMemo(() => { const criterionOptions = useMemo(() => {
const filteredOptions = filterOptions.criterionOptions.filter((o) => { return [...filterOptions.criterionOptions].sort((a, b) => {
return o.type !== "none";
});
filteredOptions.sort((a, b) => {
return intl return intl
.formatMessage({ id: a.messageID }) .formatMessage({ id: a.messageID })
.localeCompare(intl.formatMessage({ id: b.messageID })); .localeCompare(intl.formatMessage({ id: b.messageID }));
}); });
return filteredOptions;
}, [intl, filterOptions.criterionOptions]); }, [intl, filterOptions.criterionOptions]);
const optionSelected = useCallback( const optionSelected = useCallback(
@@ -270,11 +263,11 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
if (existing) { if (existing) {
setCriterion(existing); setCriterion(existing);
} else { } else {
const newCriterion = makeCriteria(filter.mode, option.type); const newCriterion = filter.makeCriterion(option.type);
setCriterion(newCriterion); setCriterion(newCriterion);
} }
}, },
[filter.mode, criteria] [filter, criteria]
); );
const ui = (configuration?.ui ?? {}) as IUIConfig; const ui = (configuration?.ui ?? {}) as IUIConfig;

View File

@@ -17,67 +17,50 @@ export const DurationFilter: React.FC<IDurationFilterProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
function onChanged(valueAsNumber: number, property: "value" | "value2") { function onChanged(v: number | undefined, property: "value" | "value2") {
const { value } = criterion; const { value } = criterion;
value[property] = valueAsNumber; value[property] = v;
onValueChanged(value); onValueChanged(value);
} }
let equalsControl: JSX.Element | null = null; function renderTop() {
if ( let placeholder: string;
criterion.modifier === CriterionModifier.Equals ||
criterion.modifier === CriterionModifier.NotEquals
) {
equalsControl = (
<Form.Group>
<DurationInput
numericValue={criterion.value?.value}
onValueChange={(v: number) => onChanged(v, "value")}
placeholder={intl.formatMessage({ id: "criterion.value" })}
/>
</Form.Group>
);
}
let lowerControl: JSX.Element | null = null;
if ( if (
criterion.modifier === CriterionModifier.GreaterThan || criterion.modifier === CriterionModifier.GreaterThan ||
criterion.modifier === CriterionModifier.Between || criterion.modifier === CriterionModifier.Between ||
criterion.modifier === CriterionModifier.NotBetween criterion.modifier === CriterionModifier.NotBetween
) { ) {
lowerControl = ( placeholder = intl.formatMessage({ id: "criterion.greater_than" });
} else if (criterion.modifier === CriterionModifier.LessThan) {
placeholder = intl.formatMessage({ id: "criterion.less_than" });
} else {
placeholder = intl.formatMessage({ id: "criterion.value" });
}
return (
<Form.Group> <Form.Group>
<DurationInput <DurationInput
numericValue={criterion.value?.value} value={criterion.value?.value}
onValueChange={(v: number) => onChanged(v, "value")} setValue={(v) => onChanged(v, "value")}
placeholder={intl.formatMessage({ id: "criterion.greater_than" })} placeholder={placeholder}
/> />
</Form.Group> </Form.Group>
); );
} }
let upperControl: JSX.Element | null = null; function renderBottom() {
if ( if (
criterion.modifier === CriterionModifier.LessThan || criterion.modifier !== CriterionModifier.Between &&
criterion.modifier === CriterionModifier.Between || criterion.modifier !== CriterionModifier.NotBetween
criterion.modifier === CriterionModifier.NotBetween
) { ) {
upperControl = ( return;
}
return (
<Form.Group> <Form.Group>
<DurationInput <DurationInput
numericValue={ value={criterion.value?.value2}
criterion.modifier === CriterionModifier.LessThan setValue={(v) => onChanged(v, "value2")}
? criterion.value?.value
: criterion.value?.value2
}
onValueChange={(v: number) =>
onChanged(
v,
criterion.modifier === CriterionModifier.LessThan
? "value"
: "value2"
)
}
placeholder={intl.formatMessage({ id: "criterion.less_than" })} placeholder={intl.formatMessage({ id: "criterion.less_than" })}
/> />
</Form.Group> </Form.Group>
@@ -86,9 +69,8 @@ export const DurationFilter: React.FC<IDurationFilterProps> = ({
return ( return (
<> <>
{equalsControl} {renderTop()}
{lowerControl} {renderBottom()}
{upperControl}
</> </>
); );
}; };

View File

@@ -135,10 +135,10 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
} }
if (state.duration) { if (state.duration) {
formik.setFieldValue( const seconds = DurationUtils.stringToSeconds(state.duration);
"duration", if (seconds !== undefined) {
DurationUtils.stringToSeconds(state.duration) formik.setFieldValue("duration", seconds);
); }
} }
if (state.date) { if (state.date) {
@@ -402,10 +402,8 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
</Form.Label> </Form.Label>
<Col xs={fieldXS} xl={fieldXL}> <Col xs={fieldXS} xl={fieldXL}>
<DurationInput <DurationInput
numericValue={formik.values.duration ?? undefined} value={formik.values.duration ?? undefined}
onValueChange={(valueAsNumber) => { setValue={(v) => formik.setFieldValue("duration", v ?? null)}
formik.setFieldValue("duration", valueAsNumber ?? null);
}}
/> />
</Col> </Col>
</Form.Group> </Form.Group>

View File

@@ -18,7 +18,7 @@ export const MovieScenesPanel: React.FC<IMovieScenesPanel> = ({
// if movie is already present, then we modify it, otherwise add // if movie is already present, then we modify it, otherwise add
let movieCriterion = filter.criteria.find((c) => { let movieCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "movies"; return c.criterionOption.type === "movies";
}) as MoviesCriterion; }) as MoviesCriterion | undefined;
if ( if (
movieCriterion && movieCriterion &&

View File

@@ -8,7 +8,6 @@ import React, {
useState, useState,
} from "react"; } from "react";
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js"; import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
import abLoopPlugin from "videojs-abloop";
import useScript from "src/hooks/useScript"; import useScript from "src/hooks/useScript";
import "videojs-contrib-dash"; import "videojs-contrib-dash";
import "videojs-mobile-ui"; import "videojs-mobile-ui";
@@ -24,12 +23,6 @@ import "./big-buttons";
import "./track-activity"; import "./track-activity";
import "./vrmode"; import "./vrmode";
import cx from "classnames"; import cx from "classnames";
// @ts-ignore
import airplay from "@silvermine/videojs-airplay";
// @ts-ignore
import chromecast from "@silvermine/videojs-chromecast";
airplay(videojs);
chromecast(videojs);
import { import {
useSceneSaveActivity, useSceneSaveActivity,
useSceneIncrementPlayCount, useSceneIncrementPlayCount,
@@ -47,6 +40,17 @@ import { languageMap } from "src/utils/caption";
import { VIDEO_PLAYER_ID } from "./util"; import { VIDEO_PLAYER_ID } from "./util";
import { IUIConfig } from "src/core/config"; import { IUIConfig } from "src/core/config";
// @ts-ignore
import airplay from "@silvermine/videojs-airplay";
// @ts-ignore
import chromecast from "@silvermine/videojs-chromecast";
import abLoopPlugin from "videojs-abloop";
// register videojs plugins
airplay(videojs);
chromecast(videojs);
abLoopPlugin(window, videojs);
function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) { function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
function seekStep(step: number) { function seekStep(step: number) {
const time = player.currentTime() + step; const time = player.currentTime() + step;
@@ -378,8 +382,6 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
videoEl.classList.add("vjs-big-play-centered"); videoEl.classList.add("vjs-big-play-centered");
videoRef.current!.appendChild(videoEl); videoRef.current!.appendChild(videoEl);
abLoopPlugin(window, videojs);
const vjs = videojs(videoEl, options); const vjs = videojs(videoEl, options);
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */

View File

@@ -145,15 +145,14 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({
</Form.Label> </Form.Label>
<div className="col-sm-8 col-xl-12"> <div className="col-sm-8 col-xl-12">
<DurationInput <DurationInput
onValueChange={(s) => formik.setFieldValue("seconds", s)} value={formik.values.seconds ?? 0}
setValue={(v) => formik.setFieldValue("seconds", v ?? null)}
onReset={() => onReset={() =>
formik.setFieldValue( formik.setFieldValue(
"seconds", "seconds",
Math.round(getPlayerPosition() ?? 0) Math.round(getPlayerPosition() ?? 0)
) )
} }
numericValue={formik.values.seconds}
mandatory
/> />
</div> </div>
</div> </div>

View File

@@ -356,8 +356,8 @@ export const SettingsInterfacePanel: React.FC = () => {
onChange={(v) => saveInterface({ maximumLoopDuration: v })} onChange={(v) => saveInterface({ maximumLoopDuration: v })}
renderField={(value, setValue) => ( renderField={(value, setValue) => (
<DurationInput <DurationInput
numericValue={value} value={value}
onValueChange={(duration) => setValue(duration ?? 0)} setValue={(duration) => setValue(duration ?? 0)}
/> />
)} )}
renderValue={(v) => { renderValue={(v) => {

View File

@@ -43,17 +43,13 @@ export const SettingsServicesPanel: React.FC = () => {
} = React.useContext(SettingStateContext); } = React.useContext(SettingStateContext);
// undefined to hide dialog, true for enable, false for disable // undefined to hide dialog, true for enable, false for disable
const [enableDisable, setEnableDisable] = useState<boolean | undefined>( const [enableDisable, setEnableDisable] = useState<boolean>();
undefined
);
const [enableUntilRestart, setEnableUntilRestart] = useState<boolean>(false); const [enableUntilRestart, setEnableUntilRestart] = useState<boolean>(false);
const [enableDuration, setEnableDuration] = useState<number | undefined>( const [enableDuration, setEnableDuration] = useState<number>(0);
undefined
);
const [ipEntry, setIPEntry] = useState<string>(""); const [ipEntry, setIPEntry] = useState<string>("");
const [tempIP, setTempIP] = useState<string | undefined>(); const [tempIP, setTempIP] = useState<string>();
const { data: statusData, loading, refetch: statusRefetch } = useDLNAStatus(); const { data: statusData, loading, refetch: statusRefetch } = useDLNAStatus();
@@ -273,8 +269,8 @@ export const SettingsServicesPanel: React.FC = () => {
<Form.Group id="temp-enable-duration"> <Form.Group id="temp-enable-duration">
<DurationInput <DurationInput
numericValue={enableDuration ?? 0} value={enableDuration}
onValueChange={(v) => setEnableDuration(v ?? 0)} setValue={(v) => setEnableDuration(v ?? 0)}
disabled={enableUntilRestart} disabled={enableUntilRestart}
/> />
<Form.Text className="text-muted"> <Form.Text className="text-muted">
@@ -315,8 +311,8 @@ export const SettingsServicesPanel: React.FC = () => {
<Form.Group id="temp-enable-duration"> <Form.Group id="temp-enable-duration">
<DurationInput <DurationInput
numericValue={enableDuration ?? 0} value={enableDuration}
onValueChange={(v) => setEnableDuration(v ?? 0)} setValue={(v) => setEnableDuration(v ?? 0)}
disabled={enableUntilRestart} disabled={enableUntilRestart}
/> />
<Form.Text className="text-muted"> <Form.Text className="text-muted">

View File

@@ -3,67 +3,58 @@ import {
faChevronUp, faChevronUp,
faClock, faClock,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import React, { useState, useEffect } from "react"; import React, { useState } from "react";
import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap"; import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap";
import { Icon } from "./Icon"; import { Icon } from "./Icon";
import DurationUtils from "src/utils/duration"; import DurationUtils from "src/utils/duration";
interface IProps { interface IProps {
disabled?: boolean; disabled?: boolean;
numericValue: number | undefined; value: number | undefined;
mandatory?: boolean; setValue(value: number | undefined): void;
onValueChange(
valueAsNumber: number | undefined,
valueAsString?: string
): void;
onReset?(): void; onReset?(): void;
className?: string; className?: string;
placeholder?: string; placeholder?: string;
} }
export const DurationInput: React.FC<IProps> = (props: IProps) => { export const DurationInput: React.FC<IProps> = ({
const [value, setValue] = useState<string | undefined>( disabled,
props.numericValue !== undefined value,
? DurationUtils.secondsToString(props.numericValue) setValue,
: undefined onReset,
); className,
placeholder,
}) => {
const [tmpValue, setTmpValue] = useState<string>();
useEffect(() => { function onChange(e: React.ChangeEvent<HTMLInputElement>) {
if (props.numericValue !== undefined || props.mandatory) { setTmpValue(e.currentTarget.value);
setValue(DurationUtils.secondsToString(props.numericValue ?? 0)); }
} else {
setValue(undefined); function onBlur() {
if (tmpValue !== undefined) {
setValue(DurationUtils.stringToSeconds(tmpValue));
setTmpValue(undefined);
}
} }
}, [props.numericValue, props.mandatory]);
function increment() { function increment() {
if (value === undefined) { setTmpValue(undefined);
return; setValue((value ?? 0) + 1);
}
let seconds = DurationUtils.stringToSeconds(value);
seconds += 1;
props.onValueChange(seconds, DurationUtils.secondsToString(seconds));
} }
function decrement() { function decrement() {
if (value === undefined) { setTmpValue(undefined);
return; setValue((value ?? 0) - 1);
}
let seconds = DurationUtils.stringToSeconds(value);
seconds -= 1;
props.onValueChange(seconds, DurationUtils.secondsToString(seconds));
} }
function renderButtons() { function renderButtons() {
if (!props.disabled) { if (!disabled) {
return ( return (
<ButtonGroup vertical> <ButtonGroup vertical>
<Button <Button
variant="secondary" variant="secondary"
className="duration-button" className="duration-button"
disabled={props.disabled}
onClick={() => increment()} onClick={() => increment()}
> >
<Icon icon={faChevronUp} /> <Icon icon={faChevronUp} />
@@ -71,7 +62,6 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
<Button <Button
variant="secondary" variant="secondary"
className="duration-button" className="duration-button"
disabled={props.disabled}
onClick={() => decrement()} onClick={() => decrement()}
> >
<Icon icon={faChevronDown} /> <Icon icon={faChevronDown} />
@@ -81,46 +71,33 @@ export const DurationInput: React.FC<IProps> = (props: IProps) => {
} }
} }
function onReset() {
if (props.onReset) {
props.onReset();
}
}
function maybeRenderReset() { function maybeRenderReset() {
if (props.onReset) { if (onReset) {
return ( return (
<Button variant="secondary" onClick={onReset}> <Button variant="secondary" onClick={() => onReset()}>
<Icon icon={faClock} /> <Icon icon={faClock} />
</Button> </Button>
); );
} }
} }
let inputValue = "";
if (tmpValue !== undefined) {
inputValue = tmpValue;
} else if (value !== undefined) {
inputValue = DurationUtils.secondsToString(value);
}
return ( return (
<div className={`duration-input ${props.className}`}> <div className={`duration-input ${className}`}>
<InputGroup> <InputGroup>
<Form.Control <Form.Control
className="duration-control text-input" className="duration-control text-input"
disabled={props.disabled} disabled={disabled}
value={value} value={inputValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={onChange}
setValue(e.currentTarget.value) onBlur={onBlur}
} placeholder={placeholder ? `${placeholder} (hh:mm:ss)` : "hh:mm:ss"}
onBlur={() => {
if (props.mandatory || (value !== undefined && value !== "")) {
props.onValueChange(DurationUtils.stringToSeconds(value), value);
} else {
props.onValueChange(undefined);
}
}}
placeholder={
!props.disabled
? props.placeholder
? `${props.placeholder} (hh:mm:ss)`
: "hh:mm:ss"
: undefined
}
/> />
<InputGroup.Append> <InputGroup.Append>
{maybeRenderReset()} {maybeRenderReset()}

View File

@@ -18,7 +18,7 @@ export const StudioChildrenPanel: React.FC<IStudioChildrenPanel> = ({
// if studio is already present, then we modify it, otherwise add // if studio is already present, then we modify it, otherwise add
let parentStudioCriterion = filter.criteria.find((c) => { let parentStudioCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "parents"; return c.criterionOption.type === "parents";
}) as ParentStudiosCriterion; }) as ParentStudiosCriterion | undefined;
if ( if (
parentStudioCriterion && parentStudioCriterion &&

View File

@@ -21,7 +21,7 @@ export const TagMarkersPanel: React.FC<ITagMarkersPanel> = ({
// if tag is already present, then we modify it, otherwise add // if tag is already present, then we modify it, otherwise add
let tagCriterion = filter.criteria.find((c) => { let tagCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "tags"; return c.criterionOption.type === "tags";
}) as TagsCriterion; }) as TagsCriterion | undefined;
if ( if (
tagCriterion && tagCriterion &&

View File

@@ -16,7 +16,7 @@ export const usePerformerFilterHook = (
// if performers is already present, then we modify it, otherwise add // if performers is already present, then we modify it, otherwise add
let performerCriterion = filter.criteria.find((c) => { let performerCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "performers"; return c.criterionOption.type === "performers";
}) as PerformersCriterion; }) as PerformersCriterion | undefined;
if (performerCriterion) { if (performerCriterion) {
if ( if (

View File

@@ -12,7 +12,7 @@ export const useStudioFilterHook = (studio: GQL.StudioDataFragment) => {
// if studio is already present, then we modify it, otherwise add // if studio is already present, then we modify it, otherwise add
let studioCriterion = filter.criteria.find((c) => { let studioCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "studios"; return c.criterionOption.type === "studios";
}) as StudiosCriterion; }) as StudiosCriterion | undefined;
if (studioCriterion) { if (studioCriterion) {
// we should be showing studio only. Remove other values // we should be showing studio only. Remove other values

View File

@@ -17,7 +17,7 @@ export const useTagFilterHook = (tag: GQL.TagDataFragment) => {
// if tag is already present, then we modify it, otherwise add // if tag is already present, then we modify it, otherwise add
let tagCriterion = filter.criteria.find((c) => { let tagCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "tags"; return c.criterionOption.type === "tags";
}) as TagsCriterion; }) as TagsCriterion | undefined;
if (tagCriterion) { if (tagCriterion) {
if ( if (

View File

@@ -1,15 +1,12 @@
import { CriterionModifier } from "src/core/generated-graphql"; import { CriterionModifier } from "src/core/generated-graphql";
import { languageMap, valueToCode } from "src/utils/caption"; import { languageMap, valueToCode } from "src/utils/caption";
import { CriterionType } from "../types";
import { CriterionOption, StringCriterion } from "./criterion"; import { CriterionOption, StringCriterion } from "./criterion";
const languageStrings = Array.from(languageMap.values()); const languageStrings = Array.from(languageMap.values());
class CaptionsCriterionOptionType extends CriterionOption { export const CaptionsCriterionOption = new CriterionOption({
constructor(value: CriterionType) { messageID: "captions",
super({ type: "captions",
messageID: value,
type: value,
modifierOptions: [ modifierOptions: [
CriterionModifier.Includes, CriterionModifier.Includes,
CriterionModifier.Excludes, CriterionModifier.Excludes,
@@ -19,15 +16,13 @@ class CaptionsCriterionOptionType extends CriterionOption {
defaultModifier: CriterionModifier.Includes, defaultModifier: CriterionModifier.Includes,
options: languageStrings, options: languageStrings,
makeCriterion: () => new CaptionCriterion(), makeCriterion: () => new CaptionCriterion(),
}); });
}
}
export const CaptionsCriterionOption = new CaptionsCriterionOptionType(
"captions"
);
export class CaptionCriterion extends StringCriterion { export class CaptionCriterion extends StringCriterion {
constructor() {
super(CaptionsCriterionOption);
}
protected toCriterionInput() { protected toCriterionInput() {
const value = valueToCode(this.value) ?? ""; const value = valueToCode(this.value) ?? "";
@@ -36,8 +31,4 @@ export class CaptionCriterion extends StringCriterion {
modifier: this.modifier, modifier: this.modifier,
}; };
} }
constructor() {
super(CaptionsCriterionOption);
}
} }

View File

@@ -9,13 +9,13 @@ import { CriterionOption, MultiStringCriterion } from "./criterion";
export const CircumcisedCriterionOption = new CriterionOption({ export const CircumcisedCriterionOption = new CriterionOption({
messageID: "circumcised", messageID: "circumcised",
type: "circumcised", type: "circumcised",
options: circumcisedStrings,
modifierOptions: [ modifierOptions: [
CriterionModifier.Includes, CriterionModifier.Includes,
CriterionModifier.Excludes, CriterionModifier.Excludes,
CriterionModifier.IsNull, CriterionModifier.IsNull,
CriterionModifier.NotNull, CriterionModifier.NotNull,
], ],
options: circumcisedStrings,
makeCriterion: () => new CircumcisedCriterion(), makeCriterion: () => new CircumcisedCriterion(),
}); });

View File

@@ -1,20 +1,13 @@
import { IntlShape } from "react-intl"; import { IntlShape } from "react-intl";
import { CriterionModifier } from "src/core/generated-graphql"; import { CriterionModifier } from "src/core/generated-graphql";
import { getCountryByISO } from "src/utils/country"; import { getCountryByISO } from "src/utils/country";
import { import { StringCriterion, StringCriterionOption } from "./criterion";
CriterionOption,
StringCriterion,
StringCriterionOption,
} from "./criterion";
export const CountryCriterionOption = new CriterionOption({ export const CountryCriterionOption = new StringCriterionOption(
messageID: "country", "country",
type: "country", "country",
modifierOptions: StringCriterionOption.modifierOptions, () => new CountryCriterion()
defaultModifier: StringCriterionOption.defaultModifier, );
makeCriterion: () => new CountryCriterion(),
inputType: StringCriterionOption.inputType,
});
export class CountryCriterion extends StringCriterion { export class CountryCriterion extends StringCriterion {
constructor() { constructor() {

View File

@@ -1,13 +1,10 @@
/* eslint-disable consistent-return */
/* eslint @typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ /* eslint @typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
import { IntlShape } from "react-intl"; import { IntlShape } from "react-intl";
import { import {
CriterionModifier, CriterionModifier,
HierarchicalMultiCriterionInput, HierarchicalMultiCriterionInput,
IntCriterionInput, IntCriterionInput,
MultiCriterionInput, MultiCriterionInput,
PHashDuplicationCriterionInput,
DateCriterionInput, DateCriterionInput,
TimestampCriterionInput, TimestampCriterionInput,
ConfigDataFragment, ConfigDataFragment,
@@ -152,22 +149,18 @@ export abstract class Criterion<V extends CriterionValue> {
this.modifier = encodedCriterion.modifier; this.modifier = encodedCriterion.modifier;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any public apply(outputFilter: Record<string, unknown>) {
public apply(outputFilter: Record<string, any>) {
// eslint-disable-next-line no-param-reassign
outputFilter[this.criterionOption.type] = this.toCriterionInput(); outputFilter[this.criterionOption.type] = this.toCriterionInput();
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any protected toCriterionInput(): unknown {
protected toCriterionInput(): any {
return { return {
value: this.value, value: this.value,
modifier: this.modifier, modifier: this.modifier,
}; };
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any public toSavedFilter(outputFilter: Record<string, unknown>) {
public toSavedFilter(outputFilter: Record<string, any>) {
outputFilter[this.criterionOption.type] = { outputFilter[this.criterionOption.type] = {
value: this.value, value: this.value,
modifier: this.modifier, modifier: this.modifier,
@@ -226,274 +219,13 @@ export class CriterionOption {
} }
} }
export class StringCriterionOption extends CriterionOption {
public static readonly modifierOptions = [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.Includes,
CriterionModifier.Excludes,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
CriterionModifier.MatchesRegex,
CriterionModifier.NotMatchesRegex,
];
public static readonly defaultModifier = CriterionModifier.Equals;
public static readonly inputType = "text";
constructor(messageID: string, type: CriterionType, options?: Option[]) {
super({
messageID,
type,
modifierOptions: StringCriterionOption.modifierOptions,
defaultModifier: StringCriterionOption.defaultModifier,
options,
inputType: StringCriterionOption.inputType,
makeCriterion: () => new StringCriterion(this),
});
}
}
export function createStringCriterionOption(
type: CriterionType,
messageID?: string
) {
return new StringCriterionOption(messageID ?? type, type);
}
export class StringCriterion extends Criterion<string> {
constructor(type: CriterionOption) {
super(type, "");
}
protected getLabelValue(_intl: IntlShape) {
return this.value;
}
public isValid(): boolean {
return (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull ||
this.value.length > 0
);
}
}
export class MultiStringCriterion extends Criterion<string[]> {
constructor(type: CriterionOption) {
super(type, []);
}
protected getLabelValue(_intl: IntlShape) {
return this.value.join(", ");
}
public isValid(): boolean {
return (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull ||
this.value.length > 0
);
}
}
export class MandatoryStringCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType, options?: Option[]) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.Includes,
CriterionModifier.Excludes,
CriterionModifier.MatchesRegex,
CriterionModifier.NotMatchesRegex,
],
defaultModifier: CriterionModifier.Equals,
options,
inputType: "text",
makeCriterion: () => new StringCriterion(this),
});
}
}
export function createMandatoryStringCriterionOption(
value: CriterionType,
messageID?: string
) {
return new MandatoryStringCriterionOption(messageID ?? value, value);
}
export class PathCriterionOption extends StringCriterionOption {}
export function createPathCriterionOption(
type: CriterionType,
messageID?: string
) {
return new PathCriterionOption(messageID ?? type, type);
}
export class BooleanCriterionOption extends CriterionOption {
constructor(
messageID: string,
value: CriterionType,
makeCriterion?: () => Criterion<CriterionValue>
) {
super({
messageID,
type: value,
modifierOptions: [],
defaultModifier: CriterionModifier.Equals,
options: [true.toString(), false.toString()],
makeCriterion: makeCriterion
? makeCriterion
: () => new BooleanCriterion(this),
});
}
}
export class BooleanCriterion extends StringCriterion {
protected toCriterionInput(): boolean {
return this.value === "true";
}
public isValid() {
return this.value === "true" || this.value === "false";
}
}
export function createBooleanCriterionOption(
value: CriterionType,
messageID?: string
) {
return new BooleanCriterionOption(messageID ?? value, value);
}
export class NumberCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType, options?: Option[]) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.GreaterThan,
CriterionModifier.LessThan,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
CriterionModifier.Between,
CriterionModifier.NotBetween,
],
defaultModifier: CriterionModifier.Equals,
options,
inputType: "number",
makeCriterion: () => new NumberCriterion(this),
});
}
}
export class NullNumberCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.GreaterThan,
CriterionModifier.LessThan,
CriterionModifier.Between,
CriterionModifier.NotBetween,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
],
defaultModifier: CriterionModifier.Equals,
inputType: "number",
makeCriterion: () => new NumberCriterion(this),
});
}
}
export function createNumberCriterionOption(value: CriterionType) {
return new NumberCriterionOption(value, value);
}
export function createNullNumberCriterionOption(value: CriterionType) {
return new NullNumberCriterionOption(value, value);
}
export class NumberCriterion extends Criterion<INumberValue> {
public get value(): INumberValue {
return this._value;
}
public set value(newValue: number | INumberValue) {
// backwards compatibility - if this.value is a number, use that
if (typeof newValue !== "object") {
this._value = {
value: newValue,
value2: undefined,
};
} else {
this._value = newValue;
}
}
protected toCriterionInput(): IntCriterionInput {
return {
modifier: this.modifier,
value: this.value?.value ?? 0,
value2: this.value?.value2,
};
}
protected getLabelValue(_intl: IntlShape) {
const { value, value2 } = this.value;
if (
this.modifier === CriterionModifier.Between ||
this.modifier === CriterionModifier.NotBetween
) {
return `${value}, ${value2 ?? 0}`;
} else {
return `${value}`;
}
}
public isValid(): boolean {
if (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull
) {
return true;
}
const { value, value2 } = this.value;
if (value === undefined) {
return false;
}
if (
value2 === undefined &&
(this.modifier === CriterionModifier.Between ||
this.modifier === CriterionModifier.NotBetween)
) {
return false;
}
return true;
}
constructor(type: CriterionOption) {
super(type, { value: undefined, value2: undefined });
}
}
export class ILabeledIdCriterionOption extends CriterionOption { export class ILabeledIdCriterionOption extends CriterionOption {
constructor( constructor(
messageID: string, messageID: string,
value: CriterionType, value: CriterionType,
includeAll: boolean, includeAll: boolean,
inputType: InputType inputType: InputType,
makeCriterion?: () => Criterion<CriterionValue>
) { ) {
const modifierOptions = [ const modifierOptions = [
CriterionModifier.Includes, CriterionModifier.Includes,
@@ -513,8 +245,10 @@ export class ILabeledIdCriterionOption extends CriterionOption {
type: value, type: value,
modifierOptions, modifierOptions,
defaultModifier, defaultModifier,
makeCriterion: () => new ILabeledIdCriterion(this),
inputType, inputType,
makeCriterion: makeCriterion
? makeCriterion
: () => new ILabeledIdCriterion(this),
}); });
} }
} }
@@ -689,8 +423,231 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
} }
} }
export class MandatoryNumberCriterionOption extends CriterionOption { export class StringCriterionOption extends CriterionOption {
constructor(
messageID: string,
value: CriterionType,
makeCriterion?: () => Criterion<CriterionValue>
) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.Includes,
CriterionModifier.Excludes,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
CriterionModifier.MatchesRegex,
CriterionModifier.NotMatchesRegex,
],
defaultModifier: CriterionModifier.Equals,
inputType: "text",
makeCriterion: makeCriterion
? makeCriterion
: () => new StringCriterion(this),
});
}
}
export function createStringCriterionOption(
type: CriterionType,
messageID?: string
) {
return new StringCriterionOption(messageID ?? type, type);
}
export class MandatoryStringCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType) { constructor(messageID: string, value: CriterionType) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.Includes,
CriterionModifier.Excludes,
CriterionModifier.MatchesRegex,
CriterionModifier.NotMatchesRegex,
],
defaultModifier: CriterionModifier.Equals,
inputType: "text",
makeCriterion: () => new StringCriterion(this),
});
}
}
export function createMandatoryStringCriterionOption(
value: CriterionType,
messageID?: string
) {
return new MandatoryStringCriterionOption(messageID ?? value, value);
}
export class StringCriterion extends Criterion<string> {
constructor(type: CriterionOption) {
super(type, "");
}
protected getLabelValue(_intl: IntlShape) {
return this.value;
}
public isValid(): boolean {
return (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull ||
this.value.length > 0
);
}
}
export class MultiStringCriterion extends Criterion<string[]> {
constructor(type: CriterionOption) {
super(type, []);
}
protected getLabelValue(_intl: IntlShape) {
return this.value.join(", ");
}
public isValid(): boolean {
return (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull ||
this.value.length > 0
);
}
}
export class BooleanCriterionOption extends CriterionOption {
constructor(
messageID: string,
value: CriterionType,
makeCriterion?: () => Criterion<CriterionValue>
) {
super({
messageID,
type: value,
modifierOptions: [],
defaultModifier: CriterionModifier.Equals,
options: ["true", "false"],
makeCriterion: makeCriterion
? makeCriterion
: () => new BooleanCriterion(this),
});
}
}
export function createBooleanCriterionOption(
value: CriterionType,
messageID?: string
) {
return new BooleanCriterionOption(messageID ?? value, value);
}
export class BooleanCriterion extends StringCriterion {
protected toCriterionInput(): boolean {
return this.value === "true";
}
public isValid() {
return this.value === "true" || this.value === "false";
}
}
export class StringBooleanCriterionOption extends CriterionOption {
constructor(
messageID: string,
value: CriterionType,
makeCriterion?: () => Criterion<CriterionValue>
) {
super({
messageID,
type: value,
options: ["true", "false"],
makeCriterion: makeCriterion
? makeCriterion
: () => new StringBooleanCriterion(this),
});
}
}
export class StringBooleanCriterion extends StringCriterion {
protected toCriterionInput(): string {
return this.value;
}
public isValid() {
return this.value === "true" || this.value === "false";
}
}
export class NumberCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.GreaterThan,
CriterionModifier.LessThan,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
CriterionModifier.Between,
CriterionModifier.NotBetween,
],
defaultModifier: CriterionModifier.Equals,
inputType: "number",
makeCriterion: () => new NumberCriterion(this),
});
}
}
export function createNumberCriterionOption(
value: CriterionType,
messageID?: string
) {
return new NumberCriterionOption(messageID ?? value, value);
}
export class NullNumberCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.GreaterThan,
CriterionModifier.LessThan,
CriterionModifier.Between,
CriterionModifier.NotBetween,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
],
defaultModifier: CriterionModifier.Equals,
inputType: "number",
makeCriterion: () => new NumberCriterion(this),
});
}
}
export function createNullNumberCriterionOption(
value: CriterionType,
messageID?: string
) {
return new NullNumberCriterionOption(messageID ?? value, value);
}
export class MandatoryNumberCriterionOption extends CriterionOption {
constructor(
messageID: string,
value: CriterionType,
makeCriterion?: () => Criterion<CriterionValue>
) {
super({ super({
messageID, messageID,
type: value, type: value,
@@ -704,7 +661,9 @@ export class MandatoryNumberCriterionOption extends CriterionOption {
], ],
defaultModifier: CriterionModifier.Equals, defaultModifier: CriterionModifier.Equals,
inputType: "number", inputType: "number",
makeCriterion: () => new NumberCriterion(this), makeCriterion: makeCriterion
? makeCriterion
: () => new NumberCriterion(this),
}); });
} }
} }
@@ -716,6 +675,84 @@ export function createMandatoryNumberCriterionOption(
return new MandatoryNumberCriterionOption(messageID ?? value, value); return new MandatoryNumberCriterionOption(messageID ?? value, value);
} }
export class NumberCriterion extends Criterion<INumberValue> {
public get value(): INumberValue {
return this._value;
}
public set value(newValue: number | INumberValue) {
// backwards compatibility - if this.value is a number, use that
if (typeof newValue !== "object") {
this._value = {
value: newValue,
value2: undefined,
};
} else {
this._value = newValue;
}
}
protected toCriterionInput(): IntCriterionInput {
return {
modifier: this.modifier,
value: this.value?.value ?? 0,
value2: this.value?.value2,
};
}
protected getLabelValue(_intl: IntlShape) {
const { value, value2 } = this.value;
if (
this.modifier === CriterionModifier.Between ||
this.modifier === CriterionModifier.NotBetween
) {
return `${value}, ${value2 ?? 0}`;
} else {
return `${value}`;
}
}
public isValid(): boolean {
if (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull
) {
return true;
}
const { value, value2 } = this.value;
if (value === undefined) {
return false;
}
if (
value2 === undefined &&
(this.modifier === CriterionModifier.Between ||
this.modifier === CriterionModifier.NotBetween)
) {
return false;
}
return true;
}
constructor(type: CriterionOption) {
super(type, { value: undefined, value2: undefined });
}
}
export class DurationCriterionOption extends MandatoryNumberCriterionOption {
constructor(messageID: string, value: CriterionType) {
super(messageID, value, () => new DurationCriterion(this));
}
}
export function createDurationCriterionOption(
value: CriterionType,
messageID?: string
) {
return new DurationCriterionOption(messageID ?? value, value);
}
export class DurationCriterion extends Criterion<INumberValue> { export class DurationCriterion extends Criterion<INumberValue> {
constructor(type: CriterionOption) { constructor(type: CriterionOption) {
super(type, { value: undefined, value2: undefined }); super(type, { value: undefined, value2: undefined });
@@ -730,17 +767,16 @@ export class DurationCriterion extends Criterion<INumberValue> {
} }
protected getLabelValue(_intl: IntlShape) { protected getLabelValue(_intl: IntlShape) {
return this.modifier === CriterionModifier.Between || const value = DurationUtils.secondsToString(this.value.value ?? 0);
const value2 = DurationUtils.secondsToString(this.value.value2 ?? 0);
if (
this.modifier === CriterionModifier.Between ||
this.modifier === CriterionModifier.NotBetween this.modifier === CriterionModifier.NotBetween
? `${DurationUtils.secondsToString( ) {
this.value.value ?? 0 return `${value}, ${value2}`;
)} ${DurationUtils.secondsToString(this.value.value2 ?? 0)}` } else {
: this.modifier === CriterionModifier.GreaterThan || return value;
this.modifier === CriterionModifier.LessThan || }
this.modifier === CriterionModifier.Equals ||
this.modifier === CriterionModifier.NotEquals
? DurationUtils.secondsToString(this.value.value ?? 0)
: "?";
} }
public isValid(): boolean { public isValid(): boolean {
@@ -768,16 +804,8 @@ export class DurationCriterion extends Criterion<INumberValue> {
} }
} }
export class PhashDuplicateCriterion extends StringCriterion {
protected toCriterionInput(): PHashDuplicationCriterionInput {
return {
duplicated: this.value === "true",
};
}
}
export class DateCriterionOption extends CriterionOption { export class DateCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType, options?: Option[]) { constructor(messageID: string, value: CriterionType) {
super({ super({
messageID, messageID,
type: value, type: value,
@@ -792,7 +820,6 @@ export class DateCriterionOption extends CriterionOption {
CriterionModifier.NotBetween, CriterionModifier.NotBetween,
], ],
defaultModifier: CriterionModifier.Equals, defaultModifier: CriterionModifier.Equals,
options,
inputType: "text", inputType: "text",
makeCriterion: () => new DateCriterion(this), makeCriterion: () => new DateCriterion(this),
}); });
@@ -857,7 +884,7 @@ export class DateCriterion extends Criterion<IDateValue> {
} }
export class TimestampCriterionOption extends CriterionOption { export class TimestampCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType, options?: Option[]) { constructor(messageID: string, value: CriterionType) {
super({ super({
messageID, messageID,
type: value, type: value,
@@ -870,7 +897,6 @@ export class TimestampCriterionOption extends CriterionOption {
CriterionModifier.NotBetween, CriterionModifier.NotBetween,
], ],
defaultModifier: CriterionModifier.GreaterThan, defaultModifier: CriterionModifier.GreaterThan,
options,
inputType: "text", inputType: "text",
makeCriterion: () => new TimestampCriterion(this), makeCriterion: () => new TimestampCriterion(this),
}); });
@@ -881,6 +907,28 @@ export function createTimestampCriterionOption(value: CriterionType) {
return new TimestampCriterionOption(value, value); return new TimestampCriterionOption(value, value);
} }
export class MandatoryTimestampCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.GreaterThan,
CriterionModifier.LessThan,
CriterionModifier.Between,
CriterionModifier.NotBetween,
],
defaultModifier: CriterionModifier.GreaterThan,
inputType: "text",
makeCriterion: () => new TimestampCriterion(this),
});
}
}
export function createMandatoryTimestampCriterionOption(value: CriterionType) {
return new MandatoryTimestampCriterionOption(value, value);
}
export class TimestampCriterion extends Criterion<ITimestampValue> { export class TimestampCriterion extends Criterion<ITimestampValue> {
public encodeValue() { public encodeValue() {
return { return {
@@ -944,26 +992,3 @@ export class TimestampCriterion extends Criterion<ITimestampValue> {
super(type, { value: "", value2: undefined }); super(type, { value: "", value2: undefined });
} }
} }
export class MandatoryTimestampCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType, options?: Option[]) {
super({
messageID,
type: value,
modifierOptions: [
CriterionModifier.GreaterThan,
CriterionModifier.LessThan,
CriterionModifier.Between,
CriterionModifier.NotBetween,
],
defaultModifier: CriterionModifier.GreaterThan,
options,
inputType: "text",
makeCriterion: () => new TimestampCriterion(this),
});
}
}
export function createMandatoryTimestampCriterionOption(value: CriterionType) {
return new MandatoryTimestampCriterionOption(value, value);
}

View File

@@ -1,37 +0,0 @@
import * as GQL from "src/core/generated-graphql";
import { SceneListFilterOptions } from "../scenes";
import { MovieListFilterOptions } from "../movies";
import { GalleryListFilterOptions } from "../galleries";
import { PerformerListFilterOptions } from "../performers";
import { ImageListFilterOptions } from "../images";
import { SceneMarkerListFilterOptions } from "../scene-markers";
import { StudioListFilterOptions } from "../studios";
import { TagListFilterOptions } from "../tags";
import { CriterionType } from "../types";
const filterModeOptions = {
[GQL.FilterMode.Galleries]: GalleryListFilterOptions.criterionOptions,
[GQL.FilterMode.Images]: ImageListFilterOptions.criterionOptions,
[GQL.FilterMode.Movies]: MovieListFilterOptions.criterionOptions,
[GQL.FilterMode.Performers]: PerformerListFilterOptions.criterionOptions,
[GQL.FilterMode.SceneMarkers]: SceneMarkerListFilterOptions.criterionOptions,
[GQL.FilterMode.Scenes]: SceneListFilterOptions.criterionOptions,
[GQL.FilterMode.Studios]: StudioListFilterOptions.criterionOptions,
[GQL.FilterMode.Tags]: TagListFilterOptions.criterionOptions,
};
export function makeCriteria(
mode: GQL.FilterMode,
type: CriterionType,
config?: GQL.ConfigDataFragment
) {
const criterionOptions = filterModeOptions[mode];
const option = criterionOptions.find((o) => o.type === type);
if (!option) {
throw new Error(`Unknown criterion parameter name: ${type}`);
}
return option?.makeCriterion(config);
}

View File

@@ -2,7 +2,8 @@ import { BooleanCriterion, BooleanCriterionOption } from "./criterion";
export const FavoriteCriterionOption = new BooleanCriterionOption( export const FavoriteCriterionOption = new BooleanCriterionOption(
"favourite", "favourite",
"filter_favorites" "filter_favorites",
() => new FavoriteCriterion()
); );
export class FavoriteCriterion extends BooleanCriterion { export class FavoriteCriterion extends BooleanCriterion {
@@ -13,7 +14,8 @@ export class FavoriteCriterion extends BooleanCriterion {
export const PerformerFavoriteCriterionOption = new BooleanCriterionOption( export const PerformerFavoriteCriterionOption = new BooleanCriterionOption(
"performer_favorite", "performer_favorite",
"performer_favorite" "performer_favorite",
() => new PerformerFavoriteCriterion()
); );
export class PerformerFavoriteCriterion extends BooleanCriterion { export class PerformerFavoriteCriterion extends BooleanCriterion {

View File

@@ -2,15 +2,16 @@ import { ILabeledIdCriterion, ILabeledIdCriterionOption } from "./criterion";
const inputType = "galleries"; const inputType = "galleries";
const galleriesCriterionOption = new ILabeledIdCriterionOption( export const GalleriesCriterionOption = new ILabeledIdCriterionOption(
"galleries", "galleries",
"galleries", "galleries",
true, true,
inputType inputType,
() => new GalleriesCriterion()
); );
export class GalleriesCriterion extends ILabeledIdCriterion { export class GalleriesCriterion extends ILabeledIdCriterion {
constructor() { constructor() {
super(galleriesCriterionOption); super(GalleriesCriterionOption);
} }
} }

View File

@@ -1,18 +1,16 @@
import { CriterionOption, StringCriterion } from "./criterion"; import {
StringBooleanCriterion,
StringBooleanCriterionOption,
} from "./criterion";
export const HasChaptersCriterionOption = new CriterionOption({ export const HasChaptersCriterionOption = new StringBooleanCriterionOption(
messageID: "hasChapters", "hasChapters",
type: "has_chapters", "has_chapters",
options: [true.toString(), false.toString()], () => new HasChaptersCriterion()
makeCriterion: () => new HasChaptersCriterion(), );
});
export class HasChaptersCriterion extends StringCriterion { export class HasChaptersCriterion extends StringBooleanCriterion {
constructor() { constructor() {
super(HasChaptersCriterionOption); super(HasChaptersCriterionOption);
} }
protected toCriterionInput(): string {
return this.value;
}
} }

View File

@@ -1,18 +1,16 @@
import { CriterionOption, StringCriterion } from "./criterion"; import {
StringBooleanCriterion,
StringBooleanCriterionOption,
} from "./criterion";
export const HasMarkersCriterionOption = new CriterionOption({ export const HasMarkersCriterionOption = new StringBooleanCriterionOption(
messageID: "hasMarkers", "hasMarkers",
type: "has_markers", "has_markers",
options: [true.toString(), false.toString()], () => new HasMarkersCriterion()
makeCriterion: () => new HasMarkersCriterion(), );
});
export class HasMarkersCriterion extends StringCriterion { export class HasMarkersCriterion extends StringBooleanCriterion {
constructor() { constructor() {
super(HasMarkersCriterionOption); super(HasMarkersCriterionOption);
} }
protected toCriterionInput(): string {
return this.value;
}
} }

View File

@@ -2,7 +2,8 @@ import { BooleanCriterion, BooleanCriterionOption } from "./criterion";
export const InteractiveCriterionOption = new BooleanCriterionOption( export const InteractiveCriterionOption = new BooleanCriterionOption(
"interactive", "interactive",
"interactive" "interactive",
() => new InteractiveCriterion()
); );
export class InteractiveCriterion extends BooleanCriterion { export class InteractiveCriterion extends BooleanCriterion {

View File

@@ -3,26 +3,25 @@ import { CriterionType } from "../types";
import { CriterionOption, StringCriterion, Option } from "./criterion"; import { CriterionOption, StringCriterion, Option } from "./criterion";
export class IsMissingCriterion extends StringCriterion { export class IsMissingCriterion extends StringCriterion {
public modifierOptions = [];
protected toCriterionInput(): string { protected toCriterionInput(): string {
return this.value; return this.value;
} }
} }
class IsMissingCriterionOptionClass extends CriterionOption { class IsMissingCriterionOption extends CriterionOption {
constructor(messageID: string, type: CriterionType, options: Option[]) { constructor(messageID: string, type: CriterionType, options: Option[]) {
super({ super({
messageID, messageID,
type, type,
options, options,
modifierOptions: [],
defaultModifier: CriterionModifier.Equals, defaultModifier: CriterionModifier.Equals,
makeCriterion: () => new IsMissingCriterion(this), makeCriterion: () => new IsMissingCriterion(this),
}); });
} }
} }
export const SceneIsMissingCriterionOption = new IsMissingCriterionOptionClass( export const SceneIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing", "isMissing",
"is_missing", "is_missing",
[ [
@@ -40,14 +39,16 @@ export const SceneIsMissingCriterionOption = new IsMissingCriterionOptionClass(
] ]
); );
export const ImageIsMissingCriterionOption = new IsMissingCriterionOptionClass( export const ImageIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing", "isMissing",
"is_missing", "is_missing",
["title", "galleries", "studio", "performers", "tags"] ["title", "galleries", "studio", "performers", "tags"]
); );
export const PerformerIsMissingCriterionOption = export const PerformerIsMissingCriterionOption = new IsMissingCriterionOption(
new IsMissingCriterionOptionClass("isMissing", "is_missing", [ "isMissing",
"is_missing",
[
"url", "url",
"twitter", "twitter",
"instagram", "instagram",
@@ -67,33 +68,28 @@ export const PerformerIsMissingCriterionOption =
"image", "image",
"details", "details",
"stash_id", "stash_id",
]); ]
);
export const GalleryIsMissingCriterionOption = export const GalleryIsMissingCriterionOption = new IsMissingCriterionOption(
new IsMissingCriterionOptionClass("isMissing", "is_missing", [ "isMissing",
"title", "is_missing",
"details", ["title", "details", "url", "date", "studio", "performers", "tags", "scenes"]
"url", );
"date",
"studio",
"performers",
"tags",
"scenes",
]);
export const TagIsMissingCriterionOption = new IsMissingCriterionOptionClass( export const TagIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing", "isMissing",
"is_missing", "is_missing",
["image"] ["image"]
); );
export const StudioIsMissingCriterionOption = new IsMissingCriterionOptionClass( export const StudioIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing", "isMissing",
"is_missing", "is_missing",
["image", "stash_id", "details"] ["image", "stash_id", "details"]
); );
export const MovieIsMissingCriterionOption = new IsMissingCriterionOptionClass( export const MovieIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing", "isMissing",
"is_missing", "is_missing",
["front_image", "back_image", "scenes"] ["front_image", "back_image", "scenes"]

View File

@@ -6,7 +6,8 @@ export const MoviesCriterionOption = new ILabeledIdCriterionOption(
"movies", "movies",
"movies", "movies",
false, false,
inputType inputType,
() => new MoviesCriterion()
); );
export class MoviesCriterion extends ILabeledIdCriterion { export class MoviesCriterion extends ILabeledIdCriterion {

View File

@@ -1,12 +0,0 @@
import { Criterion, StringCriterionOption } from "./criterion";
export const NoneCriterionOption = new StringCriterionOption("none", "none");
export class NoneCriterion extends Criterion<string> {
constructor() {
super(NoneCriterionOption, "none");
}
protected getLabelValue(): string {
return "";
}
}

View File

@@ -2,7 +2,8 @@ import { BooleanCriterion, BooleanCriterionOption } from "./criterion";
export const OrganizedCriterionOption = new BooleanCriterionOption( export const OrganizedCriterionOption = new BooleanCriterionOption(
"organized", "organized",
"organized" "organized",
() => new OrganizedCriterion()
); );
export class OrganizedCriterion extends BooleanCriterion { export class OrganizedCriterion extends BooleanCriterion {

View File

@@ -0,0 +1,13 @@
import { StringCriterion, StringCriterionOption } from "./criterion";
export const PathCriterionOption = new StringCriterionOption(
"path",
"path",
() => new PathCriterion()
);
export class PathCriterion extends StringCriterion {
constructor() {
super(PathCriterionOption);
}
}

View File

@@ -24,8 +24,8 @@ export const PerformersCriterionOption = new CriterionOption({
type: "performers", type: "performers",
modifierOptions, modifierOptions,
defaultModifier, defaultModifier,
makeCriterion: () => new PerformersCriterion(),
inputType, inputType,
makeCriterion: () => new PerformersCriterion(),
}); });
export class PerformersCriterion extends Criterion<ILabeledValueListValue> { export class PerformersCriterion extends Criterion<ILabeledValueListValue> {

View File

@@ -1,13 +1,14 @@
import { import {
CriterionModifier, CriterionModifier,
PhashDistanceCriterionInput, PhashDistanceCriterionInput,
PHashDuplicationCriterionInput,
} from "src/core/generated-graphql"; } from "src/core/generated-graphql";
import { IPhashDistanceValue } from "../types"; import { IPhashDistanceValue } from "../types";
import { import {
BooleanCriterionOption, BooleanCriterionOption,
Criterion, Criterion,
CriterionOption, CriterionOption,
PhashDuplicateCriterion, StringCriterion,
} from "./criterion"; } from "./criterion";
export const PhashCriterionOption = new CriterionOption({ export const PhashCriterionOption = new CriterionOption({
@@ -56,8 +57,14 @@ export const DuplicatedCriterionOption = new BooleanCriterionOption(
() => new DuplicatedCriterion() () => new DuplicatedCriterion()
); );
export class DuplicatedCriterion extends PhashDuplicateCriterion { export class DuplicatedCriterion extends StringCriterion {
constructor() { constructor() {
super(DuplicatedCriterionOption); super(DuplicatedCriterionOption);
} }
protected toCriterionInput(): PHashDuplicationCriterionInput {
return {
duplicated: this.value === "true",
};
}
} }

View File

@@ -8,7 +8,7 @@ import {
ConfigDataFragment, ConfigDataFragment,
CriterionModifier, CriterionModifier,
IntCriterionInput, IntCriterionInput,
} from "../../../core/generated-graphql"; } from "src/core/generated-graphql";
import { INumberValue } from "../types"; import { INumberValue } from "../types";
import { Criterion, CriterionOption } from "./criterion"; import { Criterion, CriterionOption } from "./criterion";
import { IUIConfig } from "src/core/config"; import { IUIConfig } from "src/core/config";

View File

@@ -11,20 +11,7 @@ import {
StringCriterion, StringCriterion,
} from "./criterion"; } from "./criterion";
abstract class AbstractResolutionCriterion extends StringCriterion { class BaseResolutionCriterionOption extends CriterionOption {
protected toCriterionInput(): ResolutionCriterionInput | undefined {
const value = stringToResolution(this.value);
if (value !== undefined) {
return {
value,
modifier: this.modifier,
};
}
}
}
class ResolutionCriterionOptionType extends CriterionOption {
constructor( constructor(
value: CriterionType, value: CriterionType,
makeCriterion: () => Criterion<CriterionValue> makeCriterion: () => Criterion<CriterionValue>
@@ -44,23 +31,37 @@ class ResolutionCriterionOptionType extends CriterionOption {
} }
} }
export const ResolutionCriterionOption = new ResolutionCriterionOptionType( class BaseResolutionCriterion extends StringCriterion {
protected toCriterionInput(): ResolutionCriterionInput | undefined {
const value = stringToResolution(this.value);
if (value !== undefined) {
return {
value,
modifier: this.modifier,
};
}
}
}
export const ResolutionCriterionOption = new BaseResolutionCriterionOption(
"resolution", "resolution",
() => new ResolutionCriterion() () => new ResolutionCriterion()
); );
export class ResolutionCriterion extends AbstractResolutionCriterion {
export class ResolutionCriterion extends BaseResolutionCriterion {
constructor() { constructor() {
super(ResolutionCriterionOption); super(ResolutionCriterionOption);
} }
} }
export const AverageResolutionCriterionOption = export const AverageResolutionCriterionOption =
new ResolutionCriterionOptionType( new BaseResolutionCriterionOption(
"average_resolution", "average_resolution",
() => new AverageResolutionCriterion() () => new AverageResolutionCriterion()
); );
export class AverageResolutionCriterion extends AbstractResolutionCriterion { export class AverageResolutionCriterion extends BaseResolutionCriterion {
constructor() { constructor() {
super(AverageResolutionCriterionOption); super(AverageResolutionCriterionOption);
} }

View File

@@ -20,8 +20,8 @@ export const StudiosCriterionOption = new CriterionOption({
type: "studios", type: "studios",
modifierOptions, modifierOptions,
defaultModifier, defaultModifier,
makeCriterion: () => new StudiosCriterion(),
inputType, inputType,
makeCriterion: () => new StudiosCriterion(),
}); });
export class StudiosCriterion extends IHierarchicalLabeledIdCriterion { export class StudiosCriterion extends IHierarchicalLabeledIdCriterion {
@@ -34,8 +34,10 @@ export const ParentStudiosCriterionOption = new ILabeledIdCriterionOption(
"parent_studios", "parent_studios",
"parents", "parents",
false, false,
inputType inputType,
() => new ParentStudiosCriterion()
); );
export class ParentStudiosCriterion extends ILabeledIdCriterion { export class ParentStudiosCriterion extends ILabeledIdCriterion {
constructor() { constructor() {
super(ParentStudiosCriterionOption); super(ParentStudiosCriterionOption);

View File

@@ -20,7 +20,7 @@ const withoutEqualsModifierOptions = [
const defaultModifier = CriterionModifier.IncludesAll; const defaultModifier = CriterionModifier.IncludesAll;
const inputType = "tags"; const inputType = "tags";
export class TagsCriterionOptionClass extends CriterionOption { class BaseTagsCriterionOption extends CriterionOption {
constructor( constructor(
messageID: string, messageID: string,
type: CriterionType, type: CriterionType,
@@ -31,37 +31,37 @@ export class TagsCriterionOptionClass extends CriterionOption {
type, type,
modifierOptions, modifierOptions,
defaultModifier, defaultModifier,
makeCriterion: () => new TagsCriterion(this),
inputType, inputType,
makeCriterion: () => new TagsCriterion(this),
}); });
} }
} }
export const TagsCriterionOption = new TagsCriterionOptionClass( export const TagsCriterionOption = new BaseTagsCriterionOption(
"tags", "tags",
"tags", "tags",
defaultModifierOptions defaultModifierOptions
); );
export const SceneTagsCriterionOption = new TagsCriterionOptionClass( export const SceneTagsCriterionOption = new BaseTagsCriterionOption(
"scene_tags", "scene_tags",
"scene_tags", "scene_tags",
defaultModifierOptions defaultModifierOptions
); );
export const PerformerTagsCriterionOption = new TagsCriterionOptionClass( export const PerformerTagsCriterionOption = new BaseTagsCriterionOption(
"performer_tags", "performer_tags",
"performer_tags", "performer_tags",
withoutEqualsModifierOptions withoutEqualsModifierOptions
); );
export const ParentTagsCriterionOption = new TagsCriterionOptionClass( export const ParentTagsCriterionOption = new BaseTagsCriterionOption(
"parent_tags", "parent_tags",
"parents", "parents",
withoutEqualsModifierOptions withoutEqualsModifierOptions
); );
export const ChildTagsCriterionOption = new TagsCriterionOptionClass( export const ChildTagsCriterionOption = new BaseTagsCriterionOption(
"sub_tags", "sub_tags",
"children", "children",
withoutEqualsModifierOptions withoutEqualsModifierOptions

View File

@@ -6,7 +6,7 @@ import {
SortDirectionEnum, SortDirectionEnum,
} from "src/core/generated-graphql"; } from "src/core/generated-graphql";
import { Criterion, CriterionValue } from "./criteria/criterion"; import { Criterion, CriterionValue } from "./criteria/criterion";
import { makeCriteria } from "./criteria/factory"; import { getFilterOptions } from "./factory";
import { CriterionType, DisplayMode } from "./types"; import { CriterionType, DisplayMode } from "./types";
interface IDecodedParams { interface IDecodedParams {
@@ -128,16 +128,9 @@ export class ListFilterModel {
for (const jsonString of params.c) { for (const jsonString of params.c) {
try { try {
const encodedCriterion = JSON.parse(jsonString); const encodedCriterion = JSON.parse(jsonString);
const criterion = makeCriteria( const criterion = this.makeCriterion(encodedCriterion.type);
this.mode,
encodedCriterion.type,
this.config
);
// it's possible that we have unsupported criteria. Just skip if so.
if (criterion) {
criterion.setFromEncodedCriterion(encodedCriterion); criterion.setFromEncodedCriterion(encodedCriterion);
this.criteria.push(criterion); this.criteria.push(criterion);
}
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Failed to parse encoded criterion:", err); console.error("Failed to parse encoded criterion:", err);
@@ -280,16 +273,9 @@ export class ListFilterModel {
this.criteria = []; this.criteria = [];
if (objectFilter) { if (objectFilter) {
Object.keys(objectFilter).forEach((key) => { Object.keys(objectFilter).forEach((key) => {
const criterion = makeCriteria( const criterion = this.makeCriterion(key as CriterionType);
this.mode,
key as CriterionType,
this.config
);
// it's possible that we have unsupported criteria. Just skip if so.
if (criterion) {
criterion.setFromEncodedCriterion(objectFilter[key]); criterion.setFromEncodedCriterion(objectFilter[key]);
this.criteria.push(criterion); this.criteria.push(criterion);
}
}); });
} }
} }
@@ -426,6 +412,18 @@ export class ListFilterModel {
return query.join("&"); return query.join("&");
} }
public makeCriterion(type: CriterionType) {
const { criterionOptions } = getFilterOptions(this.mode);
const option = criterionOptions.find((o) => o.type === type);
if (!option) {
throw new Error(`Unknown criterion parameter name: ${type}`);
}
return option.makeCriterion(this.config);
}
// TODO: These don't support multiple of the same criteria, only the last one set is used. // TODO: These don't support multiple of the same criteria, only the last one set is used.
public makeFindFilter(): FindFilterType { public makeFindFilter(): FindFilterType {
@@ -439,8 +437,7 @@ export class ListFilterModel {
} }
public makeFilter() { public makeFilter() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const output: Record<string, unknown> = {};
const output: Record<string, any> = {};
this.criteria.forEach((criterion) => { this.criteria.forEach((criterion) => {
criterion.apply(output); criterion.apply(output);
}); });
@@ -449,8 +446,7 @@ export class ListFilterModel {
} }
public makeSavedFindFilter() { public makeSavedFindFilter() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const output: Record<string, unknown> = {};
const output: Record<string, any> = {};
this.criteria.forEach((criterion) => { this.criteria.forEach((criterion) => {
criterion.toSavedFilter(output); criterion.toSavedFilter(output);
}); });
@@ -458,8 +454,7 @@ export class ListFilterModel {
return output; return output;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any public makeUIOptions(): Record<string, unknown> {
public makeUIOptions(): Record<string, any> {
return { return {
display_mode: this.displayMode, display_mode: this.displayMode,
zoom_index: this.zoomIndex, zoom_index: this.zoomIndex,

View File

@@ -3,7 +3,6 @@ import {
createStringCriterionOption, createStringCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createPathCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { GalleryIsMissingCriterionOption } from "./criteria/is-missing"; import { GalleryIsMissingCriterionOption } from "./criteria/is-missing";
@@ -19,6 +18,7 @@ import {
import { ListFilterOptions, MediaSortByOptions } from "./filter-options"; import { ListFilterOptions, MediaSortByOptions } from "./filter-options";
import { DisplayMode } from "./types"; import { DisplayMode } from "./types";
import { RatingCriterionOption } from "./criteria/rating"; import { RatingCriterionOption } from "./criteria/rating";
import { PathCriterionOption } from "./criteria/path";
const defaultSortBy = "path"; const defaultSortBy = "path";
@@ -44,7 +44,7 @@ const displayModeOptions = [
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createStringCriterionOption("details"), createStringCriterionOption("details"),
createPathCriterionOption("path"), PathCriterionOption,
createStringCriterionOption("checksum", "media_info.checksum"), createStringCriterionOption("checksum", "media_info.checksum"),
RatingCriterionOption, RatingCriterionOption,
OrganizedCriterionOption, OrganizedCriterionOption,
@@ -52,13 +52,13 @@ const criterionOptions = [
GalleryIsMissingCriterionOption, GalleryIsMissingCriterionOption,
TagsCriterionOption, TagsCriterionOption,
HasChaptersCriterionOption, HasChaptersCriterionOption,
createStringCriterionOption("tag_count"), createMandatoryNumberCriterionOption("tag_count"),
PerformerTagsCriterionOption, PerformerTagsCriterionOption,
PerformersCriterionOption, PerformersCriterionOption,
createStringCriterionOption("performer_count"), createMandatoryNumberCriterionOption("performer_count"),
createMandatoryNumberCriterionOption("performer_age"), createMandatoryNumberCriterionOption("performer_age"),
PerformerFavoriteCriterionOption, PerformerFavoriteCriterionOption,
createStringCriterionOption("image_count"), createMandatoryNumberCriterionOption("image_count"),
StudiosCriterionOption, StudiosCriterionOption,
createStringCriterionOption("url"), createStringCriterionOption("url"),
createMandatoryNumberCriterionOption("file_count", "zip_file_count"), createMandatoryNumberCriterionOption("file_count", "zip_file_count"),

View File

@@ -4,11 +4,11 @@ import {
createStringCriterionOption, createStringCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createPathCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { ImageIsMissingCriterionOption } from "./criteria/is-missing"; import { ImageIsMissingCriterionOption } from "./criteria/is-missing";
import { OrganizedCriterionOption } from "./criteria/organized"; import { OrganizedCriterionOption } from "./criteria/organized";
import { PathCriterionOption } from "./criteria/path";
import { PerformersCriterionOption } from "./criteria/performers"; import { PerformersCriterionOption } from "./criteria/performers";
import { RatingCriterionOption } from "./criteria/rating"; import { RatingCriterionOption } from "./criteria/rating";
import { ResolutionCriterionOption } from "./criteria/resolution"; import { ResolutionCriterionOption } from "./criteria/resolution";
@@ -34,7 +34,7 @@ const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createMandatoryStringCriterionOption("checksum", "media_info.checksum"), createMandatoryStringCriterionOption("checksum", "media_info.checksum"),
createPathCriterionOption("path"), PathCriterionOption,
OrganizedCriterionOption, OrganizedCriterionOption,
createMandatoryNumberCriterionOption("o_counter"), createMandatoryNumberCriterionOption("o_counter"),
ResolutionCriterionOption, ResolutionCriterionOption,

View File

@@ -1,8 +1,8 @@
import { import {
createMandatoryNumberCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createDurationCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { MovieIsMissingCriterionOption } from "./criteria/is-missing"; import { MovieIsMissingCriterionOption } from "./criteria/is-missing";
import { StudiosCriterionOption } from "./criteria/studios"; import { StudiosCriterionOption } from "./criteria/studios";
@@ -29,7 +29,7 @@ const criterionOptions = [
createStringCriterionOption("name"), createStringCriterionOption("name"),
createStringCriterionOption("director"), createStringCriterionOption("director"),
createStringCriterionOption("synopsis"), createStringCriterionOption("synopsis"),
createMandatoryNumberCriterionOption("duration"), createDurationCriterionOption("duration"),
RatingCriterionOption, RatingCriterionOption,
PerformersCriterionOption, PerformersCriterionOption,
createDateCriterionOption("date"), createDateCriterionOption("date"),

View File

@@ -5,7 +5,6 @@ import {
createBooleanCriterionOption, createBooleanCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
NumberCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { FavoriteCriterionOption } from "./criteria/favorite"; import { FavoriteCriterionOption } from "./criteria/favorite";
import { GenderCriterionOption } from "./criteria/gender"; import { GenderCriterionOption } from "./criteria/gender";
@@ -94,9 +93,9 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("gallery_count"), createMandatoryNumberCriterionOption("gallery_count"),
createMandatoryNumberCriterionOption("o_counter"), createMandatoryNumberCriterionOption("o_counter"),
createBooleanCriterionOption("ignore_auto_tag"), createBooleanCriterionOption("ignore_auto_tag"),
new NumberCriterionOption("height", "height_cm"),
...numberCriteria.map((c) => createNumberCriterionOption(c)),
CountryCriterionOption, CountryCriterionOption,
createNumberCriterionOption("height_cm", "height"),
...numberCriteria.map((c) => createNumberCriterionOption(c)),
...stringCriteria.map((c) => createStringCriterionOption(c)), ...stringCriteria.map((c) => createStringCriterionOption(c)),
createDateCriterionOption("birthdate"), createDateCriterionOption("birthdate"),
createDateCriterionOption("death_date"), createDateCriterionOption("death_date"),

View File

@@ -4,7 +4,7 @@ import {
createStringCriterionOption, createStringCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createPathCriterionOption, createDurationCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { HasMarkersCriterionOption } from "./criteria/has-markers"; import { HasMarkersCriterionOption } from "./criteria/has-markers";
import { SceneIsMissingCriterionOption } from "./criteria/is-missing"; import { SceneIsMissingCriterionOption } from "./criteria/is-missing";
@@ -28,6 +28,7 @@ import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { CaptionsCriterionOption } from "./criteria/captions"; import { CaptionsCriterionOption } from "./criteria/captions";
import { StashIDCriterionOption } from "./criteria/stash-ids"; import { StashIDCriterionOption } from "./criteria/stash-ids";
import { RatingCriterionOption } from "./criteria/rating"; import { RatingCriterionOption } from "./criteria/rating";
import { PathCriterionOption } from "./criteria/path";
const defaultSortBy = "date"; const defaultSortBy = "date";
const sortByOptions = [ const sortByOptions = [
@@ -60,7 +61,7 @@ const displayModeOptions = [
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createStringCriterionOption("code", "scene_code"), createStringCriterionOption("code", "scene_code"),
createPathCriterionOption("path"), PathCriterionOption,
createStringCriterionOption("details"), createStringCriterionOption("details"),
createStringCriterionOption("director"), createStringCriterionOption("director"),
createMandatoryStringCriterionOption("oshash", "media_info.hash"), createMandatoryStringCriterionOption("oshash", "media_info.hash"),
@@ -73,9 +74,9 @@ const criterionOptions = [
ResolutionCriterionOption, ResolutionCriterionOption,
createStringCriterionOption("video_codec"), createStringCriterionOption("video_codec"),
createStringCriterionOption("audio_codec"), createStringCriterionOption("audio_codec"),
createMandatoryNumberCriterionOption("duration"), createDurationCriterionOption("duration"),
createMandatoryNumberCriterionOption("resume_time"), createDurationCriterionOption("resume_time"),
createMandatoryNumberCriterionOption("play_duration"), createDurationCriterionOption("play_duration"),
createMandatoryNumberCriterionOption("play_count"), createMandatoryNumberCriterionOption("play_count"),
HasMarkersCriterionOption, HasMarkersCriterionOption,
SceneIsMissingCriterionOption, SceneIsMissingCriterionOption,

View File

@@ -1,5 +1,4 @@
// NOTE: add new enum values to the end, to ensure existing data // NOTE: add new enum values to the end, to ensure existing data
// is not impacted // is not impacted
export enum DisplayMode { export enum DisplayMode {
Grid, Grid,
@@ -60,38 +59,51 @@ export interface IPhashDistanceValue {
} }
export function criterionIsHierarchicalLabelValue( export function criterionIsHierarchicalLabelValue(
// eslint-disable-next-line @typescript-eslint/no-explicit-any value: unknown
value: any
): value is IHierarchicalLabelValue { ): value is IHierarchicalLabelValue {
return typeof value === "object" && "items" in value && "depth" in value; return (
typeof value === "object" && !!value && "items" in value && "depth" in value
);
} }
export function criterionIsNumberValue( export function criterionIsNumberValue(value: unknown): value is INumberValue {
// eslint-disable-next-line @typescript-eslint/no-explicit-any return (
value: any typeof value === "object" &&
): value is INumberValue { !!value &&
return typeof value === "object" && "value" in value && "value2" in value; "value" in value &&
"value2" in value
);
} }
export function criterionIsStashIDValue( export function criterionIsStashIDValue(
// eslint-disable-next-line @typescript-eslint/no-explicit-any value: unknown
value: any
): value is IStashIDValue { ): value is IStashIDValue {
return typeof value === "object" && "endpoint" in value && "stashID" in value; return (
typeof value === "object" &&
!!value &&
"endpoint" in value &&
"stashID" in value
);
} }
export function criterionIsDateValue( export function criterionIsDateValue(value: unknown): value is IDateValue {
// eslint-disable-next-line @typescript-eslint/no-explicit-any return (
value: any typeof value === "object" &&
): value is IDateValue { !!value &&
return typeof value === "object" && "value" in value && "value2" in value; "value" in value &&
"value2" in value
);
} }
export function criterionIsTimestampValue( export function criterionIsTimestampValue(
// eslint-disable-next-line @typescript-eslint/no-explicit-any value: unknown
value: any
): value is ITimestampValue { ): value is ITimestampValue {
return typeof value === "object" && "value" in value && "value2" in value; return (
typeof value === "object" &&
!!value &&
"value" in value &&
"value2" in value
);
} }
export interface IOptionType { export interface IOptionType {
@@ -101,7 +113,6 @@ export interface IOptionType {
} }
export type CriterionType = export type CriterionType =
| "none"
| "path" | "path"
| "rating" | "rating"
| "rating100" | "rating100"

View File

@@ -16,13 +16,13 @@ const secondsToString = (seconds: number) => {
const stringToSeconds = (v?: string) => { const stringToSeconds = (v?: string) => {
if (!v) { if (!v) {
return 0; return undefined;
} }
const splits = v.split(":"); const splits = v.split(":");
if (splits.length > 3) { if (splits.length > 3) {
return 0; return undefined;
} }
let seconds = 0; let seconds = 0;
@@ -30,12 +30,12 @@ const stringToSeconds = (v?: string) => {
while (splits.length > 0) { while (splits.length > 0) {
const thisSplit = splits.pop(); const thisSplit = splits.pop();
if (thisSplit === undefined) { if (thisSplit === undefined) {
return 0; return undefined;
} }
const thisInt = parseInt(thisSplit, 10); const thisInt = parseInt(thisSplit, 10);
if (Number.isNaN(thisInt)) { if (Number.isNaN(thisInt)) {
return 0; return undefined;
} }
seconds += factor * thisInt; seconds += factor * thisInt;

View File

@@ -79,29 +79,14 @@ const renderInputGroup = (options: {
}; };
const renderDurationInput = (options: { const renderDurationInput = (options: {
value: string | undefined; value: number | undefined;
isEditing: boolean; isEditing: boolean;
url?: string; onChange: (value: number | undefined) => void;
asString?: boolean;
onChange: (value: string | undefined) => void;
}) => { }) => {
let numericValue: number | undefined;
if (options.value) {
if (!options.asString) {
try {
numericValue = Number.parseInt(options.value, 10);
} catch {
// ignore
}
} else {
numericValue = DurationUtils.stringToSeconds(options.value);
}
}
if (!options.isEditing) { if (!options.isEditing) {
let durationString; let durationString;
if (numericValue !== undefined) { if (options.value !== undefined) {
durationString = DurationUtils.secondsToString(numericValue); durationString = DurationUtils.secondsToString(options.value);
} }
return ( return (
@@ -113,19 +98,11 @@ const renderDurationInput = (options: {
/> />
); );
} }
return ( return (
<DurationInput <DurationInput
disabled={!options.isEditing} value={options.value}
numericValue={numericValue} setValue={(v) => options.onChange(v)}
onValueChange={(valueAsNumber: number, valueAsString?: string) => {
let value = valueAsString;
if (!options.asString) {
value =
valueAsNumber !== undefined ? valueAsNumber.toString() : undefined;
}
options.onChange(value);
}}
/> />
); );
}; };

View File

@@ -86,10 +86,9 @@ const renderInputGroup = (options: {
const renderDurationInput = (options: { const renderDurationInput = (options: {
title: string; title: string;
placeholder?: string; placeholder?: string;
value: string | undefined; value: number | undefined;
isEditing: boolean; isEditing: boolean;
asString?: boolean; onChange: (value: number | undefined) => void;
onChange: (value: string | undefined) => void;
labelProps?: FormLabelProps; labelProps?: FormLabelProps;
inputProps?: ColProps; inputProps?: ColProps;
}) => { }) => {

View File

@@ -41,10 +41,9 @@ const renderInputGroup = (options: {
const renderDurationInput = (options: { const renderDurationInput = (options: {
title: string; title: string;
placeholder?: string; placeholder?: string;
value: string | undefined; value: number | undefined;
isEditing: boolean; isEditing: boolean;
asString?: boolean; onChange: (value: number | undefined) => void;
onChange: (value: string | undefined) => void;
}) => { }) => {
return ( return (
<tr> <tr>