feat: Add spinner to video element to load

Signed-off-by: Farhaan Bukhsh <farhaan@opencraft.com>
This commit is contained in:
Farhaan Bukhsh
2023-06-17 06:15:38 +05:30
parent f3c4669604
commit a86b844208
4 changed files with 155 additions and 77 deletions

View File

@@ -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,
};

View File

@@ -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));

View 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;

View File

@@ -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,
};