mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Filter tweaks (#3772)
* Use debounce hook * Wait until search request complete before refreshing results * Add back null modifiers * Convert old excludes criterion to includes criterion * Display criteria with only excludes items as excludes * Fix depth display * Reset search after selection * Add back is modifier to tag filter * Focus the input dialog after select/unselect * Update unsupported modifiers
This commit is contained in:
parent
de4237e626
commit
09df203bcf
18 changed files with 344 additions and 216 deletions
|
|
@ -77,17 +77,15 @@ const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Group className="modifier-options">
|
<Form.Group className="modifier-options">
|
||||||
{modifierOptions.map((c) => (
|
{modifierOptions.map((m) => (
|
||||||
<Button
|
<Button
|
||||||
className={cx("modifier-option", {
|
className={cx("modifier-option", {
|
||||||
selected: criterion.modifier === c.value,
|
selected: criterion.modifier === m,
|
||||||
})}
|
})}
|
||||||
key={c.value}
|
key={m}
|
||||||
onClick={() =>
|
onClick={() => onChangedModifierSelect(m)}
|
||||||
onChangedModifierSelect(c.value as CriterionModifier)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{c.label ? intl.formatMessage({ id: c.label }) : ""}
|
{Criterion.getModifierLabel(intl, m)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { PerformersCriterion } from "src/models/list-filter/criteria/performers";
|
import { PerformersCriterion } from "src/models/list-filter/criteria/performers";
|
||||||
import { useFindPerformersQuery } from "src/core/generated-graphql";
|
import { useFindPerformersQuery } from "src/core/generated-graphql";
|
||||||
import { ObjectsFilter } from "./SelectableFilter";
|
import { ObjectsFilter } from "./SelectableFilter";
|
||||||
|
|
@ -9,7 +9,7 @@ interface IPerformersFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePerformerQuery(query: string) {
|
function usePerformerQuery(query: string) {
|
||||||
const results = useFindPerformersQuery({
|
const { data, loading } = useFindPerformersQuery({
|
||||||
variables: {
|
variables: {
|
||||||
filter: {
|
filter: {
|
||||||
q: query,
|
q: query,
|
||||||
|
|
@ -18,14 +18,18 @@ function usePerformerQuery(query: string) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const results = useMemo(
|
||||||
results.data?.findPerformers.performers.map((p) => {
|
() =>
|
||||||
return {
|
data?.findPerformers.performers.map((p) => {
|
||||||
id: p.id,
|
return {
|
||||||
label: p.name,
|
id: p.id,
|
||||||
};
|
label: p.name,
|
||||||
}) ?? []
|
};
|
||||||
|
}) ?? [],
|
||||||
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { results, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
const PerformersFilter: React.FC<IPerformersFilter> = ({
|
const PerformersFilter: React.FC<IPerformersFilter> = ({
|
||||||
|
|
@ -36,7 +40,7 @@ const PerformersFilter: React.FC<IPerformersFilter> = ({
|
||||||
<ObjectsFilter
|
<ObjectsFilter
|
||||||
criterion={criterion}
|
criterion={criterion}
|
||||||
setCriterion={setCriterion}
|
setCriterion={setCriterion}
|
||||||
queryHook={usePerformerQuery}
|
useResults={usePerformerQuery}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Button, Form } from "react-bootstrap";
|
import { Button, Form } from "react-bootstrap";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import {
|
import {
|
||||||
|
|
@ -14,7 +14,7 @@ import {
|
||||||
ILabeledId,
|
ILabeledId,
|
||||||
ILabeledValueListValue,
|
ILabeledValueListValue,
|
||||||
} from "src/models/list-filter/types";
|
} from "src/models/list-filter/types";
|
||||||
import { cloneDeep, debounce } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
Criterion,
|
||||||
IHierarchicalLabeledIdCriterion,
|
IHierarchicalLabeledIdCriterion,
|
||||||
|
|
@ -22,6 +22,8 @@ import {
|
||||||
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { keyboardClickHandler } from "src/utils/keyboard";
|
import { keyboardClickHandler } from "src/utils/keyboard";
|
||||||
|
import { useDebouncedSetState } from "src/hooks/debounce";
|
||||||
|
import useFocus from "src/utils/focus";
|
||||||
|
|
||||||
interface ISelectedItem {
|
interface ISelectedItem {
|
||||||
item: ILabeledId;
|
item: ILabeledId;
|
||||||
|
|
@ -77,40 +79,29 @@ const SelectedItem: React.FC<ISelectedItem> = ({
|
||||||
|
|
||||||
interface ISelectableFilter {
|
interface ISelectableFilter {
|
||||||
query: string;
|
query: string;
|
||||||
setQuery: (query: string) => void;
|
onQueryChange: (query: string) => void;
|
||||||
single: boolean;
|
modifier: CriterionModifier;
|
||||||
includeOnly: boolean;
|
inputFocus: ReturnType<typeof useFocus>;
|
||||||
|
canExclude: boolean;
|
||||||
queryResults: ILabeledId[];
|
queryResults: ILabeledId[];
|
||||||
selected: ILabeledId[];
|
selected: ILabeledId[];
|
||||||
excluded: ILabeledId[];
|
excluded: ILabeledId[];
|
||||||
onSelect: (value: ILabeledId, include: boolean) => void;
|
onSelect: (value: ILabeledId, exclude: boolean) => void;
|
||||||
onUnselect: (value: ILabeledId) => void;
|
onUnselect: (value: ILabeledId) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectableFilter: React.FC<ISelectableFilter> = ({
|
const SelectableFilter: React.FC<ISelectableFilter> = ({
|
||||||
query,
|
query,
|
||||||
setQuery,
|
onQueryChange,
|
||||||
single,
|
modifier,
|
||||||
|
inputFocus,
|
||||||
|
canExclude,
|
||||||
queryResults,
|
queryResults,
|
||||||
selected,
|
selected,
|
||||||
excluded,
|
excluded,
|
||||||
includeOnly,
|
|
||||||
onSelect,
|
onSelect,
|
||||||
onUnselect,
|
onUnselect,
|
||||||
}) => {
|
}) => {
|
||||||
const [internalQuery, setInternalQuery] = useState(query);
|
|
||||||
|
|
||||||
const onInputChange = useMemo(() => {
|
|
||||||
return debounce((input: string) => {
|
|
||||||
setQuery(input);
|
|
||||||
}, 250);
|
|
||||||
}, [setQuery]);
|
|
||||||
|
|
||||||
function onInternalInputChange(input: string) {
|
|
||||||
setInternalQuery(input);
|
|
||||||
onInputChange(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const objects = useMemo(() => {
|
const objects = useMemo(() => {
|
||||||
return queryResults.filter(
|
return queryResults.filter(
|
||||||
(p) =>
|
(p) =>
|
||||||
|
|
@ -119,8 +110,10 @@ const SelectableFilter: React.FC<ISelectableFilter> = ({
|
||||||
);
|
);
|
||||||
}, [queryResults, selected, excluded]);
|
}, [queryResults, selected, excluded]);
|
||||||
|
|
||||||
const includingOnly = includeOnly || (selected.length > 0 && single);
|
const includingOnly = modifier == CriterionModifier.Equals;
|
||||||
const excludingOnly = excluded.length > 0 && single;
|
const excludingOnly =
|
||||||
|
modifier == CriterionModifier.Excludes ||
|
||||||
|
modifier == CriterionModifier.NotEquals;
|
||||||
|
|
||||||
const includeIcon = <Icon className="fa-fw include-button" icon={faPlus} />;
|
const includeIcon = <Icon className="fa-fw include-button" icon={faPlus} />;
|
||||||
const excludeIcon = <Icon className="fa-fw exclude-icon" icon={faMinus} />;
|
const excludeIcon = <Icon className="fa-fw exclude-icon" icon={faMinus} />;
|
||||||
|
|
@ -128,13 +121,18 @@ const SelectableFilter: React.FC<ISelectableFilter> = ({
|
||||||
return (
|
return (
|
||||||
<div className="selectable-filter">
|
<div className="selectable-filter">
|
||||||
<ClearableInput
|
<ClearableInput
|
||||||
value={internalQuery}
|
focus={inputFocus}
|
||||||
setValue={(v) => onInternalInputChange(v)}
|
value={query}
|
||||||
|
setValue={(v) => onQueryChange(v)}
|
||||||
/>
|
/>
|
||||||
<ul>
|
<ul>
|
||||||
{selected.map((p) => (
|
{selected.map((p) => (
|
||||||
<li key={p.id} className="selected-object">
|
<li key={p.id} className="selected-object">
|
||||||
<SelectedItem item={p} onClick={() => onUnselect(p)} />
|
<SelectedItem
|
||||||
|
item={p}
|
||||||
|
excluded={excludingOnly}
|
||||||
|
onClick={() => onUnselect(p)}
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{excluded.map((p) => (
|
{excluded.map((p) => (
|
||||||
|
|
@ -144,12 +142,9 @@ const SelectableFilter: React.FC<ISelectableFilter> = ({
|
||||||
))}
|
))}
|
||||||
{objects.map((p) => (
|
{objects.map((p) => (
|
||||||
<li key={p.id} className="unselected-object">
|
<li key={p.id} className="unselected-object">
|
||||||
{/* if excluding only, clicking on an item also excludes it */}
|
|
||||||
<a
|
<a
|
||||||
onClick={() => onSelect(p, !excludingOnly)}
|
onClick={() => onSelect(p, false)}
|
||||||
onKeyDown={keyboardClickHandler(() =>
|
onKeyDown={keyboardClickHandler(() => onSelect(p, false))}
|
||||||
onSelect(p, !excludingOnly)
|
|
||||||
)}
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -159,11 +154,11 @@ const SelectableFilter: React.FC<ISelectableFilter> = ({
|
||||||
<div>
|
<div>
|
||||||
{/* TODO item count */}
|
{/* TODO item count */}
|
||||||
{/* <span className="object-count">{p.id}</span> */}
|
{/* <span className="object-count">{p.id}</span> */}
|
||||||
{!includingOnly && !excludingOnly && (
|
{canExclude && !includingOnly && !excludingOnly && (
|
||||||
<Button
|
<Button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onSelect(p, false);
|
onSelect(p, true);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => e.stopPropagation()}
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
className="minimal exclude-button"
|
className="minimal exclude-button"
|
||||||
|
|
@ -183,36 +178,62 @@ const SelectableFilter: React.FC<ISelectableFilter> = ({
|
||||||
|
|
||||||
interface IObjectsFilter<T extends Criterion<ILabeledValueListValue>> {
|
interface IObjectsFilter<T extends Criterion<ILabeledValueListValue>> {
|
||||||
criterion: T;
|
criterion: T;
|
||||||
single?: boolean;
|
|
||||||
setCriterion: (criterion: T) => void;
|
setCriterion: (criterion: T) => void;
|
||||||
queryHook: (query: string) => ILabeledId[];
|
useResults: (query: string) => { results: ILabeledId[]; loading: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ObjectsFilter = <
|
export const ObjectsFilter = <
|
||||||
T extends Criterion<ILabeledValueListValue | IHierarchicalLabelValue>
|
T extends Criterion<ILabeledValueListValue | IHierarchicalLabelValue>
|
||||||
>(
|
>({
|
||||||
props: IObjectsFilter<T>
|
criterion,
|
||||||
) => {
|
setCriterion,
|
||||||
const { criterion, setCriterion, queryHook, single = false } = props;
|
useResults,
|
||||||
|
}: IObjectsFilter<T>) => {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
const [displayQuery, setDisplayQuery] = useState(query);
|
||||||
|
|
||||||
const queryResults = queryHook(query);
|
const debouncedSetQuery = useDebouncedSetState(setQuery, 250);
|
||||||
|
const onQueryChange = useCallback(
|
||||||
|
(input: string) => {
|
||||||
|
setDisplayQuery(input);
|
||||||
|
debouncedSetQuery(input);
|
||||||
|
},
|
||||||
|
[debouncedSetQuery, setDisplayQuery]
|
||||||
|
);
|
||||||
|
|
||||||
function onSelect(value: ILabeledId, newInclude: boolean) {
|
const [queryResults, setQueryResults] = useState<ILabeledId[]>([]);
|
||||||
|
const { results, loading: resultsLoading } = useResults(query);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!resultsLoading) {
|
||||||
|
setQueryResults(results);
|
||||||
|
}
|
||||||
|
}, [results, resultsLoading]);
|
||||||
|
|
||||||
|
const inputFocus = useFocus();
|
||||||
|
const [, setInputFocus] = inputFocus;
|
||||||
|
|
||||||
|
function onSelect(value: ILabeledId, newExclude: boolean) {
|
||||||
let newCriterion: T = cloneDeep(criterion);
|
let newCriterion: T = cloneDeep(criterion);
|
||||||
|
|
||||||
if (newInclude) {
|
if (newExclude) {
|
||||||
newCriterion.value.items.push(value);
|
|
||||||
} else {
|
|
||||||
if (newCriterion.value.excluded) {
|
if (newCriterion.value.excluded) {
|
||||||
newCriterion.value.excluded.push(value);
|
newCriterion.value.excluded.push(value);
|
||||||
} else {
|
} else {
|
||||||
newCriterion.value.excluded = [value];
|
newCriterion.value.excluded = [value];
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
newCriterion.value.items.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCriterion(newCriterion);
|
setCriterion(newCriterion);
|
||||||
|
|
||||||
|
// reset filter query after selecting
|
||||||
|
debouncedSetQuery.cancel();
|
||||||
|
setQuery("");
|
||||||
|
setDisplayQuery("");
|
||||||
|
|
||||||
|
// focus the input box
|
||||||
|
setInputFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUnselect = useCallback(
|
const onUnselect = useCallback(
|
||||||
|
|
@ -229,8 +250,11 @@ export const ObjectsFilter = <
|
||||||
);
|
);
|
||||||
|
|
||||||
setCriterion(newCriterion);
|
setCriterion(newCriterion);
|
||||||
|
|
||||||
|
// focus the input box
|
||||||
|
setInputFocus();
|
||||||
},
|
},
|
||||||
[criterion, setCriterion]
|
[criterion, setCriterion, setInputFocus]
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedSelected = useMemo(() => {
|
const sortedSelected = useMemo(() => {
|
||||||
|
|
@ -246,12 +270,19 @@ export const ObjectsFilter = <
|
||||||
return ret;
|
return ret;
|
||||||
}, [criterion]);
|
}, [criterion]);
|
||||||
|
|
||||||
|
// if excludes is not a valid modifierOption then we can use `value.excluded`
|
||||||
|
const canExclude =
|
||||||
|
criterion.criterionOption.modifierOptions.find(
|
||||||
|
(m) => m === CriterionModifier.Excludes
|
||||||
|
) === undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectableFilter
|
<SelectableFilter
|
||||||
single={single}
|
query={displayQuery}
|
||||||
includeOnly={criterion.modifier === CriterionModifier.Equals}
|
onQueryChange={onQueryChange}
|
||||||
query={query}
|
modifier={criterion.modifier}
|
||||||
setQuery={setQuery}
|
inputFocus={inputFocus}
|
||||||
|
canExclude={canExclude}
|
||||||
selected={sortedSelected}
|
selected={sortedSelected}
|
||||||
queryResults={queryResults}
|
queryResults={queryResults}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useFindStudiosQuery } from "src/core/generated-graphql";
|
import { useFindStudiosQuery } from "src/core/generated-graphql";
|
||||||
import { HierarchicalObjectsFilter } from "./SelectableFilter";
|
import { HierarchicalObjectsFilter } from "./SelectableFilter";
|
||||||
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
||||||
|
|
@ -9,7 +9,7 @@ interface IStudiosFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
function useStudioQuery(query: string) {
|
function useStudioQuery(query: string) {
|
||||||
const results = useFindStudiosQuery({
|
const { data, loading } = useFindStudiosQuery({
|
||||||
variables: {
|
variables: {
|
||||||
filter: {
|
filter: {
|
||||||
q: query,
|
q: query,
|
||||||
|
|
@ -18,14 +18,18 @@ function useStudioQuery(query: string) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const results = useMemo(
|
||||||
results.data?.findStudios.studios.map((p) => {
|
() =>
|
||||||
return {
|
data?.findStudios.studios.map((p) => {
|
||||||
id: p.id,
|
return {
|
||||||
label: p.name,
|
id: p.id,
|
||||||
};
|
label: p.name,
|
||||||
}) ?? []
|
};
|
||||||
|
}) ?? [],
|
||||||
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { results, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
const StudiosFilter: React.FC<IStudiosFilter> = ({
|
const StudiosFilter: React.FC<IStudiosFilter> = ({
|
||||||
|
|
@ -36,7 +40,7 @@ const StudiosFilter: React.FC<IStudiosFilter> = ({
|
||||||
<HierarchicalObjectsFilter
|
<HierarchicalObjectsFilter
|
||||||
criterion={criterion}
|
criterion={criterion}
|
||||||
setCriterion={setCriterion}
|
setCriterion={setCriterion}
|
||||||
queryHook={useStudioQuery}
|
useResults={useStudioQuery}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useFindTagsQuery } from "src/core/generated-graphql";
|
import { useFindTagsQuery } from "src/core/generated-graphql";
|
||||||
import { HierarchicalObjectsFilter } from "./SelectableFilter";
|
import { HierarchicalObjectsFilter } from "./SelectableFilter";
|
||||||
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
|
||||||
|
|
@ -8,8 +8,8 @@ interface ITagsFilter {
|
||||||
setCriterion: (c: StudiosCriterion) => void;
|
setCriterion: (c: StudiosCriterion) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useStudioQuery(query: string) {
|
function useTagQuery(query: string) {
|
||||||
const results = useFindTagsQuery({
|
const { data, loading } = useFindTagsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
filter: {
|
filter: {
|
||||||
q: query,
|
q: query,
|
||||||
|
|
@ -18,14 +18,18 @@ function useStudioQuery(query: string) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const results = useMemo(
|
||||||
results.data?.findTags.tags.map((p) => {
|
() =>
|
||||||
return {
|
data?.findTags.tags.map((p) => {
|
||||||
id: p.id,
|
return {
|
||||||
label: p.name,
|
id: p.id,
|
||||||
};
|
label: p.name,
|
||||||
}) ?? []
|
};
|
||||||
|
}) ?? [],
|
||||||
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { results, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsFilter: React.FC<ITagsFilter> = ({ criterion, setCriterion }) => {
|
const TagsFilter: React.FC<ITagsFilter> = ({ criterion, setCriterion }) => {
|
||||||
|
|
@ -33,7 +37,7 @@ const TagsFilter: React.FC<ITagsFilter> = ({ criterion, setCriterion }) => {
|
||||||
<HierarchicalObjectsFilter
|
<HierarchicalObjectsFilter
|
||||||
criterion={criterion}
|
criterion={criterion}
|
||||||
setCriterion={setCriterion}
|
setCriterion={setCriterion}
|
||||||
queryHook={useStudioQuery}
|
useResults={useTagQuery}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,17 @@ import useFocus from "src/utils/focus";
|
||||||
interface IClearableInput {
|
interface IClearableInput {
|
||||||
value: string;
|
value: string;
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
|
focus: ReturnType<typeof useFocus>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClearableInput: React.FC<IClearableInput> = ({
|
export const ClearableInput: React.FC<IClearableInput> = ({
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
|
focus,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const [queryRef, setQueryFocus] = useFocus();
|
const [queryRef, setQueryFocus] = focus;
|
||||||
const queryClearShowing = !!value;
|
const queryClearShowing = !!value;
|
||||||
|
|
||||||
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
||||||
|
|
|
||||||
|
|
@ -730,7 +730,9 @@
|
||||||
"equals": "is",
|
"equals": "is",
|
||||||
"excludes": "excludes",
|
"excludes": "excludes",
|
||||||
"format_string": "{criterion} {modifierString} {valueString}",
|
"format_string": "{criterion} {modifierString} {valueString}",
|
||||||
|
"format_string_depth": "{criterion} {modifierString} {valueString} (+{depth, plural, =-1 {all} other {{depth}}})",
|
||||||
"format_string_excludes": "{criterion} {modifierString} {valueString} (excludes {excludedString})",
|
"format_string_excludes": "{criterion} {modifierString} {valueString} (excludes {excludedString})",
|
||||||
|
"format_string_excludes_depth": "{criterion} {modifierString} {valueString} (excludes {excludedString}) (+{depth, plural, =-1 {all} other {{depth}}})",
|
||||||
"greater_than": "is greater than",
|
"greater_than": "is greater than",
|
||||||
"includes": "includes",
|
"includes": "includes",
|
||||||
"includes_all": "includes all",
|
"includes_all": "includes all",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export class CountryCriterion extends StringCriterion {
|
||||||
super(countryCriterionOption);
|
super(countryCriterionOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(intl: IntlShape) {
|
protected getLabelValue(intl: IntlShape) {
|
||||||
if (
|
if (
|
||||||
this.modifier === CriterionModifier.Equals ||
|
this.modifier === CriterionModifier.Equals ||
|
||||||
this.modifier === CriterionModifier.NotEquals
|
this.modifier === CriterionModifier.NotEquals
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import {
|
||||||
CriterionType,
|
CriterionType,
|
||||||
IHierarchicalLabelValue,
|
IHierarchicalLabelValue,
|
||||||
ILabeledId,
|
ILabeledId,
|
||||||
ILabeledValue,
|
|
||||||
INumberValue,
|
INumberValue,
|
||||||
IOptionType,
|
IOptionType,
|
||||||
IStashIDValue,
|
IStashIDValue,
|
||||||
|
|
@ -39,6 +38,11 @@ export type CriterionValue =
|
||||||
| ITimestampValue
|
| ITimestampValue
|
||||||
| IPhashDistanceValue;
|
| IPhashDistanceValue;
|
||||||
|
|
||||||
|
export interface IEncodedCriterion<T extends CriterionValue> {
|
||||||
|
modifier: CriterionModifier;
|
||||||
|
value: T | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const modifierMessageIDs = {
|
const modifierMessageIDs = {
|
||||||
[CriterionModifier.Equals]: "criterion_modifier.equals",
|
[CriterionModifier.Equals]: "criterion_modifier.equals",
|
||||||
[CriterionModifier.NotEquals]: "criterion_modifier.not_equals",
|
[CriterionModifier.NotEquals]: "criterion_modifier.not_equals",
|
||||||
|
|
@ -57,15 +61,15 @@ const modifierMessageIDs = {
|
||||||
|
|
||||||
// V = criterion value type
|
// V = criterion value type
|
||||||
export abstract class Criterion<V extends CriterionValue> {
|
export abstract class Criterion<V extends CriterionValue> {
|
||||||
public static getModifierOption(
|
|
||||||
modifier: CriterionModifier = CriterionModifier.Equals
|
|
||||||
): ILabeledValue {
|
|
||||||
const messageID = modifierMessageIDs[modifier];
|
|
||||||
return { value: modifier, label: messageID };
|
|
||||||
}
|
|
||||||
|
|
||||||
public criterionOption: CriterionOption;
|
public criterionOption: CriterionOption;
|
||||||
public modifier: CriterionModifier;
|
|
||||||
|
protected _modifier!: CriterionModifier;
|
||||||
|
public get modifier(): CriterionModifier {
|
||||||
|
return this._modifier;
|
||||||
|
}
|
||||||
|
public set modifier(value: CriterionModifier) {
|
||||||
|
this._modifier = value;
|
||||||
|
}
|
||||||
|
|
||||||
protected _value!: V;
|
protected _value!: V;
|
||||||
public get value(): V {
|
public get value(): V {
|
||||||
|
|
@ -79,7 +83,7 @@ export abstract class Criterion<V extends CriterionValue> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract getLabelValue(intl: IntlShape): string;
|
protected abstract getLabelValue(intl: IntlShape): string;
|
||||||
|
|
||||||
constructor(type: CriterionOption, value: V) {
|
constructor(type: CriterionOption, value: V) {
|
||||||
this.criterionOption = type;
|
this.criterionOption = type;
|
||||||
|
|
@ -140,8 +144,11 @@ export abstract class Criterion<V extends CriterionValue> {
|
||||||
return JSON.stringify(encodedCriterion);
|
return JSON.stringify(encodedCriterion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setValueFromQueryString(v: V) {
|
public setFromEncodedCriterion(encodedCriterion: IEncodedCriterion<V>) {
|
||||||
this.value = v;
|
if (encodedCriterion.value !== undefined) {
|
||||||
|
this.value = encodedCriterion.value;
|
||||||
|
}
|
||||||
|
this.modifier = encodedCriterion.modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|
@ -174,7 +181,7 @@ export class CriterionOption {
|
||||||
public readonly messageID: string;
|
public readonly messageID: string;
|
||||||
public readonly type: CriterionType;
|
public readonly type: CriterionType;
|
||||||
public readonly parameterName: string;
|
public readonly parameterName: string;
|
||||||
public readonly modifierOptions: ILabeledValue[];
|
public readonly modifierOptions: CriterionModifier[];
|
||||||
public readonly defaultModifier: CriterionModifier;
|
public readonly defaultModifier: CriterionModifier;
|
||||||
public readonly options: Option[] | undefined;
|
public readonly options: Option[] | undefined;
|
||||||
public readonly inputType: InputType;
|
public readonly inputType: InputType;
|
||||||
|
|
@ -183,9 +190,7 @@ export class CriterionOption {
|
||||||
this.messageID = options.messageID;
|
this.messageID = options.messageID;
|
||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
this.parameterName = options.parameterName ?? options.type;
|
this.parameterName = options.parameterName ?? options.type;
|
||||||
this.modifierOptions = (options.modifierOptions ?? []).map((o) =>
|
this.modifierOptions = options.modifierOptions ?? [];
|
||||||
Criterion.getModifierOption(o)
|
|
||||||
);
|
|
||||||
this.defaultModifier = options.defaultModifier ?? CriterionModifier.Equals;
|
this.defaultModifier = options.defaultModifier ?? CriterionModifier.Equals;
|
||||||
this.options = options.options;
|
this.options = options.options;
|
||||||
this.inputType = options.inputType;
|
this.inputType = options.inputType;
|
||||||
|
|
@ -237,7 +242,7 @@ export class StringCriterion extends Criterion<string> {
|
||||||
super(type, "");
|
super(type, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
return this.value;
|
return this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +260,7 @@ export class MultiStringCriterion extends Criterion<string[]> {
|
||||||
super(type, []);
|
super(type, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
return this.value.join(", ");
|
return this.value.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -437,7 +442,7 @@ export class NumberCriterion extends Criterion<INumberValue> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
const { value, value2 } = this.value;
|
const { value, value2 } = this.value;
|
||||||
if (
|
if (
|
||||||
this.modifier === CriterionModifier.Between ||
|
this.modifier === CriterionModifier.Between ||
|
||||||
|
|
@ -509,7 +514,7 @@ export class ILabeledIdCriterionOption extends CriterionOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ILabeledIdCriterion extends Criterion<ILabeledId[]> {
|
export class ILabeledIdCriterion extends Criterion<ILabeledId[]> {
|
||||||
public getLabelValue(_intl: IntlShape): string {
|
protected getLabelValue(_intl: IntlShape): string {
|
||||||
return this.value.map((v) => v.label).join(", ");
|
return this.value.map((v) => v.label).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,15 +552,53 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setValueFromQueryString(v: IHierarchicalLabelValue) {
|
override get modifier(): CriterionModifier {
|
||||||
this.value = {
|
return this._modifier;
|
||||||
items: v.items || [],
|
}
|
||||||
excluded: v.excluded || [],
|
override set modifier(value: CriterionModifier) {
|
||||||
depth: v.depth || 0,
|
this._modifier = value;
|
||||||
};
|
|
||||||
|
// excluded only makes sense for includes and includes all
|
||||||
|
// so reset it for other modifiers
|
||||||
|
if (
|
||||||
|
value !== CriterionModifier.Includes &&
|
||||||
|
value !== CriterionModifier.IncludesAll
|
||||||
|
) {
|
||||||
|
this.value.excluded = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape): string {
|
public setFromEncodedCriterion(
|
||||||
|
encodedCriterion: IEncodedCriterion<IHierarchicalLabelValue>
|
||||||
|
) {
|
||||||
|
const { modifier, value } = encodedCriterion;
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
this.value = {
|
||||||
|
items: value.items || [],
|
||||||
|
excluded: value.excluded || [],
|
||||||
|
depth: value.depth || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the previous modifier was excludes, replace it with the equivalent includes criterion
|
||||||
|
// this is what is done on the backend
|
||||||
|
// only replace if excludes is not a valid modifierOption
|
||||||
|
if (
|
||||||
|
modifier === CriterionModifier.Excludes &&
|
||||||
|
this.criterionOption.modifierOptions.find(
|
||||||
|
(m) => m === CriterionModifier.Excludes
|
||||||
|
) === undefined
|
||||||
|
) {
|
||||||
|
this.modifier = CriterionModifier.Includes;
|
||||||
|
this.value.excluded = [...this.value.excluded, ...this.value.items];
|
||||||
|
this.value.items = [];
|
||||||
|
} else {
|
||||||
|
this.modifier = modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getLabelValue(_intl: IntlShape): string {
|
||||||
const labels = (this.value.items ?? []).map((v) => v.label).join(", ");
|
const labels = (this.value.items ?? []).map((v) => v.label).join(", ");
|
||||||
|
|
||||||
if (this.value.depth === 0) {
|
if (this.value.depth === 0) {
|
||||||
|
|
@ -586,8 +629,7 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
||||||
public isValid(): boolean {
|
public isValid(): boolean {
|
||||||
if (
|
if (
|
||||||
this.modifier === CriterionModifier.IsNull ||
|
this.modifier === CriterionModifier.IsNull ||
|
||||||
this.modifier === CriterionModifier.NotNull ||
|
this.modifier === CriterionModifier.NotNull
|
||||||
this.modifier === CriterionModifier.Equals
|
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -599,22 +641,33 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape): string {
|
||||||
const modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
let id = "criterion_modifier.format_string";
|
||||||
|
let modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
||||||
let valueString = "";
|
let valueString = "";
|
||||||
|
let excludedString = "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.modifier !== CriterionModifier.IsNull &&
|
this.modifier !== CriterionModifier.IsNull &&
|
||||||
this.modifier !== CriterionModifier.NotNull
|
this.modifier !== CriterionModifier.NotNull
|
||||||
) {
|
) {
|
||||||
valueString = this.value.items.map((v) => v.label).join(", ");
|
valueString = this.value.items.map((v) => v.label).join(", ");
|
||||||
}
|
|
||||||
|
|
||||||
let id = "criterion_modifier.format_string";
|
if (this.value.excluded && this.value.excluded.length > 0) {
|
||||||
let excludedString = "";
|
if (this.value.items.length === 0) {
|
||||||
|
modifierString = Criterion.getModifierLabel(
|
||||||
|
intl,
|
||||||
|
CriterionModifier.Excludes
|
||||||
|
);
|
||||||
|
valueString = this.value.excluded.map((v) => v.label).join(", ");
|
||||||
|
} else {
|
||||||
|
id = "criterion_modifier.format_string_excludes";
|
||||||
|
excludedString = this.value.excluded.map((v) => v.label).join(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.value.excluded && this.value.excluded.length > 0) {
|
if (this.value.depth !== 0) {
|
||||||
id = "criterion_modifier.format_string_excludes";
|
id += "_depth";
|
||||||
excludedString = this.value.excluded.map((v) => v.label).join(", ");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return intl.formatMessage(
|
return intl.formatMessage(
|
||||||
|
|
@ -624,6 +677,7 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
||||||
modifierString,
|
modifierString,
|
||||||
valueString,
|
valueString,
|
||||||
excludedString,
|
excludedString,
|
||||||
|
depth: this.value.depth,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -669,7 +723,7 @@ export class DurationCriterion extends Criterion<INumberValue> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
return this.modifier === CriterionModifier.Between ||
|
return this.modifier === CriterionModifier.Between ||
|
||||||
this.modifier === CriterionModifier.NotBetween
|
this.modifier === CriterionModifier.NotBetween
|
||||||
? `${DurationUtils.secondsToString(
|
? `${DurationUtils.secondsToString(
|
||||||
|
|
@ -764,7 +818,7 @@ export class DateCriterion extends Criterion<IDateValue> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue() {
|
protected getLabelValue() {
|
||||||
const { value } = this.value;
|
const { value } = this.value;
|
||||||
return this.modifier === CriterionModifier.Between ||
|
return this.modifier === CriterionModifier.Between ||
|
||||||
this.modifier === CriterionModifier.NotBetween
|
this.modifier === CriterionModifier.NotBetween
|
||||||
|
|
@ -849,7 +903,7 @@ export class TimestampCriterion extends Criterion<ITimestampValue> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue() {
|
protected getLabelValue() {
|
||||||
const { value } = this.value;
|
const { value } = this.value;
|
||||||
return this.modifier === CriterionModifier.Between ||
|
return this.modifier === CriterionModifier.Between ||
|
||||||
this.modifier === CriterionModifier.NotBetween
|
this.modifier === CriterionModifier.NotBetween
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { CriterionOption, StringCriterion, Option } from "./criterion";
|
||||||
|
|
||||||
export class IsMissingCriterion extends StringCriterion {
|
export class IsMissingCriterion extends StringCriterion {
|
||||||
public modifierOptions = [];
|
public modifierOptions = [];
|
||||||
public modifier = CriterionModifier.Equals;
|
|
||||||
|
|
||||||
protected toCriterionInput(): string {
|
protected toCriterionInput(): string {
|
||||||
return this.value;
|
return this.value;
|
||||||
|
|
@ -23,6 +22,7 @@ class IsMissingCriterionOptionClass extends CriterionOption {
|
||||||
type: value,
|
type: value,
|
||||||
parameterName,
|
parameterName,
|
||||||
options,
|
options,
|
||||||
|
defaultModifier: CriterionModifier.Equals,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ export class NoneCriterion extends Criterion<string> {
|
||||||
super(NoneCriterionOption, "none");
|
super(NoneCriterionOption, "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
protected getLabelValue(): string {
|
||||||
public getLabelValue(): string {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ import {
|
||||||
MultiCriterionInput,
|
MultiCriterionInput,
|
||||||
} from "src/core/generated-graphql";
|
} from "src/core/generated-graphql";
|
||||||
import { ILabeledId, ILabeledValueListValue } from "../types";
|
import { ILabeledId, ILabeledValueListValue } from "../types";
|
||||||
import { Criterion, CriterionOption } from "./criterion";
|
import { Criterion, CriterionOption, IEncodedCriterion } from "./criterion";
|
||||||
|
|
||||||
const modifierOptions = [
|
const modifierOptions = [
|
||||||
CriterionModifier.IncludesAll,
|
CriterionModifier.IncludesAll,
|
||||||
CriterionModifier.Includes,
|
CriterionModifier.Includes,
|
||||||
CriterionModifier.Equals,
|
CriterionModifier.Equals,
|
||||||
|
CriterionModifier.IsNull,
|
||||||
|
CriterionModifier.NotNull,
|
||||||
];
|
];
|
||||||
|
|
||||||
const defaultModifier = CriterionModifier.IncludesAll;
|
const defaultModifier = CriterionModifier.IncludesAll;
|
||||||
|
|
@ -28,20 +30,50 @@ export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
||||||
super(PerformersCriterionOption, { items: [], excluded: [] });
|
super(PerformersCriterionOption, { items: [], excluded: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
public setValueFromQueryString(v: ILabeledId[] | ILabeledValueListValue) {
|
override get modifier(): CriterionModifier {
|
||||||
// #3619 - the format of performer value was changed from an array
|
return this._modifier;
|
||||||
// to an object. Check for both formats.
|
}
|
||||||
if (Array.isArray(v)) {
|
override set modifier(value: CriterionModifier) {
|
||||||
this.value = { items: v, excluded: [] };
|
this._modifier = value;
|
||||||
} else {
|
|
||||||
this.value = {
|
// excluded only makes sense for includes and includes all
|
||||||
items: v.items || [],
|
// reset it for other modifiers
|
||||||
excluded: v.excluded || [],
|
if (
|
||||||
};
|
value !== CriterionModifier.Includes &&
|
||||||
|
value !== CriterionModifier.IncludesAll
|
||||||
|
) {
|
||||||
|
this.value.excluded = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape): string {
|
public setFromEncodedCriterion(
|
||||||
|
encodedCriterion: IEncodedCriterion<ILabeledId[] | ILabeledValueListValue>
|
||||||
|
) {
|
||||||
|
const { modifier, value } = encodedCriterion;
|
||||||
|
|
||||||
|
// #3619 - the format of performer value was changed from an array
|
||||||
|
// to an object. Check for both formats.
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this.value = { items: value, excluded: [] };
|
||||||
|
} else if (value !== undefined) {
|
||||||
|
this.value = {
|
||||||
|
items: value.items || [],
|
||||||
|
excluded: value.excluded || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the previous modifier was excludes, replace it with the equivalent includes criterion
|
||||||
|
// this is what is done on the backend
|
||||||
|
if (modifier === CriterionModifier.Excludes) {
|
||||||
|
this.modifier = CriterionModifier.Includes;
|
||||||
|
this.value.excluded = [...this.value.excluded, ...this.value.items];
|
||||||
|
this.value.items = [];
|
||||||
|
} else {
|
||||||
|
this.modifier = modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getLabelValue(_intl: IntlShape): string {
|
||||||
return this.value.items.map((v) => v.label).join(", ");
|
return this.value.items.map((v) => v.label).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,8 +92,7 @@ export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
||||||
public isValid(): boolean {
|
public isValid(): boolean {
|
||||||
if (
|
if (
|
||||||
this.modifier === CriterionModifier.IsNull ||
|
this.modifier === CriterionModifier.IsNull ||
|
||||||
this.modifier === CriterionModifier.NotNull ||
|
this.modifier === CriterionModifier.NotNull
|
||||||
this.modifier === CriterionModifier.Equals
|
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -73,22 +104,29 @@ export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape): string {
|
||||||
const modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
let id = "criterion_modifier.format_string";
|
||||||
|
let modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
||||||
let valueString = "";
|
let valueString = "";
|
||||||
|
let excludedString = "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.modifier !== CriterionModifier.IsNull &&
|
this.modifier !== CriterionModifier.IsNull &&
|
||||||
this.modifier !== CriterionModifier.NotNull
|
this.modifier !== CriterionModifier.NotNull
|
||||||
) {
|
) {
|
||||||
valueString = this.value.items.map((v) => v.label).join(", ");
|
valueString = this.value.items.map((v) => v.label).join(", ");
|
||||||
}
|
|
||||||
|
|
||||||
let id = "criterion_modifier.format_string";
|
if (this.value.excluded && this.value.excluded.length > 0) {
|
||||||
let excludedString = "";
|
if (this.value.items.length === 0) {
|
||||||
|
modifierString = Criterion.getModifierLabel(
|
||||||
if (this.value.excluded && this.value.excluded.length > 0) {
|
intl,
|
||||||
id = "criterion_modifier.format_string_excludes";
|
CriterionModifier.Excludes
|
||||||
excludedString = this.value.excluded.map((v) => v.label).join(", ");
|
);
|
||||||
|
valueString = this.value.excluded.map((v) => v.label).join(", ");
|
||||||
|
} else {
|
||||||
|
id = "criterion_modifier.format_string_excludes";
|
||||||
|
excludedString = this.value.excluded.map((v) => v.label).join(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return intl.formatMessage(
|
return intl.formatMessage(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export class PhashCriterion extends Criterion<IPhashDistanceValue> {
|
||||||
super(PhashCriterionOption, { value: "", distance: 0 });
|
super(PhashCriterionOption, { value: "", distance: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue() {
|
protected getLabelValue() {
|
||||||
const { value, distance } = this.value;
|
const { value, distance } = this.value;
|
||||||
if (
|
if (
|
||||||
(this.modifier === CriterionModifier.Equals ||
|
(this.modifier === CriterionModifier.Equals ||
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export class RatingCriterion extends Criterion<INumberValue> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue() {
|
protected getLabelValue() {
|
||||||
const { value, value2 } = this.value;
|
const { value, value2 } = this.value;
|
||||||
if (
|
if (
|
||||||
this.modifier === CriterionModifier.Between ||
|
this.modifier === CriterionModifier.Between ||
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class StashIDCriterion extends Criterion<IStashIDValue> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLabelValue(_intl: IntlShape) {
|
protected getLabelValue(_intl: IntlShape) {
|
||||||
let ret = this.value.stashID;
|
let ret = this.value.stashID;
|
||||||
if (this.value.endpoint) {
|
if (this.value.endpoint) {
|
||||||
ret += " (" + this.value.endpoint + ")";
|
ret += " (" + this.value.endpoint + ")";
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,11 @@ import {
|
||||||
ILabeledIdCriterionOption,
|
ILabeledIdCriterionOption,
|
||||||
} from "./criterion";
|
} from "./criterion";
|
||||||
|
|
||||||
const modifierOptions = [CriterionModifier.Includes];
|
const modifierOptions = [
|
||||||
|
CriterionModifier.Includes,
|
||||||
|
CriterionModifier.IsNull,
|
||||||
|
CriterionModifier.NotNull,
|
||||||
|
];
|
||||||
|
|
||||||
const defaultModifier = CriterionModifier.Includes;
|
const defaultModifier = CriterionModifier.Includes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,57 @@
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { CriterionType } from "../types";
|
|
||||||
import { CriterionOption, IHierarchicalLabeledIdCriterion } from "./criterion";
|
import { CriterionOption, IHierarchicalLabeledIdCriterion } from "./criterion";
|
||||||
|
|
||||||
export class TagsCriterion extends IHierarchicalLabeledIdCriterion {}
|
const modifierOptions = [
|
||||||
|
|
||||||
const tagsModifierOptions = [
|
|
||||||
CriterionModifier.Includes,
|
|
||||||
CriterionModifier.IncludesAll,
|
CriterionModifier.IncludesAll,
|
||||||
|
CriterionModifier.Includes,
|
||||||
CriterionModifier.Equals,
|
CriterionModifier.Equals,
|
||||||
|
CriterionModifier.IsNull,
|
||||||
|
CriterionModifier.NotNull,
|
||||||
];
|
];
|
||||||
|
|
||||||
const withoutEqualsModifierOptions = [
|
const withoutEqualsModifierOptions = [
|
||||||
CriterionModifier.Includes,
|
|
||||||
CriterionModifier.IncludesAll,
|
CriterionModifier.IncludesAll,
|
||||||
|
CriterionModifier.Includes,
|
||||||
|
CriterionModifier.IsNull,
|
||||||
|
CriterionModifier.NotNull,
|
||||||
];
|
];
|
||||||
|
|
||||||
class tagsCriterionOption extends CriterionOption {
|
const defaultModifier = CriterionModifier.IncludesAll;
|
||||||
constructor(
|
|
||||||
messageID: string,
|
|
||||||
value: CriterionType,
|
|
||||||
parameterName: string,
|
|
||||||
modifierOptions: CriterionModifier[]
|
|
||||||
) {
|
|
||||||
let defaultModifier = CriterionModifier.IncludesAll;
|
|
||||||
|
|
||||||
super({
|
export const TagsCriterionOption = new CriterionOption({
|
||||||
messageID,
|
messageID: "tags",
|
||||||
type: value,
|
type: "tags",
|
||||||
parameterName,
|
parameterName: "tags",
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
defaultModifier,
|
defaultModifier,
|
||||||
});
|
});
|
||||||
}
|
export const SceneTagsCriterionOption = new CriterionOption({
|
||||||
}
|
messageID: "sceneTags",
|
||||||
|
type: "sceneTags",
|
||||||
|
parameterName: "scene_tags",
|
||||||
|
modifierOptions,
|
||||||
|
defaultModifier,
|
||||||
|
});
|
||||||
|
export const PerformerTagsCriterionOption = new CriterionOption({
|
||||||
|
messageID: "performerTags",
|
||||||
|
type: "performerTags",
|
||||||
|
parameterName: "performer_tags",
|
||||||
|
modifierOptions: withoutEqualsModifierOptions,
|
||||||
|
defaultModifier,
|
||||||
|
});
|
||||||
|
export const ParentTagsCriterionOption = new CriterionOption({
|
||||||
|
messageID: "parent_tags",
|
||||||
|
type: "parentTags",
|
||||||
|
parameterName: "parents",
|
||||||
|
modifierOptions: withoutEqualsModifierOptions,
|
||||||
|
defaultModifier,
|
||||||
|
});
|
||||||
|
export const ChildTagsCriterionOption = new CriterionOption({
|
||||||
|
messageID: "sub_tags",
|
||||||
|
type: "childTags",
|
||||||
|
parameterName: "children",
|
||||||
|
modifierOptions: withoutEqualsModifierOptions,
|
||||||
|
defaultModifier,
|
||||||
|
});
|
||||||
|
|
||||||
export const TagsCriterionOption = new tagsCriterionOption(
|
export class TagsCriterion extends IHierarchicalLabeledIdCriterion {}
|
||||||
"tags",
|
|
||||||
"tags",
|
|
||||||
"tags",
|
|
||||||
tagsModifierOptions
|
|
||||||
);
|
|
||||||
export const SceneTagsCriterionOption = new tagsCriterionOption(
|
|
||||||
"sceneTags",
|
|
||||||
"sceneTags",
|
|
||||||
"scene_tags",
|
|
||||||
tagsModifierOptions
|
|
||||||
);
|
|
||||||
export const PerformerTagsCriterionOption = new tagsCriterionOption(
|
|
||||||
"performerTags",
|
|
||||||
"performerTags",
|
|
||||||
"performer_tags",
|
|
||||||
withoutEqualsModifierOptions
|
|
||||||
);
|
|
||||||
export const ParentTagsCriterionOption = new tagsCriterionOption(
|
|
||||||
"parent_tags",
|
|
||||||
"parentTags",
|
|
||||||
"parents",
|
|
||||||
withoutEqualsModifierOptions
|
|
||||||
);
|
|
||||||
export const ChildTagsCriterionOption = new tagsCriterionOption(
|
|
||||||
"sub_tags",
|
|
||||||
"childTags",
|
|
||||||
"children",
|
|
||||||
withoutEqualsModifierOptions
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -130,10 +130,7 @@ export class ListFilterModel {
|
||||||
const criterion = makeCriteria(this.config, encodedCriterion.type);
|
const criterion = makeCriteria(this.config, encodedCriterion.type);
|
||||||
// it's possible that we have unsupported criteria. Just skip if so.
|
// it's possible that we have unsupported criteria. Just skip if so.
|
||||||
if (criterion) {
|
if (criterion) {
|
||||||
if (encodedCriterion.value !== undefined) {
|
criterion.setFromEncodedCriterion(encodedCriterion);
|
||||||
criterion.setValueFromQueryString(encodedCriterion.value);
|
|
||||||
}
|
|
||||||
criterion.modifier = encodedCriterion.modifier;
|
|
||||||
this.criteria.push(criterion);
|
this.criteria.push(criterion);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue