From 9f4d0af88611e48a834749230ba52fa129b2bba9 Mon Sep 17 00:00:00 2001 From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com> Date: Tue, 12 Sep 2023 02:53:32 +0200 Subject: [PATCH] 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 --- .../GalleryDetails/GalleryAddPanel.tsx | 2 +- .../GalleryDetails/GalleryImagesPanel.tsx | 2 +- .../src/components/List/CriterionEditor.tsx | 4 +- .../src/components/List/EditFilterDialog.tsx | 13 +- .../List/Filters/DurationFilter.tsx | 82 +-- .../Movies/MovieDetails/MovieEditPanel.tsx | 14 +- .../Movies/MovieDetails/MovieScenesPanel.tsx | 2 +- .../components/ScenePlayer/ScenePlayer.tsx | 20 +- .../Scenes/SceneDetails/SceneMarkerForm.tsx | 5 +- .../SettingsInterfacePanel.tsx | 4 +- .../Settings/SettingsServicesPanel.tsx | 18 +- .../src/components/Shared/DurationInput.tsx | 105 ++- .../StudioDetails/StudioChildrenPanel.tsx | 2 +- .../Tags/TagDetails/TagMarkersPanel.tsx | 2 +- ui/v2.5/src/core/performers.ts | 2 +- ui/v2.5/src/core/studios.ts | 2 +- ui/v2.5/src/core/tags.ts | 2 +- .../models/list-filter/criteria/captions.ts | 43 +- .../list-filter/criteria/circumcised.ts | 2 +- .../models/list-filter/criteria/country.ts | 19 +- .../models/list-filter/criteria/criterion.ts | 679 +++++++++--------- .../models/list-filter/criteria/factory.ts | 37 - .../models/list-filter/criteria/favorite.ts | 6 +- .../models/list-filter/criteria/galleries.ts | 7 +- .../list-filter/criteria/has-chapters.ts | 22 +- .../list-filter/criteria/has-markers.ts | 22 +- .../list-filter/criteria/interactive.ts | 3 +- .../models/list-filter/criteria/is-missing.ts | 40 +- .../src/models/list-filter/criteria/movies.ts | 3 +- .../src/models/list-filter/criteria/none.ts | 12 - .../models/list-filter/criteria/organized.ts | 3 +- .../src/models/list-filter/criteria/path.ts | 13 + .../models/list-filter/criteria/performers.ts | 2 +- .../src/models/list-filter/criteria/phash.ts | 11 +- .../src/models/list-filter/criteria/rating.ts | 2 +- .../models/list-filter/criteria/resolution.ts | 37 +- .../models/list-filter/criteria/studios.ts | 6 +- .../src/models/list-filter/criteria/tags.ts | 14 +- ui/v2.5/src/models/list-filter/filter.ts | 49 +- ui/v2.5/src/models/list-filter/galleries.ts | 10 +- ui/v2.5/src/models/list-filter/images.ts | 4 +- ui/v2.5/src/models/list-filter/movies.ts | 4 +- ui/v2.5/src/models/list-filter/performers.ts | 5 +- ui/v2.5/src/models/list-filter/scenes.ts | 11 +- ui/v2.5/src/models/list-filter/types.ts | 53 +- ui/v2.5/src/utils/duration.ts | 8 +- ui/v2.5/src/utils/editabletext.tsx | 37 +- ui/v2.5/src/utils/form.tsx | 5 +- ui/v2.5/src/utils/table.tsx | 5 +- 49 files changed, 682 insertions(+), 773 deletions(-) delete mode 100644 ui/v2.5/src/models/list-filter/criteria/factory.ts delete mode 100644 ui/v2.5/src/models/list-filter/criteria/none.ts create mode 100644 ui/v2.5/src/models/list-filter/criteria/path.ts diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx index b423b11048c..e007f2f1f0f 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx @@ -30,7 +30,7 @@ export const GalleryAddPanel: React.FC = ({ // if galleries is already present, then we modify it, otherwise add let galleryCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "galleries"; - }) as GalleriesCriterion; + }) as GalleriesCriterion | undefined; if ( galleryCriterion && diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx index 18741102089..eefc92ee8b4 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx @@ -33,7 +33,7 @@ export const GalleryImagesPanel: React.FC = ({ // if galleries is already present, then we modify it, otherwise add let galleryCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "galleries"; - }) as GalleriesCriterion; + }) as GalleriesCriterion | undefined; if ( galleryCriterion && diff --git a/ui/v2.5/src/components/List/CriterionEditor.tsx b/ui/v2.5/src/components/List/CriterionEditor.tsx index e35ba50c2fc..96278560bed 100644 --- a/ui/v2.5/src/components/List/CriterionEditor.tsx +++ b/ui/v2.5/src/components/List/CriterionEditor.tsx @@ -12,7 +12,6 @@ import { DateCriterion, TimestampCriterion, BooleanCriterion, - PathCriterionOption, } from "src/models/list-filter/criteria/criterion"; import { useIntl } from "react-intl"; import { @@ -47,6 +46,7 @@ import TagsFilter from "./Filters/TagsFilter"; import { PhashCriterion } from "src/models/list-filter/criteria/phash"; import { PhashFilter } from "./Filters/PhashFilter"; import cx from "classnames"; +import { PathCriterion } from "src/models/list-filter/criteria/path"; interface IGenericCriterionEditor { criterion: Criterion; @@ -175,7 +175,7 @@ const GenericCriterionEditor: React.FC = ({ ); } } - if (criterion.criterionOption instanceof PathCriterionOption) { + if (criterion instanceof PathCriterion) { return ( ); diff --git a/ui/v2.5/src/components/List/EditFilterDialog.tsx b/ui/v2.5/src/components/List/EditFilterDialog.tsx index 7ddb7fbdb5c..531af632cb3 100644 --- a/ui/v2.5/src/components/List/EditFilterDialog.tsx +++ b/ui/v2.5/src/components/List/EditFilterDialog.tsx @@ -14,7 +14,6 @@ import { Criterion, CriterionOption, } from "src/models/list-filter/criteria/criterion"; -import { makeCriteria } from "src/models/list-filter/criteria/factory"; import { FormattedMessage, useIntl } from "react-intl"; import { ConfigurationContext } from "src/hooks/Config"; import { ListFilterModel } from "src/models/list-filter/filter"; @@ -243,17 +242,11 @@ export const EditFilterDialog: React.FC = ({ }, [currentFilter.mode]); const criterionOptions = useMemo(() => { - const filteredOptions = filterOptions.criterionOptions.filter((o) => { - return o.type !== "none"; - }); - - filteredOptions.sort((a, b) => { + return [...filterOptions.criterionOptions].sort((a, b) => { return intl .formatMessage({ id: a.messageID }) .localeCompare(intl.formatMessage({ id: b.messageID })); }); - - return filteredOptions; }, [intl, filterOptions.criterionOptions]); const optionSelected = useCallback( @@ -270,11 +263,11 @@ export const EditFilterDialog: React.FC = ({ if (existing) { setCriterion(existing); } else { - const newCriterion = makeCriteria(filter.mode, option.type); + const newCriterion = filter.makeCriterion(option.type); setCriterion(newCriterion); } }, - [filter.mode, criteria] + [filter, criteria] ); const ui = (configuration?.ui ?? {}) as IUIConfig; diff --git a/ui/v2.5/src/components/List/Filters/DurationFilter.tsx b/ui/v2.5/src/components/List/Filters/DurationFilter.tsx index 772bb0137a4..59d8f0a0700 100644 --- a/ui/v2.5/src/components/List/Filters/DurationFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/DurationFilter.tsx @@ -17,67 +17,50 @@ export const DurationFilter: React.FC = ({ }) => { const intl = useIntl(); - function onChanged(valueAsNumber: number, property: "value" | "value2") { + function onChanged(v: number | undefined, property: "value" | "value2") { const { value } = criterion; - value[property] = valueAsNumber; + value[property] = v; onValueChanged(value); } - let equalsControl: JSX.Element | null = null; - if ( - criterion.modifier === CriterionModifier.Equals || - criterion.modifier === CriterionModifier.NotEquals - ) { - equalsControl = ( - - onChanged(v, "value")} - placeholder={intl.formatMessage({ id: "criterion.value" })} - /> - - ); - } + function renderTop() { + let placeholder: string; + if ( + criterion.modifier === CriterionModifier.GreaterThan || + criterion.modifier === CriterionModifier.Between || + criterion.modifier === CriterionModifier.NotBetween + ) { + 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" }); + } - let lowerControl: JSX.Element | null = null; - if ( - criterion.modifier === CriterionModifier.GreaterThan || - criterion.modifier === CriterionModifier.Between || - criterion.modifier === CriterionModifier.NotBetween - ) { - lowerControl = ( + return ( onChanged(v, "value")} - placeholder={intl.formatMessage({ id: "criterion.greater_than" })} + value={criterion.value?.value} + setValue={(v) => onChanged(v, "value")} + placeholder={placeholder} /> ); } - let upperControl: JSX.Element | null = null; - if ( - criterion.modifier === CriterionModifier.LessThan || - criterion.modifier === CriterionModifier.Between || - criterion.modifier === CriterionModifier.NotBetween - ) { - upperControl = ( + function renderBottom() { + if ( + criterion.modifier !== CriterionModifier.Between && + criterion.modifier !== CriterionModifier.NotBetween + ) { + return; + } + + return ( - onChanged( - v, - criterion.modifier === CriterionModifier.LessThan - ? "value" - : "value2" - ) - } + value={criterion.value?.value2} + setValue={(v) => onChanged(v, "value2")} placeholder={intl.formatMessage({ id: "criterion.less_than" })} /> @@ -86,9 +69,8 @@ export const DurationFilter: React.FC = ({ return ( <> - {equalsControl} - {lowerControl} - {upperControl} + {renderTop()} + {renderBottom()} ); }; diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx index 60717ad28ae..d4fd4c93913 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx @@ -135,10 +135,10 @@ export const MovieEditPanel: React.FC = ({ } if (state.duration) { - formik.setFieldValue( - "duration", - DurationUtils.stringToSeconds(state.duration) - ); + const seconds = DurationUtils.stringToSeconds(state.duration); + if (seconds !== undefined) { + formik.setFieldValue("duration", seconds); + } } if (state.date) { @@ -402,10 +402,8 @@ export const MovieEditPanel: React.FC = ({ { - formik.setFieldValue("duration", valueAsNumber ?? null); - }} + value={formik.values.duration ?? undefined} + setValue={(v) => formik.setFieldValue("duration", v ?? null)} /> diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx index 22215de5a76..9bfbf8b55e2 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx @@ -18,7 +18,7 @@ export const MovieScenesPanel: React.FC = ({ // if movie is already present, then we modify it, otherwise add let movieCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "movies"; - }) as MoviesCriterion; + }) as MoviesCriterion | undefined; if ( movieCriterion && diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx index a1b9a7ae9bf..b7df466c08e 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx @@ -8,7 +8,6 @@ import React, { useState, } from "react"; import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js"; -import abLoopPlugin from "videojs-abloop"; import useScript from "src/hooks/useScript"; import "videojs-contrib-dash"; import "videojs-mobile-ui"; @@ -24,12 +23,6 @@ import "./big-buttons"; import "./track-activity"; import "./vrmode"; 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 { useSceneSaveActivity, useSceneIncrementPlayCount, @@ -47,6 +40,17 @@ import { languageMap } from "src/utils/caption"; import { VIDEO_PLAYER_ID } from "./util"; 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 seekStep(step: number) { const time = player.currentTime() + step; @@ -378,8 +382,6 @@ export const ScenePlayer: React.FC = ({ videoEl.classList.add("vjs-big-play-centered"); videoRef.current!.appendChild(videoEl); - abLoopPlugin(window, videojs); - const vjs = videojs(videoEl, options); /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx index 51983ada9c6..9ba03490192 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx @@ -145,15 +145,14 @@ export const SceneMarkerForm: React.FC = ({
formik.setFieldValue("seconds", s)} + value={formik.values.seconds ?? 0} + setValue={(v) => formik.setFieldValue("seconds", v ?? null)} onReset={() => formik.setFieldValue( "seconds", Math.round(getPlayerPosition() ?? 0) ) } - numericValue={formik.values.seconds} - mandatory />
diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index 4d8c5544c41..49f72b52db1 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -356,8 +356,8 @@ export const SettingsInterfacePanel: React.FC = () => { onChange={(v) => saveInterface({ maximumLoopDuration: v })} renderField={(value, setValue) => ( setValue(duration ?? 0)} + value={value} + setValue={(duration) => setValue(duration ?? 0)} /> )} renderValue={(v) => { diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 38a1ccb7932..57a8bf99fc0 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -43,17 +43,13 @@ export const SettingsServicesPanel: React.FC = () => { } = React.useContext(SettingStateContext); // undefined to hide dialog, true for enable, false for disable - const [enableDisable, setEnableDisable] = useState( - undefined - ); + const [enableDisable, setEnableDisable] = useState(); const [enableUntilRestart, setEnableUntilRestart] = useState(false); - const [enableDuration, setEnableDuration] = useState( - undefined - ); + const [enableDuration, setEnableDuration] = useState(0); const [ipEntry, setIPEntry] = useState(""); - const [tempIP, setTempIP] = useState(); + const [tempIP, setTempIP] = useState(); const { data: statusData, loading, refetch: statusRefetch } = useDLNAStatus(); @@ -273,8 +269,8 @@ export const SettingsServicesPanel: React.FC = () => { setEnableDuration(v ?? 0)} + value={enableDuration} + setValue={(v) => setEnableDuration(v ?? 0)} disabled={enableUntilRestart} /> @@ -315,8 +311,8 @@ export const SettingsServicesPanel: React.FC = () => { setEnableDuration(v ?? 0)} + value={enableDuration} + setValue={(v) => setEnableDuration(v ?? 0)} disabled={enableUntilRestart} /> diff --git a/ui/v2.5/src/components/Shared/DurationInput.tsx b/ui/v2.5/src/components/Shared/DurationInput.tsx index 0e346acd759..b0d396df143 100644 --- a/ui/v2.5/src/components/Shared/DurationInput.tsx +++ b/ui/v2.5/src/components/Shared/DurationInput.tsx @@ -3,67 +3,58 @@ import { faChevronUp, faClock, } 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 { Icon } from "./Icon"; import DurationUtils from "src/utils/duration"; interface IProps { disabled?: boolean; - numericValue: number | undefined; - mandatory?: boolean; - onValueChange( - valueAsNumber: number | undefined, - valueAsString?: string - ): void; + value: number | undefined; + setValue(value: number | undefined): void; onReset?(): void; className?: string; placeholder?: string; } -export const DurationInput: React.FC = (props: IProps) => { - const [value, setValue] = useState( - props.numericValue !== undefined - ? DurationUtils.secondsToString(props.numericValue) - : undefined - ); +export const DurationInput: React.FC = ({ + disabled, + value, + setValue, + onReset, + className, + placeholder, +}) => { + const [tmpValue, setTmpValue] = useState(); - useEffect(() => { - if (props.numericValue !== undefined || props.mandatory) { - setValue(DurationUtils.secondsToString(props.numericValue ?? 0)); - } else { - setValue(undefined); - } - }, [props.numericValue, props.mandatory]); + function onChange(e: React.ChangeEvent) { + setTmpValue(e.currentTarget.value); + } - function increment() { - if (value === undefined) { - return; + function onBlur() { + if (tmpValue !== undefined) { + setValue(DurationUtils.stringToSeconds(tmpValue)); + setTmpValue(undefined); } + } - let seconds = DurationUtils.stringToSeconds(value); - seconds += 1; - props.onValueChange(seconds, DurationUtils.secondsToString(seconds)); + function increment() { + setTmpValue(undefined); + setValue((value ?? 0) + 1); } function decrement() { - if (value === undefined) { - return; - } - - let seconds = DurationUtils.stringToSeconds(value); - seconds -= 1; - props.onValueChange(seconds, DurationUtils.secondsToString(seconds)); + setTmpValue(undefined); + setValue((value ?? 0) - 1); } function renderButtons() { - if (!props.disabled) { + if (!disabled) { return ( ); } } + let inputValue = ""; + if (tmpValue !== undefined) { + inputValue = tmpValue; + } else if (value !== undefined) { + inputValue = DurationUtils.secondsToString(value); + } + return ( -
+
) => - setValue(e.currentTarget.value) - } - 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 - } + disabled={disabled} + value={inputValue} + onChange={onChange} + onBlur={onBlur} + placeholder={placeholder ? `${placeholder} (hh:mm:ss)` : "hh:mm:ss"} /> {maybeRenderReset()} diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioChildrenPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioChildrenPanel.tsx index 134dabe69d4..8048de66a48 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioChildrenPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioChildrenPanel.tsx @@ -18,7 +18,7 @@ export const StudioChildrenPanel: React.FC = ({ // if studio is already present, then we modify it, otherwise add let parentStudioCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "parents"; - }) as ParentStudiosCriterion; + }) as ParentStudiosCriterion | undefined; if ( parentStudioCriterion && diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagMarkersPanel.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagMarkersPanel.tsx index 0713f13d524..95d2d56074b 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/TagMarkersPanel.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/TagMarkersPanel.tsx @@ -21,7 +21,7 @@ export const TagMarkersPanel: React.FC = ({ // if tag is already present, then we modify it, otherwise add let tagCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "tags"; - }) as TagsCriterion; + }) as TagsCriterion | undefined; if ( tagCriterion && diff --git a/ui/v2.5/src/core/performers.ts b/ui/v2.5/src/core/performers.ts index c69d93c5661..81d7b0b8c6a 100644 --- a/ui/v2.5/src/core/performers.ts +++ b/ui/v2.5/src/core/performers.ts @@ -16,7 +16,7 @@ export const usePerformerFilterHook = ( // if performers is already present, then we modify it, otherwise add let performerCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "performers"; - }) as PerformersCriterion; + }) as PerformersCriterion | undefined; if (performerCriterion) { if ( diff --git a/ui/v2.5/src/core/studios.ts b/ui/v2.5/src/core/studios.ts index 95649c1992e..07ddce0b4a9 100644 --- a/ui/v2.5/src/core/studios.ts +++ b/ui/v2.5/src/core/studios.ts @@ -12,7 +12,7 @@ export const useStudioFilterHook = (studio: GQL.StudioDataFragment) => { // if studio is already present, then we modify it, otherwise add let studioCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "studios"; - }) as StudiosCriterion; + }) as StudiosCriterion | undefined; if (studioCriterion) { // we should be showing studio only. Remove other values diff --git a/ui/v2.5/src/core/tags.ts b/ui/v2.5/src/core/tags.ts index d4f6fc1bfaf..b34d8ea43c7 100644 --- a/ui/v2.5/src/core/tags.ts +++ b/ui/v2.5/src/core/tags.ts @@ -17,7 +17,7 @@ export const useTagFilterHook = (tag: GQL.TagDataFragment) => { // if tag is already present, then we modify it, otherwise add let tagCriterion = filter.criteria.find((c) => { return c.criterionOption.type === "tags"; - }) as TagsCriterion; + }) as TagsCriterion | undefined; if (tagCriterion) { if ( diff --git a/ui/v2.5/src/models/list-filter/criteria/captions.ts b/ui/v2.5/src/models/list-filter/criteria/captions.ts index 2d0fbdb1d4f..fd4bb5be8d1 100644 --- a/ui/v2.5/src/models/list-filter/criteria/captions.ts +++ b/ui/v2.5/src/models/list-filter/criteria/captions.ts @@ -1,33 +1,28 @@ import { CriterionModifier } from "src/core/generated-graphql"; import { languageMap, valueToCode } from "src/utils/caption"; -import { CriterionType } from "../types"; import { CriterionOption, StringCriterion } from "./criterion"; const languageStrings = Array.from(languageMap.values()); -class CaptionsCriterionOptionType extends CriterionOption { - constructor(value: CriterionType) { - super({ - messageID: value, - type: value, - modifierOptions: [ - CriterionModifier.Includes, - CriterionModifier.Excludes, - CriterionModifier.IsNull, - CriterionModifier.NotNull, - ], - defaultModifier: CriterionModifier.Includes, - options: languageStrings, - makeCriterion: () => new CaptionCriterion(), - }); - } -} - -export const CaptionsCriterionOption = new CaptionsCriterionOptionType( - "captions" -); +export const CaptionsCriterionOption = new CriterionOption({ + messageID: "captions", + type: "captions", + modifierOptions: [ + CriterionModifier.Includes, + CriterionModifier.Excludes, + CriterionModifier.IsNull, + CriterionModifier.NotNull, + ], + defaultModifier: CriterionModifier.Includes, + options: languageStrings, + makeCriterion: () => new CaptionCriterion(), +}); export class CaptionCriterion extends StringCriterion { + constructor() { + super(CaptionsCriterionOption); + } + protected toCriterionInput() { const value = valueToCode(this.value) ?? ""; @@ -36,8 +31,4 @@ export class CaptionCriterion extends StringCriterion { modifier: this.modifier, }; } - - constructor() { - super(CaptionsCriterionOption); - } } diff --git a/ui/v2.5/src/models/list-filter/criteria/circumcised.ts b/ui/v2.5/src/models/list-filter/criteria/circumcised.ts index dd8fbfbb171..bff2c6052b2 100644 --- a/ui/v2.5/src/models/list-filter/criteria/circumcised.ts +++ b/ui/v2.5/src/models/list-filter/criteria/circumcised.ts @@ -9,13 +9,13 @@ import { CriterionOption, MultiStringCriterion } from "./criterion"; export const CircumcisedCriterionOption = new CriterionOption({ messageID: "circumcised", type: "circumcised", - options: circumcisedStrings, modifierOptions: [ CriterionModifier.Includes, CriterionModifier.Excludes, CriterionModifier.IsNull, CriterionModifier.NotNull, ], + options: circumcisedStrings, makeCriterion: () => new CircumcisedCriterion(), }); diff --git a/ui/v2.5/src/models/list-filter/criteria/country.ts b/ui/v2.5/src/models/list-filter/criteria/country.ts index 36c52a9f789..2430c2a59e3 100644 --- a/ui/v2.5/src/models/list-filter/criteria/country.ts +++ b/ui/v2.5/src/models/list-filter/criteria/country.ts @@ -1,20 +1,13 @@ import { IntlShape } from "react-intl"; import { CriterionModifier } from "src/core/generated-graphql"; import { getCountryByISO } from "src/utils/country"; -import { - CriterionOption, - StringCriterion, - StringCriterionOption, -} from "./criterion"; +import { StringCriterion, StringCriterionOption } from "./criterion"; -export const CountryCriterionOption = new CriterionOption({ - messageID: "country", - type: "country", - modifierOptions: StringCriterionOption.modifierOptions, - defaultModifier: StringCriterionOption.defaultModifier, - makeCriterion: () => new CountryCriterion(), - inputType: StringCriterionOption.inputType, -}); +export const CountryCriterionOption = new StringCriterionOption( + "country", + "country", + () => new CountryCriterion() +); export class CountryCriterion extends StringCriterion { constructor() { diff --git a/ui/v2.5/src/models/list-filter/criteria/criterion.ts b/ui/v2.5/src/models/list-filter/criteria/criterion.ts index eab7e98a24b..6b2b79ac2e1 100644 --- a/ui/v2.5/src/models/list-filter/criteria/criterion.ts +++ b/ui/v2.5/src/models/list-filter/criteria/criterion.ts @@ -1,13 +1,10 @@ -/* eslint-disable consistent-return */ /* eslint @typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ - import { IntlShape } from "react-intl"; import { CriterionModifier, HierarchicalMultiCriterionInput, IntCriterionInput, MultiCriterionInput, - PHashDuplicationCriterionInput, DateCriterionInput, TimestampCriterionInput, ConfigDataFragment, @@ -152,22 +149,18 @@ export abstract class Criterion { this.modifier = encodedCriterion.modifier; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public apply(outputFilter: Record) { - // eslint-disable-next-line no-param-reassign + public apply(outputFilter: Record) { outputFilter[this.criterionOption.type] = this.toCriterionInput(); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected toCriterionInput(): any { + protected toCriterionInput(): unknown { return { value: this.value, modifier: this.modifier, }; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public toSavedFilter(outputFilter: Record) { + public toSavedFilter(outputFilter: Record) { outputFilter[this.criterionOption.type] = { value: this.value, 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 { - 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 { - 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 - ) { - 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 { - 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 { constructor( messageID: string, value: CriterionType, includeAll: boolean, - inputType: InputType + inputType: InputType, + makeCriterion?: () => Criterion ) { const modifierOptions = [ CriterionModifier.Includes, @@ -513,8 +245,10 @@ export class ILabeledIdCriterionOption extends CriterionOption { type: value, modifierOptions, defaultModifier, - makeCriterion: () => new ILabeledIdCriterion(this), inputType, + makeCriterion: makeCriterion + ? makeCriterion + : () => new ILabeledIdCriterion(this), }); } } @@ -689,24 +423,249 @@ export class IHierarchicalLabeledIdCriterion extends Criterion Criterion + ) { super({ messageID, type: value, modifierOptions: [ CriterionModifier.Equals, CriterionModifier.NotEquals, - CriterionModifier.GreaterThan, - CriterionModifier.LessThan, - CriterionModifier.Between, - CriterionModifier.NotBetween, + CriterionModifier.Includes, + CriterionModifier.Excludes, + CriterionModifier.IsNull, + CriterionModifier.NotNull, + CriterionModifier.MatchesRegex, + CriterionModifier.NotMatchesRegex, ], defaultModifier: CriterionModifier.Equals, - inputType: "number", - makeCriterion: () => new NumberCriterion(this), - }); - } + 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) { + 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 { + 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 { + 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 + ) { + 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 + ) { + 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 + ) { + super({ + messageID, + type: value, + modifierOptions: [ + CriterionModifier.Equals, + CriterionModifier.NotEquals, + CriterionModifier.GreaterThan, + CriterionModifier.LessThan, + CriterionModifier.Between, + CriterionModifier.NotBetween, + ], + defaultModifier: CriterionModifier.Equals, + inputType: "number", + makeCriterion: makeCriterion + ? makeCriterion + : () => new NumberCriterion(this), + }); + } } export function createMandatoryNumberCriterionOption( @@ -716,9 +675,20 @@ export function createMandatoryNumberCriterionOption( return new MandatoryNumberCriterionOption(messageID ?? value, value); } -export class DurationCriterion extends Criterion { - constructor(type: CriterionOption) { - super(type, { value: undefined, value2: undefined }); +export class NumberCriterion extends Criterion { + 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 { @@ -730,17 +700,15 @@ export class DurationCriterion extends Criterion { } protected getLabelValue(_intl: IntlShape) { - return this.modifier === CriterionModifier.Between || + const { value, value2 } = this.value; + if ( + this.modifier === CriterionModifier.Between || this.modifier === CriterionModifier.NotBetween - ? `${DurationUtils.secondsToString( - this.value.value ?? 0 - )} ${DurationUtils.secondsToString(this.value.value2 ?? 0)}` - : this.modifier === CriterionModifier.GreaterThan || - this.modifier === CriterionModifier.LessThan || - this.modifier === CriterionModifier.Equals || - this.modifier === CriterionModifier.NotEquals - ? DurationUtils.secondsToString(this.value.value ?? 0) - : "?"; + ) { + return `${value}, ${value2 ?? 0}`; + } else { + return `${value}`; + } } public isValid(): boolean { @@ -766,18 +734,78 @@ export class DurationCriterion extends Criterion { return true; } + + constructor(type: CriterionOption) { + super(type, { value: undefined, value2: undefined }); + } } -export class PhashDuplicateCriterion extends StringCriterion { - protected toCriterionInput(): PHashDuplicationCriterionInput { +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 { + constructor(type: CriterionOption) { + super(type, { value: undefined, value2: undefined }); + } + + protected toCriterionInput(): IntCriterionInput { return { - duplicated: this.value === "true", + modifier: this.modifier, + value: this.value?.value ?? 0, + value2: this.value?.value2, }; } + + protected getLabelValue(_intl: IntlShape) { + 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 + ) { + return `${value}, ${value2}`; + } 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; + } } export class DateCriterionOption extends CriterionOption { - constructor(messageID: string, value: CriterionType, options?: Option[]) { + constructor(messageID: string, value: CriterionType) { super({ messageID, type: value, @@ -792,7 +820,6 @@ export class DateCriterionOption extends CriterionOption { CriterionModifier.NotBetween, ], defaultModifier: CriterionModifier.Equals, - options, inputType: "text", makeCriterion: () => new DateCriterion(this), }); @@ -857,7 +884,7 @@ export class DateCriterion extends Criterion { } export class TimestampCriterionOption extends CriterionOption { - constructor(messageID: string, value: CriterionType, options?: Option[]) { + constructor(messageID: string, value: CriterionType) { super({ messageID, type: value, @@ -870,7 +897,6 @@ export class TimestampCriterionOption extends CriterionOption { CriterionModifier.NotBetween, ], defaultModifier: CriterionModifier.GreaterThan, - options, inputType: "text", makeCriterion: () => new TimestampCriterion(this), }); @@ -881,6 +907,28 @@ export function createTimestampCriterionOption(value: CriterionType) { 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 { public encodeValue() { return { @@ -944,26 +992,3 @@ export class TimestampCriterion extends Criterion { 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); -} diff --git a/ui/v2.5/src/models/list-filter/criteria/factory.ts b/ui/v2.5/src/models/list-filter/criteria/factory.ts deleted file mode 100644 index 1687d97aa04..00000000000 --- a/ui/v2.5/src/models/list-filter/criteria/factory.ts +++ /dev/null @@ -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); -} diff --git a/ui/v2.5/src/models/list-filter/criteria/favorite.ts b/ui/v2.5/src/models/list-filter/criteria/favorite.ts index 5479980c3cb..f5412fd4a0f 100644 --- a/ui/v2.5/src/models/list-filter/criteria/favorite.ts +++ b/ui/v2.5/src/models/list-filter/criteria/favorite.ts @@ -2,7 +2,8 @@ import { BooleanCriterion, BooleanCriterionOption } from "./criterion"; export const FavoriteCriterionOption = new BooleanCriterionOption( "favourite", - "filter_favorites" + "filter_favorites", + () => new FavoriteCriterion() ); export class FavoriteCriterion extends BooleanCriterion { @@ -13,7 +14,8 @@ export class FavoriteCriterion extends BooleanCriterion { export const PerformerFavoriteCriterionOption = new BooleanCriterionOption( "performer_favorite", - "performer_favorite" + "performer_favorite", + () => new PerformerFavoriteCriterion() ); export class PerformerFavoriteCriterion extends BooleanCriterion { diff --git a/ui/v2.5/src/models/list-filter/criteria/galleries.ts b/ui/v2.5/src/models/list-filter/criteria/galleries.ts index 60368853a2d..f59f3a3d4f4 100644 --- a/ui/v2.5/src/models/list-filter/criteria/galleries.ts +++ b/ui/v2.5/src/models/list-filter/criteria/galleries.ts @@ -2,15 +2,16 @@ import { ILabeledIdCriterion, ILabeledIdCriterionOption } from "./criterion"; const inputType = "galleries"; -const galleriesCriterionOption = new ILabeledIdCriterionOption( +export const GalleriesCriterionOption = new ILabeledIdCriterionOption( "galleries", "galleries", true, - inputType + inputType, + () => new GalleriesCriterion() ); export class GalleriesCriterion extends ILabeledIdCriterion { constructor() { - super(galleriesCriterionOption); + super(GalleriesCriterionOption); } } diff --git a/ui/v2.5/src/models/list-filter/criteria/has-chapters.ts b/ui/v2.5/src/models/list-filter/criteria/has-chapters.ts index 8f38783f034..f9fa7665cb4 100644 --- a/ui/v2.5/src/models/list-filter/criteria/has-chapters.ts +++ b/ui/v2.5/src/models/list-filter/criteria/has-chapters.ts @@ -1,18 +1,16 @@ -import { CriterionOption, StringCriterion } from "./criterion"; +import { + StringBooleanCriterion, + StringBooleanCriterionOption, +} from "./criterion"; -export const HasChaptersCriterionOption = new CriterionOption({ - messageID: "hasChapters", - type: "has_chapters", - options: [true.toString(), false.toString()], - makeCriterion: () => new HasChaptersCriterion(), -}); +export const HasChaptersCriterionOption = new StringBooleanCriterionOption( + "hasChapters", + "has_chapters", + () => new HasChaptersCriterion() +); -export class HasChaptersCriterion extends StringCriterion { +export class HasChaptersCriterion extends StringBooleanCriterion { constructor() { super(HasChaptersCriterionOption); } - - protected toCriterionInput(): string { - return this.value; - } } diff --git a/ui/v2.5/src/models/list-filter/criteria/has-markers.ts b/ui/v2.5/src/models/list-filter/criteria/has-markers.ts index 23a72152ff5..947b2e25a8e 100644 --- a/ui/v2.5/src/models/list-filter/criteria/has-markers.ts +++ b/ui/v2.5/src/models/list-filter/criteria/has-markers.ts @@ -1,18 +1,16 @@ -import { CriterionOption, StringCriterion } from "./criterion"; +import { + StringBooleanCriterion, + StringBooleanCriterionOption, +} from "./criterion"; -export const HasMarkersCriterionOption = new CriterionOption({ - messageID: "hasMarkers", - type: "has_markers", - options: [true.toString(), false.toString()], - makeCriterion: () => new HasMarkersCriterion(), -}); +export const HasMarkersCriterionOption = new StringBooleanCriterionOption( + "hasMarkers", + "has_markers", + () => new HasMarkersCriterion() +); -export class HasMarkersCriterion extends StringCriterion { +export class HasMarkersCriterion extends StringBooleanCriterion { constructor() { super(HasMarkersCriterionOption); } - - protected toCriterionInput(): string { - return this.value; - } } diff --git a/ui/v2.5/src/models/list-filter/criteria/interactive.ts b/ui/v2.5/src/models/list-filter/criteria/interactive.ts index fd7da07dd54..0135c0166e4 100644 --- a/ui/v2.5/src/models/list-filter/criteria/interactive.ts +++ b/ui/v2.5/src/models/list-filter/criteria/interactive.ts @@ -2,7 +2,8 @@ import { BooleanCriterion, BooleanCriterionOption } from "./criterion"; export const InteractiveCriterionOption = new BooleanCriterionOption( "interactive", - "interactive" + "interactive", + () => new InteractiveCriterion() ); export class InteractiveCriterion extends BooleanCriterion { diff --git a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts index acd96ad8c60..6ca7ca91944 100644 --- a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts +++ b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts @@ -3,26 +3,25 @@ import { CriterionType } from "../types"; import { CriterionOption, StringCriterion, Option } from "./criterion"; export class IsMissingCriterion extends StringCriterion { - public modifierOptions = []; - protected toCriterionInput(): string { return this.value; } } -class IsMissingCriterionOptionClass extends CriterionOption { +class IsMissingCriterionOption extends CriterionOption { constructor(messageID: string, type: CriterionType, options: Option[]) { super({ messageID, type, options, + modifierOptions: [], defaultModifier: CriterionModifier.Equals, makeCriterion: () => new IsMissingCriterion(this), }); } } -export const SceneIsMissingCriterionOption = new IsMissingCriterionOptionClass( +export const SceneIsMissingCriterionOption = new IsMissingCriterionOption( "isMissing", "is_missing", [ @@ -40,14 +39,16 @@ export const SceneIsMissingCriterionOption = new IsMissingCriterionOptionClass( ] ); -export const ImageIsMissingCriterionOption = new IsMissingCriterionOptionClass( +export const ImageIsMissingCriterionOption = new IsMissingCriterionOption( "isMissing", "is_missing", ["title", "galleries", "studio", "performers", "tags"] ); -export const PerformerIsMissingCriterionOption = - new IsMissingCriterionOptionClass("isMissing", "is_missing", [ +export const PerformerIsMissingCriterionOption = new IsMissingCriterionOption( + "isMissing", + "is_missing", + [ "url", "twitter", "instagram", @@ -67,33 +68,28 @@ export const PerformerIsMissingCriterionOption = "image", "details", "stash_id", - ]); + ] +); -export const GalleryIsMissingCriterionOption = - new IsMissingCriterionOptionClass("isMissing", "is_missing", [ - "title", - "details", - "url", - "date", - "studio", - "performers", - "tags", - "scenes", - ]); +export const GalleryIsMissingCriterionOption = new IsMissingCriterionOption( + "isMissing", + "is_missing", + ["title", "details", "url", "date", "studio", "performers", "tags", "scenes"] +); -export const TagIsMissingCriterionOption = new IsMissingCriterionOptionClass( +export const TagIsMissingCriterionOption = new IsMissingCriterionOption( "isMissing", "is_missing", ["image"] ); -export const StudioIsMissingCriterionOption = new IsMissingCriterionOptionClass( +export const StudioIsMissingCriterionOption = new IsMissingCriterionOption( "isMissing", "is_missing", ["image", "stash_id", "details"] ); -export const MovieIsMissingCriterionOption = new IsMissingCriterionOptionClass( +export const MovieIsMissingCriterionOption = new IsMissingCriterionOption( "isMissing", "is_missing", ["front_image", "back_image", "scenes"] diff --git a/ui/v2.5/src/models/list-filter/criteria/movies.ts b/ui/v2.5/src/models/list-filter/criteria/movies.ts index 0cd7926eda1..547fc40b6f9 100644 --- a/ui/v2.5/src/models/list-filter/criteria/movies.ts +++ b/ui/v2.5/src/models/list-filter/criteria/movies.ts @@ -6,7 +6,8 @@ export const MoviesCriterionOption = new ILabeledIdCriterionOption( "movies", "movies", false, - inputType + inputType, + () => new MoviesCriterion() ); export class MoviesCriterion extends ILabeledIdCriterion { diff --git a/ui/v2.5/src/models/list-filter/criteria/none.ts b/ui/v2.5/src/models/list-filter/criteria/none.ts deleted file mode 100644 index 9aabef0f9de..00000000000 --- a/ui/v2.5/src/models/list-filter/criteria/none.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Criterion, StringCriterionOption } from "./criterion"; - -export const NoneCriterionOption = new StringCriterionOption("none", "none"); -export class NoneCriterion extends Criterion { - constructor() { - super(NoneCriterionOption, "none"); - } - - protected getLabelValue(): string { - return ""; - } -} diff --git a/ui/v2.5/src/models/list-filter/criteria/organized.ts b/ui/v2.5/src/models/list-filter/criteria/organized.ts index a62384100e5..a3689ba710c 100644 --- a/ui/v2.5/src/models/list-filter/criteria/organized.ts +++ b/ui/v2.5/src/models/list-filter/criteria/organized.ts @@ -2,7 +2,8 @@ import { BooleanCriterion, BooleanCriterionOption } from "./criterion"; export const OrganizedCriterionOption = new BooleanCriterionOption( "organized", - "organized" + "organized", + () => new OrganizedCriterion() ); export class OrganizedCriterion extends BooleanCriterion { diff --git a/ui/v2.5/src/models/list-filter/criteria/path.ts b/ui/v2.5/src/models/list-filter/criteria/path.ts new file mode 100644 index 00000000000..2b57faf85a0 --- /dev/null +++ b/ui/v2.5/src/models/list-filter/criteria/path.ts @@ -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); + } +} diff --git a/ui/v2.5/src/models/list-filter/criteria/performers.ts b/ui/v2.5/src/models/list-filter/criteria/performers.ts index 612c7b47b49..6ef7c39aeb7 100644 --- a/ui/v2.5/src/models/list-filter/criteria/performers.ts +++ b/ui/v2.5/src/models/list-filter/criteria/performers.ts @@ -24,8 +24,8 @@ export const PerformersCriterionOption = new CriterionOption({ type: "performers", modifierOptions, defaultModifier, - makeCriterion: () => new PerformersCriterion(), inputType, + makeCriterion: () => new PerformersCriterion(), }); export class PerformersCriterion extends Criterion { diff --git a/ui/v2.5/src/models/list-filter/criteria/phash.ts b/ui/v2.5/src/models/list-filter/criteria/phash.ts index 433b06b9bb2..1393d8aa622 100644 --- a/ui/v2.5/src/models/list-filter/criteria/phash.ts +++ b/ui/v2.5/src/models/list-filter/criteria/phash.ts @@ -1,13 +1,14 @@ import { CriterionModifier, PhashDistanceCriterionInput, + PHashDuplicationCriterionInput, } from "src/core/generated-graphql"; import { IPhashDistanceValue } from "../types"; import { BooleanCriterionOption, Criterion, CriterionOption, - PhashDuplicateCriterion, + StringCriterion, } from "./criterion"; export const PhashCriterionOption = new CriterionOption({ @@ -56,8 +57,14 @@ export const DuplicatedCriterionOption = new BooleanCriterionOption( () => new DuplicatedCriterion() ); -export class DuplicatedCriterion extends PhashDuplicateCriterion { +export class DuplicatedCriterion extends StringCriterion { constructor() { super(DuplicatedCriterionOption); } + + protected toCriterionInput(): PHashDuplicationCriterionInput { + return { + duplicated: this.value === "true", + }; + } } diff --git a/ui/v2.5/src/models/list-filter/criteria/rating.ts b/ui/v2.5/src/models/list-filter/criteria/rating.ts index 66605bcea02..1135115a231 100644 --- a/ui/v2.5/src/models/list-filter/criteria/rating.ts +++ b/ui/v2.5/src/models/list-filter/criteria/rating.ts @@ -8,7 +8,7 @@ import { ConfigDataFragment, CriterionModifier, IntCriterionInput, -} from "../../../core/generated-graphql"; +} from "src/core/generated-graphql"; import { INumberValue } from "../types"; import { Criterion, CriterionOption } from "./criterion"; import { IUIConfig } from "src/core/config"; diff --git a/ui/v2.5/src/models/list-filter/criteria/resolution.ts b/ui/v2.5/src/models/list-filter/criteria/resolution.ts index 6961e22b1b9..e7c705a28d6 100644 --- a/ui/v2.5/src/models/list-filter/criteria/resolution.ts +++ b/ui/v2.5/src/models/list-filter/criteria/resolution.ts @@ -11,20 +11,7 @@ import { StringCriterion, } from "./criterion"; -abstract class AbstractResolutionCriterion extends StringCriterion { - protected toCriterionInput(): ResolutionCriterionInput | undefined { - const value = stringToResolution(this.value); - - if (value !== undefined) { - return { - value, - modifier: this.modifier, - }; - } - } -} - -class ResolutionCriterionOptionType extends CriterionOption { +class BaseResolutionCriterionOption extends CriterionOption { constructor( value: CriterionType, makeCriterion: () => Criterion @@ -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", () => new ResolutionCriterion() ); -export class ResolutionCriterion extends AbstractResolutionCriterion { + +export class ResolutionCriterion extends BaseResolutionCriterion { constructor() { super(ResolutionCriterionOption); } } export const AverageResolutionCriterionOption = - new ResolutionCriterionOptionType( + new BaseResolutionCriterionOption( "average_resolution", () => new AverageResolutionCriterion() ); -export class AverageResolutionCriterion extends AbstractResolutionCriterion { +export class AverageResolutionCriterion extends BaseResolutionCriterion { constructor() { super(AverageResolutionCriterionOption); } diff --git a/ui/v2.5/src/models/list-filter/criteria/studios.ts b/ui/v2.5/src/models/list-filter/criteria/studios.ts index e238943c1c5..e6775d57f32 100644 --- a/ui/v2.5/src/models/list-filter/criteria/studios.ts +++ b/ui/v2.5/src/models/list-filter/criteria/studios.ts @@ -20,8 +20,8 @@ export const StudiosCriterionOption = new CriterionOption({ type: "studios", modifierOptions, defaultModifier, - makeCriterion: () => new StudiosCriterion(), inputType, + makeCriterion: () => new StudiosCriterion(), }); export class StudiosCriterion extends IHierarchicalLabeledIdCriterion { @@ -34,8 +34,10 @@ export const ParentStudiosCriterionOption = new ILabeledIdCriterionOption( "parent_studios", "parents", false, - inputType + inputType, + () => new ParentStudiosCriterion() ); + export class ParentStudiosCriterion extends ILabeledIdCriterion { constructor() { super(ParentStudiosCriterionOption); diff --git a/ui/v2.5/src/models/list-filter/criteria/tags.ts b/ui/v2.5/src/models/list-filter/criteria/tags.ts index fe19fc2860c..e85392b6500 100644 --- a/ui/v2.5/src/models/list-filter/criteria/tags.ts +++ b/ui/v2.5/src/models/list-filter/criteria/tags.ts @@ -20,7 +20,7 @@ const withoutEqualsModifierOptions = [ const defaultModifier = CriterionModifier.IncludesAll; const inputType = "tags"; -export class TagsCriterionOptionClass extends CriterionOption { +class BaseTagsCriterionOption extends CriterionOption { constructor( messageID: string, type: CriterionType, @@ -31,37 +31,37 @@ export class TagsCriterionOptionClass extends CriterionOption { type, modifierOptions, defaultModifier, - makeCriterion: () => new TagsCriterion(this), inputType, + makeCriterion: () => new TagsCriterion(this), }); } } -export const TagsCriterionOption = new TagsCriterionOptionClass( +export const TagsCriterionOption = new BaseTagsCriterionOption( "tags", "tags", defaultModifierOptions ); -export const SceneTagsCriterionOption = new TagsCriterionOptionClass( +export const SceneTagsCriterionOption = new BaseTagsCriterionOption( "scene_tags", "scene_tags", defaultModifierOptions ); -export const PerformerTagsCriterionOption = new TagsCriterionOptionClass( +export const PerformerTagsCriterionOption = new BaseTagsCriterionOption( "performer_tags", "performer_tags", withoutEqualsModifierOptions ); -export const ParentTagsCriterionOption = new TagsCriterionOptionClass( +export const ParentTagsCriterionOption = new BaseTagsCriterionOption( "parent_tags", "parents", withoutEqualsModifierOptions ); -export const ChildTagsCriterionOption = new TagsCriterionOptionClass( +export const ChildTagsCriterionOption = new BaseTagsCriterionOption( "sub_tags", "children", withoutEqualsModifierOptions diff --git a/ui/v2.5/src/models/list-filter/filter.ts b/ui/v2.5/src/models/list-filter/filter.ts index 037540772dc..fbcd5b256fb 100644 --- a/ui/v2.5/src/models/list-filter/filter.ts +++ b/ui/v2.5/src/models/list-filter/filter.ts @@ -6,7 +6,7 @@ import { SortDirectionEnum, } from "src/core/generated-graphql"; import { Criterion, CriterionValue } from "./criteria/criterion"; -import { makeCriteria } from "./criteria/factory"; +import { getFilterOptions } from "./factory"; import { CriterionType, DisplayMode } from "./types"; interface IDecodedParams { @@ -128,16 +128,9 @@ export class ListFilterModel { for (const jsonString of params.c) { try { const encodedCriterion = JSON.parse(jsonString); - const criterion = makeCriteria( - this.mode, - encodedCriterion.type, - this.config - ); - // it's possible that we have unsupported criteria. Just skip if so. - if (criterion) { - criterion.setFromEncodedCriterion(encodedCriterion); - this.criteria.push(criterion); - } + const criterion = this.makeCriterion(encodedCriterion.type); + criterion.setFromEncodedCriterion(encodedCriterion); + this.criteria.push(criterion); } catch (err) { // eslint-disable-next-line no-console console.error("Failed to parse encoded criterion:", err); @@ -280,16 +273,9 @@ export class ListFilterModel { this.criteria = []; if (objectFilter) { Object.keys(objectFilter).forEach((key) => { - const criterion = makeCriteria( - 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]); - this.criteria.push(criterion); - } + const criterion = this.makeCriterion(key as CriterionType); + criterion.setFromEncodedCriterion(objectFilter[key]); + this.criteria.push(criterion); }); } } @@ -426,6 +412,18 @@ export class ListFilterModel { 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. public makeFindFilter(): FindFilterType { @@ -439,8 +437,7 @@ export class ListFilterModel { } public makeFilter() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const output: Record = {}; + const output: Record = {}; this.criteria.forEach((criterion) => { criterion.apply(output); }); @@ -449,8 +446,7 @@ export class ListFilterModel { } public makeSavedFindFilter() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const output: Record = {}; + const output: Record = {}; this.criteria.forEach((criterion) => { criterion.toSavedFilter(output); }); @@ -458,8 +454,7 @@ export class ListFilterModel { return output; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public makeUIOptions(): Record { + public makeUIOptions(): Record { return { display_mode: this.displayMode, zoom_index: this.zoomIndex, diff --git a/ui/v2.5/src/models/list-filter/galleries.ts b/ui/v2.5/src/models/list-filter/galleries.ts index 693eb0ccb86..8dc99d78b4c 100644 --- a/ui/v2.5/src/models/list-filter/galleries.ts +++ b/ui/v2.5/src/models/list-filter/galleries.ts @@ -3,7 +3,6 @@ import { createStringCriterionOption, createDateCriterionOption, createMandatoryTimestampCriterionOption, - createPathCriterionOption, } from "./criteria/criterion"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { GalleryIsMissingCriterionOption } from "./criteria/is-missing"; @@ -19,6 +18,7 @@ import { import { ListFilterOptions, MediaSortByOptions } from "./filter-options"; import { DisplayMode } from "./types"; import { RatingCriterionOption } from "./criteria/rating"; +import { PathCriterionOption } from "./criteria/path"; const defaultSortBy = "path"; @@ -44,7 +44,7 @@ const displayModeOptions = [ const criterionOptions = [ createStringCriterionOption("title"), createStringCriterionOption("details"), - createPathCriterionOption("path"), + PathCriterionOption, createStringCriterionOption("checksum", "media_info.checksum"), RatingCriterionOption, OrganizedCriterionOption, @@ -52,13 +52,13 @@ const criterionOptions = [ GalleryIsMissingCriterionOption, TagsCriterionOption, HasChaptersCriterionOption, - createStringCriterionOption("tag_count"), + createMandatoryNumberCriterionOption("tag_count"), PerformerTagsCriterionOption, PerformersCriterionOption, - createStringCriterionOption("performer_count"), + createMandatoryNumberCriterionOption("performer_count"), createMandatoryNumberCriterionOption("performer_age"), PerformerFavoriteCriterionOption, - createStringCriterionOption("image_count"), + createMandatoryNumberCriterionOption("image_count"), StudiosCriterionOption, createStringCriterionOption("url"), createMandatoryNumberCriterionOption("file_count", "zip_file_count"), diff --git a/ui/v2.5/src/models/list-filter/images.ts b/ui/v2.5/src/models/list-filter/images.ts index 3a54f34f46d..fdea73952c7 100644 --- a/ui/v2.5/src/models/list-filter/images.ts +++ b/ui/v2.5/src/models/list-filter/images.ts @@ -4,11 +4,11 @@ import { createStringCriterionOption, createMandatoryTimestampCriterionOption, createDateCriterionOption, - createPathCriterionOption, } from "./criteria/criterion"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { ImageIsMissingCriterionOption } from "./criteria/is-missing"; import { OrganizedCriterionOption } from "./criteria/organized"; +import { PathCriterionOption } from "./criteria/path"; import { PerformersCriterionOption } from "./criteria/performers"; import { RatingCriterionOption } from "./criteria/rating"; import { ResolutionCriterionOption } from "./criteria/resolution"; @@ -34,7 +34,7 @@ const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall]; const criterionOptions = [ createStringCriterionOption("title"), createMandatoryStringCriterionOption("checksum", "media_info.checksum"), - createPathCriterionOption("path"), + PathCriterionOption, OrganizedCriterionOption, createMandatoryNumberCriterionOption("o_counter"), ResolutionCriterionOption, diff --git a/ui/v2.5/src/models/list-filter/movies.ts b/ui/v2.5/src/models/list-filter/movies.ts index 3cc8f8da412..9a769e0249f 100644 --- a/ui/v2.5/src/models/list-filter/movies.ts +++ b/ui/v2.5/src/models/list-filter/movies.ts @@ -1,8 +1,8 @@ import { - createMandatoryNumberCriterionOption, createStringCriterionOption, createDateCriterionOption, createMandatoryTimestampCriterionOption, + createDurationCriterionOption, } from "./criteria/criterion"; import { MovieIsMissingCriterionOption } from "./criteria/is-missing"; import { StudiosCriterionOption } from "./criteria/studios"; @@ -29,7 +29,7 @@ const criterionOptions = [ createStringCriterionOption("name"), createStringCriterionOption("director"), createStringCriterionOption("synopsis"), - createMandatoryNumberCriterionOption("duration"), + createDurationCriterionOption("duration"), RatingCriterionOption, PerformersCriterionOption, createDateCriterionOption("date"), diff --git a/ui/v2.5/src/models/list-filter/performers.ts b/ui/v2.5/src/models/list-filter/performers.ts index bf63bb59661..421e47a63b5 100644 --- a/ui/v2.5/src/models/list-filter/performers.ts +++ b/ui/v2.5/src/models/list-filter/performers.ts @@ -5,7 +5,6 @@ import { createBooleanCriterionOption, createDateCriterionOption, createMandatoryTimestampCriterionOption, - NumberCriterionOption, } from "./criteria/criterion"; import { FavoriteCriterionOption } from "./criteria/favorite"; import { GenderCriterionOption } from "./criteria/gender"; @@ -94,9 +93,9 @@ const criterionOptions = [ createMandatoryNumberCriterionOption("gallery_count"), createMandatoryNumberCriterionOption("o_counter"), createBooleanCriterionOption("ignore_auto_tag"), - new NumberCriterionOption("height", "height_cm"), - ...numberCriteria.map((c) => createNumberCriterionOption(c)), CountryCriterionOption, + createNumberCriterionOption("height_cm", "height"), + ...numberCriteria.map((c) => createNumberCriterionOption(c)), ...stringCriteria.map((c) => createStringCriterionOption(c)), createDateCriterionOption("birthdate"), createDateCriterionOption("death_date"), diff --git a/ui/v2.5/src/models/list-filter/scenes.ts b/ui/v2.5/src/models/list-filter/scenes.ts index 5953b5f3982..d76adc05612 100644 --- a/ui/v2.5/src/models/list-filter/scenes.ts +++ b/ui/v2.5/src/models/list-filter/scenes.ts @@ -4,7 +4,7 @@ import { createStringCriterionOption, createDateCriterionOption, createMandatoryTimestampCriterionOption, - createPathCriterionOption, + createDurationCriterionOption, } from "./criteria/criterion"; import { HasMarkersCriterionOption } from "./criteria/has-markers"; import { SceneIsMissingCriterionOption } from "./criteria/is-missing"; @@ -28,6 +28,7 @@ import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { CaptionsCriterionOption } from "./criteria/captions"; import { StashIDCriterionOption } from "./criteria/stash-ids"; import { RatingCriterionOption } from "./criteria/rating"; +import { PathCriterionOption } from "./criteria/path"; const defaultSortBy = "date"; const sortByOptions = [ @@ -60,7 +61,7 @@ const displayModeOptions = [ const criterionOptions = [ createStringCriterionOption("title"), createStringCriterionOption("code", "scene_code"), - createPathCriterionOption("path"), + PathCriterionOption, createStringCriterionOption("details"), createStringCriterionOption("director"), createMandatoryStringCriterionOption("oshash", "media_info.hash"), @@ -73,9 +74,9 @@ const criterionOptions = [ ResolutionCriterionOption, createStringCriterionOption("video_codec"), createStringCriterionOption("audio_codec"), - createMandatoryNumberCriterionOption("duration"), - createMandatoryNumberCriterionOption("resume_time"), - createMandatoryNumberCriterionOption("play_duration"), + createDurationCriterionOption("duration"), + createDurationCriterionOption("resume_time"), + createDurationCriterionOption("play_duration"), createMandatoryNumberCriterionOption("play_count"), HasMarkersCriterionOption, SceneIsMissingCriterionOption, diff --git a/ui/v2.5/src/models/list-filter/types.ts b/ui/v2.5/src/models/list-filter/types.ts index 453081ce5a3..59a0e81dd43 100644 --- a/ui/v2.5/src/models/list-filter/types.ts +++ b/ui/v2.5/src/models/list-filter/types.ts @@ -1,5 +1,4 @@ // NOTE: add new enum values to the end, to ensure existing data - // is not impacted export enum DisplayMode { Grid, @@ -60,38 +59,51 @@ export interface IPhashDistanceValue { } export function criterionIsHierarchicalLabelValue( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any + value: unknown ): 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( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any -): value is INumberValue { - return typeof value === "object" && "value" in value && "value2" in value; +export function criterionIsNumberValue(value: unknown): value is INumberValue { + return ( + typeof value === "object" && + !!value && + "value" in value && + "value2" in value + ); } export function criterionIsStashIDValue( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any + value: unknown ): 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( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any -): value is IDateValue { - return typeof value === "object" && "value" in value && "value2" in value; +export function criterionIsDateValue(value: unknown): value is IDateValue { + return ( + typeof value === "object" && + !!value && + "value" in value && + "value2" in value + ); } export function criterionIsTimestampValue( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any + value: unknown ): 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 { @@ -101,7 +113,6 @@ export interface IOptionType { } export type CriterionType = - | "none" | "path" | "rating" | "rating100" diff --git a/ui/v2.5/src/utils/duration.ts b/ui/v2.5/src/utils/duration.ts index 949601fd1ce..2b1116152a8 100644 --- a/ui/v2.5/src/utils/duration.ts +++ b/ui/v2.5/src/utils/duration.ts @@ -16,13 +16,13 @@ const secondsToString = (seconds: number) => { const stringToSeconds = (v?: string) => { if (!v) { - return 0; + return undefined; } const splits = v.split(":"); if (splits.length > 3) { - return 0; + return undefined; } let seconds = 0; @@ -30,12 +30,12 @@ const stringToSeconds = (v?: string) => { while (splits.length > 0) { const thisSplit = splits.pop(); if (thisSplit === undefined) { - return 0; + return undefined; } const thisInt = parseInt(thisSplit, 10); if (Number.isNaN(thisInt)) { - return 0; + return undefined; } seconds += factor * thisInt; diff --git a/ui/v2.5/src/utils/editabletext.tsx b/ui/v2.5/src/utils/editabletext.tsx index a7d143afb9a..500cc2cd9df 100644 --- a/ui/v2.5/src/utils/editabletext.tsx +++ b/ui/v2.5/src/utils/editabletext.tsx @@ -79,29 +79,14 @@ const renderInputGroup = (options: { }; const renderDurationInput = (options: { - value: string | undefined; + value: number | undefined; isEditing: boolean; - url?: string; - asString?: boolean; - onChange: (value: string | undefined) => void; + onChange: (value: number | 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) { let durationString; - if (numericValue !== undefined) { - durationString = DurationUtils.secondsToString(numericValue); + if (options.value !== undefined) { + durationString = DurationUtils.secondsToString(options.value); } return ( @@ -113,19 +98,11 @@ const renderDurationInput = (options: { /> ); } + return ( { - let value = valueAsString; - if (!options.asString) { - value = - valueAsNumber !== undefined ? valueAsNumber.toString() : undefined; - } - - options.onChange(value); - }} + value={options.value} + setValue={(v) => options.onChange(v)} /> ); }; diff --git a/ui/v2.5/src/utils/form.tsx b/ui/v2.5/src/utils/form.tsx index ff1bc3f6ca8..feff48838c0 100644 --- a/ui/v2.5/src/utils/form.tsx +++ b/ui/v2.5/src/utils/form.tsx @@ -86,10 +86,9 @@ const renderInputGroup = (options: { const renderDurationInput = (options: { title: string; placeholder?: string; - value: string | undefined; + value: number | undefined; isEditing: boolean; - asString?: boolean; - onChange: (value: string | undefined) => void; + onChange: (value: number | undefined) => void; labelProps?: FormLabelProps; inputProps?: ColProps; }) => { diff --git a/ui/v2.5/src/utils/table.tsx b/ui/v2.5/src/utils/table.tsx index ca408696e2c..5ba54014766 100644 --- a/ui/v2.5/src/utils/table.tsx +++ b/ui/v2.5/src/utils/table.tsx @@ -41,10 +41,9 @@ const renderInputGroup = (options: { const renderDurationInput = (options: { title: string; placeholder?: string; - value: string | undefined; + value: number | undefined; isEditing: boolean; - asString?: boolean; - onChange: (value: string | undefined) => void; + onChange: (value: number | undefined) => void; }) => { return (