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:
Derek Williams 2024-04-23 10:45:25 -04:00
parent 3a01174aaa
commit b06cd5aa96
7 changed files with 201 additions and 9 deletions

View File

@ -97,3 +97,4 @@
@import 'bundles/context_cards';
@import 'bundles/rce';
@import 'pages/react_files/_FilePreview.scss';
@import 'components/media_recorder';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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