diff --git a/src/editors/containers/VideoEditor/components/VideoEditorModal.jsx b/src/editors/containers/VideoEditor/components/VideoEditorModal.jsx index 836fe2d39..ab201e9d5 100644 --- a/src/editors/containers/VideoEditor/components/VideoEditorModal.jsx +++ b/src/editors/containers/VideoEditor/components/VideoEditorModal.jsx @@ -12,9 +12,9 @@ export const { } = appHooks; export const hooks = { - initialize: (dispatch) => { + initialize: (dispatch, selectedVideoId) => { React.useEffect(() => { - dispatch(thunkActions.video.loadVideoData()); + dispatch(thunkActions.video.loadVideoData(selectedVideoId)); }, []); }, returnToGallery: () => { @@ -29,10 +29,11 @@ const VideoEditorModal = ({ isOpen, }) => { const dispatch = useDispatch(); - module.hooks.initialize(dispatch); const searchParams = new URLSearchParams(document.location.search); - const showReturn = searchParams.get('return') !== null; + const selectedVideoId = searchParams.get('selectedVideoId'); + const showReturn = selectedVideoId != null; const onReturn = module.hooks.returnToGallery(); + module.hooks.initialize(dispatch, selectedVideoId); return ( { selectBtnProps: { onClick: () => { if (highlighted) { - // TODO save the metadata of the video on the block to fill it into the cideo editor - navigateTo(`/course/${learningContextId}/editor/video/${blockId}?return=true`); + navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoId=${highlighted}`); } else { setShowSelectVideoError(true); } diff --git a/src/editors/data/redux/thunkActions/video.js b/src/editors/data/redux/thunkActions/video.js index eb9c64805..6daa21afa 100644 --- a/src/editors/data/redux/thunkActions/video.js +++ b/src/editors/data/redux/thunkActions/video.js @@ -1,16 +1,27 @@ /* eslint-disable import/no-cycle */ import { actions, selectors } from '..'; -import { removeItemOnce } from '../../../utils'; +import { formatDuration, removeItemOnce } from '../../../utils'; import * as requests from './requests'; import * as module from './video'; import { valueFromDuration } from '../../../containers/VideoEditor/components/VideoSettingsModal/components/DurationWidget/hooks'; import { parseYoutubeId } from '../../services/cms/api'; -export const loadVideoData = () => (dispatch, getState) => { +export const loadVideoData = (selectedVideoId) => (dispatch, getState) => { const state = getState(); const blockValueData = state.app.blockValue.data; const rawVideoData = blockValueData.metadata ? blockValueData.metadata : {}; const courseData = state.app.courseDetails.data ? state.app.courseDetails.data : {}; + if (Object.keys(rawVideoData).length === 0 && selectedVideoId !== null) { + const rawVideos = Object.values(selectors.app.videos(state)); + const selectedVideo = rawVideos.find(video => video.edx_video_id === selectedVideoId); + // TODO it's missing laod the transcripts + rawVideoData = { + edx_video_id: selectedVideo.edx_video_id, + thumbnail: selectedVideo.course_video_image_url, + end_time: formatDuration(selectedVideo.duration), + duration: selectedVideo.duration, + }; + } const studioView = state.app.studioView?.data?.html; const { videoId, @@ -45,7 +56,7 @@ export const loadVideoData = () => (dispatch, getState) => { duration: { // TODO duration is not always sent so they should be calculated. startTime: valueFromDuration(rawVideoData.start_time || '00:00:00'), stopTime: valueFromDuration(rawVideoData.end_time || '00:00:00'), - total: 0, // TODO can we get total duration? if not, probably dropping from widget + total: rawVideoData.duration || 0, // TODO can we get total duration? if not, probably dropping from widget }, handout: rawVideoData.handout, licenseType, diff --git a/src/editors/data/redux/thunkActions/video.test.js b/src/editors/data/redux/thunkActions/video.test.js index 29c7796af..32d9b06ad 100644 --- a/src/editors/data/redux/thunkActions/video.test.js +++ b/src/editors/data/redux/thunkActions/video.test.js @@ -12,6 +12,7 @@ jest.mock('..', () => ({ selectors: { app: { courseDetails: (state) => ({ courseDetails: state }), + videos: (state) => ({ videos: state.app.videos }), }, video: { videoId: (state) => ({ videoId: state }), @@ -34,6 +35,7 @@ jest.mock('./requests', () => ({ })); jest.mock('../../../utils', () => ({ + ...jest.requireActual('../../../utils'), removeItemOnce: (args) => (args), })); @@ -56,6 +58,7 @@ const mockVideoFeatures = { videoSharingEnabled: 'soMEbOolEAn', }, }; +const mockSelectedVideoId = 'ThisIsAVideoId'; const testMetadata = { download_track: 'dOWNlOAdTraCK', @@ -80,6 +83,11 @@ const testState = { originalThumbnail: null, videoId: 'soMEvIDEo', }; +const testVideosState = { + edx_video_id: mockSelectedVideoId, + thumbnail: 'thumbnail', + duration: 60, +}; const testUpload = { transcripts: ['la', 'en'] }; const testReplaceUpload = { file: mockFile, @@ -130,25 +138,37 @@ describe('video thunkActions', () => { jest.spyOn(thunkActions, thunkActionsKeys.parseTranscripts).mockReturnValue( testMetadata.transcripts, ); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it('dispatches fetchVideoFeatures action', () => { thunkActions.loadVideoData()(dispatch, getState); [ [dispatchedLoad], [dispatchedAction1], [dispatchedAction2], ] = dispatch.mock.calls; - }); - afterEach(() => { - jest.restoreAllMocks(); - }); - it('dispatches fetchVideoFeatures action', () => { expect(dispatchedLoad).not.toEqual(undefined); expect(dispatchedAction1.fetchVideoFeatures).not.toEqual(undefined); }); it('dispatches checkTranscriptsForImport action', () => { + thunkActions.loadVideoData()(dispatch, getState); + [ + [dispatchedLoad], + [dispatchedAction1], + [dispatchedAction2], + ] = dispatch.mock.calls; expect(dispatchedLoad).not.toEqual(undefined); expect(dispatchedAction2.checkTranscriptsForImport).not.toEqual(undefined); }); it('dispatches actions.video.load', () => { + thunkActions.loadVideoData()(dispatch, getState); + [ + [dispatchedLoad], + [dispatchedAction1], + [dispatchedAction2], + ] = dispatch.mock.calls; expect(dispatchedLoad.load).toEqual({ videoSource: 'videOsOurce', videoId: 'videOiD', @@ -186,7 +206,62 @@ describe('video thunkActions', () => { thumbnail: testMetadata.thumbnail, }); }); + it('dispatches actions.video.load with selectedVideoId', () => { + getState = jest.fn(() => ({ + app: { + blockId: 'soMEBloCk', + studioEndpointUrl: 'soMEeNDPoiNT', + blockValue: { data: { metadata: {} } }, + courseDetails: { data: { license: null } }, + studioView: { data: { html: 'sOMeHTml' } }, + videos: testVideosState, + }, + })); + thunkActions.loadVideoData(mockSelectedVideoId)(dispatch, getState); + [ + [dispatchedLoad], + [dispatchedAction1], + [dispatchedAction2], + ] = dispatch.mock.calls; + expect(dispatchedLoad.load).toEqual({ + videoSource: 'videOsOurce', + videoId: 'videOiD', + fallbackVideos: 'fALLbACKvIDeos', + allowVideoDownloads: undefined, + transcripts: testMetadata.transcripts, + allowTranscriptDownloads: undefined, + allowVideoSharing: undefined, + showTranscriptByDefault: undefined, + duration: { + startTime: testMetadata.start_time, + stopTime: testVideosState.duration * 1000, + total: testVideosState.duration, + }, + handout: undefined, + licenseType: 'liCENSEtyPe', + licenseDetails: { + attribution: true, + noncommercial: true, + noDerivatives: true, + shareAlike: false, + }, + courseLicenseType: 'liCENSEtyPe', + courseLicenseDetails: { + attribution: true, + noncommercial: true, + noDerivatives: true, + shareAlike: false, + }, + thumbnail: undefined, + }); + }); it('dispatches actions.video.updateField on success', () => { + thunkActions.loadVideoData()(dispatch, getState); + [ + [dispatchedLoad], + [dispatchedAction1], + [dispatchedAction2], + ] = dispatch.mock.calls; dispatch.mockClear(); dispatchedAction1.fetchVideoFeatures.onSuccess(mockVideoFeatures); expect(dispatch).toHaveBeenCalledWith(actions.video.updateField({