Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI nested instead #5125

Merged
merged 3 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading