Merge remote-tracking branch 'upstream/v2.7.x-release' into 27-dev-apr24

This commit is contained in:
Ramón Souza 2023-04-24 17:26:54 -03:00
commit b121fcbd87
342 changed files with 3943 additions and 1471 deletions

View File

@ -43,6 +43,7 @@ jobs:
- run: ./build/setup.sh bbb-record-core
- run: ./build/setup.sh bbb-web
- run: ./build/setup.sh bbb-webrtc-sfu
- run: ./build/setup.sh bbb-webrtc-recorder
- run: ./build/setup.sh bigbluebutton
- run: tar cvf artifacts.tar artifacts/
- name: Archive packages
@ -182,6 +183,7 @@ jobs:
cp /etc/bigbluebutton/bigbluebutton-release configs/bigbluebutton-release
cp /etc/bigbluebutton/turn-stun-servers.xml configs/turn-stun-servers.xml
cp /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml configs/bbb-webrtc-sfu-default.yml
cp /etc/bbb-webrtc-recorder/bbb-webrtc-recorder.yml configs/bbb-webrtc-recorder-default.yml
cp /usr/share/bigbluebutton/nginx/sip.nginx configs/nginx_sip.nginx
cp /etc/hosts /configs/hosts
chmod a+r -R configs
@ -197,4 +199,4 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: bbb-logs
path: ./bbb-logs.tar.gz
path: ./bbb-logs.tar.gz

View File

@ -12,7 +12,7 @@ stages:
# define which docker image to use for builds
default:
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2022-12-29-grails-524
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2023-04-18
# This stage uses git to find out since when each package has been unmodified.
# it then checks an API endpoint on the package server to find out for which of
@ -47,11 +47,12 @@ get_external_dependencies:
- bbb-etherpad
- bbb-webhooks
- bbb-webrtc-sfu
- bbb-webrtc-recorder
- freeswitch
- bbb-pads
- bbb-playback
expire_in: 1h 30min
# template job for build step
.build_job:
stage: build
@ -170,6 +171,11 @@ bbb-webrtc-sfu-build:
script:
- build/setup-inside-docker.sh bbb-webrtc-sfu
bbb-webrtc-recorder-build:
extends: .build_job
script:
- build/setup-inside-docker.sh bbb-webrtc-recorder
bigbluebutton-build:
extends: .build_job
script:
@ -180,12 +186,12 @@ push_packages:
stage: push packages
script: build/push_packages.sh
resource_group: push_packages
# uncomment the lines below if you want one final
# "artifacts" dir with all packages (increases runtime, fills up space on gitlab server)
# uncomment the lines below if you want one final
# "artifacts" dir with all packages (increases runtime, fills up space on gitlab server)
#artifacts:
# paths:
# - artifacts/*
# expire_in: 2 days

View File

@ -14,6 +14,7 @@ import org.bigbluebutton.SystemConfiguration
import java.util.concurrent.TimeUnit
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.RunningMeeting
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core2.RunningMeetings
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.service.HealthzService
@ -183,6 +184,9 @@ class BigBlueButtonActor(
// Stop the meeting actor.
context.stop(m.actorRef)
}
//Remove ColorPicker idx of the meeting
ColorPicker.reset(m.props.meetingProp.intId)
}
}

View File

@ -0,0 +1,42 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait ChangeUserMobileFlagReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleChangeUserMobileFlagReqMsg(msg: ChangeUserMobileFlagReqMsg): Unit = {
log.info("handleChangeUserMobileFlagReqMsg: mobile={} userId={}", msg.body.mobile, msg.body.userId)
def broadcastUserMobileChanged(user: UserState, mobile: Boolean): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserMobileFlagChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserMobileFlagChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
user.intId)
val bodyChange = UserMobileFlagChangedEvtMsgBody(user.intId, mobile)
val eventChange = UserMobileFlagChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
if (user.mobile != msg.body.mobile) {
val userMobile = Users2x.setMobile(liveMeeting.users2x, user)
broadcastUserMobileChanged(userMobile, msg.body.mobile)
}
}
}
}

View File

@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
trait RegisterUserReqMsgHdlr {
@ -56,7 +57,7 @@ trait RegisterUserReqMsgHdlr {
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
msg.body.name, msg.body.role, msg.body.authToken,
msg.body.avatarURL, msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false)
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false)
checkUserConcurrentAccesses(regUser)
@ -89,7 +90,7 @@ trait RegisterUserReqMsgHdlr {
val g = GuestApprovedVO(regUser.id, GuestStatus.ALLOW)
UsersApp.approveOrRejectGuest(liveMeeting, outGW, g, SystemUser.ID)
case GuestStatus.WAIT =>
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.authed, regUser.registeredOn)
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.color, regUser.authed, regUser.registeredOn)
addGuestToWaitingForApproval(guest, liveMeeting.guestsWaiting)
notifyModeratorsOfGuestWaiting(Vector(guest), liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
val notifyEvent = MsgBuilder.buildNotifyRoleInMeetingEvtMsg(

View File

@ -19,8 +19,8 @@ trait UserConnectedToGlobalAudioMsgHdlr {
val header = BbbClientMsgHeader(UserJoinedVoiceConfToClientEvtMsg.NAME, props.meetingProp.intId, vu.intId)
val body = UserJoinedVoiceConfToClientEvtMsgBody(voiceConf = msg.header.voiceConf, intId = vu.intId, voiceUserId = vu.intId,
callingWith = vu.callingWith, callerName = vu.callerName,
callerNum = vu.callerNum, muted = true, talking = false, listenOnly = true)
callingWith = vu.callingWith, callerName = vu.callerName, callerNum = vu.callerNum, color = vu.color,
muted = true, talking = false, listenOnly = true)
val event = UserJoinedVoiceConfToClientEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
@ -36,6 +36,7 @@ trait UserConnectedToGlobalAudioMsgHdlr {
callingWith = "flash",
callerName = user.name,
callerNum = user.name,
color = user.color,
muted = true,
talking = false,
listenOnly = true,

View File

@ -158,6 +158,7 @@ class UsersApp(
with SelectRandomViewerReqMsgHdlr
with AssignPresenterReqMsgHdlr
with ChangeUserPinStateReqMsgHdlr
with ChangeUserMobileFlagReqMsgHdlr
with EjectUserFromMeetingCmdMsgHdlr
with EjectUserFromMeetingSysMsgHdlr
with MuteUserCmdMsgHdlr {

View File

@ -130,7 +130,7 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
def sendAllVoiceUsersInMeeting(requesterId: String, voiceUsers: VoiceUsers, meetingId: String): Unit = {
val vu = VoiceUsers.findAll(voiceUsers).map { u =>
VoiceConfUser(intId = u.intId, voiceUserId = u.voiceUserId, callingWith = u.callingWith, callerName = u.callerName,
callerNum = u.callerNum, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
callerNum = u.callerNum, color = u.color, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
}
val event = MsgBuilder.buildGetVoiceUsersMeetingRespMsg(meetingId, requesterId, vu)

View File

@ -14,7 +14,7 @@ trait SyncGetVoiceUsersMsgHdlr {
def buildSyncGetVoiceUsersRespMsg(): BbbCommonEnvCoreMsg = {
val voiceUsers = VoiceUsers.findAll(liveMeeting.voiceUsers).map { u =>
VoiceConfUser(intId = u.intId, voiceUserId = u.voiceUserId, callingWith = u.callingWith, callerName = u.callerName,
callerNum = u.callerNum, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
callerNum = u.callerNum, color = u.color, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
}
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)

View File

@ -5,7 +5,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core2.MeetingStatus2x
trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
@ -19,6 +19,8 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
val guestPolicy = GuestsWaiting.getGuestPolicy(liveMeeting.guestsWaiting)
val isDialInUser = msg.body.intId.startsWith(IntIdPrefixType.DIAL_IN)
val userColor = ColorPicker.nextColor(liveMeeting.props.meetingProp.intId)
def notifyModeratorsOfGuestWaiting(guest: GuestWaiting, users: Users2x, meetingId: String): Unit = {
val moderators = Users2x.findAll(users).filter(p => p.role == Roles.MODERATOR_ROLE)
moderators foreach { mod =>
@ -32,7 +34,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
def registerUserInRegisteredUsers() = {
val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId,
msg.body.callerIdName, Roles.VIEWER_ROLE, "",
msg.body.callerIdName, Roles.VIEWER_ROLE, "", userColor,
"", true, true, GuestStatus.WAIT, true, false)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser)
}
@ -48,9 +50,11 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
guestStatus = GuestStatus.WAIT,
emoji = "none",
pin = false,
mobile = false,
presenter = false,
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = "",
color = userColor,
clientType = "",
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0)
@ -60,7 +64,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
def registerUserAsGuest() = {
if (GuestsWaiting.findWithIntId(liveMeeting.guestsWaiting, msg.body.intId) == None) {
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", true, System.currentTimeMillis())
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", userColor, true, System.currentTimeMillis())
GuestsWaiting.add(liveMeeting.guestsWaiting, guest)
notifyModeratorsOfGuestWaiting(guest, liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
@ -84,6 +88,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
msg.body.callingWith,
msg.body.callerIdName,
msg.body.callerIdNum,
userColor,
msg.body.muted,
msg.body.talking,
"freeswitch"

View File

@ -7,9 +7,10 @@ import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core.running.{LiveMeeting, MeetingActor, OutMsgRouter}
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.util.ColorPicker
object VoiceApp extends SystemConfiguration {
@ -164,6 +165,7 @@ object VoiceApp extends SystemConfiguration {
cvu.callingWith,
cvu.callerIdName,
cvu.callerIdNum,
ColorPicker.nextColor(liveMeeting.props.meetingProp.intId),
cvu.muted,
cvu.talking,
cvu.calledInto
@ -213,6 +215,7 @@ object VoiceApp extends SystemConfiguration {
callingWith: String,
callerIdName: String,
callerIdNum: String,
color: String,
muted: Boolean,
talking: Boolean,
callingInto: String
@ -240,6 +243,7 @@ object VoiceApp extends SystemConfiguration {
voiceUserState.voiceUserId,
voiceUserState.callerName,
voiceUserState.callerNum,
voiceUserState.color,
voiceUserState.muted,
voiceUserState.talking,
voiceUserState.callingWith,
@ -267,6 +271,7 @@ object VoiceApp extends SystemConfiguration {
callingWith,
callerIdName,
callerIdNum,
color,
muted,
talking,
listenOnly = isListenOnly,

View File

@ -68,7 +68,7 @@ class GuestsWaiting {
}
}
case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean, registeredOn: Long)
case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, color: String, authenticated: Boolean, registeredOn: Long)
case class GuestPolicy(policy: String, setBy: String)
object GuestPolicyType {

View File

@ -5,7 +5,7 @@ import org.bigbluebutton.core.domain.BreakoutRoom2x
object RegisteredUsers {
def create(userId: String, extId: String, name: String, roles: String,
token: String, avatar: String, guest: Boolean, authenticated: Boolean,
token: String, avatar: String, color: String, guest: Boolean, authenticated: Boolean,
guestStatus: String, excludeFromDashboard: Boolean, loggedOut: Boolean): RegisteredUser = {
new RegisteredUser(
userId,
@ -14,6 +14,7 @@ object RegisteredUsers {
roles,
token,
avatar,
color,
guest,
authenticated,
guestStatus,
@ -191,6 +192,7 @@ case class RegisteredUser(
role: String,
authToken: String,
avatarURL: String,
color: String,
guest: Boolean,
authed: Boolean,
guestStatus: String,

View File

@ -107,6 +107,12 @@ object Users2x {
newUserState
}
def setMobile(users: Users2x, u: UserState): UserState = {
val newUserState = modify(u)(_.mobile).setTo(true)
users.save(newUserState)
newUserState
}
def ejectFromMeeting(users: Users2x, intId: String): Option[UserState] = {
for {
_ <- users.remove(intId)
@ -354,12 +360,14 @@ case class UserState(
role: String,
guest: Boolean,
pin: Boolean,
mobile: Boolean,
authed: Boolean,
guestStatus: String,
emoji: String,
locked: Boolean,
presenter: Boolean,
avatar: String,
color: String,
roleChangedOn: Long = System.currentTimeMillis(),
lastActivityTime: Long = System.currentTimeMillis(),
lastInactivityInspect: Long = 0,

View File

@ -174,6 +174,7 @@ case class VoiceUserState(
callingWith: String,
callerName: String,
callerNum: String,
color: String,
muted: Boolean,
talking: Boolean,
listenOnly: Boolean,

View File

@ -109,6 +109,8 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[UserActivitySignCmdMsg](envelope, jsonNode)
case ChangeUserPinStateReqMsg.NAME =>
routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode)
case ChangeUserMobileFlagReqMsg.NAME =>
routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode)
case SelectRandomViewerReqMsg.NAME =>
routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode)

View File

@ -63,9 +63,11 @@ trait HandlerHelpers extends SystemConfiguration {
guestStatus = regUser.guestStatus,
emoji = "none",
pin = false,
mobile = false,
presenter = false,
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = regUser.avatarURL,
color = regUser.color,
clientType = clientType,
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0)

View File

@ -384,10 +384,11 @@ class MeetingActor(
case m: RecordAndClearPreviousMarkersCmdMsg =>
state = usersApp.handleRecordAndClearPreviousMarkersCmdMsg(m, state)
updateUserLastActivity(m.body.setBy)
case m: GetRecordingStatusReqMsg => usersApp.handleGetRecordingStatusReqMsg(m)
case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m)
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m)
case m: GetRecordingStatusReqMsg => usersApp.handleGetRecordingStatusReqMsg(m)
case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m)
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m)
case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m)
// Client requested to eject user
case m: EjectUserFromMeetingCmdMsg =>

View File

@ -0,0 +1,19 @@
package org.bigbluebutton.core.util
object ColorPicker {
private val colors = List("#7b1fa2", "#6a1b9a", "#4a148c", "#5e35b1", "#512da8", "#4527a0", "#311b92",
"#3949ab", "#303f9f", "#283593", "#1a237e", "#1976d2", "#1565c0", "#0d47a1", "#0277bd", "#01579b")
private var meetingCurrIdx: Map[String, Int] = Map()
def nextColor(meetingId: String): String = {
val currentIdx = meetingCurrIdx.getOrElse(meetingId, 0)
val color = colors(currentIdx)
meetingCurrIdx += meetingId -> (currentIdx + 1) % colors.length
color
}
def reset(meetingId: String): Unit = {
meetingCurrIdx -= meetingId
}
}

View File

@ -13,5 +13,6 @@ object RandomStringGenerator {
// Generate a random alphabnumeric string of length n
def randomAlphanumericString(n: Int) =
randomString("abcdefghijklmnopqrstuvwxyz0123456789")(n)
}

View File

@ -77,6 +77,7 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: UserDisconnectedFromGlobalAudioMsg => logMessage(msg)
case m: AssignPresenterReqMsg => logMessage(msg)
case m: ChangeUserPinStateReqMsg => logMessage(msg)
case m: ChangeUserMobileFlagReqMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg)

View File

@ -42,6 +42,7 @@ trait GuestsWaitingApprovedMsgHdlr extends HandlerHelpers with RightsManagementT
"none",
dialInUser.name,
dialInUser.name,
dialInUser.color,
MeetingStatus2x.isMeetingMuted(liveMeeting.status),
false,
"freeswitch"

View File

@ -78,7 +78,7 @@ object MsgBuilder {
val envelope = BbbCoreEnvelope(GetGuestsWaitingApprovalRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetGuestsWaitingApprovalRespMsg.NAME, meetingId, userId)
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.authenticated, g.registeredOn))
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.color, g.authenticated, g.registeredOn))
val body = GetGuestsWaitingApprovalRespMsgBody(guestsWaiting)
val event = GetGuestsWaitingApprovalRespMsg(header, body)
@ -90,7 +90,7 @@ object MsgBuilder {
val envelope = BbbCoreEnvelope(GuestsWaitingForApprovalEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(GuestsWaitingForApprovalEvtMsg.NAME, meetingId, userId)
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.authenticated, g.registeredOn))
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.color, g.authenticated, g.registeredOn))
val body = GuestsWaitingForApprovalEvtMsgBody(guestsWaiting)
val event = GuestsWaitingForApprovalEvtMsg(header, body)

View File

@ -13,7 +13,7 @@ object UserJoinedMeetingEvtMsgBuilder {
guestStatus = userState.guestStatus,
emoji = userState.emoji,
pin = userState.pin,
presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar,
presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, color = userState.color,
clientType = userState.clientType)
val event = UserJoinedMeetingEvtMsg(meetingId, userState.intId, body)

View File

@ -20,13 +20,13 @@ trait FakeTestData {
val guest1 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.WEBRTC, muted = false,
talking = false, listenOnly = false)
Users2x.add(liveMeeting.users2x, guest1)
val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, "", guest1.authed, System.currentTimeMillis())
val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, "", "#ff6242", guest1.authed, System.currentTimeMillis())
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait1)
val guest2 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.FLASH, muted = false,
talking = false, listenOnly = false)
Users2x.add(liveMeeting.users2x, guest2)
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", guest2.authed, System.currentTimeMillis())
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", "#ff6242", guest2.authed, System.currentTimeMillis())
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait2)
val vu1 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
@ -67,8 +67,8 @@ trait FakeTestData {
def createFakeUser(liveMeeting: LiveMeeting, regUser: RegisteredUser): UserState = {
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, pin = false,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown",
mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown",
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
}

View File

@ -52,9 +52,10 @@ object FakeUserGenerator {
val authToken = RandomStringGenerator.randomAlphanumericString(16)
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
RandomStringGenerator.randomAlphanumericString(10) + ".png"
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role,
authToken, avatarURL, guest, authed, guestStatus = GuestStatus.ALLOW, false, false)
authToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, false)
RegisteredUsers.add(users, ru)
ru
}
@ -64,7 +65,7 @@ object FakeUserGenerator {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val lastFloorTime = System.currentTimeMillis().toString();
VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name,
callerNum = user.name, muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime)
callerNum = user.name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime)
}
def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean,
@ -74,7 +75,7 @@ object FakeUserGenerator {
val name = getRandomElement(firstNames, random) + " " + getRandomElement(lastNames, random)
val lastFloorTime = System.currentTimeMillis().toString();
VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name,
callerNum = name, muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime)
callerNum = name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime)
}
def createFakeWebcamStreamFor(userId: String, subscribers: Set[String]): WebcamStream = {

View File

@ -24,7 +24,7 @@ object TestDataGen {
listenOnly: Boolean): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name,
callerNum = user.name, muted, talking, listenOnly)
callerNum = user.name, "#ff6242", muted, talking, listenOnly)
}
def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean,
@ -32,7 +32,7 @@ object TestDataGen {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val intId = "v_" + RandomStringGenerator.randomAlphanumericString(16)
VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name,
callerNum = name, muted, talking, listenOnly)
callerNum = name, "#ff6242", muted, talking, listenOnly)
}
def createFakeWebcamStreamFor(userId: String, subscribers: Set[String]): WebcamStream = {
@ -43,8 +43,8 @@ object TestDataGen {
def createUserFor(liveMeeting: LiveMeeting, regUser: RegisteredUser, presenter: Boolean): UserState = {
val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown",
userLeftFlag = UserLeftFlag(false, 0))
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242",
clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))
Users2x.add(liveMeeting.users2x, u)
u
}

View File

@ -19,7 +19,7 @@ case class GetGuestsWaitingApprovalRespMsg(
body: GetGuestsWaitingApprovalRespMsgBody
) extends BbbCoreMsg
case class GetGuestsWaitingApprovalRespMsgBody(guests: Vector[GuestWaitingVO])
case class GuestWaitingVO(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean, registeredOn: Long)
case class GuestWaitingVO(intId: String, name: String, role: String, guest: Boolean, avatar: String, color: String, authenticated: Boolean, registeredOn: Long)
/**
* Message sent to client for list of guest waiting for approval. This is sent when

View File

@ -88,11 +88,22 @@ case class UserJoinedMeetingEvtMsg(
header: BbbClientMsgHeader,
body: UserJoinedMeetingEvtMsgBody
) extends BbbCoreMsg
case class UserJoinedMeetingEvtMsgBody(intId: String, extId: String, name: String, role: String,
guest: Boolean, authed: Boolean, guestStatus: String,
emoji: String,
pin: Boolean,
presenter: Boolean, locked: Boolean, avatar: String, clientType: String)
case class UserJoinedMeetingEvtMsgBody(
intId: String,
extId: String,
name: String,
role: String,
guest: Boolean,
authed: Boolean,
guestStatus: String,
emoji: String,
pin: Boolean,
presenter: Boolean,
locked: Boolean,
avatar: String,
color: String,
clientType: String
)
/**
* Sent by client to get all users in a meeting.
@ -189,6 +200,20 @@ object UserEmojiChangedEvtMsg { val NAME = "UserEmojiChangedEvtMsg" }
case class UserEmojiChangedEvtMsg(header: BbbClientMsgHeader, body: UserEmojiChangedEvtMsgBody) extends BbbCoreMsg
case class UserEmojiChangedEvtMsgBody(userId: String, emoji: String)
/**
* Sent from client about a user mobile flag.
*/
object ChangeUserMobileFlagReqMsg { val NAME = "ChangeUserMobileFlagReqMsg" }
case class ChangeUserMobileFlagReqMsg(header: BbbClientMsgHeader, body: ChangeUserMobileFlagReqMsgBody) extends StandardMsg
case class ChangeUserMobileFlagReqMsgBody(userId: String, mobile: Boolean)
/**
* Sent to all clients about a user mobile flag.
*/
object UserMobileFlagChangedEvtMsg { val NAME = "UserMobileFlagChangedEvtMsg" }
case class UserMobileFlagChangedEvtMsg(header: BbbClientMsgHeader, body: UserMobileFlagChangedEvtMsgBody) extends BbbCoreMsg
case class UserMobileFlagChangedEvtMsgBody(userId: String, mobile: Boolean)
object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" }
case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg
case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String)
@ -348,7 +373,7 @@ object GetVoiceUsersMeetingRespMsg {
case class GetVoiceUsersMeetingRespMsg(header: BbbClientMsgHeader, body: GetVoiceUsersMeetingRespMsgBody) extends BbbCoreMsg
case class GetVoiceUsersMeetingRespMsgBody(users: Vector[VoiceConfUser])
case class VoiceConfUser(intId: String, voiceUserId: String, callingWith: String, callerName: String,
callerNum: String, muted: Boolean, talking: Boolean, listenOnly: Boolean)
callerNum: String, color: String, muted: Boolean, talking: Boolean, listenOnly: Boolean)
/**
* Sent from client to add user to the presenter group of a meeting.

View File

@ -408,7 +408,7 @@ case class UserJoinedVoiceConfEvtMsgBody(voiceConf: String, voiceUserId: String,
object UserJoinedVoiceConfToClientEvtMsg { val NAME = "UserJoinedVoiceConfToClientEvtMsg" }
case class UserJoinedVoiceConfToClientEvtMsg(header: BbbClientMsgHeader, body: UserJoinedVoiceConfToClientEvtMsgBody) extends BbbCoreMsg
case class UserJoinedVoiceConfToClientEvtMsgBody(voiceConf: String, intId: String, voiceUserId: String, callerName: String,
callerNum: String, muted: Boolean,
callerNum: String, color: String, muted: Boolean,
talking: Boolean, callingWith: String, listenOnly: Boolean)
/**

View File

@ -1,6 +1,6 @@
#!/bin/sh
set -ex
RELEASE=4.0.1
RELEASE=4.0.2
cat <<MSG
This tool downloads prebuilt packages built on Github Actions
The corresponding source can be browsed at https://github.com/bigbluebutton/bbb-presentation-video/tree/${RELEASE}

View File

@ -0,0 +1 @@
git clone --branch v0.2.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-recorder bbb-webrtc-recorder

View File

@ -1 +1 @@
git clone --branch v2.10.0-alpha.1 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
git clone --branch v2.10.0-alpha.2 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu

View File

@ -44,9 +44,10 @@ fi
HOST=$(cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties $tmpfile $BBB_WEB_ETC_CONFIG | grep -v '#' | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}' | tail -n 1)
HTML5_CONFIG=/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml
BBB_WEB_CONFIG=$SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties
HTML5_CONFIG=/etc/bigbluebutton/bbb-html5.yml
if [ ! -f "${HTML5_CONFIG}" ]; then
touch $HTML5_CONFIG
fi
#
# Enable Looging of the HTML5 client for debugging

View File

@ -154,6 +154,14 @@ get_bbb_web_config_value() {
RECORD_CONFIG=/usr/local/bigbluebutton/core/scripts/bigbluebutton.yml
WEBRTC_RECORDER_DEFAULT_CONFIG=/etc/bbb-webrtc-recorder/bbb-webrtc-recorder.yml
WEBRTC_RECORDER_ETC_CONFIG=/etc/bigbluebutton/bbb-webrtc-recorder.yml
if [ -f $WEBRTC_RECORDER_ETC_CONFIG ]; then
WEBRTC_RECORDER_CONFIG=$(yq m -x $WEBRTC_RECORDER_DEFAULT_CONFIG $WEBRTC_RECORDER_ETC_CONFIG)
else
WEBRTC_RECORDER_CONFIG=$(yq r $WEBRTC_RECORDER_DEFAULT_CONFIG)
fi
HTML5_DEFAULT_CONFIG=/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml
HTML5_ETC_CONFIG=/etc/bigbluebutton/bbb-html5.yml
if [ -f $HTML5_ETC_CONFIG ]; then
@ -407,16 +415,7 @@ display_bigbluebutton_status () {
fi
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
units="$units mongod bbb-html5 bbb-webrtc-sfu kurento-media-server"
for i in `seq 8888 8890`; do
# check if multi-kurento setup is configured
if [ -f /usr/lib/systemd/system/kurento-media-server-${i}.service ]; then
if systemctl is-enabled kurento-media-server-${i}.service > /dev/null; then
units="$units kurento-media-server-${i}"
fi
fi
done
units="$units mongod bbb-html5"
source /usr/share/meteor/bundle/bbb-html5-with-roles.conf
@ -433,6 +432,27 @@ display_bigbluebutton_status () {
done
fi
if [ -f /usr/lib/systemd/system/bbb-webrtc-sfu.service ]; then
units="$units bbb-webrtc-sfu"
fi
if [ -f /usr/lib/systemd/system/bbb-webrtc-recorder.service ]; then
units="$units bbb-webrtc-recorder"
fi
if [ -f /usr/lib/systemd/system/kurento-media-server.service ]; then
units="$units kurento-media-server"
fi
for i in `seq 8888 8890`; do
# check if multi-kurento setup is configured
if [ -f /usr/lib/systemd/system/kurento-media-server-${i}.service ]; then
if systemctl is-enabled kurento-media-server-${i}.service > /dev/null; then
units="$units kurento-media-server-${i}"
fi
fi
done
if [ -f /usr/share/etherpad-lite/settings.json ]; then
units="$units etherpad"
fi
@ -706,6 +726,9 @@ if [[ $PORT_RANGE ]]; then
yq w -i $WEBRTC_SFU_ETC_CONFIG mediasoup.worker.rtcMinPort $START_PORT
yq w -i $WEBRTC_SFU_ETC_CONFIG mediasoup.worker.rtcMaxPort $END_PORT
yq w -i $WEBRTC_RECORDER_DEFAULT_CONFIG webrtc.rtcMinPort $START_PORT
yq w -i $WEBRTC_RECORDER_DEFAULT_CONFIG webrtc.rtcMaxPort $END_PORT
echo
echo "BigBlueButton's UDP port range is now $START_PORT-$END_PORT"
echo
@ -744,14 +767,18 @@ check_configuration() {
# Look for properties with no values set
#
CONFIG_FILES="$SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties"
ignore_configs_args=()
ignore_configs_args+=(-e "redis.pass")
ignore_configs_args+=(-e "redisPassword")
ignore_configs_args+=(-e "disabledFeatures")
for file in $CONFIG_FILES ; do
if [ ! -f $file ]; then
echo "# Error: File not found: $file"
else
if cat $file | grep -v redis.pass | grep -v redisPassword | grep -v ^# | grep -q "^[^=]*=[ ]*$"; then
if cat $file | grep -v "${ignore_configs_args[@]}" | grep -v ^# | grep -q "^[^=]*=[ ]*$"; then
echo "# The following properties in $file have no value:"
echo "# $(grep '^[^=#]*=[ ]*$' $file | grep -v redis.pass | grep -v redisPassword | sed 's/=//g')"
echo "# $(grep '^[^=#]*=[ ]*$' $file | grep -v "${ignore_configs_args[@]}" | sed 's/=//g')"
fi
fi
done
@ -1393,6 +1420,8 @@ if [ $CHECK ]; then
echo " kurento: $(awk -F '=' '{if (! ($0 ~ /^;/) && $0 ~ /minPort/) print $2}' /etc/kurento/modules/kurento/BaseRtpEndpoint.conf.ini)-$(awk -F '=' '{if (! ($0 ~ /^;/) && $0 ~ /maxPort/) print $2}' /etc/kurento/modules/kurento/BaseRtpEndpoint.conf.ini)"
echo " bbb-webrtc-sfu: $(echo "$WEBRTC_SFU_CONFIG" | yq r - mediasoup.worker.rtcMinPort)-$(echo "$WEBRTC_SFU_CONFIG" | yq r - mediasoup.worker.rtcMaxPort)"
echo " bbb-webrtc-recorder: $(echo "$WEBRTC_RECORDER_CONFIG" | yq r - webrtc.rtcMinPort)-$(echo "$WEBRTC_RECORDER_CONFIG" | yq r - webrtc.rtcMaxPort)"
# if [ -f ${LTI_DIR}/WEB-INF/classes/lti-config.properties ]; then
@ -1427,10 +1456,20 @@ if [ $CHECK ]; then
echo " kurento.ip: $(echo "$WEBRTC_SFU_CONFIG" | yq r - kurento[0].ip)"
echo " kurento.url: $(echo "$WEBRTC_SFU_CONFIG" | yq r - kurento[0].url)"
echo " freeswitch.sip_ip: $(echo "$WEBRTC_SFU_CONFIG" | yq r - freeswitch.sip_ip)"
echo " recordingAdapter: $(echo "$WEBRTC_SFU_CONFIG" | yq r - recordingAdapter)"
echo " recordScreenSharing: $(echo "$WEBRTC_SFU_CONFIG" | yq r - recordScreenSharing)"
echo " recordWebcams: $(echo "$WEBRTC_SFU_CONFIG" | yq r - recordWebcams)"
echo " codec_video_main: $(echo "$WEBRTC_SFU_CONFIG" | yq r - conference-media-specs.codec_video_main)"
echo " codec_video_content: $(echo "$WEBRTC_SFU_CONFIG" | yq r - conference-media-specs.codec_video_content)"
fi
if [ -n "$WEBRTC_RECORDER_CONFIG" ]; then
echo
echo "/etc/bbb-webrtc-recorder/bbb-webrtc-recorder.yml (bbb-webrtc-recorder)"
echo "/etc/bigbluebutton/bbb-webrtc-recorder.yml (bbb-webrtc-recorder - override)"
echo " debug: $(echo "$WEBRTC_RECORDER_CONFIG" | yq r - debug)"
echo " recorder.directory: $(echo "$WEBRTC_RECORDER_CONFIG" | yq r - recorder.directory)"
fi
if [ -n "$HTML5_CONFIG" ]; then

View File

@ -108,6 +108,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
background-color: rgba(66, 133, 244, 1) !important;
color: #FFF !important;
}
.fade-in {
opacity: 1 !important;
}
.fade-out {
opacity: 0 !important;
}
</style>
<script>
document.addEventListener('gesturestart', function (e) {
@ -149,4 +157,5 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<span id="destination"></span>
<audio id="remote-media" autoplay>
</audio>
<div id="modals-container"></div>
</body>

View File

@ -348,3 +348,9 @@
.icon-bbb-closed_caption_stop:before {
content: "\e966";
}
.icon-bbb-link:before {
content: "\e967";
}
.icon-bbb-manage_layout:before {
content: "\e968";
}

View File

@ -38,3 +38,39 @@
border: none;
background-color: transparent;
}
/* Prevents that an element within app shows over a modal */
#app {
position: relative;
z-index: 1000 !important;
}
.modal-low {
z-index: 1001;
}
.modal-medium {
z-index: 1002;
}
.modal-high {
z-index: 1003;
}
/* Within a same priority, hide all but first (FIFO) */
.modal-low ~ .modal-low,
.modal-medium ~ .modal-medium,
.modal-high ~ .modal-high {
display: none;
}
/* Hide all low priority modals when a medium or high priority modals are displayed */
#modals-container:has(.modal-medium) .modal-low,
#modals-container:has(.modal-high) .modal-low {
display: none;
}
/* Hide all medium priority modals when a high priority modal is displayed */
#modals-container:has(.modal-high) .modal-medium {
display: none;
}

