diff --git a/bigbluebutton-html5/imports/api/breakouts/server/eventHandlers.js b/bigbluebutton-html5/imports/api/breakouts/server/eventHandlers.js old mode 100644 new mode 100755 index 40df1e7d3d..48d54c7de9 --- a/bigbluebutton-html5/imports/api/breakouts/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/eventHandlers.js @@ -6,5 +6,6 @@ import handleBreakoutClosed from './handlers/breakoutClosed'; RedisPubSub.on('BreakoutRoomStartedEvtMsg', handleBreakoutStarted); RedisPubSub.on('BreakoutRoomJoinURLEvtMsg', handleBreakoutJoinURL); +RedisPubSub.on('RequestBreakoutJoinURLRespMsg', handleBreakoutJoinURL); RedisPubSub.on('BreakoutRoomsTimeRemainingUpdateEvtMsg', handleUpdateTimeRemaining); RedisPubSub.on('BreakoutRoomEndedEvtMsg', handleBreakoutClosed); diff --git a/bigbluebutton-html5/imports/api/breakouts/server/methods.js b/bigbluebutton-html5/imports/api/breakouts/server/methods.js old mode 100644 new mode 100755 index 6ce8893022..9958a42d5e --- a/bigbluebutton-html5/imports/api/breakouts/server/methods.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/methods.js @@ -1,3 +1,6 @@ import { Meteor } from 'meteor/meteor'; +import requestJoinURL from './methods/requestJoinURL'; -Meteor.methods({}); +Meteor.methods({ + requestJoinURL, +}); diff --git a/bigbluebutton-html5/imports/api/breakouts/server/methods/requestJoinURL.js b/bigbluebutton-html5/imports/api/breakouts/server/methods/requestJoinURL.js new file mode 100755 index 0000000000..a2a5c19789 --- /dev/null +++ b/bigbluebutton-html5/imports/api/breakouts/server/methods/requestJoinURL.js @@ -0,0 +1,25 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; + + +export default function requestJoinURL(credentials, { breakoutId }) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + + const { meetingId, requesterUserId, requesterToken } = credentials; + + check(meetingId, String); + check(requesterUserId, String); + check(requesterToken, String); + + const eventName = 'RequestBreakoutJoinURLReqMsg'; + return RedisPubSub.publishUserMessage( + CHANNEL, eventName, meetingId, requesterUserId, + { + meetingId, + breakoutId, + userId: requesterUserId, + }, + ); +} diff --git a/bigbluebutton-html5/imports/api/breakouts/server/publishers.js b/bigbluebutton-html5/imports/api/breakouts/server/publishers.js old mode 100644 new mode 100755 index e0828ded3f..98f321c29a --- a/bigbluebutton-html5/imports/api/breakouts/server/publishers.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/publishers.js @@ -12,12 +12,18 @@ function breakouts(credentials) { Logger.info(`Publishing Breakouts for ${meetingId} ${requesterUserId}`); const selector = { - $or: [{ - parentMeetingId: meetingId, - 'users.userId': requesterUserId, - }, { - breakoutId: meetingId, - }, + $or: [ + { + parentMeetingId: meetingId, + freeJoin: true, + }, + { + parentMeetingId: meetingId, + 'users.userId': requesterUserId, + }, + { + breakoutId: meetingId, + }, ], }; diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx old mode 100644 new mode 100755 index f281b03f8f..d5ed0e13a6 --- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx @@ -3,6 +3,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { withModalMounter } from '/imports/ui/components/modal/service'; import Modal from '/imports/ui/components/modal/fullscreen/component'; import AudioService from '../audio/service'; +import { styles } from './styles'; const intlMessages = defineMessages({ title: { @@ -13,6 +14,10 @@ const intlMessages = defineMessages({ id: 'app.breakoutJoinConfirmation.message', description: 'Join breakout confim message', }, + freeJoinMessage: { + id: 'app.breakoutJoinConfirmation.freeJoinMessage', + description: 'Join breakout confim message', + }, confirmLabel: { id: 'app.breakoutJoinConfirmation.confirmLabel', description: 'Join confirmation button label', @@ -35,24 +40,54 @@ class BreakoutJoinConfirmation extends Component { constructor(props) { super(props); + this.state = { + selectValue: props.breakout.breakoutId, + }; + this.handleJoinBreakoutConfirmation = this.handleJoinBreakoutConfirmation.bind(this); + this.renderSelectMeeting = this.renderSelectMeeting.bind(this); + this.handleSelectChange = this.handleSelectChange.bind(this); } handleJoinBreakoutConfirmation() { const { - breakoutURL, + getURL, mountModal, + breakoutURL, + isFreeJoin, } = this.props; - + const url = isFreeJoin ? getURL(this.state.selectValue) : breakoutURL; // leave main room's audio when joining a breakout room AudioService.exitAudio(); - window.open(breakoutURL); + window.open(url); mountModal(null); } + handleSelectChange(e) { + const { value } = e.target; + this.setState({ selectValue: value }); + this.props.requestJoinURL(value); + } + + renderSelectMeeting() { + const { breakouts, intl } = this.props; + return ( +
+ {`${intl.formatMessage(intlMessages.freeJoinMessage)}`} + +
+ ); + } + render() { - const { intl, breakoutName } = this.props; + const { intl, breakoutName, isFreeJoin } = this.props; return ( - {`${intl.formatMessage(intlMessages.message)} ${breakoutName}?`} + { isFreeJoin ? this.renderSelectMeeting() : `${intl.formatMessage(intlMessages.message)} ${breakoutName}?`} ); } diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx new file mode 100755 index 0000000000..91a23bed43 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import Breakouts from '/imports/api/breakouts'; +import Auth from '/imports/ui/services/auth'; +import { makeCall } from '/imports/ui/services/api'; +import navBarService from '/imports/ui/components/nav-bar/service'; +import BreakoutJoinConfirmationComponent from './component'; + +const BreakoutJoinConfirmationContrainer = props => + (); + +const getURL = (breakoutId) => { + const currentUserId = Auth.userID; + const getBreakout = Breakouts.findOne({ breakoutId }); + const user = getBreakout ? getBreakout.users.find(u => u.userId === currentUserId) : ''; + if (user) return user.redirectToHtml5JoinURL; + return ''; +}; + +const requestJoinURL = (breakoutId) => { + makeCall('requestJoinURL', { + breakoutId, + }); +}; + +export default withTracker(({ breakout, mountModal, breakoutName }) => { + const isFreeJoin = breakout.freeJoin; + const { breakoutId } = breakout; + const url = getURL(breakoutId); + if (isFreeJoin && !url) { + requestJoinURL(breakoutId); + } + + return { + isFreeJoin, + mountModal, + breakoutName, + breakoutURL: url, + breakouts: navBarService.getBreakouts(), + requestJoinURL, + getURL, + }; +})(BreakoutJoinConfirmationContrainer); diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss new file mode 100755 index 0000000000..5225e42035 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss @@ -0,0 +1,14 @@ +@import "../../stylesheets/variables/_all"; + +.selectParent { + display: flex; + flex-direction: column; + align-items: center; +} + +.select { + background-color: $color-white; + width: 50%; + margin: 1rem; + border-color: $color-gray-lighter; +} \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx old mode 100644 new mode 100755 index 4842a69476..5d5c28b135 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import _ from 'lodash'; import cx from 'classnames'; import Icon from '/imports/ui/components/icon/component'; -import BreakoutJoinConfirmation from '/imports/ui/components/breakout-join-confirmation/component'; +import BreakoutJoinConfirmation from '/imports/ui/components/breakout-join-confirmation/container'; import Dropdown from '/imports/ui/components/dropdown/component'; import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; import DropdownContent from '/imports/ui/components/dropdown/content/component'; @@ -58,9 +58,9 @@ const defaultProps = { const SHORTCUTS_CONFIG = Meteor.settings.public.app.shortcuts; const TOGGLE_USERLIST_AK = SHORTCUTS_CONFIG.toggleUserList.accesskey; -const openBreakoutJoinConfirmation = (breakoutURL, breakoutName, mountModal) => +const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) => mountModal(); @@ -83,6 +83,9 @@ class NavBar extends Component { this.props.toggleUserList(); } + shouldComponentUpdate(nextProps) { + return nextProps.breakouts.length !== this.props.breakouts.length; + } componentDidUpdate(oldProps) { const { breakouts, @@ -102,10 +105,8 @@ class NavBar extends Component { return; } - const breakoutURL = getBreakoutJoinURL(breakout); - if (!this.state.didSendBreakoutInvite && !isBreakoutRoom) { - this.inviteUserToBreakout(breakout, breakoutURL); + this.inviteUserToBreakout(breakout); } }); @@ -114,13 +115,13 @@ class NavBar extends Component { } } - inviteUserToBreakout(breakout, breakoutURL) { + inviteUserToBreakout(breakout) { const { mountModal, } = this.props; this.setState({ didSendBreakoutInvite: true }, () => { - openBreakoutJoinConfirmation.call(this, breakoutURL, breakout.name, mountModal); + openBreakoutJoinConfirmation.call(this, breakout, breakout.name, mountModal); }); } @@ -136,6 +137,7 @@ class NavBar extends Component {

{presentationTitle}

); } + const breakoutItems = breakouts.map(breakout => this.renderBreakoutItem(breakout)); return ( @@ -148,7 +150,7 @@ class NavBar extends Component { placement="bottom" > - {breakouts.map(breakout => this.renderBreakoutItem(breakout))} + {breakoutItems} @@ -157,19 +159,17 @@ class NavBar extends Component { renderBreakoutItem(breakout) { const { - getBreakoutJoinURL, mountModal, } = this.props; const breakoutName = breakout.name; - const breakoutURL = getBreakoutJoinURL(breakout); return ( ); } diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx old mode 100644 new mode 100755 index ec1f569e17..a2c025fca6 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx @@ -59,7 +59,6 @@ export default withRouter(withTracker(({ location, router }) => { breakouts, currentUserId, meetingId, - getBreakoutJoinURL: Service.getBreakoutJoinURL, presentationTitle: meetingTitle, hasUnreadMessages: checkUnreadMessages(), isBreakoutRoom: meetingIsBreakout(), diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/service.js b/bigbluebutton-html5/imports/ui/components/nav-bar/service.js old mode 100644 new mode 100755 index 816ff4f643..56522370cb --- a/bigbluebutton-html5/imports/ui/components/nav-bar/service.js +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/service.js @@ -1,22 +1,8 @@ -import Auth from '/imports/ui/services/auth'; import Breakouts from '/imports/api/breakouts'; -const getBreakouts = () => Breakouts.find().fetch(); - -const getBreakoutJoinURL = (breakout) => { - const currentUserId = Auth.userID; - - if (breakout.users) { - const user = breakout.users.find(u => u.userId === currentUserId); - - if (user) { - return user.redirectToHtml5JoinURL; - } - } - return ''; -}; +const getBreakouts = () => Breakouts.find({}, { sort: { sequence: 1 } }).fetch(); export default { getBreakouts, - getBreakoutJoinURL, }; + diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index fea8077a65..2b74264f1a 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -242,6 +242,7 @@ "app.breakoutJoinConfirmation.confirmDesc": "Join you to the Breakout Room", "app.breakoutJoinConfirmation.dismissLabel": "Cancel", "app.breakoutJoinConfirmation.dismissDesc": "Closes and rejects Joining the Breakout Room", + "app.breakoutJoinConfirmation.freeJoinMessage": "Choose a Breakout Room to join", "app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {0}", "app.breakoutWillCloseMessage": "Time ended. Breakout Room will close soon", "app.calculatingBreakoutTimeRemaining": "Calculating remaining time...",