add progress bar for student media uploads
closes EVAL-1381 flag=none Test Plan: - ensure Assignment enhancements is turned on and notorious plugin is configured - have a course with a student - make a media upload Assignment - attempt to upload a video file as a student and ensure a progress bar is shown in the modal after clicking submit - video should upload and modal should close - media should display within canvas Change-Id: I61d582120022908b76279d89b6b3209fd9320be8 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/261074 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Adrian Packel <apackel@instructure.com> QA-Review: Syed Hussain <shussain@instructure.com> Product-Review: Syed Hussain <shussain@instructure.com>
This commit is contained in:
parent
edaa30c274
commit
f63cba7b55
|
@ -24,6 +24,8 @@ import {Heading} from '@instructure/ui-heading'
|
|||
import {Modal} from '@instructure/ui-modal'
|
||||
import {Tabs} from '@instructure/ui-tabs'
|
||||
import {px} from '@instructure/ui-utils'
|
||||
import {ProgressBar} from '@instructure/ui-progress'
|
||||
import {Text} from '@instructure/ui-text'
|
||||
|
||||
import {ACCEPTED_FILE_TYPES} from './acceptedMediaFileTypes'
|
||||
import LoadingIndicator from './shared/LoadingIndicator'
|
||||
|
@ -74,6 +76,8 @@ export default class UploadMedia extends React.Component {
|
|||
|
||||
this.state = {
|
||||
hasUploadedFile: false,
|
||||
uploading: false,
|
||||
progress: 0,
|
||||
selectedPanel: defaultSelectedPanel,
|
||||
computerFile: props.computerFile || null,
|
||||
subtitles: [],
|
||||
|
@ -109,8 +113,19 @@ export default class UploadMedia extends React.Component {
|
|||
}
|
||||
|
||||
uploadFile(file) {
|
||||
this.setState({uploading: true})
|
||||
this.props.onStartUpload && this.props.onStartUpload(file)
|
||||
saveMediaRecording(file, this.props.contextId, this.props.contextType, this.saveMediaCallback)
|
||||
saveMediaRecording(
|
||||
file,
|
||||
this.props.contextId,
|
||||
this.props.contextType,
|
||||
this.saveMediaCallback,
|
||||
this.onSaveMediaProgress
|
||||
)
|
||||
}
|
||||
|
||||
onSaveMediaProgress = progress => {
|
||||
this.setState({progress})
|
||||
}
|
||||
|
||||
saveMediaCallback = async (err, data) => {
|
||||
|
@ -139,8 +154,10 @@ export default class UploadMedia extends React.Component {
|
|||
modalBodySize.width !== prevState.modalBodySize.width ||
|
||||
modalBodySize.height !== prevState.modalBodySize.height
|
||||
) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({modalBodySize})
|
||||
if (modalBodySize.width > 0 && modalBodySize.height > 0) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({modalBodySize})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,9 +238,24 @@ export default class UploadMedia extends React.Component {
|
|||
return null
|
||||
}
|
||||
|
||||
const {CLOSE_TEXT, SUBMIT_TEXT} = this.props.uploadMediaTranslations.UploadMediaStrings
|
||||
const {
|
||||
CLOSE_TEXT,
|
||||
SUBMIT_TEXT,
|
||||
PROGRESS_LABEL
|
||||
} = this.props.uploadMediaTranslations.UploadMediaStrings
|
||||
return (
|
||||
<Modal.Footer>
|
||||
{this.state.uploading && (
|
||||
<ProgressBar
|
||||
screenReaderLabel={PROGRESS_LABEL}
|
||||
valueNow={this.state.progress}
|
||||
valueMax={100}
|
||||
renderValue={({valueNow}) => {
|
||||
return <Text>{valueNow}%</Text>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button onClick={this.onModalClose}> {CLOSE_TEXT} </Button>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -56,13 +56,14 @@ describe('saveMediaRecording', () => {
|
|||
done()
|
||||
})
|
||||
|
||||
it('returns error if k5.filreError is dispatched', () => {
|
||||
it('returns error if k5.fileError is dispatched', () => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 200,
|
||||
response: mediaServerSession()
|
||||
})
|
||||
const doneFunction = jest.fn()
|
||||
return saveMediaRecording({}, '1', 'course', doneFunction).then(uploader => {
|
||||
const progressFunction = jest.fn()
|
||||
return saveMediaRecording({}, '1', 'course', doneFunction, progressFunction).then(uploader => {
|
||||
uploader.dispatchEvent('K5.fileError', {error: 'womp womp'}, uploader)
|
||||
expect(doneFunction).toHaveBeenCalledTimes(1)
|
||||
expect(doneFunction.mock.calls[0][0].error).toBe('womp womp')
|
||||
|
@ -75,13 +76,33 @@ describe('saveMediaRecording', () => {
|
|||
response: mediaServerSession()
|
||||
})
|
||||
const doneFunction = jest.fn()
|
||||
const progressFunction = jest.fn()
|
||||
const uploadFileFunc = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction).then(uploader => {
|
||||
uploader.uploadFile = uploadFileFunc
|
||||
uploader.dispatchEvent('K5.ready', uploader)
|
||||
expect(uploadFileFunc).toHaveBeenCalledTimes(1)
|
||||
expect(uploadFileFunc.mock.calls[0][0].file).toBe('thing')
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction, progressFunction).then(
|
||||
uploader => {
|
||||
uploader.uploadFile = uploadFileFunc
|
||||
uploader.dispatchEvent('K5.ready', uploader)
|
||||
expect(uploadFileFunc).toHaveBeenCalledTimes(1)
|
||||
expect(uploadFileFunc.mock.calls[0][0].file).toBe('thing')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('k5.progress calls progress function when dispatched', () => {
|
||||
moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
|
||||
status: 200,
|
||||
response: mediaServerSession()
|
||||
})
|
||||
const doneFunction = jest.fn()
|
||||
const progressFunction = jest.fn()
|
||||
const uploadFileFunc = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction, progressFunction).then(
|
||||
uploader => {
|
||||
uploader.uploadFile = uploadFileFunc
|
||||
uploader.dispatchEvent('K5.progress', uploader)
|
||||
expect(progressFunction).toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('k5.complete calls done with canvasMediaObject data if succeeds', () => {
|
||||
|
@ -94,7 +115,8 @@ describe('saveMediaRecording', () => {
|
|||
response: {data: 'media object data'}
|
||||
})
|
||||
const doneFunction2 = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2).then(
|
||||
const progressFunction = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2, progressFunction).then(
|
||||
async uploader => {
|
||||
uploader.dispatchEvent('K5.complete', {stuff: 'datatatatatatatat'}, uploader)
|
||||
await new Promise(setTimeout)
|
||||
|
@ -118,7 +140,8 @@ describe('saveMediaRecording', () => {
|
|||
response: {error: 'womp womp'}
|
||||
})
|
||||
const doneFunction2 = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2).then(
|
||||
const progressFunction = jest.fn()
|
||||
return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2, progressFunction).then(
|
||||
async uploader => {
|
||||
uploader.dispatchEvent('K5.complete', {stuff: 'datatatatatatatat'}, uploader)
|
||||
await new Promise(setTimeout)
|
||||
|
|
|
@ -20,6 +20,7 @@ import axios from 'axios'
|
|||
import K5Uploader from '@instructure/k5uploader'
|
||||
|
||||
export const VIDEO_SIZE_OPTIONS = {height: '432px', width: '768px'}
|
||||
const STARTING_PROGRESS_VALUE = 33
|
||||
|
||||
function generateUploadOptions(mediatypes, sessionData) {
|
||||
const sessionDataCopy = JSON.parse(JSON.stringify(sessionData))
|
||||
|
@ -42,6 +43,13 @@ function addUploaderReadyEventListeners(uploader, file) {
|
|||
})
|
||||
}
|
||||
|
||||
function addUploaderProgressEventListeners(uploader, onProgress) {
|
||||
uploader.addEventListener('K5.progress', progress => {
|
||||
const percentUploaded = Math.round(progress.loaded / progress.total) * STARTING_PROGRESS_VALUE
|
||||
onProgress(STARTING_PROGRESS_VALUE + percentUploaded)
|
||||
})
|
||||
}
|
||||
|
||||
function addUploaderFileErrorEventListeners(uploader, done, file) {
|
||||
uploader.addEventListener('K5.fileError', error => {
|
||||
uploader.destroy()
|
||||
|
@ -49,7 +57,7 @@ function addUploaderFileErrorEventListeners(uploader, done, file) {
|
|||
})
|
||||
}
|
||||
|
||||
function addUploaderFileCompleteEventListeners(uploader, context, file, done) {
|
||||
function addUploaderFileCompleteEventListeners(uploader, context, file, done, onProgress) {
|
||||
uploader.addEventListener('K5.complete', async mediaServerMediaObject => {
|
||||
mediaServerMediaObject.contextCode = `${context.contextType}_${context.contextId}`
|
||||
mediaServerMediaObject.type = `${context.contextType}_${context.contextId}`
|
||||
|
@ -67,7 +75,17 @@ function addUploaderFileCompleteEventListeners(uploader, context, file, done) {
|
|||
}
|
||||
|
||||
try {
|
||||
const canvasMediaObject = await axios.post('/api/v1/media_objects', body)
|
||||
const config = {
|
||||
onUploadProgress: progressEvent => {
|
||||
const startingValue = 2 * STARTING_PROGRESS_VALUE
|
||||
const percentUploaded =
|
||||
Math.round(progressEvent.loaded / progressEvent.total) * (STARTING_PROGRESS_VALUE + 1)
|
||||
if (onProgress) {
|
||||
onProgress(startingValue + percentUploaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
const canvasMediaObject = await axios.post('/api/v1/media_objects', body, config)
|
||||
uploader.destroy()
|
||||
doDone(done, null, {mediaObject: canvasMediaObject.data, uploadedFile: file})
|
||||
} catch (ex) {
|
||||
|
@ -77,20 +95,32 @@ function addUploaderFileCompleteEventListeners(uploader, context, file, done) {
|
|||
})
|
||||
}
|
||||
|
||||
export default async function saveMediaRecording(file, contextId, contextType, done) {
|
||||
export default async function saveMediaRecording(file, contextId, contextType, done, onProgress) {
|
||||
try {
|
||||
window.addEventListener('beforeunload', handleUnloadWhileUploading)
|
||||
const mediaServerSession = await axios.post(
|
||||
'/api/v1/services/kaltura_session?include_upload_config=1'
|
||||
)
|
||||
if (onProgress) {
|
||||
onProgress(STARTING_PROGRESS_VALUE)
|
||||
}
|
||||
const session = generateUploadOptions(
|
||||
['video', 'audio', 'webm', 'video/webm', 'audio/webm'],
|
||||
mediaServerSession.data
|
||||
)
|
||||
const k5UploaderSession = new K5Uploader(session)
|
||||
addUploaderReadyEventListeners(k5UploaderSession, file)
|
||||
if (onProgress) {
|
||||
addUploaderProgressEventListeners(k5UploaderSession, onProgress)
|
||||
}
|
||||
addUploaderFileErrorEventListeners(k5UploaderSession, done, file)
|
||||
addUploaderFileCompleteEventListeners(k5UploaderSession, {contextId, contextType}, file, done)
|
||||
addUploaderFileCompleteEventListeners(
|
||||
k5UploaderSession,
|
||||
{contextId, contextType},
|
||||
file,
|
||||
done,
|
||||
onProgress
|
||||
)
|
||||
return k5UploaderSession
|
||||
} catch (err) {
|
||||
doDone(done, err, {uploadedFile: file})
|
||||
|
@ -99,20 +129,17 @@ export default async function saveMediaRecording(file, contextId, contextType, d
|
|||
|
||||
export async function saveClosedCaptions(mediaId, files) {
|
||||
const axiosRequests = []
|
||||
files.forEach(function(file) {
|
||||
files.forEach(function (file) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = function(e) {
|
||||
reader.onload = function (e) {
|
||||
const content = e.target.result
|
||||
const params = {
|
||||
content,
|
||||
locale: file.locale,
|
||||
'exclude[]': 'sources'
|
||||
}
|
||||
axios
|
||||
.post(`/media_objects/${mediaId}/media_tracks`, params)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
axios.post(`/media_objects/${mediaId}/media_tracks`, params).then(resolve).catch(reject)
|
||||
}
|
||||
reader.readAsText(file.file)
|
||||
})
|
||||
|
|
|
@ -41,6 +41,7 @@ const MediaCaptureStrings = {
|
|||
|
||||
const UploadMediaStrings = {
|
||||
LOADING_MEDIA: I18n.t('Loading Media'),
|
||||
PROGRESS_LABEL: I18n.t('Uploading media Progress'),
|
||||
ADD_CLOSED_CAPTIONS_OR_SUBTITLES: I18n.t('Add CC/Subtitle'),
|
||||
COMPUTER_PANEL_TITLE: I18n.t('Computer'),
|
||||
DRAG_FILE_TEXT: I18n.t('Drag a File Here'),
|
||||
|
|
Loading…
Reference in New Issue