View File

@ -758,9 +758,18 @@ class SIPSession {
const setupRemoteMedia = () => {
const mediaElement = document.querySelector(MEDIA_TAG);
const { sdp } = this.currentSession.sessionDescriptionHandler
.peerConnection.remoteDescription;
logger.info({
logCode: 'sip_js_session_setup_remote_media',
extraInfo: {
callerIdName: this.user.callerIdName,
sdp,
},
}, 'Audio call - setup remote media');
this.remoteStream = new MediaStream();
this.currentSession.sessionDescriptionHandler
.peerConnection.getReceivers().forEach((receiver) => {
if (receiver.track) {
@ -792,22 +801,15 @@ class SIPSession {
fsReady,
},
}, 'Audio call - check if ICE is finished and FreeSWITCH is ready');
if (iceCompleted && fsReady) {
if (iceCompleted) {
this.webrtcConnected = true;
setupRemoteMedia();
}
const { sdp } = this.currentSession.sessionDescriptionHandler
.peerConnection.remoteDescription;
logger.info({
logCode: 'sip_js_session_setup_remote_media',
extraInfo: {
callerIdName: this.user.callerIdName,
sdp,
},
}, 'Audio call - setup remote media');
if (fsReady) {
this.callback({ status: this.baseCallStates.started, bridge: this.bridgeName });
resolve();
}
};

View File

@ -1,15 +1,8 @@
import stringHash from 'string-hash';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import GuestUsers from '/imports/api/guest-users/';
import updatePositionInWaitingQueue from '../methods/updatePositionInWaitingQueue';
const COLOR_LIST = [
'#7b1fa2', '#6a1b9a', '#4a148c', '#5e35b1', '#512da8', '#4527a0',
'#311b92', '#3949ab', '#303f9f', '#283593', '#1a237e', '#1976d2', '#1565c0',
'#0d47a1', '#0277bd', '#01579b',
];
export default async function handleGuestsWaitingForApproval({ body }, meetingId) {
const { guests } = body;
check(guests, Array);
@ -27,7 +20,6 @@ export default async function handleGuestsWaitingForApproval({ body }, meetingId
meetingId,
loginTime: guest.registeredOn,
privateGuestLobbyMessage: '',
color: COLOR_LIST[stringHash(guest.intId) % COLOR_LIST.length],
});
if (insertedId) {

View File

@ -42,7 +42,7 @@ export default async function changeLayout(payload) {
pushLayout: Boolean,
presentationIsOpen: Boolean,
isResizing: Boolean,
cameraPosition: String,
cameraPosition: Match.Maybe(String),
focusedCamera: String,
presentationVideoRate: Number,
});

View File

@ -15,7 +15,7 @@ export default function setPushLayout(payload) {
check(requesterUserId, String);
check(payload, {
pushLayout: Boolean,
pushLayout: Match.Maybe(Boolean),
});
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);

View File

@ -8,6 +8,7 @@ import handleEmojiStatus from './handlers/emojiStatus';
import handleChangeRole from './handlers/changeRole';
import handleUserPinChanged from './handlers/userPinChanged';
import handleUserInactivityInspect from './handlers/userInactivityInspect';
import handleChangeMobileFlag from '/imports/api/users/server/handlers/changeMobileFlag';
RedisPubSub.on('PresenterAssignedEvtMsg', handlePresenterAssigned);
RedisPubSub.on('UserJoinedMeetingEvtMsg', handleUserJoined);
@ -15,6 +16,7 @@ RedisPubSub.on('UserLeftMeetingEvtMsg', handleRemoveUser);
RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken);
RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus);
RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole);
RedisPubSub.on('UserMobileFlagChangedEvtMsg', handleChangeMobileFlag);
RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated);
RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged);
RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect);

View File

@ -0,0 +1,13 @@
import { check } from 'meteor/check';
import setMobile from '/imports/api/users/server/modifiers/setMobile';
export default async function handleChangeMobileFlag(payload, meetingId) {
check(payload.body, Object);
check(meetingId, String);
const { userId: requesterUserId, mobile } = payload.body;
if (mobile) {
await setMobile(meetingId, requesterUserId);
}
}

View File

