Files
stash/ui/v2.5/src/pluginApi.tsx
xtc1337 15bf28d5be Adding the ability to support different Haptic Devices (#5856)
* refactored `Interactive` class to allow more HapticDevice devices
* simplified api hooks
* update creation of `interactive` to pass `stashConfig`
* updated UIPluginApi to mention `PluginApi.InteractiveUtils`
2025-09-25 15:27:58 +10:00

197 lines
7.2 KiB
TypeScript

import React from "react";
import ReactDOM from "react-dom";
import * as ReactRouterDOM from "react-router-dom";
import Mousetrap from "mousetrap";
import MousetrapPause from "mousetrap-pause";
import NavUtils from "./utils/navigation";
import * as GQL from "src/core/generated-graphql";
import * as StashService from "src/core/StashService";
import * as Apollo from "@apollo/client";
import * as Bootstrap from "react-bootstrap";
import * as Intl from "react-intl";
import * as FontAwesomeSolid from "@fortawesome/free-solid-svg-icons";
import * as FontAwesomeRegular from "@fortawesome/free-regular-svg-icons";
import * as FontAwesomeBrands from "@fortawesome/free-brands-svg-icons";
import * as ReactSelect from "react-select";
import { useSpriteInfo } from "./hooks/sprite";
import { useToast } from "./hooks/Toast";
import Event from "./hooks/event";
import { after, before, components, instead, RegisterComponent } from "./patch";
import { useSettings } from "./components/Settings/context";
import { useInteractive } from "./hooks/Interactive/context";
import InteractiveUtils from "./hooks/Interactive/utils";
import { useLightbox, useGalleryLightbox } from "./hooks/Lightbox/hooks";
// due to code splitting, some components may not have been loaded when a plugin
// page is loaded. This function will load all components passed to it.
// The components need to be imported here. Any required imports will be added
// to the loadableComponents object in the plugin api.
async function loadComponents(c: (() => Promise<unknown>)[]) {
await Promise.all(c.map((fn) => fn()));
}
// useLoadComponents is a hook that loads all components passed to it.
// It returns a boolean indicating whether the components are still loading.
function useLoadComponents(toLoad: (() => Promise<unknown>)[]) {
const [loading, setLoading] = React.useState(true);
const [componentList] = React.useState(toLoad);
async function load(c: (() => Promise<unknown>)[]) {
await loadComponents(c);
setLoading(false);
}
React.useEffect(() => {
setLoading(true);
load(componentList);
}, [componentList]);
return loading;
}
function registerRoute(path: string, component: React.FC) {
before("PluginRoutes", function (props: React.PropsWithChildren<{}>) {
return [
{
children: (
<>
{props.children}
<ReactRouterDOM.Route path={path} component={component} />
</>
),
},
];
});
}
export const PluginApi = {
React,
ReactDOM,
GQL,
libraries: {
ReactRouterDOM,
Bootstrap,
Apollo,
Intl,
FontAwesomeRegular,
FontAwesomeSolid,
FontAwesomeBrands,
Mousetrap,
MousetrapPause,
ReactSelect,
},
register: {
// register a route to be added to the main router
route: registerRoute,
// register a component to be added to the components object
component: RegisterComponent,
},
loadableComponents: {
// add any lazy loaded imports here - this is coarse-grained and will load all components
// in the import
Performers: () => import("./components/Performers/Performers"),
FrontPage: () => import("./components/FrontPage/FrontPage"),
Scenes: () => import("./components/Scenes/Scenes"),
Settings: () => import("./components/Settings/Settings"),
Stats: () => import("./components/Stats"),
Studios: () => import("./components/Studios/Studios"),
Galleries: () => import("./components/Galleries/Galleries"),
Groups: () => import("./components/Groups/Groups"),
Tags: () => import("./components/Tags/Tags"),
Images: () => import("./components/Images/Images"),
SubmitStashBoxDraft: () => import("src/components/Dialogs/SubmitDraft"),
GenerateDialog: () => import("./components/Dialogs/GenerateDialog"),
ScenePlayer: () => import("src/components/ScenePlayer/ScenePlayer"),
GalleryViewer: () => import("src/components/Galleries/GalleryViewer"),
DeleteScenesDialog: () => import("./components/Scenes/DeleteScenesDialog"),
SceneList: () => import("./components/Scenes/SceneList"),
SceneMarkerList: () => import("./components/Scenes/SceneMarkerList"),
Scene: () => import("./components/Scenes/SceneDetails/Scene"),
SceneCreate: () => import("./components/Scenes/SceneDetails/SceneCreate"),
ExternalPlayerButton: () =>
import("./components/Scenes/SceneDetails/ExternalPlayerButton"),
QueueViewer: () => import("./components/Scenes/SceneDetails/QueueViewer"),
SceneMarkersPanel: () =>
import("./components/Scenes/SceneDetails/SceneMarkersPanel"),
SceneFileInfoPanel: () =>
import("./components/Scenes/SceneDetails/SceneFileInfoPanel"),
SceneDetailPanel: () =>
import("./components/Scenes/SceneDetails/SceneDetailPanel"),
SceneHistoryPanel: () =>
import("./components/Scenes/SceneDetails/SceneHistoryPanel"),
SceneGroupPanel: () =>
import("./components/Scenes/SceneDetails/SceneGroupPanel"),
SceneGalleriesPanel: () =>
import("./components/Scenes/SceneDetails/SceneGalleriesPanel"),
SceneVideoFilterPanel: () =>
import("./components/Scenes/SceneDetails/SceneVideoFilterPanel"),
SceneScrapeDialog: () =>
import("./components/Scenes/SceneDetails/SceneScrapeDialog"),
SceneQueryModal: () =>
import("./components/Scenes/SceneDetails/SceneQueryModal"),
LightboxComponent: () => import("src/hooks/Lightbox/Lightbox"),
// intentionally omitting these for now
// Setup: () => import("./components/Setup/Setup"),
// Migrate: () => import("./components/Setup/Migrate"),
// SceneFilenameParser: () => import("./components/SceneFilenameParser/SceneFilenameParser"),
// SceneDuplicateChecker: () => import("./components/SceneDuplicateChecker/SceneDuplicateChecker"),
// Manual: () => import("./Manual"),
// individual components here
// add components as needed for plugins that provide pages
SceneCard: () => import("./components/Scenes/SceneCard"),
PerformerSelect: () => import("./components/Performers/PerformerSelect"),
TagLink: () => import("./components/Shared/TagLink"),
PerformerCard: () => import("./components/Performers/PerformerCard"),
},
components,
utils: {
InteractiveUtils,
NavUtils,
StashService,
loadComponents,
},
hooks: {
useLoadComponents,
useSpriteInfo,
useToast,
useSettings,
useInteractive,
useLightbox,
useGalleryLightbox,
},
patch: {
// intercept the arguments of supported functions
// the provided function should accept the arguments and return the new arguments
before,
// replace a function with a new one implementation
// the provided function will be called with the arguments passed to the original function
// and the original function as the last argument
// only one instead function can be registered per component
instead,
// intercept the result of supported functions
// the provided function will be called with the arguments passed to the original function
// and the result of the original function
after,
},
Event: Event,
};
export default PluginApi;
interface IWindow {
PluginApi: typeof PluginApi;
}
const localWindow = window as unknown as IWindow;
// export the plugin api to the window object
localWindow.PluginApi = PluginApi;