Skip to content

Commit 4b4575e

Browse files
committed
fix: do open editor of new xblock when duplicating (#1887)
Fixes bug where after duplicating an xblock, the editor modal of the old xblock is being open instead of the new copied xblock.
1 parent 7c9f259 commit 4b4575e

File tree

15 files changed

+573
-324
lines changed

15 files changed

+573
-324
lines changed

src/course-unit/CourseUnit.test.jsx

Lines changed: 345 additions & 277 deletions
Large diffs are not rendered by default.

src/course-unit/add-component/AddComponent.jsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '@openedx/paragon';
99

1010
import { getCourseSectionVertical } from '../data/selectors';
11+
import { getWaffleFlags } from '../../data/selectors';
1112
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
1213
import ComponentModalView from './add-component-modals/ComponentModalView';
1314
import AddComponentButton from './add-component-btn';
@@ -16,6 +17,7 @@ import { ComponentPicker } from '../../library-authoring/component-picker';
1617
import { messageTypes } from '../constants';
1718
import { useIframe } from '../../generic/hooks/context/hooks';
1819
import { useEventListener } from '../../generic/hooks';
20+
import VideoSelectorPage from '../../editors/VideoSelectorPage';
1921
import EditorPage from '../../editors/EditorPage';
2022

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

3740
const [blockType, setBlockType] = useState(null);
@@ -41,6 +44,7 @@ const AddComponent = ({
4144
const [selectedComponents, setSelectedComponents] = useState([]);
4245
const [usageId, setUsageId] = useState(null);
4346
const { sendMessageToIframe } = useIframe();
47+
const { useVideoGalleryFlow } = useSelector(getWaffleFlags);
4448

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

6266
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
6367
closeXBlockEditorModal();
68+
closeVideoSelectorModal();
6469
sendMessageToIframe(messageTypes.refreshXBlock, null);
65-
}, [closeXBlockEditorModal, sendMessageToIframe]);
70+
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);
6671

