remove blocking modal when recording screen share
The modal that is used to record screen share media on speed grader submission comments was blocking the page, so you could not interact with the page while the modal was open. This change removes the modal and instead shows an indicator bar. flag=speedgrader_studio_media_capture closes EVAL-4130 test plan: - go to speed grader - click on the media recorder icon - click the start button (this should default to the webcam recoring) - the modal should stay on the page - close the modal and click the media recorder icon again - select screen share this time - click the start button and the modal should disappear and the indicator bar should show at the top of the page - click the 'cancel' button and the indicator bar should disappear - click the media recorder icon again and select screen share - click the start button and the indicator bar should show at the top of the page again - click the 'finish' button and the indicator bar should disappear and the modal should show Change-Id: I20911ae96c0bd9c2ce1519e3958f9b659adc0ec0 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/345967 Reviewed-by: Cameron Ray <cameron.ray@instructure.com> QA-Review: Cameron Ray <cameron.ray@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Product-Review: Melissa Kruger <melissa.kruger@instructure.com>
This commit is contained in:
parent
3a01174aaa
commit
b06cd5aa96
|
@ -97,3 +97,4 @@
|
|||
@import 'bundles/context_cards';
|
||||
@import 'bundles/rce';
|
||||
@import 'pages/react_files/_FilePreview.scss';
|
||||
@import 'components/media_recorder';
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
.RecordingBar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 0.8625rem;
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: inline-flex;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
}
|
|
@ -106,6 +106,7 @@
|
|||
</figure>
|
||||
<section id="full_width_container" style="display:none; flex: 1;">
|
||||
<div id="left_side" class="full_height">
|
||||
<div id="screen-capture-indicator-mount-point"></div>
|
||||
<div id="left_side_inner">
|
||||
<div id="no_annotation_warning" class="alert speedgrader_alert">
|
||||
<a class="dismiss_alert close" href="#">×</a>
|
||||
|
|
|
@ -42,7 +42,7 @@ export default class DialogManager {
|
|||
}
|
||||
this.dialog.text(I18n.t('messages.loading', 'Loading...'))
|
||||
this.dialog.dialog({
|
||||
title: I18n.t('titles.record_upload_media_comment', 'Record/Upload Media Comment'),
|
||||
title: I18n.t('Studio Capture'),
|
||||
resizable: false,
|
||||
width: 470,
|
||||
height: 300,
|
||||
|
@ -65,7 +65,7 @@ export default class DialogManager {
|
|||
|
||||
showUpdateDialog() {
|
||||
return this.dialog.dialog({
|
||||
title: I18n.t('titles.record_upload_media_comment', 'Record/Upload Media Comment'),
|
||||
title: I18n.t('Studio Capture'),
|
||||
width: 650,
|
||||
height: 550,
|
||||
modal: true,
|
||||
|
|
|
@ -311,7 +311,7 @@ $.mediaComment.init = function (mediaType, opts) {
|
|||
}
|
||||
$('#video_record_title,#audio_record_title').val(defaultTitle)
|
||||
$dialog.dialog({
|
||||
title: I18n.t('titles.record_upload_media_comment', 'Record/Upload Media Comment'),
|
||||
title: I18n.t('Studio Capture'),
|
||||
width: 560,
|
||||
height: 475,
|
||||
modal: true,
|
||||
|
@ -613,7 +613,7 @@ $.mediaComment.init = function (mediaType, opts) {
|
|||
const $div = $('<div/>').attr('id', 'media_comment_dialog')
|
||||
$div.text(I18n.t('messages.loading', 'Loading...'))
|
||||
$div.dialog({
|
||||
title: I18n.t('titles.record_upload_media_comment', 'Record/Upload Media Comment'),
|
||||
title: I18n.t('Studio Capture'),
|
||||
resizable: false,
|
||||
width: 470,
|
||||
height: 300,
|
||||
|
|
|
@ -17,15 +17,25 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom';
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import {MediaCapture, canUseMediaCapture} from '@instructure/media-capture'
|
||||
import {ScreenCapture, canUseScreenCapture} from '@instructure/media-capture-new'
|
||||
import {func} from 'prop-types'
|
||||
import {func, string} from 'prop-types'
|
||||
import {mediaExtension} from '../../mimetypes'
|
||||
const I18n = useI18nScope('media_recorder')
|
||||
const DEFAULT_EXTENSION = 'webm'
|
||||
const fileExtensionRegex = /\.\S/
|
||||
|
||||
import {
|
||||
IconRecordSolid,
|
||||
IconStopLine
|
||||
} from '@instructure/ui-icons';
|
||||
|
||||
import { View } from '@instructure/ui-view';
|
||||
import { Heading } from '@instructure/ui-heading';
|
||||
import { Button } from '@instructure/ui-buttons';
|
||||
|
||||
const translations = {
|
||||
ARIA_VIDEO_LABEL: I18n.t('Video Player'),
|
||||
ARIA_VOLUME: I18n.t('Current Volume Level'),
|
||||
|
@ -60,8 +70,12 @@ export function fileWithExtension(file) {
|
|||
}
|
||||
|
||||
export default class CanvasMediaRecorder extends React.Component {
|
||||
dialogRef = React.createRef()
|
||||
|
||||
static propTypes = {
|
||||
onSaveFile: func,
|
||||
onModalShowToggle: func,
|
||||
indicatorBarMountPointId: string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -73,12 +87,96 @@ export default class CanvasMediaRecorder extends React.Component {
|
|||
this.props.onSaveFile(file)
|
||||
}
|
||||
|
||||
screenCaptureStarted = () => {
|
||||
const finishButton = document.querySelector('#screen_capture_finish_button')
|
||||
return finishButton?.getAttribute('data-is-screen-share') === 'true'
|
||||
}
|
||||
|
||||
onRecordingStart = () => {
|
||||
if (this.screenCaptureStarted()){
|
||||
this.hideModal()
|
||||
this.renderIndicatorBar()
|
||||
}
|
||||
}
|
||||
|
||||
renderIndicatorBar = () => {
|
||||
const {indicatorBarMountPointId} = this.props
|
||||
if (!indicatorBarMountPointId) return
|
||||
const mountPoint = document.getElementById(indicatorBarMountPointId)
|
||||
if (mountPoint) {
|
||||
ReactDOM.render(
|
||||
<ScreenCaptureIndicatorBar
|
||||
onFinishClick={this.handleFinishClick}
|
||||
onCancelClick={this.handleCancelClick}
|
||||
/>,
|
||||
mountPoint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
removeIndicatorBar = () => {
|
||||
const {indicatorBarMountPointId} = this.props
|
||||
const mountPoint = document.getElementById(indicatorBarMountPointId)
|
||||
ReactDOM.unmountComponentAtNode(mountPoint)
|
||||
}
|
||||
|
||||
handleCancelClick = () => {
|
||||
const dialog = this.dialogRef.current
|
||||
this.removeIndicatorBar()
|
||||
this.toggleBackgroundItems(false)
|
||||
const closeButton = dialog.querySelector('a.ui-dialog-titlebar-close')
|
||||
closeButton.click()
|
||||
}
|
||||
|
||||
handleFinishClick = () => {
|
||||
const dialog = this.dialogRef.current
|
||||
const finishButton = dialog.querySelector('#screen_capture_finish_button')
|
||||
finishButton.click()
|
||||
this.showModal()
|
||||
this.dialogRef.current = null
|
||||
this.removeIndicatorBar()
|
||||
}
|
||||
|
||||
showModal = () => {
|
||||
const dialog = this.dialogRef.current
|
||||
dialog.style.display = 'block'
|
||||
this.toggleBackgroundItems(false)
|
||||
}
|
||||
|
||||
hideModal = () => {
|
||||
const dialog = document.querySelectorAll('.ui-dialog:not([style*="display: none"])')[0]
|
||||
dialog.style.display = 'none'
|
||||
this.toggleBackgroundItems(true)
|
||||
this.dialogRef.current = dialog
|
||||
}
|
||||
|
||||
toggleBackgroundItems = (disabled) => {
|
||||
// toggle the modal's backgroud overlay
|
||||
const overlay = document.querySelector('.ui-widget-overlay')
|
||||
if (overlay) {
|
||||
overlay.style.display = disabled ? 'none' : 'block'
|
||||
// enables anchors and inputs on the page behind the hidden modal
|
||||
$.ui.dialog.overlay.maxZ = disabled ? 0 : 1000
|
||||
}
|
||||
|
||||
if (this.props.onModalShowToggle) {
|
||||
this.props.onModalShowToggle(disabled)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (ENV.studio_media_capture_enabled) {
|
||||
return (
|
||||
<div>
|
||||
{canUseScreenCapture() && (
|
||||
<ScreenCapture translations={translations} onCompleted={this.saveFile} />
|
||||
<ScreenCapture
|
||||
translations={translations}
|
||||
onCompleted={this.saveFile}
|
||||
// give the finish button time to render, that's how we tell if it's a screen share
|
||||
onStreamInitialized={() => setTimeout(this.onRecordingStart, 250)}
|
||||
// allows you to include the current tab in the screen share
|
||||
experimentalScreenShareOptions={{selfBrowserSurface: 'include'}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
@ -92,3 +190,46 @@ export default class CanvasMediaRecorder extends React.Component {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ScreenCaptureIndicatorBar = ({onCancelClick, onFinishClick}) => {
|
||||
return (
|
||||
<View
|
||||
as="div"
|
||||
className="RecordingBar"
|
||||
padding={'x-small small'}
|
||||
>
|
||||
<View margin="0 auto 0 0" className="RecordingBar__time">
|
||||
<View className="RecordingBar__icon">
|
||||
<IconRecordSolid color="error" />
|
||||
</View>
|
||||
<Heading level="reset" as="h2">
|
||||
{I18n.t('Screen recording is in progress ')}
|
||||
</Heading>
|
||||
</View>
|
||||
<Button
|
||||
color="secondary"
|
||||
withBackground
|
||||
margin="none"
|
||||
size="medium"
|
||||
onClick={onCancelClick}
|
||||
id="screen_capture_bar_cancel_button"
|
||||
>
|
||||
{I18n.t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
renderIcon={IconStopLine}
|
||||
color="primary"
|
||||
size="medium"
|
||||
margin="none"
|
||||
onClick={onFinishClick}
|
||||
id="screen_capture_bar_finish_button"
|
||||
themeOverride={{
|
||||
iconSizeMedium: '1.125rem'
|
||||
}}
|
||||
>
|
||||
{I18n.t('Finish Recording')}
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -20,8 +20,39 @@ import React from 'react'
|
|||
import ReactDOM from 'react-dom'
|
||||
import CanvasMediaRecorder from './react/components/MediaRecorder'
|
||||
|
||||
// We have this simple code in a seperate file so that we can take advantage
|
||||
// of code splitting
|
||||
export default function renderCanvasMediaRecorder(element, onSaveFile) {
|
||||
ReactDOM.render(<CanvasMediaRecorder onSaveFile={onSaveFile} />, element)
|
||||
const fromSpeedGrader = window.location.href.includes('/speed_grader')
|
||||
|
||||
let indicatorBarMountPointId = null
|
||||
let onModalShowToggle = null
|
||||
|
||||
if (fromSpeedGrader) {
|
||||
indicatorBarMountPointId = "screen-capture-indicator-mount-point"
|
||||
|
||||
onModalShowToggle = (disabled) => {
|
||||
// disable media comment button and next/prev student buttons when recording
|
||||
['media_comment_button', 'next-student-button', 'prev-student-button', 'comment_submit_button'].forEach(id => {
|
||||
const element = document.getElementById(id)
|
||||
if (element) {
|
||||
element.disabled = disabled
|
||||
}
|
||||
})
|
||||
|
||||
// disables the student picker dropdown when recording
|
||||
const studentPicker = document.getElementById('combo_box_container')
|
||||
if (studentPicker) {
|
||||
studentPicker.style.pointerEvents = disabled ? 'none' : 'auto'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<CanvasMediaRecorder
|
||||
onSaveFile={onSaveFile}
|
||||
onModalShowToggle={onModalShowToggle}
|
||||
indicatorBarMountPointId={indicatorBarMountPointId}
|
||||
/>,
|
||||
element
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue