diff --git a/bigbluebutton-html5/imports/api/breakouts/server/handlers/userBreakoutChanged.js b/bigbluebutton-html5/imports/api/breakouts/server/handlers/userBreakoutChanged.js new file mode 100644 index 0000000000..dfb999cdc0 --- /dev/null +++ b/bigbluebutton-html5/imports/api/breakouts/server/handlers/userBreakoutChanged.js @@ -0,0 +1,57 @@ +import Breakouts from '/imports/api/breakouts'; +import Logger from '/imports/startup/server/logger'; +import { check } from 'meteor/check'; + +export default function userBreakoutChanged({ body }) { + check(body, Object); + + const { + parentId, + userId, + fromBreakoutId, + toBreakoutId, + redirectToHtml5JoinUrl, + } = body; + + check(parentId, String); + check(userId, String); + check(fromBreakoutId, String); + check(toBreakoutId, String); + check(redirectToHtml5JoinUrl, String); + + const oldBreakoutSelector = { + parentMeetingId: parentId, + breakoutId: fromBreakoutId, + }; + + const newBreakoutSelector = { + parentMeetingId: parentId, + breakoutId: toBreakoutId, + }; + + const oldModifier = { + $unset: { + [`url_${userId}`]: '', + }, + }; + + const newModifier = { + $set: { + [`url_${userId}`]: { + redirectToHtml5JoinUrl, + insertedTime: new Date().getTime(), + }, + }, + }; + + try { + const numberAffectedOld = Breakouts.update(oldBreakoutSelector, oldModifier); + const numberAffectedNew = Breakouts.update(newBreakoutSelector, newModifier); + + if (numberAffectedOld && numberAffectedNew) { + Logger.info(`Updated user breakout for userId=${userId}`); + } + } catch (err) { + Logger.error(`Updating user breakout: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/api/breakouts/server/methods.js b/bigbluebutton-html5/imports/api/breakouts/server/methods.js index 6ad26cd142..27b26c4dbf 100755 --- a/bigbluebutton-html5/imports/api/breakouts/server/methods.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/methods.js @@ -4,6 +4,7 @@ import requestJoinURL from './methods/requestJoinURL'; import endAllBreakouts from './methods/endAllBreakouts'; import setBreakoutsTime from '/imports/api/breakouts/server/methods/setBreakoutsTime'; import sendMessageToAllBreakouts from './methods/sendMessageToAllBreakouts'; +import moveUser from '/imports/api/breakouts/server/methods/moveUser'; Meteor.methods({ requestJoinURL, @@ -11,4 +12,5 @@ Meteor.methods({ endAllBreakouts, setBreakoutsTime, sendMessageToAllBreakouts, + moveUser, }); diff --git a/bigbluebutton-html5/imports/api/breakouts/server/methods/moveUser.js b/bigbluebutton-html5/imports/api/breakouts/server/methods/moveUser.js new file mode 100644 index 0000000000..e017cee79c --- /dev/null +++ b/bigbluebutton-html5/imports/api/breakouts/server/methods/moveUser.js @@ -0,0 +1,32 @@ +import { Meteor } from 'meteor/meteor'; +import RedisPubSub from '/imports/startup/server/redis'; +import { extractCredentials } from '/imports/api/common/server/helpers'; +import { check } from 'meteor/check'; +import Logger from '/imports/startup/server/logger'; + +export default function moveUser(fromBreakoutId, toBreakoutId, userIdToMove) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'ChangeUserBreakoutReqMsg'; + + try { + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + + const userId = userIdToMove || requesterUserId; + + return RedisPubSub.publishUserMessage( + CHANNEL, EVENT_NAME, meetingId, requesterUserId, + { + meetingId, + fromBreakoutId, + toBreakoutId, + userId, + }, + ); + } catch (err) { + Logger.error(`Exception while invoking method moveUser ${err.stack}`); + } +} 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 index 21d1704bc5..f2c1028f08 100755 --- 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 @@ -9,6 +9,7 @@ import Modal from '/imports/ui/components/common/modal/fullscreen/component'; import { withModalMounter } from '/imports/ui/components/common/modal/service'; import SortList from './sort-user-list/component'; import Styled from './styles'; +import Icon from '/imports/ui/components/common/icon/component.jsx'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; @@ -21,6 +22,26 @@ const intlMessages = defineMessages({ id: 'app.createBreakoutRoom.modalDesc', description: 'modal description', }, + breakoutRoomUpdateDesc: { + id: 'app.updateBreakoutRoom.modalDesc', + description: 'update modal description', + }, + cancelLabel: { + id: 'app.updateBreakoutRoom.cancelLabel', + description: 'used in the button that close update modal', + }, + updateTitle: { + id: 'app.updateBreakoutRoom.title', + description: 'update breakout title', + }, + updateConfirm: { + id: 'app.updateBreakoutRoom.confirm', + description: 'Update to breakout confirm button label', + }, + resetUserRoom: { + id: 'app.update.resetRoom', + description: 'Reset user room button label', + }, confirmButton: { id: 'app.createBreakoutRoom.confirm', description: 'confirm button label', @@ -153,7 +174,7 @@ const propTypes = { meetingName: PropTypes.string.isRequired, users: PropTypes.arrayOf(PropTypes.object).isRequired, createBreakoutRoom: PropTypes.func.isRequired, - getUsersNotAssigned: PropTypes.func.isRequired, + getUsersNotJoined: PropTypes.func.isRequired, getBreakouts: PropTypes.func.isRequired, sendInvitation: PropTypes.func.isRequired, mountModal: PropTypes.func.isRequired, @@ -192,10 +213,11 @@ class BreakoutRoom extends PureComponent { this.removeRoomUsers = this.removeRoomUsers.bind(this); this.renderErrorMessages = this.renderErrorMessages.bind(this); this.renderJoinedUsers = this.renderJoinedUsers.bind(this); + this.onUpdateBreakouts = this.onUpdateBreakouts.bind(this); this.state = { numberOfRooms: MIN_BREAKOUT_ROOMS, - seletedId: '', + selectedId: '', users: [], durationTime: 15, freeJoin: false, @@ -220,7 +242,7 @@ class BreakoutRoom extends PureComponent { componentDidMount() { const { - isInvitation, breakoutJoinedUsers, getLastBreakouts, groups, + isInvitation, breakoutJoinedUsers, getLastBreakouts, groups, isUpdate, } = this.props; this.setRoomUsers(); if (isInvitation) { @@ -231,6 +253,30 @@ class BreakoutRoom extends PureComponent { }); } + if (isUpdate) { + const usersToMerge = [] + breakoutJoinedUsers.forEach((breakout) => { + breakout.joinedUsers.forEach((user) => { + usersToMerge.push({ + userId: user.userId, + userName: user.name, + from: breakout.sequence, + room: breakout.sequence, + isModerator: user.role === ROLE_MODERATOR, + joined: true, + }); + }); + }); + this.setState((prevState) => { + return { + users: [ + ...prevState.users, + ...usersToMerge, + ], + }; + }); + } + const lastBreakouts = getLastBreakouts(); if (lastBreakouts.length > 0) { this.populateWithLastBreakouts(lastBreakouts); @@ -424,6 +470,51 @@ class BreakoutRoom extends PureComponent { this.handleDismiss(); } + getBreakoutBySequence(sequence) { + const { getBreakouts } = this.props; + const breakouts = getBreakouts(); + + return breakouts.find((breakout) => breakout.sequence === sequence); + } + + changeUserBreakout(fromBreakoutId, toBreakoutId, userId) { + const { moveUser } = this.props; + + moveUser(fromBreakoutId, toBreakoutId, userId); + } + + onUpdateBreakouts() { + const { users } = this.state; + const { sendInvitation } = this.props; + const leastOneUserIsValid = users.some((user) => user.from !== user.room); + + if (!leastOneUserIsValid) { + this.setState({ leastOneUserIsValid }); + } + + users.forEach((user) => { + const { from, room } = user; + let { userId } = user; + + if (from === room || room === 0) return; + + const toBreakout = this.getBreakoutBySequence(room); + const { breakoutId: toBreakoutId } = toBreakout; + + if (!user.joined) return sendInvitation(toBreakoutId, userId); + + userId = userId.split('-')[0]; + const fromBreakout = this.getBreakoutBySequence(from); + const { breakoutId: fromBreakoutId } = fromBreakout; + + if (toBreakout.freeJoin) return sendInvitation(toBreakoutId, userId); + + this.changeUserBreakout(fromBreakoutId, toBreakoutId, userId); + }); + + this.handleDismiss(); + } + onAssignRandomly() { const { numberOfRooms } = this.state; const { users } = this.state; @@ -463,15 +554,16 @@ class BreakoutRoom extends PureComponent { } setRoomUsers() { - const { users, getUsersNotAssigned } = this.props; + const { users, getUsersNotJoined } = this.props; const { users: stateUsers } = this.state; const stateUsersId = stateUsers.map((user) => user.userId); - const roomUsers = getUsersNotAssigned(users) + const roomUsers = getUsersNotJoined(users) .filter((user) => !stateUsersId.includes(user.userId)) .map((user) => ({ userId: user.userId, userName: user.name, isModerator: user.role === ROLE_MODERATOR, + from: 0, room: 0, })); @@ -532,7 +624,7 @@ class BreakoutRoom extends PureComponent { const usersCopy = [...users]; - usersCopy[idxUser].room = room; + if (idxUser >= 0) usersCopy[idxUser].room = room; this.setState({ users: usersCopy, @@ -691,7 +783,7 @@ class BreakoutRoom extends PureComponent { ev.preventDefault(); const data = ev.dataTransfer.getData('text'); this.changeUserRoom(data, room); - this.setState({ seletedId: '' }); + this.setState({ selectedId: '' }); }; const changeRoomName = (position) => (ev) => { @@ -767,6 +859,7 @@ class BreakoutRoom extends PureComponent { const { intl, isInvitation, + isUpdate, } = this.props; const { numberOfRooms, @@ -774,7 +867,7 @@ class BreakoutRoom extends PureComponent { numberOfRoomsIsValid, durationIsValid, } = this.state; - if (isInvitation) return null; + if (isInvitation || isUpdate) return null; return ( @@ -905,8 +998,8 @@ class BreakoutRoom extends PureComponent { } renderCheckboxes() { - const { intl, isInvitation, isBreakoutRecordable } = this.props; - if (isInvitation) return null; + const { intl, isInvitation, isUpdate, isBreakoutRecordable } = this.props; + if (isInvitation || isUpdate) return null; const { freeJoin, record, @@ -946,14 +1039,14 @@ class BreakoutRoom extends PureComponent { renderUserItemByRoom(room) { const { leastOneUserIsValid, - seletedId, + selectedId, } = this.state; const { intl, isMe } = this.props; const dragStart = (ev) => { ev.dataTransfer.setData('text', ev.target.id); - this.setState({ seletedId: ev.target.id }); + this.setState({ selectedId: ev.target.id }); if (!leastOneUserIsValid) { this.setState({ leastOneUserIsValid: true }); @@ -961,7 +1054,7 @@ class BreakoutRoom extends PureComponent { }; const dragEnd = () => { - this.setState({ seletedId: '' }); + this.setState({ selectedId: '' }); }; return this.getUserByRoom(room) @@ -970,14 +1063,27 @@ class BreakoutRoom extends PureComponent { tabIndex={-1} id={`roomUserItem-${user.userId}`} key={user.userId} - selected={seletedId === user.userId} + selected={selectedId.replace('roomUserItem-', '') === user.userId.replace('roomUserItem-', '')} disabled={false} + highlight={room !== user.from} draggable onDragStart={dragStart} onDragEnd={dragEnd} > - {user.userName} - {(isMe(user.userId)) ? ` (${intl.formatMessage(intlMessages.you)})` : ''} + + {user.userName} + {(isMe(user.userId)) ? ` (${intl.formatMessage(intlMessages.you)})` : ''} + + { room !== user.from ? ( + this.changeUserRoom(user.userId, user.from)} + > + + + ) : null } )); } @@ -1112,16 +1218,18 @@ class BreakoutRoom extends PureComponent { } renderTitle() { - const { intl } = this.props; + const { intl, isUpdate } = this.props; return ( - {intl.formatMessage(intlMessages.breakoutRoomDesc)} + { isUpdate + ? intl.formatMessage(intlMessages.breakoutRoomUpdateDesc) + : intl.formatMessage(intlMessages.breakoutRoomDesc) } ); } render() { - const { intl, isInvitation } = this.props; + const { intl, isInvitation, isUpdate } = this.props; const { preventClosing, leastOneUserIsValid, @@ -1138,14 +1246,18 @@ class BreakoutRoom extends PureComponent { title={ isInvitation ? intl.formatMessage(intlMessages.invitationTitle) + : isUpdate + ? intl.formatMessage(intlMessages.updateTitle) : intl.formatMessage(intlMessages.breakoutRoomTitle) } confirm={ { label: isInvitation ? intl.formatMessage(intlMessages.invitationConfirm) + : isUpdate + ? intl.formatMessage(intlMessages.updateConfirm) : intl.formatMessage(intlMessages.confirmButton), - callback: isInvitation ? this.onInviteBreakout : this.onCreateBreakouts, + callback: isInvitation ? this.onInviteBreakout : isUpdate ? this.onUpdateBreakouts : this.onCreateBreakouts, disabled: !leastOneUserIsValid || !numberOfRoomsIsValid || !roomNameDuplicatedIsValid @@ -1156,7 +1268,9 @@ class BreakoutRoom extends PureComponent { } dismiss={{ callback: this.handleDismiss, - label: intl.formatMessage(intlMessages.dismissLabel), + label: isUpdate + ? intl.formatMessage(intlMessages.cancelLabel) + : intl.formatMessage(intlMessages.dismissLabel), }} preventClosing={preventClosing} > diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/container.jsx index b5a1530f7e..5c50fa7279 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/container.jsx @@ -20,7 +20,7 @@ export default withTracker(() => ({ getBreakouts: ActionsBarService.getBreakouts, getLastBreakouts: ActionsBarService.getLastBreakouts, getBreakoutUserWasIn: BreakoutRoomService.getBreakoutUserWasIn, - getUsersNotAssigned: ActionsBarService.getUsersNotAssigned, + getUsersNotJoined: ActionsBarService.getUsersNotJoined, sendInvitation: ActionsBarService.sendInvitation, breakoutJoinedUsers: ActionsBarService.breakoutJoinedUsers(), users: ActionsBarService.users(), @@ -28,4 +28,5 @@ export default withTracker(() => ({ isMe: ActionsBarService.isMe, meetingName: ActionsBarService.meetingName(), amIModerator: ActionsBarService.amIModerator(), + moveUser: ActionsBarService.moveUser, }))(CreateBreakoutRoomContainer); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js index 08f6487a50..ae96a7f2e7 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js @@ -12,12 +12,15 @@ import { colorWhite, colorPrimary, colorBlueLight, + colorBlueLightest, + colorGrayLightest, } from '/imports/ui/stylesheets/styled-components/palette'; -import { fontSizeSmall, fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography'; +import { fontSizeSmall, fontSizeBase, fontSizeSmaller } from '/imports/ui/stylesheets/styled-components/typography'; import { borderRadius, borderSize, lgPaddingX, + lgPaddingY, } from '/imports/ui/stylesheets/styled-components/general'; const BoxContainer = styled.div` @@ -66,9 +69,10 @@ const FreeJoinLabel = styled.label` const BreakoutNameInput = styled.input` width: 100%; text-align: left; - font-weight: normal; - padding: .25rem; + font-weight: 600; + padding: .25rem .25rem .25rem 0; margin: 0; + border: none; &::placeholder { color: ${colorGray}; opacity: 1; @@ -79,8 +83,9 @@ const BreakoutBox = styled(ScrollboxVertical)` width: 100%; min-height: 6rem; max-height: 8rem; - border: 1px solid ${colorGrayLighter}; - border-radius: ${borderRadius}; + border: 1px solid ${colorGrayLightest}; + border-radius: ${borderRadius}; + padding: ${lgPaddingY} 0; `; const SpanWarn = styled.span` @@ -228,12 +233,21 @@ const RoomUserItem = styled.p` text-overflow: ellipsis; white-space: nowrap; cursor: pointer; - border-bottom: solid .5px ${colorGrayLighter}; + display: flex; + justify-content: space-between; [dir="rtl"] & { padding: .25rem .25rem .25rem 0; } + span.close { + display: flex; + flex-direction: column; + justify-content: center; + margin-right: 5px; + font-size: ${fontSizeSmaller}; + } + ${({ selected }) => selected && ` background-color: ${colorPrimary}; color: ${colorWhite}; @@ -243,6 +257,10 @@ const RoomUserItem = styled.p` cursor: not-allowed; color: ${colorGrayLighter}; `} + + ${({ highlight }) => highlight && ` + background-color: ${colorBlueLightest}; + `} `; const LockIcon = styled.span` diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js index 146ab005c1..e84d1e7c1f 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js @@ -30,7 +30,7 @@ const currentBreakoutUsers = (user) => !Breakouts.findOne({ const filterBreakoutUsers = (filter) => (users) => users.filter(filter); -const getUsersNotAssigned = filterBreakoutUsers(currentBreakoutUsers); +const getUsersNotJoined = filterBreakoutUsers(currentBreakoutUsers); const takePresenterRole = () => makeCall('assignPresenter', Auth.userID); @@ -70,9 +70,10 @@ export default { breakoutJoinedUsers: () => Breakouts.find({ joinedUsers: { $exists: true }, }, { fields: { joinedUsers: 1, breakoutId: 1, sequence: 1 }, sort: { sequence: 1 } }).fetch(), + moveUser: (fromBreakoutId, toBreakoutId, userId) => makeCall('moveUser', fromBreakoutId, toBreakoutId, userId), getBreakouts, getLastBreakouts, - getUsersNotAssigned, + getUsersNotJoined, takePresenterRole, isSharingVideo: () => getVideoUrl(), }; diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/breakout-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-room/breakout-dropdown/component.jsx index d486604c09..6c95c85bc2 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/breakout-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/breakout-dropdown/component.jsx @@ -1,7 +1,9 @@ import React, { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; +import { withModalMounter } from '/imports/ui/components/common/modal/service'; import BBBMenu from "/imports/ui/components/common/menu/component"; import Button from '/imports/ui/components/common/button/component'; +import CreateBreakoutRoomModal from '/imports/ui/components/actions-bar/create-breakout-room/container'; const intlMessages = defineMessages({ options: { @@ -12,6 +14,11 @@ const intlMessages = defineMessages({ id: 'app.breakout.dropdown.manageDuration', description: 'Manage duration label', }, + manageUsers: { + id: 'app.breakout.dropdown.manageUsers', + description: 'Manage users label', + defaultMessage: 'Manage Users', + }, destroy: { id: 'app.breakout.dropdown.destroyAll', description: 'Destroy breakouts label', @@ -30,6 +37,7 @@ class BreakoutDropdown extends PureComponent { endAllBreakouts, isMeteorConnected, amIModerator, + mountModal, } = this.props; this.menuItems = []; @@ -45,6 +53,19 @@ class BreakoutDropdown extends PureComponent { } ); + this.menuItems.push( + { + key: 'updateBreakoutUsers', + dataTest: 'openUpdateBreakoutUsersModal', + label: intl.formatMessage(intlMessages.manageUsers), + onClick: () => { + mountModal( + + ); + } + } + ); + if (amIModerator) { this.menuItems.push( { @@ -101,4 +122,4 @@ class BreakoutDropdown extends PureComponent { } } -export default injectIntl(BreakoutDropdown); +export default withModalMounter(injectIntl(BreakoutDropdown)); diff --git a/bigbluebutton-html5/imports/ui/components/common/modal/fullscreen/styles.js b/bigbluebutton-html5/imports/ui/components/common/modal/fullscreen/styles.js index 6048436322..fc351816d9 100644 --- a/bigbluebutton-html5/imports/ui/components/common/modal/fullscreen/styles.js +++ b/bigbluebutton-html5/imports/ui/components/common/modal/fullscreen/styles.js @@ -2,16 +2,17 @@ import styled from 'styled-components'; import Styled from '../base/styles'; import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints'; import Button from '/imports/ui/components/common/button/component'; -import { borderSize, smPaddingX } from '/imports/ui/stylesheets/styled-components/general'; +import { borderSize, smPaddingX, borderRadius } from '/imports/ui/stylesheets/styled-components/general'; import { lineHeightComputed, modalTitleFw, } from '/imports/ui/stylesheets/styled-components/typography'; import { - colorGrayLighter, + colorGrayLightest, colorText, colorWhite, colorLink, + colorBlueLight, } from '/imports/ui/stylesheets/styled-components/palette'; const FullscreenModal = styled(Styled.BaseModal)` @@ -32,7 +33,7 @@ const FullscreenModal = styled(Styled.BaseModal)` const Header = styled.header` display: flex; padding: ${lineHeightComputed} 0; - border-bottom: ${borderSize} solid ${colorGrayLighter}; + border-bottom: ${borderSize} solid ${colorGrayLightest}; `; const Title = styled.h1` @@ -61,12 +62,26 @@ const Content = styled.div` const DismissButton = styled(Button)` flex: 0 1 48%; + border: 1px solid ${colorBlueLight}; + border-radius: ${borderRadius}; + color: ${colorBlueLight}; + + &:focus, + .buttonWrapper:focus:not([aria-disabled="true"]) & { + color: ${colorBlueLight}; + } + + &:hover, + .buttonWrapper:hover & { + color: ${colorBlueLight}; + } `; const ConfirmButton = styled(Button)` flex: 0 1 48%; color: ${colorWhite} !important; background-color: ${colorLink} !important; + border-width: 1px; ${({ popout }) => popout === 'popout' && ` & > i { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx index f03790dee8..68f2e647d9 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx @@ -213,7 +213,7 @@ class UserOptions extends PureComponent { meetingIsBreakout, hasBreakoutRoom, isBreakoutEnabled, - getUsersNotAssigned, + getUsersNotJoined, openLearningDashboardUrl, amIModerator, users, @@ -229,7 +229,7 @@ class UserOptions extends PureComponent { const canInviteUsers = amIModerator && !meetingIsBreakout && hasBreakoutRoom - && getUsersNotAssigned(users).length; + && getUsersNotJoined(users).length; const { locale } = intl; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx index 5f50d670c8..3c16e19d23 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx @@ -85,7 +85,7 @@ const UserOptionsContainer = withTracker((props) => { toggleStatus, isMeetingMuted: isMeetingMuteOnStart(), amIModerator: ActionsBarService.amIModerator(), - getUsersNotAssigned: ActionsBarService.getUsersNotAssigned, + getUsersNotJoined: ActionsBarService.getUsersNotJoined, hasBreakoutRoom: UserListService.hasBreakoutRoom(), isBreakoutEnabled: ActionsBarService.isBreakoutEnabled(), isBreakoutRecordable: ActionsBarService.isBreakoutRecordable(), diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index 33b4fd5102..b8b2feff80 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -913,6 +913,11 @@ "app.createBreakoutRoom.setTimeCancel": "Cancel", "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "The breakout rooms duration can't exceed the meeting remaining time.", "app.createBreakoutRoom.roomNameInputDesc": "Updates breakout room name", + "app.updateBreakoutRoom.modalDesc": "To update or invite a user, simply drag them into the desired room.", + "app.updateBreakoutRoom.cancelLabel": "Cancel", + "app.updateBreakoutRoom.title": "Update Breakout Rooms", + "app.updateBreakoutRoom.confirm": "Apply", + "app.update.resetRoom": "Reset user room", "app.externalVideo.start": "Share a new video", "app.externalVideo.title": "Share an external video", "app.externalVideo.input": "External Video URL",