feat!: organize plugin slots as components, add footer slot (#1381)

BREAKING CHANGE: slot ids have been changed for consistency
* `sequence_container_plugin` -> `sequence_container_slot`
* `unit_title_plugin` -> `unit_title_slot`
This commit is contained in:
Brian Smith
2024-05-17 11:09:47 -04:00
committed by GitHub
parent f124c0d491
commit e656f5445c
18 changed files with 2727 additions and 533 deletions

View File

@@ -9,11 +9,11 @@ import {
import { useIntl } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import PageLoading from '@src/generic/PageLoading';
import { useModel } from '@src/generic/model-store';
import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '@src/alerts/sequence-alerts/hooks';
import SequenceContainerSlot from '../../../plugin-slots/SequenceContainerSlot';
import CourseLicense from '../course-license';
import Sidebar from '../sidebar/Sidebar';
@@ -196,13 +196,7 @@ const Sequence = ({
</div>
{isNewDiscussionSidebarViewEnabled ? <NewSidebar /> : <Sidebar />}
</div>
<PluginSlot
id="sequence_container_plugin"
pluginProps={{
courseId,
unitId,
}}
/>
<SequenceContainerSlot courseId={courseId} unitId={unitId} />
</>
);

View File

@@ -28,14 +28,9 @@ exports[`Unit component output snapshot: not bookmarked, do not show content 1`]
>
unit-title
</h3>
<PluginSlot
id="unit_title_plugin"
pluginProps={
Object {
"courseId": "test-course-id",
"unitId": "test-props-id",
}
}
<UnitTitleSlot
courseId="test-course-id"
unitId="test-props-id"
/>
</div>
<h2

View File

@@ -7,7 +7,6 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { useModel } from '@src/generic/model-store';
import { usePluginsCallback } from '@src/generic/plugin-store';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import BookmarkButton from '../../bookmark/BookmarkButton';
import messages from '../messages';
import ContentIFrame from './ContentIFrame';
@@ -15,6 +14,7 @@ import UnitSuspense from './UnitSuspense';
import { modelKeys, views } from './constants';
import { useExamAccess, useShouldDisplayHonorCode } from './hooks';
import { getIFrameUrl } from './urls';
import UnitTitleSlot from '../../../../plugin-slots/UnitTitleSlot';
const Unit = ({
courseId,
@@ -43,13 +43,7 @@ const Unit = ({
<div className="unit">
<div className="mb-0">
<h3 className="h3">{unit.title}</h3>
<PluginSlot
id="unit_title_plugin"
pluginProps={{
courseId,
unitId: id,
}}
/>
<UnitTitleSlot courseId={courseId} unitId={id} />
</div>
<h2 className="sr-only">{formatMessage(messages.headerPlaceholder)}</h2>
<BookmarkButton

View File

@@ -1,9 +1,9 @@
import React, { useEffect } from 'react';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import Footer from '@edx/frontend-component-footer';
import { useParams, Navigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import FooterSlot from '@openedx/frontend-slot-footer';
import useActiveEnterpriseAlert from '../alerts/active-enteprise-alert';
import { AlertList } from './user-messages';
import { fetchDiscussionTab } from '../course-home/data/thunks';
@@ -32,7 +32,7 @@ const CourseAccessErrorPage = ({ intl }) => {
<PageLoading
srMessage={intl.formatMessage(messages.loading)}
/>
<Footer />
<FooterSlot />
</>
);
}
@@ -51,7 +51,7 @@ const CourseAccessErrorPage = ({ intl }) => {
}}
/>
</main>
<Footer />
<FooterSlot />
</>
);
};

View File

@@ -0,0 +1,50 @@
# Footer Slot
### Slot ID: `footer_slot`
## Description
This slot is used to replace/modify/hide the footer.
The implementation of the `FooterSlot` component lives in [the `frontend-slot-footer` repository](https://github.com/openedx/frontend-slot-footer/).
## Example
The following `env.config.jsx` will replace the default footer.
![Screenshot of Default Footer](./images/default_footer.png)
with a simple custom footer
![Screenshot of Custom Footer](./images/custom_footer.png)
```jsx
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
footer_slot: {
plugins: [
{
// Hide the default footer
op: PLUGIN_OPERATIONS.Hide,
widgetId: 'default_contents',
},
{
// Insert a custom footer
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_footer',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<h1 style={{textAlign: 'center'}}>🦶</h1>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,5 @@
# `frontend-app-learning` Plugin Slots
* [`footer_slot`](./FooterSlot/)
* [`sequence_container_slot`](./SequenceContainerSlot/)
* [`unit_title_slot`](./UnitTitleSlot/)

View File

@@ -0,0 +1,45 @@
# Sequence Container Slot
### Slot ID: `sequence_container_slot`
### Props:
* `courseId`
* `unitId`
## Description
This slot is used for adding content after the Sequence content section.
## Example
The following `env.config.jsx` will render the `course_id` and `unit_id` of the course as `<p>` elements in a `<div>`.
![Screenshot of Content added after the Sequence Container](./images/post_sequence_container.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
sequence_container_slot: {
plugins: [
{
// Insert custom content after sequence content
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_sequence_container_content',
type: DIRECT_PLUGIN,
RenderWidget: ({courseId, unitId}) => (
<div>
<p>📚: {courseId}</p>
<p>📙: {unitId}</p>
</div>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,23 @@
import PropTypes from 'prop-types';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
const SequenceContainerSlot = ({ courseId, unitId }) => (
<PluginSlot
id="sequence_container_slot"
pluginProps={{
courseId,
unitId,
}}
/>
);
SequenceContainerSlot.propTypes = {
courseId: PropTypes.string.isRequired,
unitId: PropTypes.string,
};
SequenceContainerSlot.defaultProps = {
unitId: null,
};
export default SequenceContainerSlot;

View File

@@ -0,0 +1,45 @@
# Unit Title Slot
### Slot ID: `unit_title_slot`
### Props:
* `courseId`
* `unitId`
## Description
This slot is used for adding content after the Unit title.
## Example
The following `env.config.jsx` will render the `course_id` and `unit_id` of the course as `<p>` elements.
![Screenshot of Content added after the Unit Title](./images/post_unit_title.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
unit_title_slot: {
plugins: [
{
// Insert custom content after unit title
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_unit_title_content',
type: DIRECT_PLUGIN,
RenderWidget: ({courseId, unitId}) => (
<>
<p>📚: {courseId}</p>
<p>📙: {unitId}</p>
</>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
const UnitTitleSlot = ({ courseId, unitId }) => (
<PluginSlot
id="unit_title_slot"
pluginProps={{
courseId,
unitId,
}}
/>
);
UnitTitleSlot.propTypes = {
courseId: PropTypes.string.isRequired,
unitId: PropTypes.string.isRequired,
};
export default UnitTitleSlot;

View File

@@ -4,9 +4,9 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useDispatch, useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
import Footer from '@edx/frontend-component-footer';
import { Toast } from '@openedx/paragon';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import FooterSlot from '@openedx/frontend-slot-footer';
import PageLoading from '../generic/PageLoading';
import { getAccessDeniedRedirectUrl } from '../shared/access';
import { useModel } from '../generic/model-store';
@@ -80,7 +80,7 @@ const TabPage = ({ intl, ...props }) => {
{intl.formatMessage(messages.failure)}
</p>
)}
<Footer />
<FooterSlot />
</>
);
};