Skip to content
622 changes: 345 additions & 277 deletions src/course-unit/CourseUnit.test.jsx

Large diffs are not rendered by default.

41 changes: 39 additions & 2 deletions src/course-unit/add-component/AddComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@openedx/paragon';

import { getCourseSectionVertical } from '../data/selectors';
import { getWaffleFlags } from '../../data/selectors';
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
import ComponentModalView from './add-component-modals/ComponentModalView';
import AddComponentButton from './add-component-btn';
Expand All @@ -16,6 +17,7 @@ import { ComponentPicker } from '../../library-authoring/component-picker';
import { messageTypes } from '../constants';
import { useIframe } from '../../generic/hooks/context/hooks';
import { useEventListener } from '../../generic/hooks';
import VideoSelectorPage from '../../editors/VideoSelectorPage';
import EditorPage from '../../editors/EditorPage';

const AddComponent = ({
Expand All @@ -32,6 +34,7 @@ const AddComponent = ({
const { componentTemplates = {} } = useSelector(getCourseSectionVertical);
const blockId = addComponentTemplateData.parentLocator || parentLocator;
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
const [isVideoSelectorModalOpen, showVideoSelectorModal, closeVideoSelectorModal] = useToggle();
const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle();

const [blockType, setBlockType] = useState(null);
Expand All @@ -41,6 +44,7 @@ const AddComponent = ({
const [selectedComponents, setSelectedComponents] = useState([]);
const [usageId, setUsageId] = useState(null);
const { sendMessageToIframe } = useIframe();
const { useVideoGalleryFlow } = useSelector(getWaffleFlags);

const receiveMessage = useCallback(({ data: { type, payload } }) => {
if (type === messageTypes.showMultipleComponentPicker) {
Expand All @@ -61,8 +65,9 @@ const AddComponent = ({

const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
closeXBlockEditorModal();
closeVideoSelectorModal();
sendMessageToIframe(messageTypes.refreshXBlock, null);
}, [closeXBlockEditorModal, sendMessageToIframe]);
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);

const handleLibraryV2Selection = useCallback((selection) => {
handleCreateNewCourseXBlock({
Expand All @@ -80,7 +85,6 @@ const AddComponent = ({
case COMPONENT_TYPES.dragAndDrop:
handleCreateNewCourseXBlock({ type, parentLocator: blockId });
break;
case COMPONENT_TYPES.video:
case COMPONENT_TYPES.problem:
handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
setCourseId(courseKey);
Expand All @@ -89,6 +93,21 @@ const AddComponent = ({
showXBlockEditorModal();
});
break;
case COMPONENT_TYPES.video:
handleCreateNewCourseXBlock(
{ type, parentLocator: blockId },
/* istanbul ignore next */ ({ courseKey, locator }) => {
setCourseId(courseKey);
setBlockType(type);
setNewBlockId(locator);
if (useVideoGalleryFlow) {
showVideoSelectorModal();
} else {
showXBlockEditorModal();
}
},
);
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:
Expand Down Expand Up @@ -215,6 +234,24 @@ const AddComponent = ({
onChangeComponentSelection={setSelectedComponents}
/>
</StandardModal>
<StandardModal
title={intl.formatMessage(messages.videoPickerModalTitle)}
isOpen={isVideoSelectorModalOpen}
onClose={closeVideoSelectorModal}
isOverflowVisible={false}
size="xl"
>
<div className="selector-page">
<VideoSelectorPage
blockId={newBlockId}
courseId={courseId}
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
lmsEndpointUrl={getConfig().LMS_BASE_URL}
onCancel={closeVideoSelectorModal}
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
/>
</div>
</StandardModal>
<StandardModal
title={intl.formatMessage(messages.blockEditorModalTitle)}
isOpen={isXBlockEditorModalOpen}
Expand Down
2 changes: 1 addition & 1 deletion src/course-unit/xblock-container-iframe/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type UseMessageHandlersTypes = {
setIframeOffset: (height: number) => void;
handleDeleteXBlock: (usageId: string) => void;
handleScrollToXBlock: (scrollOffset: number) => void;
handleDuplicateXBlock: (blockType: string, usageId: string) => void;
handleDuplicateXBlock: (usageId: string) => void;
handleEditXBlock: (blockType: string, usageId: string) => void;
handleManageXBlockAccess: (usageId: string) => void;
handleShowLegacyEditXBlockModal: (id: string) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const useMessageHandlers = ({
[messageTypes.copyXBlock]: ({ usageId }) => copyToClipboard(usageId),
[messageTypes.deleteXBlock]: ({ usageId }) => handleDeleteXBlock(usageId),
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => handleEditXBlock(blockType, usageId),
[messageTypes.duplicateXBlock]: ({ blockType, usageId }) => handleDuplicateXBlock(blockType, usageId),
[messageTypes.duplicateXBlock]: ({ usageId }) => handleDuplicateXBlock(usageId),
[messageTypes.manageXBlockAccess]: ({ usageId }) => handleManageXBlockAccess(usageId),
[messageTypes.scrollToXBlock]: debounce(({ scrollOffset }) => handleScrollToXBlock(scrollOffset), 1000),
[messageTypes.toggleCourseXBlockDropdown]: ({
Expand Down
46 changes: 33 additions & 13 deletions src/course-unit/xblock-container-iframe/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {
} from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useToggle, Sheet, StandardModal } from '@openedx/paragon';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';

import {
hideProcessingNotification,
Expand All @@ -14,9 +13,9 @@ import {
import DeleteModal from '../../generic/delete-modal/DeleteModal';
import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
import ModalIframe from '../../generic/modal-iframe';
import { getWaffleFlags } from '../../data/selectors';
import { IFRAME_FEATURE_POLICY } from '../../constants';
import ContentTagsDrawer from '../../content-tags-drawer/ContentTagsDrawer';
import supportedEditors from '../../editors/supportedEditors';
import { useIframe } from '../../generic/hooks/context/hooks';
import {
fetchCourseSectionVerticalData,
Expand All @@ -36,19 +35,21 @@ import messages from './messages';
import { useIframeBehavior } from '../../generic/hooks/useIframeBehavior';
import { useIframeContent } from '../../generic/hooks/useIframeContent';
import { useIframeMessages } from '../../generic/hooks/useIframeMessages';
import VideoSelectorPage from '../../editors/VideoSelectorPage';
import EditorPage from '../../editors/EditorPage';

const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType,
}) => {
const intl = useIntl();
const dispatch = useDispatch();
const navigate = useNavigate();

const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
const [isVideoSelectorModalOpen, showVideoSelectorModal, closeVideoSelectorModal] = useToggle();
const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle();
const [blockType, setBlockType] = useState<string>('');
const { useVideoGalleryFlow } = useSelector(getWaffleFlags);
const [newBlockId, setNewBlockId] = useState<string>('');
const [accessManagedXBlockData, setAccessManagedXBlockData] = useState<AccessManagedXBlockDataTypes | {}>({});
const [iframeOffset, setIframeOffset] = useState(0);
Expand All @@ -71,24 +72,25 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({

const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
closeXBlockEditorModal();
closeVideoSelectorModal();
sendMessageToIframe(messageTypes.refreshXBlock, null);
}, [closeXBlockEditorModal, sendMessageToIframe]);
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);

const handleEditXBlock = useCallback((type: string, id: string) => {
setBlockType(type);
setNewBlockId(id);
showXBlockEditorModal();
}, [showXBlockEditorModal]);
if (type === 'video' && useVideoGalleryFlow) {
showVideoSelectorModal();
} else {
showXBlockEditorModal();
}
}, [showVideoSelectorModal, showXBlockEditorModal]);

const handleDuplicateXBlock = useCallback(
(type: string, usageId: string) => {
(usageId: string) => {
unitXBlockActions.handleDuplicate(usageId);
if (supportedEditors[type]) {
// istanbul ignore next
handleEditXBlock(type, usageId);
}
},
[unitXBlockActions, courseId, navigate],
[unitXBlockActions, courseId],
);

const handleDeleteXBlock = (usageId: string) => {
Expand Down Expand Up @@ -198,6 +200,24 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
close={closeDeleteModal}
onDeleteSubmit={onDeleteSubmit}
/>
<StandardModal
title={intl.formatMessage(messages.videoPickerModalTitle)}
isOpen={isVideoSelectorModalOpen}
onClose={closeVideoSelectorModal}
isOverflowVisible={false}
size="xl"
>
<div className="selector-page">
<VideoSelectorPage
blockId={newBlockId}
courseId={courseId}
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
lmsEndpointUrl={getConfig().LMS_BASE_URL}
onCancel={closeVideoSelectorModal}
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
/>
</div>
</StandardModal>
<StandardModal
title={intl.formatMessage(messages.blockEditorModalTitle)}
isOpen={isXBlockEditorModalOpen}
Expand Down
1 change: 1 addition & 0 deletions src/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const slice = createSlice({
useNewCertificatesPage: true,
useNewTextbooksPage: true,
useNewGroupConfigurationsPage: true,
useVideoGalleryFlow: false,
},
},
reducers: {
Expand Down
6 changes: 5 additions & 1 deletion src/editors/VideoSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const VideoSelector = ({
learningContextId,
lmsEndpointUrl,
studioEndpointUrl,
returnFunction,
onCancel,
}) => {
const dispatch = useDispatch();
const loading = hooks.useInitializeApp({
Expand All @@ -26,7 +28,7 @@ const VideoSelector = ({
return null;
}
return (
<VideoGallery />
<VideoGallery returnFunction={returnFunction} onCancel={onCancel} />
);
};

Expand All @@ -35,6 +37,8 @@ VideoSelector.propTypes = {
learningContextId: PropTypes.string.isRequired,
lmsEndpointUrl: PropTypes.string.isRequired,
studioEndpointUrl: PropTypes.string.isRequired,
returnFunction: PropTypes.func,
onCancel: PropTypes.func,
};

export default VideoSelector;
6 changes: 6 additions & 0 deletions src/editors/VideoSelectorPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const VideoSelectorPage = ({
courseId,
lmsEndpointUrl,
studioEndpointUrl,
returnFunction,
onCancel,
}) => (
<Provider store={store}>
<ErrorBoundary
Expand All @@ -24,6 +26,8 @@ const VideoSelectorPage = ({
learningContextId: courseId,
lmsEndpointUrl,
studioEndpointUrl,
returnFunction,
onCancel,
}}
/>
</ErrorBoundary>
Expand All @@ -42,6 +46,8 @@ VideoSelectorPage.propTypes = {
courseId: PropTypes.string,
lmsEndpointUrl: PropTypes.string,
studioEndpointUrl: PropTypes.string,
returnFunction: PropTypes.func,
onCancel: PropTypes.func,
};

export default VideoSelectorPage;
18 changes: 14 additions & 4 deletions src/editors/containers/VideoGallery/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const filterList = ({
export const useVideoListProps = ({
searchSortProps,
videos,
returnFunction,
}) => {
const [highlighted, setHighlighted] = React.useState(null);
const [
Expand Down Expand Up @@ -128,7 +129,10 @@ export const useVideoListProps = ({
},
selectBtnProps: {
onClick: () => {
if (highlighted) {
/* istanbul ignore next */
if (returnFunction) {
returnFunction()();
} else if (highlighted) {
navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoId=${highlighted}`);
} else {
setShowSelectVideoError(true);
Expand All @@ -138,10 +142,15 @@ export const useVideoListProps = ({
};
};

export const useVideoUploadHandler = ({ replace }) => {
export const useVideoUploadHandler = ({ replace, uploadHandler }) => {
const learningContextId = useSelector(selectors.app.learningContextId);
const blockId = useSelector(selectors.app.blockId);
const path = `/course/${learningContextId}/editor/video_upload/${blockId}`;
if (uploadHandler) {
return () => {
uploadHandler();
};
}
if (replace) {
return () => window.location.replace(path);
}
Expand Down Expand Up @@ -191,19 +200,20 @@ export const getstatusBadgeVariant = ({ status }) => {

export const getStatusMessage = ({ status }) => Object.values(filterMessages).find((m) => m.defaultMessage === status);

export const useVideoProps = ({ videos }) => {
export const useVideoProps = ({ videos, uploadHandler, returnFunction }) => {
const searchSortProps = useSearchAndSortProps();
const videoList = useVideoListProps({
searchSortProps,
videos,
returnFunction,
});
const {
galleryError,
galleryProps,
inputError,
selectBtnProps,
} = videoList;
const fileInput = { click: useVideoUploadHandler({ replace: false }) };
const fileInput = { click: useVideoUploadHandler({ replace: false, uploadHandler }) };

return {
galleryError,
Expand Down
Loading