Skip to content

Commit

Permalink
feat: ensure default_contents widgets support merging props with Plug…
Browse files Browse the repository at this point in the history
…inOperations.Modify
  • Loading branch information
adamstankiewicz committed Aug 23, 2024
1 parent bf8705b commit e068ebd
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 96 deletions.
4 changes: 2 additions & 2 deletions example-plugin-app/src/DefaultIframe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Plugin } from '@openedx/frontend-plugin-framework';
function DefaultComponent() {
return (
<section className="bg-light p-3 h-100">
<h3>Default iFrame Widget</h3>
<h4>Default iFrame Widget</h4>
<p>
This is a component that lives in the example-plugins-app and is provided in this host MFE via iFrame.
</p>
Expand All @@ -17,7 +17,7 @@ function DefaultComponent() {
function ErrorFallback(error) {
return (
<div className="text-center">
<p className="h3 text-muted">
<p className="h4 text-muted">
Oops! An error occurred. Please refresh the screen to try again.
</p>
<br />
Expand Down
2 changes: 1 addition & 1 deletion example-plugin-app/src/PluginIframe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function PluginIframe() {
return (
<Plugin>
<section className="bg-light p-3 h-100">
<h3>Inserted iFrame Plugin</h3>
<h4>Inserted iFrame Plugin</h4>
<p>
This is a component that lives in the example-plugins-app and is provided in this host MFE via iFrame plugin.
</p>
Expand Down
39 changes: 32 additions & 7 deletions example/env.config.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
IFRAME_PLUGIN,
PLUGIN_OPERATIONS,
} from '@openedx/frontend-plugin-framework';
import DefaultDirectWidget from './src/components/DefaultDirectWidget';
import PluginDirect from './src/components/PluginDirect';
import ModularComponent from './src/components/ModularComponent';

Expand All @@ -18,12 +17,28 @@ const modifyWidget = (widget) => {
return modifiedWidget;
};

const wrapWidget = ({ component, idx }) => (
<div className="bg-warning" data-testid={`wrapper${idx + 1}`} key={idx}>
<p>This is a wrapper component that is placed around the default content.</p>
const modifyWidgetDefaultContents = (widget) => {
const newContent = {
'data-custom-attr': 'customValue',
'data-another-custom-attr': '',
className: 'font-weight-bold',
style: { color: 'blue' },
onClick: () => { console.log('hello!'); },
};
widget.content = newContent;
return widget;
};

const wrapWidget = ({ component }) => (
<div className="bg-warning" data-testid="wrapper">
<div className="px-3">
<p className="mb-0">This is a wrapper component that is placed around the default content.</p>
</div>
{component}
<p>With this wrapper, you can add anything before or after a component.</p>
<p>Note in the JS config that an iFrame plugin was Inserted, but a Hide operation was also used to hide it!</p>
<div className="px-3">
<p>With this wrapper, you can add anything before or after a component.</p>
<p className="mb-0">Note in the JS config that an iFrame plugin was Inserted, but a Hide operation was also used to hide it!</p>
</div>
</div>
);

Expand Down Expand Up @@ -179,7 +194,17 @@ const config = {
},
},
],
}
},
slot_with_username_pii: {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Modify,
widgetId: 'default_contents',
fn: modifyWidgetDefaultContents,
},
],
},
},
};

