Skip to content

Commit

Permalink
UI nested instead (#5125)
Browse files Browse the repository at this point in the history
* Support multiple calls to PluginApi.patch.instead for a component.

Allow calling the original/chained function from the hook function.

* Add example of new usage of instead
* Update documentation
  • Loading branch information
WithoutPants authored Aug 20, 2024
1 parent a94bf29 commit 49060e6
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 8 deletions.
13 changes: 12 additions & 1 deletion pkg/plugin/examples/react-component/src/testReact.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,15 @@
.scene-performer-popover .image-thumbnail {
margin: 1em;
}


.example-react-component-custom-overlay {
display: block;
font-weight: 900;
height: 100%;
opacity: 0.25;
position: absolute;
text-align: center;
top: 0;
width: 100%;
z-index: 8;
}
8 changes: 8 additions & 0 deletions pkg/plugin/examples/react-component/src/testReact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,18 @@ interface IPluginApi {
);
}

function Overlays() {
return <span className="example-react-component-custom-overlay">Custom overlay</span>;
}

PluginApi.patch.instead("SceneCard.Details", function (props: any, _: any, original: any) {
return <SceneDetails {...props} />;
});

PluginApi.patch.instead("SceneCard.Overlays", function (props: any, _: any, original: (props: any) => any) {
return <><Overlays />{original({...props})}</>;
});

const TestPage: React.FC = () => {
const componentsLoading = PluginApi.hooks.useLoadComponents([PluginApi.loadableComponents.SceneCard]);

Expand Down
4 changes: 2 additions & 2 deletions ui/v2.5/src/docs/en/Manual/UIPluginApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ Returns `void`.

#### `PluginApi.patch.instead`

Registers a replacement function for a component. The provided function will be called with the arguments passed to the original render function, plus the original render function as the last argument. An error will be thrown if the component already has a replacement function registered.
Registers a replacement function for a component. The provided function will be called with the arguments passed to the original render function, plus the next render function as the last argument. Replacement functions will be called in the order that they are registered. If a replacement function does not call the next render function then the following replacement functions will not be called or applied.

| Parameter | Type | Description |
|-----------|------|-------------|
| `component` | `string` | The name of the component to patch. |
| `fn` | `Function` | The replacement function. It accepts the same arguments as the original render function, plus the original render function, and is expected to return the replacement component. |
| `fn` | `Function` | The replacement function. It accepts the same arguments as the original render function, plus the next render function, and is expected to return the replacement component. |

Returns `void`.

Expand Down
42 changes: 37 additions & 5 deletions ui/v2.5/src/patch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const components: Record<string, Function> = {
};

const beforeFns: Record<string, Function[]> = {};
const insteadFns: Record<string, Function> = {};
const insteadFns: Record<string, Function[]> = {};
const afterFns: Record<string, Function[]> = {};

// patch functions
Expand All @@ -23,11 +23,14 @@ export function before(component: string, fn: Function) {
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]) {
throw new Error("instead has already been called for " + component);
if (!insteadFns[component]) {
insteadFns[component] = [];
}
insteadFns[component] = fn;
insteadFns[component].push(fn);
}

export function after(component: string, fn: Function) {
Expand All @@ -51,6 +54,35 @@ export function RegisterComponent<T extends Function>(
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<T extends Function>(name: string, fn: T) {
return new Proxy(fn, {
Expand All @@ -61,7 +93,7 @@ export function PatchFunction<T extends Function>(name: string, fn: T) {
args = beforeFn.apply(ctx, args);
}
if (insteadFns[name]) {
result = insteadFns[name].apply(ctx, args.concat(target));
result = runInstead(insteadFns[name], target, ctx, args);
} else {
result = target.apply(ctx, args);
}
Expand Down

0 comments on commit 49060e6

Please sign in to comment.