6772
const handleLibraryV2Selection = useCallback((selection) => {
6873
handleCreateNewCourseXBlock({
@@ -80,7 +85,6 @@ const AddComponent = ({
8085
case COMPONENT_TYPES.dragAndDrop:
8186
handleCreateNewCourseXBlock({ type, parentLocator: blockId });
8287
break;
83-
case COMPONENT_TYPES.video:
8488
case COMPONENT_TYPES.problem:
8589
handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
8690
setCourseId(courseKey);
@@ -89,6 +93,21 @@ const AddComponent = ({
8993
showXBlockEditorModal();
9094
});
9195
break;
96+
case COMPONENT_TYPES.video:
97+
handleCreateNewCourseXBlock(
98+
{ type, parentLocator: blockId },
99+
/* istanbul ignore next */ ({ courseKey, locator }) => {
100+
setCourseId(courseKey);
101+
setBlockType(type);
102+
setNewBlockId(locator);
103+
if (useVideoGalleryFlow) {
104+
showVideoSelectorModal();
105+
} else {
106+
showXBlockEditorModal();
107+
}
108+
},
109+
);
110+
break;
92111
// TODO: The library functional will be a bit different of current legacy (CMS)
93112
// behaviour and this ticket is on hold (blocked by other development team).
94113
case COMPONENT_TYPES.library:
@@ -215,6 +234,24 @@ const AddComponent = ({
215234
onChangeComponentSelection={setSelectedComponents}
216235
/>
217236
</StandardModal>
237+
<StandardModal
238+
title={intl.formatMessage(messages.videoPickerModalTitle)}
239+
isOpen={isVideoSelectorModalOpen}
240+
onClose={closeVideoSelectorModal}
241+
isOverflowVisible={false}
242+
size="xl"
243+
>
244+
<div className="selector-page">
245+
<VideoSelectorPage
246+
blockId={newBlockId}
247+
courseId={courseId}
248+
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
249+
lmsEndpointUrl={getConfig().LMS_BASE_URL}
250+
onCancel={closeVideoSelectorModal}
251+
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
252+
/>
253+
</div>
254+
</StandardModal>
218255
<StandardModal
219256
title={intl.formatMessage(messages.blockEditorModalTitle)}
220257
isOpen={isXBlockEditorModalOpen}

src/course-unit/xblock-container-iframe/hooks/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export type UseMessageHandlersTypes = {
44
setIframeOffset: (height: number) => void;
55
handleDeleteXBlock: (usageId: string) => void;
66
handleScrollToXBlock: (scrollOffset: number) => void;
7-
handleDuplicateXBlock: (blockType: string, usageId: string) => void;
7+
handleDuplicateXBlock: (usageId: string) => void;
88
handleEditXBlock: (blockType: string, usageId: string) => void;
99
handleManageXBlockAccess: (usageId: string) => void;
1010
handleShowLegacyEditXBlockModal: (id: string) => void;

src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const useMessageHandlers = ({
3737
[messageTypes.copyXBlock]: ({ usageId }) => copyToClipboard(usageId),
3838
[messageTypes.deleteXBlock]: ({ usageId }) => handleDeleteXBlock(usageId),
3939
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => handleEditXBlock(blockType, usageId),
40-
[messageTypes.duplicateXBlock]: ({ blockType, usageId }) => handleDuplicateXBlock(blockType, usageId),
40+
[messageTypes.duplicateXBlock]: ({ usageId }) => handleDuplicateXBlock(usageId),
4141
[messageTypes.manageXBlockAccess]: ({ usageId }) => handleManageXBlockAccess(usageId),
4242
[messageTypes.scrollToXBlock]: debounce(({ scrollOffset }) => handleScrollToXBlock(scrollOffset), 1000),
4343
[messageTypes.toggleCourseXBlockDropdown]: ({

src/course-unit/xblock-container-iframe/index.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import {
44
} from 'react';
55
import { useIntl } from '@edx/frontend-platform/i18n';
66
import { useToggle, Sheet, StandardModal } from '@openedx/paragon';
7-
import { useDispatch } from 'react-redux';
8-
import { useNavigate } from 'react-router-dom';
7+
import { useDispatch, useSelector } from 'react-redux';
98

109
import {
1110
hideProcessingNotification,
@@ -14,9 +13,9 @@ import {
1413
import DeleteModal from '../../generic/delete-modal/DeleteModal';
1514
import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
1615
import ModalIframe from '../../generic/modal-iframe';
16+
import { getWaffleFlags } from '../../data/selectors';
1717
import { IFRAME_FEATURE_POLICY } from '../../constants';
1818
import ContentTagsDrawer from '../../content-tags-drawer/ContentTagsDrawer';
19-
import supportedEditors from '../../editors/supportedEditors';
2019
import { useIframe } from '../../generic/hooks/context/hooks';
2120
import {
2221
fetchCourseSectionVerticalData,
@@ -36,19 +35,21 @@ import messages from './messages';
3635
import { useIframeBehavior } from '../../generic/hooks/useIframeBehavior';
3736
import { useIframeContent } from '../../generic/hooks/useIframeContent';
3837
import { useIframeMessages } from '../../generic/hooks/useIframeMessages';
38+
import VideoSelectorPage from '../../editors/VideoSelectorPage';
3939
import EditorPage from '../../editors/EditorPage';
4040

4141
const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
4242
courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType,
4343
}) => {
4444
const intl = useIntl();
4545
const dispatch = useDispatch();
46-
const navigate = useNavigate();
4746

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

7273
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
7374
closeXBlockEditorModal();
75+
closeVideoSelectorModal();
7476
sendMessageToIframe(messageTypes.refreshXBlock, null);
75-
}, [closeXBlockEditorModal, sendMessageToIframe]);
77+
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);
7678

7779
const handleEditXBlock = useCallback((type: string, id: string) => {
7880
setBlockType(type);
7981
setNewBlockId(id);
80-
showXBlockEditorModal();
81-
}, [showXBlockEditorModal]);
82+
if (type === 'video' && useVideoGalleryFlow) {
83+
showVideoSelectorModal();
84+
} else {
85+
showXBlockEditorModal();
86+
}
87+
}, [showVideoSelectorModal, showXBlockEditorModal]);
8288

8389
const handleDuplicateXBlock = useCallback(
84-
(type: string, usageId: string) => {
90+
(usageId: string) => {
8591
unitXBlockActions.handleDuplicate(usageId);
86-
if (supportedEditors[type]) {
87-
// istanbul ignore next
88-
handleEditXBlock(type, usageId);
89-
}
9092
},
91-
[unitXBlockActions, courseId, navigate],
93+
[unitXBlockActions, courseId],
9294
);
9395

9496
const handleDeleteXBlock = (usageId: string) => {
@@ -198,6 +200,24 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
198200
close={closeDeleteModal}
199201
onDeleteSubmit={onDeleteSubmit}
200202
/>
203+
<StandardModal
204+
title={intl.formatMessage(messages.videoPickerModalTitle)}
205+
isOpen={isVideoSelectorModalOpen}
206+
onClose={closeVideoSelectorModal}
207+
isOverflowVisible={false}
208+
size="xl"
209+
>
210+
<div className="selector-page">
211+
<VideoSelectorPage
212+
blockId={newBlockId}
213+
courseId={courseId}
214+
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
215+
lmsEndpointUrl={getConfig().LMS_BASE_URL}
216+
onCancel={closeVideoSelectorModal}
217+
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
218+
/>
219+
</div>
220+
</StandardModal>
201221
<StandardModal
202222
title={intl.formatMessage(messages.blockEditorModalTitle)}
203223
isOpen={isXBlockEditorModalOpen}

src/data/slice.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const slice = createSlice({
2626
useNewCertificatesPage: true,
2727
useNewTextbooksPage: true,
2828
useNewGroupConfigurationsPage: true,
29+
useVideoGalleryFlow: false,
2930
},
3031
},
3132
reducers: {

src/editors/VideoSelector.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const VideoSelector = ({
99
learningContextId,
1010
lmsEndpointUrl,
1111
studioEndpointUrl,
12+
returnFunction,
13+
onCancel,
1214
}) => {
1315
const dispatch = useDispatch();
1416
const loading = hooks.useInitializeApp({
@@ -26,7 +28,7 @@ const VideoSelector = ({
2628
return null;
2729
}
2830
return (
29-
<VideoGallery />
31+
<VideoGallery returnFunction={returnFunction} onCancel={onCancel} />
3032
);
3133
};
3234

@@ -35,6 +37,8 @@ VideoSelector.propTypes = {
3537
learningContextId: PropTypes.string.isRequired,
3638
lmsEndpointUrl: PropTypes.string.isRequired,
3739
studioEndpointUrl: PropTypes.string.isRequired,
40+
returnFunction: PropTypes.func,
41+
onCancel: PropTypes.func,
3842
};
3943

4044
export default VideoSelector;

src/editors/VideoSelectorPage.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const VideoSelectorPage = ({
1010
courseId,
1111
lmsEndpointUrl,
1212
studioEndpointUrl,
13+
returnFunction,
14+
onCancel,
1315
}) => (
1416
<Provider store={store}>
1517
<ErrorBoundary
@@ -24,6 +26,8 @@ const VideoSelectorPage = ({
2426
learningContextId: courseId,
2527
lmsEndpointUrl,
2628
studioEndpointUrl,
29+
returnFunction,
30+
onCancel,
2731
}}
2832
/>
2933
</ErrorBoundary>
@@ -42,6 +46,8 @@ VideoSelectorPage.propTypes = {
4246
courseId: PropTypes.string,
4347
lmsEndpointUrl: PropTypes.string,
4448
studioEndpointUrl: PropTypes.string,
49+
returnFunction: PropTypes.func,
50+
onCancel: PropTypes.func,
4551
};
4652

4753
export default VideoSelectorPage;

src/editors/containers/VideoGallery/hooks.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const filterList = ({
8686
export const useVideoListProps = ({
8787
searchSortProps,
8888
videos,
89+
returnFunction,
8990
}) => {
9091
const [highlighted, setHighlighted] = React.useState(null);
9192
const [
@@ -128,7 +129,10 @@ export const useVideoListProps = ({
128129
},
129130
selectBtnProps: {
130131
onClick: () => {
131-
if (highlighted) {
132+
/* istanbul ignore next */
133+
if (returnFunction) {
134+
returnFunction()();
135+
} else if (highlighted) {
132136
navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoId=${highlighted}`);
133137
} else {
134138
setShowSelectVideoError(true);
@@ -138,10 +142,15 @@ export const useVideoListProps = ({
138142
};
139143
};
140144

141-
export const useVideoUploadHandler = ({ replace }) => {
145+
export const useVideoUploadHandler = ({ replace, uploadHandler }) => {
142146
const learningContextId = useSelector(selectors.app.learningContextId);
143147
const blockId = useSelector(selectors.app.blockId);
144148
const path = `/course/${learningContextId}/editor/video_upload/${blockId}`;
149+
if (uploadHandler) {
150+
return () => {
151+
uploadHandler();
152+
};
153+
}
145154
if (replace) {
146155
return () => window.location.replace(path);
147156
}
@@ -191,19 +200,20 @@ export const getstatusBadgeVariant = ({ status }) => {
191200

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

194-
export const useVideoProps = ({ videos }) => {
203+
export const useVideoProps = ({ videos, uploadHandler, returnFunction }) => {
195204
const searchSortProps = useSearchAndSortProps();
196205
const videoList = useVideoListProps({
197206
searchSortProps,
198207
videos,
208+
returnFunction,
199209
});
200210
const {
201211
galleryError,
202212
galleryProps,
203213
inputError,
204214
selectBtnProps,
205215
} = videoList;
206-
const fileInput = { click: useVideoUploadHandler({ replace: false }) };
216+
const fileInput = { click: useVideoUploadHandler({ replace: false, uploadHandler }) };
207217

208218
return {
209219
galleryError,

0 commit comments

Comments
 (0)