diff --git a/ui/v2.5/graphql/data/tag.graphql b/ui/v2.5/graphql/data/tag.graphql index e0f432b67..4b0c0aef9 100644 --- a/ui/v2.5/graphql/data/tag.graphql +++ b/ui/v2.5/graphql/data/tag.graphql @@ -57,3 +57,31 @@ fragment SelectTagData on Tag { updated_at } } + +# Optimized fragment for tag list page - excludes expensive recursive *_count_all fields +fragment TagListData on Tag { + id + name + sort_name + description + aliases + ignore_auto_tag + favorite + image_path + # Direct counts only - no recursive depth queries + scene_count + scene_marker_count + image_count + gallery_count + performer_count + studio_count + group_count + + parents { + ...SlimTagData + } + + children { + ...SlimTagData + } +} diff --git a/ui/v2.5/graphql/queries/tag.graphql b/ui/v2.5/graphql/queries/tag.graphql index ab1fc62f8..e0b20ee02 100644 --- a/ui/v2.5/graphql/queries/tag.graphql +++ b/ui/v2.5/graphql/queries/tag.graphql @@ -25,3 +25,13 @@ query FindTagsForSelect( } } } + +# Optimized query for tag list page - uses TagListData fragment without recursive counts +query FindTagsForList($filter: FindFilterType, $tag_filter: TagFilterType) { + findTags(filter: $filter, tag_filter: $tag_filter) { + count + tags { + ...TagListData + } + } +} diff --git a/ui/v2.5/src/components/Tags/EditTagsDialog.tsx b/ui/v2.5/src/components/Tags/EditTagsDialog.tsx index 6780a8ce8..896016098 100644 --- a/ui/v2.5/src/components/Tags/EditTagsDialog.tsx +++ b/ui/v2.5/src/components/Tags/EditTagsDialog.tsx @@ -55,7 +55,7 @@ function Tags(props: { } interface IListOperationProps { - selected: GQL.TagDataFragment[]; + selected: (GQL.TagDataFragment | GQL.TagListDataFragment)[]; onClose: (applied: boolean) => void; } @@ -134,7 +134,7 @@ export const EditTagsDialog: React.FC = ( let updateChildTagIds: string[] = []; let first = true; - state.forEach((tag: GQL.TagDataFragment) => { + state.forEach((tag: GQL.TagDataFragment | GQL.TagListDataFragment) => { getAggregateStateObject(updateState, tag, tagFields, first); const thisParents = (tag.parents ?? []).map((t) => t.id).sort(); diff --git a/ui/v2.5/src/components/Tags/TagCard.tsx b/ui/v2.5/src/components/Tags/TagCard.tsx index 48c11b12b..9e2019287 100644 --- a/ui/v2.5/src/components/Tags/TagCard.tsx +++ b/ui/v2.5/src/components/Tags/TagCard.tsx @@ -14,7 +14,7 @@ import cx from "classnames"; import { useTagUpdate } from "src/core/StashService"; interface IProps { - tag: GQL.TagDataFragment; + tag: GQL.TagDataFragment | GQL.TagListDataFragment; cardWidth?: number; zoomIndex: number; selecting?: boolean; diff --git a/ui/v2.5/src/components/Tags/TagCardGrid.tsx b/ui/v2.5/src/components/Tags/TagCardGrid.tsx index f75a83cdf..ac3bf0317 100644 --- a/ui/v2.5/src/components/Tags/TagCardGrid.tsx +++ b/ui/v2.5/src/components/Tags/TagCardGrid.tsx @@ -7,7 +7,7 @@ import { import { TagCard } from "./TagCard"; interface ITagCardGrid { - tags: GQL.TagDataFragment[]; + tags: (GQL.TagDataFragment | GQL.TagListDataFragment)[]; selectedIds: Set; zoomIndex: number; onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx index ca866bbb9..8373fa836 100644 --- a/ui/v2.5/src/components/Tags/TagList.tsx +++ b/ui/v2.5/src/components/Tags/TagList.tsx @@ -8,9 +8,9 @@ import { Button } from "react-bootstrap"; import { Link, useHistory } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; import { - queryFindTags, + queryFindTagsForList, mutateMetadataAutoTag, - useFindTags, + useFindTagsForList, useTagDestroy, useTagsDestroy, } from "src/core/StashService"; @@ -27,11 +27,11 @@ import { TagCardGrid } from "./TagCardGrid"; import { EditTagsDialog } from "./EditTagsDialog"; import { View } from "../List/views"; -function getItems(result: GQL.FindTagsQueryResult) { +function getItems(result: GQL.FindTagsForListQueryResult) { return result?.data?.findTags?.tags ?? []; } -function getCount(result: GQL.FindTagsQueryResult) { +function getCount(result: GQL.FindTagsForListQueryResult) { return result?.data?.findTags?.count ?? 0; } @@ -43,7 +43,7 @@ interface ITagList { export const TagList: React.FC = ({ filterHook, alterQuery }) => { const Toast = useToast(); const [deletingTag, setDeletingTag] = - useState | null>(null); + useState | null>(null); const filterMode = GQL.FilterMode.Tags; const view = View.Tags; @@ -79,7 +79,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { ]; function addKeybinds( - result: GQL.FindTagsQueryResult, + result: GQL.FindTagsForListQueryResult, filter: ListFilterModel ) { Mousetrap.bind("p r", () => { @@ -92,7 +92,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { } async function viewRandom( - result: GQL.FindTagsQueryResult, + result: GQL.FindTagsForListQueryResult, filter: ListFilterModel ) { // query for a random tag @@ -103,7 +103,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { const filterCopy = cloneDeep(filter); filterCopy.itemsPerPage = 1; filterCopy.currentPage = index + 1; - const singleResult = await queryFindTags(filterCopy); + const singleResult = await queryFindTagsForList(filterCopy); if (singleResult.data.findTags.tags.length === 1) { const { id } = singleResult.data.findTags.tags[0]; // navigate to the tag page @@ -122,7 +122,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { setIsExportDialogOpen(true); } - async function onAutoTag(tag: GQL.TagDataFragment) { + async function onAutoTag(tag: GQL.TagListDataFragment) { if (!tag) return; try { await mutateMetadataAutoTag({ tags: [tag.id] }); @@ -139,7 +139,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { children: deletingTag?.children ?? [], }; await deleteTag(); - tagRelationHook(deletingTag as GQL.TagDataFragment, oldRelations, { + tagRelationHook(deletingTag as GQL.TagListDataFragment, oldRelations, { parents: [], children: [], }); @@ -160,7 +160,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { } function renderContent( - result: GQL.FindTagsQueryResult, + result: GQL.FindTagsForListQueryResult, filter: ListFilterModel, selectedIds: Set, onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void @@ -324,14 +324,14 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { } function renderEditDialog( - selectedTags: GQL.TagDataFragment[], + selectedTags: GQL.TagListDataFragment[], onClose: (confirmed: boolean) => void ) { return ; } function renderDeleteDialog( - selectedTags: GQL.TagDataFragment[], + selectedTags: GQL.TagListDataFragment[], onClose: (confirmed: boolean) => void ) { return ( @@ -357,7 +357,7 @@ export const TagList: React.FC = ({ filterHook, alterQuery }) => { return ( }, }); +// Optimized query for tag list page - excludes expensive recursive *_count_all fields +export const useFindTagsForList = (filter?: ListFilterModel) => + GQL.useFindTagsForListQuery({ + skip: filter === undefined, + variables: { + filter: filter?.makeFindFilter(), + tag_filter: filter?.makeFilter(), + }, + }); + export const queryFindTags = (filter: ListFilterModel) => client.query({ query: GQL.FindTagsDocument, @@ -438,6 +448,16 @@ export const queryFindTags = (filter: ListFilterModel) => }, }); +// Optimized query for tag list page +export const queryFindTagsForList = (filter: ListFilterModel) => + client.query({ + query: GQL.FindTagsForListDocument, + variables: { + filter: filter.makeFindFilter(), + tag_filter: filter.makeFilter(), + }, + }); + export const queryFindTagsByIDForSelect = (tagIDs: string[]) => client.query({ query: GQL.FindTagsForSelectDocument, diff --git a/ui/v2.5/src/core/tags.ts b/ui/v2.5/src/core/tags.ts index b62e69547..4740397e7 100644 --- a/ui/v2.5/src/core/tags.ts +++ b/ui/v2.5/src/core/tags.ts @@ -58,7 +58,7 @@ interface ITagRelationTuple { } export const tagRelationHook = ( - tag: GQL.SlimTagDataFragment | GQL.TagDataFragment, + tag: GQL.SlimTagDataFragment | GQL.TagDataFragment | GQL.TagListDataFragment, old: ITagRelationTuple, updated: ITagRelationTuple ) => {