mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Custom fields filter UI (#5632)
* Refactor criteria and criterion options * Add custom fields filtering in UI
This commit is contained in:
@@ -29,7 +29,7 @@ import { sortByRelevance } from "src/utils/query";
|
|||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
import { PatchComponent, PatchFunction } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { PathCriterion } from "src/models/list-filter/criteria/path";
|
import { PathCriterion } from "src/models/list-filter/criteria/path";
|
||||||
@@ -46,7 +46,7 @@ type Option = SelectOption<Gallery>;
|
|||||||
type ExtraGalleryProps = {
|
type ExtraGalleryProps = {
|
||||||
hoverPlacement?: Placement;
|
hoverPlacement?: Placement;
|
||||||
excludeIds?: string[];
|
excludeIds?: string[];
|
||||||
extraCriteria?: Array<Criterion<CriterionValue>>;
|
extraCriteria?: Array<ModifierCriterion<CriterionValue>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FindGalleriesResult = Awaited<
|
type FindGalleriesResult = Awaited<
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
import { Button, Form } from "react-bootstrap";
|
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
DurationCriterion,
|
DurationCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
IHierarchicalLabeledIdCriterion,
|
IHierarchicalLabeledIdCriterion,
|
||||||
NumberCriterion,
|
NumberCriterion,
|
||||||
ILabeledIdCriterion,
|
ILabeledIdCriterion,
|
||||||
DateCriterion,
|
DateCriterion,
|
||||||
TimestampCriterion,
|
TimestampCriterion,
|
||||||
BooleanCriterion,
|
BooleanCriterion,
|
||||||
|
Criterion,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
import {
|
import {
|
||||||
criterionIsHierarchicalLabelValue,
|
criterionIsHierarchicalLabelValue,
|
||||||
criterionIsNumberValue,
|
criterionIsNumberValue,
|
||||||
@@ -45,21 +44,21 @@ import { TagsCriterion } from "src/models/list-filter/criteria/tags";
|
|||||||
import TagsFilter from "./Filters/TagsFilter";
|
import TagsFilter from "./Filters/TagsFilter";
|
||||||
import { PhashCriterion } from "src/models/list-filter/criteria/phash";
|
import { PhashCriterion } from "src/models/list-filter/criteria/phash";
|
||||||
import { PhashFilter } from "./Filters/PhashFilter";
|
import { PhashFilter } from "./Filters/PhashFilter";
|
||||||
import cx from "classnames";
|
|
||||||
import { PathCriterion } from "src/models/list-filter/criteria/path";
|
import { PathCriterion } from "src/models/list-filter/criteria/path";
|
||||||
|
import { ModifierSelectorButtons } from "./ModifierSelect";
|
||||||
|
import { CustomFieldsCriterion } from "src/models/list-filter/criteria/custom-fields";
|
||||||
|
import { CustomFieldsFilter } from "./Filters/CustomFieldsFilter";
|
||||||
|
|
||||||
interface IGenericCriterionEditor {
|
interface IGenericCriterionEditor {
|
||||||
criterion: Criterion<CriterionValue>;
|
criterion: ModifierCriterion<CriterionValue>;
|
||||||
setCriterion: (c: Criterion<CriterionValue>) => void;
|
setCriterion: (c: ModifierCriterion<CriterionValue>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
|
const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
|
||||||
criterion,
|
criterion,
|
||||||
setCriterion,
|
setCriterion,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const { options, modifierOptions } = criterion.modifierCriterionOption();
|
||||||
|
|
||||||
const { options, modifierOptions } = criterion.criterionOption;
|
|
||||||
|
|
||||||
const showModifierSelector = useMemo(() => {
|
const showModifierSelector = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
@@ -97,26 +96,17 @@ const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Group className="modifier-options">
|
<ModifierSelectorButtons
|
||||||
{modifierOptions.map((m) => (
|
options={modifierOptions}
|
||||||
<Button
|
value={criterion.modifier}
|
||||||
className={cx("modifier-option", {
|
onChanged={onChangedModifierSelect}
|
||||||
selected: criterion.modifier === m,
|
/>
|
||||||
})}
|
|
||||||
key={m}
|
|
||||||
onClick={() => onChangedModifierSelect(m)}
|
|
||||||
>
|
|
||||||
{Criterion.getModifierLabel(intl, m)}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</Form.Group>
|
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
showModifierSelector,
|
showModifierSelector,
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
onChangedModifierSelect,
|
onChangedModifierSelect,
|
||||||
criterion.modifier,
|
criterion.modifier,
|
||||||
intl,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const valueControl = useMemo(() => {
|
const valueControl = useMemo(() => {
|
||||||
@@ -268,8 +258,8 @@ const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface ICriterionEditor {
|
interface ICriterionEditor {
|
||||||
criterion: Criterion<CriterionValue>;
|
criterion: Criterion;
|
||||||
setCriterion: (c: Criterion<CriterionValue>) => void;
|
setCriterion: (c: Criterion) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CriterionEditor: React.FC<ICriterionEditor> = ({
|
export const CriterionEditor: React.FC<ICriterionEditor> = ({
|
||||||
@@ -283,12 +273,22 @@ export const CriterionEditor: React.FC<ICriterionEditor> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (criterion instanceof CustomFieldsCriterion) {
|
||||||
<GenericCriterionEditor
|
return (
|
||||||
criterion={criterion}
|
<CustomFieldsFilter criterion={criterion} setCriterion={setCriterion} />
|
||||||
setCriterion={setCriterion}
|
);
|
||||||
/>
|
}
|
||||||
);
|
|
||||||
|
if (criterion instanceof ModifierCriterion) {
|
||||||
|
return (
|
||||||
|
<GenericCriterionEditor
|
||||||
|
criterion={criterion}
|
||||||
|
setCriterion={setCriterion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}, [criterion, setCriterion]);
|
}, [criterion, setCriterion]);
|
||||||
|
|
||||||
return <div className="criterion-editor">{filterControl}</div>;
|
return <div className="criterion-editor">{filterControl}</div>;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import React, {
|
|||||||
import { Accordion, Button, Card, Form, Modal } from "react-bootstrap";
|
import { Accordion, Button, Card, Form, Modal } from "react-bootstrap";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import {
|
import {
|
||||||
CriterionValue,
|
|
||||||
Criterion,
|
Criterion,
|
||||||
CriterionOption,
|
CriterionOption,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
@@ -38,8 +37,8 @@ import ScreenUtils from "src/utils/screen";
|
|||||||
|
|
||||||
interface ICriterionList {
|
interface ICriterionList {
|
||||||
criteria: string[];
|
criteria: string[];
|
||||||
currentCriterion?: Criterion<CriterionValue>;
|
currentCriterion?: Criterion;
|
||||||
setCriterion: (c: Criterion<CriterionValue>) => void;
|
setCriterion: (c: Criterion) => void;
|
||||||
criterionOptions: CriterionOption[];
|
criterionOptions: CriterionOption[];
|
||||||
pinnedCriterionOptions: CriterionOption[];
|
pinnedCriterionOptions: CriterionOption[];
|
||||||
selected?: CriterionOption;
|
selected?: CriterionOption;
|
||||||
@@ -228,7 +227,7 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
|||||||
const [currentFilter, setCurrentFilter] = useState<ListFilterModel>(
|
const [currentFilter, setCurrentFilter] = useState<ListFilterModel>(
|
||||||
cloneDeep(filter)
|
cloneDeep(filter)
|
||||||
);
|
);
|
||||||
const [criterion, setCriterion] = useState<Criterion<CriterionValue>>();
|
const [criterion, setCriterion] = useState<Criterion>();
|
||||||
|
|
||||||
const [searchRef, setSearchFocus] = useFocusOnce(!ScreenUtils.isTouch());
|
const [searchRef, setSearchFocus] = useFocusOnce(!ScreenUtils.isTouch());
|
||||||
|
|
||||||
@@ -364,7 +363,7 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceCriterion(c: Criterion<CriterionValue>) {
|
function replaceCriterion(c: Criterion) {
|
||||||
const newFilter = cloneDeep(currentFilter);
|
const newFilter = cloneDeep(currentFilter);
|
||||||
|
|
||||||
if (!c.isValid()) {
|
if (!c.isValid()) {
|
||||||
@@ -397,18 +396,26 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
|||||||
setCriterion(c);
|
setCriterion(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCriterion(c: Criterion<CriterionValue>) {
|
function removeCriterion(c: Criterion, valueIndex?: number) {
|
||||||
const newFilter = cloneDeep(currentFilter);
|
if (valueIndex !== undefined) {
|
||||||
|
setCurrentFilter(
|
||||||
|
currentFilter.removeCustomFieldCriterion(
|
||||||
|
c.criterionOption.type,
|
||||||
|
valueIndex
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const newFilter = cloneDeep(currentFilter);
|
||||||
|
const newCriteria = criteria.filter((cc) => {
|
||||||
|
return cc.getId() !== c.getId();
|
||||||
|
});
|
||||||
|
|
||||||
const newCriteria = criteria.filter((cc) => {
|
newFilter.criteria = newCriteria;
|
||||||
return cc.getId() !== c.getId();
|
|
||||||
});
|
|
||||||
|
|
||||||
newFilter.criteria = newCriteria;
|
setCurrentFilter(newFilter);
|
||||||
|
if (criterion?.getId() === c.getId()) {
|
||||||
setCurrentFilter(newFilter);
|
optionSelected(undefined);
|
||||||
if (criterion?.getId() === c.getId()) {
|
}
|
||||||
optionSelected(undefined);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +469,7 @@ export const EditFilterDialog: React.FC<IEditFilterProps> = ({
|
|||||||
<FilterTags
|
<FilterTags
|
||||||
criteria={criteria}
|
criteria={criteria}
|
||||||
onEditCriterion={(c) => optionSelected(c.criterionOption)}
|
onEditCriterion={(c) => optionSelected(c.criterionOption)}
|
||||||
onRemoveCriterion={(c) => removeCriterion(c)}
|
onRemoveCriterion={removeCriterion}
|
||||||
onRemoveAll={() => onClearAll()}
|
onRemoveAll={() => onClearAll()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,50 @@
|
|||||||
import React from "react";
|
import React, { PropsWithChildren } from "react";
|
||||||
import { Badge, Button } from "react-bootstrap";
|
import { Badge, BadgeProps, Button } from "react-bootstrap";
|
||||||
import {
|
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
||||||
Criterion,
|
|
||||||
CriterionValue,
|
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { BsPrefixProps, ReplaceProps } from "react-bootstrap/esm/helpers";
|
||||||
|
import { CustomFieldsCriterion } from "src/models/list-filter/criteria/custom-fields";
|
||||||
|
|
||||||
|
type TagItemProps = PropsWithChildren<
|
||||||
|
ReplaceProps<"span", BsPrefixProps<"span"> & BadgeProps>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const TagItem: React.FC<TagItemProps> = (props) => {
|
||||||
|
const { children } = props;
|
||||||
|
return (
|
||||||
|
<Badge className="tag-item" variant="secondary" {...props}>
|
||||||
|
{children}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FilterTag: React.FC<{
|
||||||
|
label: React.ReactNode;
|
||||||
|
onClick: React.MouseEventHandler<HTMLSpanElement>;
|
||||||
|
onRemove: React.MouseEventHandler<HTMLElement>;
|
||||||
|
}> = ({ label, onClick, onRemove }) => {
|
||||||
|
return (
|
||||||
|
<TagItem onClick={onClick}>
|
||||||
|
{label}
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={(e) => {
|
||||||
|
onRemove(e);
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon={faTimes} />
|
||||||
|
</Button>
|
||||||
|
</TagItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface IFilterTagsProps {
|
interface IFilterTagsProps {
|
||||||
criteria: Criterion<CriterionValue>[];
|
criteria: Criterion[];
|
||||||
onEditCriterion: (c: Criterion<CriterionValue>) => void;
|
onEditCriterion: (c: Criterion) => void;
|
||||||
onRemoveCriterion: (c: Criterion<CriterionValue>) => void;
|
onRemoveCriterion: (c: Criterion, valueIndex?: number) => void;
|
||||||
onRemoveAll: () => void;
|
onRemoveAll: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,59 +57,62 @@ export const FilterTags: React.FC<IFilterTagsProps> = ({
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
function onRemoveCriterionTag(
|
function onRemoveCriterionTag(
|
||||||
criterion: Criterion<CriterionValue>,
|
criterion: Criterion,
|
||||||
$event: React.MouseEvent<HTMLElement, MouseEvent>
|
$event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||||
|
valueIndex?: number
|
||||||
) {
|
) {
|
||||||
if (!criterion) {
|
if (!criterion) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onRemoveCriterion(criterion);
|
onRemoveCriterion(criterion, valueIndex);
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickCriterionTag(criterion: Criterion<CriterionValue>) {
|
function onClickCriterionTag(criterion: Criterion) {
|
||||||
onEditCriterion(criterion);
|
onEditCriterion(criterion);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFilterTags() {
|
function renderFilterTags(criterion: Criterion) {
|
||||||
return criteria.map((criterion) => (
|
if (
|
||||||
<Badge
|
criterion instanceof CustomFieldsCriterion &&
|
||||||
className="tag-item"
|
criterion.value.length > 1
|
||||||
variant="secondary"
|
) {
|
||||||
key={criterion.getId()}
|
return criterion.value.map((value, index) => {
|
||||||
onClick={() => onClickCriterionTag(criterion)}
|
return (
|
||||||
>
|
<FilterTag
|
||||||
{criterion.getLabel(intl)}
|
key={index}
|
||||||
<Button
|
label={criterion.getValueLabel(intl, value)}
|
||||||
variant="secondary"
|
onClick={() => onClickCriterionTag(criterion)}
|
||||||
onClick={($event) => onRemoveCriterionTag(criterion, $event)}
|
onRemove={($event) =>
|
||||||
>
|
onRemoveCriterionTag(criterion, $event, index)
|
||||||
<Icon icon={faTimes} />
|
}
|
||||||
</Button>
|
/>
|
||||||
</Badge>
|
);
|
||||||
));
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function maybeRenderClearAll() {
|
|
||||||
if (criteria.length < 3) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<FilterTag
|
||||||
variant="minimal"
|
key={criterion.getId()}
|
||||||
className="clear-all-button"
|
label={criterion.getLabel(intl)}
|
||||||
onClick={() => onRemoveAll()}
|
onClick={() => onClickCriterionTag(criterion)}
|
||||||
>
|
onRemove={($event) => onRemoveCriterionTag(criterion, $event)}
|
||||||
<FormattedMessage id="actions.clear" />
|
/>
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex justify-content-center mb-2 wrap-tags filter-tags">
|
<div className="d-flex justify-content-center mb-2 wrap-tags filter-tags">
|
||||||
{renderFilterTags()}
|
{criteria.map(renderFilterTags)}
|
||||||
{maybeRenderClearAll()}
|
{criteria.length >= 3 && (
|
||||||
|
<Button
|
||||||
|
variant="minimal"
|
||||||
|
className="clear-all-button"
|
||||||
|
onClick={() => onRemoveAll()}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="actions.clear" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
312
ui/v2.5/src/components/List/Filters/CustomFieldsFilter.tsx
Normal file
312
ui/v2.5/src/components/List/Filters/CustomFieldsFilter.tsx
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import { CustomFieldsCriterion } from "src/models/list-filter/criteria/custom-fields";
|
||||||
|
import { Button, Col, Form, Row } from "react-bootstrap";
|
||||||
|
import {
|
||||||
|
CriterionModifier,
|
||||||
|
CustomFieldCriterionInput,
|
||||||
|
} from "src/core/generated-graphql";
|
||||||
|
import { cloneDeep } from "@apollo/client/utilities";
|
||||||
|
import { ModifierSelect } from "../ModifierSelect";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
|
import { faCheck, faPencil, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FilterTag } from "../FilterTags";
|
||||||
|
import { ModifierCriterion } from "src/models/list-filter/criteria/criterion";
|
||||||
|
|
||||||
|
interface ICustomFieldCriterionEditor {
|
||||||
|
criterion?: CustomFieldCriterionInput;
|
||||||
|
setCriterion: (c: CustomFieldCriterionInput) => void;
|
||||||
|
cancel: () => void;
|
||||||
|
editing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(v: string) {
|
||||||
|
// if the value is numeric, convert it to a number
|
||||||
|
const num = Number(v);
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
return num;
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomFieldCriterionEditor: React.FC<ICustomFieldCriterionEditor> = ({
|
||||||
|
criterion,
|
||||||
|
setCriterion,
|
||||||
|
editing = false,
|
||||||
|
cancel,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [field, setField] = React.useState(criterion?.field ?? "");
|
||||||
|
const [value, setValue] = React.useState(criterion?.value);
|
||||||
|
const [modifier, setModifier] = React.useState(
|
||||||
|
criterion?.modifier ?? CriterionModifier.Equals
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstValue = value && value.length > 0 ? (value[0] as string) : "";
|
||||||
|
const secondValue = value && value.length > 1 ? (value[1] as string) : "";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setField((criterion?.field as string) ?? "");
|
||||||
|
setValue(criterion?.value ?? []);
|
||||||
|
setModifier(criterion?.modifier ?? CriterionModifier.Equals);
|
||||||
|
}, [criterion]);
|
||||||
|
|
||||||
|
function setFirstValue(v: string) {
|
||||||
|
// convert to numeric if possible
|
||||||
|
const nv = getValue(v);
|
||||||
|
|
||||||
|
if (
|
||||||
|
modifier === CriterionModifier.Between ||
|
||||||
|
modifier === CriterionModifier.NotBetween
|
||||||
|
) {
|
||||||
|
setValue([nv, secondValue]);
|
||||||
|
} else {
|
||||||
|
setValue([nv]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSecondValue(v: string) {
|
||||||
|
setValue([firstValue, getValue(v)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeModifier(m: CriterionModifier) {
|
||||||
|
setModifier(m);
|
||||||
|
if (m === CriterionModifier.IsNull || m === CriterionModifier.NotNull) {
|
||||||
|
setValue(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfirm() {
|
||||||
|
setCriterion({
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
modifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPlaceholder =
|
||||||
|
modifier === CriterionModifier.Between ||
|
||||||
|
modifier === CriterionModifier.NotBetween
|
||||||
|
? intl.formatMessage({ id: "criterion.greater_than" })
|
||||||
|
: intl.formatMessage({ id: "custom_fields.value" });
|
||||||
|
|
||||||
|
const hasTwoValues =
|
||||||
|
modifier === CriterionModifier.Between ||
|
||||||
|
modifier === CriterionModifier.NotBetween;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Group className="custom-field-filter">
|
||||||
|
<div>
|
||||||
|
<Row noGutters>
|
||||||
|
<Col xs={6}>
|
||||||
|
<Form.Control
|
||||||
|
className="btn-secondary"
|
||||||
|
type="text"
|
||||||
|
placeholder={intl.formatMessage({ id: "custom_fields.field" })}
|
||||||
|
onChange={(e) => setField(e.target.value)}
|
||||||
|
value={field}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col xs={6}>
|
||||||
|
<ModifierSelect
|
||||||
|
value={modifier}
|
||||||
|
onChanged={(m) => onChangeModifier(m)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row noGutters>
|
||||||
|
{modifier !== CriterionModifier.IsNull &&
|
||||||
|
modifier !== CriterionModifier.NotNull && (
|
||||||
|
<Col xs={hasTwoValues ? 6 : 12}>
|
||||||
|
<Form.Control
|
||||||
|
placeholder={firstPlaceholder}
|
||||||
|
className="btn-secondary"
|
||||||
|
type="text"
|
||||||
|
onChange={(e) => setFirstValue(e.target.value)}
|
||||||
|
value={firstValue}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
{(modifier === CriterionModifier.Between ||
|
||||||
|
modifier === CriterionModifier.NotBetween) && (
|
||||||
|
<Col xs={6}>
|
||||||
|
<Form.Control
|
||||||
|
placeholder={intl.formatMessage({ id: "criterion.less_than" })}
|
||||||
|
className="btn-secondary"
|
||||||
|
type="text"
|
||||||
|
onChange={(e) => setSecondValue(e.target.value)}
|
||||||
|
value={secondValue}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
<div className="custom-field-filter-buttons">
|
||||||
|
<Button variant="success" onClick={() => onConfirm()} disabled={!field}>
|
||||||
|
<Icon icon={faCheck} />
|
||||||
|
</Button>
|
||||||
|
{editing && (
|
||||||
|
<Button variant="secondary" onClick={() => cancel()}>
|
||||||
|
<Icon icon={faTimes} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Form.Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function valueToString(value: unknown[] | undefined | null) {
|
||||||
|
if (!value) return "";
|
||||||
|
return value.map((v) => v as string).join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomFieldFilterTag: React.FC<{
|
||||||
|
criterion: CustomFieldCriterionInput;
|
||||||
|
editing?: boolean;
|
||||||
|
onEditCriterion: () => void;
|
||||||
|
onRemoveCriterion: () => void;
|
||||||
|
}> = ({ criterion, editing, onEditCriterion, onRemoveCriterion }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const label = useMemo(() => {
|
||||||
|
const { field, modifier, value } = criterion;
|
||||||
|
const modifierString = ModifierCriterion.getModifierLabel(intl, modifier);
|
||||||
|
|
||||||
|
const str = intl.formatMessage(
|
||||||
|
{ id: "criterion_modifier.format_string" },
|
||||||
|
{
|
||||||
|
criterion: field,
|
||||||
|
modifierString,
|
||||||
|
valueString: valueToString(value),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Icon icon={faPencil} />
|
||||||
|
{str}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{str}</>;
|
||||||
|
}, [criterion, editing, intl]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterTag
|
||||||
|
label={label}
|
||||||
|
onClick={onEditCriterion}
|
||||||
|
onRemove={onRemoveCriterion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomFieldsCriteriaPills: React.FC<{
|
||||||
|
criteria: CustomFieldCriterionInput[];
|
||||||
|
editIndex?: number;
|
||||||
|
onEditCriterion: (index: number) => void;
|
||||||
|
onRemoveCriterion: (index: number) => void;
|
||||||
|
}> = ({ criteria, editIndex, onEditCriterion, onRemoveCriterion }) => {
|
||||||
|
return (
|
||||||
|
<div className="d-flex justify-content-center mb-2 wrap-tags filter-tags">
|
||||||
|
{criteria.map((c, index) => (
|
||||||
|
<CustomFieldFilterTag
|
||||||
|
key={index}
|
||||||
|
editing={index === editIndex}
|
||||||
|
criterion={c}
|
||||||
|
onEditCriterion={() => onEditCriterion(index)}
|
||||||
|
onRemoveCriterion={() => onRemoveCriterion(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICustomFieldsFilter {
|
||||||
|
criterion: CustomFieldsCriterion;
|
||||||
|
setCriterion: (c: CustomFieldsCriterion) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCriterion(
|
||||||
|
criterion: CustomFieldsCriterion
|
||||||
|
): CustomFieldsCriterion {
|
||||||
|
return cloneDeep(criterion);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewCriterion(): CustomFieldCriterionInput {
|
||||||
|
return {
|
||||||
|
field: "",
|
||||||
|
value: [],
|
||||||
|
modifier: CriterionModifier.Equals,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomFieldsFilter: React.FC<ICustomFieldsFilter> = ({
|
||||||
|
criterion,
|
||||||
|
setCriterion,
|
||||||
|
}) => {
|
||||||
|
const [localCriterion, setLocalCriterion] = React.useState(
|
||||||
|
initCriterion(criterion)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [editCriterion, setEditCriterion] = useState(createNewCriterion());
|
||||||
|
const editIndex = useMemo(
|
||||||
|
() => localCriterion.value.indexOf(editCriterion),
|
||||||
|
[localCriterion, editCriterion]
|
||||||
|
);
|
||||||
|
|
||||||
|
function updateCriteria(newCriteria: CustomFieldCriterionInput[]) {
|
||||||
|
// update the parent - filter out invalid criteria
|
||||||
|
const validCriteria = newCriteria.filter((c) => c.field !== "");
|
||||||
|
const newValue = cloneDeep(criterion);
|
||||||
|
newValue.value = validCriteria;
|
||||||
|
setCriterion(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(nv: CustomFieldCriterionInput) {
|
||||||
|
const newValue = cloneDeep(localCriterion);
|
||||||
|
|
||||||
|
// if the criterion is new, add it to the list
|
||||||
|
if (editIndex === -1) {
|
||||||
|
newValue.value.push(nv);
|
||||||
|
} else {
|
||||||
|
newValue.value[editIndex] = nv;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalCriterion(newValue);
|
||||||
|
updateCriteria(newValue.value);
|
||||||
|
setEditCriterion(createNewCriterion());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemove(index: number) {
|
||||||
|
const c = cloneDeep(localCriterion);
|
||||||
|
c.value.splice(index, 1);
|
||||||
|
setLocalCriterion(c);
|
||||||
|
updateCriteria(c.value);
|
||||||
|
if (index === editIndex) {
|
||||||
|
setEditCriterion(createNewCriterion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Group>
|
||||||
|
<CustomFieldCriterionEditor
|
||||||
|
criterion={editCriterion}
|
||||||
|
editing={editCriterion.field !== ""}
|
||||||
|
setCriterion={onChange}
|
||||||
|
cancel={() => setEditCriterion(createNewCriterion())}
|
||||||
|
/>
|
||||||
|
<CustomFieldsCriteriaPills
|
||||||
|
criteria={localCriterion.value}
|
||||||
|
editIndex={editIndex !== -1 ? editIndex : undefined}
|
||||||
|
onEditCriterion={(index) =>
|
||||||
|
setEditCriterion(localCriterion.value[index])
|
||||||
|
}
|
||||||
|
onRemoveCriterion={(index) => onRemove(index)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,11 +3,11 @@ import { Form } from "react-bootstrap";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||||
import { IDateValue } from "../../../models/list-filter/types";
|
import { IDateValue } from "../../../models/list-filter/types";
|
||||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "../../../models/list-filter/criteria/criterion";
|
||||||
import { DateInput } from "src/components/Shared/DateInput";
|
import { DateInput } from "src/components/Shared/DateInput";
|
||||||
|
|
||||||
interface IDateFilterProps {
|
interface IDateFilterProps {
|
||||||
criterion: Criterion<IDateValue>;
|
criterion: ModifierCriterion<IDateValue>;
|
||||||
onValueChanged: (value: IDateValue) => void;
|
onValueChanged: (value: IDateValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { useIntl } from "react-intl";
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { DurationInput } from "src/components/Shared/DurationInput";
|
import { DurationInput } from "src/components/Shared/DurationInput";
|
||||||
import { INumberValue } from "src/models/list-filter/types";
|
import { INumberValue } from "src/models/list-filter/types";
|
||||||
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "src/models/list-filter/criteria/criterion";
|
||||||
|
|
||||||
interface IDurationFilterProps {
|
interface IDurationFilterProps {
|
||||||
criterion: Criterion<INumberValue>;
|
criterion: ModifierCriterion<INumberValue>;
|
||||||
onValueChanged: (value: INumberValue) => void;
|
onValueChanged: (value: INumberValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import React from "react";
|
|||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
||||||
import { FilterSelect, SelectObject } from "src/components/Shared/Select";
|
import { FilterSelect, SelectObject } from "src/components/Shared/Select";
|
||||||
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "src/models/list-filter/criteria/criterion";
|
||||||
import { IHierarchicalLabelValue } from "src/models/list-filter/types";
|
import { IHierarchicalLabelValue } from "src/models/list-filter/types";
|
||||||
import { NumberField } from "src/utils/form";
|
import { NumberField } from "src/utils/form";
|
||||||
|
|
||||||
interface IHierarchicalLabelValueFilterProps {
|
interface IHierarchicalLabelValueFilterProps {
|
||||||
criterion: Criterion<IHierarchicalLabelValue>;
|
criterion: ModifierCriterion<IHierarchicalLabelValue>;
|
||||||
onValueChanged: (value: IHierarchicalLabelValue) => void;
|
onValueChanged: (value: IHierarchicalLabelValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HierarchicalLabelValueFilter: React.FC<
|
export const HierarchicalLabelValueFilter: React.FC<
|
||||||
IHierarchicalLabelValueFilterProps
|
IHierarchicalLabelValueFilterProps
|
||||||
> = ({ criterion, onValueChanged }) => {
|
> = ({ criterion, onValueChanged }) => {
|
||||||
const { criterionOption } = criterion;
|
const criterionOption = criterion.modifierCriterionOption();
|
||||||
const { type, inputType } = criterionOption;
|
const { type, inputType } = criterionOption;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
} from "../../../models/list-filter/criteria/criterion";
|
} from "../../../models/list-filter/criteria/criterion";
|
||||||
|
|
||||||
interface IInputFilterProps {
|
interface IInputFilterProps {
|
||||||
criterion: Criterion<CriterionValue>;
|
criterion: ModifierCriterion<CriterionValue>;
|
||||||
onValueChanged: (value: string) => void;
|
onValueChanged: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export const InputFilter: React.FC<IInputFilterProps> = ({
|
|||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
className="btn-secondary"
|
className="btn-secondary"
|
||||||
type={criterion.criterionOption.inputType}
|
type={criterion.modifierCriterionOption().inputType}
|
||||||
onChange={onChanged}
|
onChange={onChanged}
|
||||||
value={criterion.value ? criterion.value.toString() : ""}
|
value={criterion.value ? criterion.value.toString() : ""}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { Form } from "react-bootstrap";
|
|||||||
import { FilterSelect, SelectObject } from "src/components/Shared/Select";
|
import { FilterSelect, SelectObject } from "src/components/Shared/Select";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "src/models/list-filter/criteria/criterion";
|
||||||
import { ILabeledId } from "src/models/list-filter/types";
|
import { ILabeledId } from "src/models/list-filter/types";
|
||||||
|
|
||||||
interface ILabeledIdFilterProps {
|
interface ILabeledIdFilterProps {
|
||||||
criterion: Criterion<ILabeledId[]>;
|
criterion: ModifierCriterion<ILabeledId[]>;
|
||||||
onValueChanged: (value: ILabeledId[]) => void;
|
onValueChanged: (value: ILabeledId[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export const LabeledIdFilter: React.FC<ILabeledIdFilterProps> = ({
|
|||||||
criterion,
|
criterion,
|
||||||
onValueChanged,
|
onValueChanged,
|
||||||
}) => {
|
}) => {
|
||||||
const { criterionOption } = criterion;
|
const criterionOption = criterion.modifierCriterionOption();
|
||||||
const { inputType } = criterionOption;
|
const { inputType } = criterionOption;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import React from "react";
|
|||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
|
|
||||||
interface IOptionsFilter {
|
interface IOptionsFilter {
|
||||||
criterion: Criterion<CriterionValue>;
|
criterion: ModifierCriterion<CriterionValue>;
|
||||||
setCriterion: (c: Criterion<CriterionValue>) => void;
|
setCriterion: (c: ModifierCriterion<CriterionValue>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OptionFilter: React.FC<IOptionsFilter> = ({
|
export const OptionFilter: React.FC<IOptionsFilter> = ({
|
||||||
@@ -26,7 +26,7 @@ export const OptionFilter: React.FC<IOptionsFilter> = ({
|
|||||||
setCriterion(c);
|
setCriterion(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { options } = criterion.criterionOption;
|
const { options } = criterion.modifierCriterionOption();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="option-list-filter">
|
<div className="option-list-filter">
|
||||||
@@ -45,8 +45,8 @@ export const OptionFilter: React.FC<IOptionsFilter> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IOptionsListFilter {
|
interface IOptionsListFilter {
|
||||||
criterion: Criterion<CriterionValue>;
|
criterion: ModifierCriterion<CriterionValue>;
|
||||||
setCriterion: (c: Criterion<CriterionValue>) => void;
|
setCriterion: (c: ModifierCriterion<CriterionValue>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OptionListFilter: React.FC<IOptionsListFilter> = ({
|
export const OptionListFilter: React.FC<IOptionsListFilter> = ({
|
||||||
@@ -65,7 +65,7 @@ export const OptionListFilter: React.FC<IOptionsListFilter> = ({
|
|||||||
setCriterion(c);
|
setCriterion(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { options } = criterion.criterionOption;
|
const { options } = criterion.modifierCriterionOption();
|
||||||
const value = criterion.value as string[];
|
const value = criterion.value as string[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
} from "../../../models/list-filter/criteria/criterion";
|
} from "../../../models/list-filter/criteria/criterion";
|
||||||
|
|
||||||
interface IInputFilterProps {
|
interface IInputFilterProps {
|
||||||
criterion: Criterion<CriterionValue>;
|
criterion: ModifierCriterion<CriterionValue>;
|
||||||
onValueChanged: (value: string) => void;
|
onValueChanged: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export const PathFilter: React.FC<IInputFilterProps> = ({
|
|||||||
{regex ? (
|
{regex ? (
|
||||||
<Form.Control
|
<Form.Control
|
||||||
className="btn-secondary"
|
className="btn-secondary"
|
||||||
type={criterion.criterionOption.inputType}
|
type={criterion.modifierCriterionOption().inputType}
|
||||||
onChange={(v) => onValueChanged(v.target.value)}
|
onChange={(v) => onValueChanged(v.target.value)}
|
||||||
value={criterion.value ? criterion.value.toString() : ""}
|
value={criterion.value ? criterion.value.toString() : ""}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import React from "react";
|
|||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { IPhashDistanceValue } from "../../../models/list-filter/types";
|
import { IPhashDistanceValue } from "../../../models/list-filter/types";
|
||||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "../../../models/list-filter/criteria/criterion";
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { NumberField } from "src/utils/form";
|
import { NumberField } from "src/utils/form";
|
||||||
|
|
||||||
interface IPhashFilterProps {
|
interface IPhashFilterProps {
|
||||||
criterion: Criterion<IPhashDistanceValue>;
|
criterion: ModifierCriterion<IPhashDistanceValue>;
|
||||||
onValueChanged: (value: IPhashDistanceValue) => void;
|
onValueChanged: (value: IPhashDistanceValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React from "react";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||||
import { INumberValue } from "../../../models/list-filter/types";
|
import { INumberValue } from "../../../models/list-filter/types";
|
||||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "../../../models/list-filter/criteria/criterion";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
|
|
||||||
interface IRatingFilterProps {
|
interface IRatingFilterProps {
|
||||||
criterion: Criterion<INumberValue>;
|
criterion: ModifierCriterion<INumberValue>;
|
||||||
onValueChanged: (value: INumberValue) => void;
|
onValueChanged: (value: INumberValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "src/models/list-filter/types";
|
} from "src/models/list-filter/types";
|
||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
IHierarchicalLabeledIdCriterion,
|
IHierarchicalLabeledIdCriterion,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
||||||
@@ -316,7 +316,7 @@ const SelectableFilter: React.FC<ISelectableFilter> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IObjectsFilter<T extends Criterion<ILabeledValueListValue>> {
|
interface IObjectsFilter<T extends ModifierCriterion<ILabeledValueListValue>> {
|
||||||
criterion: T;
|
criterion: T;
|
||||||
setCriterion: (criterion: T) => void;
|
setCriterion: (criterion: T) => void;
|
||||||
useResults: (query: string) => { results: ILabeledId[]; loading: boolean };
|
useResults: (query: string) => { results: ILabeledId[]; loading: boolean };
|
||||||
@@ -324,7 +324,7 @@ interface IObjectsFilter<T extends Criterion<ILabeledValueListValue>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ObjectsFilter = <
|
export const ObjectsFilter = <
|
||||||
T extends Criterion<ILabeledValueListValue | IHierarchicalLabelValue>
|
T extends ModifierCriterion<ILabeledValueListValue | IHierarchicalLabelValue>
|
||||||
>({
|
>({
|
||||||
criterion,
|
criterion,
|
||||||
setCriterion,
|
setCriterion,
|
||||||
@@ -426,9 +426,10 @@ export const ObjectsFilter = <
|
|||||||
|
|
||||||
// if excludes is not a valid modifierOption then we can use `value.excluded`
|
// if excludes is not a valid modifierOption then we can use `value.excluded`
|
||||||
const canExclude =
|
const canExclude =
|
||||||
criterion.criterionOption.modifierOptions.find(
|
criterion
|
||||||
(m) => m === CriterionModifier.Excludes
|
.modifierCriterionOption()
|
||||||
) === undefined;
|
.modifierOptions.find((m) => m === CriterionModifier.Excludes) ===
|
||||||
|
undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectableFilter
|
<SelectableFilter
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React from "react";
|
|||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { IStashIDValue } from "../../../models/list-filter/types";
|
import { IStashIDValue } from "../../../models/list-filter/types";
|
||||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "../../../models/list-filter/criteria/criterion";
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
|
|
||||||
interface IStashIDFilterProps {
|
interface IStashIDFilterProps {
|
||||||
criterion: Criterion<IStashIDValue>;
|
criterion: ModifierCriterion<IStashIDValue>;
|
||||||
onValueChanged: (value: IStashIDValue) => void;
|
onValueChanged: (value: IStashIDValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { Form } from "react-bootstrap";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||||
import { ITimestampValue } from "../../../models/list-filter/types";
|
import { ITimestampValue } from "../../../models/list-filter/types";
|
||||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
import { ModifierCriterion } from "../../../models/list-filter/criteria/criterion";
|
||||||
import { DateInput } from "src/components/Shared/DateInput";
|
import { DateInput } from "src/components/Shared/DateInput";
|
||||||
|
|
||||||
interface ITimestampFilterProps {
|
interface ITimestampFilterProps {
|
||||||
criterion: Criterion<ITimestampValue>;
|
criterion: ModifierCriterion<ITimestampValue>;
|
||||||
onValueChanged: (value: ITimestampValue) => void;
|
onValueChanged: (value: ITimestampValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ import React, {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { QueryResult } from "@apollo/client";
|
import { QueryResult } from "@apollo/client";
|
||||||
import {
|
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
||||||
Criterion,
|
|
||||||
CriterionValue,
|
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import { EditFilterDialog } from "src/components/List/EditFilterDialog";
|
import { EditFilterDialog } from "src/components/List/EditFilterDialog";
|
||||||
import { FilterTags } from "./FilterTags";
|
import { FilterTags } from "./FilterTags";
|
||||||
@@ -220,8 +217,19 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
|
|||||||
result.refetch();
|
result.refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRemoveCriterion(removedCriterion: Criterion<CriterionValue>) {
|
function onRemoveCriterion(removedCriterion: Criterion, valueIndex?: number) {
|
||||||
updateFilter(filter.removeCriterion(removedCriterion.criterionOption.type));
|
if (valueIndex === undefined) {
|
||||||
|
updateFilter(
|
||||||
|
filter.removeCriterion(removedCriterion.criterionOption.type)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updateFilter(
|
||||||
|
filter.removeCustomFieldCriterion(
|
||||||
|
removedCriterion.criterionOption.type,
|
||||||
|
valueIndex
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClearAllCriteria() {
|
function onClearAllCriteria() {
|
||||||
|
|||||||
72
ui/v2.5/src/components/List/ModifierSelect.tsx
Normal file
72
ui/v2.5/src/components/List/ModifierSelect.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button, Form } from "react-bootstrap";
|
||||||
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
|
import { ModifierCriterion } from "src/models/list-filter/criteria/criterion";
|
||||||
|
import cx from "classnames";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
const defaultOptions = [
|
||||||
|
CriterionModifier.IsNull,
|
||||||
|
CriterionModifier.NotNull,
|
||||||
|
CriterionModifier.Equals,
|
||||||
|
CriterionModifier.NotEquals,
|
||||||
|
CriterionModifier.Includes,
|
||||||
|
CriterionModifier.Excludes,
|
||||||
|
CriterionModifier.GreaterThan,
|
||||||
|
CriterionModifier.LessThan,
|
||||||
|
CriterionModifier.Between,
|
||||||
|
CriterionModifier.NotBetween,
|
||||||
|
];
|
||||||
|
|
||||||
|
interface IModifierSelect {
|
||||||
|
options?: CriterionModifier[];
|
||||||
|
value: CriterionModifier;
|
||||||
|
onChanged: (m: CriterionModifier) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModifierSelectorButtons: React.FC<IModifierSelect> = ({
|
||||||
|
options = defaultOptions,
|
||||||
|
value,
|
||||||
|
onChanged,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Group className="modifier-options">
|
||||||
|
{options.map((m) => (
|
||||||
|
<Button
|
||||||
|
className={cx("modifier-option", {
|
||||||
|
selected: value === m,
|
||||||
|
})}
|
||||||
|
key={m}
|
||||||
|
onClick={() => onChanged(m)}
|
||||||
|
>
|
||||||
|
{ModifierCriterion.getModifierLabel(intl, m)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Form.Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModifierSelect: React.FC<IModifierSelect> = ({
|
||||||
|
options = defaultOptions,
|
||||||
|
value,
|
||||||
|
onChanged,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Control
|
||||||
|
as="select"
|
||||||
|
onChange={(e) => onChanged(e.target.value as CriterionModifier)}
|
||||||
|
value={value}
|
||||||
|
className="btn-secondary modifier-selector"
|
||||||
|
>
|
||||||
|
{options.map((m) => (
|
||||||
|
<option key={m} value={m}>
|
||||||
|
{ModifierCriterion.getModifierLabel(intl, m)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -67,7 +67,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||||||
mode: filter.mode,
|
mode: filter.mode,
|
||||||
name,
|
name,
|
||||||
find_filter: filterCopy.makeFindFilter(),
|
find_filter: filterCopy.makeFindFilter(),
|
||||||
object_filter: filterCopy.makeSavedFilter(),
|
object_filter: filterCopy.makeFilter(),
|
||||||
ui_options: filterCopy.makeSavedUIOptions(),
|
ui_options: filterCopy.makeSavedUIOptions(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -142,7 +142,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
|
|||||||
value: {
|
value: {
|
||||||
mode: filter.mode,
|
mode: filter.mode,
|
||||||
find_filter: filterCopy.makeFindFilter(),
|
find_filter: filterCopy.makeFindFilter(),
|
||||||
object_filter: filterCopy.makeSavedFilter(),
|
object_filter: filterCopy.makeFilter(),
|
||||||
ui_options: filterCopy.makeSavedUIOptions(),
|
ui_options: filterCopy.makeSavedUIOptions(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -587,3 +587,29 @@ input[type="range"].zoom-slider {
|
|||||||
.search-term-input {
|
.search-term-input {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-field-filter {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div:first-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-field-filter-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Icon } from "../Shared/Icon";
|
|||||||
import { TagLink } from "../Shared/TagLink";
|
import { TagLink } from "../Shared/TagLink";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||||
@@ -25,10 +25,10 @@ import ScreenUtils from "src/utils/screen";
|
|||||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||||
|
|
||||||
export interface IPerformerCardExtraCriteria {
|
export interface IPerformerCardExtraCriteria {
|
||||||
scenes?: Criterion<CriterionValue>[];
|
scenes?: ModifierCriterion<CriterionValue>[];
|
||||||
images?: Criterion<CriterionValue>[];
|
images?: ModifierCriterion<CriterionValue>[];
|
||||||
galleries?: Criterion<CriterionValue>[];
|
galleries?: ModifierCriterion<CriterionValue>[];
|
||||||
groups?: Criterion<CriterionValue>[];
|
groups?: ModifierCriterion<CriterionValue>[];
|
||||||
performer?: ILabeledId;
|
performer?: ILabeledId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { sortByRelevance } from "src/utils/query";
|
|||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { PatchComponent, PatchFunction } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
@@ -45,7 +45,7 @@ type Option = SelectOption<Scene>;
|
|||||||
type ExtraSceneProps = {
|
type ExtraSceneProps = {
|
||||||
hoverPlacement?: Placement;
|
hoverPlacement?: Placement;
|
||||||
excludeIds?: string[];
|
excludeIds?: string[];
|
||||||
extraCriteria?: Array<Criterion<CriterionValue>>;
|
extraCriteria?: Array<ModifierCriterion<CriterionValue>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FindScenesResult = Awaited<
|
type FindScenesResult = Awaited<
|
||||||
|
|||||||
@@ -856,6 +856,8 @@
|
|||||||
},
|
},
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"custom_fields": {
|
"custom_fields": {
|
||||||
|
"criteria_format_string": "{criterion} (custom field) {modifierString} {valueString}",
|
||||||
|
"criteria_format_string_others": "{criterion} (custom field) {modifierString} {valueString} (+{others} others)",
|
||||||
"field": "Field",
|
"field": "Field",
|
||||||
"title": "Custom Fields",
|
"title": "Custom Fields",
|
||||||
"value": "Value"
|
"value": "Value"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { languageMap, valueToCode } from "src/utils/caption";
|
import { languageMap, valueToCode } from "src/utils/caption";
|
||||||
import { CriterionOption, StringCriterion } from "./criterion";
|
import { ModifierCriterionOption, StringCriterion } from "./criterion";
|
||||||
|
|
||||||
const languageStrings = Array.from(languageMap.values());
|
const languageStrings = Array.from(languageMap.values());
|
||||||
|
|
||||||
export const CaptionsCriterionOption = new CriterionOption({
|
export const CaptionsCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "captions",
|
messageID: "captions",
|
||||||
type: "captions",
|
type: "captions",
|
||||||
modifierOptions: [
|
modifierOptions: [
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
CriterionModifier,
|
CriterionModifier,
|
||||||
} from "src/core/generated-graphql";
|
} from "src/core/generated-graphql";
|
||||||
import { circumcisedStrings, stringToCircumcised } from "src/utils/circumcised";
|
import { circumcisedStrings, stringToCircumcised } from "src/utils/circumcised";
|
||||||
import { CriterionOption, MultiStringCriterion } from "./criterion";
|
import { ModifierCriterionOption, MultiStringCriterion } from "./criterion";
|
||||||
|
|
||||||
export const CircumcisedCriterionOption = new CriterionOption({
|
export const CircumcisedCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "circumcised",
|
messageID: "circumcised",
|
||||||
type: "circumcised",
|
type: "circumcised",
|
||||||
modifierOptions: [
|
modifierOptions: [
|
||||||
|
|||||||
@@ -57,10 +57,41 @@ const modifierMessageIDs = {
|
|||||||
[CriterionModifier.NotBetween]: "criterion_modifier.not_between",
|
[CriterionModifier.NotBetween]: "criterion_modifier.not_between",
|
||||||
};
|
};
|
||||||
|
|
||||||
// V = criterion value type
|
export abstract class Criterion {
|
||||||
export abstract class Criterion<V extends CriterionValue> {
|
|
||||||
public criterionOption: CriterionOption;
|
public criterionOption: CriterionOption;
|
||||||
|
|
||||||
|
constructor(type: CriterionOption) {
|
||||||
|
this.criterionOption = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isValid(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clone() {
|
||||||
|
const ret = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
||||||
|
ret.cloneValues();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected cloneValues() {}
|
||||||
|
|
||||||
|
public abstract getLabel(intl: IntlShape): string;
|
||||||
|
|
||||||
|
public getId(): string {
|
||||||
|
return `${this.criterionOption.type}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract toJSON(): string;
|
||||||
|
|
||||||
|
public abstract applyToCriterionInput(input: Record<string, unknown>): void;
|
||||||
|
public abstract setFromSavedCriterion(criterion: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// V = criterion value type
|
||||||
|
export abstract class ModifierCriterion<
|
||||||
|
V extends CriterionValue
|
||||||
|
> extends Criterion {
|
||||||
protected _modifier!: CriterionModifier;
|
protected _modifier!: CriterionModifier;
|
||||||
public get modifier(): CriterionModifier {
|
public get modifier(): CriterionModifier {
|
||||||
return this._modifier;
|
return this._modifier;
|
||||||
@@ -83,12 +114,16 @@ export abstract class Criterion<V extends CriterionValue> {
|
|||||||
|
|
||||||
protected abstract getLabelValue(intl: IntlShape): string;
|
protected abstract getLabelValue(intl: IntlShape): string;
|
||||||
|
|
||||||
constructor(type: CriterionOption, value: V) {
|
constructor(type: ModifierCriterionOption, value: V) {
|
||||||
this.criterionOption = type;
|
super(type);
|
||||||
this.modifier = type.defaultModifier;
|
this.modifier = type.defaultModifier;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public modifierCriterionOption() {
|
||||||
|
return this.criterionOption as ModifierCriterionOption;
|
||||||
|
}
|
||||||
|
|
||||||
public clone() {
|
public clone() {
|
||||||
const ret = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
const ret = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
||||||
ret.cloneValues();
|
ret.cloneValues();
|
||||||
@@ -106,7 +141,10 @@ export abstract class Criterion<V extends CriterionValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape): string {
|
||||||
const modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
const modifierString = ModifierCriterion.getModifierLabel(
|
||||||
|
intl,
|
||||||
|
this.modifier
|
||||||
|
);
|
||||||
let valueString = "";
|
let valueString = "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -126,10 +164,6 @@ export abstract class Criterion<V extends CriterionValue> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getId(): string {
|
|
||||||
return `${this.criterionOption.type}-${this.modifier.toString()}`; // TODO add values?
|
|
||||||
}
|
|
||||||
|
|
||||||
public toJSON() {
|
public toJSON() {
|
||||||
let encodedCriterion;
|
let encodedCriterion;
|
||||||
if (
|
if (
|
||||||
@@ -157,14 +191,11 @@ export abstract class Criterion<V extends CriterionValue> {
|
|||||||
this.modifier = criterion.modifier;
|
this.modifier = criterion.modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toCriterionInput(): unknown {
|
public applyToCriterionInput(input: Record<string, unknown>) {
|
||||||
return {
|
input[this.criterionOption.type] = this.toCriterionInput();
|
||||||
value: this.value,
|
|
||||||
modifier: this.modifier,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public toSavedCriterion(): ISavedCriterion<V> {
|
public toCriterionInput(): unknown {
|
||||||
return {
|
return {
|
||||||
value: this.value,
|
value: this.value,
|
||||||
modifier: this.modifier,
|
modifier: this.modifier,
|
||||||
@@ -185,44 +216,31 @@ export type InputType =
|
|||||||
| "galleries"
|
| "galleries"
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
interface ICriterionOptionsParams {
|
type MakeCriterionFn = (
|
||||||
|
o: CriterionOption,
|
||||||
|
config?: ConfigDataFragment
|
||||||
|
) => Criterion;
|
||||||
|
|
||||||
|
interface ICriterionOptionParams {
|
||||||
messageID: string;
|
messageID: string;
|
||||||
type: CriterionType;
|
type: CriterionType;
|
||||||
inputType?: InputType;
|
makeCriterion: MakeCriterionFn;
|
||||||
modifierOptions?: CriterionModifier[];
|
|
||||||
defaultModifier?: CriterionModifier;
|
|
||||||
options?: Option[];
|
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
makeCriterion: (
|
|
||||||
o: CriterionOption,
|
|
||||||
config?: ConfigDataFragment
|
|
||||||
) => Criterion<CriterionValue>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CriterionOption {
|
export class CriterionOption {
|
||||||
public readonly messageID: string;
|
|
||||||
public readonly type: CriterionType;
|
public readonly type: CriterionType;
|
||||||
public readonly modifierOptions: CriterionModifier[];
|
public readonly messageID: string;
|
||||||
public readonly defaultModifier: CriterionModifier;
|
public readonly makeCriterionFn: MakeCriterionFn;
|
||||||
public readonly options: Option[] | undefined;
|
|
||||||
public readonly inputType: InputType;
|
|
||||||
|
|
||||||
// used for legacy criteria that are not shown in the UI
|
// used for legacy criteria that are not shown in the UI
|
||||||
public readonly hidden: boolean = false;
|
public readonly hidden: boolean = false;
|
||||||
|
|
||||||
public readonly makeCriterionFn: (
|
constructor(options: ICriterionOptionParams) {
|
||||||
o: CriterionOption,
|
|
||||||
config?: ConfigDataFragment
|
|
||||||
) => Criterion<CriterionValue>;
|
|
||||||
|
|
||||||
constructor(options: ICriterionOptionsParams) {
|
|
||||||
this.messageID = options.messageID;
|
|
||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
this.modifierOptions = options.modifierOptions ?? [];
|
this.messageID = options.messageID;
|
||||||
this.defaultModifier = options.defaultModifier ?? CriterionModifier.Equals;
|
|
||||||
this.options = options.options;
|
|
||||||
this.inputType = options.inputType;
|
|
||||||
this.hidden = options.hidden ?? false;
|
|
||||||
this.makeCriterionFn = options.makeCriterion;
|
this.makeCriterionFn = options.makeCriterion;
|
||||||
|
this.hidden = options.hidden ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public makeCriterion(config?: ConfigDataFragment) {
|
public makeCriterion(config?: ConfigDataFragment) {
|
||||||
@@ -230,13 +248,35 @@ export class CriterionOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ILabeledIdCriterionOption extends CriterionOption {
|
interface IModifierCriterionOptionParams extends ICriterionOptionParams {
|
||||||
|
inputType?: InputType;
|
||||||
|
modifierOptions?: CriterionModifier[];
|
||||||
|
defaultModifier?: CriterionModifier;
|
||||||
|
options?: Option[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModifierCriterionOption extends CriterionOption {
|
||||||
|
public readonly modifierOptions: CriterionModifier[];
|
||||||
|
public readonly defaultModifier: CriterionModifier;
|
||||||
|
public readonly options: Option[] | undefined;
|
||||||
|
public readonly inputType: InputType;
|
||||||
|
|
||||||
|
constructor(options: IModifierCriterionOptionParams) {
|
||||||
|
super(options);
|
||||||
|
this.modifierOptions = options.modifierOptions ?? [];
|
||||||
|
this.defaultModifier = options.defaultModifier ?? CriterionModifier.Equals;
|
||||||
|
this.options = options.options;
|
||||||
|
this.inputType = options.inputType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ILabeledIdCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
includeAll: boolean,
|
includeAll: boolean,
|
||||||
inputType: InputType,
|
inputType: InputType,
|
||||||
makeCriterion?: () => Criterion<CriterionValue>
|
makeCriterion?: () => ModifierCriterion<CriterionValue>
|
||||||
) {
|
) {
|
||||||
const modifierOptions = [
|
const modifierOptions = [
|
||||||
CriterionModifier.Includes,
|
CriterionModifier.Includes,
|
||||||
@@ -264,8 +304,8 @@ export class ILabeledIdCriterionOption extends CriterionOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ILabeledIdCriterion extends Criterion<ILabeledId[]> {
|
export class ILabeledIdCriterion extends ModifierCriterion<ILabeledId[]> {
|
||||||
constructor(type: CriterionOption, value: ILabeledId[] = []) {
|
constructor(type: ModifierCriterionOption, value: ILabeledId[] = []) {
|
||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,9 +336,9 @@ export class ILabeledIdCriterion extends Criterion<ILabeledId[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabelValue> {
|
export class IHierarchicalLabeledIdCriterion extends ModifierCriterion<IHierarchicalLabelValue> {
|
||||||
constructor(
|
constructor(
|
||||||
type: CriterionOption,
|
type: ModifierCriterionOption,
|
||||||
value: IHierarchicalLabelValue = {
|
value: IHierarchicalLabelValue = {
|
||||||
items: [],
|
items: [],
|
||||||
excluded: [],
|
excluded: [],
|
||||||
@@ -346,14 +386,16 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modifierOptions =
|
||||||
|
(this.criterionOption as ModifierCriterionOption).modifierOptions ?? [];
|
||||||
|
|
||||||
// if the previous modifier was excludes, replace it with the equivalent includes criterion
|
// if the previous modifier was excludes, replace it with the equivalent includes criterion
|
||||||
// this is what is done on the backend
|
// this is what is done on the backend
|
||||||
// only replace if excludes is not a valid modifierOption
|
// only replace if excludes is not a valid modifierOption
|
||||||
if (
|
if (
|
||||||
modifier === CriterionModifier.Excludes &&
|
modifier === CriterionModifier.Excludes &&
|
||||||
this.criterionOption.modifierOptions.find(
|
modifierOptions.find((m) => m === CriterionModifier.Excludes) ===
|
||||||
(m) => m === CriterionModifier.Excludes
|
undefined
|
||||||
) === undefined
|
|
||||||
) {
|
) {
|
||||||
this.modifier = CriterionModifier.Includes;
|
this.modifier = CriterionModifier.Includes;
|
||||||
this.value.excluded = [...this.value.excluded, ...this.value.items];
|
this.value.excluded = [...this.value.excluded, ...this.value.items];
|
||||||
@@ -407,7 +449,10 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape): string {
|
||||||
let id = "criterion_modifier.format_string";
|
let id = "criterion_modifier.format_string";
|
||||||
let modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
let modifierString = ModifierCriterion.getModifierLabel(
|
||||||
|
intl,
|
||||||
|
this.modifier
|
||||||
|
);
|
||||||
let valueString = "";
|
let valueString = "";
|
||||||
let excludedString = "";
|
let excludedString = "";
|
||||||
|
|
||||||
@@ -419,7 +464,7 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||||||
|
|
||||||
if (this.value.excluded && this.value.excluded.length > 0) {
|
if (this.value.excluded && this.value.excluded.length > 0) {
|
||||||
if (this.value.items.length === 0) {
|
if (this.value.items.length === 0) {
|
||||||
modifierString = Criterion.getModifierLabel(
|
modifierString = ModifierCriterion.getModifierLabel(
|
||||||
intl,
|
intl,
|
||||||
CriterionModifier.Excludes
|
CriterionModifier.Excludes
|
||||||
);
|
);
|
||||||
@@ -448,11 +493,11 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StringCriterionOption extends CriterionOption {
|
export class StringCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion?: () => Criterion<CriterionValue>
|
makeCriterion?: () => ModifierCriterion<CriterionValue>
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -483,7 +528,7 @@ export function createStringCriterionOption(
|
|||||||
return new StringCriterionOption(messageID ?? type, type);
|
return new StringCriterionOption(messageID ?? type, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MandatoryStringCriterionOption extends CriterionOption {
|
export class MandatoryStringCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, value: CriterionType) {
|
constructor(messageID: string, value: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -510,8 +555,8 @@ export function createMandatoryStringCriterionOption(
|
|||||||
return new MandatoryStringCriterionOption(messageID ?? value, value);
|
return new MandatoryStringCriterionOption(messageID ?? value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StringCriterion extends Criterion<string> {
|
export class StringCriterion extends ModifierCriterion<string> {
|
||||||
constructor(type: CriterionOption) {
|
constructor(type: ModifierCriterionOption) {
|
||||||
super(type, "");
|
super(type, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,8 +573,8 @@ export class StringCriterion extends Criterion<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class MultiStringCriterion extends Criterion<string[]> {
|
export abstract class MultiStringCriterion extends ModifierCriterion<string[]> {
|
||||||
constructor(type: CriterionOption, value: string[] = []) {
|
constructor(type: ModifierCriterionOption, value: string[] = []) {
|
||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,11 +595,11 @@ export abstract class MultiStringCriterion extends Criterion<string[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BooleanCriterionOption extends CriterionOption {
|
export class BooleanCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion?: () => Criterion<CriterionValue>
|
makeCriterion?: () => ModifierCriterion<CriterionValue>
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -586,11 +631,11 @@ export class BooleanCriterion extends StringCriterion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StringBooleanCriterionOption extends CriterionOption {
|
export class StringBooleanCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion?: () => Criterion<CriterionValue>
|
makeCriterion?: () => ModifierCriterion<CriterionValue>
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -613,7 +658,7 @@ export class StringBooleanCriterion extends StringCriterion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberCriterionOption extends CriterionOption {
|
export class NumberCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, value: CriterionType) {
|
constructor(messageID: string, value: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -642,11 +687,11 @@ export function createNumberCriterionOption(
|
|||||||
return new NumberCriterionOption(messageID ?? value, value);
|
return new NumberCriterionOption(messageID ?? value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NullNumberCriterionOption extends CriterionOption {
|
export class NullNumberCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion?: () => Criterion<CriterionValue>
|
makeCriterion?: MakeCriterionFn
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -677,11 +722,11 @@ export function createNullNumberCriterionOption(
|
|||||||
return new NullNumberCriterionOption(messageID ?? value, value);
|
return new NullNumberCriterionOption(messageID ?? value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MandatoryNumberCriterionOption extends CriterionOption {
|
export class MandatoryNumberCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion?: () => Criterion<CriterionValue>
|
makeCriterion?: () => ModifierCriterion<CriterionValue>
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -710,8 +755,8 @@ export function createMandatoryNumberCriterionOption(
|
|||||||
return new MandatoryNumberCriterionOption(messageID ?? value, value);
|
return new MandatoryNumberCriterionOption(messageID ?? value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberCriterion extends Criterion<INumberValue> {
|
export class NumberCriterion extends ModifierCriterion<INumberValue> {
|
||||||
constructor(type: CriterionOption) {
|
constructor(type: ModifierCriterionOption) {
|
||||||
super(type, { value: undefined, value2: undefined });
|
super(type, { value: undefined, value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,8 +850,8 @@ export function createNullDurationCriterionOption(
|
|||||||
return new NullDurationCriterionOption(messageID ?? value, value);
|
return new NullDurationCriterionOption(messageID ?? value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DurationCriterion extends Criterion<INumberValue> {
|
export class DurationCriterion extends ModifierCriterion<INumberValue> {
|
||||||
constructor(type: CriterionOption) {
|
constructor(type: ModifierCriterionOption) {
|
||||||
super(type, { value: undefined, value2: undefined });
|
super(type, { value: undefined, value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,7 +905,7 @@ export class DurationCriterion extends Criterion<INumberValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DateCriterionOption extends CriterionOption {
|
export class DateCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, value: CriterionType) {
|
constructor(messageID: string, value: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -886,8 +931,8 @@ export function createDateCriterionOption(value: CriterionType) {
|
|||||||
return new DateCriterionOption(value, value);
|
return new DateCriterionOption(value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DateCriterion extends Criterion<IDateValue> {
|
export class DateCriterion extends ModifierCriterion<IDateValue> {
|
||||||
constructor(type: CriterionOption) {
|
constructor(type: ModifierCriterionOption) {
|
||||||
super(type, { value: "", value2: undefined });
|
super(type, { value: "", value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -943,7 +988,7 @@ export class DateCriterion extends Criterion<IDateValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimestampCriterionOption extends CriterionOption {
|
export class TimestampCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, value: CriterionType) {
|
constructor(messageID: string, value: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -967,7 +1012,7 @@ export function createTimestampCriterionOption(value: CriterionType) {
|
|||||||
return new TimestampCriterionOption(value, value);
|
return new TimestampCriterionOption(value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MandatoryTimestampCriterionOption extends CriterionOption {
|
export class MandatoryTimestampCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, value: CriterionType) {
|
constructor(messageID: string, value: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -989,8 +1034,8 @@ export function createMandatoryTimestampCriterionOption(value: CriterionType) {
|
|||||||
return new MandatoryTimestampCriterionOption(value, value);
|
return new MandatoryTimestampCriterionOption(value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimestampCriterion extends Criterion<ITimestampValue> {
|
export class TimestampCriterion extends ModifierCriterion<ITimestampValue> {
|
||||||
constructor(type: CriterionOption) {
|
constructor(type: ModifierCriterionOption) {
|
||||||
super(type, { value: "", value2: undefined });
|
super(type, { value: "", value2: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
ui/v2.5/src/models/list-filter/criteria/custom-fields.ts
Normal file
109
ui/v2.5/src/models/list-filter/criteria/custom-fields.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { IntlShape } from "react-intl";
|
||||||
|
import { Criterion, CriterionOption, ModifierCriterion } from "./criterion";
|
||||||
|
import {
|
||||||
|
CriterionModifier,
|
||||||
|
CustomFieldCriterionInput,
|
||||||
|
} from "src/core/generated-graphql";
|
||||||
|
import { cloneDeep } from "@apollo/client/utilities";
|
||||||
|
|
||||||
|
export const CustomFieldsCriterionOption = new CriterionOption({
|
||||||
|
type: "custom_fields",
|
||||||
|
messageID: "custom_fields.title",
|
||||||
|
makeCriterion: () => new CustomFieldsCriterion(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export class CustomFieldsCriterion extends Criterion {
|
||||||
|
public value: CustomFieldCriterionInput[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(CustomFieldsCriterionOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isValid(): boolean {
|
||||||
|
return this.value.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyToCriterionInput(input: Record<string, unknown>): void {
|
||||||
|
input.custom_fields = cloneDeep(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLabel(intl: IntlShape): string {
|
||||||
|
// show first criterion
|
||||||
|
if (this.value.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = this.value[0];
|
||||||
|
let messageID;
|
||||||
|
let valueString = "";
|
||||||
|
|
||||||
|
if (
|
||||||
|
first.modifier !== CriterionModifier.IsNull &&
|
||||||
|
first.modifier !== CriterionModifier.NotNull &&
|
||||||
|
(first.value?.length ?? 0) > 0
|
||||||
|
) {
|
||||||
|
valueString = (first.value![0] as string) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifierString = ModifierCriterion.getModifierLabel(
|
||||||
|
intl,
|
||||||
|
first.modifier
|
||||||
|
);
|
||||||
|
const opts = {
|
||||||
|
criterion: first.field,
|
||||||
|
modifierString,
|
||||||
|
valueString,
|
||||||
|
others: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.value.length === 1) {
|
||||||
|
messageID = "custom_fields.criteria_format_string";
|
||||||
|
} else {
|
||||||
|
messageID = "custom_fields.criteria_format_string_others";
|
||||||
|
opts.others = (this.value.length - 1).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return intl.formatMessage({ id: messageID }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValueLabel(intl: IntlShape, v: CustomFieldCriterionInput): string {
|
||||||
|
let valueString = "";
|
||||||
|
|
||||||
|
if (
|
||||||
|
v.modifier !== CriterionModifier.IsNull &&
|
||||||
|
v.modifier !== CriterionModifier.NotNull &&
|
||||||
|
(v.value?.length ?? 0) > 0
|
||||||
|
) {
|
||||||
|
valueString = (v.value![0] as string) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifierString = ModifierCriterion.getModifierLabel(intl, v.modifier);
|
||||||
|
const opts = {
|
||||||
|
criterion: v.field,
|
||||||
|
modifierString,
|
||||||
|
valueString,
|
||||||
|
others: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return intl.formatMessage(
|
||||||
|
{ id: "custom_fields.criteria_format_string" },
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toJSON(): string {
|
||||||
|
const encodedCriterion = {
|
||||||
|
type: this.criterionOption.type,
|
||||||
|
value: this.value,
|
||||||
|
};
|
||||||
|
return JSON.stringify(encodedCriterion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setFromSavedCriterion(criterion: {
|
||||||
|
type: string;
|
||||||
|
value: CustomFieldCriterionInput[];
|
||||||
|
}): void {
|
||||||
|
const { value } = criterion;
|
||||||
|
this.value = cloneDeep(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
} from "src/core/generated-graphql";
|
} from "src/core/generated-graphql";
|
||||||
import { genderStrings, stringToGender } from "src/utils/gender";
|
import { genderStrings, stringToGender } from "src/utils/gender";
|
||||||
import {
|
import {
|
||||||
CriterionOption,
|
ModifierCriterionOption,
|
||||||
ISavedCriterion,
|
ISavedCriterion,
|
||||||
MultiStringCriterion,
|
MultiStringCriterion,
|
||||||
} from "./criterion";
|
} from "./criterion";
|
||||||
|
|
||||||
export const GenderCriterionOption = new CriterionOption({
|
export const GenderCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "gender",
|
messageID: "gender",
|
||||||
type: "gender",
|
type: "gender",
|
||||||
options: genderStrings,
|
options: genderStrings,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { CriterionOption, IHierarchicalLabeledIdCriterion } from "./criterion";
|
import {
|
||||||
|
ModifierCriterionOption,
|
||||||
|
IHierarchicalLabeledIdCriterion,
|
||||||
|
} from "./criterion";
|
||||||
import { CriterionType } from "../types";
|
import { CriterionType } from "../types";
|
||||||
|
|
||||||
const inputType = "groups";
|
const inputType = "groups";
|
||||||
@@ -13,7 +16,7 @@ const modifierOptions = [
|
|||||||
|
|
||||||
const defaultModifier = CriterionModifier.Includes;
|
const defaultModifier = CriterionModifier.Includes;
|
||||||
|
|
||||||
class BaseGroupsCriterionOption extends CriterionOption {
|
class BaseGroupsCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, type: CriterionType) {
|
constructor(messageID: string, type: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
@@ -44,7 +47,7 @@ export const SubGroupsCriterionOption = new BaseGroupsCriterionOption(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// redirects to GroupsCriterion
|
// redirects to GroupsCriterion
|
||||||
export const LegacyMoviesCriterionOption = new CriterionOption({
|
export const LegacyMoviesCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "groups",
|
messageID: "groups",
|
||||||
type: "movies",
|
type: "movies",
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { CriterionType } from "../types";
|
import { CriterionType } from "../types";
|
||||||
import { CriterionOption, StringCriterion, Option } from "./criterion";
|
import { ModifierCriterionOption, StringCriterion, Option } from "./criterion";
|
||||||
|
|
||||||
export class IsMissingCriterion extends StringCriterion {
|
export class IsMissingCriterion extends StringCriterion {
|
||||||
public toCriterionInput(): string {
|
public toCriterionInput(): string {
|
||||||
@@ -8,7 +8,7 @@ export class IsMissingCriterion extends StringCriterion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IsMissingCriterionOption extends CriterionOption {
|
class IsMissingCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(messageID: string, type: CriterionType, options: Option[]) {
|
constructor(messageID: string, type: CriterionType, options: Option[]) {
|
||||||
super({
|
super({
|
||||||
messageID,
|
messageID,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { orientationStrings, stringToOrientation } from "src/utils/orientation";
|
import { orientationStrings, stringToOrientation } from "src/utils/orientation";
|
||||||
import { CriterionType } from "../types";
|
import { CriterionType } from "../types";
|
||||||
import { CriterionOption, MultiStringCriterion } from "./criterion";
|
import { ModifierCriterionOption, MultiStringCriterion } from "./criterion";
|
||||||
import {
|
import {
|
||||||
OrientationCriterionInput,
|
OrientationCriterionInput,
|
||||||
OrientationEnum,
|
OrientationEnum,
|
||||||
@@ -16,7 +16,7 @@ export class OrientationCriterion extends MultiStringCriterion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseOrientationCriterionOption extends CriterionOption {
|
class BaseOrientationCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(value: CriterionType) {
|
constructor(value: CriterionType) {
|
||||||
super({
|
super({
|
||||||
messageID: value,
|
messageID: value,
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ 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, ISavedCriterion } from "./criterion";
|
import {
|
||||||
|
ModifierCriterion,
|
||||||
|
ModifierCriterionOption,
|
||||||
|
ISavedCriterion,
|
||||||
|
} from "./criterion";
|
||||||
|
|
||||||
const modifierOptions = [
|
const modifierOptions = [
|
||||||
CriterionModifier.IncludesAll,
|
CriterionModifier.IncludesAll,
|
||||||
@@ -19,7 +23,7 @@ const defaultModifier = CriterionModifier.IncludesAll;
|
|||||||
|
|
||||||
const inputType = "performers";
|
const inputType = "performers";
|
||||||
|
|
||||||
export const PerformersCriterionOption = new CriterionOption({
|
export const PerformersCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "performers",
|
messageID: "performers",
|
||||||
type: "performers",
|
type: "performers",
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
@@ -28,7 +32,7 @@ export const PerformersCriterionOption = new CriterionOption({
|
|||||||
makeCriterion: () => new PerformersCriterion(),
|
makeCriterion: () => new PerformersCriterion(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
export class PerformersCriterion extends ModifierCriterion<ILabeledValueListValue> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(PerformersCriterionOption, { items: [], excluded: [] });
|
super(PerformersCriterionOption, { items: [], excluded: [] });
|
||||||
}
|
}
|
||||||
@@ -116,7 +120,10 @@ export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
|||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape): string {
|
||||||
let id = "criterion_modifier.format_string";
|
let id = "criterion_modifier.format_string";
|
||||||
let modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
let modifierString = ModifierCriterion.getModifierLabel(
|
||||||
|
intl,
|
||||||
|
this.modifier
|
||||||
|
);
|
||||||
let valueString = "";
|
let valueString = "";
|
||||||
let excludedString = "";
|
let excludedString = "";
|
||||||
|
|
||||||
@@ -128,7 +135,7 @@ export class PerformersCriterion extends Criterion<ILabeledValueListValue> {
|
|||||||
|
|
||||||
if (this.value.excluded && this.value.excluded.length > 0) {
|
if (this.value.excluded && this.value.excluded.length > 0) {
|
||||||
if (this.value.items.length === 0) {
|
if (this.value.items.length === 0) {
|
||||||
modifierString = Criterion.getModifierLabel(
|
modifierString = ModifierCriterion.getModifierLabel(
|
||||||
intl,
|
intl,
|
||||||
CriterionModifier.Excludes
|
CriterionModifier.Excludes
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
import { IPhashDistanceValue } from "../types";
|
import { IPhashDistanceValue } from "../types";
|
||||||
import {
|
import {
|
||||||
BooleanCriterionOption,
|
BooleanCriterionOption,
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionOption,
|
ModifierCriterionOption,
|
||||||
StringCriterion,
|
StringCriterion,
|
||||||
} from "./criterion";
|
} from "./criterion";
|
||||||
|
|
||||||
export const PhashCriterionOption = new CriterionOption({
|
export const PhashCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "media_info.phash",
|
messageID: "media_info.phash",
|
||||||
type: "phash_distance",
|
type: "phash_distance",
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
@@ -24,7 +24,7 @@ export const PhashCriterionOption = new CriterionOption({
|
|||||||
makeCriterion: () => new PhashCriterion(),
|
makeCriterion: () => new PhashCriterion(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class PhashCriterion extends Criterion<IPhashDistanceValue> {
|
export class PhashCriterion extends ModifierCriterion<IPhashDistanceValue> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(PhashCriterionOption, { value: "", distance: 0 });
|
super(PhashCriterionOption, { value: "", distance: 0 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
IntCriterionInput,
|
IntCriterionInput,
|
||||||
} from "src/core/generated-graphql";
|
} from "src/core/generated-graphql";
|
||||||
import { INumberValue } from "../types";
|
import { INumberValue } from "../types";
|
||||||
import { Criterion, CriterionOption } from "./criterion";
|
import { ModifierCriterion, ModifierCriterionOption } from "./criterion";
|
||||||
|
|
||||||
const modifierOptions = [
|
const modifierOptions = [
|
||||||
CriterionModifier.Equals,
|
CriterionModifier.Equals,
|
||||||
@@ -27,7 +27,7 @@ function getRatingSystemOptions(config?: ConfigDataFragment) {
|
|||||||
return config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
return config?.ui.ratingSystemOptions ?? defaultRatingSystemOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RatingCriterionOption = new CriterionOption({
|
export const RatingCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "rating",
|
messageID: "rating",
|
||||||
type: "rating100",
|
type: "rating100",
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
@@ -37,7 +37,7 @@ export const RatingCriterionOption = new CriterionOption({
|
|||||||
inputType: "number",
|
inputType: "number",
|
||||||
});
|
});
|
||||||
|
|
||||||
export class RatingCriterion extends Criterion<INumberValue> {
|
export class RatingCriterion extends ModifierCriterion<INumberValue> {
|
||||||
ratingSystem: RatingSystemOptions;
|
ratingSystem: RatingSystemOptions;
|
||||||
|
|
||||||
constructor(ratingSystem: RatingSystemOptions) {
|
constructor(ratingSystem: RatingSystemOptions) {
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import {
|
|||||||
import { stringToResolution, resolutionStrings } from "src/utils/resolution";
|
import { stringToResolution, resolutionStrings } from "src/utils/resolution";
|
||||||
import { CriterionType } from "../types";
|
import { CriterionType } from "../types";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionOption,
|
ModifierCriterionOption,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
StringCriterion,
|
StringCriterion,
|
||||||
} from "./criterion";
|
} from "./criterion";
|
||||||
|
|
||||||
class BaseResolutionCriterionOption extends CriterionOption {
|
class BaseResolutionCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
value: CriterionType,
|
value: CriterionType,
|
||||||
makeCriterion: () => Criterion<CriterionValue>
|
makeCriterion: () => ModifierCriterion<CriterionValue>
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
messageID: value,
|
messageID: value,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
CriterionOption,
|
ModifierCriterionOption,
|
||||||
ILabeledIdCriterion,
|
ILabeledIdCriterion,
|
||||||
ILabeledIdCriterionOption,
|
ILabeledIdCriterionOption,
|
||||||
} from "./criterion";
|
} from "./criterion";
|
||||||
@@ -28,7 +28,7 @@ const modifierOptions = [
|
|||||||
|
|
||||||
const defaultModifier = CriterionModifier.Includes;
|
const defaultModifier = CriterionModifier.Includes;
|
||||||
|
|
||||||
export const MarkersScenesCriterionOption = new CriterionOption({
|
export const MarkersScenesCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "scenes",
|
messageID: "scenes",
|
||||||
type: "scenes",
|
type: "scenes",
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
StashIdCriterionInput,
|
StashIdCriterionInput,
|
||||||
} from "src/core/generated-graphql";
|
} from "src/core/generated-graphql";
|
||||||
import { IStashIDValue } from "../types";
|
import { IStashIDValue } from "../types";
|
||||||
import { Criterion, CriterionOption } from "./criterion";
|
import { ModifierCriterion, ModifierCriterionOption } from "./criterion";
|
||||||
|
|
||||||
export const StashIDCriterionOption = new CriterionOption({
|
export const StashIDCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "stash_id",
|
messageID: "stash_id",
|
||||||
type: "stash_id_endpoint",
|
type: "stash_id_endpoint",
|
||||||
modifierOptions: [
|
modifierOptions: [
|
||||||
@@ -19,7 +19,7 @@ export const StashIDCriterionOption = new CriterionOption({
|
|||||||
makeCriterion: () => new StashIDCriterion(),
|
makeCriterion: () => new StashIDCriterion(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class StashIDCriterion extends Criterion<IStashIDValue> {
|
export class StashIDCriterion extends ModifierCriterion<IStashIDValue> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(StashIDCriterionOption, {
|
super(StashIDCriterionOption, {
|
||||||
endpoint: "",
|
endpoint: "",
|
||||||
@@ -56,7 +56,10 @@ export class StashIDCriterion extends Criterion<IStashIDValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getLabel(intl: IntlShape): string {
|
public getLabel(intl: IntlShape): string {
|
||||||
const modifierString = Criterion.getModifierLabel(intl, this.modifier);
|
const modifierString = ModifierCriterion.getModifierLabel(
|
||||||
|
intl,
|
||||||
|
this.modifier
|
||||||
|
);
|
||||||
let valueString = "";
|
let valueString = "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
CriterionOption,
|
ModifierCriterionOption,
|
||||||
IHierarchicalLabeledIdCriterion,
|
IHierarchicalLabeledIdCriterion,
|
||||||
ILabeledIdCriterion,
|
ILabeledIdCriterion,
|
||||||
ILabeledIdCriterionOption,
|
ILabeledIdCriterionOption,
|
||||||
@@ -15,7 +15,7 @@ const modifierOptions = [
|
|||||||
const defaultModifier = CriterionModifier.Includes;
|
const defaultModifier = CriterionModifier.Includes;
|
||||||
const inputType = "studios";
|
const inputType = "studios";
|
||||||
|
|
||||||
export const StudiosCriterionOption = new CriterionOption({
|
export const StudiosCriterionOption = new ModifierCriterionOption({
|
||||||
messageID: "studios",
|
messageID: "studios",
|
||||||
type: "studios",
|
type: "studios",
|
||||||
modifierOptions,
|
modifierOptions,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { CriterionOption, IHierarchicalLabeledIdCriterion } from "./criterion";
|
import {
|
||||||
|
ModifierCriterionOption,
|
||||||
|
IHierarchicalLabeledIdCriterion,
|
||||||
|
} from "./criterion";
|
||||||
import { CriterionType } from "../types";
|
import { CriterionType } from "../types";
|
||||||
|
|
||||||
const defaultModifierOptions = [
|
const defaultModifierOptions = [
|
||||||
@@ -20,7 +23,7 @@ const withoutEqualsModifierOptions = [
|
|||||||
const defaultModifier = CriterionModifier.IncludesAll;
|
const defaultModifier = CriterionModifier.IncludesAll;
|
||||||
const inputType = "tags";
|
const inputType = "tags";
|
||||||
|
|
||||||
class BaseTagsCriterionOption extends CriterionOption {
|
class BaseTagsCriterionOption extends ModifierCriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
type: CriterionType,
|
type: CriterionType,
|
||||||
|
|||||||
@@ -11,13 +11,9 @@ import {
|
|||||||
ISavedCriterion,
|
ISavedCriterion,
|
||||||
} from "./criteria/criterion";
|
} from "./criteria/criterion";
|
||||||
import { getFilterOptions } from "./factory";
|
import { getFilterOptions } from "./factory";
|
||||||
import {
|
import { CriterionType, DisplayMode, SavedUIOptions } from "./types";
|
||||||
CriterionType,
|
|
||||||
DisplayMode,
|
|
||||||
SavedObjectFilter,
|
|
||||||
SavedUIOptions,
|
|
||||||
} from "./types";
|
|
||||||
import { ListFilterOptions } from "./filter-options";
|
import { ListFilterOptions } from "./filter-options";
|
||||||
|
import { CustomFieldsCriterion } from "./criteria/custom-fields";
|
||||||
|
|
||||||
interface IDecodedParams {
|
interface IDecodedParams {
|
||||||
perPage?: number;
|
perPage?: number;
|
||||||
@@ -60,7 +56,7 @@ export class ListFilterModel {
|
|||||||
public sortBy?: string;
|
public sortBy?: string;
|
||||||
public displayMode: DisplayMode = DEFAULT_PARAMS.displayMode;
|
public displayMode: DisplayMode = DEFAULT_PARAMS.displayMode;
|
||||||
public zoomIndex: number = 1;
|
public zoomIndex: number = 1;
|
||||||
public criteria: Array<Criterion<CriterionValue>> = [];
|
public criteria: Array<Criterion> = [];
|
||||||
public randomSeed = -1;
|
public randomSeed = -1;
|
||||||
private defaultZoomIndex: number = 1;
|
private defaultZoomIndex: number = 1;
|
||||||
|
|
||||||
@@ -446,15 +442,7 @@ export class ListFilterModel {
|
|||||||
public makeFilter() {
|
public makeFilter() {
|
||||||
const output: Record<string, unknown> = {};
|
const output: Record<string, unknown> = {};
|
||||||
for (const c of this.criteria) {
|
for (const c of this.criteria) {
|
||||||
output[c.criterionOption.type] = c.toCriterionInput();
|
c.applyToCriterionInput(output);
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public makeSavedFilter() {
|
|
||||||
const output: SavedObjectFilter = {};
|
|
||||||
for (const c of this.criteria) {
|
|
||||||
output[c.criterionOption.type] = c.toSavedCriterion();
|
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -488,6 +476,20 @@ export class ListFilterModel {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public removeCustomFieldCriterion(type: CriterionType, index: number) {
|
||||||
|
const ret = this.clone();
|
||||||
|
const c = ret.criteria.find((cc) => cc.criterionOption.type === type);
|
||||||
|
|
||||||
|
if (!c) return ret;
|
||||||
|
|
||||||
|
if (c instanceof CustomFieldsCriterion) {
|
||||||
|
const newCriteria = c.value.filter((_, i) => i !== index);
|
||||||
|
c.value = newCriteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public setPageSize(pageSize: number) {
|
public setPageSize(pageSize: number) {
|
||||||
const ret = this.clone();
|
const ret = this.clone();
|
||||||
ret.itemsPerPage = pageSize;
|
ret.itemsPerPage = pageSize;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { ListFilterOptions } from "./filter-options";
|
|||||||
import { CriterionType, DisplayMode } from "./types";
|
import { CriterionType, DisplayMode } from "./types";
|
||||||
import { CountryCriterionOption } from "./criteria/country";
|
import { CountryCriterionOption } from "./criteria/country";
|
||||||
import { RatingCriterionOption } from "./criteria/rating";
|
import { RatingCriterionOption } from "./criteria/rating";
|
||||||
|
import { CustomFieldsCriterionOption } from "./criteria/custom-fields";
|
||||||
|
|
||||||
const defaultSortBy = "name";
|
const defaultSortBy = "name";
|
||||||
const sortByOptions = [
|
const sortByOptions = [
|
||||||
@@ -108,6 +109,7 @@ const criterionOptions = [
|
|||||||
createDateCriterionOption("death_date"),
|
createDateCriterionOption("death_date"),
|
||||||
createMandatoryTimestampCriterionOption("created_at"),
|
createMandatoryTimestampCriterionOption("created_at"),
|
||||||
createMandatoryTimestampCriterionOption("updated_at"),
|
createMandatoryTimestampCriterionOption("updated_at"),
|
||||||
|
CustomFieldsCriterionOption,
|
||||||
];
|
];
|
||||||
export const PerformerListFilterOptions = new ListFilterOptions(
|
export const PerformerListFilterOptions = new ListFilterOptions(
|
||||||
defaultSortBy,
|
defaultSortBy,
|
||||||
|
|||||||
@@ -220,4 +220,5 @@ export type CriterionType =
|
|||||||
| "photographer"
|
| "photographer"
|
||||||
| "disambiguation"
|
| "disambiguation"
|
||||||
| "has_chapters"
|
| "has_chapters"
|
||||||
| "sort_name";
|
| "sort_name"
|
||||||
|
| "custom_fields";
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ import {
|
|||||||
SubGroupsCriterionOption,
|
SubGroupsCriterionOption,
|
||||||
} from "src/models/list-filter/criteria/groups";
|
} from "src/models/list-filter/criteria/groups";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
ModifierCriterion,
|
||||||
CriterionOption,
|
ModifierCriterionOption,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
StringCriterion,
|
StringCriterion,
|
||||||
createStringCriterionOption,
|
createStringCriterionOption,
|
||||||
|
Criterion,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries";
|
import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries";
|
||||||
import { PhashCriterion } from "src/models/list-filter/criteria/phash";
|
import { PhashCriterion } from "src/models/list-filter/criteria/phash";
|
||||||
@@ -33,10 +34,7 @@ import { galleryTitle } from "src/core/galleries";
|
|||||||
import { MarkersScenesCriterion } from "src/models/list-filter/criteria/scenes";
|
import { MarkersScenesCriterion } from "src/models/list-filter/criteria/scenes";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
|
|
||||||
function addExtraCriteria(
|
function addExtraCriteria(dest: Criterion[], src?: Criterion[]) {
|
||||||
dest: Criterion<CriterionValue>[],
|
|
||||||
src?: Criterion<CriterionValue>[]
|
|
||||||
) {
|
|
||||||
if (src && src.length > 0) {
|
if (src && src.length > 0) {
|
||||||
dest.push(...src);
|
dest.push(...src);
|
||||||
}
|
}
|
||||||
@@ -45,7 +43,7 @@ function addExtraCriteria(
|
|||||||
const makePerformerScenesUrl = (
|
const makePerformerScenesUrl = (
|
||||||
performer: Partial<GQL.PerformerDataFragment>,
|
performer: Partial<GQL.PerformerDataFragment>,
|
||||||
extraPerformer?: ILabeledId,
|
extraPerformer?: ILabeledId,
|
||||||
extraCriteria?: Criterion<CriterionValue>[]
|
extraCriteria?: ModifierCriterion<CriterionValue>[]
|
||||||
) => {
|
) => {
|
||||||
if (!performer.id) return "#";
|
if (!performer.id) return "#";
|
||||||
const filter = new ListFilterModel(GQL.FilterMode.Scenes, undefined);
|
const filter = new ListFilterModel(GQL.FilterMode.Scenes, undefined);
|
||||||
@@ -66,7 +64,7 @@ const makePerformerScenesUrl = (
|
|||||||
const makePerformerImagesUrl = (
|
const makePerformerImagesUrl = (
|
||||||
performer: Partial<GQL.PerformerDataFragment>,
|
performer: Partial<GQL.PerformerDataFragment>,
|
||||||
extraPerformer?: ILabeledId,
|
extraPerformer?: ILabeledId,
|
||||||
extraCriteria?: Criterion<CriterionValue>[]
|
extraCriteria?: ModifierCriterion<CriterionValue>[]
|
||||||
) => {
|
) => {
|
||||||
if (!performer.id) return "#";
|
if (!performer.id) return "#";
|
||||||
const filter = new ListFilterModel(GQL.FilterMode.Images, undefined);
|
const filter = new ListFilterModel(GQL.FilterMode.Images, undefined);
|
||||||
@@ -93,7 +91,7 @@ export interface INamedObject {
|
|||||||
const makePerformerGalleriesUrl = (
|
const makePerformerGalleriesUrl = (
|
||||||
performer: INamedObject,
|
performer: INamedObject,
|
||||||
extraPerformer?: ILabeledId,
|
extraPerformer?: ILabeledId,
|
||||||
extraCriteria?: Criterion<CriterionValue>[]
|
extraCriteria?: ModifierCriterion<CriterionValue>[]
|
||||||
) => {
|
) => {
|
||||||
if (!performer.id) return "#";
|
if (!performer.id) return "#";
|
||||||
const filter = new ListFilterModel(GQL.FilterMode.Galleries, undefined);
|
const filter = new ListFilterModel(GQL.FilterMode.Galleries, undefined);
|
||||||
@@ -114,7 +112,7 @@ const makePerformerGalleriesUrl = (
|
|||||||
const makePerformerGroupsUrl = (
|
const makePerformerGroupsUrl = (
|
||||||
performer: Partial<GQL.PerformerDataFragment>,
|
performer: Partial<GQL.PerformerDataFragment>,
|
||||||
extraPerformer?: ILabeledId,
|
extraPerformer?: ILabeledId,
|
||||||
extraCriteria?: Criterion<CriterionValue>[]
|
extraCriteria?: ModifierCriterion<CriterionValue>[]
|
||||||
) => {
|
) => {
|
||||||
if (!performer.id) return "#";
|
if (!performer.id) return "#";
|
||||||
const filter = new ListFilterModel(GQL.FilterMode.Groups, undefined);
|
const filter = new ListFilterModel(GQL.FilterMode.Groups, undefined);
|
||||||
@@ -346,7 +344,7 @@ const makeScenesPHashMatchUrl = (phash: GQL.Maybe<string> | undefined) => {
|
|||||||
|
|
||||||
const makeGalleryImagesUrl = (
|
const makeGalleryImagesUrl = (
|
||||||
gallery: Partial<GQL.GalleryDataFragment | GQL.SlimGalleryDataFragment>,
|
gallery: Partial<GQL.GalleryDataFragment | GQL.SlimGalleryDataFragment>,
|
||||||
extraCriteria?: Criterion<CriterionValue>[]
|
extraCriteria?: ModifierCriterion<CriterionValue>[]
|
||||||
) => {
|
) => {
|
||||||
if (!gallery.id) return "#";
|
if (!gallery.id) return "#";
|
||||||
const filter = new ListFilterModel(GQL.FilterMode.Images, undefined);
|
const filter = new ListFilterModel(GQL.FilterMode.Images, undefined);
|
||||||
@@ -357,7 +355,7 @@ const makeGalleryImagesUrl = (
|
|||||||
return `/images?${filter.makeQueryParameters()}`;
|
return `/images?${filter.makeQueryParameters()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
function stringEqualsCriterion(option: CriterionOption, value: string) {
|
function stringEqualsCriterion(option: ModifierCriterionOption, value: string) {
|
||||||
const criterion = new StringCriterion(option);
|
const criterion = new StringCriterion(option);
|
||||||
criterion.modifier = GQL.CriterionModifier.Equals;
|
criterion.modifier = GQL.CriterionModifier.Equals;
|
||||||
criterion.value = value;
|
criterion.value = value;
|
||||||
|
|||||||
Reference in New Issue
Block a user