From cfd2e3f94b70d1b03d327e5a6e09766fa0680175 Mon Sep 17 00:00:00 2001 From: Tainan Felipe <tainanfelipe214@gmail.com> Date: Fri, 28 Sep 2018 11:42:07 -0300 Subject: [PATCH] Add create breakout rooms modal --- .../actions-dropdown/component.jsx | 10 +- .../create-breakout-room/component.jsx | 107 +++++++++++++++ .../create-breakout-room/styles.scss | 77 +++++++++++ .../zoom-tool/component.jsx | 71 +++++----- .../zoom-tool/holdButton/component.jsx | 125 ++++++++++++++++++ 5 files changed, 358 insertions(+), 32 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss create mode 100644 bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index 4647a79570..d4eabdb9f7 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -9,6 +9,7 @@ import DropdownList from '/imports/ui/components/dropdown/list/component'; import DropdownListItem from '/imports/ui/components/dropdown/list/item/component'; import PresentationUploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container'; import { withModalMounter } from '/imports/ui/components/modal/service'; +import BreakoutRoom from '../create-breakout-room/component'; import { styles } from '../styles'; const propTypes = { @@ -71,12 +72,14 @@ class ActionsDropdown extends Component { constructor(props) { super(props); this.handlePresentationClick = this.handlePresentationClick.bind(this); + this.handleCreateBreakoutRoomClick = this.handleCreateBreakoutRoomClick.bind(this); } componentWillMount() { this.presentationItemId = _.uniqueId('action-item-'); this.videoItemId = _.uniqueId('action-item-'); this.recordId = _.uniqueId('action-item-'); + this.createBreakoutRoomId = _.uniqueId('action-item-'); } componentWillUpdate(nextProps) { @@ -124,8 +127,8 @@ class ActionsDropdown extends Component { icon="rooms" label={intl.formatMessage(intlMessages.createBreakoutRoom)} description={intl.formatMessage(intlMessages.createBreakoutRoomDesc)} - key={this.presentationItemId} - onClick={this.handlePresentationClick} + key={this.createBreakoutRoomId} + onClick={this.handleCreateBreakoutRoomClick} /> : null), ]); @@ -134,6 +137,9 @@ class ActionsDropdown extends Component { handlePresentationClick() { this.props.mountModal(<PresentationUploaderContainer />); } + handleCreateBreakoutRoomClick() { + this.props.mountModal(<BreakoutRoom />); + } render() { const { diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx new file mode 100644 index 0000000000..0785d99b7d --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx @@ -0,0 +1,107 @@ +import React, { Component } from 'react'; +import Modal from '/imports/ui/components/modal/fullscreen/component'; +import { defineMessages, injectIntl } from 'react-intl'; +import { styles } from './styles'; +import Icon from '../../icon/component'; +import _ from 'lodash'; +import HoldButton from '/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx'; + +const intlMessages = defineMessages({ + BreakoutRoomTitle: { + id: 'app.createBreakoutRoom.title', + description: 'modal title', + }, + BreakoutRoomDesc: { + id: 'app.createBreakoutRoom.modalDesc', + description: 'modal title', + }, +}); + +const MIN_BREAKOUT_ROOMS = 1; +const MAX_BREAKOUT_ROOMS = 8; + +class BreakoutRoom extends Component { + constructor(props) { + super(props); + this.changeNumberOfBreakouts = this.changeNumberOfBreakouts.bind(this); + this.changeDurationTime = this.changeDurationTime.bind(this); + this.increaseDurationTime = this.increaseDurationTime.bind(this); + this.decreaseDurationTime = this.decreaseDurationTime.bind(this); + + this.state = { + numberOfBreakouts: 1, + durationTime: 1, + }; + } + + changeNumberOfBreakouts(event) { + this.setState({ numberOfBreakouts: event.target.value }); + } + + changeDurationTime(event) { + this.setState({ durationTime: Number.parseInt(event.target.value, 10) || '' }); + } + + increaseDurationTime() { + this.setState({ durationTime: (1 * this.state.durationTime) + 1 }); + } + + decreaseDurationTime() { + this.setState({ durationTime: (1 * this.state.durationTime) - 1 }); + } + + render() { + const { intl } = this.props; + return ( + <Modal + title={intl.formatMessage(intlMessages.BreakoutRoomTitle)} + > + <div className={styles.content}> + <p className={styles.subTitle}> + {intl.formatMessage(intlMessages.BreakoutRoomDesc)} + </p> + <div className={styles.breakoutSettings}> + <label> + <p className={styles.labelText}>Number of Rooms</p> + <select name="numberOfBreakouts" className={styles.inputRooms} value={this.state.numberOfBreakouts} onChange={this.changeNumberOfBreakouts}> + { + _.range(MIN_BREAKOUT_ROOMS, MAX_BREAKOUT_ROOMS + 1).map(item => (<option key={_.uniqueId('value-')}>{item}</option>)) + } + </select> + </label> + <label > + <p className={styles.labelText}>Duration (minutes)</p> + <div className={styles.durationArea}> + <input type="number" className={styles.duration} min={MIN_BREAKOUT_ROOMS} value={this.state.durationTime} onChange={this.changeDurationTime} /> + <span> + <HoldButton + exec={this.decreaseDurationTime} + minBound={MIN_BREAKOUT_ROOMS} + value={this.state.durationTime} + > + <Icon + className={styles.iconsColor} + iconName="substract" + /> + </HoldButton> + <HoldButton + exec={this.increaseDurationTime} + > + <Icon + className={styles.iconsColor} + iconName="add" + /> + </HoldButton> + + </span> + </div> + </label> + <p className={styles.randomText}>Randomly Assign</p> + </div> + </div> + </Modal > + ); + } +} + +export default injectIntl(BreakoutRoom); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss new file mode 100644 index 0000000000..4a23328745 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss @@ -0,0 +1,77 @@ +@import "/imports/ui/stylesheets/variables/_all"; +input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; +} +.subTitle { + font-size: $font-size-base; + text-align: justify; + color: $color-gray-light; +} + +.breakoutSettings { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr; + grid-gap: 1rem; + @include mq($small-only) { + grid-template-columns: 1fr ; + grid-template-rows: 1fr 1fr 1fr; + flex-direction: column; + } +} + +.content { + display: flex; + flex-direction: column; +} + +.labelText { + color: $color-gray; + white-space: nowrap; + margin-bottom: .5rem; +} +.duration, +.inputRooms { + background-color: $color-white; + color: $color-gray; + border: 1px solid $color-gray-lighter; + border-radius: $border-radius; + width: 100%; + padding-top: .25rem; + padding-bottom: .25rem; + padding: .25rem 0 .25rem .25rem; +} + +.duration { + width: 50%; + text-align: center; + padding: .25rem; + &::placeholder { + color: $color-gray; + opacity: 1; + } +} + +.iconsColor { + cursor: pointer; + color: $color-gray-light; + font-size: $font-size-large; + @include mq($small-only) { + font-size: 2rem; + margin-left: .5rem; + } +} + +.durationArea { + display: flex; + align-items: center; + justify-content: space-between; +} + +.randomText { + color: $color-primary; + font-size: $font-size-small; + white-space: nowrap; + margin-bottom: .5rem; + align-self: flex-end; +} diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx index 0e976e08d0..ea759393af 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import { styles } from '../styles.scss'; +import HoldButton from './holdButton/component'; const DELAY_MILLISECONDS = 200; const STEP_TIME = 100; @@ -150,21 +151,26 @@ export default class ZoomTool extends Component { return ( [ ZoomTool.renderAriaLabelsDescs(), - (<Button - key="zoom-tool-1" - aria-labelledby="zoomInLabel" - aria-describedby="zoomInDesc" - role="button" - label="-" - icon="minus" - onClick={() => this.decrement()} - disabled={(value <= minBound)} - onMouseDown={() => this.mouseDownHandler(false)} - onMouseUp={this.mouseUpHandler} - onMouseLeave={this.mouseUpHandler} - className={styles.prevSlide} - hideLabel - />), + ( + <HoldButton + key="zoom-tool-1" + exec={this.decrement} + value={value} + minBound={minBound} + > + <Button + key="zoom-tool-1" + aria-labelledby="zoomInLabel" + aria-describedby="zoomInDesc" + role="button" + label="-" + icon="minus" + disabled={(value <= minBound)} + className={styles.prevSlide} + hideLabel + /> + </HoldButton> + ), ( <span key="zoom-tool-2" @@ -175,21 +181,26 @@ export default class ZoomTool extends Component { {`${this.state.value}%`} </span> ), - (<Button - key="zoom-tool-3" - aria-labelledby="zoomOutLabel" - aria-describedby="zoomOutDesc" - role="button" - label="+" - icon="plus" - onClick={() => this.increment()} - disabled={(value >= maxBound)} - onMouseDown={() => this.mouseDownHandler(true)} - onMouseUp={this.mouseUpHandler} - onMouseLeave={this.mouseUpHandler} - className={styles.skipSlide} - hideLabel - />), + ( + <HoldButton + key="zoom-tool-3" + exec={this.increment} + value={value} + maxBound={maxBound} + > + <Button + key="zoom-tool-3" + aria-labelledby="zoomOutLabel" + aria-describedby="zoomOutDesc" + role="button" + label="+" + icon="plus" + disabled={(value >= maxBound)} + className={styles.skipSlide} + hideLabel + /> + </HoldButton> + ), ] ); } diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx new file mode 100644 index 0000000000..dea39ebaaa --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx @@ -0,0 +1,125 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; + +const DELAY_MILLISECONDS = 300; +const STEP_TIME = 100; + +class HoldDownButton extends Component { + constructor(props) { + super(props); + this.mouseDownHandler = this.mouseDownHandler.bind(this); + this.mouseUpHandler = this.mouseUpHandler.bind(this); + this.touchStart = this.touchStart.bind(this); + this.touchEnd = this.touchEnd.bind(this); + this.execInterval = this.execInterval.bind(this); + this.onClick = this.onClick.bind(this); + this.setInt = 0; + this.state = { + mouseHolding: false, + }; + } + + onClick() { + const { + exec, + minBound, + maxBound, + value, + } = this.props; + const bounds = (value === maxBound) || (value === minBound); + if (bounds) return; + exec(); + } + + execInterval() { + const interval = () => { + clearInterval(this.setInt); + this.setInt = setInterval(this.onClick, STEP_TIME); + }; + + setTimeout(() => { + if (this.state.mouseHolding) { + interval(); + } + }, DELAY_MILLISECONDS); + } + + mouseDownHandler() { + this.setState({ + ...this.state, + mouseHolding: true, + }, () => { + this.execInterval(); + }); + } + + mouseUpHandler() { + this.setState({ + ...this.state, + mouseHolding: false, + }, () => clearInterval(this.setInt)); + } + + touchStart() { + this.setState({ + ...this.state, + mouseHolding: true, + }, () => { + this.execInterval(); + }); + } + + touchEnd() { + this.setState({ + ...this.state, + mouseHolding: false, + }, () => clearInterval(this.setInt)); + } + + + render() { + const { + key, + className, + children, + } = this.props; + + return ( + <span + role="button" + key={key} + onClick={this.onClick} + onMouseDown={this.mouseDownHandler} + onMouseUp={this.mouseUpHandler} + onTouchStart={this.touchStart} + onTouchEnd={this.touchEnd} + onMouseLeave={this.mouseUpHandler} + className={className} + > + {children} + </span> + ); + } +} + +const defaultProps = { + exec: () => {}, + minBound: null, + maxBound: Infinity, + key: _.uniqueId('holdButton-'), + value: 0, +}; + +const propTypes = { + key: PropTypes.string, + exec: PropTypes.func.isRequired, + minBound: PropTypes.number, + maxBound: PropTypes.number, + children: PropTypes.node.isRequired, +}; + +HoldDownButton.defaultProps = propTypes; +HoldDownButton.defaultProps = defaultProps; + +export default HoldDownButton; -- GitLab