@ -1,18 +1,27 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import setMobile from '../modifiers/setMobile';
import { extractCredentials } from '/imports/api/common/server/helpers';
import RedisPubSub from '/imports/startup/server/redis';
export default async function setMobileUser() {
try {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ChangeUserMobileFlagReqMsg';
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(requesterUserId, String);
const payload = {
userId: requesterUserId,
mobile: true,
};
Logger.verbose(`Mobile user ${requesterUserId} from meeting ${meetingId}`);
await setMobile(meetingId, requesterUserId);
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
} catch (err) {
Logger.error(`Exception while invoking method setMobileUser ${err.stack}`);
}

View File

@ -8,7 +8,7 @@ export default async function addDialInUser(meetingId, voiceUser) {
const USER_CONFIG = Meteor.settings.public.user;
const ROLE_VIEWER = USER_CONFIG.role_viewer;
const { intId, callerName } = voiceUser;
const { intId, callerName, color } = voiceUser;
const voiceOnlyUser = {
intId,
@ -23,6 +23,7 @@ export default async function addDialInUser(meetingId, voiceUser) {
presenter: false,
locked: false, // TODO
avatar: '',
color,
pin: false,
clientType: 'dial-in-user',
};

View File

@ -4,18 +4,11 @@ import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import VoiceUsers from '/imports/api/voice-users/';
import addUserPsersistentData from '/imports/api/users-persistent-data/server/modifiers/addUserPersistentData';
import stringHash from 'string-hash';
import flat from 'flat';
import { lowercaseTrim } from '/imports/utils/string-utils';
import addVoiceUser from '/imports/api/voice-users/server/modifiers/addVoiceUser';
const COLOR_LIST = [
'#7b1fa2', '#6a1b9a', '#4a148c', '#5e35b1', '#512da8', '#4527a0',
'#311b92', '#3949ab', '#303f9f', '#283593', '#1a237e', '#1976d2', '#1565c0',
'#0d47a1', '#0277bd', '#01579b',
];
export default async function addUser(meetingId, userData) {
const user = userData;
@ -34,6 +27,7 @@ export default async function addUser(meetingId, userData) {
presenter: Boolean,
locked: Boolean,
avatar: String,
color: String,
pin: Boolean,
clientType: String,
});
@ -46,14 +40,9 @@ export default async function addUser(meetingId, userData) {
};
const Meeting = await Meetings.findOneAsync({ meetingId });
/* While the akka-apps dont generate a color we just pick one
from a list based on the userId */
const color = COLOR_LIST[stringHash(user.intId) % COLOR_LIST.length];
const userInfos = {
meetingId,
sortName: lowercaseTrim(user.name),
color,
speechLocale: '',
mobile: false,
breakoutProps: {
@ -81,6 +70,7 @@ export default async function addUser(meetingId, userData) {
intId: userId,
callerName: user.name,
callerNum: '',
color: user.color,
muted: false,
talking: false,
callingWith: '',

View File

@ -40,6 +40,7 @@ export default async function handleGetVoiceUsers({ body }, meetingId) {
callerName: user.callerName,
callerNum: user.callerNum,
muted: user.muted,
color: user.color,
talking: user.talking,
callingWith: user.callingWith,
listenOnly: user.listenOnly,

View File

@ -3,7 +3,6 @@ import Users from '/imports/api/users';
import addDialInUser from '/imports/api/users/server/modifiers/addDialInUser';
import addVoiceUser from '../modifiers/addVoiceUser';
export default async function handleJoinVoiceUser({ body }, meetingId) {
const voiceUser = body;
voiceUser.joined = true;
@ -15,6 +14,7 @@ export default async function handleJoinVoiceUser({ body }, meetingId) {
voiceUserId: String,
callerName: String,
callerNum: String,
color: String,
muted: Boolean,
talking: Boolean,
callingWith: String,

View File

@ -5,7 +5,6 @@ import removeVoiceUser from '../modifiers/removeVoiceUser';
import updateVoiceUser from '../modifiers/updateVoiceUser';
import addVoiceUser from '../modifiers/addVoiceUser';
export default async function handleVoiceUsers({ header, body }) {
const { voiceUsers } = body;
const { meetingId } = header;
@ -38,6 +37,7 @@ export default async function handleVoiceUsers({ header, body }) {
intId: voice.intId,
callerName: voice.callerName,
callerNum: voice.callerNum,
color: voice.color,
muted: voice.muted,
talking: voice.talking,
callingWith: voice.callingWith,

View File

@ -1,7 +1,6 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import VoiceUsers from '/imports/api/voice-users';
import Users from '/imports/api/users';
import flat from 'flat';
export default async function addVoiceUser(meetingId, voiceUser) {
@ -11,6 +10,7 @@ export default async function addVoiceUser(meetingId, voiceUser) {
intId: String,
callerName: String,
callerNum: String,
color: String,
muted: Boolean,
talking: Boolean,
callingWith: String,
@ -27,19 +27,12 @@ export default async function addVoiceUser(meetingId, voiceUser) {
};
const modifier = {
$set: Object.assign(
{ meetingId, spoke: talking },
flat(voiceUser),
),
};
const user = await Users.findOneAsync({ meetingId, userId: intId }, {
fields: {
color: 1,
$set: {
meetingId,
spoke: talking,
...flat(voiceUser),
},
});
if (user) modifier.$set.color = user.color;
};
try {
const { numberAffected } = await VoiceUsers.upsertAsync(selector, modifier);

View File

@ -2,8 +2,6 @@ import VoiceUsers from '/imports/api/voice-users';
import { Meteor } from 'meteor/meteor';
import Logger from '/imports/startup/server/logger';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
import ejectUserFromVoice from './methods/ejectUserFromVoice';
import { debounce } from 'radash';
async function voiceUser() {
const tokenValidation = await AuthTokenValidation
@ -15,22 +13,8 @@ async function voiceUser() {
}
const { meetingId, userId: requesterUserId } = tokenValidation;
const onCloseConnection = Meteor.bindEnvironment(async () => {
try {
// I used user because voiceUser is the function's name
const User = await VoiceUsers.findOneAsync({ meetingId, requesterUserId });
if (User) {
await ejectUserFromVoice(requesterUserId);
}
} catch (e) {
Logger.error(`Exception while executing ejectUserFromVoice for ${requesterUserId}: ${e}`);
}
});
Logger.debug('Publishing Voice User', { meetingId, requesterUserId });
this._session.socket.on('close', debounce({ delay: 100 }, onCloseConnection));
return VoiceUsers.find({ meetingId });
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
const intlMessages = defineMessages({
title: {
@ -38,7 +38,8 @@ const intlMessages = defineMessages({
},
});
const AboutComponent = ({ intl, settings }) => {
const AboutComponent = (props) => {
const { intl, settings, isOpen, onRequestClose, priority, } = props;
const {
html5ClientBuild,
copyright,
@ -54,20 +55,25 @@ const AboutComponent = ({ intl, settings }) => {
);
return (
<Modal
<ModalSimple
data-test="aboutModalTitleLabel"
title={intl.formatMessage(intlMessages.title)}
dismiss={{
label: intl.formatMessage(intlMessages.dismissLabel),
description: intl.formatMessage(intlMessages.dismissDesc),
}}
{...{
isOpen,
onRequestClose,
priority,
}}
>
{`${intl.formatMessage(intlMessages.copyright)} ${copyright}`}
<br />
{`${intl.formatMessage(intlMessages.version)} ${html5ClientBuild}`}
{displayBbbServerVersion ? showLabelVersion() : null}
</Modal>
</ModalSimple>
);
};

View File

@ -1,7 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { defineMessages } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import ExternalVideoModal from '/imports/ui/components/external-video-player/modal/container';
import RandomUserSelectContainer from '/imports/ui/components/common/modal/random-user/container';
@ -19,7 +18,6 @@ const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
mountModal: PropTypes.func.isRequired,
amIModerator: PropTypes.bool.isRequired,
shortcuts: PropTypes.string,
handleTakePresenter: PropTypes.func.isRequired,
@ -109,22 +107,29 @@ class ActionsDropdown extends PureComponent {
this.pollId = uniqueId('action-item-');
this.takePresenterId = uniqueId('action-item-');
this.selectUserRandId = uniqueId('action-item-');
this.state = {
isExternalVideoModalOpen: false,
isRandomUserSelectModalOpen: false,
isLayoutModalOpen: false,
}
this.handleExternalVideoClick = this.handleExternalVideoClick.bind(this);
this.makePresentationItems = this.makePresentationItems.bind(this);
this.setExternalVideoModalIsOpen = this.setExternalVideoModalIsOpen.bind(this);
this.setRandomUserSelectModalIsOpen = this.setRandomUserSelectModalIsOpen.bind(this);
this.setLayoutModalIsOpen = this.setLayoutModalIsOpen.bind(this);
}
componentDidUpdate(prevProps) {
const { amIPresenter: wasPresenter } = prevProps;
const { amIPresenter: isPresenter, mountModal } = this.props;
const { amIPresenter: isPresenter } = this.props;
if (wasPresenter && !isPresenter) {
mountModal(null);
this.setExternalVideoModalIsOpen(false);
}
}
handleExternalVideoClick() {
const { mountModal } = this.props;
mountModal(<ExternalVideoModal />);
this.setExternalVideoModalIsOpen(true);
}
getAvailableActions() {
@ -137,7 +142,6 @@ class ActionsDropdown extends PureComponent {
isPollingEnabled,
isSelectRandomUserEnabled,
stopExternalVideoShare,
mountModal,
layoutContextDispatch,
setMeetingLayout,
setPushLayout,
@ -216,7 +220,7 @@ class ActionsDropdown extends PureComponent {
icon: "user",
label: intl.formatMessage(intlMessages.selectRandUserLabel),
key: this.selectUserRandId,
onClick: () => mountModal(<RandomUserSelectContainer isSelectedUser={false} />),
onClick: () => this.setRandomUserSelectModalIsOpen(true),
dataTest: "selectRandomUser",
})
}
@ -236,7 +240,7 @@ class ActionsDropdown extends PureComponent {
icon: 'send',
label: intl.formatMessage(intlMessages.layoutModal),
key: 'layoutModal',
onClick: () => mountModal(<LayoutModalContainer {...this.props} />),
onClick: () => this.setLayoutModalIsOpen(true),
dataTest: 'layoutModal',
});
}
@ -281,6 +285,27 @@ class ActionsDropdown extends PureComponent {
return presentationItemElements;
}
setExternalVideoModalIsOpen(value) {
this.setState({isExternalVideoModalOpen: value});
}
setRandomUserSelectModalIsOpen(value) {
this.setState({isRandomUserSelectModalOpen: value});
}
setLayoutModalIsOpen(value) {
this.setState({isLayoutModalOpen: value});
}
renderModal(isOpen, setIsOpen, priority, Component) {
return isOpen ? <Component
{...{
onRequestClose: () => setIsOpen(false),
priority,
setIsOpen,
isOpen
}}
/> : null
}
render() {
const {
intl,
@ -290,8 +315,12 @@ class ActionsDropdown extends PureComponent {
isDropdownOpen,
isMobile,
isRTL,
isSelectRandomUserEnabled,
} = this.props;
const { isExternalVideoModalOpen,
isRandomUserSelectModalOpen, isLayoutModalOpen } = this.state;
const availableActions = this.getAvailableActions();
const availablePresentations = this.makePresentationItems();
const children = availablePresentations.length > 1 && amIPresenter
@ -305,35 +334,43 @@ class ActionsDropdown extends PureComponent {
}
return (
<BBBMenu
customStyles={!isMobile ? customStyles : null}
accessKey={OPEN_ACTIONS_AK}
trigger={
<Styled.HideDropdownButton
open={isDropdownOpen}
hideLabel
aria-label={intl.formatMessage(intlMessages.actionsLabel)}
data-test="actionsButton"
label={intl.formatMessage(intlMessages.actionsLabel)}
icon="plus"
color="primary"
size="lg"
circle
onClick={() => null}
/>
}
actions={children}
opts={{
id: "actions-dropdown-menu",
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getContentAnchorEl: null,
fullwidth: "true",
anchorOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
transformOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
}}
/>
<>
<BBBMenu
customStyles={!isMobile ? customStyles : null}
accessKey={OPEN_ACTIONS_AK}
trigger={
<Styled.HideDropdownButton
open={isDropdownOpen}
hideLabel
aria-label={intl.formatMessage(intlMessages.actionsLabel)}
data-test="actionsButton"
label={intl.formatMessage(intlMessages.actionsLabel)}
icon="plus"
color="primary"
size="lg"
circle
onClick={() => null}
/>
}
actions={children}
opts={{
id: "actions-dropdown-menu",
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getContentAnchorEl: null,
fullwidth: "true",
anchorOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
transformOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
}}
/>
{this.renderModal(isExternalVideoModalOpen, this.setExternalVideoModalIsOpen, "low",
ExternalVideoModal)}
{(amIPresenter && isSelectRandomUserEnabled) ? this.renderModal(isRandomUserSelectModalOpen, this.setRandomUserSelectModalIsOpen,
"low", RandomUserSelectContainer) : null }
{this.renderModal(isLayoutModalOpen, this.setLayoutModalIsOpen,
"low", LayoutModalContainer)}
</>
);
}
}
@ -341,4 +378,4 @@ class ActionsDropdown extends PureComponent {
ActionsDropdown.propTypes = propTypes;
ActionsDropdown.defaultProps = defaultProps;
export default withShortcutHelper(withModalMounter(ActionsDropdown), 'openActions');
export default withShortcutHelper(ActionsDropdown, 'openActions');

View File

@ -4,6 +4,7 @@ import deviceInfo from '/imports/utils/deviceInfo';
import Styled from './styles';
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 AudioControlsContainer from '../audio/audio-controls/container';
import JoinVideoOptionsContainer from '../video-provider/video-button/container';
@ -12,6 +13,20 @@ import RaiseHandDropdownContainer from './raise-hand/container';
import { isPresentationEnabled } from '/imports/ui/services/features';
class ActionsBar extends PureComponent {
constructor(props) {
super(props);
this.state = {
isCaptionsReaderMenuModalOpen: false,
};
this.setCaptionsReaderMenuModalIsOpen = this.setCaptionsReaderMenuModalIsOpen.bind(this);
}
setCaptionsReaderMenuModalIsOpen(value) {
this.setState({ isCaptionsReaderMenuModalOpen: value })
}
render() {
const {
amIPresenter,
@ -41,8 +56,10 @@ class ActionsBar extends PureComponent {
setPushLayout,
} = this.props;
const shouldShowOptionsButton = (isPresentationEnabled() && isThereCurrentPresentation)
|| isSharingVideo || hasScreenshare || isSharedNotesPinned;
const { isCaptionsReaderMenuModalOpen } = this.state;
const shouldShowOptionsButton = (isPresentationEnabled() && isThereCurrentPresentation)
|| isSharingVideo || hasScreenshare || isSharedNotesPinned;
return (
<Styled.ActionsBar
style={
@ -71,7 +88,20 @@ class ActionsBar extends PureComponent {
/>
{isCaptionsAvailable
? (
<CaptionsButtonContainer {...{ intl }} />
<>
<CaptionsButtonContainer {...{ intl,
setIsOpen: this.setCaptionsReaderMenuModalIsOpen,}} />
{
isCaptionsReaderMenuModalOpen ? <CaptionsReaderMenuContainer
{...{
onRequestClose: () => this.setCaptionsReaderMenuModalIsOpen(false),
priority: "low",
setIsOpen: this.setCaptionsReaderMenuModalIsOpen,
isOpen: isCaptionsReaderMenuModalOpen,
}}
/> : null
}
</>
)
: null}
{ !deviceInfo.isMobile

View File

@ -5,8 +5,7 @@ import { range } from '/imports/utils/array-utils';
import deviceInfo from '/imports/utils/deviceInfo';
import Button from '/imports/ui/components/common/button/component';
import { Session } from 'meteor/session';
import Modal from '/imports/ui/components/common/modal/fullscreen/component';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import ModalFullscreen from '/imports/ui/components/common/modal/fullscreen/component';
import SortList from './sort-user-list/component';
import Styled from './styles';
import Icon from '/imports/ui/components/common/icon/component';
@ -192,7 +191,6 @@ const propTypes = {
getUsersNotJoined: PropTypes.func.isRequired,
getBreakouts: PropTypes.func.isRequired,
sendInvitation: PropTypes.func.isRequired,
mountModal: PropTypes.func.isRequired,
isBreakoutRecordable: PropTypes.bool,
};
@ -411,10 +409,10 @@ class BreakoutRoom extends PureComponent {
}
handleDismiss() {
const { mountModal } = this.props;
const { setIsOpen } = this.props;
setPresentationVisibility('block');
return new Promise((resolve) => {
mountModal(null);
setIsOpen(false);
this.setState({
preventClosing: false,
@ -1317,7 +1315,7 @@ class BreakoutRoom extends PureComponent {
}
render() {
const { intl, isUpdate } = this.props;
const { intl, isUpdate, isOpen, priority, setIsOpen, } = this.props;
const {
preventClosing,
leastOneUserIsValid,
@ -1330,7 +1328,7 @@ class BreakoutRoom extends PureComponent {
const { isMobile } = deviceInfo;
return (
<Modal
<ModalFullscreen
title={
isUpdate
? intl.formatMessage(intlMessages.updateTitle)
@ -1357,16 +1355,21 @@ class BreakoutRoom extends PureComponent {
: intl.formatMessage(intlMessages.dismissLabel),
}}
preventClosing={preventClosing}
{...{
isOpen,
priority,
setIsOpen,
}}
>
<Styled.Content>
{this.renderTitle()}
{isMobile ? this.renderMobile() : this.renderDesktop()}
</Styled.Content>
</Modal>
</ModalFullscreen>
);
}
}
BreakoutRoom.propTypes = propTypes;
export default withModalMounter(injectIntl(BreakoutRoom));
export default injectIntl(BreakoutRoom);

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import Button from '/imports/ui/components/common/button/component';
const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
@ -47,7 +48,8 @@ const PresentationOptionsContainer = ({
buttonType = 'desktop';
}
const isThereCurrentPresentation = hasExternalVideo || hasScreenshare || hasPresentation || hasPinnedSharedNotes;
const isThereCurrentPresentation = hasExternalVideo || hasScreenshare
|| hasPresentation || hasPinnedSharedNotes;
return (
<Button
icon={`${buttonType}${!presentationIsOpen ? '_off' : ''}`}
@ -59,7 +61,12 @@ const PresentationOptionsContainer = ({
hideLabel
circle
size="lg"
onClick={() => setPresentationIsOpen(layoutContextDispatch, !presentationIsOpen)}
onClick={() => {
setPresentationIsOpen(layoutContextDispatch, !presentationIsOpen);
if (!hasExternalVideo && !hasScreenshare && !hasPinnedSharedNotes) {
Session.set('presentationLastState', !presentationIsOpen);
}
}}
id="restore-presentation"
ghost={!presentationIsOpen}
disabled={!isThereCurrentPresentation}

View File

@ -1,11 +1,10 @@
import React, { memo } from 'react';
import React, { memo, useState } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import deviceInfo from '/imports/utils/deviceInfo';
import browserInfo from '/imports/utils/browserInfo';
import logger from '/imports/startup/client/logger';
import { notify } from '/imports/ui/services/notification';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import Styled from './styles';
import ScreenshareBridgeService from '/imports/api/screenshare/client/bridge/service';
import {
@ -14,6 +13,7 @@ import {
} from '/imports/ui/components/screenshare/service';
import { SCREENSHARING_ERRORS } from '/imports/api/screenshare/client/bridge/errors';
import Button from '/imports/ui/components/common/button/component';
import { parsePayloads } from 'sdp-transform';
const { isMobile } = deviceInfo;
const { isSafari, isTabletApp } = browserInfo;
@ -117,8 +117,6 @@ const ScreenshareButton = ({
isVideoBroadcasting,
amIPresenter,
isMeteorConnected,
screenshareDataSavingSetting,
mountModal,
}) => {
// This is the failure callback that will be passed to the /api/screenshare/kurento.js
// script on the presenter's call
@ -141,18 +139,19 @@ const ScreenshareButton = ({
screenshareHasEnded();
};
const renderScreenshareUnavailableModal = () => mountModal(
const [isScreenshareUnavailableModalOpen, setScreenshareUnavailableModalIsOpen] = useState(false);
const RenderScreenshareUnavailableModal = (otherProps) =>
<Styled.ScreenShareModal
onRequestClose={() => mountModal(null)}
hideBorder
contentLabel={intl.formatMessage(intlMessages.screenShareUnavailable)}
{...otherProps}
>
<Styled.Title>
{intl.formatMessage(intlMessages.screenShareUnavailable)}
</Styled.Title>
<p>{intl.formatMessage(intlMessages.screenShareNotSupported)}</p>
</Styled.ScreenShareModal>,
);
</Styled.ScreenShareModal>;
const screenshareLabel = intlMessages.desktopShareLabel;
@ -168,32 +167,46 @@ const ScreenshareButton = ({
const dataTest = isVideoBroadcasting ? 'stopScreenShare' : 'startScreenShare';
return shouldAllowScreensharing
? (
<Button
disabled={(!isMeteorConnected && !isVideoBroadcasting)}
icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'}
data-test={dataTest}
label={intl.formatMessage(vLabel)}
description={intl.formatMessage(vDescr)}
color={isVideoBroadcasting ? 'primary' : 'default'}
ghost={!isVideoBroadcasting}
hideLabel
circle
size="lg"
onClick={isVideoBroadcasting
? screenshareHasEnded
: () => {
if (isSafari && !ScreenshareBridgeService.HAS_DISPLAY_MEDIA) {
renderScreenshareUnavailableModal();
} else {
shareScreen(amIPresenter, handleFailure);
}
}}
id={isVideoBroadcasting ? 'unshare-screen-button' : 'share-screen-button'}
/>
) : null;
return <>
{
shouldAllowScreensharing
? (
<Button
disabled={(!isMeteorConnected && !isVideoBroadcasting)}
icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'}
data-test={dataTest}
label={intl.formatMessage(vLabel)}
description={intl.formatMessage(vDescr)}
color={isVideoBroadcasting ? 'primary' : 'default'}
ghost={!isVideoBroadcasting}
hideLabel
circle
size="lg"
onClick={isVideoBroadcasting
? screenshareHasEnded
: () => {
if (isSafari && !ScreenshareBridgeService.HAS_DISPLAY_MEDIA) {
setScreenshareUnavailableModalIsOpen(true);
} else {
shareScreen(amIPresenter, handleFailure);
}
}}
id={isVideoBroadcasting ? 'unshare-screen-button' : 'share-screen-button'}
/>
) : null
}
{
isScreenshareUnavailableModalOpen ? <RenderScreenshareUnavailableModal
{...{
onRequestClose: () => setScreenshareUnavailableModalIsOpen(false),
priority: "low",
setIsOpen: setScreenshareUnavailableModalIsOpen,
isOpen: isScreenshareUnavailableModalOpen,
}}
/> : null
}
</>
};
ScreenshareButton.propTypes = propTypes;
export default withModalMounter(injectIntl(memo(ScreenshareButton)));
export default injectIntl(memo(ScreenshareButton));

View File

@ -1,6 +1,5 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import ScreenshareButton from './component';
import { isScreenSharingEnabled } from '/imports/ui/services/features';
import {
@ -18,8 +17,8 @@ const ScreenshareButtonContainer = (props) => <ScreenshareButton {...props} />;
* isMeteorConnected,
* screenshareDataSavingSetting,
*/
export default withModalMounter(withTracker(() => ({
export default withTracker(() => ({
isVideoBroadcasting: isVideoBroadcasting(),
screenshareDataSavingSetting: dataSavingSetting(),
enabled: isScreenSharingEnabled(),
}))(ScreenshareButtonContainer));
}))(ScreenshareButtonContainer);

View File

@ -1,5 +1,5 @@
import styled from 'styled-components';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import { colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
import {
jumboPaddingY,
@ -9,7 +9,7 @@ import {
} from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeLarge } from '/imports/ui/stylesheets/styled-components/typography';
const ScreenShareModal = styled(Modal)`
const ScreenShareModal = styled(ModalSimple)`
padding: ${jumboPaddingY};
min-height: ${minModalHeight};
text-align: center;

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { defineMessages } from 'react-intl';
import Button from '/imports/ui/components/common/button/component';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import { makeCall } from '/imports/ui/services/api';
import { Meteor } from 'meteor/meteor';
@ -92,7 +92,7 @@ class ActivityCheck extends Component {
const { responseDelay } = this.state;
return (
<Modal
<ModalSimple
hideBorder
onRequestClose={handleInactivityDismiss}
shouldCloseOnOverlayClick={false}
@ -110,7 +110,7 @@ class ActivityCheck extends Component {
size="lg"
/>
</Styled.ActivityModalContent>
</Modal>
</ModalSimple>
);
}
}

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { throttle } from '/imports/utils/throttle';
import { defineMessages, injectIntl } from 'react-intl';
import Modal from 'react-modal';
import ReactModal from 'react-modal';
import browserInfo from '/imports/utils/browserInfo';
import deviceInfo from '/imports/utils/deviceInfo';
import PollingContainer from '/imports/ui/components/polling/container';
@ -13,7 +13,6 @@ import BreakoutRoomInvitation from '/imports/ui/components/breakout-room/invitat
import { Meteor } from 'meteor/meteor';
import ToastContainer from '/imports/ui/components/common/toast/container';
import PadsSessionsContainer from '/imports/ui/components/pads/sessions/container';
import ModalContainer from '/imports/ui/components/common/modal/container';
import NotificationsBarContainer from '../notifications-bar/container';
import AudioContainer from '../audio/container';
import ChatAlertContainer from '../chat/alert/container';
@ -135,10 +134,16 @@ class App extends Component {
super(props);
this.state = {
enableResize: !window.matchMedia(MOBILE_MEDIA).matches,
isAudioModalOpen: false,
isRandomUserSelectModalOpen: false,
isVideoPreviewModalOpen: false,
};
this.handleWindowResize = throttle(this.handleWindowResize).bind(this);
this.shouldAriaHide = this.shouldAriaHide.bind(this);
this.setAudioModalIsOpen = this.setAudioModalIsOpen.bind(this);
this.setRandomUserSelectModalIsOpen = this.setRandomUserSelectModalIsOpen.bind(this);
this.setVideoPreviewModalIsOpen = this.setVideoPreviewModalIsOpen.bind(this);
this.throttledDeviceType = throttle(() => this.setDeviceType(),
50, { trailing: true, leading: true }).bind(this);
@ -162,7 +167,7 @@ class App extends Component {
value: isRTL,
});
Modal.setAppElement('#app');
ReactModal.setAppElement('#app');
const fontSize = isMobile() ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
document.getElementsByTagName('html')[0].style.fontSize = fontSize;
@ -220,7 +225,6 @@ class App extends Component {
notify,
currentUserEmoji,
intl,
mountModal,
deviceType,
mountRandomUserModal,
selectedLayout,
@ -228,12 +232,11 @@ class App extends Component {
layoutContextDispatch,
numCameras,
presentationIsOpen,
ignorePollNotifications,
} = this.props;
this.renderDarkMode();
if (mountRandomUserModal) mountModal(<RandomUserSelectContainer />);
if (mountRandomUserModal) this.setRandomUserSelectModalIsOpen(true);
if (prevProps.currentUserEmoji.status !== currentUserEmoji.status) {
const formattedEmojiStatus = intl.formatMessage({ id: `app.actionsBar.emojiMenu.${currentUserEmoji.status}Label` })
@ -506,12 +509,26 @@ class App extends Component {
setMeetingLayout,
setPushLayout,
shouldShowScreenshare,
shouldShowExternalVideo,
shouldShowExternalVideo: !!shouldShowExternalVideo,
}}
/>
);
}
setAudioModalIsOpen(value) {
this.setState({isAudioModalOpen: value});
}
setVideoPreviewModalIsOpen(value) {
this.setState({isVideoPreviewModalOpen: value});
}
setRandomUserSelectModalIsOpen(value) {
const {setMountRandomUserModal} = this.props;
this.setState({isRandomUserSelectModalOpen: value});
setMountRandomUserModal(false);
}
render() {
const {
customStyle,
@ -528,6 +545,7 @@ class App extends Component {
darkTheme,
} = this.props;
const { isAudioModalOpen, isRandomUserSelectModalOpen, isVideoPreviewModalOpen } = this.state;
return (
<>
<Notifications />
@ -571,7 +589,12 @@ class App extends Component {
<UploaderContainer />
<CaptionsSpeechContainer />
<BreakoutRoomInvitation />
<AudioContainer />
<AudioContainer {...{
isAudioModalOpen,
setAudioModalIsOpen: this.setAudioModalIsOpen,
isVideoPreviewModalOpen,
setVideoPreviewModalIsOpen: this.setVideoPreviewModalIsOpen,
}} />
<ToastContainer rtl />
{(audioAlertEnabled || pushAlertEnabled)
&& (
@ -583,11 +606,18 @@ class App extends Component {
<StatusNotifier status="raiseHand" />
<ManyWebcamsNotifier />
<PollingContainer />
<ModalContainer />
<PadsSessionsContainer />
{this.renderActionsBar()}
{customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null}
{customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null}
{isRandomUserSelectModalOpen ? <RandomUserSelectContainer
{...{
onRequestClose: () => this.setRandomUserSelectModalIsOpen(false),
priority: "low",
setIsOpen: this.setRandomUserSelectModalIsOpen,
isOpen: isRandomUserSelectModalOpen,
}}
/> : null}
</Styled.Layout>
</>
);

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
@ -31,8 +31,6 @@ import {
validIOSVersion,
} from './service';
import { withModalMounter, getModal } from '/imports/ui/components/common/modal/service';
import App from './component';
const CUSTOM_STYLE_URL = Meteor.settings.public.app.customStyleUrl;
@ -116,10 +114,14 @@ const AppContainer = (props) => {
const prevRandomUser = usePrevious(randomlySelectedUser);
const mountRandomUserModal = !isPresenter
&& !isEqual(prevRandomUser, randomlySelectedUser)
&& randomlySelectedUser.length > 0
&& !isModalOpen;
const [mountRandomUserModal, setMountRandomUserModal] = useState(false);
useEffect(() => {
setMountRandomUserModal(!isPresenter
&& !isEqual(prevRandomUser, randomlySelectedUser)
&& randomlySelectedUser.length > 0
&& !isModalOpen);
}, [isPresenter, prevRandomUser, randomlySelectedUser, isModalOpen]);
const setPushLayout = () => {
LayoutService.setPushLayout(pushLayout);
@ -176,6 +178,7 @@ const AppContainer = (props) => {
sidebarContentIsOpen,
shouldShowPresentation,
mountRandomUserModal,
setMountRandomUserModal,
isPresenter,
numCameras: cameraDockInput.numCameras,
}}
@ -196,7 +199,7 @@ const currentUserEmoji = (currentUser) => (currentUser
}
);
export default withModalMounter(withTracker(() => {
export default withTracker(() => {
Users.find({ userId: Auth.userID, meetingId: Auth.meetingID }).observe({
removed(userData) {
// wait 3secs (before endMeeting), client will try to authenticate again
@ -315,7 +318,6 @@ export default withModalMounter(withTracker(() => {
),
hidePresentationOnJoin: getFromUserSettings('bbb_hide_presentation_on_join', LAYOUT_CONFIG.hidePresentationOnJoin),
hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false),
isModalOpen: !!getModal(),
ignorePollNotifications: Session.get('ignorePollNotifications'),
};
})(AppContainer));
})(AppContainer);

View File

@ -7,6 +7,7 @@ import InputStreamLiveSelectorContainer from './input-stream-live-selector/conta
import MutedAlert from '/imports/ui/components/muted-alert/component';
import Styled from './styles';
import Button from '/imports/ui/components/common/button/component';
import AudioModalContainer from '../audio-modal/container';
const intlMessages = defineMessages({
joinAudio: {
@ -30,7 +31,6 @@ const intlMessages = defineMessages({
const propTypes = {
shortcuts: PropTypes.objectOf(PropTypes.string).isRequired,
handleToggleMuteMicrophone: PropTypes.func.isRequired,
handleJoinAudio: PropTypes.func.isRequired,
handleLeaveAudio: PropTypes.func.isRequired,
disable: PropTypes.bool.isRequired,
muted: PropTypes.bool.isRequired,
@ -46,21 +46,28 @@ const propTypes = {
class AudioControls extends PureComponent {
constructor(props) {
super(props);
this.state = {
isAudioModalOpen: false,
};
this.renderButtonsAndStreamSelector = this.renderButtonsAndStreamSelector.bind(this);
this.renderJoinLeaveButton = this.renderJoinLeaveButton.bind(this);
this.setAudioModalIsOpen = this.setAudioModalIsOpen.bind(this);
}
renderJoinButton() {
const {
handleJoinAudio,
disable,
intl,
shortcuts,
joinListenOnly,
isConnected
} = this.props;
return (
<Button
onClick={handleJoinAudio}
onClick={() => this.handleJoinAudio(joinListenOnly, isConnected)}
disabled={disable}
hideLabel
aria-label={intl.formatMessage(intlMessages.joinAudio)}
@ -119,6 +126,16 @@ class AudioControls extends PureComponent {
return this.renderJoinButton();
}
handleJoinAudio(joinListenOnly, isConnected) {
(isConnected()
? joinListenOnly()
: this.setAudioModalIsOpen(true)
)}
setAudioModalIsOpen(value) {
this.setState({ isAudioModalOpen: value })
}
render() {
const {
showMute,
@ -130,6 +147,8 @@ class AudioControls extends PureComponent {
isPresenter,
} = this.props;
const { isAudioModalOpen } = this.state;
const MUTE_ALERT_CONFIG = Meteor.settings.public.app.mutedAlert;
const { enabled: muteAlertEnabled } = MUTE_ALERT_CONFIG;
@ -144,6 +163,15 @@ class AudioControls extends PureComponent {
{
this.renderJoinLeaveButton()
}
{
isAudioModalOpen ? <AudioModalContainer
{...{
priority: "low",
setIsOpen: this.setAudioModalIsOpen,
isOpen: isAudioModalOpen
}}
/> : null
}
</Styled.Container>
);
}

View File

@ -1,6 +1,5 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import AudioManager from '/imports/ui/services/audio-manager';
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
import { withUsersConsumer } from '/imports/ui/components/components-data/users-context/context';
@ -9,7 +8,6 @@ import Auth from '/imports/ui/services/auth';
import Storage from '/imports/ui/services/storage/session';
import getFromUserSettings from '/imports/ui/services/users-settings';
import AudioControls from './component';
import AudioModalContainer from '../audio-modal/container';
import {
setUserSelectedMicrophone,
setUserSelectedListenOnly,
@ -65,7 +63,7 @@ const {
export default withUsersConsumer(
lockContextContainer(
withModalMounter(withTracker(({ mountModal, userLocks, users }) => {
withTracker(({ userLocks, users }) => {
const currentUser = users[Auth.meetingID][Auth.userID];
const isViewer = currentUser.role === ROLE_VIEWER;
const isPresenter = currentUser.presenter;
@ -87,15 +85,13 @@ export default withUsersConsumer(
talking: isTalking() && !isMuted(),
isVoiceUser: isVoiceUser(),
handleToggleMuteMicrophone: () => toggleMuteMicrophone(),
handleJoinAudio: () => (isConnected()
? joinListenOnly()
: mountModal(<AudioModalContainer />)
),
joinListenOnly,
handleLeaveAudio,
inputStream: AudioManager.inputStream,
isViewer,
isPresenter,
isConnected,
});
})(AudioControlsContainer)),
})(AudioControlsContainer),
),
);

View File

@ -14,6 +14,7 @@ const AUDIO_INPUT = 'audioinput';
const AUDIO_OUTPUT = 'audiooutput';
const DEFAULT_DEVICE = 'default';
const DEVICE_LABEL_MAX_LENGTH = 40;
const SET_SINK_ID_SUPPORTED = 'setSinkId' in HTMLMediaElement.prototype;
const intlMessages = defineMessages({
changeAudioDevice: {
@ -52,6 +53,10 @@ const intlMessages = defineMessages({
id: 'app.audioNotification.deviceChangeFailed',
description: 'Device change failed',
},
defaultOutputDeviceLabel: {
id: 'app.audio.audioSettings.defaultOutputDeviceLabel',
description: 'Default output device label',
},
});
const propTypes = {
@ -263,8 +268,10 @@ class InputStreamLiveSelector extends Component {
},
];
const deviceList = (listLength > 0)
? list.map((device, index) => (
let deviceList = [];
if (listLength > 0) {
deviceList = list.map((device, index) => (
{
key: `${device.deviceId}-${deviceKind}`,
dataTest: `${deviceKind}-${index + 1}`,
@ -273,8 +280,21 @@ class InputStreamLiveSelector extends Component {
iconRight: (device.deviceId === currentDeviceId) ? 'check' : null,
onClick: () => this.onDeviceListClick(device.deviceId, deviceKind, callback),
}
))
: [
));
} else if (deviceKind === AUDIO_OUTPUT && !SET_SINK_ID_SUPPORTED && listLength === 0) {
// If the browser doesn't support setSinkId, show the chosen output device
// as a placeholder Default - like it's done in audio/device-selector
deviceList = [
{
key: `defaultDeviceKey-${deviceKind}`,
label: intl.formatMessage(intlMessages.defaultOutputDeviceLabel),
customStyles: Styled.SelectedLabel,
iconRight: 'check',
disabled: true,
},
];
} else {
deviceList = [
{
key: `noDeviceFoundKey-${deviceKind}-`,
label: listLength < 0
@ -282,6 +302,8 @@ class InputStreamLiveSelector extends Component {
: intl.formatMessage(intlMessages.noDeviceFound),
},
];
}
return listTitle.concat(deviceList);
}

View File

@ -13,7 +13,6 @@ import AudioDial from '../audio-dial/component';
import AudioAutoplayPrompt from '../autoplay/component';
import Settings from '/imports/ui/services/settings';
import CaptionsSelectContainer from '/imports/ui/components/audio/captions/select/container';
import { showModal } from '/imports/ui/components/common/modal/service';
const propTypes = {
intl: PropTypes.shape({
@ -177,7 +176,6 @@ class AudioModal extends Component {
audioLocked,
isUsingAudio,
} = this.props;
window.addEventListener("CLOSE_AUDIO_MODAL", this.handleCloseAudioModal);
if (!isUsingAudio) {
if (forceListenOnlyAttendee || audioLocked) return this.handleJoinListenOnly();
@ -212,7 +210,6 @@ class AudioModal extends Component {
exitAudio();
}
if (resolve) resolve();
window.removeEventListener("CLOSE_AUDIO_MODAL", this.handleCloseAudioModal);
Session.set('audioModalIsOpen', false);
}
@ -252,10 +249,6 @@ class AudioModal extends Component {
});
}
handleCloseAudioModal = () => {
showModal(null);
}
handleGoToEchoTest() {
const { AudioError } = this.props;
const { MIC_ERROR } = AudioError;
@ -599,6 +592,9 @@ class AudioModal extends Component {
showPermissionsOvelay,
closeModal,
isIE,
isOpen,
priority,
setIsOpen,
} = this.props;
const { content } = this.state;
@ -607,6 +603,7 @@ class AudioModal extends Component {
<span>
{showPermissionsOvelay ? <PermissionsOverlay closeModal={closeModal} /> : null}
<Styled.AudioModal
modalName="AUDIO"
onRequestClose={closeModal}
data-test="audioModal"
contentLabel={intl.formatMessage(intlMessages.ariaModalTitle)}
@ -619,6 +616,11 @@ class AudioModal extends Component {
)
: null
}
{...{
setIsOpen,
isOpen,
priority,
}}
>
{isIE ? (
<Styled.BrowserWarning>

View File

@ -1,6 +1,5 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import browserInfo from '/imports/utils/browserInfo';
import getFromUserSettings from '/imports/ui/services/users-settings';
import AudioModal from './component';
@ -25,7 +24,7 @@ const APP_CONFIG = Meteor.settings.public.app;
const invalidDialNumbers = ['0', '613-555-1212', '613-555-1234', '0000'];
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
export default lockContextContainer(withModalMounter(withTracker(({ userLocks }) => {
export default lockContextContainer(withTracker(({ userLocks, setIsOpen }) => {
const listenOnlyMode = getFromUserSettings('bbb_listen_only_mode', APP_CONFIG.listenOnlyMode);
const forceListenOnly = getFromUserSettings('bbb_force_listen_only', APP_CONFIG.forceListenOnly);
const skipCheck = getFromUserSettings('bbb_skip_check_audio', APP_CONFIG.skipCheck);
@ -65,7 +64,7 @@ export default lockContextContainer(withModalMounter(withTracker(({ userLocks })
return ({
meetingIsBreakout,
closeModal,
closeModal: () => closeModal(() => setIsOpen(false)),
joinMicrophone: (skipEchoTest) => joinMicrophone(skipEchoTest || skipCheck || skipCheckOnJoin),
joinListenOnly,
leaveEchoTest,
@ -100,4 +99,4 @@ export default lockContextContainer(withModalMounter(withTracker(({ userLocks })
isRTL,
AudioError,
});
})(AudioModalContainer)));
})(AudioModalContainer));

View File

@ -1,4 +1,3 @@
import { showModal } from '/imports/ui/components/common/modal/service';
import Service from '../service';
import Storage from '/imports/ui/services/storage/session';
@ -38,7 +37,7 @@ export const joinMicrophone = (skipEchoTest = false) => {
});
return call.then(() => {
window.dispatchEvent(new Event("CLOSE_AUDIO_MODAL"));
document.dispatchEvent(new Event("CLOSE_MODAL_AUDIO"));
}).catch((error) => {
throw error;
});
@ -55,7 +54,7 @@ export const joinListenOnly = () => {
// prop transitions to a state where it was handled OR the user opts
// to close the modal.
if (!Service.autoplayBlocked()) {
window.dispatchEvent(new Event("CLOSE_AUDIO_MODAL"));
document.dispatchEvent(new Event("CLOSE_MODAL_AUDIO"));
}
resolve();
});
@ -72,11 +71,11 @@ export const leaveEchoTest = () => {
return Service.exitAudio();
};
export const closeModal = () => {
export const closeModal = (callback) => {
if (Service.isConnecting()) {
Service.forceExitAudio();
}
showModal(null);
callback();
};
export default {

View File

@ -1,6 +1,6 @@
import styled, { css, keyframes } from 'styled-components';
import Button from '/imports/ui/components/common/button/component';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import { colorPrimary } from '/imports/ui/stylesheets/styled-components/palette';
import {
@ -90,7 +90,7 @@ const ConnectingAnimation = styled.span`
}
`;
const AudioModal = styled(Modal)`
const AudioModal = styled(ModalSimple)`
padding: 1rem;
min-height: 20rem;
`;

View File

@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import Button from '/imports/ui/components/common/button/component';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import AudioTestContainer from '/imports/ui/components/audio/audio-test/container';
import Styled from './styles';
import logger from '/imports/startup/client/logger';
@ -375,4 +374,4 @@ class AudioSettings extends React.Component {
AudioSettings.propTypes = propTypes;
AudioSettings.defaultProps = defaultProps;
export default withModalMounter(injectIntl(AudioSettings));
export default injectIntl(AudioSettings);

View File

@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data';
import { Session } from 'meteor/session';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import { injectIntl, defineMessages } from 'react-intl';
import { range } from '/imports/utils/array-utils';
import Auth from '/imports/ui/services/auth';
@ -133,7 +132,24 @@ class AudioContainer extends PureComponent {
}
render() {
return null;
const { isAudioModalOpen, setAudioModalIsOpen,
setVideoPreviewModalIsOpen, isVideoPreviewModalOpen } = this.props;
return <>
{isAudioModalOpen ? <AudioModalContainer
{...{
priority: "low",
setIsOpen: setAudioModalIsOpen,
isOpen: isAudioModalOpen
}}
/> : null}
{isVideoPreviewModalOpen ? <VideoPreviewContainer
{...{
priority: "low",
setIsOpen: setVideoPreviewModalIsOpen,
isOpen: isVideoPreviewModalOpen
}}
/> : null}
</>;
}
}
@ -163,7 +179,8 @@ const messages = {
},
};
export default lockContextContainer(withModalMounter(injectIntl(withTracker(({ mountModal, intl, userLocks }) => {
export default lockContextContainer(injectIntl(withTracker(({ intl, userLocks, isAudioModalOpen, setAudioModalIsOpen,
setVideoPreviewModalIsOpen, isVideoPreviewModalOpen }) => {
const { microphoneConstraints } = Settings.application;
const autoJoin = getFromUserSettings('bbb_auto_join_audio', APP_CONFIG.autoJoin);
const enableVideo = getFromUserSettings('bbb_enable_video', KURENTO_CONFIG.enableVideo);
@ -174,14 +191,12 @@ export default lockContextContainer(withModalMounter(injectIntl(withTracker(({ m
const userSelectedListenOnly = didUserSelectedListenOnly();
const meetingIsBreakout = AppService.meetingIsBreakout();
const hasBreakoutRooms = AppService.getBreakoutRooms().length > 0;
const openAudioModal = () => new Promise((resolve) => {
mountModal(<AudioModalContainer resolve={resolve} />);
});
const openAudioModal = () => setAudioModalIsOpen(true);
const openVideoPreviewModal = () => new Promise((resolve) => {
if (userWebcam) return resolve();
mountModal(<VideoPreviewContainer resolve={resolve} />);
});
const openVideoPreviewModal = () => {
if (userWebcam) return;
setVideoPreviewModalIsOpen(true);
};
if (Service.isConnected() && !Service.isListenOnly()) {
Service.updateAudioConstraints(microphoneConstraints);
@ -208,11 +223,12 @@ export default lockContextContainer(withModalMounter(injectIntl(withTracker(({ m
return;
}
setTimeout(() => openAudioModal().then(() => {
if (enableVideo && autoShareWebcam) {
openVideoPreviewModal();
setTimeout(() => {
openAudioModal();
if (enableVideo && autoShareWebcam) {
openVideoPreviewModal();
}
}), 0);
}, 0);
},
});
}
@ -222,6 +238,8 @@ export default lockContextContainer(withModalMounter(injectIntl(withTracker(({ m
meetingIsBreakout,
userSelectedMicrophone,
userSelectedListenOnly,
isAudioModalOpen,
setAudioModalIsOpen,
init: async () => {
await Service.init(messages, intl);
const enableVideo = getFromUserSettings('bbb_enable_video', KURENTO_CONFIG.enableVideo);
@ -234,7 +252,9 @@ export default lockContextContainer(withModalMounter(injectIntl(withTracker(({ m
}
Session.set('audioModalIsOpen', true);
if (enableVideo && autoShareWebcam) {
openAudioModal().then(() => { openVideoPreviewModal(); didMountAutoJoin = true; });
openAudioModal()
openVideoPreviewModal();
didMountAutoJoin = true;
} else if (!(
userSelectedMicrophone
&& userSelectedListenOnly
@ -245,7 +265,7 @@ export default lockContextContainer(withModalMounter(injectIntl(withTracker(({ m
return Promise.resolve(true);
},
};
})(AudioContainer))));
})(AudioContainer)));
AudioContainer.propTypes = {
hasBreakoutRooms: PropTypes.bool.isRequired,

View File

@ -129,13 +129,12 @@ class DeviceSelector extends Component {
} = this.props;
const { options } = this.state;
const { isSafari } = browserInfo;
let notFoundOption;
if (blocked) {
notFoundOption = <option value="finding">{intl.formatMessage(intlMessages.findingDevicesLabel)}</option>;
} else if (kind === 'audiooutput' && isSafari) {
} else if (kind === 'audiooutput' && !('setSinkId' in HTMLMediaElement.prototype)) {
const defaultOutputDeviceLabel = intl.formatMessage(intlMessages.defaultOutputDeviceLabel);
notFoundOption = <option value="not-found">{defaultOutputDeviceLabel}</option>;
} else {

View File

@ -1,5 +1,5 @@
import styled, { css, keyframes } from 'styled-components';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import { colorBlack } from '/imports/ui/stylesheets/styled-components/palette';
import { jumboPaddingX } from '/imports/ui/stylesheets/styled-components/general';
@ -22,7 +22,7 @@ const bounce = keyframes`
}
`;
const PermissionsOverlayModal = styled(Modal)`
const PermissionsOverlayModal = styled(ModalSimple)`
${({ isFirefox }) => isFirefox && `
top: 8em;
left: 22em;

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import Modal from '/imports/ui/components/common/modal/fullscreen/component';
import ModalFullscreen from '/imports/ui/components/common/modal/fullscreen/component';
import logger from '/imports/startup/client/logger';
import PropTypes from 'prop-types';
import AudioService from '../audio/service';
@ -49,7 +48,6 @@ const propTypes = {
intl: PropTypes.object.isRequired,
breakout: PropTypes.objectOf(Object).isRequired,
getURL: PropTypes.func.isRequired,
mountModal: PropTypes.func.isRequired,
breakoutURL: PropTypes.string.isRequired,
isFreeJoin: PropTypes.bool.isRequired,
voiceUserJoined: PropTypes.bool.isRequired,
@ -97,7 +95,7 @@ class BreakoutJoinConfirmation extends Component {
handleJoinBreakoutConfirmation() {
const {
getURL,
mountModal,
setIsOpen,
breakoutURL,
isFreeJoin,
voiceUserJoined,
@ -133,7 +131,7 @@ class BreakoutJoinConfirmation extends Component {
Session.set('lastBreakoutIdOpened', selectValue);
window.open(url);
mountModal(null);
setIsOpen(false);
}
async fetchJoinURL(selectValue) {
@ -201,11 +199,13 @@ class BreakoutJoinConfirmation extends Component {
}
render() {
const { intl, breakoutName, isFreeJoin } = this.props;
const { intl, breakoutName, isFreeJoin, setIsOpen,
isOpen, priority,
} = this.props;
const { waiting } = this.state;
return (
<Modal
<ModalFullscreen
title={intl.formatMessage(intlMessages.title)}
confirm={{
callback: this.handleJoinBreakoutConfirmation,
@ -215,16 +215,22 @@ class BreakoutJoinConfirmation extends Component {
disabled: waiting,
}}
dismiss={{
callback: () => setIsOpen(false),
label: intl.formatMessage(intlMessages.dismissLabel),
description: intl.formatMessage(intlMessages.dismissDesc),
}}
{...{
setIsOpen,
isOpen,
priority,
}}
>
{ isFreeJoin ? this.renderSelectMeeting() : `${intl.formatMessage(intlMessages.message)} ${breakoutName}?`}
</Modal>
</ModalFullscreen>
);
}
}
export default withModalMounter(injectIntl(BreakoutJoinConfirmation));
export default injectIntl(BreakoutJoinConfirmation);
BreakoutJoinConfirmation.propTypes = propTypes;

View File

@ -33,14 +33,13 @@ const requestJoinURL = (breakoutId) => {
});
};
export default withTracker(({ breakout, mountModal, breakoutName }) => {
export default withTracker(({ breakout, breakoutName }) => {
const isFreeJoin = breakout.freeJoin;
const { breakoutId } = breakout;
const url = getURL(breakoutId);
return {
isFreeJoin,
mountModal,
breakoutName,
breakoutURL: url,
breakouts: breakoutService.getBreakouts(),

View File

@ -1,8 +1,7 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import BBBMenu from "/imports/ui/components/common/menu/component";
import CreateBreakoutRoomModal from '/imports/ui/components/actions-bar/create-breakout-room/container';
import CreateBreakoutRoomContainer from '/imports/ui/components/actions-bar/create-breakout-room/container';
import Trigger from "/imports/ui/components/common/control-header/right/component";
const intlMessages = defineMessages({
@ -27,6 +26,11 @@ const intlMessages = defineMessages({
class BreakoutDropdown extends PureComponent {
constructor(props) {
super(props);
this.state = {
isCreateBreakoutRoomModalOpen: false,
};
this.setCreateBreakoutRoomModalIsOpen = this.setCreateBreakoutRoomModalIsOpen.bind(this);
}
getAvailableActions() {
@ -36,7 +40,6 @@ class BreakoutDropdown extends PureComponent {
endAllBreakouts,
isMeteorConnected,
amIModerator,
mountModal,
} = this.props;
this.menuItems = [];
@ -58,9 +61,7 @@ class BreakoutDropdown extends PureComponent {
dataTest: 'openUpdateBreakoutUsersModal',
label: intl.formatMessage(intlMessages.manageUsers),
onClick: () => {
mountModal(
<CreateBreakoutRoomModal isUpdate />
);
this.setCreateBreakoutRoomModalIsOpen(true);
}
}
);
@ -82,12 +83,19 @@ class BreakoutDropdown extends PureComponent {
return this.menuItems;
}
setCreateBreakoutRoomModalIsOpen(value) {
this.setState({
isCreateBreakoutRoomModalOpen: value,
});
}
render() {
const {
intl,
isRTL,
} = this.props;
const { isCreateBreakoutRoomModalOpen } = this.state;
return (
<>
<BBBMenu
@ -112,9 +120,18 @@ class BreakoutDropdown extends PureComponent {
}}
actions={this.getAvailableActions()}
/>
{isCreateBreakoutRoomModalOpen ? <CreateBreakoutRoomContainer
{...{
isUpdate: true,
onRequestClose: () => this.setCreateBreakoutRoomModalIsOpen(false),
priority: "low",
setIsOpen: this.setCreateBreakoutRoomModalIsOpen,
isOpen: isCreateBreakoutRoomModalOpen
}}
/> : null}
</>
);
}
}
export default withModalMounter(injectIntl(BreakoutDropdown));
export default injectIntl(BreakoutDropdown);

View File

@ -1,14 +1,12 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Session } from 'meteor/session';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import BreakoutJoinConfirmation from '/imports/ui/components/breakout-join-confirmation/container';
import BreakoutJoinConfirmationContainer from '/imports/ui/components/breakout-join-confirmation/container';
import BreakoutService from '../service';
const BREAKOUT_MODAL_DELAY = 200;
const propTypes = {
mountModal: PropTypes.func.isRequired,
lastBreakoutReceived: PropTypes.shape({
breakoutUrlData: PropTypes.object.isRequired,
}),
@ -26,20 +24,17 @@ const defaultProps = {
breakouts: [],
};
const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) => mountModal(
<BreakoutJoinConfirmation
breakout={breakout}
breakoutName={breakoutName}
/>,
);
class BreakoutRoomInvitation extends Component {
constructor(props) {
super(props);
this.state = {
didSendBreakoutInvite: false,
isBreakoutJoinConfirmationModalOpen: false,
breakout: null,
breakoutName: null,
};
this.setBreakoutJoinConfirmationModalIsOpen = this.setBreakoutJoinConfirmationModalIsOpen.bind(this);
}
componentDidMount() {
@ -101,23 +96,43 @@ class BreakoutRoomInvitation extends Component {
inviteUserToBreakout(breakout) {
Session.set('lastBreakoutIdInvited', breakout.breakoutId);
const {
mountModal,
} = this.props;
// There's a race condition on page load with modals. Only one modal can be shown at a
// time and new ones overwrite old ones. We delay the opening of the breakout modal
// because it should always be on top if breakouts are running.
setTimeout(() => {
openBreakoutJoinConfirmation.call(this, breakout, breakout.name, mountModal);
this.setState({
breakout: breakout,
breakoutName: breakout.name,
})
this.setBreakoutJoinConfirmationModalIsOpen(true);
}, BREAKOUT_MODAL_DELAY);
}
setBreakoutJoinConfirmationModalIsOpen(value) {
this.setState({
isBreakoutJoinConfirmationModalOpen: value,
});
}
render() {
return null;
const { isBreakoutJoinConfirmationModalOpen, breakout, breakoutName } = this.state;
return (<>
{isBreakoutJoinConfirmationModalOpen ? <BreakoutJoinConfirmationContainer
breakout={breakout}
breakoutName={breakoutName}
{...{
onRequestClose: () => this.setBreakoutJoinConfirmationModalIsOpen(false),
priority: "medium",
setIsOpen: this.setBreakoutJoinConfirmationModalIsOpen,
isOpen: isBreakoutJoinConfirmationModalOpen
}}
/> : null}
</>
);
}
}
BreakoutRoomInvitation.propTypes = propTypes;
BreakoutRoomInvitation.defaultProps = defaultProps;
export default withModalMounter(BreakoutRoomInvitation);
export default BreakoutRoomInvitation;

View File

@ -1,15 +1,13 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import Service from '/imports/ui/components/captions/service';
import CaptionsReaderMenuContainer from '/imports/ui/components/captions/reader-menu/container';
import CaptionButton from './component';
const Container = (props) => <CaptionButton {...props} />;
export default withModalMounter(withTracker(({ mountModal }) => ({
export default withTracker(({ setIsOpen }) => ({
isActive: Service.isCaptionsActive(),
handleOnClick: () => (Service.isCaptionsActive()
? Service.deactivateCaptions()
: mountModal(<CaptionsReaderMenuContainer />)),
}))(Container));
: setIsOpen(true)),
}))(Container);

View File

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import Button from '/imports/ui/components/common/button/component';
import ColorPicker from "./color-picker/component";
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import Styled from './styles';
const DEFAULT_VALUE = 'select';
@ -207,6 +206,8 @@ class ReaderMenu extends PureComponent {
intl,
ownedLocales,
closeModal,
isOpen,
priority,
} = this.props;
const {
@ -231,6 +232,10 @@ class ReaderMenu extends PureComponent {
onRequestClose={closeModal}
hideBorder
contentLabel={intl.formatMessage(intlMessages.title)}
{...{
isOpen,
priority,
}}
>
<Styled.Title>
{intl.formatMessage(intlMessages.title)}
@ -408,4 +413,4 @@ class ReaderMenu extends PureComponent {
ReaderMenu.propTypes = propTypes;
export default injectIntl(withModalMounter(ReaderMenu));
export default injectIntl(ReaderMenu);

View File

@ -1,14 +1,13 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import ReaderMenu from './component';
import CaptionsService from '/imports/ui/components/captions/service';
const ReaderMenuContainer = (props) => <ReaderMenu {...props} />;
export default withModalMounter(withTracker(({ mountModal }) => ({
closeModal: () => mountModal(null),
export default withTracker(({ setIsOpen }) => ({
closeModal: () => setIsOpen(false),
activateCaptions: (locale, settings) => CaptionsService.activateCaptions(locale, settings),
getCaptionsSettings: () => CaptionsService.getCaptionsSettings(),
ownedLocales: CaptionsService.getOwnedLocales(),
}))(ReaderMenuContainer));
}))(ReaderMenuContainer);

View File

@ -1,6 +1,6 @@
import styled from 'styled-components';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import {
colorGrayDark,
colorWhite,
@ -10,7 +10,7 @@ import {
} from '/imports/ui/stylesheets/styled-components/palette';
import { borderSize, borderSizeLarge } from '/imports/ui/stylesheets/styled-components/general';
const ReaderMenuModal = styled(Modal)`
const ReaderMenuModal = styled(ModalSimple)`
padding: 1rem;
`;

View File

@ -1,6 +1,5 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import PropTypes from 'prop-types';
import Service from '/imports/ui/components/captions/service';
import LocalesDropdown from '/imports/ui/components/common/locales-dropdown/component';
@ -44,7 +43,6 @@ const intlMessages = defineMessages({
const propTypes = {
availableLocales: PropTypes.arrayOf(PropTypes.object).isRequired,
closeModal: PropTypes.func.isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
@ -68,9 +66,9 @@ class WriterMenu extends PureComponent {
}
componentWillUnmount() {
const { closeModal } = this.props;
const { setIsOpen } = this.props;
closeModal();
setIsOpen(false);
}
handleChange(event) {
@ -79,7 +77,7 @@ class WriterMenu extends PureComponent {
handleStart() {
const {
closeModal,
setIsOpen,
layoutContextDispatch,
} = this.props;
@ -95,24 +93,33 @@ class WriterMenu extends PureComponent {
value: PANELS.CAPTIONS,
});
closeModal();
setIsOpen(false);
}
render() {
const {
intl,
availableLocales,
closeModal,
isOpen,
onRequestClose,
priority,
setIsOpen
} = this.props;
const { locale } = this.state;
return (
<Styled.WriterMenuModal
onRequestClose={closeModal}
hideBorder
contentLabel={intl.formatMessage(intlMessages.title)}
title={intl.formatMessage(intlMessages.title)}
{...{
isOpen,
onRequestClose,
priority,
setIsOpen
}}
>
<Styled.Content>
<span>
@ -151,4 +158,4 @@ class WriterMenu extends PureComponent {
WriterMenu.propTypes = propTypes;
export default injectIntl(withModalMounter(WriterMenu));
export default injectIntl(WriterMenu);

View File

@ -1,6 +1,5 @@
import React, { useContext } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import Service from '/imports/ui/components/captions/service';
import WriterMenu from './component';
import { layoutDispatch } from '../../layout/context';
@ -20,7 +19,6 @@ const WriterMenuContainer = (props) => {
return amIModerator && <WriterMenu {...{ layoutContextDispatch, ...props }} />;
};
export default withModalMounter(withTracker(({ mountModal }) => ({
closeModal: () => mountModal(null),
export default withTracker(() => ({
availableLocales: Service.getAvailableLocales(),
}))(WriterMenuContainer));
}))(WriterMenuContainer);

View File

@ -12,9 +12,9 @@ import {
colorPrimary,
} from '/imports/ui/stylesheets/styled-components/palette';
import Button from '/imports/ui/components/common/button/component';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
const WriterMenuModal = styled(Modal)`
const WriterMenuModal = styled(ModalSimple)`
min-height: 20rem;
`;

View File

@ -1,6 +1,5 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import BBBMenu from '/imports/ui/components/common/menu/component';
import { getDateString, uniqueId } from '/imports/utils/string-utils';
import Trigger from '/imports/ui/components/common/control-header/right/component';
@ -159,4 +158,4 @@ class ChatDropdown extends PureComponent {
}
}
export default withModalMounter(injectIntl(ChatDropdown));
export default injectIntl(ChatDropdown);

View File

@ -329,7 +329,6 @@ class TimeWindowList extends PureComponent {
}}
key="chat-list"
data-test="chatMessages"
aria-live="polite"
ref={node => this.messageListWrapper = node}
onCopy={(e) => { e.stopPropagation(); }}
>

View File

@ -164,6 +164,7 @@ export default class ButtonBase extends React.Component {
'iconRight',
'isVisualEffects',
'panning',
'panSelected',
];
return (

View File

@ -44,6 +44,7 @@ const EmojiButton = styled.button`
overflow: hidden;
z-index: 2;
border: none;
padding: 0;
[dir="rtl"] & {
right: initial;

View File

@ -123,6 +123,8 @@ export default class Button extends BaseButton {
'aria-label': ariaLabel,
'aria-expanded': ariaExpanded,
tooltipLabel,
tooltipdelay,
tooltipplacement,
} = this.props;
const renderFuncName = circle ? 'renderCircle' : 'renderDefault';
@ -132,6 +134,8 @@ export default class Button extends BaseButton {
return (
<TooltipContainer
title={tooltipLabel || buttonLabel}
delay={tooltipdelay}
placement={tooltipplacement}
>
{this[renderFuncName]()}
</TooltipContainer>

View File

@ -1,7 +1,6 @@
import React from 'react';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import FallbackView from '../fallback-view/component';
const intlMessages = defineMessages({
@ -11,14 +10,17 @@ const intlMessages = defineMessages({
},
});
const FallbackModal = ({ error, intl, mountModal }) => (
const FallbackModal = ({ error, intl }) => {
return (
<ModalSimple
hideBorder
onRequestClose={() => mountModal(null)}
priority="medium"
shouldShowCloseButton={false}
contentLabel={intl.formatMessage(intlMessages.ariaTitle)}
isOpen={!!error}
>
<FallbackView {...{ error }} />
</ModalSimple>
);
)};
export default withModalMounter(injectIntl(FallbackModal));
export default injectIntl(FallbackModal);

View File

@ -1,91 +1,39 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { useCallback, useEffect } from 'react';
import Styled from './styles';
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
const propTypes = {
overlayClassName: PropTypes.string.isRequired,
portalClassName: PropTypes.string.isRequired,
contentLabel: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
};
const BaseModal = (props) => {
const { setIsOpen, modalName, children,
isOpen, onRequestClose, className, overlayClassName,
} = props;
const defaultProps = {
overlayClassName: 'modalOverlay',
contentLabel: 'Modal',
isOpen: true,
};
const closeEventHandler = useCallback (() => {
setIsOpen(false);
} , []);
useEffect( () => {
// Only add event listener if name is specified
if(!modalName) return;
export default class ModalBase extends Component {
const closeEventName = `CLOSE_MODAL_${modalName.toUpperCase()}`;
componentDidMount() {
registerTitleView(this.props.contentLabel);
}
// Listen to close event on mount
document.addEventListener(closeEventName, closeEventHandler);
componentWillUnmount() {
unregisterTitleView();
}
// Remove listener on unmount
return () => {
document.removeEventListener(closeEventName, closeEventHandler);
};
}, []);
const priority = props.priority ? props.priority : "low"
return (<Styled.BaseModal
portalClassName={`modal-${priority}`}
parentSelector={()=>document.querySelector('#modals-container')}
isOpen={isOpen}
onRequestClose={onRequestClose}
className={className}
overlayClassName={overlayClassName}
>
{children}
</Styled.BaseModal>
)}
render() {
const {
isOpen,
'data-test': dataTest,
} = this.props;
if (!isOpen) return null;
return (
<Styled.BaseModal
{...this.props}
parentSelector={() => {
if (document.fullscreenElement &&
document.fullscreenElement.nodeName &&
document.fullscreenElement.nodeName.toLowerCase() === 'div')
return document.fullscreenElement;
else return document.body;
}}
data={{
test: dataTest ?? null,
}}
>
{this.props.children}
</Styled.BaseModal>
);
}
}
ModalBase.propTypes = propTypes;
ModalBase.defaultProps = defaultProps;
export const withModalState = ComponentToWrap =>
class ModalStateWrapper extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: true,
};
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
}
hide(cb = () => { }) {
Promise.resolve(cb())
.then(() => this.setState({ isOpen: false }));
}
show(cb = () => { }) {
Promise.resolve(cb())
.then(() => this.setState({ isOpen: true }));
}
render() {
return (<ComponentToWrap
{...this.props}
modalHide={this.hide}
modalShow={this.show}
modalisOpen={this.state.isOpen}
/>);
}
};
export default { BaseModal };

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react';
import { defineMessages } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import PropTypes from 'prop-types';
import Styled from './styles';
@ -39,7 +38,7 @@ class ConfirmationModal extends Component {
render() {
const {
intl,
mountModal,
setIsOpen,
onConfirm,
title,
titleMessageId,
@ -51,6 +50,9 @@ class ConfirmationModal extends Component {
confirmParam,
disableConfirmButton,
description,
isOpen,
onRequestClose,
priority,
} = this.props;
const {
@ -61,9 +63,14 @@ class ConfirmationModal extends Component {
return (
<Styled.ConfirmationModal
onRequestClose={() => mountModal(null)}
onRequestClose={() => setIsOpen(false)}
contentLabel={title}
title={title || intl.formatMessage({ id: titleMessageId }, { 0: titleMessageExtra })}
{...{
isOpen,
onRequestClose,
priority,
}}
>
<Styled.Container>
<Styled.Description>
@ -92,12 +99,12 @@ class ConfirmationModal extends Component {
data-test={confirmButtonDataTest}
onClick={() => {
onConfirm(confirmParam, checked);
mountModal(null);
setIsOpen(false);
}}
/>
<Styled.CancelButton
label={intl.formatMessage(messages.noLabel)}
onClick={() => mountModal(null)}
onClick={() => setIsOpen(false)}
/>
</Styled.Footer>
</Styled.Container>
@ -109,4 +116,4 @@ class ConfirmationModal extends Component {
ConfirmationModal.propTypes = propTypes;
ConfirmationModal.defaultProps = defaultProps;
export default withModalMounter(ConfirmationModal);
export default ConfirmationModal;

View File

@ -1,6 +1,6 @@
import styled from 'styled-components';
import Button from '/imports/ui/components/common/button/component';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import {
smPaddingX,
mdPaddingX,
@ -10,7 +10,7 @@ import {
import { colorGray } from '/imports/ui/stylesheets/styled-components/palette';
import { lineHeightBase } from '/imports/ui/stylesheets/styled-components/typography';
const ConfirmationModal = styled(Modal)`
const ConfirmationModal = styled(ModalSimple)`
padding: ${mdPaddingX};
`;

View File

@ -1,6 +0,0 @@
import { withTracker } from 'meteor/react-meteor-data';
import { getModal } from './service';
export default withTracker(() => ({
modalComponent: getModal(),
}))(({ modalComponent }) => modalComponent);

View File

@ -1,7 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalState } from '../base/component';
import Styled from './styles';
const intlMessages = defineMessages({
@ -61,7 +60,7 @@ class ModalFullscreen extends PureComponent {
}
handleAction(name) {
const { confirm, dismiss, modalHide } = this.props;
const { confirm, dismiss } = this.props;
const { callback: callBackConfirm } = confirm;
const { callback: callBackDismiss } = dismiss;
@ -78,7 +77,7 @@ class ModalFullscreen extends PureComponent {
break;
}
return modalHide(callback);
return callback();
}
render() {
@ -89,7 +88,7 @@ class ModalFullscreen extends PureComponent {
dismiss,
className,
children,
modalisOpen,
isOpen,
preventClosing,
...otherProps
} = this.props;
@ -103,7 +102,7 @@ class ModalFullscreen extends PureComponent {
return (
<Styled.FullscreenModal
id="fsmodal"
isOpen={modalisOpen || preventClosing}
isOpen={isOpen || preventClosing}
contentLabel={title}
overlayClassName={"fullscreenModalOverlay"}
{...otherProps}
@ -147,4 +146,4 @@ class ModalFullscreen extends PureComponent {
ModalFullscreen.propTypes = propTypes;
ModalFullscreen.defaultProps = defaultProps;
export default withModalState(injectIntl(ModalFullscreen));
export default injectIntl(ModalFullscreen);

View File

@ -1,5 +1,5 @@
import styled from 'styled-components';
import Styled from '../base/styles';
import Styled from '../base/component';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Button from '/imports/ui/components/common/button/component';
import {

View File

@ -3,7 +3,6 @@ import Styled from './styles';
import PropTypes from 'prop-types';
const propTypes = {
children: PropTypes.node.isRequired,
hideBorder: PropTypes.bool,
headerPosition: PropTypes.string,
shouldShowCloseButton: PropTypes.bool,

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import Modal from '/imports/ui/components/common/modal/simple/component';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import AudioService from '/imports/ui/components/audio/service';
import Styled from './styles';
@ -42,7 +42,6 @@ const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
mountModal: PropTypes.func.isRequired,
numAvailableViewers: PropTypes.number.isRequired,
randomUserReq: PropTypes.func.isRequired,
};
@ -123,11 +122,13 @@ class RandomUserSelect extends Component {
keepModalOpen,
toggleKeepModalOpen,
intl,
mountModal,
setIsOpen,
numAvailableViewers,
currentUser,
clearRandomlySelectedUser,
mappedRandomlySelectedUsers,
isOpen,
priority,
} = this.props;
const counter = SELECT_RANDOM_USER_COUNTDOWN ? this.state.count : 0;
@ -189,17 +190,21 @@ class RandomUserSelect extends Component {
}
if (keepModalOpen) {
return (
<Modal
<ModalSimple
onRequestClose={() => {
if (currentUser.presenter) clearRandomlySelectedUser();
toggleKeepModalOpen();
mountModal(null);
setIsOpen(false);
}}
contentLabel={intl.formatMessage(messages.ariaModalTitle)}
title={title}
{...{
isOpen,
priority,
}}
>
{viewElement}
</Modal>
</ModalSimple>
);
} else {
return null;

View File

@ -3,7 +3,6 @@ import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings';
import Users from '/imports/api/users';
import Auth from '/imports/ui/services/auth';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import { makeCall } from '/imports/ui/services/api';
import RandomUserSelect from './component';
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
@ -56,12 +55,14 @@ const RandomUserSelectContainer = (props) => {
if (randomlySelectedUser) {
mappedRandomlySelectedUsers = randomlySelectedUser.map((ui) => {
const selectedUser = users[Auth.meetingID][ui[0]];
return [{
userId: selectedUser.userId,
avatar: selectedUser.avatar,
color: selectedUser.color,
name: selectedUser.name,
}, ui[1]];
if (selectedUser){
return [{
userId: selectedUser.userId,
avatar: selectedUser.avatar,
color: selectedUser.color,
name: selectedUser.name,
}, ui[1]];
}
});
}
@ -74,7 +75,7 @@ const RandomUserSelectContainer = (props) => {
/>
);
};
export default withModalMounter(withTracker(({ mountModal }) => {
export default withTracker(() => {
const viewerPool = Users.find({
meetingId: Auth.meetingID,
presenter: { $ne: true },
@ -96,11 +97,10 @@ export default withModalMounter(withTracker(({ mountModal }) => {
const clearRandomlySelectedUser = () => (SELECT_RANDOM_USER_ENABLED ? makeCall('clearRandomlySelectedUser') : null);
return ({
closeModal: () => mountModal(null),
toggleKeepModalOpen,
numAvailableViewers: viewerPool.length,
randomUserReq,
clearRandomlySelectedUser,
randomlySelectedUser: meeting.randomlySelectedUser,
});
})(RandomUserSelectContainer));
})(RandomUserSelectContainer);

Some files were not shown because too many files have changed in this diff Show More