feat: Add spinner to video element to load
Signed-off-by: Farhaan Bukhsh <farhaan@opencraft.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import * as requests from '../../data/redux/thunkActions/requests';
|
||||
import React from 'react';
|
||||
import * as module from './hooks';
|
||||
import { selectors } from '../../data/redux';
|
||||
import store from '../../data/store';
|
||||
@@ -8,59 +8,45 @@ export const {
|
||||
navigateTo,
|
||||
} = appHooks;
|
||||
|
||||
export const uploadVideo = async ({ dispatch, supportedFiles }) => {
|
||||
const data = { files: [] };
|
||||
supportedFiles.forEach((file) => {
|
||||
data.files.push({
|
||||
file_name: file.name,
|
||||
content_type: file.type,
|
||||
});
|
||||
});
|
||||
const onFileUploadedHook = module.onFileUploaded();
|
||||
dispatch(await requests.uploadVideo({
|
||||
data,
|
||||
onSuccess: async (response) => {
|
||||
const { files } = response.data;
|
||||
await Promise.all(Object.values(files).map(async (fileObj) => {
|
||||
const fileName = fileObj.file_name;
|
||||
const edxVideoId = fileObj.edx_video_id;
|
||||
const uploadUrl = fileObj.upload_url;
|
||||
const uploadFile = supportedFiles.find((file) => file.name === fileName);
|
||||
|
||||
if (!uploadFile) {
|
||||
console.error(`Could not find file object with name "${fileName}" in supportedFiles array.`);
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('uploaded-file', uploadFile);
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
.then(() => onFileUploadedHook(edxVideoId))
|
||||
.catch((error) => console.error('Error uploading file:', error));
|
||||
}));
|
||||
},
|
||||
}));
|
||||
export const state = {
|
||||
loading: (val) => React.useState(val),
|
||||
errorMessage: (val) => React.useState(val),
|
||||
textInputValue: (val) => React.useState(val),
|
||||
};
|
||||
|
||||
export const onFileUploaded = () => {
|
||||
const state = store.getState();
|
||||
const learningContextId = selectors.app.learningContextId(state);
|
||||
const blockId = selectors.app.blockId(state);
|
||||
return (edxVideoId) => navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoId=${edxVideoId}`);
|
||||
export const uploadEditor = () => {
|
||||
const [loading, setLoading] = module.state.loading(false);
|
||||
const [errorMessage, setErrorMessage] = module.state.errorMessage(null);
|
||||
return {
|
||||
loading,
|
||||
setLoading,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
};
|
||||
};
|
||||
|
||||
export const onUrlUploaded = () => {
|
||||
const state = store.getState();
|
||||
const learningContextId = selectors.app.learningContextId(state);
|
||||
const blockId = selectors.app.blockId(state);
|
||||
export const uploader = () => {
|
||||
const [textInputValue, settextInputValue] = module.state.textInputValue('');
|
||||
return {
|
||||
textInputValue,
|
||||
settextInputValue,
|
||||
};
|
||||
};
|
||||
|
||||
export const postUploadRedirect = (storeState) => {
|
||||
const learningContextId = selectors.app.learningContextId(storeState);
|
||||
const blockId = selectors.app.blockId(storeState);
|
||||
return (videoUrl) => navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoUrl=${videoUrl}`);
|
||||
};
|
||||
|
||||
export default {
|
||||
uploadVideo,
|
||||
export const onVideoUpload = () => {
|
||||
const storeState = store.getState();
|
||||
return module.postUploadRedirect(storeState);
|
||||
};
|
||||
|
||||
export default {
|
||||
postUploadRedirect,
|
||||
uploadEditor,
|
||||
uploader,
|
||||
onVideoUpload,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, IconButton } from '@edx/paragon';
|
||||
import './index.scss';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Icon, IconButton, Spinner } from '@edx/paragon';
|
||||
import { ArrowForward, Close, FileUpload } from '@edx/paragon/icons';
|
||||
import { connect } from 'react-redux';
|
||||
import { thunkActions } from '../../data/redux';
|
||||
import './index.scss';
|
||||
import * as hooks from './hooks';
|
||||
import messages from '../../messages';
|
||||
import messages from './messages';
|
||||
import * as editorHooks from '../EditorContainer/hooks';
|
||||
|
||||
export const VideoUploader = ({ onUpload, errorMessage }) => {
|
||||
const [, setUploadedFile] = useState();
|
||||
const [textInputValue, setTextInputValue] = useState('');
|
||||
const onUrlUpdatedHook = hooks.onUrlUploaded();
|
||||
const { textInputValue, setTextInputValue } = hooks.uploader();
|
||||
const onURLUpload = hooks.onVideoUpload();
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
accept: 'video/*',
|
||||
@@ -21,7 +21,6 @@ export const VideoUploader = ({ onUpload, errorMessage }) => {
|
||||
onDrop: (acceptedFiles) => {
|
||||
if (acceptedFiles.length > 0) {
|
||||
const uploadfile = acceptedFiles[0];
|
||||
setUploadedFile(uploadfile);
|
||||
onUpload(uploadfile);
|
||||
}
|
||||
},
|
||||
@@ -32,7 +31,7 @@ export const VideoUploader = ({ onUpload, errorMessage }) => {
|
||||
};
|
||||
|
||||
const handleSaveButtonClick = () => {
|
||||
onUrlUpdatedHook(textInputValue);
|
||||
onURLUpload(textInputValue);
|
||||
};
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -85,12 +84,21 @@ VideoUploader.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const VideoUploadEditor = ({ intl, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [errorMessage, setErrorMessage] = useState(null);
|
||||
const handleCancel = () => {
|
||||
editorHooks.handleCancel({ onClose });
|
||||
};
|
||||
const VideoUploadEditor = (
|
||||
{
|
||||
intl,
|
||||
onClose,
|
||||
// Redux states
|
||||
uploadVideo,
|
||||
},
|
||||
) => {
|
||||
const {
|
||||
loading,
|
||||
setLoading,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
} = hooks.uploadEditor();
|
||||
const handleCancel = editorHooks.handleCancel({ onClose });
|
||||
|
||||
const handleDrop = (file) => {
|
||||
if (!file) {
|
||||
@@ -113,24 +121,39 @@ const VideoUploadEditor = ({ intl, onClose }) => {
|
||||
const newFile = new File([file], file.name, { type });
|
||||
|
||||
if (supportedFormats.includes(ext)) {
|
||||
hooks.uploadVideo({ dispatch, supportedFiles: [newFile] });
|
||||
uploadVideo({
|
||||
supportedFiles: [newFile],
|
||||
setLoadSpinner: setLoading,
|
||||
postUploadRedirect: hooks.onVideoUpload(),
|
||||
});
|
||||
} else {
|
||||
const errorMsg = 'Video must be an MP4 or MOV file';
|
||||
console.log(errorMsg);
|
||||
setErrorMessage(errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="marked-area">
|
||||
<div className="d-flex justify-content-end close-button-container">
|
||||
<IconButton
|
||||
src={Close}
|
||||
iconAs={Icon}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
</div>
|
||||
<VideoUploader onUpload={handleDrop} errorMessage={errorMessage} intl={intl} />
|
||||
<div>
|
||||
{(!loading) ? (
|
||||
<div className="marked-area">
|
||||
<div className="d-flex justify-content-end close-button-container">
|
||||
<IconButton
|
||||
src={Close}
|
||||
iconAs={Icon}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
</div>
|
||||
<VideoUploader onUpload={handleDrop} errorMessage={errorMessage} intl={intl} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-6">
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="m-3"
|
||||
screenreadertext={intl.formatMessage(messages.spinnerScreenReaderText)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -138,6 +161,13 @@ const VideoUploadEditor = ({ intl, onClose }) => {
|
||||
VideoUploadEditor.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
uploadVideo: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(VideoUploadEditor);
|
||||
export const mapStateToProps = () => ({});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
uploadVideo: thunkActions.video.uploadVideo,
|
||||
};
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(VideoUploadEditor));
|
||||
|
||||
21
src/editors/containers/VideoUploadEditor/messages.js
Normal file
21
src/editors/containers/VideoUploadEditor/messages.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
spinnerScreenReaderText: {
|
||||
id: 'authoring.videoUpload.spinnerScreenReaderText',
|
||||
defaultMessage: 'loading',
|
||||
description: 'Loading message for spinner screenreader text.',
|
||||
},
|
||||
dropVideoFileHere: {
|
||||
defaultMessage: 'Drag and drop video here or click to upload',
|
||||
id: 'VideoUploadEditor.dropVideoFileHere',
|
||||
description: 'Display message for Drag and Drop zone',
|
||||
},
|
||||
info: {
|
||||
id: 'VideoUploadEditor.uploadInfo',
|
||||
defaultMessage: 'Upload MP4 or MOV files (5 GB max)',
|
||||
description: 'Info message for supported formats',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -370,6 +370,46 @@ export const replaceTranscript = ({ newFile, newFilename, language }) => (dispat
|
||||
}));
|
||||
};
|
||||
|
||||
export const uploadVideo = ({ supportedFiles, setLoadSpinner, postUploadRedirect }) => (dispatch) => {
|
||||
const data = { files: [] };
|
||||
setLoadSpinner(true);
|
||||
supportedFiles.forEach((file) => {
|
||||
data.files.push({
|
||||
file_name: file.name,
|
||||
content_type: file.type,
|
||||
});
|
||||
});
|
||||
dispatch(requests.uploadVideo({
|
||||
data,
|
||||
onSuccess: async (response) => {
|
||||
const { files } = response.data;
|
||||
await Promise.all(Object.values(files).map(async (fileObj) => {
|
||||
const fileName = fileObj.file_name;
|
||||
const edxVideoId = fileObj.edx_video_id;
|
||||
const uploadUrl = fileObj.upload_url;
|
||||
const uploadFile = supportedFiles.find((file) => file.name === fileName);
|
||||
|
||||
if (!uploadFile) {
|
||||
console.error(`Could not find file object with name "${fileName}" in supportedFiles array.`);
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('uploaded-file', uploadFile);
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
.then(() => postUploadRedirect(edxVideoId))
|
||||
.catch((error) => console.error('Error uploading file:', error));
|
||||
}));
|
||||
setLoadSpinner(false);
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
export default {
|
||||
loadVideoData,
|
||||
determineVideoSources,
|
||||
@@ -382,4 +422,5 @@ export default {
|
||||
updateTranscriptLanguage,
|
||||
replaceTranscript,
|
||||
uploadHandout,
|
||||
uploadVideo,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user