From ead0c7fe077723dbf0ae93b650e5f7a95e217371 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 27 Feb 2026 07:44:23 +1100 Subject: [PATCH] Add sidebar to Tag list (#6610) * Fix image export dialog * Add sidebar to TagList * Update plugin docs and types * Remove ItemList as it is no longer referenced --- ui/v2.5/src/components/Images/ImageList.tsx | 2 +- ui/v2.5/src/components/List/ItemList.tsx | 372 +-------- ui/v2.5/src/components/Tags/TagList.tsx | 874 ++++++++++++-------- ui/v2.5/src/components/Tags/Tags.tsx | 4 +- ui/v2.5/src/docs/en/Manual/UIPluginApi.md | 2 + ui/v2.5/src/pluginApi.d.ts | 1 + 6 files changed, 528 insertions(+), 727 deletions(-) diff --git a/ui/v2.5/src/components/Images/ImageList.tsx b/ui/v2.5/src/components/Images/ImageList.tsx index 8c11abdee..cc8aa48f7 100644 --- a/ui/v2.5/src/components/Images/ImageList.tsx +++ b/ui/v2.5/src/components/Images/ImageList.tsx @@ -618,7 +618,7 @@ export const FilteredImageList = PatchComponent( showModal( { - view?: View; - otherOperations?: IItemListOperation[]; - renderContent: ( - result: T, - filter: ListFilterModel, - selectedIds: Set, - onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void, - onChangePage: (page: number) => void, - pageCount: number - ) => React.ReactNode; - renderMetadataByline?: (data: T, metadataInfo?: M) => React.ReactNode; - renderEditDialog?: ( - selected: E[], - onClose: (applied: boolean) => void - ) => React.ReactNode; - renderDeleteDialog?: ( - selected: E[], - onClose: (confirmed: boolean) => void - ) => React.ReactNode; - addKeybinds?: ( - result: T, - filter: ListFilterModel, - selectedIds: Set - ) => () => void; - renderToolbar?: (props: IFilteredListToolbar) => React.ReactNode; -} - -export const ItemList = ( - props: IItemListProps -) => { - const { - view, - otherOperations, - renderContent, - renderEditDialog, - renderDeleteDialog, - renderMetadataByline, - addKeybinds, - renderToolbar: providedToolbar, - } = props; - - const { filter, setFilter: updateFilter } = useFilter(); - const { effectiveFilter, result, metadataInfo, cachedResult, totalCount } = - useQueryResultContext(); - const listSelect = useListContext(); - const { - selectedIds, - getSelected, - onSelectChange, - onSelectAll, - onSelectNone, - onInvertSelection, - } = listSelect; - - // scroll to the top of the page when the page changes - useScrollToTopOnPageChange(filter.currentPage, result.loading); - - const { modal, showModal, closeModal } = useModal(); - - const metadataByline = useMemo(() => { - if (cachedResult.loading) return ""; - - return renderMetadataByline?.(cachedResult, metadataInfo) ?? ""; - }, [renderMetadataByline, cachedResult, metadataInfo]); - - const pages = Math.ceil(totalCount / filter.itemsPerPage); - - const onChangePage = useCallback( - (p: number) => { - updateFilter(filter.changePage(p)); - }, - [filter, updateFilter] - ); - - useEnsureValidPage(filter, totalCount, updateFilter); - - const showEditFilter = useCallback( - (editingCriterion?: string) => { - function onApplyEditFilter(f: ListFilterModel) { - closeModal(); - updateFilter(f); - } - - showModal( - closeModal()} - editingCriterion={editingCriterion} - /> - ); - }, - [filter, updateFilter, showModal, closeModal] - ); - - useListKeyboardShortcuts({ - currentPage: filter.currentPage, - onChangePage, - onSelectAll, - onSelectNone, - onInvertSelection, - pages, - showEditFilter, - }); - - const zoomable = - filter.displayMode === DisplayMode.Grid || - filter.displayMode === DisplayMode.Wall; - - useZoomKeybinds({ - zoomIndex: zoomable ? filter.zoomIndex : undefined, - onChangeZoom: (zoom) => updateFilter(filter.setZoom(zoom)), - }); - - useEffect(() => { - if (addKeybinds) { - const unbindExtras = addKeybinds(result, effectiveFilter, selectedIds); - return () => { - unbindExtras(); - }; - } - }, [addKeybinds, result, effectiveFilter, selectedIds]); - - const operations = useMemo(() => { - async function onOperationClicked(o: IItemListOperation) { - await o.onClick(result, effectiveFilter, selectedIds); - if (o.postRefetch) { - result.refetch(); - } - } - - return otherOperations?.map((o) => ({ - text: o.text, - onClick: () => { - onOperationClicked(o); - }, - isDisplayed: () => { - if (o.isDisplayed) { - return o.isDisplayed(result, effectiveFilter, selectedIds); - } - - return true; - }, - icon: o.icon, - buttonVariant: o.buttonVariant, - })); - }, [result, effectiveFilter, selectedIds, otherOperations]); - - function onEdit() { - if (!renderEditDialog) { - return; - } - - showModal( - renderEditDialog(getSelected(), (applied) => onEditDialogClosed(applied)) - ); - } - - function onEditDialogClosed(applied: boolean) { - if (applied) { - onSelectNone(); - } - closeModal(); - - // refetch - result.refetch(); - } - - function onDelete() { - if (!renderDeleteDialog) { - return; - } - - showModal( - renderDeleteDialog(getSelected(), (deleted) => - onDeleteDialogClosed(deleted) - ) - ); - } - - function onDeleteDialogClosed(deleted: boolean) { - if (deleted) { - onSelectNone(); - } - closeModal(); - - // refetch - result.refetch(); - } - - function onRemoveCriterion(removedCriterion: Criterion, valueIndex?: number) { - if (valueIndex === undefined) { - updateFilter( - filter.removeCriterion(removedCriterion.criterionOption.type) - ); - } else { - updateFilter( - filter.removeCustomFieldCriterion( - removedCriterion.criterionOption.type, - valueIndex - ) - ); - } - } - - function onClearAllCriteria() { - updateFilter(filter.clearCriteria()); - } - - const filterListToolbarProps: IFilteredListToolbar = { - filter, - setFilter: updateFilter, - listSelect, - showEditFilter, - view: view, - operations: operations, - zoomable: zoomable, - onEdit: renderEditDialog ? onEdit : undefined, - onDelete: renderDeleteDialog ? onDelete : undefined, - }; - - return ( -
- {providedToolbar ? ( - providedToolbar(filterListToolbarProps) - ) : ( - - )} - showEditFilter(c.criterionOption.type)} - onRemoveCriterion={onRemoveCriterion} - onRemoveAll={() => onClearAllCriteria()} - /> - {modal} - - - {renderContent( - result, - // #4780 - use effectiveFilter to ensure filterHook is applied - effectiveFilter, - selectedIds, - onSelectChange, - onChangePage, - pages - )} - -
- ); -}; - -interface IItemListContextProps< - T extends QueryResult, - E extends IHasID, - M = unknown -> { - filterMode: GQL.FilterMode; - defaultSort?: string; - defaultFilter?: ListFilterModel; - useResult: (filter: ListFilterModel) => T; - useMetadataInfo?: (filter: ListFilterModel) => M; - getCount: (data: T) => number; - getItems: (data: T) => E[]; - filterHook?: (filter: ListFilterModel) => ListFilterModel; - view?: View; - alterQuery?: boolean; - selectable?: boolean; -} - -// Provides the contexts for the ItemList component. Includes functionality to scroll -// to top on page change. -export const ItemListContext = < - T extends QueryResult, - E extends IHasID, - M = unknown ->( - props: PropsWithChildren> -) => { - const { - filterMode, - defaultSort, - defaultFilter: providedDefaultFilter, - useResult, - useMetadataInfo, - getCount, - getItems, - view, - filterHook, - alterQuery = true, - selectable, - children, - } = props; - - const { configuration: config } = useConfigurationContext(); - - const emptyFilter = useMemo( - () => - providedDefaultFilter?.clone() ?? - new ListFilterModel(filterMode, config, { - defaultSortBy: defaultSort, - }), - [config, filterMode, defaultSort, providedDefaultFilter] - ); - - const [filter, setFilterState] = useState( - () => - new ListFilterModel(filterMode, config, { defaultSortBy: defaultSort }) - ); - - const { defaultFilter } = useDefaultFilter(emptyFilter, view); - - return ( - - - - {({ items }) => ( - - {children} - - )} - - - - ); -}; - export const showWhenSelected = ( result: T, filter: ListFilterModel, diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx index 61b81b727..38cc13141 100644 --- a/ui/v2.5/src/components/Tags/TagList.tsx +++ b/ui/v2.5/src/components/Tags/TagList.tsx @@ -1,9 +1,9 @@ -import React, { useState } from "react"; +import React, { useCallback, useEffect } from "react"; import cloneDeep from "lodash-es/cloneDeep"; import Mousetrap from "mousetrap"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; -import { ItemList, ItemListContext, showWhenSelected } from "../List/ItemList"; +import { useFilteredItemList } from "../List/ItemList"; import { Button } from "react-bootstrap"; import { Link, useHistory } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; @@ -11,33 +11,269 @@ import { queryFindTagsForList, mutateMetadataAutoTag, useFindTagsForList, - useTagDestroy, useTagsDestroy, } from "src/core/StashService"; import { useToast } from "src/hooks/Toast"; import { FormattedMessage, FormattedNumber, useIntl } from "react-intl"; import NavUtils from "src/utils/navigation"; import { Icon } from "../Shared/Icon"; -import { ModalComponent } from "../Shared/Modal"; import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog"; import { ExportDialog } from "../Shared/ExportDialog"; import { tagRelationHook } from "../../core/tags"; import { faTrashAlt } from "@fortawesome/free-solid-svg-icons"; import { TagMergeModal } from "./TagMergeDialog"; -import { Tag } from "./TagSelect"; import { TagCardGrid } from "./TagCardGrid"; import { EditTagsDialog } from "./EditTagsDialog"; import { View } from "../List/views"; -import { IItemListOperation } from "../List/FilteredListToolbar"; -import { PatchComponent } from "src/patch"; +import { + FilteredListToolbar, + IItemListOperation, +} from "../List/FilteredListToolbar"; +import { PatchComponent, PatchContainerComponent } from "src/patch"; import { TagTagger } from "../Tagger/tags/TagTagger"; +import useFocus from "src/utils/focus"; +import { + Sidebar, + SidebarPane, + SidebarPaneContent, + SidebarStateContext, + useSidebarState, +} from "../Shared/Sidebar"; +import { useCloseEditDelete, useFilterOperations } from "../List/util"; +import { + FilteredSidebarHeader, + useFilteredSidebarKeybinds, +} from "../List/Filters/FilterSidebar"; +import { ListOperations } from "../List/ListOperationButtons"; +import cx from "classnames"; +import { FilterTags } from "../List/FilterTags"; +import { Pagination, PaginationIndex } from "../List/Pagination"; +import { LoadedContent } from "../List/PagedList"; +import { SidebarBooleanFilter } from "../List/Filters/BooleanFilter"; +import { FavoriteTagCriterionOption } from "src/models/list-filter/criteria/favorite"; -function getItems(result: GQL.FindTagsForListQueryResult) { - return result?.data?.findTags?.tags ?? []; +const TagList: React.FC<{ + tags: GQL.TagListDataFragment[]; + filter: ListFilterModel; + selectedIds: Set; + onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; + onDelete: (tag: GQL.TagListDataFragment) => void; + onAutoTag: (tag: GQL.TagListDataFragment) => void; +}> = PatchComponent( + "TagList", + ({ tags, filter, selectedIds, onSelectChange, onDelete, onAutoTag }) => { + if (tags.length === 0 && filter.displayMode !== DisplayMode.Tagger) { + return null; + } + + if (filter.displayMode === DisplayMode.Grid) { + return ( + + ); + } + if (filter.displayMode === DisplayMode.List) { + const tagElements = tags.map((tag) => { + return ( +
+ {tag.name} + +
+ + + + + + + :{" "} + + + +
+
+ ); + }); + + return
{tagElements}
; + } + if (filter.displayMode === DisplayMode.Tagger) { + return ; + } + + return null; + } +); + +const TagFilterSidebarSections = PatchContainerComponent( + "FilteredTagList.SidebarSections" +); + +const SidebarContent: React.FC<{ + filter: ListFilterModel; + setFilter: (filter: ListFilterModel) => void; + filterHook?: (filter: ListFilterModel) => ListFilterModel; + view?: View; + sidebarOpen: boolean; + onClose?: () => void; + showEditFilter: (editingCriterion?: string) => void; + count?: number; + focus?: ReturnType; +}> = ({ + filter, + setFilter, + // filterHook, + view, + showEditFilter, + sidebarOpen, + onClose, + count, + focus, +}) => { + const showResultsId = + count !== undefined ? "actions.show_count_results" : "actions.show_results"; + + return ( + <> + + + + {/* */} + } + filter={filter} + setFilter={setFilter} + option={FavoriteTagCriterionOption} + sectionID="favourite" + /> + + +
+ +
+ + ); +}; + +function useViewRandom(filter: ListFilterModel, count: number) { + const history = useHistory(); + + const viewRandom = useCallback(async () => { + // query for a random tag + if (count === 0) { + return; + } + + const index = Math.floor(Math.random() * count); + const filterCopy = cloneDeep(filter); + filterCopy.itemsPerPage = 1; + filterCopy.currentPage = index + 1; + const singleResult = await queryFindTagsForList(filterCopy); + if (singleResult.data.findTags.tags.length === 1) { + const { id } = singleResult.data.findTags.tags[0]; + // navigate to the tag page + history.push(`/tags/${id}`); + } + }, [history, filter, count]); + + return viewRandom; } -function getCount(result: GQL.FindTagsForListQueryResult) { - return result?.data?.findTags?.count ?? 0; +function useAddKeybinds(filter: ListFilterModel, count: number) { + const viewRandom = useViewRandom(filter, count); + + useEffect(() => { + Mousetrap.bind("p r", () => { + viewRandom(); + }); + + return () => { + Mousetrap.unbind("p r"); + }; + }, [viewRandom]); } interface ITagList { @@ -46,105 +282,155 @@ interface ITagList { extraOperations?: IItemListOperation[]; } -export const TagList: React.FC = PatchComponent( - "TagList", - ({ filterHook, alterQuery, extraOperations = [] }) => { - const Toast = useToast(); - const [deletingTag, setDeletingTag] = - useState | null>(null); - - const filterMode = GQL.FilterMode.Tags; - const view = View.Tags; - - function getDeleteTagInput() { - const tagInput: Partial = {}; - if (deletingTag) { - tagInput.id = deletingTag.id; - } - return tagInput as GQL.TagDestroyInput; - } - const [deleteTag] = useTagDestroy(getDeleteTagInput()); - +export const FilteredTagList = PatchComponent( + "FilteredTagList", + (props: ITagList) => { const intl = useIntl(); const history = useHistory(); - const [mergeTags, setMergeTags] = useState(undefined); - const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); - const [isExportAll, setIsExportAll] = useState(false); + const Toast = useToast(); - const otherOperations = [ - ...extraOperations, - { - text: intl.formatMessage({ id: "actions.view_random" }), - onClick: viewRandom, - }, - { - text: `${intl.formatMessage({ id: "actions.merge" })}…`, - onClick: merge, - isDisplayed: showWhenSelected, - }, - { - text: intl.formatMessage({ id: "actions.export" }), - onClick: onExport, - isDisplayed: showWhenSelected, - }, - { - text: intl.formatMessage({ id: "actions.export_all" }), - onClick: onExportAll, - }, - ]; + const searchFocus = useFocus(); - function addKeybinds( - result: GQL.FindTagsForListQueryResult, - filter: ListFilterModel - ) { - Mousetrap.bind("p r", () => { - viewRandom(result, filter); + const { filterHook, alterQuery, extraOperations = [] } = props; + + const view = View.Tags; + + // States + const { + showSidebar, + setShowSidebar, + sectionOpen, + setSectionOpen, + loading: sidebarStateLoading, + } = useSidebarState(view); + + const { filterState, queryResult, modalState, listSelect, showEditFilter } = + useFilteredItemList({ + filterStateProps: { + filterMode: GQL.FilterMode.Tags, + view, + useURL: alterQuery, + }, + queryResultProps: { + useResult: useFindTagsForList, + getCount: (r) => r.data?.findTags.count ?? 0, + getItems: (r) => r.data?.findTags.tags ?? [], + filterHook, + }, + }); + + const { filter, setFilter } = filterState; + + const { effectiveFilter, result, cachedResult, items, totalCount } = + queryResult; + + const { + selectedIds, + selectedItems, + onSelectChange, + onSelectAll, + onSelectNone, + onInvertSelection, + hasSelection, + } = listSelect; + + const { modal, showModal, closeModal } = modalState; + + // Utility hooks + const { setPage, removeCriterion, clearAllCriteria } = useFilterOperations({ + filter, + setFilter, + }); + + useAddKeybinds(effectiveFilter, totalCount); + useFilteredSidebarKeybinds({ + showSidebar, + setShowSidebar, + }); + + useEffect(() => { + Mousetrap.bind("e", () => { + if (hasSelection) { + onEdit?.(); + } + }); + + Mousetrap.bind("d d", () => { + if (hasSelection) { + onDelete?.(); + } }); return () => { - Mousetrap.unbind("p r"); + Mousetrap.unbind("e"); + Mousetrap.unbind("d d"); }; + }); + + const onCloseEditDelete = useCloseEditDelete({ + closeModal, + onSelectNone, + result, + }); + + const viewRandom = useViewRandom(effectiveFilter, totalCount); + + function onExport(all: boolean) { + showModal( + closeModal()} + /> + ); } - async function viewRandom( - result: GQL.FindTagsForListQueryResult, - filter: ListFilterModel - ) { - // query for a random tag - if (result.data?.findTags) { - const { count } = result.data.findTags; - - const index = Math.floor(Math.random() * count); - const filterCopy = cloneDeep(filter); - filterCopy.itemsPerPage = 1; - filterCopy.currentPage = index + 1; - const singleResult = await queryFindTagsForList(filterCopy); - if (singleResult.data.findTags.tags.length === 1) { - const { id } = singleResult.data.findTags.tags[0]; - // navigate to the tag page - history.push(`/tags/${id}`); - } - } + function onEdit() { + showModal( + + ); } - async function merge( - result: GQL.FindTagsForListQueryResult, - filter: ListFilterModel, - selectedIds: Set - ) { - const selected = - result.data?.findTags.tags.filter((t) => selectedIds.has(t.id)) ?? []; - setMergeTags(selected); + function onDelete(tag?: GQL.TagListDataFragment) { + const itemsToDelete = tag ? [tag] : selectedItems; + + showModal( + { + itemsToDelete.forEach((t) => + tagRelationHook( + t, + { parents: t.parents ?? [], children: t.children ?? [] }, + { parents: [], children: [] } + ) + ); + }} + /> + ); } - async function onExport() { - setIsExportAll(false); - setIsExportDialogOpen(true); - } - - async function onExportAll() { - setIsExportAll(true); - setIsExportDialogOpen(true); + function onMerge() { + showModal( + { + onCloseEditDelete(); + if (mergedId) { + history.push(`/tags/${mergedId}`); + } + }} + show + /> + ); } async function onAutoTag(tag: GQL.TagListDataFragment) { @@ -157,269 +443,151 @@ export const TagList: React.FC = PatchComponent( } } - async function onDelete() { - try { - const oldRelations = { - parents: deletingTag?.parents ?? [], - children: deletingTag?.children ?? [], - }; - await deleteTag(); - tagRelationHook(deletingTag as GQL.TagListDataFragment, oldRelations, { - parents: [], - children: [], - }); - Toast.success( - intl.formatMessage( - { id: "toast.delete_past_tense" }, - { - count: 1, - singularEntity: intl.formatMessage({ id: "tag" }), - pluralEntity: intl.formatMessage({ id: "tags" }), - } - ) - ); - setDeletingTag(null); - } catch (e) { - Toast.error(e); - } - } + const convertedExtraOperations = extraOperations.map((op) => ({ + text: op.text, + onClick: () => op.onClick(result, filter, selectedIds), + isDisplayed: () => op.isDisplayed?.(result, filter, selectedIds) ?? true, + })); - function renderContent( - result: GQL.FindTagsForListQueryResult, - filter: ListFilterModel, - selectedIds: Set, - onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void - ) { - function renderMergeDialog() { - if (mergeTags) { - return ( - { - setMergeTags(undefined); - if (mergedId) { - history.push(`/tags/${mergedId}`); - } - }} - show - /> - ); - } - } + const otherOperations = [ + ...convertedExtraOperations, + { + text: intl.formatMessage({ id: "actions.select_all" }), + onClick: () => onSelectAll(), + isDisplayed: () => totalCount > 0, + }, + { + text: intl.formatMessage({ id: "actions.select_none" }), + onClick: () => onSelectNone(), + isDisplayed: () => hasSelection, + }, + { + text: intl.formatMessage({ id: "actions.invert_selection" }), + onClick: () => onInvertSelection(), + isDisplayed: () => totalCount > 0, + }, + { + text: intl.formatMessage({ id: "actions.view_random" }), + onClick: viewRandom, + }, + { + text: `${intl.formatMessage({ id: "actions.merge" })}…`, + onClick: () => onMerge(), + isDisplayed: () => hasSelection, + }, + { + text: intl.formatMessage({ id: "actions.export" }), + onClick: () => onExport(false), + isDisplayed: () => hasSelection, + }, + { + text: intl.formatMessage({ id: "actions.export_all" }), + onClick: () => onExport(true), + }, + ]; - function maybeRenderExportDialog() { - if (isExportDialogOpen) { - return ( - setIsExportDialogOpen(false)} - /> - ); - } - } + // render + if (sidebarStateLoading) return null; - function renderTags() { - if (!result.data?.findTags) return; - - if (filter.displayMode === DisplayMode.Grid) { - return ( - - ); - } - if (filter.displayMode === DisplayMode.List) { - const deleteAlert = ( - {}} - show={!!deletingTag} - icon={faTrashAlt} - accept={{ - onClick: onDelete, - variant: "danger", - text: intl.formatMessage({ id: "actions.delete" }), - }} - cancel={{ onClick: () => setDeletingTag(null) }} - > - - - - - ); - - const tagElements = result.data.findTags.tags.map((tag) => { - return ( -
- {tag.name} - -
- - - - - - - :{" "} - - - -
-
- ); - }); - - return ( -
- {tagElements} - {deleteAlert} -
- ); - } - if (filter.displayMode === DisplayMode.Wall) { - return

TODO

; - } - if (filter.displayMode === DisplayMode.Tagger) { - return ; - } - } - return ( - <> - {renderMergeDialog()} - {maybeRenderExportDialog()} - {renderTags()} - - ); - } - - function renderEditDialog( - selectedTags: GQL.TagListDataFragment[], - onClose: (confirmed: boolean) => void - ) { - return ; - } - - function renderDeleteDialog( - selectedTags: GQL.TagListDataFragment[], - onClose: (confirmed: boolean) => void - ) { - return ( - { - selectedTags.forEach((t) => - tagRelationHook( - t, - { parents: t.parents ?? [], children: t.children ?? [] }, - { parents: [], children: [] } - ) - ); - }} - /> - ); - } + const operations = ( + + ); return ( - - - + {modal} + + + + setShowSidebar(false)}> + setShowSidebar(false)} + count={cachedResult.loading ? undefined : totalCount} + focus={searchFocus} + /> + + setShowSidebar(!showSidebar)} + > + + + showEditFilter(c.criterionOption.type)} + onRemoveCriterion={removeCriterion} + onRemoveAll={clearAllCriteria} + /> + +
+ setFilter(filter.changePage(page))} + /> + +
+ + + onDelete(tag)} + onAutoTag={(tag) => onAutoTag(tag)} + /> + + + {totalCount > filter.itemsPerPage && ( +
+
+ +
+
+ )} +
+
+
+ ); } ); diff --git a/ui/v2.5/src/components/Tags/Tags.tsx b/ui/v2.5/src/components/Tags/Tags.tsx index 806a0f7a6..a4336fea9 100644 --- a/ui/v2.5/src/components/Tags/Tags.tsx +++ b/ui/v2.5/src/components/Tags/Tags.tsx @@ -4,10 +4,10 @@ import { Helmet } from "react-helmet"; import { useTitleProps } from "src/hooks/title"; import Tag from "./TagDetails/Tag"; import TagCreate from "./TagDetails/TagCreate"; -import { TagList } from "./TagList"; +import { FilteredTagList } from "./TagList"; const Tags: React.FC = () => { - return ; + return ; }; const TagRoutes: React.FC = () => { diff --git a/ui/v2.5/src/docs/en/Manual/UIPluginApi.md b/ui/v2.5/src/docs/en/Manual/UIPluginApi.md index 4ff8b5143..68d5676d3 100644 --- a/ui/v2.5/src/docs/en/Manual/UIPluginApi.md +++ b/ui/v2.5/src/docs/en/Manual/UIPluginApi.md @@ -236,6 +236,7 @@ Returns `void`. - `FilteredSceneList` - `FilteredSceneMarkerList` - `FilteredStudioList` +- `FilteredTagList` - `FolderSelect` - `FrontPage` - `GalleryCard` @@ -353,6 +354,7 @@ Returns `void`. - `TagCardGrid` - `TagIDSelect` - `TagLink` +- `TagList` - `TagRecommendationRow` - `TagSelect` - `TagSelect.sort` diff --git a/ui/v2.5/src/pluginApi.d.ts b/ui/v2.5/src/pluginApi.d.ts index 77627be10..e04d472b6 100644 --- a/ui/v2.5/src/pluginApi.d.ts +++ b/ui/v2.5/src/pluginApi.d.ts @@ -673,6 +673,7 @@ declare namespace PluginApi { FilteredSceneList: React.FC; FilteredSceneMarkerList: React.FC; FilteredStudioList: React.FC; + FilteredTagList: React.FC; FolderSelect: React.FC; FrontPage: React.FC; GalleryCard: React.FC;