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:
@@ -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} />
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
50
src/plugin-slots/FooterSlot/README.md
Normal file
50
src/plugin-slots/FooterSlot/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
with a simple custom footer
|
||||
|
||||

|
||||
|
||||
```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;
|
||||
```
|
||||
BIN
src/plugin-slots/FooterSlot/images/custom_footer.png
Normal file
BIN
src/plugin-slots/FooterSlot/images/custom_footer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/plugin-slots/FooterSlot/images/default_footer.png
Normal file
BIN
src/plugin-slots/FooterSlot/images/default_footer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
5
src/plugin-slots/README.md
Normal file
5
src/plugin-slots/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# `frontend-app-learning` Plugin Slots
|
||||
|
||||
* [`footer_slot`](./FooterSlot/)
|
||||
* [`sequence_container_slot`](./SequenceContainerSlot/)
|
||||
* [`unit_title_slot`](./UnitTitleSlot/)
|
||||
45
src/plugin-slots/SequenceContainerSlot/README.md
Normal file
45
src/plugin-slots/SequenceContainerSlot/README.md
Normal 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>`.
|
||||
|
||||

|
||||
|
||||
```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 |
23
src/plugin-slots/SequenceContainerSlot/index.jsx
Normal file
23
src/plugin-slots/SequenceContainerSlot/index.jsx
Normal 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;
|
||||
45
src/plugin-slots/UnitTitleSlot/README.md
Normal file
45
src/plugin-slots/UnitTitleSlot/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
```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;
|
||||
```
|
||||
BIN
src/plugin-slots/UnitTitleSlot/images/post_unit_title.png
Normal file
BIN
src/plugin-slots/UnitTitleSlot/images/post_unit_title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
19
src/plugin-slots/UnitTitleSlot/index.jsx
Normal file
19
src/plugin-slots/UnitTitleSlot/index.jsx
Normal 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;
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user