diff --git a/app/jsx/assignments_2/student/components/AttemptType/MediaAttempt.js b/app/jsx/assignments_2/student/components/AttemptType/MediaAttempt.js
index a0a4def8788..47e2e8e71cd 100644
--- a/app/jsx/assignments_2/student/components/AttemptType/MediaAttempt.js
+++ b/app/jsx/assignments_2/student/components/AttemptType/MediaAttempt.js
@@ -21,36 +21,57 @@ import Button from '@instructure/ui-buttons/lib/components/Button'
import closedCaptionLanguages from '../../../../shared/closedCaptionLanguages'
import I18n from 'i18n!assignments_2_text_entry'
import {IconAttachMediaLine} from '@instructure/ui-icons'
-import React, {useState} from 'react'
+import React from 'react'
import UploadMedia from '@instructure/canvas-media'
import {UploadMediaStrings, MediaCaptureStrings} from '../../../../shared/UploadMediaTranslations'
import View from '@instructure/ui-layout/lib/components/View'
-export default function MediaAttempt() {
- const [mediaModalOpen, setMediaModalOpen] = useState(false)
+const languages = Object.keys(closedCaptionLanguages).map(key => {
+ return {id: key, label: closedCaptionLanguages[key]}
+})
- const languages = Object.keys(closedCaptionLanguages).map(key => {
- return {id: key, label: closedCaptionLanguages[key]}
- })
+export default class MediaAttempt extends React.Component {
+ state = {
+ mediaModalOpen: false,
+ mediaObjectUrl: null
+ }
- return (
-
- setMediaModalOpen(false)}
- open={mediaModalOpen}
- liveRegion={() => document.getElementById('flash_screenreader_holder')}
- languages={languages}
- />
- }
- message={
-
- }
- />
-
- )
+ onDismiss = mediaObject => {
+ this.setState({mediaModalOpen: false, mediaObjectUrl: mediaObject.embedded_iframe_url})
+ }
+
+ render() {
+ if (this.state.mediaObjectUrl) {
+ // TODO: figure out how the heck we want to style this thing.
+ return
+ }
+
+ return (
+
+ document.getElementById('flash_screenreader_holder')}
+ languages={languages}
+ />
+ }
+ message={
+
+ }
+ />
+
+ )
+ }
}
diff --git a/packages/canvas-media/package.json b/packages/canvas-media/package.json
index 1f1edd3a348..18250ca1aaa 100644
--- a/packages/canvas-media/package.json
+++ b/packages/canvas-media/package.json
@@ -44,6 +44,7 @@
"@instructure/ui-themes": "^6.8.1",
"@instructure/ui-toggle-details": "^6.8.1",
"@instructure/uid": "^6.8.1",
+ "axios": "^0.18.0",
"prop-types": "^15",
"react": "^16",
"react-dom": "^16"
diff --git a/packages/canvas-media/src/EmbedPanel.js b/packages/canvas-media/src/EmbedPanel.js
index 6aed6c8534c..c89707c803a 100644
--- a/packages/canvas-media/src/EmbedPanel.js
+++ b/packages/canvas-media/src/EmbedPanel.js
@@ -26,6 +26,7 @@ export default function EmbedPanel({embedCode, setEmbedCode, label}) {
maxHeight="10rem"
label={label}
value={embedCode}
+ placeholder={label}
onChange={e => setEmbedCode(e.target.value)}
/>
)
diff --git a/packages/canvas-media/src/MediaRecorder.js b/packages/canvas-media/src/MediaRecorder.js
index 5447b8452a1..28d0771be51 100644
--- a/packages/canvas-media/src/MediaRecorder.js
+++ b/packages/canvas-media/src/MediaRecorder.js
@@ -20,12 +20,14 @@ import React from 'react'
import {Alert} from '@instructure/ui-alerts'
import {canUseMediaCapture, MediaCapture} from '@instructure/media-capture'
-import {object, string} from 'prop-types'
+import {func, object, string} from 'prop-types'
+
+import saveMediaRecording from './saveMediaRecording'
export default class MediaRecorder extends React.Component {
- // saveFile = (file) => {
- // this.props.contentProps.saveMediaRecording(file, this.props.editor, this.props.dismiss)
- // }
+ saveFile = file => {
+ saveMediaRecording(file, this.props.contextId, this.props.contextType, this.props.dismiss)
+ }
render() {
return (
@@ -43,6 +45,9 @@ export default class MediaRecorder extends React.Component {
}
MediaRecorder.propTypes = {
+ contextId: string,
+ contextType: string,
+ dismiss: func,
errorMessage: string.isRequired,
MediaCaptureStrings: object // eslint-disable-line react/forbid-prop-types
}
diff --git a/packages/canvas-media/src/__mocks__/screenfull.js b/packages/canvas-media/src/__mocks__/screenfull.js
new file mode 100644
index 00000000000..d09475b3a31
--- /dev/null
+++ b/packages/canvas-media/src/__mocks__/screenfull.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 - present Instructure, Inc.
+ *
+ * This file is part of Canvas.
+ *
+ * Canvas is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, version 3 of the License.
+ *
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along
+ * with this program. If not, see .
+ */
+
+const screenfull = {
+ on() {},
+ off() {}
+}
+export default screenfull
diff --git a/packages/canvas-media/src/__tests__/EmbedPanel.test.js b/packages/canvas-media/src/__tests__/EmbedPanel.test.js
new file mode 100644
index 00000000000..ac436007c46
--- /dev/null
+++ b/packages/canvas-media/src/__tests__/EmbedPanel.test.js
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 - present Instructure, Inc.
+ *
+ * This file is part of Canvas.
+ *
+ * Canvas is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, version 3 of the License.
+ *
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along
+ * with this program. If not, see .
+ */
+import {fireEvent, render} from '@testing-library/react'
+import React from 'react'
+
+import EmbedPanel from '../EmbedPanel'
+
+describe('EmbedPanel', () => {
+ it('renders with label', () => {
+ const {getByText} = render(
+ {}} />
+ )
+ expect(getByText('embed panel')).toBeInTheDocument()
+ })
+
+ it('renders with value', () => {
+ const {getByText} = render(
+ {}} />
+ )
+ expect(getByText('the best value of the embed')).toBeInTheDocument()
+ })
+
+ it('on change calls setEmbedCode', () => {
+ const handleChange = jest.fn()
+ const {getByPlaceholderText} = render(
+
+ )
+ const textArea = getByPlaceholderText('embed label')
+ fireEvent.change(textArea, {target: {value: 'TEST VALUE'}})
+
+ expect(handleChange).toHaveBeenCalledTimes(1)
+ expect(handleChange.mock.calls[0][0]).toBe('TEST VALUE')
+ })
+})
diff --git a/packages/canvas-media/src/__tests__/UploadPanel.test.js b/packages/canvas-media/src/__tests__/UploadPanel.test.js
new file mode 100644
index 00000000000..4904026ed5d
--- /dev/null
+++ b/packages/canvas-media/src/__tests__/UploadPanel.test.js
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 - present Instructure, Inc.
+ *
+ * This file is part of Canvas.
+ *
+ * Canvas is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, version 3 of the License.
+ *
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along
+ * with this program. If not, see .
+ */
+
+import React from 'react'
+import {render, fireEvent, waitForElement} from '@testing-library/react'
+import ComputerPanel from '../ComputerPanel'
+
+function makeTranslationProps() {
+ return {
+ UploadMediaStrings: {
+ CLEAR_FILE_TEXT: 'Clear File',
+ INVALID_FILE_TEXT: 'Invalid file type',
+ DRAG_DROP_CLICK_TO_BROWSE: 'drag and drop or clik to browse'
+ }
+ }
+}
+
+describe('UploadFile: ComputerPanel', () => {
+ it('shows a failure message if the file is rejected', () => {
+ const notAnImageFile = new File(['foo'], 'foo.txt', {
+ type: 'text/plain'
+ })
+ const handleSetFile = jest.fn()
+ const handleSetHasUploadedFile = jest.fn()
+ const {getByLabelText, getByText} = render(
+
+ )
+ const dropZone = getByLabelText(/Upload File/, {selector: 'input'})
+ fireEvent.change(dropZone, {
+ target: {
+ files: [notAnImageFile]
+ }
+ })
+ expect(getByText('Invalid file type')).toBeVisible()
+ })
+
+ it('accepts file files', () => {
+ const aFile = new File(['foo'], 'foo.png', {
+ type: 'image/png'
+ })
+ const handleSetFile = jest.fn()
+ const handleSetHasUploadedFile = jest.fn()
+ const {getByLabelText, queryByText} = render(
+
+ )
+ const dropZone = getByLabelText(/Upload File/, {selector: 'input'})
+ fireEvent.change(dropZone, {
+ target: {
+ files: [aFile]
+ }
+ })
+ expect(queryByText('Invalid file type')).toBeNull()
+ })
+
+ it('clears error messages if a valid file is added', () => {
+ const notAnImageFile = new File(['foo'], 'foo.txt', {
+ type: 'text/plain'
+ })
+ const aFile = new File(['foo'], 'foo.png', {
+ type: 'image/png'
+ })
+ const handleSetFile = jest.fn()
+ const handleSetHasUploadedFile = jest.fn()
+ const {getByLabelText, getByText, queryByText} = render(
+
+ )
+ const dropZone = getByLabelText(/Upload File/, {selector: 'input'})
+ fireEvent.change(dropZone, {
+ target: {
+ files: [notAnImageFile]
+ }
+ })
+ expect(getByText('Invalid file type')).toBeVisible()
+ fireEvent.change(dropZone, {
+ target: {
+ files: [aFile]
+ }
+ })
+
+ expect(queryByText('Invalid file type')).toBeNull()
+ })
+
+ it('Renders a video player preview if a file type is a video', async () => {
+ global.URL.createObjectURL = jest.fn(() => 'www.blah.com')
+ const handleSetFile = jest.fn()
+ const handleSetHasUploadedFile = jest.fn()
+ const aFile = new File(['foo'], 'foo.mp4', {
+ type: 'video/mp4'
+ })
+ const {getByText} = render(
+
+ )
+ const playButton = await waitForElement(() => getByText('Play'))
+ expect(playButton).toBeInTheDocument()
+ })
+})
diff --git a/packages/canvas-media/src/__tests__/saveMediaRecording.test.js b/packages/canvas-media/src/__tests__/saveMediaRecording.test.js
new file mode 100644
index 00000000000..c97b67f77fc
--- /dev/null
+++ b/packages/canvas-media/src/__tests__/saveMediaRecording.test.js
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 - present Instructure, Inc.
+ *
+ * This file is part of Canvas.
+ *
+ * Canvas is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, version 3 of the License.
+ *
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along
+ * with this program. If not, see .
+ */
+import K5Uploader from '@instructure/k5uploader'
+import moxios from 'moxios'
+import sinon from 'sinon'
+import saveMediaRecording from '../saveMediaRecording'
+
+function mediaServerSession() {
+ return {
+ ks: 'averylongstring',
+ subp_id: '0',
+ partner_id: '9',
+ uid: '1234_567',
+ serverTime: 1234,
+ kaltura_setting: {
+ uploadUrl: 'url.url.url',
+ entryUrl: 'url.url.url',
+ uiconfUrl: 'url.url.url',
+ partnerData: 'data from our partners'
+ }
+ }
+}
+
+describe('saveMediaRecording', () => {
+ beforeEach(() => {
+ moxios.install()
+ })
+ afterEach(() => {
+ moxios.uninstall()
+ })
+ it('fails if request for kaltura session fails', async done => {
+ moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
+ status: 500,
+ response: {error: 'womp womp'}
+ })
+ const doneFunction = jest.fn()
+ sinon.stub(K5Uploader.prototype, 'loadUiConf').callsFake(() => 'mock')
+ await saveMediaRecording({}, '1', 'course', doneFunction)
+ expect(doneFunction).toHaveBeenCalledTimes(1)
+ expect(doneFunction.mock.calls[0][0].message).toBe('Request failed with status code 500')
+ done()
+ })
+
+ it('returns error if k5.filreError 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 => {
+ uploader.dispatchEvent('K5.fileError', {error: 'womp womp'}, uploader)
+ expect(doneFunction).toHaveBeenCalledTimes(1)
+ expect(doneFunction.mock.calls[0][0].error).toBe('womp womp')
+ })
+ })
+
+ it('k5.ready calls uploaders uploadFile with file', () => {
+ moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
+ status: 200,
+ response: mediaServerSession()
+ })
+ const doneFunction = 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')
+ })
+ })
+
+ it('k5.complete calls done with canvasMediaObject data if succeeds', () => {
+ moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
+ status: 200,
+ response: mediaServerSession()
+ })
+ moxios.stubRequest('/api/v1/media_objects', {
+ status: 200,
+ response: {data: 'media object data'}
+ })
+ const doneFunction2 = jest.fn()
+ return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2).then(
+ async uploader => {
+ uploader.dispatchEvent('K5.complete', {stuff: 'datatatatatatatat'}, uploader)
+ await new Promise(setTimeout)
+ expect(doneFunction2).toHaveBeenCalledTimes(1)
+ expect(doneFunction2.mock.calls[0][0].data).toBe('media object data')
+ }
+ )
+ })
+
+ it('fails if request to create media object fails', async () => {
+ moxios.stubRequest('/api/v1/services/kaltura_session?include_upload_config=1', {
+ status: 200,
+ response: mediaServerSession()
+ })
+ moxios.stubRequest('/api/v1/media_objects', {
+ status: 500,
+ response: {error: 'womp womp'}
+ })
+ const doneFunction2 = jest.fn()
+ return saveMediaRecording({file: 'thing'}, '1', 'course', doneFunction2).then(
+ async uploader => {
+ uploader.dispatchEvent('K5.complete', {stuff: 'datatatatatatatat'}, uploader)
+ await new Promise(setTimeout)
+ expect(doneFunction2).toHaveBeenCalledTimes(1)
+ expect(doneFunction2.mock.calls[0][0].message).toBe('Request failed with status code 500')
+ }
+ )
+ })
+})
diff --git a/packages/canvas-media/src/index.js b/packages/canvas-media/src/index.js
index 3acbff7fda2..9f27e71aa6c 100644
--- a/packages/canvas-media/src/index.js
+++ b/packages/canvas-media/src/index.js
@@ -25,6 +25,7 @@ import {Tabs} from '@instructure/ui-tabs'
import {View} from '@instructure/ui-layout'
import {ACCEPTED_FILE_TYPES} from './acceptedMediaFileTypes'
+import saveMediaRecording from './saveMediaRecording'
import translationShape from './translationShape'
const ClosedCaptionPanel = React.lazy(() => import('./ClosedCaptionPanel'))
@@ -39,24 +40,6 @@ export const PANELS = {
CLOSED_CAPTIONS: 3
}
-export const handleSubmit = (editor, selectedPanel, uploadData, saveMediaRecording, onDismiss) => {
- switch (selectedPanel) {
- case PANELS.COMPUTER: {
- const {theFile} = uploadData
- saveMediaRecording(theFile, editor, onDismiss)
- break
- }
- case PANELS.EMBED: {
- const {embedCode} = uploadData
- editor.insertContent(embedCode)
- onDismiss()
- break
- }
- default:
- throw new Error('Selected Panel is invalid') // Should never get here
- }
-}
-
export default class UploadMedia extends React.Component {
static propTypes = {
languages: arrayOf(
@@ -66,8 +49,15 @@ export default class UploadMedia extends React.Component {
})
),
liveRegion: func,
+ contextId: string,
+ contextType: string,
onDismiss: func,
open: bool,
+ tabs: shape({
+ embed: bool,
+ record: bool,
+ upload: bool
+ }),
uploadMediaTranslations: translationShape
}
@@ -78,6 +68,26 @@ export default class UploadMedia extends React.Component {
theFile: null
}
+ handleSubmit = () => {
+ switch (this.state.selectedPanel) {
+ case PANELS.COMPUTER: {
+ saveMediaRecording(
+ this.state.theFile,
+ this.props.contextId,
+ this.props.contextType,
+ this.props.onDismiss
+ )
+ break
+ }
+ case PANELS.EMBED: {
+ this.props.onDismiss()
+ break
+ }
+ default:
+ throw new Error('Selected Panel is invalid') // Should never get here
+ }
+ }
+
renderFallbackSpinner = () => {
const {LOADING_MEDIA} = this.props.uploadMediaTranslations.UploadMediaStrings
return (
@@ -104,51 +114,56 @@ export default class UploadMedia extends React.Component {
maxWidth="large"
onRequestTabChange={(_, {index}) => this.setState({selectedPanel: index})}
>
- COMPUTER_PANEL_TITLE}
- >
-
- this.setState({theFile: file})}
- hasUploadedFile={this.state.hasUploadedFile}
- setHasUploadedFile={uploadFileState =>
- this.setState({hasUploadedFile: uploadFileState})
- }
- label={DRAG_FILE_TEXT}
- uploadMediaTranslations={this.props.uploadMediaTranslations}
- accept={ACCEPTED_FILE_TYPES}
- />
-
-
-
- RECORD_PANEL_TITLE}
- >
-
-
-
-
-
- EMBED_PANEL_TITLE}
- >
-
- this.setState({embedCode})}
- />
-
-
-
+ {this.props.tabs.upload && (
+ COMPUTER_PANEL_TITLE}
+ >
+
+ this.setState({theFile: file})}
+ hasUploadedFile={this.state.hasUploadedFile}
+ setHasUploadedFile={uploadFileState =>
+ this.setState({hasUploadedFile: uploadFileState})
+ }
+ label={DRAG_FILE_TEXT}
+ uploadMediaTranslations={this.props.uploadMediaTranslations}
+ accept={ACCEPTED_FILE_TYPES}
+ />
+
+
+ )}
+ {this.props.tabs.record && (
+ RECORD_PANEL_TITLE}
+ >
+
+
+
+
+ )}
+ {this.props.tabs.embed && (
+ EMBED_PANEL_TITLE}
+ >
+
+ this.setState({embedCode})}
+ />
+
+
+ )}
CLOSED_CAPTIONS_PANEL_TITLE}
@@ -178,7 +193,7 @@ export default class UploadMedia extends React.Component {