bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx

352 lines
11 KiB
React
Raw Normal View History

import React, { Component } from 'react';
import PropTypes from 'prop-types';
2019-06-06 23:04:02 +08:00
import { defineMessages, injectIntl } from 'react-intl';
2018-09-18 02:02:52 +08:00
import browser from 'browser-detect';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import Button from '/imports/ui/components/button/component';
import { HUNDRED_PERCENT, MAX_PERCENT, STEP } from '/imports/utils/slideCalcUtils';
import cx from 'classnames';
2018-01-08 14:17:18 +08:00
import { styles } from './styles.scss';
2018-08-23 01:49:33 +08:00
import ZoomTool from './zoom-tool/component';
import FullscreenButtonContainer from '../../video-provider/fullscreen-button/container';
2019-03-12 00:21:12 +08:00
import Tooltip from '/imports/ui/components/tooltip/component';
import KEY_CODES from '/imports/utils/keyCodes';
2016-08-03 06:55:20 +08:00
const intlMessages = defineMessages({
previousSlideLabel: {
id: 'app.presentation.presentationToolbar.prevSlideLabel',
2017-04-10 23:50:03 +08:00
description: 'Previous slide button label',
},
2019-06-06 23:04:02 +08:00
previousSlideDesc: {
id: 'app.presentation.presentationToolbar.prevSlideDesc',
description: 'Aria description for when switching to previous slide',
},
nextSlideLabel: {
id: 'app.presentation.presentationToolbar.nextSlideLabel',
2017-04-26 22:08:47 +08:00
description: 'Next slide button label',
},
2019-06-06 23:04:02 +08:00
nextSlideDesc: {
id: 'app.presentation.presentationToolbar.nextSlideDesc',
description: 'Aria description for when switching to next slide',
},
noNextSlideDesc: {
id: 'app.presentation.presentationToolbar.noNextSlideDesc',
description: '',
},
noPrevSlideDesc: {
id: 'app.presentation.presentationToolbar.noPrevSlideDesc',
description: '',
},
skipSlideLabel: {
id: 'app.presentation.presentationToolbar.skipSlideLabel',
description: 'Aria label for when switching to a specific slide',
},
skipSlideDesc: {
id: 'app.presentation.presentationToolbar.skipSlideDesc',
description: 'Aria description for when switching to a specific slide',
},
goToSlide: {
id: 'app.presentation.presentationToolbar.goToSlide',
description: 'button for slide select',
},
2019-03-12 00:21:12 +08:00
selectLabel: {
id: 'app.presentation.presentationToolbar.selectLabel',
description: 'slide select label',
},
2018-10-25 01:54:19 +08:00
fitToWidth: {
id: 'app.presentation.presentationToolbar.fitToWidth',
2018-10-24 04:34:09 +08:00
description: 'button for fit to width',
},
2019-06-06 23:04:02 +08:00
fitToWidthDesc: {
id: 'app.presentation.presentationToolbar.fitWidthDesc',
description: 'Aria description to display the whole width of the slide',
},
fitToPage: {
id: 'app.presentation.presentationToolbar.fitToPage',
description: 'button label for fit to width',
},
2019-06-06 23:04:02 +08:00
fitToPageDesc: {
id: 'app.presentation.presentationToolbar.fitScreenDesc',
description: 'Aria description to display the whole slide',
},
presentationLabel: {
id: 'app.presentationUploder.title',
description: 'presentation area element label',
},
});
class PresentationToolbar extends Component {
2016-08-03 06:55:20 +08:00
constructor(props) {
super(props);
2018-09-25 20:47:49 +08:00
this.state = {
sliderValue: 100,
};
2016-08-03 06:55:20 +08:00
this.handleValuesChange = this.handleValuesChange.bind(this);
this.handleSkipToSlideChange = this.handleSkipToSlideChange.bind(this);
2018-08-23 01:49:33 +08:00
this.change = this.change.bind(this);
2019-06-06 23:04:02 +08:00
this.renderAriaDescs = this.renderAriaDescs.bind(this);
this.switchSlide = this.switchSlide.bind(this);
2018-08-23 01:49:33 +08:00
this.setInt = 0;
}
componentDidMount() {
document.addEventListener('keydown', this.switchSlide);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.switchSlide);
}
switchSlide(event) {
const { target, which } = event;
const isBody = target.nodeName === 'BODY';
const { actions } = this.props;
if (isBody) {
if ([KEY_CODES.ARROW_LEFT].includes(which)) {
actions.previousSlideHandler();
}
if ([KEY_CODES.ARROW_RIGHT].includes(which)) {
actions.nextSlideHandler();
}
}
}
handleSkipToSlideChange(event) {
2018-12-22 00:03:55 +08:00
const { actions } = this.props;
const requestedSlideNum = Number.parseInt(event.target.value, 10);
actions.skipToSlideHandler(requestedSlideNum);
2016-08-03 06:55:20 +08:00
}
handleValuesChange(event) {
2018-12-22 00:03:55 +08:00
const { sliderValue } = this.state;
2018-08-23 01:49:33 +08:00
this.setState(
{ sliderValue: event.target.value },
2018-12-22 00:03:55 +08:00
() => this.handleZoom(sliderValue),
2018-08-23 01:49:33 +08:00
);
2016-08-03 06:55:20 +08:00
}
2018-08-23 01:49:33 +08:00
change(value) {
2018-12-22 00:03:55 +08:00
const { zoomChanger } = this.props;
zoomChanger(value);
2018-08-23 01:49:33 +08:00
}
2019-06-06 23:04:02 +08:00
renderAriaDescs() {
const { intl } = this.props;
return (
<div hidden>
{/* Aria description's for toolbar buttons */}
<div id="prevSlideDesc">
{intl.formatMessage(intlMessages.previousSlideDesc)}
</div>
<div id="noPrevSlideDesc">
{intl.formatMessage(intlMessages.noPrevSlideDesc)}
</div>
<div id="nextSlideDesc">
{intl.formatMessage(intlMessages.nextSlideDesc)}
</div>
<div id="noNextSlideDesc">
{intl.formatMessage(intlMessages.noNextSlideDesc)}
</div>
<div id="skipSlideDesc">
{intl.formatMessage(intlMessages.skipSlideDesc)}
</div>
<div id="fitWidthDesc">
{intl.formatMessage(intlMessages.fitToWidthDesc)}
</div>
<div id="fitPageDesc">
{intl.formatMessage(intlMessages.fitToPageDesc)}
</div>
</div>
);
}
renderSkipSlideOpts(numberOfSlides) {
// Fill drop down menu with all the slides in presentation
const { intl } = this.props;
const optionList = [];
for (let i = 1; i <= numberOfSlides; i += 1) {
optionList.push((
<option
value={i}
key={i}
>
{
intl.formatMessage(intlMessages.goToSlide, { 0: i })
}
</option>));
}
return optionList;
}
2016-08-03 06:55:20 +08:00
render() {
const {
currentSlideNum,
numberOfSlides,
2018-12-22 00:03:55 +08:00
fitToWidthHandler,
fitToWidth,
2016-08-06 02:39:24 +08:00
actions,
intl,
2018-08-23 01:49:33 +08:00
zoom,
2019-02-12 21:35:52 +08:00
isFullscreen,
fullscreenRef,
2016-08-03 06:55:20 +08:00
} = this.props;
2018-09-18 02:02:52 +08:00
const BROWSER_RESULTS = browser();
2018-12-22 00:03:55 +08:00
const isMobileBrowser = BROWSER_RESULTS.mobile
|| BROWSER_RESULTS.os.includes('Android');
2019-03-12 00:21:12 +08:00
const tooltipDistance = 35;
2019-03-12 00:21:12 +08:00
2019-06-06 23:04:02 +08:00
const startOfSlides = !(currentSlideNum > 1);
const endOfSlides = !(currentSlideNum < numberOfSlides);
const prevSlideAriaLabel = startOfSlides
? intl.formatMessage(intlMessages.previousSlideLabel)
: `${intl.formatMessage(intlMessages.previousSlideLabel)} (${currentSlideNum <= 1 ? '' : (currentSlideNum - 1)})`;
const nextSlideAriaLabel = endOfSlides
? intl.formatMessage(intlMessages.nextSlideLabel)
: `${intl.formatMessage(intlMessages.nextSlideLabel)} (${currentSlideNum >= 1 ? (currentSlideNum + 1) : ''})`;
2016-08-03 06:55:20 +08:00
return (
<div id="presentationToolbarWrapper" className={styles.presentationToolbarWrapper}>
2019-06-06 23:04:02 +08:00
{this.renderAriaDescs()}
2019-03-12 00:21:12 +08:00
{<div />}
2018-08-23 01:49:33 +08:00
{
2019-03-12 00:21:12 +08:00
<div className={styles.presentationSlideControls}>
2018-08-23 01:49:33 +08:00
<Button
role="button"
2019-06-06 23:04:02 +08:00
aria-label={prevSlideAriaLabel}
aria-describedby={startOfSlides ? 'noPrevSlideDesc' : 'prevSlideDesc'}
disabled={startOfSlides}
2018-08-23 01:49:33 +08:00
color="default"
icon="left_arrow"
size="md"
onClick={actions.previousSlideHandler}
label={intl.formatMessage(intlMessages.previousSlideLabel)}
hideLabel
className={cx(styles.prevSlide, styles.presentationBtn)}
tooltipDistance={tooltipDistance}
2018-08-23 01:49:33 +08:00
/>
2019-03-12 00:21:12 +08:00
<Tooltip
tooltipDistance={tooltipDistance}
2019-03-12 00:21:12 +08:00
title={intl.formatMessage(intlMessages.selectLabel)}
className={styles.presentationBtn}
2018-08-23 01:49:33 +08:00
>
2019-03-12 00:21:12 +08:00
<select
id="skipSlide"
2019-06-06 23:04:02 +08:00
aria-label={intl.formatMessage(intlMessages.skipSlideLabel)}
2019-03-12 00:21:12 +08:00
aria-describedby="skipSlideDesc"
aria-live="polite"
aria-relevant="all"
value={currentSlideNum}
onChange={this.handleSkipToSlideChange}
className={styles.skipSlideSelect}
>
{this.renderSkipSlideOpts(numberOfSlides)}
</select>
</Tooltip>
2018-08-23 01:49:33 +08:00
<Button
role="button"
2019-06-06 23:04:02 +08:00
aria-label={nextSlideAriaLabel}
aria-describedby={endOfSlides ? 'noNextSlideDesc' : 'nextSlideDesc'}
disabled={endOfSlides}
2018-08-23 01:49:33 +08:00
color="default"
icon="right_arrow"
size="md"
onClick={actions.nextSlideHandler}
label={intl.formatMessage(intlMessages.nextSlideLabel)}
hideLabel
className={cx(styles.skipSlide, styles.presentationBtn)}
tooltipDistance={tooltipDistance}
2018-08-23 01:49:33 +08:00
/>
2019-03-12 00:21:12 +08:00
</div>
2018-08-23 01:49:33 +08:00
}
{
2019-03-12 00:21:12 +08:00
<div className={styles.presentationZoomControls}>
2019-02-12 21:35:52 +08:00
{
!isMobileBrowser
? (
<ZoomTool
zoomValue={zoom}
change={this.change}
minBound={HUNDRED_PERCENT}
maxBound={MAX_PERCENT}
step={STEP}
tooltipDistance={tooltipDistance}
2019-02-12 21:35:52 +08:00
/>
)
: null
}
<Button
role="button"
2019-06-06 23:04:02 +08:00
aria-describedby={fitToWidth ? 'fitPageDesc' : 'fitWidthDesc'}
aria-label={fitToWidth
? `${intl.formatMessage(intlMessages.presentationLabel)} ${intl.formatMessage(intlMessages.fitToPage)}`
: `${intl.formatMessage(intlMessages.presentationLabel)} ${intl.formatMessage(intlMessages.fitToWidth)}`
}
2019-02-12 21:35:52 +08:00
color="default"
icon="fit_to_width"
size="md"
circle={false}
onClick={fitToWidthHandler}
label={fitToWidth
? intl.formatMessage(intlMessages.fitToPage)
: intl.formatMessage(intlMessages.fitToWidth)
}
2019-02-12 21:35:52 +08:00
hideLabel
className={cx(styles.fitToWidth, styles.presentationBtn)}
tooltipDistance={tooltipDistance}
2019-02-12 21:35:52 +08:00
/>
{
!isFullscreen
&& (
<FullscreenButtonContainer
fullscreenRef={fullscreenRef}
elementName={intl.formatMessage(intlMessages.presentationLabel)}
tooltipDistance={tooltipDistance}
2019-02-12 21:35:52 +08:00
dark
className={styles.presentationBtn}
2018-12-22 00:03:55 +08:00
/>
2019-02-12 21:35:52 +08:00
)
}
2019-03-12 00:21:12 +08:00
</div>
2018-08-23 01:49:33 +08:00
}
2016-08-03 06:55:20 +08:00
</div>
);
}
}
PresentationToolbar.propTypes = {
// Number of current slide being displayed
currentSlideNum: PropTypes.number.isRequired,
// Total number of slides in this presentation
numberOfSlides: PropTypes.number.isRequired,
// Actions required for the presenter toolbar
actions: PropTypes.shape({
nextSlideHandler: PropTypes.func.isRequired,
previousSlideHandler: PropTypes.func.isRequired,
skipToSlideHandler: PropTypes.func.isRequired,
}).isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
zoomChanger: PropTypes.func.isRequired,
fitToWidthHandler: PropTypes.func.isRequired,
fitToWidth: PropTypes.bool.isRequired,
2019-05-03 01:35:13 +08:00
fullscreenRef: PropTypes.instanceOf(Element),
isFullscreen: PropTypes.bool.isRequired,
zoom: PropTypes.number.isRequired,
};
2019-05-03 01:35:13 +08:00
PresentationToolbar.defaultProps = {
fullscreenRef: null,
};
export default injectWbResizeEvent(injectIntl(PresentationToolbar));