From 8e41cebe49ad6db790058c46e8b8be1370d64d56 Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Fri, 8 Nov 2024 18:34:20 +0100 Subject: [PATCH] feat: [FC-0070] rendering split test content in unit page --- src/constants.js | 1 + src/course-unit/CourseUnit.jsx | 26 +- src/course-unit/CourseUnit.scss | 1 + .../add-component/AddComponent.jsx | 243 ++++++++++++------ .../ComponentModalView.jsx | 26 +- src/course-unit/breadcrumbs/Breadcrumbs.jsx | 64 +++-- src/course-unit/breadcrumbs/Breadcrumbs.scss | 2 +- src/course-unit/constants.js | 1 + src/course-unit/data/thunk.js | 17 +- .../header-navigations/HeaderNavigations.jsx | 8 +- src/course-unit/header-title/HeaderTitle.jsx | 12 +- src/course-unit/hooks.jsx | 24 +- src/course-unit/sidebar/Sidebar.scss | 15 ++ .../sidebar/SplitTestSidebarInfo.tsx | 61 +++++ src/course-unit/sidebar/messages.js | 55 ++++ .../xblock-container-iframe/index.scss | 4 + .../xblock-container-iframe/index.tsx | 3 +- .../configure-modal/ConfigureModal.jsx | 4 +- src/generic/configure-modal/UnitTab.jsx | 20 +- src/generic/configure-modal/messages.js | 6 +- src/generic/sub-header/SubHeader.jsx | 6 +- src/utils.js | 2 +- 22 files changed, 454 insertions(+), 147 deletions(-) create mode 100644 src/course-unit/sidebar/SplitTestSidebarInfo.tsx create mode 100644 src/course-unit/xblock-container-iframe/index.scss diff --git a/src/constants.js b/src/constants.js index 9e5c5cc84d..6881547134 100644 --- a/src/constants.js +++ b/src/constants.js @@ -59,6 +59,7 @@ export const COURSE_BLOCK_NAMES = ({ sequential: { id: 'sequential', name: 'Subsection' }, vertical: { id: 'vertical', name: 'Unit' }, libraryContent: { id: 'library_content', name: 'Library content' }, + splitTest: { id: 'split_test', name: 'Split Test' }, component: { id: 'component', name: 'Component' }, }); diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx index 5392294cdb..6dd23ca177 100644 --- a/src/course-unit/CourseUnit.jsx +++ b/src/course-unit/CourseUnit.jsx @@ -29,6 +29,7 @@ import Breadcrumbs from './breadcrumbs/Breadcrumbs'; import HeaderNavigations from './header-navigations/HeaderNavigations'; import Sequence from './course-sequence'; import Sidebar from './sidebar'; +import SplitTestSidebarInfo from './sidebar/SplitTestSidebarInfo'; import { useCourseUnit } from './hooks'; import messages from './messages'; import PublishControls from './sidebar/PublishControls'; @@ -68,10 +69,14 @@ const CourseUnit = ({ courseId }) => { handleRollbackMovedXBlock, handleCloseXBlockMovedAlert, handleNavigateToTargetUnit, + addComponentTemplateData, + setAddComponentTemplateData, + handleSubmitAddComponentModal, } = useCourseUnit({ courseId, blockId }); const isUnitVerticalType = unitCategory === COURSE_BLOCK_NAMES.vertical.id; const isUnitLibraryType = unitCategory === COURSE_BLOCK_NAMES.libraryContent.id; + const isSplitTestType = unitCategory === COURSE_BLOCK_NAMES.splitTest.id; const unitLayout = [{ span: 12 }, { span: 0 }]; const defaultLayout = { @@ -161,7 +166,7 @@ const CourseUnit = ({ courseId }) => { )} headerActions={( )} @@ -192,12 +197,14 @@ const CourseUnit = ({ courseId }) => { /> )} - {isUnitVerticalType && ( - - )} + {showPasteXBlock && canPasteComponent && isUnitVerticalType && ( { )} + {isSplitTestType && ( + + + + )} diff --git a/src/course-unit/CourseUnit.scss b/src/course-unit/CourseUnit.scss index 6a7c0c72bd..fa96286310 100644 --- a/src/course-unit/CourseUnit.scss +++ b/src/course-unit/CourseUnit.scss @@ -4,6 +4,7 @@ @import "./sidebar/Sidebar"; @import "./header-title/HeaderTitle"; @import "./move-modal"; +@import "./xblock-container-iframe"; .course-unit { min-width: 900px; diff --git a/src/course-unit/add-component/AddComponent.jsx b/src/course-unit/add-component/AddComponent.jsx index 573da717fe..779385f6c4 100644 --- a/src/course-unit/add-component/AddComponent.jsx +++ b/src/course-unit/add-component/AddComponent.jsx @@ -10,121 +10,210 @@ import ComponentModalView from './add-component-modals/ComponentModalView'; import AddComponentButton from './add-component-btn'; import messages from './messages'; -const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => { +const AddComponent = ({ + parentLocator, + handleCreateNewCourseXBlock, + isUnitVerticalType, + handleSubmitAddComponentModal, + addComponentTemplateData, + setAddComponentTemplateData, +}) => { const navigate = useNavigate(); const intl = useIntl(); const [isOpenAdvanced, openAdvanced, closeAdvanced] = useToggle(false); const [isOpenHtml, openHtml, closeHtml] = useToggle(false); const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false); const { componentTemplates = {} } = useSelector(getCourseSectionVertical); + const isRequestedModalView = addComponentTemplateData?.model?.type; + const blockId = addComponentTemplateData.parentLocator || parentLocator; const handleCreateNewXBlock = (type, moduleName) => { + const preventDisplayLoading = isRequestedModalView && !isUnitVerticalType; switch (type) { case COMPONENT_TYPES.discussion: case COMPONENT_TYPES.dragAndDrop: - handleCreateNewCourseXBlock({ type, parentLocator: blockId }); + handleCreateNewCourseXBlock({ + type, + parentLocator: blockId, + }, null, preventDisplayLoading); break; case COMPONENT_TYPES.problem: case COMPONENT_TYPES.video: - handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => { - navigate(`/course/${courseKey}/editor/${type}/${locator}`); - }); + handleCreateNewCourseXBlock( + { + type, + parentLocator: blockId, + }, + ({ courseKey, locator }) => navigate(`/course/${courseKey}/editor/${type}/${locator}`), + preventDisplayLoading, + ); break; // TODO: The library functional will be a bit different of current legacy (CMS) // behaviour and this ticket is on hold (blocked by other development team). case COMPONENT_TYPES.library: - handleCreateNewCourseXBlock({ type, category: 'library_content', parentLocator: blockId }); + handleCreateNewCourseXBlock( + { + type, + category: 'library_content', + parentLocator: blockId, + }, + null, + preventDisplayLoading, + ); break; case COMPONENT_TYPES.advanced: - handleCreateNewCourseXBlock({ - type: moduleName, category: moduleName, parentLocator: blockId, - }); + handleCreateNewCourseXBlock( + { + type: moduleName, + category: moduleName, + parentLocator: blockId, + }, + null, + preventDisplayLoading, + ); break; case COMPONENT_TYPES.openassessment: - handleCreateNewCourseXBlock({ - boilerplate: moduleName, category: type, parentLocator: blockId, - }); + handleCreateNewCourseXBlock( + { + boilerplate: moduleName, + category: + type, + parentLocator: blockId, + }, + null, + preventDisplayLoading, + ); break; case COMPONENT_TYPES.html: - handleCreateNewCourseXBlock({ - type, - boilerplate: moduleName, - parentLocator: blockId, - }, ({ courseKey, locator }) => { - navigate(`/course/${courseKey}/editor/html/${locator}`); - }); + handleCreateNewCourseXBlock( + { + type, + boilerplate: moduleName, + parentLocator: blockId, + }, + ({ courseKey, locator }) => navigate(`/course/${courseKey}/editor/html/${locator}`), + preventDisplayLoading, + ); break; default: } + if (preventDisplayLoading) { + handleSubmitAddComponentModal(); + } }; - if (!Object.keys(componentTemplates).length) { - return null; + if (isRequestedModalView && !isUnitVerticalType) { + return ( + {}, + close: () => setAddComponentTemplateData({}), + isOpen: addComponentTemplateData.model, + }} + /> + ); } - return ( -
-
{intl.formatMessage(messages.title)}
-
    - {componentTemplates.map((component) => { - const { type, displayName } = component; - let modalParams; + if (Object.keys(componentTemplates).length && isUnitVerticalType) { + return ( +
    +
    {intl.formatMessage(messages.title)}
    +
      + {componentTemplates.map((component) => { + const { type, displayName } = component; + let modalParams; + + if (!component.templates.length) { + return null; + } - if (!component.templates.length) { - return null; - } + switch (type) { + case COMPONENT_TYPES.advanced: + modalParams = { + open: openAdvanced, + close: closeAdvanced, + isOpen: isOpenAdvanced, + }; + break; + case COMPONENT_TYPES.html: + modalParams = { + open: openHtml, + close: closeHtml, + isOpen: isOpenHtml, + }; + break; + case COMPONENT_TYPES.openassessment: + modalParams = { + open: openOpenAssessment, + close: closeOpenAssessment, + isOpen: isOpenOpenAssessment, + }; + break; + default: + return ( +
    • + handleCreateNewXBlock(type)} + displayName={displayName} + type={type} + /> +
    • + ); + } - switch (type) { - case COMPONENT_TYPES.advanced: - modalParams = { - open: openAdvanced, - close: closeAdvanced, - isOpen: isOpenAdvanced, - }; - break; - case COMPONENT_TYPES.html: - modalParams = { - open: openHtml, - close: closeHtml, - isOpen: isOpenHtml, - }; - break; - case COMPONENT_TYPES.openassessment: - modalParams = { - open: openOpenAssessment, - close: closeOpenAssessment, - isOpen: isOpenOpenAssessment, - }; - break; - default: - return ( -
    • - handleCreateNewXBlock(type)} - displayName={displayName} - type={type} - /> -
    • - ); - } + return ( + + ); + })} +
    +
    + ); + } + + return null; +}; - return ( - - ); - })} -
-
- ); +AddComponent.defaultProps = { + addComponentTemplateData: {}, + setAddComponentTemplateData: () => {}, }; AddComponent.propTypes = { - blockId: PropTypes.string.isRequired, + isUnitVerticalType: PropTypes.bool.isRequired, + parentLocator: PropTypes.string.isRequired, handleCreateNewCourseXBlock: PropTypes.func.isRequired, + addComponentTemplateData: { + blockId: PropTypes.string.isRequired, + model: PropTypes.shape({ + displayName: PropTypes.string.isRequired, + category: PropTypes.string, + type: PropTypes.string.isRequired, + templates: PropTypes.arrayOf( + PropTypes.shape({ + boilerplateName: PropTypes.string, + category: PropTypes.string, + displayName: PropTypes.string.isRequired, + supportLevel: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + }), + ), + supportLegend: PropTypes.shape({ + allowUnsupportedXblocks: PropTypes.bool, + documentationLabel: PropTypes.string, + showLegend: PropTypes.bool, + }), + }), + }, + setAddComponentTemplateData: PropTypes.func, + handleSubmitAddComponentModal: PropTypes.func.isRequired, }; export default AddComponent; diff --git a/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx b/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx index 4f387d4bd6..bb3946350b 100644 --- a/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx +++ b/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx @@ -14,13 +14,14 @@ const ComponentModalView = ({ component, modalParams, handleCreateNewXBlock, + isRequestedModalView, }) => { const intl = useIntl(); const dispatch = useDispatch(); const [moduleTitle, setModuleTitle] = useState(''); const { open, close, isOpen } = modalParams; const { - type, displayName, templates, supportLegend, + type, displayName, templates = [], supportLegend, } = component; const supportLabels = getXBlockSupportMessages(intl); @@ -30,15 +31,19 @@ const ComponentModalView = ({ setModuleTitle(''); }; + const renderAddComponentButton = () => ( +
  • + +
  • + ); + return ( <> -
  • - -
  • + {!isRequestedModalView && renderAddComponentButton()} { const intl = useIntl(); const { ancestorXblocks = [] } = useSelector(getCourseSectionVertical); + const hasChildWithUrl = (children = []) => !!children.filter((child) => child.url).length; + return ( -