feat: Add plugin slots for progress page components (#1496)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@openedx/paragon';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useModel } from '../../generic/model-store';
|
||||
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
import { useWindowSize } from '@openedx/paragon';
|
||||
import { useContextId } from '../../data/hooks';
|
||||
import ProgressTabCertificateStatusSidePanelSlot from '../../plugin-slots/ProgressTabCertificateStatusSidePanelSlot';
|
||||
|
||||
import CertificateStatus from './certificate-status/CertificateStatus';
|
||||
import CourseCompletion from './course-completion/CourseCompletion';
|
||||
import CourseGrade from './grades/course-grade/CourseGrade';
|
||||
import DetailedGrades from './grades/detailed-grades/DetailedGrades';
|
||||
import GradeSummary from './grades/grade-summary/GradeSummary';
|
||||
import ProgressHeader from './ProgressHeader';
|
||||
import RelatedLinks from './related-links/RelatedLinks';
|
||||
|
||||
import ProgressTabCertificateStatusMainBodySlot from '../../plugin-slots/ProgressTabCertificateStatusMainBodySlot';
|
||||
import ProgressTabCourseGradeSlot from '../../plugin-slots/ProgressTabCourseGradeSlot';
|
||||
import ProgressTabGradeBreakdownSlot from '../../plugin-slots/ProgressTabGradeBreakdownSlot';
|
||||
import ProgressTabRelatedLinksSlot from '../../plugin-slots/ProgressTabRelatedLinksSlot';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
|
||||
const ProgressTab = () => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
|
||||
const {
|
||||
gradesFeatureIsFullyLocked, disableProgressGraph,
|
||||
} = useModel('progress', courseId);
|
||||
|
||||
const applyLockedOverlay = gradesFeatureIsFullyLocked ? 'locked-overlay' : '';
|
||||
const courseId = useContextId();
|
||||
const { disableProgressGraph } = useModel('progress', courseId);
|
||||
|
||||
const windowWidth = useWindowSize().width;
|
||||
if (windowWidth === undefined) {
|
||||
@@ -31,7 +24,6 @@ const ProgressTab = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wideScreen = windowWidth >= breakpoints.large.minWidth;
|
||||
return (
|
||||
<>
|
||||
<ProgressHeader />
|
||||
@@ -39,18 +31,15 @@ const ProgressTab = () => {
|
||||
{/* Main body */}
|
||||
<div className="col-12 col-md-8 p-0">
|
||||
{!disableProgressGraph && <CourseCompletion />}
|
||||
{!wideScreen && <CertificateStatus />}
|
||||
<CourseGrade />
|
||||
<div className={`grades my-4 p-4 rounded raised-card ${applyLockedOverlay}`} aria-hidden={gradesFeatureIsFullyLocked}>
|
||||
<GradeSummary />
|
||||
<DetailedGrades />
|
||||
</div>
|
||||
<ProgressTabCertificateStatusMainBodySlot />
|
||||
<ProgressTabCourseGradeSlot />
|
||||
<ProgressTabGradeBreakdownSlot />
|
||||
</div>
|
||||
|
||||
{/* Side panel */}
|
||||
<div className="col-12 col-md-4 p-0 px-md-4">
|
||||
{wideScreen && <CertificateStatus />}
|
||||
<RelatedLinks />
|
||||
<ProgressTabCertificateStatusSidePanelSlot />
|
||||
<ProgressTabRelatedLinksSlot />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { FormattedDate, FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Button, Card } from '@openedx/paragon';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useContextId } from '../../../data/hooks';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import { COURSE_EXIT_MODES, getCourseExitMode } from '../../../courseware/course/course-exit/utils';
|
||||
import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../shared/links';
|
||||
@@ -15,9 +16,7 @@ import ProgressCertificateStatusSlot from '../../../plugin-slots/ProgressCertifi
|
||||
|
||||
const CertificateStatus = () => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
entranceExamData,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
getLocale, injectIntl, intlShape, isRtl,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { useContextId } from '../../../data/hooks';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
import CompleteDonutSegment from './CompleteDonutSegment';
|
||||
@@ -11,9 +11,7 @@ import LockedDonutSegment from './LockedDonutSegment';
|
||||
import messages from './messages';
|
||||
|
||||
const CompletionDonutChart = ({ intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
completionSummary: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { CheckCircle, WarningFilled, WatchFilled } from '@openedx/paragon/icons';
|
||||
import { Hyperlink, Icon } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../data/hooks';
|
||||
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import { DashboardLink } from '../../../shared/links';
|
||||
@@ -11,9 +11,7 @@ import { DashboardLink } from '../../../shared/links';
|
||||
import messages from './messages';
|
||||
|
||||
const CreditInformation = ({ intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
creditCourseRequirements,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
@@ -12,9 +12,7 @@ import CreditInformation from '../../credit-information/CreditInformation';
|
||||
import messages from '../messages';
|
||||
|
||||
const CourseGrade = ({ intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
creditCourseRequirements,
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { CheckCircle, WarningFilled } from '@openedx/paragon/icons';
|
||||
import { breakpoints, Icon, useWindowSize } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
import GradeRangeTooltip from './GradeRangeTooltip';
|
||||
import messages from '../messages';
|
||||
|
||||
const CourseGradeFooter = ({ intl, passingGrade }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
courseGrade: {
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Locked } from '@openedx/paragon/icons';
|
||||
import { Button, Icon } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import messages from '../messages';
|
||||
|
||||
const CourseGradeHeader = ({ intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
const {
|
||||
org,
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
getLocale, injectIntl, intlShape, isRtl,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { OverlayTrigger, Popover } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
const CurrentGradeTooltip = ({ intl, tooltipClassName }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
courseGrade: {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
getLocale, injectIntl, intlShape, isRtl,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import CurrentGradeTooltip from './CurrentGradeTooltip';
|
||||
import PassingGradeTooltip from './PassingGradeTooltip';
|
||||
@@ -12,9 +12,7 @@ import PassingGradeTooltip from './PassingGradeTooltip';
|
||||
import messages from '../messages';
|
||||
|
||||
const GradeBar = ({ intl, passingGrade }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
courseGrade: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -7,14 +6,13 @@ import { InfoOutline } from '@openedx/paragon/icons';
|
||||
import {
|
||||
Icon, IconButton, OverlayTrigger, Popover,
|
||||
} from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
const GradeRangeTooltip = ({ intl, iconButtonClassName, passingGrade }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
gradesFeatureIsFullyLocked,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Blocked } from '@openedx/paragon/icons';
|
||||
import { Icon, Hyperlink } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import { showUngradedAssignments } from '../../utils';
|
||||
|
||||
@@ -15,9 +15,7 @@ import messages from '../messages';
|
||||
|
||||
const DetailedGrades = ({ intl }) => {
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
const {
|
||||
org,
|
||||
tabs,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
getLocale, injectIntl, intlShape, isRtl,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { DataTable } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import messages from '../messages';
|
||||
@@ -12,9 +12,7 @@ import SubsectionTitleCell from './SubsectionTitleCell';
|
||||
import { showUngradedAssignments } from '../../utils';
|
||||
|
||||
const DetailedGradesTable = ({ intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
sectionScores,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
@@ -9,15 +8,14 @@ import { Collapsible, Icon, Row } from '@openedx/paragon';
|
||||
import {
|
||||
ArrowDropDown, ArrowDropUp, Blocked, Info,
|
||||
} from '@openedx/paragon/icons';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import messages from '../messages';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import ProblemScoreDrawer from './ProblemScoreDrawer';
|
||||
|
||||
const SubsectionTitleCell = ({ intl, subsection }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
const {
|
||||
org,
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Blocked } from '@openedx/paragon/icons';
|
||||
import { Icon } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import messages from '../messages';
|
||||
|
||||
const AssignmentTypeCell = ({
|
||||
intl, assignmentType, footnoteMarker, footnoteId, locked,
|
||||
}) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
gradesFeatureIsFullyLocked,
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import messages from '../messages';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
const DroppableAssignmentFootnote = ({ footnotes, intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
const {
|
||||
gradesFeatureIsFullyLocked,
|
||||
} = useModel('progress', courseId);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
import GradeSummaryHeader from './GradeSummaryHeader';
|
||||
import GradeSummaryTable from './GradeSummaryTable';
|
||||
|
||||
const GradeSummary = () => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
gradingPolicy: {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, OverlayTrigger, Tooltip } from '@openedx/paragon';
|
||||
import { Blocked, InfoOutline } from '@openedx/paragon/icons';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import messages from '../messages';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
const GradeSummaryHeader = ({ allOfSomeAssignmentTypeIsLocked }) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
const {
|
||||
gradesFeatureIsFullyLocked,
|
||||
} = useModel('progress', courseId);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { DataTable } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
import AssignmentTypeCell from './AssignmentTypeCell';
|
||||
@@ -13,9 +13,7 @@ import messages from '../messages';
|
||||
|
||||
const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
gradingPolicy: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
Tooltip,
|
||||
} from '@openedx/paragon';
|
||||
import { InfoOutline } from '@openedx/paragon/icons';
|
||||
import { useContextId } from '../../../../data/hooks';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import messages from '../messages';
|
||||
@@ -29,9 +29,7 @@ const GradeSummaryTableFooter = () => {
|
||||
0,
|
||||
).toFixed(2);
|
||||
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
|
||||
const {
|
||||
courseGrade: {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
import { useContextId } from '../../../data/hooks';
|
||||
|
||||
import messages from './messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
const RelatedLinks = ({ intl }) => {
|
||||
const {
|
||||
courseId,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const courseId = useContextId();
|
||||
const {
|
||||
org,
|
||||
tabs,
|
||||
|
||||
5
src/data/hooks.ts
Normal file
5
src/data/hooks.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../store';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const useContextId = () => useSelector<RootState>(state => state.courseHome.courseId);
|
||||
@@ -26,7 +26,7 @@ import { TabContainer } from './tab-page';
|
||||
|
||||
import { fetchDatesTab, fetchOutlineTab, fetchProgressTab } from './course-home/data';
|
||||
import { fetchCourse } from './courseware/data';
|
||||
import initializeStore from './store';
|
||||
import { store } from './store';
|
||||
import NoticesProvider from './generic/notices';
|
||||
import PathFixesProvider from './generic/path-fixes';
|
||||
import LiveTab from './course-home/live-tab/LiveTab';
|
||||
@@ -38,7 +38,7 @@ import PageNotFound from './generic/PageNotFound';
|
||||
|
||||
subscribe(APP_READY, () => {
|
||||
ReactDOM.render(
|
||||
<AppProvider store={initializeStore()}>
|
||||
<AppProvider store={store}>
|
||||
<Helmet>
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
</Helmet>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Progress Tab Certificate Status Slot
|
||||
|
||||
### Slot ID: `progress_tab_certificate_status_main_body_slot`
|
||||
### Props:
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace or modify the Certificate Status component in the
|
||||
main body of the Progress Tab.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`.
|
||||
|
||||

|
||||
|
||||
```js
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { useContextId } from './src/data/hooks';
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
progress_tab_certificate_status_main_body_slot: {
|
||||
plugins: [
|
||||
{
|
||||
// Insert custom content after certificate status
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_certificate_status_content',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: () => {
|
||||
const courseId = useContextId();
|
||||
return (
|
||||
<div>
|
||||
<p>📚: {courseId}</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,19 @@
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
import CertificateStatus from '../../course-home/progress-tab/certificate-status/CertificateStatus';
|
||||
|
||||
const ProgressTabCertificateStatusMainBodySlot = () => {
|
||||
const windowWidth = useWindowSize().width;
|
||||
const wideScreen = windowWidth >= breakpoints.large.minWidth;
|
||||
return (
|
||||
<PluginSlot
|
||||
id="progress_tab_certificate_status_main_body_slot"
|
||||
>
|
||||
{windowWidth && !wideScreen && <CertificateStatus />}
|
||||
</PluginSlot>
|
||||
);
|
||||
};
|
||||
|
||||
ProgressTabCertificateStatusMainBodySlot.propTypes = {};
|
||||
|
||||
export default ProgressTabCertificateStatusMainBodySlot;
|
||||
@@ -0,0 +1,47 @@
|
||||
# Progress Tab Certificate Status Slot
|
||||
|
||||
### Slot ID: `progress_tab_certificate_status_side_panel_slot`
|
||||
### Props:
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace or modify the Certificate Status component in the
|
||||
side panel of the Progress Tab.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`.
|
||||
|
||||

|
||||
|
||||
```js
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { useContextId } from './src/data/hooks';
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
progress_tab_certificate_status_side_panel_slot: {
|
||||
plugins: [
|
||||
{
|
||||
// Insert custom content after certificate status
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_certificate_status_content',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: () => {
|
||||
const courseId = useContextId();
|
||||
return (
|
||||
<div>
|
||||
<p>📚: {courseId}</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,19 @@
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
import CertificateStatus from '../../course-home/progress-tab/certificate-status/CertificateStatus';
|
||||
|
||||
const ProgressTabCertificateStatusSidePanelSlot = () => {
|
||||
const windowWidth = useWindowSize().width;
|
||||
const wideScreen = windowWidth >= breakpoints.large.minWidth;
|
||||
return (
|
||||
<PluginSlot
|
||||
id="progress_tab_certificate_status_side_panel_slot"
|
||||
>
|
||||
{windowWidth && wideScreen && <CertificateStatus />}
|
||||
</PluginSlot>
|
||||
);
|
||||
};
|
||||
|
||||
ProgressTabCertificateStatusSidePanelSlot.propTypes = {};
|
||||
|
||||
export default ProgressTabCertificateStatusSidePanelSlot;
|
||||
46
src/plugin-slots/ProgressTabCourseGradeSlot/README.md
Normal file
46
src/plugin-slots/ProgressTabCourseGradeSlot/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Progress Tab Course Grade Slot
|
||||
|
||||
### Slot ID: `progress_tab_course_grade_slot`
|
||||
### Props:
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace or modify the Course Grades view in the Progress Tab.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`.
|
||||
|
||||

|
||||
|
||||
```js
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { useContextId } from './src/data/hooks';
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
progress_tab_course_grade_slot: {
|
||||
plugins: [
|
||||
{
|
||||
// Insert custom content after course grade widget
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_course_grade_content',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: () => {
|
||||
const courseId = useContextId();
|
||||
return (
|
||||
<div>
|
||||
<p>📚: {courseId}</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
14
src/plugin-slots/ProgressTabCourseGradeSlot/index.jsx
Normal file
14
src/plugin-slots/ProgressTabCourseGradeSlot/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import CourseGrade from '../../course-home/progress-tab/grades/course-grade/CourseGrade';
|
||||
|
||||
const ProgressTabCourseGradeSlot = () => (
|
||||
<PluginSlot
|
||||
id="progress_tab_course_grade_slot"
|
||||
>
|
||||
<CourseGrade />
|
||||
</PluginSlot>
|
||||
);
|
||||
|
||||
ProgressTabCourseGradeSlot.propTypes = {};
|
||||
|
||||
export default ProgressTabCourseGradeSlot;
|
||||
46
src/plugin-slots/ProgressTabGradeBreakdownSlot/README.md
Normal file
46
src/plugin-slots/ProgressTabGradeBreakdownSlot/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Progress Tab Grade Breakdown Slot
|
||||
|
||||
### Slot ID: `progress_tab_grade_breakdown_slot`
|
||||
### Props:
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace or modify the Grade Summary and Details Breakdown view in the Progress Tab.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`.
|
||||
|
||||

|
||||
|
||||
```js
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { useContextId } from './src/data/hooks';
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
progress_tab_grade_breakdown_slot: {
|
||||
plugins: [
|
||||
{
|
||||
// Insert custom content after grade summary widget
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_grade_summary_content',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: () => {
|
||||
const courseId = useContextId();
|
||||
return (
|
||||
<div>
|
||||
<p>📚: {courseId}</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
29
src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx
Normal file
29
src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import React from 'react';
|
||||
import DetailedGrades from '../../course-home/progress-tab/grades/detailed-grades/DetailedGrades';
|
||||
import GradeSummary from '../../course-home/progress-tab/grades/grade-summary/GradeSummary';
|
||||
import { useContextId } from '../../data/hooks';
|
||||
|
||||
const ProgressTabGradeBreakdownSlot = () => {
|
||||
const courseId = useContextId();
|
||||
const { gradesFeatureIsFullyLocked } = useModel('progress', courseId);
|
||||
const applyLockedOverlay = gradesFeatureIsFullyLocked ? 'locked-overlay' : '';
|
||||
return (
|
||||
<PluginSlot
|
||||
id="progress_tab_grade_breakdown_slot"
|
||||
>
|
||||
<div
|
||||
className={`grades my-4 p-4 rounded raised-card ${applyLockedOverlay}`}
|
||||
aria-hidden={gradesFeatureIsFullyLocked}
|
||||
>
|
||||
<GradeSummary />
|
||||
<DetailedGrades />
|
||||
</div>
|
||||
</PluginSlot>
|
||||
);
|
||||
};
|
||||
|
||||
ProgressTabGradeBreakdownSlot.propTypes = {};
|
||||
|
||||
export default ProgressTabGradeBreakdownSlot;
|
||||
46
src/plugin-slots/ProgressTabRelatedLinksSlot/README.md
Normal file
46
src/plugin-slots/ProgressTabRelatedLinksSlot/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Progress Tab Related Links Slot
|
||||
|
||||
### Slot ID: `progress_tab_related_links_slot`
|
||||
### Props:
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace or modify the related links view in the Progress Tab.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`.
|
||||
|
||||

|
||||
|
||||
```js
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { useContextId } from './src/data/hooks';
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
progress_tab_related_links_slot: {
|
||||
plugins: [
|
||||
{
|
||||
// Insert custom content after related links widget
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_related_links_content',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: () => {
|
||||
const courseId = useContextId();
|
||||
return (
|
||||
<div>
|
||||
<p>📚: {courseId}</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
14
src/plugin-slots/ProgressTabRelatedLinksSlot/index.jsx
Normal file
14
src/plugin-slots/ProgressTabRelatedLinksSlot/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import RelatedLinks from '../../course-home/progress-tab/related-links/RelatedLinks';
|
||||
|
||||
const ProgressTabRelatedLinksSlot = () => (
|
||||
<PluginSlot
|
||||
id="progress_tab_related_links_slot"
|
||||
>
|
||||
<RelatedLinks />
|
||||
</PluginSlot>
|
||||
);
|
||||
|
||||
ProgressTabRelatedLinksSlot.propTypes = {};
|
||||
|
||||
export default ProgressTabRelatedLinksSlot;
|
||||
@@ -29,3 +29,7 @@ export default function initializeStore() {
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export const store = initializeStore();
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
Reference in New Issue
Block a user