/* eslint-disable no-use-before-define */ import React, { useEffect, useContext, useState, } from 'react'; import PropTypes from 'prop-types'; import { sendTrackEvent, sendTrackingLogEvent, } from '@edx/frontend-platform/analytics'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useSelector } from 'react-redux'; import { history } from '@edx/frontend-platform'; import PageLoading from '../../../generic/PageLoading'; import { UserMessagesContext, ALERT_TYPES } from '../../../generic/user-messages'; import { useModel } from '../../../generic/model-store'; import CourseLicense from '../course-license'; import messages from './messages'; import { SequenceNavigation, UnitNavigation } from './sequence-navigation'; import SequenceContent from './SequenceContent'; /** [MM-P2P] Experiment */ import { isMobile } from '../../../experiments/mm-p2p/utils'; import { MMP2PFlyover, MMP2PFlyoverMobile } from '../../../experiments/mm-p2p'; function Sequence({ unitId, sequenceId, courseId, unitNavigationHandler, nextSequenceHandler, previousSequenceHandler, intl, mmp2p, }) { const course = useModel('coursewareMeta', courseId); const sequence = useModel('sequences', sequenceId); const unit = useModel('units', unitId); const sequenceStatus = useSelector(state => state.courseware.sequenceStatus); const handleNext = () => { const nextIndex = sequence.unitIds.indexOf(unitId) + 1; if (nextIndex < sequence.unitIds.length) { const newUnitId = sequence.unitIds[nextIndex]; handleNavigate(newUnitId); } else { nextSequenceHandler(); } }; const handlePrevious = () => { const previousIndex = sequence.unitIds.indexOf(unitId) - 1; if (previousIndex >= 0) { const newUnitId = sequence.unitIds[previousIndex]; handleNavigate(newUnitId); } else { previousSequenceHandler(); } }; const handleNavigate = (destinationUnitId) => { unitNavigationHandler(destinationUnitId); }; const logEvent = (eventName, widgetPlacement, targetUnitId) => { // Note: tabs are tracked with a 1-indexed position // as opposed to a 0-index used throughout this MFE const currentIndex = sequence.unitIds.length > 0 ? sequence.unitIds.indexOf(unitId) : 0; const payload = { current_tab: currentIndex + 1, id: unitId, tab_count: sequence.unitIds.length, widget_placement: widgetPlacement, }; if (targetUnitId) { const targetIndex = sequence.unitIds.indexOf(targetUnitId); payload.target_tab = targetIndex + 1; } sendTrackEvent(eventName, payload); sendTrackingLogEvent(eventName, payload); }; const { add, remove } = useContext(UserMessagesContext); useEffect(() => { let id = null; if (sequenceStatus === 'loaded') { if (sequence.bannerText) { id = add({ code: null, dismissible: false, text: sequence.bannerText, type: ALERT_TYPES.INFO, topic: 'sequence', }); } } return () => { if (id) { remove(id); } }; }, [sequenceStatus, sequence]); const [unitHasLoaded, setUnitHasLoaded] = useState(false); const handleUnitLoaded = () => { setUnitHasLoaded(true); }; useEffect(() => { if (unit) { setUnitHasLoaded(false); } }, [unit]); if (sequenceStatus === 'loading') { if (!sequenceId) { return (
{intl.formatMessage(messages['learn.sequence.no.content'])}
); } return ( ); } /* TODO: When the micro-frontend supports viewing special exams without redirecting to the legacy experience, we can remove this whole conditional. For now, though, we show the spinner here because we expect CoursewareContainer to be performing a redirect to the legacy experience while we're waiting. That redirect may take a few seconds, so we show the spinner in the meantime. */ if (sequenceStatus === 'loaded' && sequence.isTimeLimited) { return ( ); } const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated; const goToCourseExitPage = () => { history.push(`/course/${courseId}/course-end`); }; if (sequenceStatus === 'loaded') { return (
{ logEvent('edx.ui.lms.sequence.next_selected', 'top'); handleNext(); }} onNavigate={(destinationUnitId) => { logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId); handleNavigate(destinationUnitId); }} previousSequenceHandler={() => { logEvent('edx.ui.lms.sequence.previous_selected', 'top'); handlePrevious(); }} goToCourseExitPage={() => goToCourseExitPage()} />
{unitHasLoaded && ( { logEvent('edx.ui.lms.sequence.previous_selected', 'bottom'); handlePrevious(); }} onClickNext={() => { logEvent('edx.ui.lms.sequence.next_selected', 'bottom'); handleNext(); }} goToCourseExitPage={() => goToCourseExitPage()} /> )}
{/** [MM-P2P] Experiment */} {(mmp2p.state.isEnabled && mmp2p.flyover.isVisible) && ( isMobile() ? : )}
); } // sequence status 'failed' and any other unexpected sequence status. return (

{intl.formatMessage(messages['learn.course.load.failure'])}

); } Sequence.propTypes = { unitId: PropTypes.string, sequenceId: PropTypes.string, courseId: PropTypes.string.isRequired, unitNavigationHandler: PropTypes.func.isRequired, nextSequenceHandler: PropTypes.func.isRequired, previousSequenceHandler: PropTypes.func.isRequired, intl: intlShape.isRequired, /** [MM-P2P] Experiment */ mmp2p: PropTypes.shape({ flyover: PropTypes.shape({ isVisible: PropTypes.bool.isRequired, }), meta: PropTypes.shape({ showLock: PropTypes.bool, }), state: PropTypes.shape({ isEnabled: PropTypes.bool.isRequired, }), }), }; Sequence.defaultProps = { sequenceId: null, unitId: null, /** [MM-P2P] Experiment */ mmp2p: { flyover: { isVisible: false }, meta: { showLock: false }, state: { isEnabled: false }, }, }; export default injectIntl(Sequence);