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