From 540e80c86be44af917b61c458424e32b1b876c0c Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 31 May 2024 18:16:31 +1000 Subject: [PATCH] Support patching select sorting function (#4903) * Fix return types for RegisterComponent and PatchFunction * Add support for patching TagSelect.sort * Add support for patching PerformerSelect.sort * Patch other select component sort functions * Document patchable functions/components --- .../components/Galleries/GallerySelect.tsx | 24 ++++++++++--- ui/v2.5/src/components/Movies/MovieSelect.tsx | 27 +++++++++++---- .../components/Performers/PerformerSelect.tsx | 29 +++++++++++++--- ui/v2.5/src/components/Scenes/SceneSelect.tsx | 21 +++++++++--- .../src/components/Studios/StudioSelect.tsx | 27 +++++++++++---- ui/v2.5/src/components/Tags/TagSelect.tsx | 24 +++++++++---- ui/v2.5/src/docs/en/Manual/UIPluginApi.md | 34 ++++++++++++++++++- ui/v2.5/src/patch.tsx | 7 ++-- 8 files changed, 156 insertions(+), 37 deletions(-) diff --git a/ui/v2.5/src/components/Galleries/GallerySelect.tsx b/ui/v2.5/src/components/Galleries/GallerySelect.tsx index e91df6b2b13..fe909238b68 100644 --- a/ui/v2.5/src/components/Galleries/GallerySelect.tsx +++ b/ui/v2.5/src/components/Galleries/GallerySelect.tsx @@ -27,7 +27,7 @@ import { useCompare } from "src/hooks/state"; import { Placement } from "react-bootstrap/esm/Overlay"; import { sortByRelevance } from "src/utils/query"; import { galleryTitle } from "src/core/galleries"; -import { PatchComponent } from "src/patch"; +import { PatchComponent, PatchFunction } from "src/patch"; import { Criterion, CriterionValue, @@ -49,6 +49,24 @@ type ExtraGalleryProps = { extraCriteria?: Array>; }; +type FindGalleriesResult = Awaited< + ReturnType +>["data"]["findGalleries"]["galleries"]; + +function sortGalleriesByRelevance( + input: string, + galleries: FindGalleriesResult +) { + return sortByRelevance(input, galleries, galleryTitle, (g) => { + return g.files.map((f) => f.path).concat(g.folder?.path ?? []); + }); +} + +const gallerySelectSort = PatchFunction( + "GallerySelect.sort", + sortGalleriesByRelevance +); + const _GallerySelect: React.FC< IFilterProps & IFilterValueProps & ExtraGalleryProps > = (props) => { @@ -78,9 +96,7 @@ const _GallerySelect: React.FC< return !exclude.includes(gallery.id.toString()); }); - return sortByRelevance(input, ret, galleryTitle, (g) => { - return g.files.map((f) => f.path).concat(g.folder?.path ?? []); - }).map((gallery) => ({ + return gallerySelectSort(input, ret).map((gallery) => ({ value: gallery.id, object: gallery, })); diff --git a/ui/v2.5/src/components/Movies/MovieSelect.tsx b/ui/v2.5/src/components/Movies/MovieSelect.tsx index 95b233f3e11..279994d1afc 100644 --- a/ui/v2.5/src/components/Movies/MovieSelect.tsx +++ b/ui/v2.5/src/components/Movies/MovieSelect.tsx @@ -27,7 +27,7 @@ import { import { useCompare } from "src/hooks/state"; import { Placement } from "react-bootstrap/esm/Overlay"; import { sortByRelevance } from "src/utils/query"; -import { PatchComponent } from "src/patch"; +import { PatchComponent, PatchFunction } from "src/patch"; import { TruncatedText } from "../Shared/TruncatedText"; export type Movie = Pick< @@ -38,6 +38,24 @@ export type Movie = Pick< }; type Option = SelectOption; +type FindMoviesResult = Awaited< + ReturnType +>["data"]["findMovies"]["movies"]; + +function sortMoviesByRelevance(input: string, movies: FindMoviesResult) { + return sortByRelevance( + input, + movies, + (m) => m.name, + (m) => (m.aliases ? [m.aliases] : []) + ); +} + +const movieSelectSort = PatchFunction( + "MovieSelect.sort", + sortMoviesByRelevance +); + const _MovieSelect: React.FC< IFilterProps & IFilterValueProps & { @@ -70,12 +88,7 @@ const _MovieSelect: React.FC< return !exclude.includes(movie.id.toString()); }); - return sortByRelevance( - input, - ret, - (m) => m.name, - (m) => (m.aliases ? [m.aliases] : []) - ).map((movie) => ({ + return movieSelectSort(input, ret).map((movie) => ({ value: movie.id, object: movie, })); diff --git a/ui/v2.5/src/components/Performers/PerformerSelect.tsx b/ui/v2.5/src/components/Performers/PerformerSelect.tsx index e47b11afff7..17d885d5a1e 100644 --- a/ui/v2.5/src/components/Performers/PerformerSelect.tsx +++ b/ui/v2.5/src/components/Performers/PerformerSelect.tsx @@ -27,7 +27,7 @@ import { import { useCompare } from "src/hooks/state"; import { Link } from "react-router-dom"; import { sortByRelevance } from "src/utils/query"; -import { PatchComponent } from "src/patch"; +import { PatchComponent, PatchFunction } from "src/patch"; export type SelectObject = { id: string; @@ -41,6 +41,27 @@ export type Performer = Pick< >; type Option = SelectOption; +type FindPerformersResult = Awaited< + ReturnType +>["data"]["findPerformers"]["performers"]; + +function sortPerformersByRelevance( + input: string, + performers: FindPerformersResult +) { + return sortByRelevance( + input, + performers, + (p) => p.name, + (p) => p.alias_list + ); +} + +const performerSelectSort = PatchFunction( + "PerformerSelect.sort", + sortPerformersByRelevance +); + const _PerformerSelect: React.FC< IFilterProps & IFilterValueProps > = (props) => { @@ -61,11 +82,9 @@ const _PerformerSelect: React.FC< filter.sortBy = "name"; filter.sortDirection = GQL.SortDirectionEnum.Asc; const query = await queryFindPerformersForSelect(filter); - return sortByRelevance( + return performerSelectSort( input, - query.data.findPerformers.performers, - (p) => p.name, - (p) => p.alias_list + query.data.findPerformers.performers.slice() ).map((performer) => ({ value: performer.id, object: performer, diff --git a/ui/v2.5/src/components/Scenes/SceneSelect.tsx b/ui/v2.5/src/components/Scenes/SceneSelect.tsx index 1a74623f542..fc7b3dec996 100644 --- a/ui/v2.5/src/components/Scenes/SceneSelect.tsx +++ b/ui/v2.5/src/components/Scenes/SceneSelect.tsx @@ -27,7 +27,7 @@ import { useCompare } from "src/hooks/state"; import { Placement } from "react-bootstrap/esm/Overlay"; import { sortByRelevance } from "src/utils/query"; import { objectTitle } from "src/core/files"; -import { PatchComponent } from "src/patch"; +import { PatchComponent, PatchFunction } from "src/patch"; import { Criterion, CriterionValue, @@ -48,6 +48,21 @@ type ExtraSceneProps = { extraCriteria?: Array>; }; +type FindScenesResult = Awaited< + ReturnType +>["data"]["findScenes"]["scenes"]; + +function sortScenesByRelevance(input: string, scenes: FindScenesResult) { + return sortByRelevance(input, scenes, objectTitle, (s) => { + return s.files.map((f) => f.path); + }); +} + +const sceneSelectSort = PatchFunction( + "SceneSelect.sort", + sortScenesByRelevance +); + const _SceneSelect: React.FC< IFilterProps & IFilterValueProps & ExtraSceneProps > = (props) => { @@ -77,9 +92,7 @@ const _SceneSelect: React.FC< return !exclude.includes(scene.id.toString()); }); - return sortByRelevance(input, ret, objectTitle, (s) => { - return s.files.map((f) => f.path); - }).map((scene) => ({ + return sceneSelectSort(input, ret).map((scene) => ({ value: scene.id, object: scene, })); diff --git a/ui/v2.5/src/components/Studios/StudioSelect.tsx b/ui/v2.5/src/components/Studios/StudioSelect.tsx index e29c2711af5..d1ab69e6b7e 100644 --- a/ui/v2.5/src/components/Studios/StudioSelect.tsx +++ b/ui/v2.5/src/components/Studios/StudioSelect.tsx @@ -27,7 +27,7 @@ import { import { useCompare } from "src/hooks/state"; import { Placement } from "react-bootstrap/esm/Overlay"; import { sortByRelevance } from "src/utils/query"; -import { PatchComponent } from "src/patch"; +import { PatchComponent, PatchFunction } from "src/patch"; export type SelectObject = { id: string; @@ -38,6 +38,24 @@ export type SelectObject = { export type Studio = Pick; type Option = SelectOption; +type FindStudiosResult = Awaited< + ReturnType +>["data"]["findStudios"]["studios"]; + +function sortStudiosByRelevance(input: string, studios: FindStudiosResult) { + return sortByRelevance( + input, + studios, + (s) => s.name, + (s) => s.aliases + ); +} + +const studioSelectSort = PatchFunction( + "StudioSelect.sort", + sortStudiosByRelevance +); + const _StudioSelect: React.FC< IFilterProps & IFilterValueProps & { @@ -70,12 +88,7 @@ const _StudioSelect: React.FC< return !exclude.includes(studio.id.toString()); }); - return sortByRelevance( - input, - ret, - (s) => s.name, - (s) => s.aliases - ).map((studio) => ({ + return studioSelectSort(input, ret).map((studio) => ({ value: studio.id, object: studio, })); diff --git a/ui/v2.5/src/components/Tags/TagSelect.tsx b/ui/v2.5/src/components/Tags/TagSelect.tsx index 0dadd294292..dbd7f4fe2aa 100644 --- a/ui/v2.5/src/components/Tags/TagSelect.tsx +++ b/ui/v2.5/src/components/Tags/TagSelect.tsx @@ -28,7 +28,7 @@ import { useCompare } from "src/hooks/state"; import { TagPopover } from "./TagPopover"; import { Placement } from "react-bootstrap/esm/Overlay"; import { sortByRelevance } from "src/utils/query"; -import { PatchComponent } from "src/patch"; +import { PatchComponent, PatchFunction } from "src/patch"; export type SelectObject = { id: string; @@ -39,6 +39,21 @@ export type SelectObject = { export type Tag = Pick; type Option = SelectOption; +type FindTagsResult = Awaited< + ReturnType +>["data"]["findTags"]["tags"]; + +function sortTagsByRelevance(input: string, tags: FindTagsResult) { + return sortByRelevance( + input, + tags, + (t) => t.name, + (t) => t.aliases + ); +} + +const tagSelectSort = PatchFunction("TagSelect.sort", sortTagsByRelevance); + const _TagSelect: React.FC< IFilterProps & IFilterValueProps & { @@ -71,12 +86,7 @@ const _TagSelect: React.FC< return !exclude.includes(tag.id.toString()); }); - return sortByRelevance( - input, - ret, - (t) => t.name, - (t) => t.aliases - ).map((tag) => ({ + return tagSelectSort(input, ret).map((tag) => ({ value: tag.id, object: tag, })); diff --git a/ui/v2.5/src/docs/en/Manual/UIPluginApi.md b/ui/v2.5/src/docs/en/Manual/UIPluginApi.md index f58fa0f4785..2ad2b714a7e 100644 --- a/ui/v2.5/src/docs/en/Manual/UIPluginApi.md +++ b/ui/v2.5/src/docs/en/Manual/UIPluginApi.md @@ -137,7 +137,39 @@ Registers an after function. An after function is called after the render functi Returns `void`. -#### `PluginApi.Event` +#### Patchable components and functions + +- `CountrySelect` +- `DateInput` +- `FolderSelect` +- `GalleryIDSelect` +- `GallerySelect` +- `GallerySelect.sort` +- `Icon` +- `MovieIDSelect` +- `MovieSelect` +- `MovieSelect.sort` +- `PerformerIDSelect` +- `PerformerSelect` +- `PerformerSelect.sort` +- `PluginRoutes` +- `SceneCard` +- `SceneCard.Details` +- `SceneCard.Image` +- `SceneCard.Overlays` +- `SceneCard.Popovers` +- `SceneIDSelect` +- `SceneSelect` +- `SceneSelect.sort` +- `Setting` +- `StudioIDSelect` +- `StudioSelect` +- `StudioSelect.sort` +- `TagIDSelect` +- `TagSelect` +- `TagSelect.sort` + +### `PluginApi.Event` Allows plugins to listen for Stash's events. diff --git a/ui/v2.5/src/patch.tsx b/ui/v2.5/src/patch.tsx index 4b35e79a4b6..83b9ef4fea0 100644 --- a/ui/v2.5/src/patch.tsx +++ b/ui/v2.5/src/patch.tsx @@ -37,7 +37,10 @@ export function after(component: string, fn: Function) { afterFns[component].push(fn); } -export function RegisterComponent(component: string, fn: Function) { +export function RegisterComponent( + component: string, + fn: T +) { // register with the plugin api if (components[component]) { throw new Error("Component " + component + " has already been registered"); @@ -49,7 +52,7 @@ export function RegisterComponent(component: string, fn: Function) { } // patches a function to implement the before/instead/after functionality -export function PatchFunction(name: string, fn: Function) { +export function PatchFunction(name: string, fn: T) { return new Proxy(fn, { apply(target, ctx, args) { let result;