mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Filter issue fixes (#5126)
* Fix filter reading from URL when not active * Use alternative clone mechanism. Fixes weird filter hook behaviour * Separate search term input component
This commit is contained in:
@@ -65,7 +65,7 @@ export const SetFilterURL = (props: {
|
|||||||
|
|
||||||
const { setFilter } = useFilterURL(filter, setFilterOrig, {
|
const { setFilter } = useFilterURL(filter, setFilterOrig, {
|
||||||
defaultFilter,
|
defaultFilter,
|
||||||
setURL,
|
active: setURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import cx from "classnames";
|
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import { SortDirectionEnum } from "src/core/generated-graphql";
|
import { SortDirectionEnum } from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
OverlayTrigger,
|
OverlayTrigger,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
FormControl,
|
|
||||||
Popover,
|
Popover,
|
||||||
Overlay,
|
Overlay,
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
@@ -26,11 +24,83 @@ import {
|
|||||||
faCaretUp,
|
faCaretUp,
|
||||||
faCheck,
|
faCheck,
|
||||||
faRandom,
|
faRandom,
|
||||||
faTimes,
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FilterButton } from "./Filters/FilterButton";
|
import { FilterButton } from "./Filters/FilterButton";
|
||||||
import { useDebounce } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
import { View } from "./views";
|
import { View } from "./views";
|
||||||
|
import { ClearableInput } from "../Shared/ClearableInput";
|
||||||
|
|
||||||
|
export function useDebouncedSearchInput(
|
||||||
|
filter: ListFilterModel,
|
||||||
|
setFilter: (filter: ListFilterModel) => void
|
||||||
|
) {
|
||||||
|
const callback = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
const newFilter = filter.clone();
|
||||||
|
newFilter.searchTerm = value;
|
||||||
|
newFilter.currentPage = 1;
|
||||||
|
setFilter(newFilter);
|
||||||
|
},
|
||||||
|
[filter, setFilter]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClear = useCallback(() => callback(""), [callback]);
|
||||||
|
|
||||||
|
const searchCallback = useDebounce(callback, 500);
|
||||||
|
|
||||||
|
return { searchCallback, onClear };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchTermInput: React.FC<{
|
||||||
|
filter: ListFilterModel;
|
||||||
|
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
||||||
|
}> = ({ filter, onFilterUpdate }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [localInput, setLocalInput] = useState(filter.searchTerm);
|
||||||
|
|
||||||
|
const focus = useFocus();
|
||||||
|
const [, setQueryFocus] = focus;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalInput(filter.searchTerm);
|
||||||
|
}, [filter.searchTerm]);
|
||||||
|
|
||||||
|
const { searchCallback, onClear } = useDebouncedSearchInput(
|
||||||
|
filter,
|
||||||
|
onFilterUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Mousetrap.bind("/", (e) => {
|
||||||
|
setQueryFocus();
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Mousetrap.unbind("/");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSetQuery(value: string) {
|
||||||
|
setLocalInput(value);
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
onClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchCallback(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClearableInput
|
||||||
|
className="search-term-input"
|
||||||
|
focus={focus}
|
||||||
|
value={localInput}
|
||||||
|
setValue={onSetQuery}
|
||||||
|
placeholder={`${intl.formatMessage({ id: "actions.search" })}…`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface IListFilterProps {
|
interface IListFilterProps {
|
||||||
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
||||||
@@ -48,44 +118,17 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
view,
|
view,
|
||||||
}) => {
|
}) => {
|
||||||
const [customPageSizeShowing, setCustomPageSizeShowing] = useState(false);
|
const [customPageSizeShowing, setCustomPageSizeShowing] = useState(false);
|
||||||
const [queryRef, setQueryFocus] = useFocus();
|
|
||||||
const [queryClearShowing, setQueryClearShowing] = useState(
|
|
||||||
!!filter.searchTerm
|
|
||||||
);
|
|
||||||
const perPageSelect = useRef(null);
|
const perPageSelect = useRef(null);
|
||||||
const [perPageInput, perPageFocus] = useFocus();
|
const [perPageInput, perPageFocus] = useFocus();
|
||||||
|
|
||||||
const filterOptions = filter.options;
|
const filterOptions = filter.options;
|
||||||
|
|
||||||
const searchQueryUpdated = useCallback(
|
|
||||||
(value: string) => {
|
|
||||||
const newFilter = cloneDeep(filter);
|
|
||||||
newFilter.searchTerm = value;
|
|
||||||
newFilter.currentPage = 1;
|
|
||||||
onFilterUpdate(newFilter);
|
|
||||||
},
|
|
||||||
[filter, onFilterUpdate]
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchCallback = useDebounce((value: string) => {
|
|
||||||
const newFilter = cloneDeep(filter);
|
|
||||||
newFilter.searchTerm = value;
|
|
||||||
newFilter.currentPage = 1;
|
|
||||||
onFilterUpdate(newFilter);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Mousetrap.bind("/", (e) => {
|
|
||||||
setQueryFocus();
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
Mousetrap.bind("r", () => onReshuffleRandomSort());
|
Mousetrap.bind("r", () => onReshuffleRandomSort());
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Mousetrap.unbind("/");
|
|
||||||
Mousetrap.unbind("r");
|
Mousetrap.unbind("r");
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -96,14 +139,6 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
}
|
}
|
||||||
}, [customPageSizeShowing, perPageFocus]);
|
}, [customPageSizeShowing, perPageFocus]);
|
||||||
|
|
||||||
// clear search input when filter is cleared
|
|
||||||
useEffect(() => {
|
|
||||||
if (!filter.searchTerm) {
|
|
||||||
if (queryRef.current) queryRef.current.value = "";
|
|
||||||
setQueryClearShowing(false);
|
|
||||||
}
|
|
||||||
}, [filter.searchTerm, queryRef]);
|
|
||||||
|
|
||||||
function onChangePageSize(val: string) {
|
function onChangePageSize(val: string) {
|
||||||
if (val === "custom") {
|
if (val === "custom") {
|
||||||
// added timeout since Firefox seems to trigger the rootClose immediately
|
// added timeout since Firefox seems to trigger the rootClose immediately
|
||||||
@@ -125,18 +160,6 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
onFilterUpdate(newFilter);
|
onFilterUpdate(newFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
|
||||||
searchCallback(event.currentTarget.value);
|
|
||||||
setQueryClearShowing(!!event.currentTarget.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClearQuery() {
|
|
||||||
if (queryRef.current) queryRef.current.value = "";
|
|
||||||
searchQueryUpdated("");
|
|
||||||
setQueryFocus();
|
|
||||||
setQueryClearShowing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChangeSortDirection() {
|
function onChangeSortDirection() {
|
||||||
const newFilter = cloneDeep(filter);
|
const newFilter = cloneDeep(filter);
|
||||||
if (filter.sortDirection === SortDirectionEnum.Asc) {
|
if (filter.sortDirection === SortDirectionEnum.Asc) {
|
||||||
@@ -209,27 +232,8 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-2 mr-2 d-flex">
|
<div className="mb-2 d-flex">
|
||||||
<div className="flex-grow-1 query-text-field-group">
|
<SearchTermInput filter={filter} onFilterUpdate={onFilterUpdate} />
|
||||||
<FormControl
|
|
||||||
ref={queryRef}
|
|
||||||
placeholder={`${intl.formatMessage({ id: "actions.search" })}…`}
|
|
||||||
defaultValue={filter.searchTerm}
|
|
||||||
onInput={onChangeQuery}
|
|
||||||
className="query-text-field bg-secondary text-white border-secondary"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={onClearQuery}
|
|
||||||
title={intl.formatMessage({ id: "actions.clear" })}
|
|
||||||
className={cx(
|
|
||||||
"query-text-field-clear",
|
|
||||||
queryClearShowing ? "" : "d-none"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon icon={faTimes} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ButtonGroup className="mr-2 mb-2">
|
<ButtonGroup className="mr-2 mb-2">
|
||||||
|
|||||||
@@ -571,3 +571,7 @@ input[type="range"].zoom-slider {
|
|||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-term-input {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ export function useFilterURL(
|
|||||||
setFilter: React.Dispatch<React.SetStateAction<ListFilterModel>>,
|
setFilter: React.Dispatch<React.SetStateAction<ListFilterModel>>,
|
||||||
options?: {
|
options?: {
|
||||||
defaultFilter?: ListFilterModel;
|
defaultFilter?: ListFilterModel;
|
||||||
setURL?: boolean;
|
active?: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { defaultFilter, setURL = true } = options ?? {};
|
const { defaultFilter, active = true } = options ?? {};
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -28,7 +28,7 @@ export function useFilterURL(
|
|||||||
) => {
|
) => {
|
||||||
const newFilter = isFunction(value) ? value(filter) : value;
|
const newFilter = isFunction(value) ? value(filter) : value;
|
||||||
|
|
||||||
if (setURL) {
|
if (active) {
|
||||||
const newParams = newFilter.makeQueryParameters();
|
const newParams = newFilter.makeQueryParameters();
|
||||||
history.replace({ ...history.location, search: newParams });
|
history.replace({ ...history.location, search: newParams });
|
||||||
} else {
|
} else {
|
||||||
@@ -36,12 +36,15 @@ export function useFilterURL(
|
|||||||
setFilter(newFilter);
|
setFilter(newFilter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[history, setURL, setFilter, filter]
|
[history, active, setFilter, filter]
|
||||||
);
|
);
|
||||||
|
|
||||||
// This hook runs on every page location change (ie navigation),
|
// This hook runs on every page location change (ie navigation),
|
||||||
// and updates the filter accordingly.
|
// and updates the filter accordingly.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// don't apply if active is false
|
||||||
|
if (!active) return;
|
||||||
|
|
||||||
// re-init to load default filter on empty new query params
|
// re-init to load default filter on empty new query params
|
||||||
if (!location.search) {
|
if (!location.search) {
|
||||||
if (defaultFilter) updateFilter(defaultFilter.clone());
|
if (defaultFilter) updateFilter(defaultFilter.clone());
|
||||||
@@ -58,7 +61,7 @@ export function useFilterURL(
|
|||||||
return prevFilter;
|
return prevFilter;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [location.search, defaultFilter, setFilter, updateFilter]);
|
}, [active, location.search, defaultFilter, setFilter, updateFilter]);
|
||||||
|
|
||||||
return { setFilter: updateFilter };
|
return { setFilter: updateFilter };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { Icon } from "./Icon";
|
import { Icon } from "./Icon";
|
||||||
import useFocus from "src/utils/focus";
|
import useFocus from "src/utils/focus";
|
||||||
|
import cx from "classnames";
|
||||||
|
|
||||||
interface IClearableInput {
|
interface IClearableInput {
|
||||||
|
className?: string;
|
||||||
value: string;
|
value: string;
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
focus?: ReturnType<typeof useFocus>;
|
focus?: ReturnType<typeof useFocus>;
|
||||||
@@ -13,6 +15,7 @@ interface IClearableInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ClearableInput: React.FC<IClearableInput> = ({
|
export const ClearableInput: React.FC<IClearableInput> = ({
|
||||||
|
className,
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
focus,
|
focus,
|
||||||
@@ -37,7 +40,7 @@ export const ClearableInput: React.FC<IClearableInput> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="clearable-input-group">
|
<div className={cx("clearable-input-group", className)}>
|
||||||
<FormControl
|
<FormControl
|
||||||
ref={queryRef}
|
ref={queryRef}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|||||||
@@ -492,12 +492,14 @@ div.react-datepicker {
|
|||||||
|
|
||||||
.clearable-text-field-clear {
|
.clearable-text-field-clear {
|
||||||
background-color: $secondary;
|
background-color: $secondary;
|
||||||
|
bottom: 0;
|
||||||
color: $muted-gray;
|
color: $muted-gray;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin: 0.375rem 0.75rem;
|
margin: 0.375rem 0.75rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
top: 0;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|||||||
@@ -89,7 +89,13 @@ export abstract class Criterion<V extends CriterionValue> {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract clone(): Criterion<V>;
|
public clone() {
|
||||||
|
const ret = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
||||||
|
ret.cloneValues();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected cloneValues() {}
|
||||||
|
|
||||||
public static getModifierLabel(intl: IntlShape, modifier: CriterionModifier) {
|
public static getModifierLabel(intl: IntlShape, modifier: CriterionModifier) {
|
||||||
const modifierMessageID = modifierMessageIDs[modifier];
|
const modifierMessageID = modifierMessageIDs[modifier];
|
||||||
@@ -257,13 +263,8 @@ export class ILabeledIdCriterion extends Criterion<ILabeledId[]> {
|
|||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): Criterion<ILabeledId[]> {
|
public cloneValues() {
|
||||||
const newCriterion = new ILabeledIdCriterion(
|
this.value = this.value.map((v) => ({ ...v }));
|
||||||
this.criterionOption,
|
|
||||||
this.value.map((v) => ({ ...v }))
|
|
||||||
);
|
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getLabelValue(_intl: IntlShape): string {
|
protected getLabelValue(_intl: IntlShape): string {
|
||||||
@@ -301,17 +302,12 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): Criterion<IHierarchicalLabelValue> {
|
public cloneValues() {
|
||||||
const newCriterion = new IHierarchicalLabeledIdCriterion(
|
this.value = {
|
||||||
this.criterionOption,
|
|
||||||
{
|
|
||||||
...this.value,
|
...this.value,
|
||||||
items: this.value.items.map((v) => ({ ...v })),
|
items: this.value.items.map((v) => ({ ...v })),
|
||||||
excluded: this.value.excluded.map((v) => ({ ...v })),
|
excluded: this.value.excluded.map((v) => ({ ...v })),
|
||||||
}
|
};
|
||||||
);
|
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override get modifier(): CriterionModifier {
|
override get modifier(): CriterionModifier {
|
||||||
@@ -512,13 +508,6 @@ export class StringCriterion extends Criterion<string> {
|
|||||||
super(type, "");
|
super(type, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
|
||||||
const newCriterion = new StringCriterion(this.criterionOption);
|
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = this.value;
|
|
||||||
return newCriterion;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
return this.value;
|
return this.value;
|
||||||
}
|
}
|
||||||
@@ -532,18 +521,13 @@ export class StringCriterion extends Criterion<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiStringCriterion extends Criterion<string[]> {
|
export abstract class MultiStringCriterion extends Criterion<string[]> {
|
||||||
constructor(type: CriterionOption, value: string[] = []) {
|
constructor(type: CriterionOption, value: string[] = []) {
|
||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): Criterion<string[]> {
|
public cloneValues() {
|
||||||
const newCriterion = new MultiStringCriterion(
|
this.value = this.value.slice();
|
||||||
this.criterionOption,
|
|
||||||
this.value.slice()
|
|
||||||
);
|
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
@@ -718,11 +702,8 @@ export class NumberCriterion extends Criterion<INumberValue> {
|
|||||||
super(type, { value: undefined, value2: undefined });
|
super(type, { value: undefined, value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new NumberCriterion(this.criterionOption);
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get value(): INumberValue {
|
public get value(): INumberValue {
|
||||||
@@ -803,11 +784,8 @@ export class DurationCriterion extends Criterion<INumberValue> {
|
|||||||
super(type, { value: undefined, value2: undefined });
|
super(type, { value: undefined, value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new DurationCriterion(this.criterionOption);
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public toCriterionInput(): IntCriterionInput {
|
public toCriterionInput(): IntCriterionInput {
|
||||||
@@ -887,11 +865,8 @@ export class DateCriterion extends Criterion<IDateValue> {
|
|||||||
super(type, { value: "", value2: undefined });
|
super(type, { value: "", value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new DateCriterion(this.criterionOption);
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public encodeValue() {
|
public encodeValue() {
|
||||||
@@ -993,11 +968,8 @@ export class TimestampCriterion extends Criterion<ITimestampValue> {
|
|||||||
super(type, { value: "", value2: undefined });
|
super(type, { value: "", value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new TimestampCriterion(this.criterionOption);
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public encodeValue() {
|
public encodeValue() {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export const GenderCriterionOption = new CriterionOption({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export class GenderCriterion extends MultiStringCriterion {
|
export class GenderCriterion extends MultiStringCriterion {
|
||||||
constructor() {
|
constructor(value: string[] = []) {
|
||||||
super(GenderCriterionOption);
|
super(GenderCriterionOption, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public toCriterionInput(): GenderCriterionInput {
|
public toCriterionInput(): GenderCriterionInput {
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
|||||||
super(PerformersCriterionOption, { items: [], excluded: [] });
|
super(PerformersCriterionOption, { items: [], excluded: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new PerformersCriterion();
|
this.value = {
|
||||||
newCriterion.modifier = this.modifier;
|
...this.value,
|
||||||
newCriterion.value = { ...this.value };
|
items: this.value.items.map((v) => ({ ...v })),
|
||||||
return newCriterion;
|
excluded: this.value.excluded.map((v) => ({ ...v })),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
override get modifier(): CriterionModifier {
|
override get modifier(): CriterionModifier {
|
||||||
|
|||||||
@@ -29,11 +29,8 @@ export class PhashCriterion extends Criterion<IPhashDistanceValue> {
|
|||||||
super(PhashCriterionOption, { value: "", distance: 0 });
|
super(PhashCriterionOption, { value: "", distance: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new PhashCriterion();
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getLabelValue() {
|
protected getLabelValue() {
|
||||||
|
|||||||
@@ -45,11 +45,8 @@ export class RatingCriterion extends Criterion<INumberValue> {
|
|||||||
this.ratingSystem = ratingSystem;
|
this.ratingSystem = ratingSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new RatingCriterion(this.ratingSystem);
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get value(): INumberValue {
|
public get value(): INumberValue {
|
||||||
|
|||||||
@@ -27,11 +27,8 @@ export class StashIDCriterion extends Criterion<IStashIDValue> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone() {
|
public cloneValues() {
|
||||||
const newCriterion = new StashIDCriterion();
|
this.value = { ...this.value };
|
||||||
newCriterion.modifier = this.modifier;
|
|
||||||
newCriterion.value = { ...this.value };
|
|
||||||
return newCriterion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get value(): IStashIDValue {
|
public get value(): IStashIDValue {
|
||||||
|
|||||||
Reference in New Issue
Block a user