mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
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
This commit is contained in:
parent
eec31723bd
commit
540e80c86b
8 changed files with 156 additions and 37 deletions
|
|
@ -27,7 +27,7 @@ import { useCompare } from "src/hooks/state";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
Criterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
|
|
@ -49,6 +49,24 @@ type ExtraGalleryProps = {
|
||||||
extraCriteria?: Array<Criterion<CriterionValue>>;
|
extraCriteria?: Array<Criterion<CriterionValue>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type FindGalleriesResult = Awaited<
|
||||||
|
ReturnType<typeof queryFindGalleriesForSelect>
|
||||||
|
>["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<
|
const _GallerySelect: React.FC<
|
||||||
IFilterProps & IFilterValueProps<Gallery> & ExtraGalleryProps
|
IFilterProps & IFilterValueProps<Gallery> & ExtraGalleryProps
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
|
|
@ -78,9 +96,7 @@ const _GallerySelect: React.FC<
|
||||||
return !exclude.includes(gallery.id.toString());
|
return !exclude.includes(gallery.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortByRelevance(input, ret, galleryTitle, (g) => {
|
return gallerySelectSort(input, ret).map((gallery) => ({
|
||||||
return g.files.map((f) => f.path).concat(g.folder?.path ?? []);
|
|
||||||
}).map((gallery) => ({
|
|
||||||
value: gallery.id,
|
value: gallery.id,
|
||||||
object: gallery,
|
object: gallery,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import { useCompare } from "src/hooks/state";
|
import { useCompare } from "src/hooks/state";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
|
|
||||||
export type Movie = Pick<
|
export type Movie = Pick<
|
||||||
|
|
@ -38,6 +38,24 @@ export type Movie = Pick<
|
||||||
};
|
};
|
||||||
type Option = SelectOption<Movie>;
|
type Option = SelectOption<Movie>;
|
||||||
|
|
||||||
|
type FindMoviesResult = Awaited<
|
||||||
|
ReturnType<typeof queryFindMoviesForSelect>
|
||||||
|
>["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<
|
const _MovieSelect: React.FC<
|
||||||
IFilterProps &
|
IFilterProps &
|
||||||
IFilterValueProps<Movie> & {
|
IFilterValueProps<Movie> & {
|
||||||
|
|
@ -70,12 +88,7 @@ const _MovieSelect: React.FC<
|
||||||
return !exclude.includes(movie.id.toString());
|
return !exclude.includes(movie.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortByRelevance(
|
return movieSelectSort(input, ret).map((movie) => ({
|
||||||
input,
|
|
||||||
ret,
|
|
||||||
(m) => m.name,
|
|
||||||
(m) => (m.aliases ? [m.aliases] : [])
|
|
||||||
).map((movie) => ({
|
|
||||||
value: movie.id,
|
value: movie.id,
|
||||||
object: movie,
|
object: movie,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import { useCompare } from "src/hooks/state";
|
import { useCompare } from "src/hooks/state";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
|
|
||||||
export type SelectObject = {
|
export type SelectObject = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -41,6 +41,27 @@ export type Performer = Pick<
|
||||||
>;
|
>;
|
||||||
type Option = SelectOption<Performer>;
|
type Option = SelectOption<Performer>;
|
||||||
|
|
||||||
|
type FindPerformersResult = Awaited<
|
||||||
|
ReturnType<typeof queryFindPerformersForSelect>
|
||||||
|
>["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<
|
const _PerformerSelect: React.FC<
|
||||||
IFilterProps & IFilterValueProps<Performer>
|
IFilterProps & IFilterValueProps<Performer>
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
|
|
@ -61,11 +82,9 @@ const _PerformerSelect: React.FC<
|
||||||
filter.sortBy = "name";
|
filter.sortBy = "name";
|
||||||
filter.sortDirection = GQL.SortDirectionEnum.Asc;
|
filter.sortDirection = GQL.SortDirectionEnum.Asc;
|
||||||
const query = await queryFindPerformersForSelect(filter);
|
const query = await queryFindPerformersForSelect(filter);
|
||||||
return sortByRelevance(
|
return performerSelectSort(
|
||||||
input,
|
input,
|
||||||
query.data.findPerformers.performers,
|
query.data.findPerformers.performers.slice()
|
||||||
(p) => p.name,
|
|
||||||
(p) => p.alias_list
|
|
||||||
).map((performer) => ({
|
).map((performer) => ({
|
||||||
value: performer.id,
|
value: performer.id,
|
||||||
object: performer,
|
object: performer,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import { useCompare } from "src/hooks/state";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
import {
|
import {
|
||||||
Criterion,
|
Criterion,
|
||||||
CriterionValue,
|
CriterionValue,
|
||||||
|
|
@ -48,6 +48,21 @@ type ExtraSceneProps = {
|
||||||
extraCriteria?: Array<Criterion<CriterionValue>>;
|
extraCriteria?: Array<Criterion<CriterionValue>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type FindScenesResult = Awaited<
|
||||||
|
ReturnType<typeof queryFindScenesForSelect>
|
||||||
|
>["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<
|
const _SceneSelect: React.FC<
|
||||||
IFilterProps & IFilterValueProps<Scene> & ExtraSceneProps
|
IFilterProps & IFilterValueProps<Scene> & ExtraSceneProps
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
|
|
@ -77,9 +92,7 @@ const _SceneSelect: React.FC<
|
||||||
return !exclude.includes(scene.id.toString());
|
return !exclude.includes(scene.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortByRelevance(input, ret, objectTitle, (s) => {
|
return sceneSelectSort(input, ret).map((scene) => ({
|
||||||
return s.files.map((f) => f.path);
|
|
||||||
}).map((scene) => ({
|
|
||||||
value: scene.id,
|
value: scene.id,
|
||||||
object: scene,
|
object: scene,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import { useCompare } from "src/hooks/state";
|
import { useCompare } from "src/hooks/state";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
|
|
||||||
export type SelectObject = {
|
export type SelectObject = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -38,6 +38,24 @@ export type SelectObject = {
|
||||||
export type Studio = Pick<GQL.Studio, "id" | "name" | "aliases" | "image_path">;
|
export type Studio = Pick<GQL.Studio, "id" | "name" | "aliases" | "image_path">;
|
||||||
type Option = SelectOption<Studio>;
|
type Option = SelectOption<Studio>;
|
||||||
|
|
||||||
|
type FindStudiosResult = Awaited<
|
||||||
|
ReturnType<typeof queryFindStudiosForSelect>
|
||||||
|
>["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<
|
const _StudioSelect: React.FC<
|
||||||
IFilterProps &
|
IFilterProps &
|
||||||
IFilterValueProps<Studio> & {
|
IFilterValueProps<Studio> & {
|
||||||
|
|
@ -70,12 +88,7 @@ const _StudioSelect: React.FC<
|
||||||
return !exclude.includes(studio.id.toString());
|
return !exclude.includes(studio.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortByRelevance(
|
return studioSelectSort(input, ret).map((studio) => ({
|
||||||
input,
|
|
||||||
ret,
|
|
||||||
(s) => s.name,
|
|
||||||
(s) => s.aliases
|
|
||||||
).map((studio) => ({
|
|
||||||
value: studio.id,
|
value: studio.id,
|
||||||
object: studio,
|
object: studio,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { useCompare } from "src/hooks/state";
|
||||||
import { TagPopover } from "./TagPopover";
|
import { TagPopover } from "./TagPopover";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent, PatchFunction } from "src/patch";
|
||||||
|
|
||||||
export type SelectObject = {
|
export type SelectObject = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -39,6 +39,21 @@ export type SelectObject = {
|
||||||
export type Tag = Pick<GQL.Tag, "id" | "name" | "aliases" | "image_path">;
|
export type Tag = Pick<GQL.Tag, "id" | "name" | "aliases" | "image_path">;
|
||||||
type Option = SelectOption<Tag>;
|
type Option = SelectOption<Tag>;
|
||||||
|
|
||||||
|
type FindTagsResult = Awaited<
|
||||||
|
ReturnType<typeof queryFindTagsForSelect>
|
||||||
|
>["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<
|
const _TagSelect: React.FC<
|
||||||
IFilterProps &
|
IFilterProps &
|
||||||
IFilterValueProps<Tag> & {
|
IFilterValueProps<Tag> & {
|
||||||
|
|
@ -71,12 +86,7 @@ const _TagSelect: React.FC<
|
||||||
return !exclude.includes(tag.id.toString());
|
return !exclude.includes(tag.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortByRelevance(
|
return tagSelectSort(input, ret).map((tag) => ({
|
||||||
input,
|
|
||||||
ret,
|
|
||||||
(t) => t.name,
|
|
||||||
(t) => t.aliases
|
|
||||||
).map((tag) => ({
|
|
||||||
value: tag.id,
|
value: tag.id,
|
||||||
object: tag,
|
object: tag,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,39 @@ Registers an after function. An after function is called after the render functi
|
||||||
|
|
||||||
Returns `void`.
|
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.
|
Allows plugins to listen for Stash's events.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,10 @@ export function after(component: string, fn: Function) {
|
||||||
afterFns[component].push(fn);
|
afterFns[component].push(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RegisterComponent(component: string, fn: Function) {
|
export function RegisterComponent<T extends Function>(
|
||||||
|
component: string,
|
||||||
|
fn: T
|
||||||
|
) {
|
||||||
// register with the plugin api
|
// register with the plugin api
|
||||||
if (components[component]) {
|
if (components[component]) {
|
||||||
throw new Error("Component " + component + " has already been registered");
|
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
|
// patches a function to implement the before/instead/after functionality
|
||||||
export function PatchFunction(name: string, fn: Function) {
|
export function PatchFunction<T extends Function>(name: string, fn: T) {
|
||||||
return new Proxy(fn, {
|
return new Proxy(fn, {
|
||||||
apply(target, ctx, args) {
|
apply(target, ctx, args) {
|
||||||
let result;
|
let result;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue