Show unsupported filter criteria in filter tags (#6604)

* Show unsupported filter criteria in filter tags

Shows a warning coloured filter tag, with warning icon and text "<type> (unsupported) ...". Cannot be edited, can only be removed. Won't be saved to saved filters.

* Generalise filtered recommendation rows. Include warning popover for unsupported criteria
This commit is contained in:
WithoutPants 2026-02-26 07:55:26 +11:00 committed by GitHub
parent 5734ee43ff
commit c522e54805
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 355 additions and 297 deletions

View file

@ -0,0 +1,79 @@
import React from "react";
import { Link } from "react-router-dom";
import Slider from "@ant-design/react-slick";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { UnsupportedCriterion } from "src/models/list-filter/criteria/criterion";
import { PopoverCard, WarningHoverPopover } from "../Shared/HoverPopover";
interface IProps {
className?: string;
isTouch: boolean;
filter: ListFilterModel;
heading: string;
count: number;
loading: boolean;
url: string;
}
export const FilteredRecommendationRow: React.FC<IProps> = PatchComponent(
"FilteredRecommendationRow",
(props) => {
const cardCount = props.count;
const unsupportedCriteria = props.filter.criteria.filter(
(criterion) => criterion instanceof UnsupportedCriterion
);
const header = unsupportedCriteria.length ? (
<div>
<span>{props.heading}</span>
<WarningHoverPopover
placement="top"
content={
<PopoverCard>
<FormattedMessage
id="unsupported_criteria"
values={{
criteria: unsupportedCriteria
.map((c) => c.criterionOption.type)
.join(", "),
}}
/>
</PopoverCard>
}
/>
</div>
) : (
props.heading
);
if (!props.loading && !cardCount) {
return null;
}
return (
<RecommendationRow
className={props.className}
header={header}
link={
<Link to={props.url}>
<FormattedMessage id="view_all" />
</Link>
}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{props.children}
</Slider>
</RecommendationRow>
);
}
);

View file

@ -3,7 +3,7 @@ import { PatchComponent } from "src/patch";
interface IProps {
className?: string;
header: string;
header: React.ReactNode;
link: JSX.Element;
}

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindGalleries } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { GalleryCard } from "./GalleryCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,40 +15,29 @@ export const GalleryRecommendationRow: React.FC<IProps> = PatchComponent(
"GalleryRecommendationRow",
(props) => {
const result = useFindGalleries(props.filter);
const cardCount = result.data?.findGalleries.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findGalleries.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="gallery-recommendations"
header={props.header}
link={
<Link to={`/galleries?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/galleries?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="gallery-skeleton skeleton-card"
></div>
))
: result.data?.findGalleries.galleries.map((g) => (
<GalleryCard key={g.id} gallery={g} zoomIndex={1} />
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="gallery-skeleton skeleton-card"
></div>
))
: result.data?.findGalleries.galleries.map((g) => (
<GalleryCard key={g.id} gallery={g} zoomIndex={1} />
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindGroups } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { GroupCard } from "./GroupCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,40 +15,26 @@ export const GroupRecommendationRow: React.FC<IProps> = PatchComponent(
"GroupRecommendationRow",
(props: IProps) => {
const result = useFindGroups(props.filter);
const cardCount = result.data?.findGroups.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findGroups.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="group-recommendations"
header={props.header}
link={
<Link to={`/groups?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/groups?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="group-skeleton skeleton-card"
></div>
))
: result.data?.findGroups.groups.map((g) => (
<GroupCard key={g.id} group={g} />
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div key={`_${i}`} className="group-skeleton skeleton-card"></div>
))
: result.data?.findGroups.groups.map((g) => (
<GroupCard key={g.id} group={g} />
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindImages } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { ImageCard } from "./ImageCard";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,40 +15,26 @@ export const ImageRecommendationRow: React.FC<IProps> = PatchComponent(
"ImageRecommendationRow",
(props: IProps) => {
const result = useFindImages(props.filter);
const cardCount = result.data?.findImages.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findImages.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="images-recommendations"
header={props.header}
link={
<Link to={`/images?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/images?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="image-skeleton skeleton-card"
></div>
))
: result.data?.findImages.images.map((i) => (
<ImageCard key={i.id} image={i} zoomIndex={1} />
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div key={`_${i}`} className="image-skeleton skeleton-card"></div>
))
: result.data?.findImages.images.map((i) => (
<ImageCard key={i.id} image={i} zoomIndex={1} />
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -6,10 +6,17 @@ import React, {
useRef,
} from "react";
import { Badge, BadgeProps, Button, Overlay, Popover } from "react-bootstrap";
import { Criterion } from "src/models/list-filter/criteria/criterion";
import {
Criterion,
UnsupportedCriterion,
} from "src/models/list-filter/criteria/criterion";
import { FormattedMessage, useIntl } from "react-intl";
import { Icon } from "../Shared/Icon";
import { faMagnifyingGlass, faTimes } from "@fortawesome/free-solid-svg-icons";
import {
faExclamationTriangle,
faMagnifyingGlass,
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";
import { useDebounce } from "src/hooks/debounce";
@ -38,9 +45,20 @@ export const FilterTag: React.FC<{
label: React.ReactNode;
onClick: React.MouseEventHandler<HTMLSpanElement>;
onRemove: React.MouseEventHandler<HTMLElement>;
}> = ({ className, label, onClick, onRemove }) => {
unsupported?: boolean;
}> = ({ className, label, onClick, onRemove, unsupported }) => {
function handleClick(e: React.MouseEvent<HTMLSpanElement, MouseEvent>) {
if (unsupported) {
return;
}
onClick(e);
}
return (
<TagItem className={className} onClick={onClick}>
<TagItem className={cx(className, { unsupported })} onClick={handleClick}>
{unsupported && (
<Icon icon={faExclamationTriangle} className="unsupported-icon" />
)}
{label}
<Button
variant="secondary"
@ -271,10 +289,13 @@ export const FilterTags: React.FC<IFilterTagsProps> = ({
});
}
const unsupported = criterion instanceof UnsupportedCriterion;
return (
<FilterTag
key={criterion.getId()}
label={criterion.getLabel(intl, sfwContentMode)}
unsupported={unsupported}
onClick={() => onClickCriterionTag(criterion)}
onRemove={($event) => onRemoveCriterionTag(criterion, $event)}
/>

View file

@ -470,6 +470,10 @@ input[type="range"].zoom-slider {
line-height: 16px;
padding: 0;
}
.tag-item.unsupported {
background-color: $warning;
}
}
.filter-button {

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindPerformers } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { PerformerCard } from "./PerformerCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,40 +15,29 @@ export const PerformerRecommendationRow: React.FC<IProps> = PatchComponent(
"PerformerRecommendationRow",
(props) => {
const result = useFindPerformers(props.filter);
const cardCount = result.data?.findPerformers.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findPerformers.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="performer-recommendations"
header={props.header}
link={
<Link to={`/performers?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/performers?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="performer-skeleton skeleton-card"
></div>
))
: result.data?.findPerformers.performers.map((p) => (
<PerformerCard key={p.id} performer={p} />
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="performer-skeleton skeleton-card"
></div>
))
: result.data?.findPerformers.performers.map((p) => (
<PerformerCard key={p.id} performer={p} />
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindSceneMarkers } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { SceneMarkerCard } from "./SceneMarkerCard";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,47 +15,34 @@ export const SceneMarkerRecommendationRow: React.FC<IProps> = PatchComponent(
"SceneMarkerRecommendationRow",
(props) => {
const result = useFindSceneMarkers(props.filter);
const cardCount = result.data?.findSceneMarkers.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findSceneMarkers.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="scene-marker-recommendations"
header={props.header}
link={
<Link to={`/scenes/markers?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/scenes/markers?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="scene-marker-skeleton skeleton-card"
></div>
))
: result.data?.findSceneMarkers.scene_markers.map(
(marker, index) => (
<SceneMarkerCard
key={marker.id}
marker={marker}
index={index}
zoomIndex={1}
/>
)
)}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="scene-marker-skeleton skeleton-card"
></div>
))
: result.data?.findSceneMarkers.scene_markers.map((marker, index) => (
<SceneMarkerCard
key={marker.id}
marker={marker}
index={index}
zoomIndex={1}
/>
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -1,14 +1,10 @@
import React, { useMemo } from "react";
import { Link } from "react-router-dom";
import { useFindScenes } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { SceneCard } from "./SceneCard";
import { SceneQueue } from "src/models/sceneQueue";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -20,50 +16,36 @@ export const SceneRecommendationRow: React.FC<IProps> = PatchComponent(
"SceneRecommendationRow",
(props) => {
const result = useFindScenes(props.filter);
const cardCount = result.data?.findScenes.count;
const count = result.data?.findScenes.count ?? 0;
const queue = useMemo(() => {
return SceneQueue.fromListFilterModel(props.filter);
}, [props.filter]);
if (!result.loading && !cardCount) {
return null;
}
return (
<RecommendationRow
<FilteredRecommendationRow
className="scene-recommendations"
header={props.header}
link={
<Link to={`/scenes?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/scenes?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="scene-skeleton skeleton-card"
></div>
))
: result.data?.findScenes.scenes.map((scene, index) => (
<SceneCard
key={scene.id}
scene={scene}
queue={queue}
index={index}
zoomIndex={1}
/>
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div key={`_${i}`} className="scene-skeleton skeleton-card"></div>
))
: result.data?.findScenes.scenes.map((scene, index) => (
<SceneCard
key={scene.id}
scene={scene}
queue={queue}
index={index}
zoomIndex={1}
/>
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -1,6 +1,8 @@
import React, { useState, useCallback, useEffect, useRef } from "react";
import { Overlay, Popover, OverlayProps } from "react-bootstrap";
import { PatchComponent } from "src/patch";
import { Icon } from "./Icon";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
interface IHoverPopover {
enterDelay?: number;
@ -85,3 +87,20 @@ export const HoverPopover: React.FC<IHoverPopover> = PatchComponent(
);
}
);
// convenience component to set the padding on popover content
export const PopoverCard: React.FC<{ className?: string }> = ({
className,
children,
}) => {
return <div className={`popover-card ${className}`}>{children}</div>;
};
export const WarningHoverPopover: React.FC<IHoverPopover> = PatchComponent(
"WarningHoverPopover",
({ children, ...props }) => (
<HoverPopover {...props} className="warning-hover-popover">
<Icon icon={faExclamationTriangle} />
</HoverPopover>
)
);

View file

@ -233,6 +233,19 @@ button.collapse-button {
.hover-popover-content {
max-width: 32rem;
text-align: center;
.popover-card {
padding: 0.5rem;
}
}
.warning-hover-popover {
display: inline-flex;
margin: 0 0.25rem;
.fa-icon {
color: $warning;
}
}
.ErrorMessage-container {

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindStudios } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { StudioCard } from "./StudioCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,40 +15,29 @@ export const StudioRecommendationRow: React.FC<IProps> = PatchComponent(
"StudioRecommendationRow",
(props) => {
const result = useFindStudios(props.filter);
const cardCount = result.data?.findStudios.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findStudios.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="studio-recommendations"
header={props.header}
link={
<Link to={`/studios?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/studios?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="studio-skeleton skeleton-card"
></div>
))
: result.data?.findStudios.studios.map((s) => (
<StudioCard key={s.id} studio={s} hideParent={true} />
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div
key={`_${i}`}
className="studio-skeleton skeleton-card"
></div>
))
: result.data?.findStudios.studios.map((s) => (
<StudioCard key={s.id} studio={s} hideParent={true} />
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -1,13 +1,9 @@
import React from "react";
import { Link } from "react-router-dom";
import { useFindTags } from "src/core/StashService";
import Slider from "@ant-design/react-slick";
import { TagCard } from "./TagCard";
import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow";
import { FormattedMessage } from "react-intl";
import { PatchComponent } from "src/patch";
import { FilteredRecommendationRow } from "../FrontPage/FilteredRecommendationRow";
interface IProps {
isTouch: boolean;
@ -19,37 +15,26 @@ export const TagRecommendationRow: React.FC<IProps> = PatchComponent(
"TagRecommendationRow",
(props) => {
const result = useFindTags(props.filter);
const cardCount = result.data?.findTags.count;
if (!result.loading && !cardCount) {
return null;
}
const count = result.data?.findTags.count ?? 0;
return (
<RecommendationRow
<FilteredRecommendationRow
className="tag-recommendations"
header={props.header}
link={
<Link to={`/tags?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" />
</Link>
}
heading={props.header}
url={`/tags?${props.filter.makeQueryParameters()}`}
count={count}
loading={result.loading}
isTouch={props.isTouch}
filter={props.filter}
>
<Slider
{...getSlickSliderSettings(
cardCount ? cardCount : props.filter.itemsPerPage,
props.isTouch
)}
>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div key={`_${i}`} className="tag-skeleton skeleton-card"></div>
))
: result.data?.findTags.tags.map((p) => (
<TagCard key={p.id} tag={p} zoomIndex={0} />
))}
</Slider>
</RecommendationRow>
{result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => (
<div key={`_${i}`} className="tag-skeleton skeleton-card"></div>
))
: result.data?.findTags.tags.map((p) => (
<TagCard key={p.id} tag={p} zoomIndex={0} />
))}
</FilteredRecommendationRow>
);
}
);

View file

@ -918,6 +918,7 @@
"criterion": {
"greater_than": "Greater than",
"less_than": "Less than",
"unsupported": "{type} (unsupported)",
"value": "Value"
},
"criterion_modifier": {
@ -1656,6 +1657,7 @@
"twitter": "Twitter",
"type": "Type",
"unknown_date": "Unknown date",
"unsupported_criteria": "Unsupported criteria: {criteria}",
"updated_at": "Updated At",
"url": "URL",
"urls": "URLs",

View file

@ -1221,3 +1221,54 @@ export class TimestampCriterion extends ModifierCriterion<ITimestampValue> {
return true;
}
}
export class UnsupportedCriterionOption extends StringCriterionOption {
constructor(type: string) {
super({
messageID: "unsupported_criterion",
type: type as CriterionType,
makeCriterion: () => new UnsupportedCriterion(this),
});
}
}
export class UnsupportedCriterion extends StringCriterion {
public getLabel(intl: IntlShape): string {
const modifierString = ModifierCriterion.getModifierLabel(
intl,
this.modifier
);
let valueString = "";
if (
this.modifier !== CriterionModifier.IsNull &&
this.modifier !== CriterionModifier.NotNull
) {
valueString = this.getLabelValue(intl);
}
return intl.formatMessage(
{ id: "criterion_modifier.format_string" },
{
criterion: intl.formatMessage(
{ id: "criterion.unsupported" },
{ type: this.criterionOption.type }
),
modifierString,
valueString,
}
);
}
public applyToCriterionInput(): void {
// do nothing
}
public applyToSavedCriterion(): void {
// do nothing
}
public setFromSavedCriterion(): void {
// do nothing
}
}

View file

@ -5,7 +5,7 @@ import {
SavedFilterDataFragment,
SortDirectionEnum,
} from "src/core/generated-graphql";
import { Criterion } from "./criteria/criterion";
import { Criterion, UnsupportedCriterionOption } from "./criteria/criterion";
import { getFilterOptions } from "./factory";
import { CriterionType, DisplayMode, SavedUIOptions } from "./types";
import { ListFilterOptions } from "./filter-options";
@ -437,7 +437,7 @@ export class ListFilterModel {
const option = criterionOptions.find((o) => o.type === type);
if (!option) {
throw new Error(`Unknown criterion parameter name: ${type}`);
return new UnsupportedCriterionOption(type).makeCriterion(this.config);
}
return option.makeCriterion(this.config);