import React from "react"; import { HoverPopover } from "./components/Shared/HoverPopover"; import { TagLink } from "./components/Shared/TagLink"; import { LoadingIndicator } from "./components/Shared/LoadingIndicator"; export const components: Record = { HoverPopover, TagLink, LoadingIndicator, }; const beforeFns: Record = {}; const insteadFns: Record = {}; const afterFns: Record = {}; // patch functions // registers a patch to a function. Before functions are expected to return the // new arguments to be passed to the function. export function before(component: string, fn: Function) { if (!beforeFns[component]) { beforeFns[component] = []; } beforeFns[component].push(fn); } // registers a patch to a function. Instead functions receive the original arguments, // plus the next function to call. In order for all instead functions to be called, // it is expected that the provided next() function will be called. export function instead(component: string, fn: Function) { if (!insteadFns[component]) { insteadFns[component] = []; } insteadFns[component].push(fn); } export function after(component: string, fn: Function) { if (!afterFns[component]) { afterFns[component] = []; } afterFns[component].push(fn); } export function RegisterComponent( component: string, fn: T ) { // register with the plugin api if (components[component]) { throw new Error("Component " + component + " has already been registered"); } components[component] = fn; return fn; } /* eslint-disable @typescript-eslint/no-explicit-any */ function runInstead( fns: Function[], targetFn: Function, thisArg: any, argArray: any[] ) { if (!fns.length) { return targetFn.apply(thisArg, argArray); } let i = 1; function next(): any { if (i >= fns.length) { return targetFn; } const thisTarget = fns[i++]; return new Proxy(thisTarget, { apply: function (target, ctx, args) { return target.apply(ctx, args.concat(next())); }, }); } return fns[0].apply(thisArg, argArray.concat(next())); } /* eslint-enable @typescript-eslint/no-explicit-any */ // patches a function to implement the before/instead/after functionality export function PatchFunction(name: string, fn: T) { return new Proxy(fn, { apply(target, ctx, args) { let result; for (const beforeFn of beforeFns[name] || []) { args = beforeFn.apply(ctx, args); } if (insteadFns[name]) { result = runInstead(insteadFns[name], target, ctx, args); } else { result = target.apply(ctx, args); } for (const afterFn of afterFns[name] || []) { result = afterFn.apply(ctx, args.concat(result)); } return result; }, }); } // patches a component and registers it in the pluginapi components object export function PatchComponent( component: string, fn: React.FC ): React.FC { const ret = PatchFunction(component, fn); // register with the plugin api RegisterComponent(component, ret); return ret as React.FC; } // patches a component and registers it in the pluginapi components object export function PatchContainerComponent( component: string ): React.FC> { const fn = (props: React.PropsWithChildren<{}>) => { return <>{props.children}; }; return PatchComponent(component, fn); }