Merge branch 'develop' into multiple-choice-poll
This commit is contained in:
commit
b1bbc3fd52
@ -49,7 +49,6 @@ trait SystemConfiguration {
|
||||
|
||||
lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false)
|
||||
lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2)
|
||||
lazy val multiUserWhiteboardDefault = Try(config.getBoolean("whiteboard.multiUserDefault")).getOrElse(false)
|
||||
|
||||
// Redis server configuration
|
||||
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
|
||||
|
@ -25,7 +25,14 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
}
|
||||
|
||||
private def createWhiteboard(wbId: String): Whiteboard = {
|
||||
new Whiteboard(wbId, multiUserWhiteboardDefault, System.currentTimeMillis(), 0, new HashMap[String, List[AnnotationVO]]())
|
||||
new Whiteboard(
|
||||
wbId,
|
||||
Array.empty[String],
|
||||
Array.empty[String],
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
new HashMap[String, List[AnnotationVO]]()
|
||||
)
|
||||
}
|
||||
|
||||
private def getAnnotationsByUserId(wb: Whiteboard, id: String): List[AnnotationVO] = {
|
||||
@ -184,7 +191,7 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
if (hasWhiteboard(wbId)) {
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
if (wb.multiUser) {
|
||||
if (wb.multiUser.contains(userId)) {
|
||||
if (wb.annotationsMap.contains(userId)) {
|
||||
val newWb = wb.copy(annotationsMap = wb.annotationsMap - userId)
|
||||
saveWhiteboard(newWb)
|
||||
@ -205,7 +212,7 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
var last: Option[AnnotationVO] = None
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
if (wb.multiUser) {
|
||||
if (wb.multiUser.contains(userId)) {
|
||||
val usersAnnotations = getAnnotationsByUserId(wb, userId)
|
||||
|
||||
//not empty and head id equals annotation id
|
||||
@ -234,13 +241,21 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
wb.copy(annotationsMap = newAnnotationsMap)
|
||||
}
|
||||
|
||||
def modifyWhiteboardAccess(wbId: String, multiUser: Boolean) {
|
||||
def modifyWhiteboardAccess(wbId: String, multiUser: Array[String]) {
|
||||
val wb = getWhiteboard(wbId)
|
||||
val newWb = wb.copy(multiUser = multiUser, changedModeOn = System.currentTimeMillis())
|
||||
val newWb = wb.copy(multiUser = multiUser, oldMultiUser = wb.multiUser, changedModeOn = System.currentTimeMillis())
|
||||
saveWhiteboard(newWb)
|
||||
}
|
||||
|
||||
def getWhiteboardAccess(wbId: String): Boolean = getWhiteboard(wbId).multiUser
|
||||
def getWhiteboardAccess(wbId: String): Array[String] = getWhiteboard(wbId).multiUser
|
||||
|
||||
def hasWhiteboardAccess(wbId: String, userId: String): Boolean = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
wb.multiUser.contains(userId) || {
|
||||
val lastChange = System.currentTimeMillis() - wb.changedModeOn
|
||||
wb.oldMultiUser.contains(userId) && lastChange < 5000
|
||||
}
|
||||
}
|
||||
|
||||
def getChangedModeOn(wbId: String): Long = getWhiteboard(wbId).changedModeOn
|
||||
|
||||
|
@ -21,7 +21,7 @@ trait ClearWhiteboardPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to clear the whiteboard."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
|
@ -9,7 +9,7 @@ trait GetWhiteboardAnnotationsReqMsgHdlr {
|
||||
|
||||
def handle(msg: GetWhiteboardAnnotationsReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
def broadcastEvent(msg: GetWhiteboardAnnotationsReqMsg, history: Array[AnnotationVO], multiUser: Boolean): Unit = {
|
||||
def broadcastEvent(msg: GetWhiteboardAnnotationsReqMsg, history: Array[AnnotationVO], multiUser: Array[String]): Unit = {
|
||||
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
|
||||
|
||||
val envelope = BbbCoreEnvelope(GetWhiteboardAnnotationsRespMsg.NAME, routing)
|
||||
|
@ -21,7 +21,7 @@ trait ModifyWhiteboardAccessPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to modify access to the whiteboard."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
|
@ -21,7 +21,7 @@ trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to send your cursor position."
|
||||
// Just drop messages as these might be delayed messages from multi-user whiteboard. Don't want to
|
||||
|
@ -71,7 +71,7 @@ trait SendWhiteboardAnnotationPubMsgHdlr extends RightsManagementTrait {
|
||||
WhiteboardKeyUtil.DRAW_UPDATE_STATUS == annotation.status)
|
||||
}
|
||||
|
||||
if (!excludedWbMsg(msg.body.annotation) && filterWhiteboardMessage(msg.body.annotation.wbId, liveMeeting) && permissionFailed(
|
||||
if (!excludedWbMsg(msg.body.annotation) && filterWhiteboardMessage(msg.body.annotation.wbId, msg.header.userId, liveMeeting) && permissionFailed(
|
||||
PermissionCheck.GUEST_LEVEL,
|
||||
PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId
|
||||
)) {
|
||||
|
@ -21,7 +21,7 @@ trait UndoWhiteboardPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to undo an annotation."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
|
@ -5,8 +5,16 @@ import akka.event.Logging
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.common2.msgs.AnnotationVO
|
||||
import org.bigbluebutton.core.apps.WhiteboardKeyUtil
|
||||
import scala.collection.immutable.{ Map, List }
|
||||
|
||||
case class Whiteboard(id: String, multiUser: Boolean, changedModeOn: Long, annotationCount: Int, annotationsMap: scala.collection.immutable.Map[String, scala.collection.immutable.List[AnnotationVO]])
|
||||
case class Whiteboard(
|
||||
id: String,
|
||||
multiUser: Array[String],
|
||||
oldMultiUser: Array[String],
|
||||
changedModeOn: Long,
|
||||
annotationCount: Int,
|
||||
annotationsMap: Map[String, List[AnnotationVO]]
|
||||
)
|
||||
|
||||
class WhiteboardApp2x(implicit val context: ActorContext)
|
||||
extends SendCursorPositionPubMsgHdlr
|
||||
@ -56,18 +64,18 @@ class WhiteboardApp2x(implicit val context: ActorContext)
|
||||
liveMeeting.wbModel.undoWhiteboard(whiteboardId, requesterId)
|
||||
}
|
||||
|
||||
def getWhiteboardAccess(whiteboardId: String, liveMeeting: LiveMeeting): Boolean = {
|
||||
def getWhiteboardAccess(whiteboardId: String, liveMeeting: LiveMeeting): Array[String] = {
|
||||
liveMeeting.wbModel.getWhiteboardAccess(whiteboardId)
|
||||
}
|
||||
|
||||
def modifyWhiteboardAccess(whiteboardId: String, multiUser: Boolean, liveMeeting: LiveMeeting) {
|
||||
def modifyWhiteboardAccess(whiteboardId: String, multiUser: Array[String], liveMeeting: LiveMeeting) {
|
||||
liveMeeting.wbModel.modifyWhiteboardAccess(whiteboardId, multiUser)
|
||||
}
|
||||
|
||||
def filterWhiteboardMessage(whiteboardId: String, liveMeeting: LiveMeeting): Boolean = {
|
||||
def filterWhiteboardMessage(whiteboardId: String, userId: String, liveMeeting: LiveMeeting): Boolean = {
|
||||
// Need to check if the wb mode change from multi-user to single-user. Give 5sec allowance to
|
||||
// allow delayed messages to be handled as clients may have been sending messages while the wb
|
||||
// mode was changed. (ralam nov 22, 2017)
|
||||
if (!liveMeeting.wbModel.getWhiteboardAccess(whiteboardId) && liveMeeting.wbModel.getChangedModeOn(whiteboardId) > 5000) true else false
|
||||
!liveMeeting.wbModel.hasWhiteboardAccess(whiteboardId, userId)
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +95,3 @@ recording {
|
||||
# set zero to disable chapter break
|
||||
chapterBreakLengthInMinutes = 0
|
||||
}
|
||||
|
||||
whiteboard {
|
||||
multiUserDefault = false
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ case class GetWhiteboardAnnotationsReqMsgBody(whiteboardId: String)
|
||||
|
||||
object ModifyWhiteboardAccessPubMsg { val NAME = "ModifyWhiteboardAccessPubMsg" }
|
||||
case class ModifyWhiteboardAccessPubMsg(header: BbbClientMsgHeader, body: ModifyWhiteboardAccessPubMsgBody) extends StandardMsg
|
||||
case class ModifyWhiteboardAccessPubMsgBody(whiteboardId: String, multiUser: Boolean)
|
||||
case class ModifyWhiteboardAccessPubMsgBody(whiteboardId: String, multiUser: Array[String])
|
||||
|
||||
object SendCursorPositionPubMsg { val NAME = "SendCursorPositionPubMsg" }
|
||||
case class SendCursorPositionPubMsg(header: BbbClientMsgHeader, body: SendCursorPositionPubMsgBody) extends StandardMsg
|
||||
@ -48,11 +48,11 @@ case class ClearWhiteboardEvtMsgBody(whiteboardId: String, userId: String, fullC
|
||||
|
||||
object GetWhiteboardAnnotationsRespMsg { val NAME = "GetWhiteboardAnnotationsRespMsg" }
|
||||
case class GetWhiteboardAnnotationsRespMsg(header: BbbClientMsgHeader, body: GetWhiteboardAnnotationsRespMsgBody) extends BbbCoreMsg
|
||||
case class GetWhiteboardAnnotationsRespMsgBody(whiteboardId: String, annotations: Array[AnnotationVO], multiUser: Boolean)
|
||||
case class GetWhiteboardAnnotationsRespMsgBody(whiteboardId: String, annotations: Array[AnnotationVO], multiUser: Array[String])
|
||||
|
||||
object ModifyWhiteboardAccessEvtMsg { val NAME = "ModifyWhiteboardAccessEvtMsg" }
|
||||
case class ModifyWhiteboardAccessEvtMsg(header: BbbClientMsgHeader, body: ModifyWhiteboardAccessEvtMsgBody) extends BbbCoreMsg
|
||||
case class ModifyWhiteboardAccessEvtMsgBody(whiteboardId: String, multiUser: Boolean)
|
||||
case class ModifyWhiteboardAccessEvtMsgBody(whiteboardId: String, multiUser: Array[String])
|
||||
|
||||
object SendCursorPositionEvtMsg { val NAME = "SendCursorPositionEvtMsg" }
|
||||
case class SendCursorPositionEvtMsg(header: BbbClientMsgHeader, body: SendCursorPositionEvtMsgBody) extends BbbCoreMsg
|
||||
|
@ -15,7 +15,7 @@ export default function handleWhiteboardAnnotations({ header, body }, meetingId)
|
||||
|
||||
check(annotations, Array);
|
||||
check(whiteboardId, String);
|
||||
check(multiUser, Boolean);
|
||||
check(multiUser, Array);
|
||||
|
||||
clearAnnotations(meetingId, whiteboardId);
|
||||
|
||||
|
@ -4,7 +4,7 @@ import modifyWhiteboardAccess from '../modifiers/modifyWhiteboardAccess';
|
||||
export default function handleModifyWhiteboardAccess({ body }, meetingId) {
|
||||
const { multiUser, whiteboardId } = body;
|
||||
|
||||
check(multiUser, Boolean);
|
||||
check(multiUser, Array);
|
||||
check(whiteboardId, String);
|
||||
check(meetingId, String);
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
import Users from '/imports/api/users';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
|
||||
const getMultiUser = (meetingId, whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne(
|
||||
{
|
||||
meetingId,
|
||||
whiteboardId,
|
||||
}, { fields: { multiUser: 1 } },
|
||||
);
|
||||
|
||||
if (!data || !data.multiUser || !Array.isArray(data.multiUser)) return [];
|
||||
|
||||
return data.multiUser;
|
||||
};
|
||||
|
||||
const getUsers = (meetingId) => {
|
||||
const data = Users.find(
|
||||
{ meetingId },
|
||||
{ fields: { userId: 1 } },
|
||||
).fetch();
|
||||
|
||||
if (!data) return [];
|
||||
|
||||
return data.map(user => user.userId);
|
||||
};
|
||||
|
||||
export {
|
||||
getMultiUser,
|
||||
getUsers,
|
||||
};
|
@ -1,6 +1,12 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import changeWhiteboardAccess from './methods/changeWhiteboardAccess';
|
||||
import addGlobalAccess from './methods/addGlobalAccess';
|
||||
import addIndividualAccess from './methods/addIndividualAccess';
|
||||
import removeGlobalAccess from './methods/removeGlobalAccess';
|
||||
import removeIndividualAccess from './methods/removeIndividualAccess';
|
||||
|
||||
Meteor.methods({
|
||||
changeWhiteboardAccess,
|
||||
addGlobalAccess,
|
||||
addIndividualAccess,
|
||||
removeGlobalAccess,
|
||||
removeIndividualAccess,
|
||||
});
|
||||
|
@ -0,0 +1,27 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { getUsers } from '/imports/api/whiteboard-multi-user/server/helpers';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function addGlobalAccess(whiteboardId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const multiUser = getUsers(meetingId);
|
||||
|
||||
const payload = {
|
||||
multiUser,
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { getMultiUser } from '/imports/api/whiteboard-multi-user/server/helpers';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function addIndividualAccess(whiteboardId, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
check(userId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const multiUser = getMultiUser(meetingId, whiteboardId);
|
||||
|
||||
if (!multiUser.includes(userId)) {
|
||||
multiUser.push(userId);
|
||||
|
||||
const payload = {
|
||||
multiUser,
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
||||
}
|
@ -3,20 +3,20 @@ import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function changeWhiteboardAccess(multiUser, whiteboardId) {
|
||||
export default function removeGlobalAccess(whiteboardId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(multiUser, Boolean);
|
||||
check(whiteboardId, String);
|
||||
|
||||
const payload = {
|
||||
multiUser,
|
||||
multiUser: [],
|
||||
whiteboardId,
|
||||
};
|
||||
|
@ -0,0 +1,30 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { getMultiUser } from '/imports/api/whiteboard-multi-user/server/helpers';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function removeIndividualAccess(whiteboardId, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
check(userId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const multiUser = getMultiUser(meetingId, whiteboardId);
|
||||
|
||||
if (multiUser.includes(userId)) {
|
||||
const payload = {
|
||||
multiUser: multiUser.filter(id => id !== userId),
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
export default function modifyWhiteboardAccess(meetingId, whiteboardId, multiUser) {
|
||||
check(meetingId, String);
|
||||
check(whiteboardId, String);
|
||||
check(multiUser, Boolean);
|
||||
check(multiUser, Array);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
|
@ -123,8 +123,8 @@ const ChatContainer = (props) => {
|
||||
const contextChat = usingChatContext?.chats[isPublicChat ? PUBLIC_GROUP_CHAT_KEY : chatID];
|
||||
const lastTimeWindow = contextChat?.lastTimewindow;
|
||||
const lastMsg = contextChat && (isPublicChat
|
||||
? contextChat.preJoinMessages[lastTimeWindow] || contextChat.posJoinMessages[lastTimeWindow]
|
||||
: contextChat.messageGroups[lastTimeWindow]);
|
||||
? contextChat?.preJoinMessages[lastTimeWindow] || contextChat?.posJoinMessages[lastTimeWindow]
|
||||
: contextChat?.messageGroups[lastTimeWindow]);
|
||||
ChatLogger.debug('ChatContainer::render::chatData',contextChat);
|
||||
applyPropsToState = () => {
|
||||
ChatLogger.debug('ChatContainer::applyPropsToState::chatData',lastMsg, stateLastMsg, contextChat?.syncing);
|
||||
|
@ -663,12 +663,9 @@ class PresentationArea extends PureComponent {
|
||||
currentSlide,
|
||||
podId,
|
||||
} = this.props;
|
||||
|
||||
const { zoom, fitToWidth, isFullscreen } = this.state;
|
||||
|
||||
if (!currentSlide) {
|
||||
return null;
|
||||
}
|
||||
if (!currentSlide) return null;
|
||||
|
||||
return (
|
||||
<PresentationToolbarContainer
|
||||
|
@ -10,6 +10,7 @@ import Auth from '/imports/ui/services/auth';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
|
||||
const ROLE_VIEWER = Meteor.settings.public.user.role_viewer;
|
||||
|
||||
@ -73,7 +74,7 @@ export default withTracker(({ podId }) => {
|
||||
slidePosition,
|
||||
downloadPresentationUri: PresentationAreaService.downloadPresentationUri(podId),
|
||||
userIsPresenter: PresentationAreaService.isPresenter(podId) && !layoutSwapped,
|
||||
multiUser: PresentationAreaService.getMultiUserStatus(currentSlide && currentSlide.id)
|
||||
multiUser: WhiteboardService.hasMultiUserAccess(currentSlide && currentSlide.id, Auth.userID)
|
||||
&& !layoutSwapped,
|
||||
presentationIsDownloadable,
|
||||
mountPresentationArea: !!currentSlide,
|
||||
|
@ -16,7 +16,7 @@ import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import CursorWrapperService from './service';
|
||||
import CursorContainer from '../container';
|
||||
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
|
||||
const CursorWrapperContainer = ({ presenterCursorId, multiUserCursorIds, ...rest }) => (
|
||||
<g>
|
||||
@ -47,7 +47,7 @@ export default withTracker((params) => {
|
||||
const cursorIds = CursorWrapperService.getCurrentCursorIds(podId, whiteboardId);
|
||||
const { presenterCursorId, multiUserCursorIds } = cursorIds;
|
||||
|
||||
const isMultiUser = CursorWrapperService.getMultiUserStatus(whiteboardId);
|
||||
const isMultiUser = WhiteboardService.isMultiUserActive(whiteboardId);
|
||||
|
||||
return {
|
||||
presenterCursorId,
|
||||
|
@ -1,15 +1,9 @@
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
import PresentationPods from '/imports/api/presentation-pods';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Cursor from '/imports/ui/components/cursor/service';
|
||||
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
const getMultiUserStatus = (whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID, whiteboardId });
|
||||
return data ? data.multiUser : false;
|
||||
};
|
||||
|
||||
const getPresenterCursorId = (whiteboardId, userId) =>
|
||||
Cursor.findOne(
|
||||
{
|
||||
@ -31,7 +25,7 @@ const getCurrentCursorIds = (podId, whiteboardId) => {
|
||||
}
|
||||
|
||||
// checking whether multiUser mode is on or off
|
||||
const isMultiUser = getMultiUserStatus(whiteboardId);
|
||||
const isMultiUser = WhiteboardService.isMultiUserActive(whiteboardId);
|
||||
|
||||
// it's a multi-user mode - fetching all the cursors except the presenter's
|
||||
if (isMultiUser) {
|
||||
@ -60,5 +54,4 @@ const getCurrentCursorIds = (podId, whiteboardId) => {
|
||||
|
||||
export default {
|
||||
getCurrentCursorIds,
|
||||
getMultiUserStatus,
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
import PresentationPods from '/imports/api/presentation-pods';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import { Slides, SlidePositions } from '/imports/api/slides';
|
||||
@ -185,21 +184,12 @@ const isPresenter = (podId) => {
|
||||
return pod.currentPresenterId === Auth.userID;
|
||||
};
|
||||
|
||||
const getMultiUserStatus = (whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne({
|
||||
meetingId: Auth.meetingID,
|
||||
whiteboardId,
|
||||
});
|
||||
return data ? data.multiUser : false;
|
||||
};
|
||||
|
||||
export default {
|
||||
getCurrentSlide,
|
||||
getSlidePosition,
|
||||
isPresenter,
|
||||
isPresentationDownloadable,
|
||||
downloadPresentationUri,
|
||||
getMultiUserStatus,
|
||||
currentSlidHasContent,
|
||||
parseCurrentSlideContent,
|
||||
getCurrentPresentation,
|
||||
|
@ -46,6 +46,7 @@ const UserAvatar = ({
|
||||
avatar,
|
||||
noVoice,
|
||||
className,
|
||||
whiteboardAccess,
|
||||
}) => (
|
||||
|
||||
<div
|
||||
@ -54,6 +55,7 @@ const UserAvatar = ({
|
||||
className={cx(styles.avatar, {
|
||||
[styles.moderator]: moderator,
|
||||
[styles.presenter]: presenter,
|
||||
[styles.whiteboardAccess]: whiteboardAccess && !presenter,
|
||||
[styles.muted]: muted,
|
||||
[styles.listenOnly]: listenOnly,
|
||||
[styles.voice]: voice,
|
||||
|
@ -117,6 +117,15 @@
|
||||
@include presenterIndicator();
|
||||
}
|
||||
|
||||
.whiteboardAccess {
|
||||
&:before {
|
||||
content: "\00a0\e925\00a0";
|
||||
padding: var(--md-padding-y);
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
@include presenterIndicator();
|
||||
}
|
||||
|
||||
.voice {
|
||||
&:after {
|
||||
content: "\00a0\e931\00a0";
|
||||
|
@ -11,6 +11,7 @@ import _ from 'lodash';
|
||||
import KEY_CODES from '/imports/utils/keyCodes';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
@ -43,6 +44,14 @@ export const setModeratorOnlyMessage = msg => Storage.setItem('ModeratorOnlyMess
|
||||
|
||||
const getCustomLogoUrl = () => Storage.getItem(CUSTOM_LOGO_URL_KEY);
|
||||
|
||||
const sortByWhiteboardAccess = (a, b) => {
|
||||
const _a = a.whiteboardAccess;
|
||||
const _b = b.whiteboardAccess;
|
||||
if (!_b && _a) return -1;
|
||||
if (!_a && _b) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
const sortUsersByName = (a, b) => {
|
||||
const aName = a.name.toLowerCase();
|
||||
const bName = b.name.toLowerCase();
|
||||
@ -125,6 +134,10 @@ const sortUsers = (a, b) => {
|
||||
sort = sortUsersByPhoneUser(a, b);
|
||||
}
|
||||
|
||||
if (sort === 0) {
|
||||
sort = sortByWhiteboardAccess(a, b);
|
||||
}
|
||||
|
||||
if (sort === 0) {
|
||||
sort = sortUsersByName(a, b);
|
||||
}
|
||||
@ -189,6 +202,30 @@ const userFindSorting = {
|
||||
userId: 1,
|
||||
};
|
||||
|
||||
const addWhiteboardAccess = (users) => {
|
||||
const whiteboardId = WhiteboardService.getCurrentWhiteboardId();
|
||||
|
||||
if (whiteboardId) {
|
||||
const multiUserWhiteboard = WhiteboardService.getMultiUser(whiteboardId);
|
||||
return users.map(user => {
|
||||
const whiteboardAccess = multiUserWhiteboard.includes(user.userId);
|
||||
|
||||
return {
|
||||
...user,
|
||||
whiteboardAccess,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return users.map(user => {
|
||||
const whiteboardAccess = false;
|
||||
return {
|
||||
...user,
|
||||
whiteboardAccess,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getUsers = () => {
|
||||
let users = Users
|
||||
.find({
|
||||
@ -206,7 +243,11 @@ const getUsers = () => {
|
||||
}
|
||||
}
|
||||
|
||||
return users.sort(sortUsers);
|
||||
return addWhiteboardAccess(users).sort(sortUsers);
|
||||
};
|
||||
|
||||
const getUserCount = () => {
|
||||
return Users.find({ meetingId: Auth.meetingID }).count();
|
||||
};
|
||||
|
||||
const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID },
|
||||
@ -250,8 +291,8 @@ const getActiveChats = ({ groupChatsMessages, groupChats, users }) => {
|
||||
const unreadTimewindows = contextChat.unreadTimeWindows;
|
||||
for (const unreadTimeWindowId of unreadTimewindows) {
|
||||
const timeWindow = (isPublicChat
|
||||
? contextChat.preJoinMessages[unreadTimeWindowId] || contextChat.posJoinMessages[unreadTimeWindowId]
|
||||
: contextChat.messageGroups[unreadTimeWindowId]);
|
||||
? contextChat?.preJoinMessages[unreadTimeWindowId] || contextChat?.posJoinMessages[unreadTimeWindowId]
|
||||
: contextChat?.messageGroups[unreadTimeWindowId]);
|
||||
unreadMessagesCount += timeWindow.content.length;
|
||||
}
|
||||
}
|
||||
@ -334,7 +375,7 @@ const curatedVoiceUser = (intId) => {
|
||||
};
|
||||
};
|
||||
|
||||
const getAvailableActions = (amIModerator, isBreakoutRoom, subjectUser, subjectVoiceUser, usersProp) => {
|
||||
const getAvailableActions = (amIModerator, isBreakoutRoom, subjectUser, subjectVoiceUser, usersProp, amIPresenter) => {
|
||||
const isDialInUser = isVoiceOnlyUser(subjectUser.userId) || subjectUser.phone_user;
|
||||
const amISubjectUser = isMe(subjectUser.userId);
|
||||
const isSubjectUserModerator = subjectUser.role === ROLE_MODERATOR;
|
||||
@ -386,6 +427,9 @@ const getAvailableActions = (amIModerator, isBreakoutRoom, subjectUser, subjectV
|
||||
&& !isSubjectUserModerator
|
||||
&& isMeetingLocked(Auth.meetingID);
|
||||
|
||||
const allowedToChangeWhiteboardAccess = amIPresenter
|
||||
&& !amISubjectUser;
|
||||
|
||||
return {
|
||||
allowedToChatPrivately,
|
||||
allowedToMuteAudio,
|
||||
@ -397,6 +441,7 @@ const getAvailableActions = (amIModerator, isBreakoutRoom, subjectUser, subjectV
|
||||
allowedToDemote,
|
||||
allowedToChangeStatus,
|
||||
allowedToChangeUserLockStatus,
|
||||
allowedToChangeWhiteboardAccess,
|
||||
};
|
||||
};
|
||||
|
||||
@ -652,4 +697,5 @@ export default {
|
||||
isUserPresenter,
|
||||
amIPresenter,
|
||||
getUsersProp,
|
||||
getUserCount,
|
||||
};
|
||||
|
@ -18,7 +18,8 @@ import { Session } from 'meteor/session';
|
||||
import { styles } from './styles';
|
||||
import UserName from '../user-name/component';
|
||||
import UserIcons from '../user-icons/component';
|
||||
import Service from '../../../../service';
|
||||
import Service from '/imports/ui/components/user-list/service';
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
|
||||
const messages = defineMessages({
|
||||
presenter: {
|
||||
@ -65,6 +66,14 @@ const messages = defineMessages({
|
||||
id: 'app.userList.menu.makePresenter.label',
|
||||
description: 'label to make another user presenter',
|
||||
},
|
||||
giveWhiteboardAccess: {
|
||||
id: 'app.userList.menu.giveWhiteboardAccess.label',
|
||||
description: 'label to give user whiteboard access',
|
||||
},
|
||||
removeWhiteboardAccess: {
|
||||
id: 'app.userList.menu.removeWhiteboardAccess.label',
|
||||
description: 'label to remove user whiteboard access',
|
||||
},
|
||||
RemoveUserLabel: {
|
||||
id: 'app.userList.menu.removeUser.label',
|
||||
description: 'Forcefully remove this user from the meeting',
|
||||
@ -237,8 +246,9 @@ class UserDropdown extends PureComponent {
|
||||
} = this.props;
|
||||
const { showNestedOptions } = this.state;
|
||||
|
||||
const amIPresenter = currentUser.presenter;
|
||||
const amIModerator = currentUser.role === ROLE_MODERATOR;
|
||||
const actionPermissions = getAvailableActions(amIModerator, meetingIsBreakout, user, voiceUser, usersProp);
|
||||
const actionPermissions = getAvailableActions(amIModerator, meetingIsBreakout, user, voiceUser, usersProp, amIPresenter);
|
||||
const actions = [];
|
||||
|
||||
const {
|
||||
@ -252,6 +262,7 @@ class UserDropdown extends PureComponent {
|
||||
allowedToDemote,
|
||||
allowedToChangeStatus,
|
||||
allowedToChangeUserLockStatus,
|
||||
allowedToChangeWhiteboardAccess,
|
||||
} = actionPermissions;
|
||||
|
||||
const { disablePrivateChat } = lockSettingsProps;
|
||||
@ -357,6 +368,17 @@ class UserDropdown extends PureComponent {
|
||||
));
|
||||
}
|
||||
|
||||
if (allowedToChangeWhiteboardAccess && !user.presenter && isMeteorConnected) {
|
||||
const label = user.whiteboardAccess ? intl.formatMessage(messages.removeWhiteboardAccess) : intl.formatMessage(messages.giveWhiteboardAccess);
|
||||
|
||||
actions.push(this.makeDropdownItem(
|
||||
'changeWhiteboardAccess',
|
||||
label,
|
||||
() => WhiteboardService.changeWhiteboardAccess(user.userId, !user.whiteboardAccess),
|
||||
'pen_tool',
|
||||
));
|
||||
}
|
||||
|
||||
if (allowedToSetPresenter && isMeteorConnected) {
|
||||
actions.push(this.makeDropdownItem(
|
||||
'setPresenter',
|
||||
@ -535,6 +557,7 @@ class UserDropdown extends PureComponent {
|
||||
voice={voiceUser.isVoiceUser}
|
||||
noVoice={!voiceUser.isVoiceUser}
|
||||
color={user.color}
|
||||
whiteboardAccess={user.whiteboardAccess}
|
||||
emoji={user.emoji !== 'none'}
|
||||
avatar={user.avatar}
|
||||
>
|
||||
|
@ -31,6 +31,9 @@ const {
|
||||
desktopPageSizes: DESKTOP_PAGE_SIZES,
|
||||
mobilePageSizes: MOBILE_PAGE_SIZES,
|
||||
} = Meteor.settings.public.kurento.pagination;
|
||||
const PAGINATION_THRESHOLDS_CONF = Meteor.settings.public.kurento.paginationThresholds;
|
||||
const PAGINATION_THRESHOLDS = PAGINATION_THRESHOLDS_CONF.thresholds.sort((t1, t2) => t1.users - t2.users);
|
||||
const PAGINATION_THRESHOLDS_ENABLED = PAGINATION_THRESHOLDS_CONF.enabled;
|
||||
|
||||
const TOKEN = '_';
|
||||
const ENABLE_PAGINATION_SESSION_VAR = 'enablePagination';
|
||||
@ -64,6 +67,7 @@ class VideoService {
|
||||
isConnected: false,
|
||||
currentVideoPageIndex: 0,
|
||||
numberOfPages: 0,
|
||||
pageSize: 0,
|
||||
});
|
||||
this.userParameterProfile = null;
|
||||
const BROWSER_RESULTS = browser();
|
||||
@ -288,17 +292,66 @@ class VideoService {
|
||||
return this.currentVideoPageIndex;
|
||||
}
|
||||
|
||||
getMyPageSize () {
|
||||
const myRole = this.getMyRole();
|
||||
const pageSizes = !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES;
|
||||
getPageSizeDictionary () {
|
||||
// Dynamic page sizes are disabled. Fetch the stock page sizes.
|
||||
if (!PAGINATION_THRESHOLDS_ENABLED || PAGINATION_THRESHOLDS.length <= 0) {
|
||||
return !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES;
|
||||
}
|
||||
|
||||
// Dynamic page sizes are enabled. Get the user count, isolate the
|
||||
// matching threshold entry, return the val.
|
||||
let targetThreshold;
|
||||
const userCount = UserListService.getUserCount();
|
||||
const processThreshold = (threshold = {
|
||||
desktopPageSizes: DESKTOP_PAGE_SIZES,
|
||||
mobilePageSizes: MOBILE_PAGE_SIZES
|
||||
}) => {
|
||||
// We don't demand that all page sizes should be set in pagination profiles.
|
||||
// That saves us some space because don't necessarily need to scale mobile
|
||||
// endpoints.
|
||||
// If eg mobile isn't set, then return the default value.
|
||||
if (!this.isMobile) {
|
||||
return threshold.desktopPageSizes || DESKTOP_PAGE_SIZES;
|
||||
} else {
|
||||
return threshold.mobilePageSizes || MOBILE_PAGE_SIZES;
|
||||
}
|
||||
};
|
||||
|
||||
// Short-circuit: no threshold yet, return stock values (processThreshold has a default arg)
|
||||
if (userCount < PAGINATION_THRESHOLDS[0].users) return processThreshold();
|
||||
|
||||
// Reverse search for the threshold where our participant count is directly equal or great
|
||||
// The PAGINATION_THRESHOLDS config is sorted when imported.
|
||||
for (let mapIndex = PAGINATION_THRESHOLDS.length - 1; mapIndex >= 0; --mapIndex) {
|
||||
targetThreshold = PAGINATION_THRESHOLDS[mapIndex];
|
||||
if (targetThreshold.users <= userCount) {
|
||||
return processThreshold(targetThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPageSize (size) {
|
||||
if (this.pageSize !== size) {
|
||||
this.pageSize = size;
|
||||
}
|
||||
|
||||
return this.pageSize;
|
||||
}
|
||||
|
||||
getMyPageSize () {
|
||||
let size;
|
||||
const myRole = this.getMyRole();
|
||||
const pageSizes = this.getPageSizeDictionary();
|
||||
switch (myRole) {
|
||||
case ROLE_MODERATOR:
|
||||
return pageSizes.moderator;
|
||||
size = pageSizes.moderator;
|
||||
break;
|
||||
case ROLE_VIEWER:
|
||||
default:
|
||||
return pageSizes.viewer
|
||||
size = pageSizes.viewer
|
||||
}
|
||||
|
||||
return this.setPageSize(size);
|
||||
}
|
||||
|
||||
getVideoPage (streams, pageSize) {
|
||||
|
@ -253,6 +253,7 @@ class VideoListItem extends Component {
|
||||
}
|
||||
{voiceUser.muted && !voiceUser.listenOnly ? <Icon className={styles.muted} iconName="unmute_filled" /> : null}
|
||||
{voiceUser.listenOnly ? <Icon className={styles.voice} iconName="listen" /> : null}
|
||||
{voiceUser.joined && !voiceUser.muted ? <Icon className={styles.voice} iconName="unmute" /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import TextShapeService from './service';
|
||||
import TextDrawComponent from './component';
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
|
||||
const TextDrawContainer = props => (
|
||||
<TextDrawComponent {...props} />
|
||||
@ -10,7 +11,7 @@ const TextDrawContainer = props => (
|
||||
export default withTracker((params) => {
|
||||
const { whiteboardId } = params;
|
||||
const isPresenter = TextShapeService.isPresenter();
|
||||
const isMultiUser = TextShapeService.getMultiUserStatus(whiteboardId);
|
||||
const isMultiUser = WhiteboardService.isMultiUserActive(whiteboardId);
|
||||
const activeTextShapeId = TextShapeService.activeTextShapeId();
|
||||
let isActive = false;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
|
||||
const DRAW_SETTINGS = 'drawSettings';
|
||||
|
||||
@ -26,11 +25,6 @@ const isPresenter = () => {
|
||||
return currentUser ? currentUser.presenter : false;
|
||||
};
|
||||
|
||||
const getMultiUserStatus = (whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID, whiteboardId });
|
||||
return data ? data.multiUser : false;
|
||||
};
|
||||
|
||||
const activeTextShapeId = () => {
|
||||
const drawSettings = Storage.getItem(DRAW_SETTINGS);
|
||||
return drawSettings ? drawSettings.textShape.textShapeActiveId : '';
|
||||
@ -41,5 +35,4 @@ export default {
|
||||
activeTextShapeId,
|
||||
isPresenter,
|
||||
resetTextShapeActiveId,
|
||||
getMultiUserStatus,
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user';
|
||||
import addAnnotationQuery from '/imports/api/annotations/addAnnotation';
|
||||
import { Slides } from '/imports/api/slides';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
|
||||
@ -184,9 +185,104 @@ Users.find({ userId: Auth.userID }, { fields: { presenter: 1 } }).observeChanges
|
||||
},
|
||||
});
|
||||
|
||||
const getMultiUser = (whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne(
|
||||
{
|
||||
meetingId: Auth.meetingID,
|
||||
whiteboardId,
|
||||
}, { fields: { multiUser: 1 } },
|
||||
);
|
||||
|
||||
if (!data || !data.multiUser || !Array.isArray(data.multiUser)) return [];
|
||||
|
||||
return data.multiUser;
|
||||
};
|
||||
|
||||
const getMultiUserSize = (whiteboardId) => {
|
||||
const multiUser = getMultiUser(whiteboardId);
|
||||
|
||||
if (multiUser.length === 0) return 0;
|
||||
|
||||
// Individual whiteboard access is controlled by an array of userIds.
|
||||
// When an user leaves the meeting or the presenter role moves from an
|
||||
// user to another we applying a filter at the whiteboard collection.
|
||||
// Ideally this should change to something more cohese but this would
|
||||
// require extra changes at multiple backend modules.
|
||||
const multiUserSize = Users.find(
|
||||
{
|
||||
meetingId: Auth.meetingID,
|
||||
userId: { $in: multiUser },
|
||||
presenter: false,
|
||||
}, { fields: { userId: 1 } },
|
||||
).fetch();
|
||||
|
||||
return multiUserSize.length;
|
||||
};
|
||||
|
||||
const getCurrentWhiteboardId = () => {
|
||||
const currentSlide = Slides.findOne({
|
||||
podId: 'DEFAULT_PRESENTATION_POD',
|
||||
meetingId: Auth.meetingID,
|
||||
current: true,
|
||||
}, { fields: { id: 1 } },
|
||||
);
|
||||
|
||||
return currentSlide && currentSlide.id;
|
||||
}
|
||||
|
||||
const isMultiUserActive = (whiteboardId) => {
|
||||
const multiUser = getMultiUser(whiteboardId);
|
||||
|
||||
return multiUser.length !== 0;
|
||||
};
|
||||
|
||||
const hasMultiUserAccess = (whiteboardId, userId) => {
|
||||
const multiUser = getMultiUser(whiteboardId);
|
||||
|
||||
return multiUser.includes(userId);
|
||||
};
|
||||
|
||||
const changeWhiteboardAccess = (userId, access) => {
|
||||
const whiteboardId = getCurrentWhiteboardId();
|
||||
|
||||
if (!whiteboardId) return;
|
||||
|
||||
if (access) {
|
||||
addIndividualAccess(whiteboardId, userId);
|
||||
} else {
|
||||
removeIndividualAccess(whiteboardId, userId);
|
||||
}
|
||||
};
|
||||
|
||||
const addGlobalAccess = (whiteboardId) => {
|
||||
makeCall('addGlobalAccess', whiteboardId);
|
||||
};
|
||||
|
||||
const addIndividualAccess = (whiteboardId, userId) => {
|
||||
makeCall('addIndividualAccess', whiteboardId, userId);
|
||||
};
|
||||
|
||||
const removeGlobalAccess = (whiteboardId) => {
|
||||
makeCall('removeGlobalAccess', whiteboardId);
|
||||
};
|
||||
|
||||
const removeIndividualAccess = (whiteboardId, userId) => {
|
||||
makeCall('removeIndividualAccess', whiteboardId, userId);
|
||||
};
|
||||
|
||||
export {
|
||||
Annotations,
|
||||
UnsentAnnotations,
|
||||
sendAnnotation,
|
||||
clearPreview,
|
||||
getMultiUser,
|
||||
getMultiUserSize,
|
||||
getCurrentWhiteboardId,
|
||||
isMultiUserActive,
|
||||
hasMultiUserAccess,
|
||||
changeWhiteboardAccess,
|
||||
addGlobalAccess,
|
||||
addIndividualAccess,
|
||||
removeGlobalAccess,
|
||||
removeIndividualAccess,
|
||||
};
|
||||
|
@ -71,14 +71,18 @@ class WhiteboardToolbar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { annotations, multiUser, isPresenter } = this.props;
|
||||
const {
|
||||
annotations,
|
||||
multiUserSize,
|
||||
isPresenter,
|
||||
} = this.props;
|
||||
|
||||
let annotationSelected = {
|
||||
icon: 'hand',
|
||||
value: 'hand',
|
||||
};
|
||||
|
||||
if (multiUser && !isPresenter) {
|
||||
if (multiUserSize !== 0 && !isPresenter) {
|
||||
annotationSelected = {
|
||||
icon: 'pen_tool',
|
||||
value: 'pencil',
|
||||
@ -132,7 +136,12 @@ class WhiteboardToolbar extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { actions, multiUser, isPresenter } = this.props;
|
||||
const {
|
||||
actions,
|
||||
multiUserSize,
|
||||
isPresenter,
|
||||
} = this.props;
|
||||
|
||||
const drawSettings = actions.getCurrentDrawSettings();
|
||||
const {
|
||||
annotationSelected, thicknessSelected, colorSelected, fontSizeSelected,
|
||||
@ -144,7 +153,7 @@ class WhiteboardToolbar extends Component {
|
||||
// if there are saved drawSettings in the session storage
|
||||
// - retrieve them and update toolbar values
|
||||
if (drawSettings) {
|
||||
if (multiUser && !isPresenter) {
|
||||
if (multiUserSize !== 0 && !isPresenter) {
|
||||
drawSettings.whiteboardAnnotationTool = 'pencil';
|
||||
this.handleAnnotationChange({ icon: 'pen_tool', value: 'pencil' });
|
||||
}
|
||||
@ -357,12 +366,16 @@ class WhiteboardToolbar extends Component {
|
||||
|
||||
handleSwitchWhiteboardMode() {
|
||||
const {
|
||||
multiUser,
|
||||
multiUserSize,
|
||||
whiteboardId,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
actions.changeWhiteboardMode(!multiUser, whiteboardId);
|
||||
if (multiUserSize !== 0) {
|
||||
actions.removeWhiteboardGlobalAccess(whiteboardId);
|
||||
} else {
|
||||
actions.addWhiteboardGlobalAccess(whiteboardId);
|
||||
}
|
||||
}
|
||||
|
||||
// changes a current selected annotation both in the state and in the session
|
||||
@ -758,19 +771,26 @@ class WhiteboardToolbar extends Component {
|
||||
}
|
||||
|
||||
renderMultiUserItem() {
|
||||
const { intl, multiUser, isMeteorConnected } = this.props;
|
||||
const {
|
||||
intl,
|
||||
isMeteorConnected,
|
||||
multiUserSize,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<span className={styles.multiUserToolItem}>
|
||||
{multiUserSize > 0 && <span className={styles.multiUserTool}>{multiUserSize}</span>}
|
||||
<ToolbarMenuItem
|
||||
disabled={!isMeteorConnected}
|
||||
label={multiUser
|
||||
label={multiUserSize > 0
|
||||
? intl.formatMessage(intlMessages.toolbarMultiUserOff)
|
||||
: intl.formatMessage(intlMessages.toolbarMultiUserOn)
|
||||
}
|
||||
icon={multiUser ? 'multi_whiteboard' : 'whiteboard'}
|
||||
icon={multiUserSize > 0 ? 'multi_whiteboard' : 'whiteboard'}
|
||||
onItemClick={this.handleSwitchWhiteboardMode}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -800,9 +820,6 @@ WhiteboardToolbar.defaultProps = {
|
||||
};
|
||||
|
||||
WhiteboardToolbar.propTypes = {
|
||||
// defines a current mode of the whiteboard, multi/single user
|
||||
multiUser: PropTypes.bool.isRequired,
|
||||
|
||||
// defines whether a current user is a presenter or not
|
||||
isPresenter: PropTypes.bool.isRequired,
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
import WhiteboardToolbarService from './service';
|
||||
import WhiteboardToolbar from './component';
|
||||
|
||||
@ -9,11 +10,13 @@ const WhiteboardToolbarContainer = props => (
|
||||
|
||||
export default withTracker((params) => {
|
||||
const { whiteboardId } = params;
|
||||
|
||||
const data = {
|
||||
actions: {
|
||||
undoAnnotation: WhiteboardToolbarService.undoAnnotation,
|
||||
clearWhiteboard: WhiteboardToolbarService.clearWhiteboard,
|
||||
changeWhiteboardMode: WhiteboardToolbarService.changeWhiteboardMode,
|
||||
addWhiteboardGlobalAccess: WhiteboardService.addGlobalAccess,
|
||||
removeWhiteboardGlobalAccess: WhiteboardService.removeGlobalAccess,
|
||||
setInitialWhiteboardToolbarValues: WhiteboardToolbarService.setInitialWhiteboardToolbarValues,
|
||||
getCurrentDrawSettings: WhiteboardToolbarService.getCurrentDrawSettings,
|
||||
setFontSize: WhiteboardToolbarService.setFontSize,
|
||||
@ -23,10 +26,10 @@ export default withTracker((params) => {
|
||||
setTextShapeObject: WhiteboardToolbarService.setTextShapeObject,
|
||||
},
|
||||
textShapeActiveId: WhiteboardToolbarService.getTextShapeActiveId(),
|
||||
multiUser: WhiteboardToolbarService.getMultiUserStatus(whiteboardId),
|
||||
isPresenter: WhiteboardToolbarService.isPresenter(),
|
||||
annotations: WhiteboardToolbarService.filterAnnotationList(),
|
||||
isMeteorConnected: Meteor.status().connected,
|
||||
multiUserSize: WhiteboardService.getMultiUserSize(whiteboardId),
|
||||
};
|
||||
|
||||
return data;
|
||||
|
@ -2,7 +2,6 @@ import { makeCall } from '/imports/ui/services/api';
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
|
||||
const DRAW_SETTINGS = 'drawSettings';
|
||||
@ -24,10 +23,6 @@ const clearWhiteboard = (whiteboardId) => {
|
||||
makeCall('clearWhiteboard', whiteboardId);
|
||||
};
|
||||
|
||||
const changeWhiteboardMode = (multiUser, whiteboardId) => {
|
||||
makeCall('changeWhiteboardAccess', multiUser, whiteboardId);
|
||||
};
|
||||
|
||||
const setInitialWhiteboardToolbarValues = (tool, thickness, color, fontSize, textShape) => {
|
||||
const _drawSettings = Storage.getItem(DRAW_SETTINGS);
|
||||
if (!_drawSettings) {
|
||||
@ -59,11 +54,6 @@ const getTextShapeActiveId = () => {
|
||||
return drawSettings ? drawSettings.textShape.textShapeActiveId : '';
|
||||
};
|
||||
|
||||
const getMultiUserStatus = (whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID, whiteboardId });
|
||||
return data ? data.multiUser : false;
|
||||
};
|
||||
|
||||
const isPresenter = () => {
|
||||
const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { presenter: 1 } });
|
||||
return currentUser ? currentUser.presenter : false;
|
||||
@ -71,10 +61,11 @@ const isPresenter = () => {
|
||||
|
||||
const filterAnnotationList = () => {
|
||||
const multiUserPenOnly = getFromUserSettings('bbb_multi_user_pen_only', WHITEBOARD_TOOLBAR.multiUserPenOnly);
|
||||
const amIPresenter = isPresenter();
|
||||
|
||||
let filteredAnnotationList = WHITEBOARD_TOOLBAR.tools;
|
||||
|
||||
if (!isPresenter() && multiUserPenOnly) {
|
||||
if (!amIPresenter && multiUserPenOnly) {
|
||||
filteredAnnotationList = [{
|
||||
icon: 'pen_tool',
|
||||
value: 'pencil',
|
||||
@ -82,13 +73,13 @@ const filterAnnotationList = () => {
|
||||
}
|
||||
|
||||
const presenterTools = getFromUserSettings('bbb_presenter_tools', WHITEBOARD_TOOLBAR.presenterTools);
|
||||
if (isPresenter() && Array.isArray(presenterTools)) {
|
||||
if (amIPresenter && Array.isArray(presenterTools)) {
|
||||
filteredAnnotationList = WHITEBOARD_TOOLBAR.tools.filter(el =>
|
||||
presenterTools.includes(el.value));
|
||||
}
|
||||
|
||||
const multiUserTools = getFromUserSettings('bbb_multi_user_tools', WHITEBOARD_TOOLBAR.multiUserTools);
|
||||
if (!isPresenter() && !multiUserPenOnly && Array.isArray(multiUserTools)) {
|
||||
if (!amIPresenter && !multiUserPenOnly && Array.isArray(multiUserTools)) {
|
||||
filteredAnnotationList = WHITEBOARD_TOOLBAR.tools.filter(el =>
|
||||
multiUserTools.includes(el.value));
|
||||
}
|
||||
@ -99,7 +90,6 @@ const filterAnnotationList = () => {
|
||||
export default {
|
||||
undoAnnotation,
|
||||
clearWhiteboard,
|
||||
changeWhiteboardMode,
|
||||
setInitialWhiteboardToolbarValues,
|
||||
getCurrentDrawSettings,
|
||||
setFontSize,
|
||||
@ -108,7 +98,6 @@ export default {
|
||||
setColor,
|
||||
setTextShapeObject,
|
||||
getTextShapeActiveId,
|
||||
getMultiUserStatus,
|
||||
isPresenter,
|
||||
filterAnnotationList,
|
||||
};
|
||||
|
@ -244,3 +244,26 @@
|
||||
color: var(--toolbar-list-color);
|
||||
}
|
||||
|
||||
.multiUserTool {
|
||||
background-color: var(--color-danger);
|
||||
border-radius: 50%;
|
||||
width: var(--lg-padding-x);
|
||||
height: var(--lg-padding-x);
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 0px;
|
||||
color: var(--color-white);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 1px 1px var(--border-size-large) var(--color-gray-dark);
|
||||
font-size: var(--sm-padding-x);
|
||||
}
|
||||
|
||||
.multiUserToolItem {
|
||||
.toolbarButton {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,41 @@ public:
|
||||
mobilePageSizes:
|
||||
moderator: 2
|
||||
viewer: 2
|
||||
paginationThresholds:
|
||||
enabled: false
|
||||
thresholds:
|
||||
- users: 30
|
||||
desktopPageSizes:
|
||||
moderator: 25
|
||||
viewer: 25
|
||||
- users: 40
|
||||
desktopPageSizes:
|
||||
moderator: 20
|
||||
viewer: 20
|
||||
- users: 50
|
||||
desktopPageSizes:
|
||||
moderator: 16
|
||||
viewer: 16
|
||||
- users: 60
|
||||
desktopPageSizes:
|
||||
moderator: 14
|
||||
viewer: 12
|
||||
- users: 70
|
||||
desktopPageSizes:
|
||||
moderator: 12
|
||||
viewer: 10
|
||||
- users: 80
|
||||
desktopPageSizes:
|
||||
moderator: 10
|
||||
viewer: 8
|
||||
- users: 90
|
||||
desktopPageSizes:
|
||||
moderator: 8
|
||||
viewer: 6
|
||||
- users: 100
|
||||
desktopPageSizes:
|
||||
moderator: 6
|
||||
viewer: 4
|
||||
syncUsersWithConnectionManager:
|
||||
enabled: false
|
||||
syncInterval: 60000
|
||||
@ -520,7 +555,6 @@ public:
|
||||
- triangle
|
||||
- rectangle
|
||||
- pencil
|
||||
- hand
|
||||
clientLog:
|
||||
server:
|
||||
enabled: false
|
||||
|
@ -82,6 +82,8 @@
|
||||
"app.userlist.menu.removeConfirmation.desc": "Prevent this user from rejoining the session.",
|
||||
"app.userList.menu.muteUserAudio.label": "Mute user",
|
||||
"app.userList.menu.unmuteUserAudio.label": "Unmute user",
|
||||
"app.userList.menu.giveWhiteboardAccess.label" : "Give whiteboard access",
|
||||
"app.userList.menu.removeWhiteboardAccess.label": "Remove whiteboard access",
|
||||
"app.userList.userAriaLabel": "{0} {1} {2} Status {3}",
|
||||
"app.userList.menu.promoteUser.label": "Promote to moderator",
|
||||
"app.userList.menu.demoteUser.label": "Demote to viewer",
|
||||
|
Loading…
Reference in New Issue
Block a user