Merge pull request #18364 from gustavotrott/merge27-into-develop-21jul2023
Merge 2.7 into Develop
@ -0,0 +1,46 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
|
||||
|
||||
trait ClearAllUsersReactionCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: BaseMeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleClearAllUsersReactionCmdMsg(msg: ClearAllUsersReactionCmdMsg) {
|
||||
val isUserModerator = !permissionFailed(
|
||||
PermissionCheck.MOD_LEVEL,
|
||||
PermissionCheck.VIEWER_LEVEL,
|
||||
liveMeeting.users2x,
|
||||
msg.header.userId
|
||||
)
|
||||
|
||||
if (isUserModerator) {
|
||||
for {
|
||||
user <- Users2x.findAll(liveMeeting.users2x)
|
||||
} yield {
|
||||
//Don't clear away and RaiseHand
|
||||
Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, "none")
|
||||
}
|
||||
sendClearedAllUsersReactionEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to clear users reactions."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
def sendClearedAllUsersReactionEvtMsg(outGW: OutMsgRouter, meetingId: String, userId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(ClearedAllUsersReactionEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(ClearedAllUsersReactionEvtMsg.NAME, meetingId, userId)
|
||||
val body = ClearedAllUsersReactionEvtMsgBody()
|
||||
val event = ClearedAllUsersReactionEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ trait UsersApp2x
|
||||
with GetLockSettingsReqMsgHdlr
|
||||
with ChangeUserEmojiCmdMsgHdlr
|
||||
with ClearAllUsersEmojiCmdMsgHdlr
|
||||
with ClearAllUsersReactionCmdMsgHdlr
|
||||
with UserReactionTimeExpiredCmdMsgHdlr {
|
||||
|
||||
this: MeetingActor =>
|
||||
|
@ -264,6 +264,8 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[UserReactionTimeExpiredCmdMsg](envelope, jsonNode)
|
||||
case ClearAllUsersEmojiCmdMsg.NAME =>
|
||||
routeGenericMsg[ClearAllUsersEmojiCmdMsg](envelope, jsonNode)
|
||||
case ClearAllUsersReactionCmdMsg.NAME =>
|
||||
routeGenericMsg[ClearAllUsersReactionCmdMsg](envelope, jsonNode)
|
||||
case ChangeUserRoleCmdMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserRoleCmdMsg](envelope, jsonNode)
|
||||
|
||||
|
@ -391,6 +391,7 @@ class MeetingActor(
|
||||
case m: ChangeUserAwayReqMsg => usersApp.handleChangeUserAwayReqMsg(m)
|
||||
case m: UserReactionTimeExpiredCmdMsg => handleUserReactionTimeExpiredCmdMsg(m)
|
||||
case m: ClearAllUsersEmojiCmdMsg => handleClearAllUsersEmojiCmdMsg(m)
|
||||
case m: ClearAllUsersReactionCmdMsg => handleClearAllUsersReactionCmdMsg(m)
|
||||
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
|
||||
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m)
|
||||
case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m)
|
||||
|
@ -272,6 +272,20 @@ object ClearedAllUsersEmojiEvtMsg { val NAME = "ClearedAllUsersEmojiEvtMsg" }
|
||||
case class ClearedAllUsersEmojiEvtMsg(header: BbbClientMsgHeader, body: ClearedAllUsersEmojiEvtMsgBody) extends StandardMsg
|
||||
case class ClearedAllUsersEmojiEvtMsgBody()
|
||||
|
||||
/**
|
||||
* Sent from client about a mod clearing all users' Reaction.
|
||||
*/
|
||||
object ClearAllUsersReactionCmdMsg { val NAME = "ClearAllUsersReactionCmdMsg" }
|
||||
case class ClearAllUsersReactionCmdMsg(header: BbbClientMsgHeader, body: ClearAllUsersReactionCmdMsgBody) extends StandardMsg
|
||||
case class ClearAllUsersReactionCmdMsgBody(userId: String)
|
||||
|
||||
/**
|
||||
* Sent to all clients about clearing all users' Reaction.
|
||||
*/
|
||||
object ClearedAllUsersReactionEvtMsg { val NAME = "ClearedAllUsersReactionEvtMsg" }
|
||||
case class ClearedAllUsersReactionEvtMsg(header: BbbClientMsgHeader, body: ClearedAllUsersReactionEvtMsgBody) extends StandardMsg
|
||||
case class ClearedAllUsersReactionEvtMsgBody()
|
||||
|
||||
/**
|
||||
* Sent from client about a user mobile flag.
|
||||
*/
|
||||
|
@ -1,4 +1,6 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleSetUserReaction from './handlers/setUserReaction';
|
||||
import handleClearUsersReaction from './handlers/clearUsersReaction';
|
||||
|
||||
RedisPubSub.on('UserReactionEmojiChangedEvtMsg', handleSetUserReaction);
|
||||
RedisPubSub.on('ClearedAllUsersReactionEvtMsg', handleClearUsersReaction);
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { check } from 'meteor/check';
|
||||
import clearReactions from '../modifiers/clearReactions';
|
||||
|
||||
export default function handleClearUsersReaction({ body }, meetingId) {
|
||||
check(meetingId, String);
|
||||
clearReactions(meetingId);
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import setUserReaction from './methods/setUserReaction';
|
||||
import clearAllUsersReaction from './methods/clearAllUsersReaction';
|
||||
|
||||
Meteor.methods({
|
||||
setUserReaction,
|
||||
clearAllUsersReaction,
|
||||
});
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function clearAllUsersReaction() {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ClearAllUsersReactionCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
userId: requesterUserId,
|
||||
};
|
||||
|
||||
Logger.verbose('Sending clear all users reactions', {
|
||||
requesterUserId, meetingId,
|
||||
});
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method clearAllUsersReaction ${err.stack}`);
|
||||
}
|
||||
}
|
@ -45,13 +45,13 @@ const intlMessages = defineMessages({
|
||||
id: 'app.actionsBar.actionsDropdown.actionsLabel',
|
||||
description: 'Actions button label',
|
||||
},
|
||||
activateTimerLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.activateTimerLabel',
|
||||
description: 'Activate timer label',
|
||||
activateTimerStopwatchLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.activateTimerStopwatchLabel',
|
||||
description: 'Activate timer/stopwatch label',
|
||||
},
|
||||
deactivateTimerLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.deactivateTimerLabel',
|
||||
description: 'Deactivate timer label',
|
||||
deactivateTimerStopwatchLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel',
|
||||
description: 'Deactivate timer/stopwatch label',
|
||||
},
|
||||
presentationLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.presentationLabel',
|
||||
@ -263,8 +263,8 @@ class ActionsDropdown extends PureComponent {
|
||||
actions.push({
|
||||
icon: 'time',
|
||||
label: isTimerActive
|
||||
? intl.formatMessage(intlMessages.deactivateTimerLabel)
|
||||
: intl.formatMessage(intlMessages.activateTimerLabel),
|
||||
? intl.formatMessage(intlMessages.deactivateTimerStopwatchLabel)
|
||||
: intl.formatMessage(intlMessages.activateTimerStopwatchLabel),
|
||||
key: this.timerId,
|
||||
onClick: () => this.handleTimerClick(),
|
||||
});
|
||||
@ -403,7 +403,7 @@ class ActionsDropdown extends PureComponent {
|
||||
<BBBMenu
|
||||
customStyles={!isMobile ? customStyles : null}
|
||||
accessKey={OPEN_ACTIONS_AK}
|
||||
trigger={
|
||||
trigger={(
|
||||
<Styled.HideDropdownButton
|
||||
open={isDropdownOpen}
|
||||
hideLabel
|
||||
@ -416,7 +416,7 @@ class ActionsDropdown extends PureComponent {
|
||||
circle
|
||||
onClick={() => null}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
actions={children}
|
||||
opts={{
|
||||
id: 'actions-dropdown-menu',
|
||||
|
@ -6,7 +6,7 @@ import ActionsDropdown from './actions-dropdown/container';
|
||||
import AudioCaptionsButtonContainer from '/imports/ui/components/audio/captions/button/container';
|
||||
import CaptionsReaderMenuContainer from '/imports/ui/components/captions/reader-menu/container';
|
||||
import ScreenshareButtonContainer from '/imports/ui/components/actions-bar/screenshare/container';
|
||||
import InteractionsButtonContainer from '/imports/ui/components/actions-bar/interactions-button/container';
|
||||
import ReactionsButtonContainer from './reactions-button/container';
|
||||
import AudioControlsContainer from '../audio/audio-controls/container';
|
||||
import JoinVideoOptionsContainer from '../video-provider/video-button/container';
|
||||
import PresentationOptionsContainer from './presentation-options/component';
|
||||
@ -32,14 +32,14 @@ class ActionsBar extends PureComponent {
|
||||
|
||||
renderRaiseHand() {
|
||||
const {
|
||||
isInteractionsButtonEnabled, isRaiseHandButtonEnabled, setEmojiStatus, currentUser, intl,
|
||||
isReactionsButtonEnabled, isRaiseHandButtonEnabled, setEmojiStatus, currentUser, intl,
|
||||
} = this.props;
|
||||
|
||||
return (<>
|
||||
{isInteractionsButtonEnabled ?
|
||||
{isReactionsButtonEnabled ?
|
||||
<>
|
||||
<Styled.Separator />
|
||||
<InteractionsButtonContainer actionsBarRef={this.actionsBarRef} />
|
||||
<ReactionsButtonContainer actionsBarRef={this.actionsBarRef} />
|
||||
</> :
|
||||
isRaiseHandButtonEnabled ? <RaiseHandDropdownContainer {...{ setEmojiStatus, currentUser, intl }} />
|
||||
: null}
|
||||
|
@ -50,9 +50,9 @@ const SELECT_RANDOM_USER_ENABLED = Meteor.settings.public.selectRandomUser.enabl
|
||||
const RAISE_HAND_BUTTON_ENABLED = Meteor.settings.public.app.raiseHandActionButton.enabled;
|
||||
const RAISE_HAND_BUTTON_CENTERED = Meteor.settings.public.app.raiseHandActionButton.centered;
|
||||
|
||||
const isInteractionsButtonEnabled = () => {
|
||||
const INTERACTIONS_BUTTON_ENABLED = Meteor.settings.public.app.interactionsButton.enabled;
|
||||
return getFromUserSettings('enable-interactions-button', INTERACTIONS_BUTTON_ENABLED);
|
||||
const isReactionsButtonEnabled = () => {
|
||||
const REACTIONS_BUTTON_ENABLED = Meteor.settings.public.app.reactionsButton.enabled;
|
||||
return getFromUserSettings('enable-reactions-button', REACTIONS_BUTTON_ENABLED);
|
||||
};
|
||||
|
||||
export default withTracker(() => ({
|
||||
@ -75,7 +75,7 @@ export default withTracker(() => ({
|
||||
isSelectRandomUserEnabled: SELECT_RANDOM_USER_ENABLED,
|
||||
isRaiseHandButtonEnabled: RAISE_HAND_BUTTON_ENABLED,
|
||||
isRaiseHandButtonCentered: RAISE_HAND_BUTTON_CENTERED,
|
||||
isInteractionsButtonEnabled: isInteractionsButtonEnabled(),
|
||||
isReactionsButtonEnabled: isReactionsButtonEnabled(),
|
||||
isThereCurrentPresentation: Presentations.findOne({ meetingId: Auth.meetingID, current: true },
|
||||
{ fields: {} }),
|
||||
allowExternalVideo: isExternalVideoEnabled(),
|
||||
|
@ -8,21 +8,22 @@ import UserListService from '/imports/ui/components/user-list/service';
|
||||
|
||||
import Styled from '../styles';
|
||||
|
||||
const InteractionsButton = (props) => {
|
||||
const ReactionsButton = (props) => {
|
||||
const {
|
||||
intl,
|
||||
actionsBarRef,
|
||||
userId,
|
||||
raiseHand,
|
||||
isMobile,
|
||||
currentUserReaction,
|
||||
} = props;
|
||||
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
interactionsLabel: {
|
||||
id: 'app.actionsBar.interactions.interactions',
|
||||
description: 'interactions Label',
|
||||
reactionsLabel: {
|
||||
id: 'app.actionsBar.reactions.reactionsButtonLabel',
|
||||
description: 'reactions Label',
|
||||
},
|
||||
});
|
||||
|
||||
@ -34,13 +35,12 @@ const InteractionsButton = (props) => {
|
||||
};
|
||||
|
||||
const handleReactionSelect = (reaction) => {
|
||||
UserReactionService.setUserReaction(reaction);
|
||||
handleClose();
|
||||
const newReaction = currentUserReaction === reaction ? 'none' : reaction;
|
||||
UserReactionService.setUserReaction(newReaction);
|
||||
};
|
||||
|
||||
const handleRaiseHandButtonClick = () => {
|
||||
UserListService.setUserRaiseHand(userId, !raiseHand);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const renderReactionsBar = () => (
|
||||
@ -54,12 +54,12 @@ const InteractionsButton = (props) => {
|
||||
return (
|
||||
<BBBMenu
|
||||
trigger={(
|
||||
<Styled.InteractionsDropdown>
|
||||
<Styled.ReactionsDropdown>
|
||||
<Styled.RaiseHandButton
|
||||
data-test="InteractionsButton"
|
||||
data-test="ReactionsButton"
|
||||
icon="hand"
|
||||
label={intl.formatMessage(intlMessages.interactionsLabel)}
|
||||
description="Interactions"
|
||||
label={intl.formatMessage(intlMessages.reactionsLabel)}
|
||||
description="Reactions"
|
||||
ghost={!showEmojiPicker}
|
||||
onKeyPress={() => {}}
|
||||
onClick={() => setShowEmojiPicker(true)}
|
||||
@ -68,7 +68,7 @@ const InteractionsButton = (props) => {
|
||||
circle
|
||||
size="lg"
|
||||
/>
|
||||
</Styled.InteractionsDropdown>
|
||||
</Styled.ReactionsDropdown>
|
||||
)}
|
||||
renderOtherComponents={showEmojiPicker ? renderReactionsBar() : null}
|
||||
onCloseCallback={() => handleClose()}
|
||||
@ -82,7 +82,7 @@ const InteractionsButton = (props) => {
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
getcontentanchorel: null,
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'center' },
|
||||
transformOrigin: { vertical: 'bottom', horizontal: 'center' },
|
||||
}}
|
||||
@ -100,6 +100,6 @@ const propTypes = {
|
||||
layoutContextDispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
InteractionsButton.propTypes = propTypes;
|
||||
ReactionsButton.propTypes = propTypes;
|
||||
|
||||
export default InteractionsButton;
|
||||
export default ReactionsButton;
|
@ -2,11 +2,12 @@ import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { layoutSelectInput, layoutDispatch } from '/imports/ui/components/layout/context';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import InteractionsButton from './component';
|
||||
import ReactionsButton from './component';
|
||||
import actionsBarService from '../service';
|
||||
import UserReactionService from '/imports/ui/components/user-reaction/service';
|
||||
import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums';
|
||||
|
||||
const InteractionsButtonContainer = ({ ...props }) => {
|
||||
const ReactionsButtonContainer = ({ ...props }) => {
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
@ -15,7 +16,7 @@ const InteractionsButtonContainer = ({ ...props }) => {
|
||||
const isMobile = browserWidth <= SMALL_VIEWPORT_BREAKPOINT;
|
||||
|
||||
return (
|
||||
<InteractionsButton {...{
|
||||
<ReactionsButton {...{
|
||||
layoutContextDispatch, sidebarContentPanel, isMobile, ...props,
|
||||
}}
|
||||
/>
|
||||
@ -24,11 +25,13 @@ const InteractionsButtonContainer = ({ ...props }) => {
|
||||
|
||||
export default injectIntl(withTracker(() => {
|
||||
const currentUser = actionsBarService.currentUser();
|
||||
const currentUserReaction = UserReactionService.getUserReaction(currentUser.userId);
|
||||
|
||||
return {
|
||||
userId: currentUser.userId,
|
||||
emoji: currentUser.emoji,
|
||||
currentUserReaction: currentUserReaction.reaction,
|
||||
raiseHand: currentUser.raiseHand,
|
||||
};
|
||||
})(InteractionsButtonContainer));
|
||||
})(ReactionsButtonContainer));
|
||||
|
@ -96,7 +96,7 @@ const ButtonContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const InteractionsDropdown = styled.div`
|
||||
const ReactionsDropdown = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
@ -141,7 +141,7 @@ export default {
|
||||
Right,
|
||||
RaiseHandButton,
|
||||
ButtonContainer,
|
||||
InteractionsDropdown,
|
||||
ReactionsDropdown,
|
||||
Wrapper,
|
||||
Separator,
|
||||
};
|
||||
|
@ -75,6 +75,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.toast.clearedEmoji.label',
|
||||
description: 'message for cleared emoji status',
|
||||
},
|
||||
clearedReaction: {
|
||||
id: 'app.toast.clearedReactions.label',
|
||||
description: 'message for cleared reactions',
|
||||
},
|
||||
setEmoji: {
|
||||
id: 'app.toast.setEmoji.label',
|
||||
description: 'message when a user emoji has been set',
|
||||
|
@ -92,14 +92,16 @@ const AppContainer = (props) => {
|
||||
|
||||
const { focusedId } = cameraDock;
|
||||
|
||||
if(
|
||||
layoutContextDispatch
|
||||
&& (typeof meetingLayout != "undefined")
|
||||
&& (layoutType.current != meetingLayout)
|
||||
useEffect(() => {
|
||||
if (
|
||||
layoutContextDispatch
|
||||
&& (typeof meetingLayout !== 'undefined')
|
||||
&& (layoutType.current !== meetingLayout)
|
||||
) {
|
||||
layoutType.current = meetingLayout;
|
||||
MediaService.setPresentationIsOpen(layoutContextDispatch, true);
|
||||
}
|
||||
}
|
||||
}, [meetingLayout, layoutContextDispatch, layoutType]);
|
||||
|
||||
const horizontalPosition = cameraDock.position === 'contentLeft' || cameraDock.position === 'contentRight';
|
||||
// this is not exactly right yet
|
||||
@ -140,7 +142,8 @@ const AppContainer = (props) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
MediaService.buildLayoutWhenPresentationAreaIsDisabled(layoutContextDispatch)});
|
||||
MediaService.buildLayoutWhenPresentationAreaIsDisabled(layoutContextDispatch)
|
||||
});
|
||||
|
||||
return currentUserId
|
||||
? (
|
||||
|
@ -100,7 +100,7 @@ class BBBMenu extends React.Component {
|
||||
const { actions, selectedEmoji, intl } = this.props;
|
||||
|
||||
return actions?.map(a => {
|
||||
const { dataTest, label, onClick, key, disabled, accessKey, description, selected } = a;
|
||||
const { dataTest, label, onClick, key, disabled, description, selected } = a;
|
||||
const emojiSelected = key?.toLowerCase()?.includes(selectedEmoji?.toLowerCase());
|
||||
|
||||
let customStyles = {
|
||||
@ -143,7 +143,7 @@ class BBBMenu extends React.Component {
|
||||
</Styled.BBBMenuItem>,
|
||||
a.divider && <Divider disabled />
|
||||
];
|
||||
});
|
||||
}) ?? [];
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -247,18 +247,7 @@ BBBMenu.propTypes = {
|
||||
|
||||
trigger: PropTypes.element.isRequired,
|
||||
|
||||
actions: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
icon: PropTypes.string,
|
||||
iconRight: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
divider: PropTypes.bool,
|
||||
dividerTop: PropTypes.bool,
|
||||
accessKey: PropTypes.string,
|
||||
dataTest: PropTypes.string,
|
||||
})).isRequired,
|
||||
actions: PropTypes.array.isRequired,
|
||||
|
||||
onCloseCallback: PropTypes.func,
|
||||
dataTest: PropTypes.string,
|
||||
|
@ -19,7 +19,7 @@ const BaseModal = (props) => {
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
// Only add event listener if name is specified
|
||||
if (!modalName) return;
|
||||
if (!modalName) return () => null;
|
||||
|
||||
const closeEventName = `CLOSE_MODAL_${modalName.toUpperCase()}`;
|
||||
|
||||
@ -28,7 +28,7 @@ const BaseModal = (props) => {
|
||||
|
||||
// Remove listener on unmount
|
||||
return () => {
|
||||
document.removeEventListener(closeEventName, closeEventHandler);
|
||||
document.removeEventListener(closeEventName, closeEventHandler);
|
||||
};
|
||||
}, []);
|
||||
const priorityValue = priority || 'low';
|
||||
|
@ -114,7 +114,7 @@ const Adapter = () => {
|
||||
}, [usingUsersContext]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!Meteor.status().connected) return;
|
||||
if (!Meteor.status().connected) return () => null;
|
||||
setSync(false);
|
||||
dispatch({
|
||||
type: ACTIONS.CLEAR_ALL,
|
||||
|
@ -20,7 +20,6 @@ const Adapter = () => {
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Adapter;
|
||||
|
@ -14,11 +14,11 @@ const propTypes = {
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
raiseHandLabel: {
|
||||
id: 'app.actionsBar.interactions.raiseHand',
|
||||
id: 'app.actionsBar.reactions.raiseHand',
|
||||
description: 'raise Hand Label',
|
||||
},
|
||||
notRaiseHandLabel: {
|
||||
id: 'app.actionsBar.interactions.lowHand',
|
||||
id: 'app.actionsBar.reactions.lowHand',
|
||||
description: 'not Raise Hand Label',
|
||||
},
|
||||
});
|
||||
@ -57,6 +57,7 @@ const ReactionsPicker = (props) => {
|
||||
onRaiseHand,
|
||||
raiseHand,
|
||||
isMobile,
|
||||
currentUserReaction,
|
||||
} = props;
|
||||
|
||||
const RaiseHandButtonLabel = () => {
|
||||
@ -67,16 +68,21 @@ const ReactionsPicker = (props) => {
|
||||
: intl.formatMessage(intlMessages.raiseHandLabel);
|
||||
};
|
||||
|
||||
const emojiProps = {
|
||||
native: true,
|
||||
size: '1.5rem',
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled.Wrapper isMobile={isMobile}>
|
||||
{reactions.map(({ id, native }) => (
|
||||
<Styled.ButtonWrapper>
|
||||
<Emoji key={id} emoji={{ id }} size={30} onClick={() => onReactionSelect(native)} />
|
||||
<Styled.ButtonWrapper active={currentUserReaction === native}>
|
||||
<Emoji key={id} emoji={{ id }} onClick={() => onReactionSelect(native)} {...emojiProps} />
|
||||
</Styled.ButtonWrapper>
|
||||
))}
|
||||
<Styled.Separator isMobile={isMobile} />
|
||||
<Styled.RaiseHandButtonWrapper onClick={() => onRaiseHand()} active={raiseHand}>
|
||||
<Emoji key='hand' emoji={{ id: 'hand' }} size={30} />
|
||||
<Emoji key='hand' emoji={{ id: 'hand' }} {...emojiProps} />
|
||||
{RaiseHandButtonLabel()}
|
||||
</Styled.RaiseHandButtonWrapper>
|
||||
</Styled.Wrapper>
|
||||
|
@ -41,6 +41,18 @@ const ButtonWrapper = styled.div`
|
||||
height: 1.8rem !important;
|
||||
width: 1.8rem !important;
|
||||
}
|
||||
|
||||
${({ active }) => active && `
|
||||
color: ${btnPrimaryColor};
|
||||
background-color: ${btnPrimaryBg};
|
||||
border: none;
|
||||
|
||||
&:hover{
|
||||
filter: brightness(90%);
|
||||
color: ${btnPrimaryColor};
|
||||
background-color: ${btnPrimaryHoverBg} !important;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const RaiseHandButtonWrapper = styled(ButtonWrapper)`
|
||||
|
@ -64,7 +64,7 @@ const CustomLayout = (props) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
if (deviceType === null) return () => null;
|
||||
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
|
@ -64,7 +64,7 @@ const PresentationFocusLayout = (props) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
if (deviceType === null) return () => null;
|
||||
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
|
@ -59,7 +59,7 @@ const SmartLayout = (props) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
if (deviceType === null) return () => null;
|
||||
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
|
@ -67,7 +67,7 @@ const VideoFocusLayout = (props) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
if (deviceType === null) return () => null;
|
||||
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
|
@ -151,7 +151,6 @@ const Notes = ({
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
const renderHeaderOnMedia = () => {
|
||||
|
@ -139,7 +139,7 @@ class PresentationDownloadDropdown extends PureComponent {
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 2,
|
||||
getContentAnchorEl: null,
|
||||
getcontentanchorel: null,
|
||||
fullwidth: 'true',
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
|
@ -65,9 +65,6 @@ const SidebarContent = (props) => {
|
||||
}
|
||||
}, [width, height]);
|
||||
|
||||
useEffect(() => {
|
||||
}, [resizeStartWidth, resizeStartHeight]);
|
||||
|
||||
const setSidebarContentSize = (dWidth, dHeight) => {
|
||||
const newWidth = resizeStartWidth + dWidth;
|
||||
const newHeight = resizeStartHeight + dHeight;
|
||||
|
@ -46,9 +46,6 @@ const SidebarNavigation = (props) => {
|
||||
if (!isResizing) setResizableWidth(width);
|
||||
}, [width]);
|
||||
|
||||
useEffect(() => {
|
||||
}, [resizeStartWidth]);
|
||||
|
||||
const setSidebarNavWidth = (dWidth) => {
|
||||
const newWidth = resizeStartWidth + dWidth;
|
||||
|
||||
|
@ -13,8 +13,6 @@ import SubscriptionRegistry, {
|
||||
import { isChatEnabled } from '/imports/ui/services/features';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
|
||||
const TYPING_INDICATOR_ENABLED = CHAT_CONFIG.typingIndicator.enabled;
|
||||
const SUBSCRIPTIONS = [
|
||||
'users',
|
||||
|
@ -12,13 +12,10 @@ const trackName = Meteor.settings.public.timer.music;
|
||||
const TAB_TIMER_INDICATOR = Meteor.settings.public.timer.tabIndicator;
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
timer: PropTypes.shape({
|
||||
stopwatch: PropTypes.bool,
|
||||
running: PropTypes.bool,
|
||||
time: PropTypes.string,
|
||||
time: PropTypes.number,
|
||||
accumulated: PropTypes.number,
|
||||
timestamp: PropTypes.number,
|
||||
}).isRequired,
|
||||
@ -28,7 +25,7 @@ const propTypes = {
|
||||
sidebarContentIsOpen: PropTypes.bool.isRequired,
|
||||
timeOffset: PropTypes.number.isRequired,
|
||||
isModerator: PropTypes.bool.isRequired,
|
||||
currentTrack: PropTypes.string.isRequired,
|
||||
currentTrack: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
class Indicator extends Component {
|
||||
|
@ -527,6 +527,10 @@ const clearAllEmojiStatus = () => {
|
||||
makeCall('clearAllUsersEmoji');
|
||||
};
|
||||
|
||||
const clearAllReactions = () => {
|
||||
makeCall('clearAllUsersReaction');
|
||||
};
|
||||
|
||||
const assignPresenter = (userId) => { makeCall('assignPresenter', userId); };
|
||||
|
||||
const removeUser = (userId, banUser) => {
|
||||
@ -792,6 +796,7 @@ export default {
|
||||
setUserAway,
|
||||
setUserRaiseHand,
|
||||
clearAllEmojiStatus,
|
||||
clearAllReactions,
|
||||
assignPresenter,
|
||||
removeUser,
|
||||
toggleVoice,
|
||||
|
@ -22,6 +22,7 @@ const propTypes = {
|
||||
users: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
setEmojiStatus: PropTypes.func.isRequired,
|
||||
clearAllEmojiStatus: PropTypes.func.isRequired,
|
||||
clearAllReactions: PropTypes.func.isRequired,
|
||||
roving: PropTypes.func.isRequired,
|
||||
requestUserInformation: PropTypes.func.isRequired,
|
||||
};
|
||||
@ -196,6 +197,7 @@ class UserParticipants extends Component {
|
||||
users,
|
||||
compact,
|
||||
clearAllEmojiStatus,
|
||||
clearAllReactions,
|
||||
currentUser,
|
||||
meetingIsBreakout,
|
||||
isMeetingMuteOnStart,
|
||||
@ -216,6 +218,7 @@ class UserParticipants extends Component {
|
||||
? (
|
||||
<UserOptionsContainer {...{
|
||||
clearAllEmojiStatus,
|
||||
clearAllReactions,
|
||||
meetingIsBreakout,
|
||||
isMeetingMuteOnStart,
|
||||
}}
|
||||
|
@ -21,6 +21,7 @@ const UserParticipantsContainer = (props) => {
|
||||
setEmojiStatus,
|
||||
setUserAway,
|
||||
clearAllEmojiStatus,
|
||||
clearAllReactions,
|
||||
roving,
|
||||
requestUserInformation,
|
||||
} = UserListService;
|
||||
@ -40,6 +41,7 @@ const UserParticipantsContainer = (props) => {
|
||||
setEmojiStatus,
|
||||
setUserAway,
|
||||
clearAllEmojiStatus,
|
||||
clearAllReactions,
|
||||
roving,
|
||||
requestUserInformation,
|
||||
isReady,
|
||||
|
@ -21,6 +21,7 @@ const propTypes = {
|
||||
toggleMuteAllUsers: PropTypes.func.isRequired,
|
||||
toggleMuteAllUsersExceptPresenter: PropTypes.func.isRequired,
|
||||
toggleStatus: PropTypes.func.isRequired,
|
||||
toggleReactions: PropTypes.func.isRequired,
|
||||
guestPolicy: PropTypes.string.isRequired,
|
||||
meetingIsBreakout: PropTypes.bool.isRequired,
|
||||
hasBreakoutRoom: PropTypes.bool.isRequired,
|
||||
@ -41,6 +42,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.userList.userOptions.clearAllDesc',
|
||||
description: 'Clear all description',
|
||||
},
|
||||
clearAllReactionsLabel: {
|
||||
id: 'app.userList.userOptions.clearAllReactionsLabel',
|
||||
description: 'Clear all reactions label',
|
||||
},
|
||||
clearAllReactionsDesc: {
|
||||
id: 'app.userList.userOptions.clearAllReactionsDesc',
|
||||
description: 'Clear all reactions description',
|
||||
},
|
||||
muteAllLabel: {
|
||||
id: 'app.userList.userOptions.muteAllLabel',
|
||||
description: 'Mute all label',
|
||||
@ -128,12 +137,14 @@ const intlMessages = defineMessages({
|
||||
});
|
||||
|
||||
const USER_STATUS_ENABLED = Meteor.settings.public.userStatus.enabled;
|
||||
const USER_REACTION_ENABLED = Meteor.settings.public.userReaction.enabled;
|
||||
|
||||
class UserOptions extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.clearStatusId = uniqueId('list-item-');
|
||||
this.clearReactionId = uniqueId('list-item-');
|
||||
this.muteId = uniqueId('list-item-');
|
||||
this.muteAllId = uniqueId('list-item-');
|
||||
this.lockId = uniqueId('list-item-');
|
||||
@ -199,6 +210,7 @@ class UserOptions extends PureComponent {
|
||||
intl,
|
||||
isMeetingMuted,
|
||||
toggleStatus,
|
||||
toggleReactions,
|
||||
toggleMuteAllUsers,
|
||||
toggleMuteAllUsersExceptPresenter,
|
||||
meetingIsBreakout,
|
||||
@ -284,6 +296,17 @@ class UserOptions extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
if (USER_REACTION_ENABLED) {
|
||||
this.menuItems.push({
|
||||
key: this.clearReactionId,
|
||||
label: intl.formatMessage(intlMessages.clearAllReactionsLabel),
|
||||
description: intl.formatMessage(intlMessages.clearAllReactionsDesc),
|
||||
onClick: toggleReactions,
|
||||
icon: 'clear_status',
|
||||
divider: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (canCreateBreakout) {
|
||||
this.menuItems.push({
|
||||
key: this.createBreakoutId,
|
||||
|
@ -17,6 +17,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.userList.content.participants.options.clearedStatus',
|
||||
description: 'Used in toast notification when emojis have been cleared',
|
||||
},
|
||||
clearReactionsMessage: {
|
||||
id: 'app.userList.content.participants.options.clearedReactions',
|
||||
description: 'Used in toast notification when reactions have been cleared',
|
||||
},
|
||||
});
|
||||
|
||||
const { dynamicGuestPolicy } = Meteor.settings.public.app;
|
||||
@ -41,10 +45,19 @@ const UserOptionsContainer = (props) => {
|
||||
export default injectIntl(withTracker((props) => {
|
||||
const {
|
||||
clearAllEmojiStatus,
|
||||
clearAllReactions,
|
||||
intl,
|
||||
isMeetingMuteOnStart,
|
||||
} = props;
|
||||
|
||||
const toggleReactions = () => {
|
||||
clearAllReactions();
|
||||
|
||||
notify(
|
||||
intl.formatMessage(intlMessages.clearReactionsMessage), 'info', 'clear_status',
|
||||
);
|
||||
};
|
||||
|
||||
const toggleStatus = () => {
|
||||
clearAllEmojiStatus();
|
||||
|
||||
@ -81,6 +94,7 @@ export default injectIntl(withTracker((props) => {
|
||||
}, 'moderator enabled meeting mute, all users muted except presenter');
|
||||
},
|
||||
toggleStatus,
|
||||
toggleReactions,
|
||||
isMeetingMuted: isMeetingMuteOnStart,
|
||||
amIModerator: ActionsBarService.amIModerator(),
|
||||
hasBreakoutRoom: UserListService.hasBreakoutRoom(),
|
||||
|
@ -12,7 +12,7 @@ export const usePreviousValue = (value) => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
usePreviousValue,
|
||||
|
@ -14,7 +14,6 @@ import Settings from '/imports/ui/services/settings';
|
||||
|
||||
const ENABLE_WEBCAM_SELECTOR_BUTTON = Meteor.settings.public.app.enableWebcamSelectorButton;
|
||||
const ENABLE_CAMERA_BRIGHTNESS = Meteor.settings.public.app.enableCameraBrightness;
|
||||
const isSelfViewDisabled = Settings.application.selfViewDisable;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
videoSettings: {
|
||||
|
@ -146,9 +146,9 @@ public:
|
||||
enabled: false
|
||||
# If true, positions the icon next to the screenshare button, if false positions it where BBB had raisedHand button up to BBB 2.6 (right-hand bottom corner in LRT)
|
||||
centered: true
|
||||
interactionsButton:
|
||||
reactionsButton:
|
||||
# Enables the new raiseHand icon inside of the reaction menu (introduced in BBB 2.7)
|
||||
# If both interactionsButton and raiseHandActionButton are enabled, interactionsButton takes precedence.
|
||||
# If both reactionsButton and raiseHandActionButton are enabled, reactionsButton takes precedence.
|
||||
enabled: true
|
||||
# If enabled, before joining microphone the client will perform a trickle
|
||||
# ICE against Kurento and use the information about successfull
|
||||
|
@ -166,6 +166,8 @@
|
||||
"app.userList.userOptions.muteAllDesc": "Mutes all users in the meeting",
|
||||
"app.userList.userOptions.clearAllLabel": "Clear all status icons",
|
||||
"app.userList.userOptions.clearAllDesc": "Clears all status icons from users",
|
||||
"app.userList.userOptions.clearAllReactionsLabel": "Clear all reactions",
|
||||
"app.userList.userOptions.clearAllReactionsDesc": "Clears all reaction emojis from users",
|
||||
"app.userList.userOptions.muteAllExceptPresenterLabel": "Mute all users except presenter",
|
||||
"app.userList.userOptions.muteAllExceptPresenterDesc": "Mutes all users in the meeting except the presenter",
|
||||
"app.userList.userOptions.unmuteAllLabel": "Turn off meeting mute",
|
||||
@ -182,6 +184,7 @@
|
||||
"app.userList.userOptions.hideUserList": "User list is now hidden for viewers",
|
||||
"app.userList.userOptions.webcamsOnlyForModerator": "Only moderators are able to see viewers' webcams (due to lock settings)",
|
||||
"app.userList.content.participants.options.clearedStatus": "Cleared all user status",
|
||||
"app.userList.content.participants.options.clearedReactions": "Cleared all user reactions",
|
||||
"app.userList.userOptions.enableCam": "Viewers' webcams are enabled",
|
||||
"app.userList.userOptions.enableMic": "Viewers' microphones are enabled",
|
||||
"app.userList.userOptions.enablePrivChat": "Private chat is enabled",
|
||||
@ -614,8 +617,8 @@
|
||||
"app.talkingIndicator.moreThanMaxIndicatorsWereTalking": "{0}+ were talking",
|
||||
"app.talkingIndicator.wasTalking": "{0} stopped talking",
|
||||
"app.actionsBar.actionsDropdown.actionsLabel": "Actions",
|
||||
"app.actionsBar.actionsDropdown.activateTimerLabel": "Activate stopwatch",
|
||||
"app.actionsBar.actionsDropdown.deactivateTimerLabel": "Deactivate stopwatch",
|
||||
"app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Activate timer/stopwatch",
|
||||
"app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Deactivate timer/stopwatch",
|
||||
"app.actionsBar.actionsDropdown.presentationLabel": "Upload/Manage presentations",
|
||||
"app.actionsBar.actionsDropdown.initPollLabel": "Initiate a poll",
|
||||
"app.actionsBar.actionsDropdown.desktopShareLabel": "Share your screen",
|
||||
@ -635,10 +638,9 @@
|
||||
"app.actionsBar.actionsDropdown.takePresenterDesc": "Assign yourself as the new presenter",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserLabel": "Select random user",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserDesc": "Chooses a user from available viewers at random",
|
||||
"app.actionsBar.interactions.interactions": "Interactions",
|
||||
"app.actionsBar.interactions.raiseHand": "Raise your hand",
|
||||
"app.actionsBar.interactions.lowHand": "Lower your hand",
|
||||
"app.actionsBar.interactions.addReaction": "Add a reaction",
|
||||
"app.actionsBar.reactions.reactionsButtonLabel": "Reactions bar",
|
||||
"app.actionsBar.reactions.raiseHand": "Raise your hand",
|
||||
"app.actionsBar.reactions.lowHand": "Lower your hand",
|
||||
"app.actionsBar.emojiMenu.statusTriggerLabel": "Set status",
|
||||
"app.actionsBar.emojiMenu.awayLabel": "Away",
|
||||
"app.actionsBar.emojiMenu.awayDesc": "Change your status to away",
|
||||
|
@ -482,16 +482,9 @@
|
||||
"app.actionsBar.actionsDropdown.takePresenterDesc": "Assume o papel de apresentador",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserLabel": "Selecione um participante aleatoriamente",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserDesc": "Escolhe aleatoriamente um participante da lista",
|
||||
"app.actionsBar.interactions.interactions": "Interações",
|
||||
"app.actionsBar.interactions.interactionsAdvancedButton": "Abrir o menu de interações",
|
||||
"app.actionsBar.interactions.raiseHand": "Levantar a mão",
|
||||
"app.actionsBar.interactions.lowHand": "Abaixar a mão",
|
||||
"app.actionsBar.interactions.writeQuestion": "Escrever uma pergunta",
|
||||
"app.actionsBar.interactions.addReaction": "Adicionar reação",
|
||||
"app.actionsBar.interactions.status": "Status",
|
||||
"app.actionsBar.interactions.present": "Presente",
|
||||
"app.actionsBar.interactions.away": "Ausente",
|
||||
"app.actionsBar.interactions.back": "Voltar",
|
||||
"app.actionsBar.reactions.reactionsButtonLabel": "Reações",
|
||||
"app.actionsBar.reactions.raiseHand": "Levantar a mão",
|
||||
"app.actionsBar.reactions.lowHand": "Abaixar a mão",
|
||||
"app.actionsBar.emojiMenu.statusTriggerLabel": "Definir status",
|
||||
"app.actionsBar.emojiMenu.awayLabel": "Ausente",
|
||||
"app.actionsBar.emojiMenu.awayDesc": "Mudar seu status para ausente",
|
||||
|
@ -40,6 +40,8 @@ async function generateSettingsData(page) {
|
||||
webcamSharingEnabled: settingsData.kurento.enableVideo,
|
||||
skipVideoPreview: settingsData.kurento.skipVideoPreview,
|
||||
skipVideoPreviewOnFirstJoin: settingsData.kurento.skipVideoPreviewOnFirstJoin,
|
||||
// User
|
||||
userStatusEnabled: settingsData.userStatus.enabled,
|
||||
}
|
||||
|
||||
return settings;
|
||||
|
@ -31,7 +31,7 @@ exports.docTitle = docTitle;
|
||||
exports.clientTitle = `userdata-bbb_client_title=${docTitle}`;
|
||||
exports.askForFeedbackOnLogout = 'userdata-bbb_ask_for_feedback_on_logout=true';
|
||||
exports.displayBrandingArea = 'userdata-bbb_display_branding_area=true';
|
||||
exports.logo = 'logo=https://bigbluebutton.org/wp-content/themes/bigbluebutton/library/images/bigbluebutton-logo.png';
|
||||
exports.logo = 'logo=https://bigbluebutton.org/wp-content/uploads/2021/01/BigBlueButton_icon.svg.png';
|
||||
exports.enableVideo = 'userdata-bbb_enable_video=false';
|
||||
exports.autoShareWebcam = 'userdata-bbb_auto_share_webcam=true';
|
||||
exports.multiUserPenOnly = 'userdata-bbb_multi_user_pen_only=true';
|
||||
|
@ -69,7 +69,7 @@ class Presentation extends MultiUsers {
|
||||
await uploadSinglePresentation(this.modPage, e.pdfFileName, UPLOAD_PDF_WAIT_TIME);
|
||||
|
||||
// wait until the notifications disappear
|
||||
await this.modPage.hasElement(e.presentationStatusInfo, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.modPage.waitAndClick(e.smallToastMsg);
|
||||
await this.modPage.wasRemoved(e.smallToastMsg, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.userPage.wasRemoved(e.presentationStatusInfo);
|
||||
await this.userPage.wasRemoved(e.smallToastMsg);
|
||||
@ -151,8 +151,8 @@ class Presentation extends MultiUsers {
|
||||
await this.modPage.waitAndClick(e.presentationOptionsDownloadBtn);
|
||||
await this.modPage.waitAndClick(e.sendPresentationInCurrentStateBtn);
|
||||
await this.modPage.hasElement(e.downloadPresentationToast);
|
||||
await this.modPage.hasElement(e.smallToastMsg, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.userPage.hasElement(e.downloadPresentation, ELEMENT_WAIT_EXTRA_LONG_TIME);
|
||||
await this.modPage.hasElement(e.smallToastMsg, ELEMENT_WAIT_EXTRA_LONG_TIME);
|
||||
await this.userPage.hasElement(e.downloadPresentation);
|
||||
const downloadPresentationLocator = this.userPage.getLocator(e.downloadPresentation);
|
||||
await this.userPage.handleDownload(downloadPresentationLocator, testInfo);
|
||||
}
|
||||
@ -171,7 +171,6 @@ class Presentation extends MultiUsers {
|
||||
}
|
||||
|
||||
async uploadAndRemoveAllPresentations() {
|
||||
await waitAndClearDefaultPresentationNotification(this.modPage);
|
||||
await uploadSinglePresentation(this.modPage, e.uploadPresentationFileName);
|
||||
|
||||
const modSlides1 = await getSlideOuterHtml(this.modPage);
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { expect } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
const e = require('../core/elements');
|
||||
const { ELEMENT_WAIT_LONGER_TIME, UPLOAD_PDF_WAIT_TIME } = require('../core/constants');
|
||||
const { UPLOAD_PDF_WAIT_TIME, ELEMENT_WAIT_EXTRA_LONG_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
|
||||
|
||||
async function checkSvgIndex(test, element) {
|
||||
const check = await test.page.evaluate(([el, slideImg]) => {
|
||||
@ -32,6 +32,7 @@ async function uploadSinglePresentation(test, fileName, uploadTimeout = UPLOAD_P
|
||||
await test.hasText('body', e.statingUploadPresentationToast);
|
||||
|
||||
await test.waitAndClick(e.confirmManagePresentation);
|
||||
await test.hasElement(e.presentationStatusInfo, ELEMENT_WAIT_LONGER_TIME);
|
||||
await test.page.waitForFunction(([selector, firstSlideSrc]) => {
|
||||
const currentSrc = document.querySelector(selector).src;
|
||||
return currentSrc != firstSlideSrc;
|
||||
@ -40,7 +41,7 @@ async function uploadSinglePresentation(test, fileName, uploadTimeout = UPLOAD_P
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadMultiplePresentations(test, fileNames, uploadTimeout = ELEMENT_WAIT_LONGER_TIME) {
|
||||
async function uploadMultiplePresentations(test, fileNames, uploadTimeout = ELEMENT_WAIT_EXTRA_LONG_TIME) {
|
||||
await test.waitAndClick(e.actions);
|
||||
await test.waitAndClick(e.managePresentations);
|
||||
await test.waitForSelector(e.fileUpload);
|
||||
|
@ -1,7 +1,9 @@
|
||||
const { default: test } = require('@playwright/test');
|
||||
const Page = require('../core/page');
|
||||
const { setStatus } = require('./util');
|
||||
const { waitAndClearNotification, waitAndClearDefaultPresentationNotification } = require('../notifications/util');
|
||||
const e = require('../core/elements');
|
||||
const { getSettings } = require('../core/settings');
|
||||
|
||||
class Status extends Page {
|
||||
constructor(browser, page) {
|
||||
@ -9,6 +11,9 @@ class Status extends Page {
|
||||
}
|
||||
|
||||
async changeUserStatus() {
|
||||
const { userStatusEnabled } = getSettings();
|
||||
test.fail(!userStatusEnabled, 'User status is disabled');
|
||||
|
||||
await waitAndClearDefaultPresentationNotification(this);
|
||||
await setStatus(this, e.applaud);
|
||||
await this.waitForSelector(e.smallToastMsg);
|
||||
|
@ -19,6 +19,8 @@ Here's a breakdown of what's new in 2.7.
|
||||
|
||||
We have enhanced the layout which is focused on webcams by providing a visual representation of each participant. This way whether a webcam was shared or not, you can more easily be aware of who is speaking, who is present etc.
|
||||
|
||||
![Grid Layout](/img/27-grid-layout.png)
|
||||
|
||||
#### Camera as content
|
||||
|
||||
In hybrid learning (and not only) there is a frequently a need for displaying a physical whiteboard or draw the attention of students to a specific physical area. We now support using a webcam as the main content to occupy the presentation area.
|
||||
@ -41,16 +43,36 @@ In BigBlueButton 2.4 and 2.5 we supported optional downloading of the entire pre
|
||||
|
||||
![You can enable original presentation downloading from the upload dialog](/img/27-enable-download-orig-presentation.png)
|
||||
|
||||
The download button is the same as in BigBlueButton 2.5!
|
||||
The download button is overlayed on top of the presentation.
|
||||
|
||||
![Once downloading is enabled, everyone in the room can use it](/img/27-download-orig-presentation.png)
|
||||
|
||||
#### Timer and stopwatch
|
||||
|
||||
We have added the long requested option to display a count down (timer) or a count up (stopwatch) in the session. They are displayed to all participants and there is an audio notification when the timer elapses.
|
||||
|
||||
![The timer can be activated from the plus button menu](/img/27-activate-timer.png)
|
||||
|
||||
Setting up a timer for four minutes.
|
||||
|
||||
![Setting up a 4 minutes timer](/img/27-timer-4mins-start.png)
|
||||
|
||||
Everyone sees the timer as it counts down.
|
||||
|
||||
![Everyone seeing 4 minutes timer](/img/27-timer-4mins.png)
|
||||
|
||||
|
||||
### Engagement
|
||||
|
||||
#### Reaction Bar
|
||||
#### Reactions Bar
|
||||
|
||||
The Reaction Bar aims to make it much easier for students to respond with emojis to the teacher. The emoji is displayed in the user avatar area for 1 minute (configurable).
|
||||
The Reactions Bar aims to make it much easier for students to respond with emojis to the teacher. The emoji is displayed in the user avatar area for 1 minute (configurable). The bar remains visible once activated, and the emoji selected remains visible until it times out or is unselected. Modifying the configuration options (settings.yml) an additional set of emojis can be displayed, or the Reactions Bar can be substituted with the Status selecter we used in BigBlueButton 2.6 and prior.
|
||||
|
||||
![Reactions Bar remains visible once activated](/img/27-reactions-bar.png)
|
||||
|
||||
Others see your reactions in the participants list.
|
||||
|
||||
![Others see your reactions in the participants list](/img/27-reactions-thumbs-up.png)
|
||||
|
||||
<!-- ### Analytics -->
|
||||
|
||||
@ -96,6 +118,7 @@ For full details on what is new in BigBlueButton 2.7, see the release notes.
|
||||
|
||||
Recent releases:
|
||||
|
||||
- [2.7.0-beta.1](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.1)
|
||||
- [2.7.0-alpha.3](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-alpha.3)
|
||||
- [2.7.0-alpha.2](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-alpha.2)
|
||||
- [2.7.0-alpha.1](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-alpha.1)
|
||||
|
42
docs/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@cmfcmf/docusaurus-search-local": "^0.11.0",
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/plugin-client-redirects": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
@ -20,7 +21,7 @@
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^2.2.0",
|
||||
"@tsconfig/docusaurus": "^1.0.6",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
@ -2206,6 +2207,29 @@
|
||||
"react-dom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-client-redirects": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.2.0.tgz",
|
||||
"integrity": "sha512-psBoWi+cbc2I+VPkKJlcZ12tRN3xiv22tnZfNKyMo18iSY8gr4B6Q0G2KZXGPgNGJ/6gq7ATfgDK6p9h9XRxMQ==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/logger": "2.2.0",
|
||||
"@docusaurus/utils": "2.2.0",
|
||||
"@docusaurus/utils-common": "2.2.0",
|
||||
"@docusaurus/utils-validation": "2.2.0",
|
||||
"eta": "^1.12.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-blog": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.2.0.tgz",
|
||||
@ -14047,6 +14071,22 @@
|
||||
"react-loadable": "npm:@docusaurus/react-loadable@5.5.2"
|
||||
}
|
||||
},
|
||||
"@docusaurus/plugin-client-redirects": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.2.0.tgz",
|
||||
"integrity": "sha512-psBoWi+cbc2I+VPkKJlcZ12tRN3xiv22tnZfNKyMo18iSY8gr4B6Q0G2KZXGPgNGJ/6gq7ATfgDK6p9h9XRxMQ==",
|
||||
"requires": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/logger": "2.2.0",
|
||||
"@docusaurus/utils": "2.2.0",
|
||||
"@docusaurus/utils-common": "2.2.0",
|
||||
"@docusaurus/utils-validation": "2.2.0",
|
||||
"eta": "^1.12.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@docusaurus/plugin-content-blog": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.2.0.tgz",
|
||||
|
BIN
docs/static/img/2.7-enable-self-view.png
vendored
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 19 KiB |
BIN
docs/static/img/27-activate-timer.png
vendored
Normal file
After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 59 KiB |
BIN
docs/static/img/27-grid-layout.png
vendored
Normal file
After Width: | Height: | Size: 603 KiB |
BIN
docs/static/img/27-reactions-bar.png
vendored
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/27-reactions-thumbs-up.png
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
docs/static/img/27-timer-4mins-start.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/static/img/27-timer-4mins.png
vendored
Normal file
After Width: | Height: | Size: 2.9 KiB |