Expand Down
6 changes: 6 additions & 0 deletions example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"author": "edX",
"license": "AAGPL-3.0",
"dependencies": {
"classnames": "^2.5.1",
"core-js": "^3.29.1",
"prop-types": "^15.8.1",
"react": "^17.0.0",
Expand Down
83 changes: 63 additions & 20 deletions example/src/ExamplePage.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,74 @@
import React from 'react';
import {
Container, Row, Col, Stack,
} from '@openedx/paragon';

import PluginSlotWithModifyDefaultContents from './pluginSlots/PluginSlotWithModifyDefaultContents';
import PluginSlotWithInsert from './pluginSlots/PluginSlotWithInsert';
import PluginSlotWithModifyWrapHide from './pluginSlots/PluginSlotWithModifyWrapHide';
import PluginSlotWithModularPlugins from './pluginSlots/PluginSlotWithModularPlugins';
import PluginSlotWithoutDefault from './pluginSlots/PluginSlotWithoutDefault';

const pluginExamples = [
{
id: 'plugin-operation-insert',
label: 'Plugin Operation: Insert',
Component: PluginSlotWithInsert,
},
{
id: 'plugin-operation-modify-wrap-hide',
label: 'Plugin Operation: Modify, Wrap, and Hide',
Component: PluginSlotWithModifyWrapHide,
},
{
id: 'plugin-operation-modify-default-content',
label: 'Plugin Operation: Modify Default Content',
Component: PluginSlotWithModifyDefaultContents,
},
{
id: 'direct-plugins-modular-components',
label: 'Direct Plugins Using Modular Components',
Component: PluginSlotWithModularPlugins,
},
{
id: 'no-default-content',
label: 'Default Content Set to False',
Component: PluginSlotWithoutDefault,
},
];

export default function ExamplePage() {
return (
<main className="center m-5">
<h1>Plugins Page</h1>

<p>
This page is here to help test the plugins module. A plugin configuration can be added in
index.jsx and this page will display that plugin.
</p>
<p>
To do this, a plugin MFE must be running on some other port.
To make it a more realistic test, you may also want to edit your
/etc/hosts file (or your system&apos;s equivalent) to provide an alternate domain for
127.0.0.1 at which you can load the plugin.
</p>
<div className="d-flex flex-column">
<PluginSlotWithInsert />
<PluginSlotWithModifyWrapHide />
<PluginSlotWithModularPlugins />
<PluginSlotWithoutDefault />
</div>
<main>
<Container size="lg" className="py-3">
<Row>
<Col>
<h1>Plugins Page</h1>
<p>
This page is here to help test the plugins module. A plugin configuration can be added in
index.jsx and this page will display that plugin.
</p>
<p>
To do this, a plugin MFE must be running on some other port.
To make it a more realistic test, you may also want to edit your
/etc/hosts file (or your system&apos;s equivalent) to provide an alternate domain for
127.0.0.1 at which you can load the plugin.
</p>
<h2>Examples</h2>
<Stack gap={3}>
<ul>
{pluginExamples.map(({ id, label }) => (
<li key={id}>
<a href={`#${id}`}>{label}</a>
</li>
))}
</ul>
<Stack gap={5}>
{pluginExamples.map(({ id, label, Component }) => <Component key={id} label={label} />)}
</Stack>
</Stack>
</Col>
</Row>
</Container>
</main>
);
}
2 changes: 1 addition & 1 deletion example/src/components/DefaultDirectWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
export default function DefaultDirectWidget() {
return (
<section className="bg-success p-3 text-light">
<h3>Default Direct Widget</h3>
<h4>Default Direct Widget</h4>
<p>
This widget is a default component that lives in the example app and is directly inserted via JS configuration.
Note that this default widget appears after the Inserted Direct Plugin. This is because this component&apos;s
Expand Down
2 changes: 1 addition & 1 deletion example/src/components/ModularComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
export default function ModularComponent({ content }) {
return (
<section className="bg-light p-3">
<h3>{ content.title }</h3>
<h4>{content.title}</h4>
<p>
This is a modular component that lives in the example app.
</p>
Expand Down
2 changes: 1 addition & 1 deletion example/src/components/PluginDirect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
export default function PluginDirect() {
return (
<section className="bg-warning p-3">
<h3>Inserted Direct Plugin</h3>
<h4>Inserted Direct Plugin</h4>
<p>
This plugin is a component that lives in the example app and is directly inserted via JS configuration.
What makes this unique is that it isn&apos;t part of the default content defined for this slot, but is instead
Expand Down
6 changes: 3 additions & 3 deletions example/src/pluginSlots/PluginSlotWithInsert.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';

function PluginSlotWithInsert() {
function PluginSlotWithInsert({ label }) {
return (
<div className="border border-primary mb-2">
<h2 className="pl-3">Plugin Operation: Insert</h2>
<h3 id="plugin-operation-insert" className="pl-3">{label}</h3>
<PluginSlot
id="slot_with_insert_operation"
>
<section className="bg-success p-3 text-light">
<h3>Default Content</h3>
<h4>Default Content</h4>
<p>
This widget represents a component that is wrapped by the Plugin Slot.

Expand Down
46 changes: 46 additions & 0 deletions example/src/pluginSlots/PluginSlotWithModifyDefaultContents.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';
import classNames from 'classnames';


// Example component used as the default child(ren) within a PluginSlot
const Username = ({ className, ...rest }) => {
const authenticatedUser = { username: 'testuser' };
const { username } = authenticatedUser;
return (
<span
className={classNames('default-classname', className)}
{...rest}
>
{username}
</span>
);
};

function PluginSlotWithModifyDefaultContents({ label }) {
return (
<div className="border border-primary mb-2 px-3">
<h3 id="plugin-operation-modify-default-content">{label}</h3>
<p>
The following <code>PluginSlot</code> examples demonstrate the <code>PLUGIN_OPERATIONS.Modify</code> operation, when
the <code>widgetId</code> is <code>default_contents</code>. Any configured, custom plugin <code>content</code> is
merged with any existing props passed to the component(s) represented by <code>default_contents</code>.
</p>
<ul>
<li>Custom <code>className</code> overrides are concatenated with the <code>className</code> prop passed to the <code>default_contents</code> component(s), if any.</li>
<li>Custom <code>style</code> overrides are shallow merged with the <code>style</code> prop passed to the <code>default_contents</code> component(s), if any.</li>
<li>Custom event handlers (e.g., <code>onClick</code>) are executed in sequence, after any event handlers passed to the <code>default_contents</code> component(s), if any.</li>
</ul>
<PluginSlot id="slot_with_username_pii" as="div">
<Username className="d-block" />
<Username className="abc123" />
</PluginSlot>
<PluginSlot id="slot_with_username_pii" as="div">
<Username className="def456" />
</PluginSlot>
</div>
);
}

export default PluginSlotWithModifyDefaultContents;
4 changes: 2 additions & 2 deletions example/src/pluginSlots/PluginSlotWithModifyWrapHide.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { PluginSlot } from '@openedx/frontend-plugin-framework';
import ModularComponent from '../components/ModularComponent';


function PluginSlotWithModifyWrapHide() {
function PluginSlotWithModifyWrapHide({ label }) {
const content = {
title: 'Default Content',
uniqueText: "Because this modular component is default content, this text is passed in as a prop within PluginSlot."
}

return (
<div className="border border-primary mb-2">
<h2 className="pl-3">Plugin Operation: Modify, Wrap, and Hide</h2>
<h3 id="plugin-operation-modify-wrap-hide" className="pl-3">{label}</h3>
<PluginSlot
id="slot_with_modify_wrap_hidden_operations"
>
Expand Down
4 changes: 2 additions & 2 deletions example/src/pluginSlots/PluginSlotWithModularPlugins.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import ModularComponent from '../components/ModularComponent';

function PluginSlotWithModularPlugins() {
function PluginSlotWithModularPlugins({ label }) {
const content = {
title: 'Default Content',
uniqueText: 'Default content are set with a priority of 50, which is why it appears second in this slot.',
}

return (
<div className="border border-primary mb-2">
<h2 className="pl-3">Direct Plugins Using Modular Components</h2>
<h3 id="direct-plugins-modular-components" className="pl-3">{label}</h3>
<PluginSlot
id="slot_with_modular_plugins"
>
Expand Down
4 changes: 2 additions & 2 deletions example/src/pluginSlots/PluginSlotWithoutDefault.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';

function PluginSlotWithoutDefault() {
function PluginSlotWithoutDefault({ label }) {
return (
<div className="border border-primary mb-2">
<h2 className="pl-3">Default Content Set to False</h2>
<h3 id="no-default-content" className="pl-3">{label}</h3>
<PluginSlot
id="slot_without_default"
>
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e068ebd

Please sign in to comment.