import React, { PureComponent } from "react"; import PropTypes from "prop-types"; import { defineMessages, injectIntl } from "react-intl"; import deviceInfo from "/imports/utils/deviceInfo"; import injectWbResizeEvent from "/imports/ui/components/presentation/resize-wrapper/component"; import { HUNDRED_PERCENT, MAX_PERCENT, STEP, } from "/imports/utils/slideCalcUtils"; import Styled from "./styles"; import ZoomTool from "./zoom-tool/component"; import TooltipContainer from "/imports/ui/components/common/tooltip/container"; import KEY_CODES from "/imports/utils/keyCodes"; const intlMessages = defineMessages({ previousSlideLabel: { id: "app.presentation.presentationToolbar.prevSlideLabel", description: "Previous slide button label", }, previousSlideDesc: { id: "app.presentation.presentationToolbar.prevSlideDesc", description: "Aria description for when switching to previous slide", }, nextSlideLabel: { id: "app.presentation.presentationToolbar.nextSlideLabel", description: "Next slide button label", }, 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", }, selectLabel: { id: "app.presentation.presentationToolbar.selectLabel", description: "slide select label", }, fitToWidth: { id: "app.presentation.presentationToolbar.fitToWidth", description: "button for fit to width", }, 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", }, 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 PureComponent { constructor(props) { super(props); this.state = { curPageId: 1, }; this.handleSkipToSlideChange = this.handleSkipToSlideChange.bind(this); this.change = this.change.bind(this); this.renderAriaDescs = this.renderAriaDescs.bind(this); this.switchSlide = this.switchSlide.bind(this); this.nextSlideHandler = this.nextSlideHandler.bind(this); this.previousSlideHandler = this.previousSlideHandler.bind(this); this.fullscreenToggleHandler = this.fullscreenToggleHandler.bind(this); } componentDidMount() { document.addEventListener("keydown", this.switchSlide); } componentWillUnmount() { document.removeEventListener("keydown", this.switchSlide); } switchSlide(event) { const { target, which } = event; const isBody = target.nodeName === "BODY"; if (isBody) { switch (which) { case KEY_CODES.ARROW_LEFT: case KEY_CODES.PAGE_UP: this.previousSlideHandler(); break; case KEY_CODES.ARROW_RIGHT: case KEY_CODES.PAGE_DOWN: this.nextSlideHandler(); break; case KEY_CODES.ENTER: this.fullscreenToggleHandler(); break; default: } } } handleSkipToSlideChange(event) { const { skipToSlide, podId } = this.props; const requestedSlideNum = Number.parseInt(event.target.value, 10); if (event) event.currentTarget.blur(); // skipToSlide(requestedSlideNum, podId); this.props?.tldrawAPI?.changePage(requestedSlideNum); this.setState({ curPageId: parseInt(this.props?.tldrawAPI?.getPage()?.id), }); } nextSlideHandler(event) { const { nextSlide, currentSlideNum, numberOfSlides, podId } = this.props; if (event) event.currentTarget.blur(); nextSlide(currentSlideNum, numberOfSlides, podId); } previousSlideHandler(event) { const { previousSlide, currentSlideNum, podId } = this.props; if (event) event.currentTarget.blur(); previousSlide(currentSlideNum, podId); } fullscreenToggleHandler() { const { fullscreenElementId, isFullscreen, layoutContextDispatch, fullscreenAction, fullscreenRef, handleToggleFullScreen, } = this.props; handleToggleFullScreen(fullscreenRef); const newElement = isFullscreen ? "" : fullscreenElementId; layoutContextDispatch({ type: fullscreenAction, value: { element: newElement, group: "", }, }); } change(value) { const { zoomChanger } = this.props; zoomChanger(value); } renderAriaDescs() { const { intl } = this.props; return ( ); } 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( ); } return optionList; } render() { const { currentSlideNum, numberOfSlides, fitToWidthHandler, fitToWidth, intl, zoom, isMeteorConnected, isPollingEnabled, amIPresenter, currentSlidHasContent, parseCurrentSlideContent, startPoll, currentSlide, toolbarWidth, } = this.props; const { isMobile } = deviceInfo; const startOfSlides = parseInt(this.state.curPageId) === 1; const endOfSlides = parseInt(this.state.curPageId) === numberOfSlides; const prevSlideAriaLabel = startOfSlides ? intl.formatMessage(intlMessages.previousSlideLabel) : `${intl.formatMessage(intlMessages.previousSlideLabel)} (${ parseInt(this.props?.tldrawAPI?.getPage()?.id) <= 1 ? "" : parseInt(this.props?.tldrawAPI?.getPage()?.id) - 1 })`; const nextSlideAriaLabel = endOfSlides ? intl.formatMessage(intlMessages.nextSlideLabel) : `${intl.formatMessage(intlMessages.nextSlideLabel)} (${ parseInt(this.props?.tldrawAPI?.getPage()?.id) >= 1 ? parseInt(this.props?.tldrawAPI?.getPage()?.id) + 1 : "" })`; return ( {this.renderAriaDescs()} {
{isPollingEnabled ? ( ) : null}
} { { this.props?.tldrawAPI?.changePage( parseInt(this.props?.tldrawAPI?.getPage()?.id) - 1 ); this.setState({ curPageId: parseInt(this.props?.tldrawAPI?.getPage()?.id), }); }} label={intl.formatMessage(intlMessages.previousSlideLabel)} hideLabel data-test="prevSlide" /> {this.renderSkipSlideOpts(numberOfSlides)} { this.props?.tldrawAPI?.changePage( parseInt(this.props?.tldrawAPI?.getPage()?.id) + 1 ); this.setState({ curPageId: parseInt(this.props?.tldrawAPI?.getPage()?.id), }); }} label={intl.formatMessage(intlMessages.nextSlideLabel)} hideLabel data-test="nextSlide" /> } { {!isMobile ? ( ) : null} this.props.tldrawAPI.zoomToFit()} label={intl.formatMessage(intlMessages.fitToPage)} hideLabel /> }
); } } PresentationToolbar.propTypes = { // The Id for the current pod. Should always be default pod podId: PropTypes.string.isRequired, // 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 nextSlide: PropTypes.func.isRequired, previousSlide: PropTypes.func.isRequired, skipToSlide: PropTypes.func.isRequired, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, zoomChanger: PropTypes.func.isRequired, fitToWidthHandler: PropTypes.func.isRequired, fitToWidth: PropTypes.bool.isRequired, zoom: PropTypes.number.isRequired, isMeteorConnected: PropTypes.bool.isRequired, fullscreenElementId: PropTypes.string.isRequired, fullscreenAction: PropTypes.string.isRequired, isFullscreen: PropTypes.bool.isRequired, layoutContextDispatch: PropTypes.func.isRequired, }; export default injectWbResizeEvent(injectIntl(PresentationToolbar));