Filter performers/tags/studios list by current filter (#5920)

This commit is contained in:
WithoutPants 2025-06-13 09:07:11 +10:00 committed by GitHub
parent e95c1bbc76
commit 574fd680c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 267 additions and 77 deletions

View file

@ -15,7 +15,13 @@ import {
ILabeledValueListValue,
} from "src/models/list-filter/types";
import { Option } from "./SidebarListFilter";
import { CriterionModifier } from "src/core/generated-graphql";
import {
CriterionModifier,
FilterMode,
InputMaybe,
IntCriterionInput,
SceneFilterType,
} from "src/core/generated-graphql";
import { useIntl } from "react-intl";
interface ILabeledIdFilterProps {
@ -316,11 +322,18 @@ export function useCriterion(
}
export function useQueryState(
useQuery: (q: string, skip: boolean) => ILoadResults<ILabeledId[]>,
useQuery: (
q: string,
filter: ListFilterModel,
skip: boolean
) => ILoadResults<ILabeledId[]>,
filter: ListFilterModel,
skip: boolean
) {
const [query, setQuery] = useState("");
const { results: queryResults } = useCacheResults(useQuery(query, skip));
const { results: queryResults } = useCacheResults(
useQuery(query, filter, skip)
);
return { query, setQuery, queryResults };
}
@ -418,7 +431,11 @@ export function useLabeledIdFilterState(props: {
option: CriterionOption;
filter: ListFilterModel;
setFilter: (f: ListFilterModel) => void;
useQuery: (q: string, skip: boolean) => ILoadResults<ILabeledId[]>;
useQuery: (
q: string,
filter: ListFilterModel,
skip: boolean
) => ILoadResults<ILabeledId[]>;
singleValue?: boolean;
hierarchical?: boolean;
includeSubMessageID?: string;
@ -436,7 +453,11 @@ export function useLabeledIdFilterState(props: {
// defer querying until the user opens the filter
const [skip, setSkip] = useState(true);
const { query, setQuery, queryResults } = useQueryState(useQuery, skip);
const { query, setQuery, queryResults } = useQueryState(
useQuery,
filter,
skip
);
const { criterion, setCriterion } = useCriterion(option, filter, setFilter);
@ -475,3 +496,39 @@ export function useLabeledIdFilterState(props: {
onOpen,
};
}
export function makeQueryVariables(query: string, extraProps: {}) {
return {
filter: {
q: query,
per_page: 200,
},
...extraProps,
};
}
interface IFilterType {
scenes_filter?: InputMaybe<SceneFilterType>;
scene_count?: InputMaybe<IntCriterionInput>;
}
export function setObjectFilter(
out: IFilterType,
mode: FilterMode,
relatedFilterOutput: SceneFilterType
) {
const empty = Object.keys(relatedFilterOutput).length === 0;
switch (mode) {
case FilterMode.Scenes:
// if empty, only get objects with scenes
if (empty) {
out.scene_count = {
modifier: CriterionModifier.GreaterThan,
value: 0,
};
}
out.scenes_filter = relatedFilterOutput;
break;
}
}

View file

@ -1,11 +1,21 @@
import React, { ReactNode, useMemo } from "react";
import { PerformersCriterion } from "src/models/list-filter/criteria/performers";
import { useFindPerformersForSelectQuery } from "src/core/generated-graphql";
import {
CriterionModifier,
FindPerformersForSelectQueryVariables,
PerformerDataFragment,
PerformerFilterType,
useFindPerformersForSelectQuery,
} from "src/core/generated-graphql";
import { ObjectsFilter } from "./SelectableFilter";
import { sortByRelevance } from "src/utils/query";
import { ListFilterModel } from "src/models/list-filter/filter";
import { CriterionOption } from "src/models/list-filter/criteria/criterion";
import { useLabeledIdFilterState } from "./LabeledIdFilter";
import {
makeQueryVariables,
setObjectFilter,
useLabeledIdFilterState,
} from "./LabeledIdFilter";
import { SidebarListFilter } from "./SidebarListFilter";
interface IPerformersFilter {
@ -13,34 +23,74 @@ interface IPerformersFilter {
setCriterion: (c: PerformersCriterion) => void;
}
function usePerformerQuery(query: string, skip?: boolean) {
interface IHasModifier {
modifier: CriterionModifier;
}
function queryVariables(
query: string,
f?: ListFilterModel
): FindPerformersForSelectQueryVariables {
const performerFilter: PerformerFilterType = {};
if (f) {
const filterOutput = f.makeFilter();
// if performer modifier is includes, take it out of the filter
if (
(filterOutput.performers as IHasModifier)?.modifier ===
CriterionModifier.Includes
) {
delete filterOutput.performers;
// TODO - look for same in AND?
}
setObjectFilter(performerFilter, f.mode, filterOutput);
}
return makeQueryVariables(query, { performer_filter: performerFilter });
}
function sortResults(
query: string,
performers?: Pick<PerformerDataFragment, "name" | "alias_list" | "id">[]
) {
return sortByRelevance(
query,
performers ?? [],
(p) => p.name,
(p) => p.alias_list
).map((p) => {
return {
id: p.id,
label: p.name,
};
});
}
function usePerformerQueryFilter(
query: string,
f?: ListFilterModel,
skip?: boolean
) {
const { data, loading } = useFindPerformersForSelectQuery({
variables: {
filter: {
q: query,
per_page: 200,
},
},
variables: queryVariables(query, f),
skip,
});
const results = useMemo(() => {
return sortByRelevance(
query,
data?.findPerformers.performers ?? [],
(p) => p.name,
(p) => p.alias_list
).map((p) => {
return {
id: p.id,
label: p.name,
};
});
}, [data, query]);
const results = useMemo(
() => sortResults(query, data?.findPerformers.performers),
[data, query]
);
return { results, loading };
}
function usePerformerQuery(query: string, skip?: boolean) {
return usePerformerQueryFilter(query, undefined, skip);
}
const PerformersFilter: React.FC<IPerformersFilter> = ({
criterion,
setCriterion,
@ -64,7 +114,7 @@ export const SidebarPerformersFilter: React.FC<{
filter,
setFilter,
option,
useQuery: usePerformerQuery,
useQuery: usePerformerQueryFilter,
});
return <SidebarListFilter {...state} title={title} />;

View file

@ -1,11 +1,19 @@
import React, { ReactNode, useMemo } from "react";
import { useFindStudiosForSelectQuery } from "src/core/generated-graphql";
import {
StudioDataFragment,
StudioFilterType,
useFindStudiosForSelectQuery,
} from "src/core/generated-graphql";
import { HierarchicalObjectsFilter } from "./SelectableFilter";
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
import { sortByRelevance } from "src/utils/query";
import { CriterionOption } from "src/models/list-filter/criteria/criterion";
import { ListFilterModel } from "src/models/list-filter/filter";
import { useLabeledIdFilterState } from "./LabeledIdFilter";
import {
makeQueryVariables,
setObjectFilter,
useLabeledIdFilterState,
} from "./LabeledIdFilter";
import { SidebarListFilter } from "./SidebarListFilter";
interface IStudiosFilter {
@ -13,34 +21,63 @@ interface IStudiosFilter {
setCriterion: (c: StudiosCriterion) => void;
}
function useStudioQuery(query: string, skip?: boolean) {
function queryVariables(query: string, f?: ListFilterModel) {
const studioFilter: StudioFilterType = {};
if (f) {
const filterOutput = f.makeFilter();
// always remove studio filter from the filter
// since modifier is includes
delete filterOutput.studios;
// TODO - look for same in AND?
setObjectFilter(studioFilter, f.mode, filterOutput);
}
return makeQueryVariables(query, { studio_filter: studioFilter });
}
function sortResults(
query: string,
studios: Pick<StudioDataFragment, "id" | "name" | "aliases">[]
) {
return sortByRelevance(
query,
studios ?? [],
(s) => s.name,
(s) => s.aliases
).map((p) => {
return {
id: p.id,
label: p.name,
};
});
}
function useStudioQueryFilter(
query: string,
filter?: ListFilterModel,
skip?: boolean
) {
const { data, loading } = useFindStudiosForSelectQuery({
variables: {
filter: {
q: query,
per_page: 200,
},
},
variables: queryVariables(query, filter),
skip,
});
const results = useMemo(() => {
return sortByRelevance(
query,
data?.findStudios.studios ?? [],
(s) => s.name,
(s) => s.aliases
).map((p) => {
return {
id: p.id,
label: p.name,
};
});
}, [data, query]);
const results = useMemo(
() => sortResults(query, data?.findStudios.studios ?? []),
[data?.findStudios.studios, query]
);
return { results, loading };
}
function useStudioQuery(query: string, skip?: boolean) {
return useStudioQueryFilter(query, undefined, skip);
}
const StudiosFilter: React.FC<IStudiosFilter> = ({
criterion,
setCriterion,
@ -65,7 +102,7 @@ export const SidebarStudiosFilter: React.FC<{
filter,
setFilter,
option,
useQuery: useStudioQuery,
useQuery: useStudioQueryFilter,
singleValue: true,
hierarchical: true,
includeSubMessageID: "subsidiary_studios",

View file

@ -1,46 +1,92 @@
import React, { ReactNode, useMemo } from "react";
import { useFindTagsForSelectQuery } from "src/core/generated-graphql";
import {
CriterionModifier,
TagDataFragment,
TagFilterType,
useFindTagsForSelectQuery,
} from "src/core/generated-graphql";
import { HierarchicalObjectsFilter } from "./SelectableFilter";
import { StudiosCriterion } from "src/models/list-filter/criteria/studios";
import { sortByRelevance } from "src/utils/query";
import { CriterionOption } from "src/models/list-filter/criteria/criterion";
import { ListFilterModel } from "src/models/list-filter/filter";
import { useLabeledIdFilterState } from "./LabeledIdFilter";
import {
makeQueryVariables,
setObjectFilter,
useLabeledIdFilterState,
} from "./LabeledIdFilter";
import { SidebarListFilter } from "./SidebarListFilter";
import { TagsCriterion } from "src/models/list-filter/criteria/tags";
interface ITagsFilter {
criterion: StudiosCriterion;
setCriterion: (c: StudiosCriterion) => void;
criterion: TagsCriterion;
setCriterion: (c: TagsCriterion) => void;
}
function useTagQuery(query: string, skip?: boolean) {
interface IHasModifier {
modifier: CriterionModifier;
}
function queryVariables(query: string, f?: ListFilterModel) {
const tagFilter: TagFilterType = {};
if (f) {
const filterOutput = f.makeFilter();
// if tag modifier is includes, take it out of the filter
if (
(filterOutput.tags as IHasModifier)?.modifier ===
CriterionModifier.Includes
) {
delete filterOutput.tags;
// TODO - look for same in AND?
}
setObjectFilter(tagFilter, f.mode, filterOutput);
}
return makeQueryVariables(query, { tag_filter: tagFilter });
}
function sortResults(
query: string,
tags: Pick<TagDataFragment, "id" | "name" | "aliases">[]
) {
return sortByRelevance(
query,
tags ?? [],
(t) => t.name,
(t) => t.aliases
).map((p) => {
return {
id: p.id,
label: p.name,
};
});
}
function useTagQueryFilter(
query: string,
filter?: ListFilterModel,
skip?: boolean
) {
const { data, loading } = useFindTagsForSelectQuery({
variables: {
filter: {
q: query,
per_page: 200,
},
},
variables: queryVariables(query, filter),
skip,
});
const results = useMemo(() => {
return sortByRelevance(
query,
data?.findTags.tags ?? [],
(t) => t.name,
(t) => t.aliases
).map((p) => {
return {
id: p.id,
label: p.name,
};
});
}, [data, query]);
const results = useMemo(
() => sortResults(query, data?.findTags.tags ?? []),
[data, query]
);
return { results, loading };
}
function useTagQuery(query: string, skip?: boolean) {
return useTagQueryFilter(query, undefined, skip);
}
const TagsFilter: React.FC<ITagsFilter> = ({ criterion, setCriterion }) => {
return (
<HierarchicalObjectsFilter
@ -61,7 +107,7 @@ export const SidebarTagsFilter: React.FC<{
filter,
setFilter,
option,
useQuery: useTagQuery,
useQuery: useTagQueryFilter,
hierarchical: true,
includeSubMessageID: "sub_tags",
});