286 lines
9.9 KiB
JavaScript
286 lines
9.9 KiB
JavaScript
import { useEffect } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { useSelector } from 'react-redux';
|
|
import { useParams } from 'react-router-dom';
|
|
import {
|
|
Container, Layout, Stack, Button, TransitionReplace,
|
|
Alert,
|
|
} from '@openedx/paragon';
|
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
import {
|
|
Warning as WarningIcon,
|
|
CheckCircle as CheckCircleIcon,
|
|
} from '@openedx/paragon/icons';
|
|
import { CourseAuthoringUnitSidebarSlot } from '../plugin-slots/CourseAuthoringUnitSidebarSlot';
|
|
|
|
import { getProcessingNotification } from '../generic/processing-notification/data/selectors';
|
|
import SubHeader from '../generic/sub-header/SubHeader';
|
|
import { RequestStatus } from '../data/constants';
|
|
import getPageHeadTitle from '../generic/utils';
|
|
import AlertMessage from '../generic/alert-message';
|
|
import { PasteComponent } from '../generic/clipboard';
|
|
import ProcessingNotification from '../generic/processing-notification';
|
|
import { SavingErrorAlert } from '../generic/saving-error-alert';
|
|
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
|
|
import Loading from '../generic/Loading';
|
|
import AddComponent from './add-component/AddComponent';
|
|
import HeaderTitle from './header-title/HeaderTitle';
|
|
import Breadcrumbs from './breadcrumbs/Breadcrumbs';
|
|
import Sequence from './course-sequence';
|
|
import Sidebar from './sidebar';
|
|
import SplitTestSidebarInfo from './sidebar/SplitTestSidebarInfo';
|
|
import { useCourseUnit, useLayoutGrid, useScrollToLastPosition } from './hooks';
|
|
import messages from './messages';
|
|
import { PasteNotificationAlert } from './clipboard';
|
|
import XBlockContainerIframe from './xblock-container-iframe';
|
|
import MoveModal from './move-modal';
|
|
import IframePreviewLibraryXBlockChanges from './preview-changes';
|
|
import CourseUnitHeaderActionsSlot from '../plugin-slots/CourseUnitHeaderActionsSlot';
|
|
|
|
const CourseUnit = ({ courseId }) => {
|
|
const { blockId } = useParams();
|
|
const intl = useIntl();
|
|
const {
|
|
courseUnit,
|
|
isLoading,
|
|
sequenceId,
|
|
unitTitle,
|
|
unitCategory,
|
|
errorMessage,
|
|
sequenceStatus,
|
|
savingStatus,
|
|
isTitleEditFormOpen,
|
|
isUnitVerticalType,
|
|
isUnitLibraryType,
|
|
isSplitTestType,
|
|
staticFileNotices,
|
|
currentlyVisibleToStudents,
|
|
unitXBlockActions,
|
|
sharedClipboardData,
|
|
showPasteXBlock,
|
|
showPasteUnit,
|
|
handleTitleEditSubmit,
|
|
headerNavigationsActions,
|
|
handleTitleEdit,
|
|
handleCreateNewCourseXBlock,
|
|
handleConfigureSubmit,
|
|
courseVerticalChildren,
|
|
canPasteComponent,
|
|
isMoveModalOpen,
|
|
openMoveModal,
|
|
closeMoveModal,
|
|
movedXBlockParams,
|
|
handleRollbackMovedXBlock,
|
|
handleCloseXBlockMovedAlert,
|
|
handleNavigateToTargetUnit,
|
|
addComponentTemplateData,
|
|
} = useCourseUnit({ courseId, blockId });
|
|
const layoutGrid = useLayoutGrid(unitCategory, isUnitLibraryType);
|
|
|
|
const readOnly = !!courseUnit.readOnly;
|
|
|
|
useEffect(() => {
|
|
document.title = getPageHeadTitle('', unitTitle);
|
|
}, [unitTitle]);
|
|
|
|
useScrollToLastPosition();
|
|
|
|
const {
|
|
isShow: isShowProcessingNotification,
|
|
title: processingNotificationTitle,
|
|
} = useSelector(getProcessingNotification);
|
|
|
|
if (isLoading) {
|
|
return <Loading />;
|
|
}
|
|
|
|
if (sequenceStatus === RequestStatus.FAILED) {
|
|
return (
|
|
<Container size="xl" className="course-unit px-4 mt-4">
|
|
<ConnectionErrorAlert />
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Container size="xl" className="course-unit px-4">
|
|
<section className="course-unit-container mb-4 mt-5">
|
|
<TransitionReplace>
|
|
{movedXBlockParams.isSuccess ? (
|
|
<AlertMessage
|
|
key="xblock-moved-alert"
|
|
data-testid="xblock-moved-alert"
|
|
show={movedXBlockParams.isSuccess}
|
|
variant="success"
|
|
icon={CheckCircleIcon}
|
|
title={movedXBlockParams.isUndo
|
|
? intl.formatMessage(messages.alertMoveCancelTitle)
|
|
: intl.formatMessage(messages.alertMoveSuccessTitle)}
|
|
description={movedXBlockParams.isUndo
|
|
? intl.formatMessage(messages.alertMoveCancelDescription, { title: movedXBlockParams.title })
|
|
: intl.formatMessage(messages.alertMoveSuccessDescription, { title: movedXBlockParams.title })}
|
|
aria-hidden={movedXBlockParams.isSuccess}
|
|
dismissible
|
|
actions={movedXBlockParams.isUndo ? null : [
|
|
<Button
|
|
onClick={handleRollbackMovedXBlock}
|
|
key="xblock-moved-alert-undo-move-button"
|
|
>
|
|
{intl.formatMessage(messages.undoMoveButton)}
|
|
</Button>,
|
|
<Button
|
|
onClick={handleNavigateToTargetUnit}
|
|
key="xblock-moved-alert-new-location-button"
|
|
>
|
|
{intl.formatMessage(messages.newLocationButton)}
|
|
</Button>,
|
|
]}
|
|
onClose={handleCloseXBlockMovedAlert}
|
|
/>
|
|
) : null}
|
|
</TransitionReplace>
|
|
{courseUnit.upstreamInfo.upstreamLink && (
|
|
<AlertMessage
|
|
title={intl.formatMessage(
|
|
messages.alertLibraryUnitReadOnlyText,
|
|
{
|
|
link: (
|
|
<Alert.Link
|
|
className="ml-1"
|
|
href={courseUnit.upstreamInfo.upstreamLink}
|
|
>
|
|
{intl.formatMessage(messages.alertLibraryUnitReadOnlyLinkText)}
|
|
</Alert.Link>
|
|
),
|
|
},
|
|
)}
|
|
variant="info"
|
|
/>
|
|
)}
|
|
<SubHeader
|
|
hideBorder
|
|
title={(
|
|
<HeaderTitle
|
|
unitTitle={unitTitle}
|
|
isTitleEditFormOpen={isTitleEditFormOpen}
|
|
handleTitleEdit={handleTitleEdit}
|
|
handleTitleEditSubmit={handleTitleEditSubmit}
|
|
handleConfigureSubmit={handleConfigureSubmit}
|
|
/>
|
|
)}
|
|
breadcrumbs={(
|
|
<Breadcrumbs
|
|
courseId={courseId}
|
|
parentUnitId={sequenceId}
|
|
/>
|
|
)}
|
|
headerActions={(
|
|
<CourseUnitHeaderActionsSlot
|
|
category={unitCategory}
|
|
headerNavigationsActions={headerNavigationsActions}
|
|
unitTitle={unitTitle}
|
|
verticalBlocks={courseVerticalChildren.children}
|
|
/>
|
|
)}
|
|
/>
|
|
{isUnitVerticalType && (
|
|
<Sequence
|
|
courseId={courseId}
|
|
sequenceId={sequenceId}
|
|
unitId={blockId}
|
|
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
|
|
showPasteUnit={showPasteUnit}
|
|
/>
|
|
)}
|
|
<Layout {...layoutGrid}>
|
|
<Layout.Element>
|
|
{currentlyVisibleToStudents && (
|
|
<AlertMessage
|
|
className="course-unit__alert"
|
|
title={intl.formatMessage(messages.alertUnpublishedVersion)}
|
|
variant="warning"
|
|
icon={WarningIcon}
|
|
/>
|
|
)}
|
|
{staticFileNotices && (
|
|
<PasteNotificationAlert
|
|
staticFileNotices={staticFileNotices}
|
|
courseId={courseId}
|
|
/>
|
|
)}
|
|
<XBlockContainerIframe
|
|
courseId={courseId}
|
|
blockId={blockId}
|
|
isUnitVerticalType={isUnitVerticalType}
|
|
unitXBlockActions={unitXBlockActions}
|
|
courseVerticalChildren={courseVerticalChildren.children}
|
|
handleConfigureSubmit={handleConfigureSubmit}
|
|
/>
|
|
{!readOnly && (
|
|
<AddComponent
|
|
parentLocator={blockId}
|
|
isSplitTestType={isSplitTestType}
|
|
isUnitVerticalType={isUnitVerticalType}
|
|
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
|
|
addComponentTemplateData={addComponentTemplateData}
|
|
/>
|
|
)}
|
|
{!readOnly && showPasteXBlock && canPasteComponent && isUnitVerticalType && (
|
|
<PasteComponent
|
|
clipboardData={sharedClipboardData}
|
|
onClick={
|
|
() => handleCreateNewCourseXBlock({ stagedContent: 'clipboard', parentLocator: blockId })
|
|
}
|
|
text={intl.formatMessage(messages.pasteButtonText)}
|
|
/>
|
|
)}
|
|
<MoveModal
|
|
isOpenModal={isMoveModalOpen}
|
|
openModal={openMoveModal}
|
|
closeModal={closeMoveModal}
|
|
courseId={courseId}
|
|
/>
|
|
<IframePreviewLibraryXBlockChanges />
|
|
</Layout.Element>
|
|
<Layout.Element>
|
|
<Stack gap={3}>
|
|
{isUnitVerticalType && (
|
|
<CourseAuthoringUnitSidebarSlot
|
|
courseId={courseId}
|
|
blockId={blockId}
|
|
unitTitle={unitTitle}
|
|
xBlocks={courseVerticalChildren.children}
|
|
readOnly={readOnly}
|
|
/>
|
|
)}
|
|
{isSplitTestType && (
|
|
<Sidebar data-testid="course-split-test-sidebar">
|
|
<SplitTestSidebarInfo />
|
|
</Sidebar>
|
|
)}
|
|
</Stack>
|
|
</Layout.Element>
|
|
</Layout>
|
|
</section>
|
|
</Container>
|
|
<div className="alert-toast">
|
|
<ProcessingNotification
|
|
isShow={isShowProcessingNotification}
|
|
title={processingNotificationTitle}
|
|
/>
|
|
<SavingErrorAlert
|
|
savingStatus={savingStatus}
|
|
errorMessage={errorMessage}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
CourseUnit.propTypes = {
|
|
courseId: PropTypes.string.isRequired,
|
|
};
|
|
|
|
export default CourseUnit;
|