diff --git a/ui/v2.5/src/components/List/Filters/LabeledIdFilter.tsx b/ui/v2.5/src/components/List/Filters/LabeledIdFilter.tsx index 533d32628..ef309ecde 100644 --- a/ui/v2.5/src/components/List/Filters/LabeledIdFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/LabeledIdFilter.tsx @@ -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, + useQuery: ( + q: string, + filter: ListFilterModel, + skip: boolean + ) => ILoadResults, + 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; + useQuery: ( + q: string, + filter: ListFilterModel, + skip: boolean + ) => ILoadResults; 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; + scene_count?: InputMaybe; +} + +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; + } +} diff --git a/ui/v2.5/src/components/List/Filters/PerformersFilter.tsx b/ui/v2.5/src/components/List/Filters/PerformersFilter.tsx index 098640ea2..84e3dd19b 100644 --- a/ui/v2.5/src/components/List/Filters/PerformersFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/PerformersFilter.tsx @@ -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[] +) { + 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 = ({ criterion, setCriterion, @@ -64,7 +114,7 @@ export const SidebarPerformersFilter: React.FC<{ filter, setFilter, option, - useQuery: usePerformerQuery, + useQuery: usePerformerQueryFilter, }); return ; diff --git a/ui/v2.5/src/components/List/Filters/StudiosFilter.tsx b/ui/v2.5/src/components/List/Filters/StudiosFilter.tsx index ade8d9b56..e9c05013d 100644 --- a/ui/v2.5/src/components/List/Filters/StudiosFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/StudiosFilter.tsx @@ -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[] +) { + 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 = ({ 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", diff --git a/ui/v2.5/src/components/List/Filters/TagsFilter.tsx b/ui/v2.5/src/components/List/Filters/TagsFilter.tsx index b8d4eddc6..0a3f8c942 100644 --- a/ui/v2.5/src/components/List/Filters/TagsFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/TagsFilter.tsx @@ -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[] +) { + 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 = ({ criterion, setCriterion }) => { return (