Merge pull request #5827 from Tainan404/issue-5562
Allow html5 user to join in any breakout room
This commit is contained in:
commit
97fb0e9b4f
1
bigbluebutton-html5/imports/api/breakouts/server/eventHandlers.js
Normal file → Executable file
1
bigbluebutton-html5/imports/api/breakouts/server/eventHandlers.js
Normal file → Executable file
@ -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);
|
||||
|
5
bigbluebutton-html5/imports/api/breakouts/server/methods.js
Normal file → Executable file
5
bigbluebutton-html5/imports/api/breakouts/server/methods.js
Normal file → Executable file
@ -1,3 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import requestJoinURL from './methods/requestJoinURL';
|
||||
|
||||
Meteor.methods({});
|
||||
Meteor.methods({
|
||||
requestJoinURL,
|
||||
});
|
||||
|
25
bigbluebutton-html5/imports/api/breakouts/server/methods/requestJoinURL.js
Executable file
25
bigbluebutton-html5/imports/api/breakouts/server/methods/requestJoinURL.js
Executable file
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
18
bigbluebutton-html5/imports/api/breakouts/server/publishers.js
Normal file → Executable file
18
bigbluebutton-html5/imports/api/breakouts/server/publishers.js
Normal file → Executable file
@ -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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
45
bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx
Normal file → Executable file
45
bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx
Normal file → Executable file
@ -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 (
|
||||
<div className={styles.selectParent}>
|
||||
{`${intl.formatMessage(intlMessages.freeJoinMessage)}`}
|
||||
<select
|
||||
className={styles.select}
|
||||
value={this.state.selectValue}
|
||||
onChange={this.handleSelectChange}
|
||||
>
|
||||
{breakouts.map(({ name, breakoutId }) => (<option key={breakoutId} value={breakoutId} >{name}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, breakoutName } = this.props;
|
||||
const { intl, breakoutName, isFreeJoin } = this.props;
|
||||
return (
|
||||
<Modal
|
||||
title={intl.formatMessage(intlMessages.title)}
|
||||
@ -66,7 +101,7 @@ class BreakoutJoinConfirmation extends Component {
|
||||
description: intl.formatMessage(intlMessages.dismissDesc),
|
||||
}}
|
||||
>
|
||||
{`${intl.formatMessage(intlMessages.message)} ${breakoutName}?`}
|
||||
{ isFreeJoin ? this.renderSelectMeeting() : `${intl.formatMessage(intlMessages.message)} ${breakoutName}?`}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -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 =>
|
||||
(<BreakoutJoinConfirmationComponent {...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);
|
@ -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;
|
||||
}
|
24
bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
Normal file → Executable file
24
bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
Normal file → Executable file
@ -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(<BreakoutJoinConfirmation
|
||||
breakoutURL={breakoutURL}
|
||||
breakout={breakout}
|
||||
breakoutName={breakoutName}
|
||||
/>);
|
||||
|
||||
@ -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 {
|
||||
<h1 className={styles.presentationTitle}>{presentationTitle}</h1>
|
||||
);
|
||||
}
|
||||
const breakoutItems = breakouts.map(breakout => this.renderBreakoutItem(breakout));
|
||||
|
||||
return (
|
||||
<Dropdown isOpen={this.state.isActionsOpen}>
|
||||
@ -148,7 +150,7 @@ class NavBar extends Component {
|
||||
placement="bottom"
|
||||
>
|
||||
<DropdownList>
|
||||
{breakouts.map(breakout => this.renderBreakoutItem(breakout))}
|
||||
{breakoutItems}
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
@ -157,19 +159,17 @@ class NavBar extends Component {
|
||||
|
||||
renderBreakoutItem(breakout) {
|
||||
const {
|
||||
getBreakoutJoinURL,
|
||||
mountModal,
|
||||
} = this.props;
|
||||
|
||||
const breakoutName = breakout.name;
|
||||
const breakoutURL = getBreakoutJoinURL(breakout);
|
||||
|
||||
return (
|
||||
<DropdownListItem
|
||||
className={styles.actionsHeader}
|
||||
key={_.uniqueId('action-header')}
|
||||
label={breakoutName}
|
||||
onClick={openBreakoutJoinConfirmation.bind(this, breakoutURL, breakoutName, mountModal)}
|
||||
onClick={openBreakoutJoinConfirmation.bind(this, breakout, breakoutName, mountModal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
1
bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx
Normal file → Executable file
1
bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx
Normal file → Executable file
@ -59,7 +59,6 @@ export default withRouter(withTracker(({ location, router }) => {
|
||||
breakouts,
|
||||
currentUserId,
|
||||
meetingId,
|
||||
getBreakoutJoinURL: Service.getBreakoutJoinURL,
|
||||
presentationTitle: meetingTitle,
|
||||
hasUnreadMessages: checkUnreadMessages(),
|
||||
isBreakoutRoom: meetingIsBreakout(),
|
||||
|
18
bigbluebutton-html5/imports/ui/components/nav-bar/service.js
Normal file → Executable file
18
bigbluebutton-html5/imports/ui/components/nav-bar/service.js
Normal file → Executable file
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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...",
|
||||
|
Loading…
Reference in New Issue
Block a user