Merge 2.7 into Develop
This commit is contained in:
commit
5681e88c60
1
.github/workflows/automated-tests.yml
vendored
1
.github/workflows/automated-tests.yml
vendored
@ -45,6 +45,7 @@ jobs:
|
||||
- run: ./build/setup.sh bbb-web
|
||||
- run: ./build/setup.sh bbb-webrtc-sfu
|
||||
- run: ./build/setup.sh bbb-webrtc-recorder
|
||||
- run: ./build/setup.sh bbb-transcription-controller
|
||||
- run: ./build/setup.sh bigbluebutton
|
||||
- run: tar cvf artifacts.tar artifacts/
|
||||
- name: Archive packages
|
||||
|
@ -12,7 +12,7 @@ stages:
|
||||
|
||||
# define which docker image to use for builds
|
||||
default:
|
||||
image: bigbluebutton/bbb-build:2023-04-25
|
||||
image: bigbluebutton/bbb-build:bbb27-2023-06-13-java17
|
||||
|
||||
# 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
|
||||
@ -51,6 +51,7 @@ get_external_dependencies:
|
||||
- freeswitch
|
||||
- bbb-pads
|
||||
- bbb-playback
|
||||
- bbb-transcription-controller
|
||||
expire_in: 1h 30min
|
||||
|
||||
# template job for build step
|
||||
@ -181,6 +182,11 @@ bbb-webrtc-recorder-build:
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-webrtc-recorder
|
||||
|
||||
bbb-transcription-controller-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-transcription-controller
|
||||
|
||||
bigbluebutton-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
|
@ -18,7 +18,7 @@ val compileSettings = Seq(
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:11",
|
||||
"-release:17",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
@ -76,4 +76,4 @@ daemonGroup in Linux := group
|
||||
|
||||
javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
|
||||
|
||||
debianPackageDependencies in Debian ++= Seq("java11-runtime-headless", "bash")
|
||||
debianPackageDependencies in Debian ++= Seq("java17-runtime-headless", "bash")
|
||||
|
@ -11,11 +11,11 @@ trait UpdateTranscriptPubMsgHdlr {
|
||||
def handle(msg: UpdateTranscriptPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
def broadcastEvent(userId: String, transcriptId: String, transcript: String, locale: String): Unit = {
|
||||
def broadcastEvent(userId: String, transcriptId: String, transcript: String, locale: String, result: Boolean): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(TranscriptUpdatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(TranscriptUpdatedEvtMsg.NAME, meetingId, userId)
|
||||
val body = TranscriptUpdatedEvtMsgBody(transcriptId, transcript, locale)
|
||||
val body = TranscriptUpdatedEvtMsgBody(transcriptId, transcript, locale, result)
|
||||
val event = TranscriptUpdatedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
@ -67,7 +67,8 @@ trait UpdateTranscriptPubMsgHdlr {
|
||||
msg.header.userId,
|
||||
msg.body.transcriptId,
|
||||
transcript,
|
||||
msg.body.locale
|
||||
msg.body.locale,
|
||||
msg.body.result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -148,10 +148,9 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
|
||||
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
|
||||
|
||||
val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
|
||||
val isOriginalPresentationType = m.body.typeOfExport == "Original"
|
||||
|
||||
if (!isOriginalPresentationType && annotationCount > 0) {
|
||||
if (!isOriginalPresentationType) {
|
||||
// Send Export Job to Redis
|
||||
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
|
||||
bus.outGW.send(job)
|
||||
@ -159,9 +158,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
// Send Annotations to Redis
|
||||
val annotations = StoredAnnotations(jobId, presId, storeAnnotationPages)
|
||||
bus.outGW.send(buildStoreAnnotationsInRedisSysMsg(annotations, liveMeeting))
|
||||
} else if (!isOriginalPresentationType && annotationCount == 0) {
|
||||
log.error("There are no annotations for presentation with Id {}... Ignoring", presId)
|
||||
} else if (isOriginalPresentationType) {
|
||||
} else {
|
||||
// Return existing uploaded file directly
|
||||
val convertedFileName = currentPres.get.filenameConverted
|
||||
val filename = if (convertedFileName == "") currentPres.get.name else convertedFileName
|
||||
|
@ -17,11 +17,8 @@ trait GetScreenBroadcastPermissionReqMsgHdlr {
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
} yield {
|
||||
if (props.meetingProp.disabledFeatures.contains("screenshare")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "Screen sharing is disabled for this meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL,
|
||||
liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to share the screen."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -33,31 +33,41 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
|
||||
ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel) +
|
||||
" URL:" + ScreenshareModel.getRTMPBroadcastingUrl(liveMeeting.screenshareModel))
|
||||
|
||||
// only valid if not broadcasting yet
|
||||
if (!ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel)) {
|
||||
// Stop external video if it's running
|
||||
ExternalVideoModel.stop(bus.outGW, liveMeeting)
|
||||
|
||||
ScreenshareModel.setRTMPBroadcastingUrl(liveMeeting.screenshareModel, msg.body.stream)
|
||||
ScreenshareModel.broadcastingRTMPStarted(liveMeeting.screenshareModel)
|
||||
ScreenshareModel.setScreenshareVideoWidth(liveMeeting.screenshareModel, msg.body.vidWidth)
|
||||
ScreenshareModel.setScreenshareVideoHeight(liveMeeting.screenshareModel, msg.body.vidHeight)
|
||||
ScreenshareModel.setVoiceConf(liveMeeting.screenshareModel, msg.body.voiceConf)
|
||||
ScreenshareModel.setScreenshareConf(liveMeeting.screenshareModel, msg.body.screenshareConf)
|
||||
ScreenshareModel.setTimestamp(liveMeeting.screenshareModel, msg.body.timestamp)
|
||||
ScreenshareModel.setHasAudio(liveMeeting.screenshareModel, msg.body.hasAudio)
|
||||
ScreenshareModel.setContentType(liveMeeting.screenshareModel, msg.body.contentType)
|
||||
|
||||
log.info("START broadcast ALLOWED when isBroadcastingRTMP=false")
|
||||
|
||||
ScreenshareDAO.insert(liveMeeting.props.meetingProp.intId, liveMeeting.screenshareModel)
|
||||
|
||||
// Notify viewers in the meeting that there's an rtmp stream to view
|
||||
val msgEvent = broadcastEvent(msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream,
|
||||
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp, msg.body.hasAudio, msg.body.contentType)
|
||||
bus.outGW.send(msgEvent)
|
||||
if (msg.body.contentType == "camera" && liveMeeting.props.meetingProp.disabledFeatures.contains("cameraAsContent")) {
|
||||
log.error(
|
||||
"Camera as a content is disabled for meeting {}, meetingID = {}",
|
||||
liveMeeting.props.meetingProp.name, liveMeeting.props.meetingProp.intId
|
||||
)
|
||||
} else if (msg.body.contentType == "screenshare" && liveMeeting.props.meetingProp.disabledFeatures.contains("screenshare")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
log.error("Screen sharing is disabled for this meeting, meetingID = {}", meetingId)
|
||||
} else {
|
||||
log.info("START broadcast NOT ALLOWED when isBroadcastingRTMP=true")
|
||||
// only valid if not broadcasting yet
|
||||
if (!ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel)) {
|
||||
// Stop external video if it's running
|
||||
ExternalVideoModel.stop(bus.outGW, liveMeeting)
|
||||
|
||||
ScreenshareModel.setRTMPBroadcastingUrl(liveMeeting.screenshareModel, msg.body.stream)
|
||||
ScreenshareModel.broadcastingRTMPStarted(liveMeeting.screenshareModel)
|
||||
ScreenshareModel.setScreenshareVideoWidth(liveMeeting.screenshareModel, msg.body.vidWidth)
|
||||
ScreenshareModel.setScreenshareVideoHeight(liveMeeting.screenshareModel, msg.body.vidHeight)
|
||||
ScreenshareModel.setVoiceConf(liveMeeting.screenshareModel, msg.body.voiceConf)
|
||||
ScreenshareModel.setScreenshareConf(liveMeeting.screenshareModel, msg.body.screenshareConf)
|
||||
ScreenshareModel.setTimestamp(liveMeeting.screenshareModel, msg.body.timestamp)
|
||||
ScreenshareModel.setHasAudio(liveMeeting.screenshareModel, msg.body.hasAudio)
|
||||
ScreenshareModel.setContentType(liveMeeting.screenshareModel, msg.body.contentType)
|
||||
|
||||
log.info("START broadcast ALLOWED when isBroadcastingRTMP=false")
|
||||
|
||||
ScreenshareDAO.insert(liveMeeting.props.meetingProp.intId, liveMeeting.screenshareModel)
|
||||
|
||||
// Notify viewers in the meeting that there's an rtmp stream to view
|
||||
val msgEvent = broadcastEvent(msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream,
|
||||
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp, msg.body.hasAudio, msg.body.contentType)
|
||||
bus.outGW.send(msgEvent)
|
||||
} else {
|
||||
log.info("START broadcast NOT ALLOWED when isBroadcastingRTMP=true")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,16 +30,23 @@ trait ActivateTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) &&
|
||||
permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter or moderator to activate timer"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
val isTimerFeatureEnabled: Boolean = !liveMeeting.props.meetingProp.disabledFeatures.contains("timer")
|
||||
|
||||
if (!isTimerFeatureEnabled) {
|
||||
log.error("Timer feature is disabled for meeting {}, meetingId={}", liveMeeting.props.meetingProp.name,
|
||||
liveMeeting.props.meetingProp.intId)
|
||||
} else {
|
||||
TimerModel.reset(liveMeeting.timerModel)
|
||||
TimerModel.setIsActive(liveMeeting.timerModel, true)
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent(msg.body.stopwatch, msg.body.running, msg.body.time, msg.body.accumulated, msg.body.track)
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) &&
|
||||
permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter or moderator to activate timer"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
TimerModel.reset(liveMeeting.timerModel)
|
||||
TimerModel.setIsActive(liveMeeting.timerModel, true)
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent(msg.body.stopwatch, msg.body.running, msg.body.time, msg.body.accumulated, msg.body.track)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,4 +28,4 @@ trait TimerEndedPubMsgHdlr extends RightsManagementTrait {
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
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 }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
|
||||
this: UsersApp =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleChangeUserAwayReqMsg(msg: ChangeUserAwayReqMsg): Unit = {
|
||||
log.info("handleChangeUserAwayReqMsg: away={} userId={}", msg.body.away, msg.body.userId)
|
||||
|
||||
def broadcast(user: UserState, away: Boolean): Unit = {
|
||||
val routingChange = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, user.intId
|
||||
)
|
||||
val envelopeChange = BbbCoreEnvelope(UserAwayChangedEvtMsg.NAME, routingChange)
|
||||
val headerChange = BbbClientMsgHeader(UserAwayChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
|
||||
user.intId)
|
||||
|
||||
val bodyChange = UserAwayChangedEvtMsgBody(user.intId, away)
|
||||
val eventChange = UserAwayChangedEvtMsg(headerChange, bodyChange)
|
||||
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
|
||||
outGW.send(msgEventChange)
|
||||
}
|
||||
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
newUserState <- Users2x.setUserAway(liveMeeting.users2x, user.intId, msg.body.away)
|
||||
} yield {
|
||||
if (msg.body.away && user.emoji == "") {
|
||||
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "away")
|
||||
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "away"))
|
||||
}
|
||||
|
||||
if (msg.body.away == false && user.emoji == "away") {
|
||||
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none")
|
||||
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none"))
|
||||
}
|
||||
|
||||
broadcast(newUserState, msg.body.away)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: BaseMeetingActor =>
|
||||
@ -37,7 +38,18 @@ trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait {
|
||||
for {
|
||||
uvo <- Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, msg.body.emoji)
|
||||
} yield {
|
||||
sendUserEmojiChangedEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji)
|
||||
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji))
|
||||
|
||||
if (initialEmojiState == "raiseHand" || nextEmojiState == "raiseHand") {
|
||||
Users2x.setUserRaiseHand(liveMeeting.users2x, msg.body.userId, msg.body.emoji == "raiseHand")
|
||||
outGW.send(MsgBuilder.buildUserRaiseHandChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji == "raiseHand"))
|
||||
}
|
||||
|
||||
if (initialEmojiState == "away" || nextEmojiState == "away") {
|
||||
Users2x.setUserAway(liveMeeting.users2x, msg.body.userId, msg.body.emoji == "away")
|
||||
outGW.send(MsgBuilder.buildUserAwayChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji == "away"))
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
@ -46,13 +58,4 @@ trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait {
|
||||
}
|
||||
}
|
||||
|
||||
def sendUserEmojiChangedEvtMsg(outGW: OutMsgRouter, meetingId: String, userId: String, emoji: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(UserEmojiChangedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserEmojiChangedEvtMsg.NAME, meetingId, userId)
|
||||
val body = UserEmojiChangedEvtMsgBody(userId, emoji)
|
||||
val event = UserEmojiChangedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.models.{ UserState, Users2x }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait ChangeUserRaiseHandReqMsgHdlr extends RightsManagementTrait {
|
||||
this: UsersApp =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleChangeUserRaiseHandReqMsg(msg: ChangeUserRaiseHandReqMsg): Unit = {
|
||||
log.info("handleChangeUserRaiseHandReqMsg: raiseHand={} userId={}", msg.body.raiseHand, msg.body.userId)
|
||||
|
||||
def broadcast(user: UserState, raiseHand: Boolean): Unit = {
|
||||
val routingChange = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, user.intId
|
||||
)
|
||||
val envelopeChange = BbbCoreEnvelope(UserRaiseHandChangedEvtMsg.NAME, routingChange)
|
||||
val headerChange = BbbClientMsgHeader(UserRaiseHandChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
|
||||
user.intId)
|
||||
|
||||
val bodyChange = UserRaiseHandChangedEvtMsgBody(user.intId, raiseHand)
|
||||
val eventChange = UserRaiseHandChangedEvtMsg(headerChange, bodyChange)
|
||||
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
|
||||
outGW.send(msgEventChange)
|
||||
}
|
||||
|
||||
val isUserSettingOwnProps = (msg.header.userId == msg.body.userId)
|
||||
|
||||
val isUserModerator = !permissionFailed(
|
||||
PermissionCheck.MOD_LEVEL,
|
||||
PermissionCheck.VIEWER_LEVEL,
|
||||
liveMeeting.users2x,
|
||||
msg.header.userId
|
||||
)
|
||||
|
||||
val isUserPresenter = !permissionFailed(
|
||||
PermissionCheck.VIEWER_LEVEL,
|
||||
PermissionCheck.PRESENTER_LEVEL,
|
||||
liveMeeting.users2x,
|
||||
msg.header.userId
|
||||
)
|
||||
|
||||
if (isUserSettingOwnProps || isUserModerator || isUserPresenter) {
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
newUserState <- Users2x.setUserRaiseHand(liveMeeting.users2x, user.intId, msg.body.raiseHand)
|
||||
} yield {
|
||||
|
||||
if (msg.body.raiseHand && user.emoji == "") {
|
||||
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "raiseHand")
|
||||
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "raiseHand"))
|
||||
}
|
||||
|
||||
if (msg.body.raiseHand == false && user.emoji == "raiseHand") {
|
||||
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none")
|
||||
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none"))
|
||||
}
|
||||
|
||||
broadcast(newUserState, msg.body.raiseHand)
|
||||
}
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to change user raiseHand prop."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
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 ChangeUserReactionEmojiReqMsgHdlr extends RightsManagementTrait {
|
||||
this: UsersApp =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleChangeUserReactionEmojiReqMsg(msg: ChangeUserReactionEmojiReqMsg): Unit = {
|
||||
log.info("handleChangeUserReactionEmojiReqMsg: reactionEmoji={} userId={}", msg.body.reactionEmoji, msg.body.userId)
|
||||
|
||||
def broadcast(user: UserState, reactionEmoji: String): Unit = {
|
||||
val routingChange = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, user.intId
|
||||
)
|
||||
val envelopeChange = BbbCoreEnvelope(UserReactionEmojiChangedEvtMsg.NAME, routingChange)
|
||||
val headerChange = BbbClientMsgHeader(UserReactionEmojiChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
|
||||
user.intId)
|
||||
|
||||
val bodyChange = UserReactionEmojiChangedEvtMsgBody(user.intId, reactionEmoji)
|
||||
val eventChange = UserReactionEmojiChangedEvtMsg(headerChange, bodyChange)
|
||||
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
|
||||
outGW.send(msgEventChange)
|
||||
}
|
||||
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji)
|
||||
} yield {
|
||||
if (user.reactionEmoji != msg.body.reactionEmoji) {
|
||||
broadcast(newUserState, msg.body.reactionEmoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,9 +22,10 @@ trait ClearAllUsersEmojiCmdMsgHdlr extends RightsManagementTrait {
|
||||
if (isUserModerator) {
|
||||
for {
|
||||
user <- Users2x.findAll(liveMeeting.users2x)
|
||||
if user.emoji.equals("raiseHand") || user.emoji.equals("away") || user.emoji.equals("notAway")
|
||||
} yield {
|
||||
Users2x.setEmojiStatus(liveMeeting.users2x, user.intId, "none")
|
||||
Users2x.setUserAway(liveMeeting.users2x, user.intId, false)
|
||||
Users2x.setUserRaiseHand(liveMeeting.users2x, user.intId, false)
|
||||
}
|
||||
sendClearedAllUsersEmojiEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
} else {
|
||||
|
@ -0,0 +1,41 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.{ UserState, Users2x }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
|
||||
trait SetUserSpeechLocaleMsgHdlr extends RightsManagementTrait {
|
||||
this: UsersApp =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleSetUserSpeechLocaleReqMsg(msg: SetUserSpeechLocaleReqMsg): Unit = {
|
||||
log.info("handleSetUserSpeechLocaleReqMsg: locale={} provider={} userId={}", msg.body.locale, msg.body.provider, msg.header.userId)
|
||||
|
||||
def broadcastUserSpeechLocaleChanged(user: UserState, locale: String, provider: String): Unit = {
|
||||
val routingChange = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, user.intId
|
||||
)
|
||||
val envelopeChange = BbbCoreEnvelope(UserSpeechLocaleChangedEvtMsg.NAME, routingChange)
|
||||
val headerChange = BbbClientMsgHeader(UserSpeechLocaleChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, user.intId)
|
||||
|
||||
val bodyChange = UserSpeechLocaleChangedEvtMsgBody(locale, provider)
|
||||
val eventChange = UserSpeechLocaleChangedEvtMsg(headerChange, bodyChange)
|
||||
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
|
||||
outGW.send(msgEventChange)
|
||||
}
|
||||
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
var changeLocale: Option[UserState] = None;
|
||||
changeLocale = Users2x.setUserSpeechLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale)
|
||||
broadcastUserSpeechLocaleChanged(user, msg.body.locale, msg.body.provider)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -12,11 +12,8 @@ trait UserReactionTimeExpiredCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handleUserReactionTimeExpiredCmdMsg(msg: UserReactionTimeExpiredCmdMsg) {
|
||||
val isNodeUser = msg.header.userId.equals("nodeJSapp")
|
||||
|
||||
val currentEmojiState = Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId).get.emoji
|
||||
|
||||
if (isNodeUser && (!currentEmojiState.equals("raiseHand") && !currentEmojiState.equals("away") && !currentEmojiState.equals("notAway"))) {
|
||||
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none")
|
||||
if (isNodeUser) {
|
||||
Users2x.setReactionEmoji(liveMeeting.users2x, msg.body.userId, "none")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ class UsersApp(
|
||||
with GetUsersMeetingReqMsgHdlr
|
||||
with RegisterUserReqMsgHdlr
|
||||
with ChangeUserRoleCmdMsgHdlr
|
||||
with SetUserSpeechLocaleMsgHdlr
|
||||
with SyncGetUsersMeetingRespMsgHdlr
|
||||
with LogoutAndEndMeetingCmdMsgHdlr
|
||||
with SetRecordingStatusCmdMsgHdlr
|
||||
@ -159,6 +160,9 @@ class UsersApp(
|
||||
with AssignPresenterReqMsgHdlr
|
||||
with ChangeUserPinStateReqMsgHdlr
|
||||
with ChangeUserMobileFlagReqMsgHdlr
|
||||
with ChangeUserReactionEmojiReqMsgHdlr
|
||||
with ChangeUserRaiseHandReqMsgHdlr
|
||||
with ChangeUserAwayReqMsgHdlr
|
||||
with EjectUserFromMeetingCmdMsgHdlr
|
||||
with EjectUserFromMeetingSysMsgHdlr
|
||||
with MuteUserCmdMsgHdlr {
|
||||
|
@ -48,6 +48,9 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
authed = true,
|
||||
guestStatus = GuestStatus.WAIT,
|
||||
emoji = "none",
|
||||
reactionEmoji = "none",
|
||||
raiseHand = false,
|
||||
away = false,
|
||||
pin = false,
|
||||
mobile = false,
|
||||
presenter = false,
|
||||
|
@ -189,11 +189,43 @@ object Users2x {
|
||||
u <- findWithIntId(users, intId)
|
||||
} yield {
|
||||
val newUser = u.modify(_.emoji).setTo(emoji)
|
||||
|
||||
users.save(newUser)
|
||||
UserDAO.update(newUser)
|
||||
newUser
|
||||
}
|
||||
}
|
||||
def setReactionEmoji(users: Users2x, intId: String, reactionEmoji: String): Option[UserState] = {
|
||||
for {
|
||||
u <- findWithIntId(users, intId)
|
||||
} yield {
|
||||
val newUser = u.modify(_.reactionEmoji).setTo(reactionEmoji)
|
||||
.modify(_.reactionChangedOn).setTo(System.currentTimeMillis())
|
||||
|
||||
users.save(newUser)
|
||||
newUser
|
||||
}
|
||||
}
|
||||
|
||||
def setUserRaiseHand(users: Users2x, intId: String, raiseHand: Boolean): Option[UserState] = {
|
||||
for {
|
||||
u <- findWithIntId(users, intId)
|
||||
} yield {
|
||||
val newUserState = u.modify(_.away).setTo(raiseHand)
|
||||
users.save(newUserState)
|
||||
newUserState
|
||||
}
|
||||
}
|
||||
|
||||
def setUserAway(users: Users2x, intId: String, away: Boolean): Option[UserState] = {
|
||||
for {
|
||||
u <- findWithIntId(users, intId)
|
||||
} yield {
|
||||
val newUserState = u.modify(_.away).setTo(away)
|
||||
users.save(newUserState)
|
||||
newUserState
|
||||
}
|
||||
}
|
||||
|
||||
def setUserLocked(users: Users2x, intId: String, locked: Boolean): Option[UserState] = {
|
||||
for {
|
||||
@ -216,6 +248,16 @@ object Users2x {
|
||||
}
|
||||
}
|
||||
|
||||
def setUserSpeechLocale(users: Users2x, intId: String, locale: String): Option[UserState] = {
|
||||
for {
|
||||
u <- findWithIntId(users, intId)
|
||||
} yield {
|
||||
val newUser = u.modify(_.speechLocale).setTo(locale)
|
||||
users.save(newUser)
|
||||
newUser
|
||||
}
|
||||
}
|
||||
|
||||
def hasPresenter(users: Users2x): Boolean = {
|
||||
findPresenter(users) match {
|
||||
case Some(p) => true
|
||||
@ -377,6 +419,10 @@ case class UserState(
|
||||
authed: Boolean,
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
reactionEmoji: String,
|
||||
reactionChangedOn: Long = 0,
|
||||
raiseHand: Boolean,
|
||||
away: Boolean,
|
||||
locked: Boolean,
|
||||
presenter: Boolean,
|
||||
avatar: String,
|
||||
@ -386,7 +432,8 @@ case class UserState(
|
||||
lastInactivityInspect: Long = 0,
|
||||
clientType: String,
|
||||
pickExempted: Boolean,
|
||||
userLeftFlag: UserLeftFlag
|
||||
userLeftFlag: UserLeftFlag,
|
||||
speechLocale: String = ""
|
||||
)
|
||||
|
||||
case class UserIdAndName(id: String, name: String)
|
||||
|
@ -111,6 +111,8 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode)
|
||||
case ChangeUserMobileFlagReqMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode)
|
||||
case SetUserSpeechLocaleReqMsg.NAME =>
|
||||
routeGenericMsg[SetUserSpeechLocaleReqMsg](envelope, jsonNode)
|
||||
case SelectRandomViewerReqMsg.NAME =>
|
||||
routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode)
|
||||
|
||||
@ -250,8 +252,14 @@ class ReceivedJsonMsgHandlerActor(
|
||||
|
||||
case UserLeaveReqMsg.NAME =>
|
||||
routeGenericMsg[UserLeaveReqMsg](envelope, jsonNode)
|
||||
case ChangeUserRaiseHandReqMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserRaiseHandReqMsg](envelope, jsonNode)
|
||||
case ChangeUserAwayReqMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserAwayReqMsg](envelope, jsonNode)
|
||||
case ChangeUserEmojiCmdMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserEmojiCmdMsg](envelope, jsonNode)
|
||||
case ChangeUserReactionEmojiReqMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserReactionEmojiReqMsg](envelope, jsonNode)
|
||||
case UserReactionTimeExpiredCmdMsg.NAME =>
|
||||
routeGenericMsg[UserReactionTimeExpiredCmdMsg](envelope, jsonNode)
|
||||
case ClearAllUsersEmojiCmdMsg.NAME =>
|
||||
|
@ -63,6 +63,9 @@ trait HandlerHelpers extends SystemConfiguration {
|
||||
authed = regUser.authed,
|
||||
guestStatus = regUser.guestStatus,
|
||||
emoji = "none",
|
||||
reactionEmoji = "none",
|
||||
raiseHand = false,
|
||||
away = false,
|
||||
pin = false,
|
||||
mobile = false,
|
||||
presenter = false,
|
||||
|
@ -390,11 +390,15 @@ class MeetingActor(
|
||||
updateUserLastActivity(m.body.setBy)
|
||||
case m: GetRecordingStatusReqMsg => usersApp.handleGetRecordingStatusReqMsg(m)
|
||||
case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m)
|
||||
case m: ChangeUserReactionEmojiReqMsg => usersApp.handleChangeUserReactionEmojiReqMsg(m)
|
||||
case m: ChangeUserRaiseHandReqMsg => usersApp.handleChangeUserRaiseHandReqMsg(m)
|
||||
case m: ChangeUserAwayReqMsg => usersApp.handleChangeUserAwayReqMsg(m)
|
||||
case m: UserReactionTimeExpiredCmdMsg => handleUserReactionTimeExpiredCmdMsg(m)
|
||||
case m: ClearAllUsersEmojiCmdMsg => handleClearAllUsersEmojiCmdMsg(m)
|
||||
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
|
||||
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m)
|
||||
case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m)
|
||||
case m: SetUserSpeechLocaleReqMsg => usersApp.handleSetUserSpeechLocaleReqMsg(m)
|
||||
|
||||
// Client requested to eject user
|
||||
case m: EjectUserFromMeetingCmdMsg =>
|
||||
|
@ -600,4 +600,32 @@ object MsgBuilder {
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildUserEmojiChangedEvtMsg(meetingId: String, userId: String, emoji: String) = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(UserEmojiChangedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserEmojiChangedEvtMsg.NAME, meetingId, userId)
|
||||
val body = UserEmojiChangedEvtMsgBody(userId, emoji)
|
||||
val event = UserEmojiChangedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildUserAwayChangedEvtMsg(meetingId: String, userId: String, away: Boolean) = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(UserAwayChangedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserAwayChangedEvtMsg.NAME, meetingId, userId)
|
||||
val body = UserAwayChangedEvtMsgBody(userId, away)
|
||||
val event = UserAwayChangedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildUserRaiseHandChangedEvtMsg(meetingId: String, userId: String, raiseHand: Boolean) = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(UserRaiseHandChangedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRaiseHandChangedEvtMsg.NAME, meetingId, userId)
|
||||
val body = UserRaiseHandChangedEvtMsgBody(userId, raiseHand)
|
||||
val event = UserRaiseHandChangedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ object UserJoinedMeetingEvtMsgBuilder {
|
||||
role = userState.role, guest = userState.guest, authed = userState.authed,
|
||||
guestStatus = userState.guestStatus,
|
||||
emoji = userState.emoji,
|
||||
reactionEmoji = userState.reactionEmoji,
|
||||
raiseHand = userState.raiseHand,
|
||||
away = userState.away,
|
||||
pin = userState.pin,
|
||||
presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, color = userState.color,
|
||||
clientType = userState.clientType)
|
||||
|
@ -68,7 +68,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,
|
||||
mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
|
||||
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown",
|
||||
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false,
|
||||
avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown",
|
||||
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ case class User(
|
||||
answers: Map[String,Vector[String]] = Map(),
|
||||
talk: Talk = Talk(),
|
||||
emojis: Vector[Emoji] = Vector(),
|
||||
reactions: Vector[Emoji] = Vector(),
|
||||
webcams: Vector[Webcam] = Vector(),
|
||||
totalOfMessages: Long = 0,
|
||||
)
|
||||
@ -140,6 +141,9 @@ class LearningDashboardActor(
|
||||
case m: UserLeaveReqMsg => handleUserLeaveReqMsg(m)
|
||||
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
|
||||
case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m)
|
||||
case m: UserAwayChangedEvtMsg => handleUserAwayChangedEvtMsg(m)
|
||||
case m: UserRaiseHandChangedEvtMsg => handleUserRaiseHandChangedEvtMsg(m)
|
||||
case m: UserReactionEmojiChangedEvtMsg => handleUserReactionEmojiChangedEvtMsg(m)
|
||||
case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m)
|
||||
case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
|
||||
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
|
||||
@ -350,7 +354,7 @@ class LearningDashboardActor(
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
if (msg.body.emoji != "none") {
|
||||
if (msg.body.emoji != "none" && msg.body.emoji != "raiseHand" && msg.body.emoji != "away") {
|
||||
val updatedUser = user.copy(emojis = user.emojis :+ Emoji(msg.body.emoji))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
|
||||
@ -359,6 +363,48 @@ class LearningDashboardActor(
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserRaiseHandChangedEvtMsg(msg: UserRaiseHandChangedEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
if (msg.body.raiseHand) {
|
||||
val updatedUser = user.copy(emojis = user.emojis :+ Emoji("raiseHand"))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserAwayChangedEvtMsg(msg: UserAwayChangedEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
if (msg.body.away) {
|
||||
val updatedUser = user.copy(emojis = user.emojis :+ Emoji("away"))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserReactionEmojiChangedEvtMsg(msg: UserReactionEmojiChangedEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
if (msg.body.reactionEmoji != "none") {
|
||||
val updatedUser = user.copy(reactions = user.reactions :+ Emoji(msg.body.reactionEmoji))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
|
@ -44,8 +44,9 @@ 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, color = "#ff6242",
|
||||
clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))
|
||||
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false,
|
||||
locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242",
|
||||
clientType = "unknown", pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
|
||||
Users2x.add(liveMeeting.users2x, u)
|
||||
u
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ val compileSettings = Seq(
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:11",
|
||||
"-release:17",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
@ -77,4 +77,4 @@ daemonGroup in Linux := group
|
||||
|
||||
javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-fsesl-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
|
||||
|
||||
debianPackageDependencies in Debian ++= Seq("java11-runtime-headless", "bash", "bbb-freeswitch-core")
|
||||
debianPackageDependencies in Debian ++= Seq("java17-runtime-headless", "bash", "bbb-freeswitch-core")
|
||||
|
@ -12,7 +12,7 @@ val compileSettings = Seq(
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:11",
|
||||
"-release:17",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
|
@ -9,10 +9,11 @@ case class UpdateTranscriptPubMsgBody(
|
||||
end: Int,
|
||||
text: String,
|
||||
transcript: String,
|
||||
locale: String
|
||||
locale: String,
|
||||
result: Boolean,
|
||||
)
|
||||
|
||||
// Out messages
|
||||
object TranscriptUpdatedEvtMsg { val NAME = "TranscriptUpdatedEvtMsg" }
|
||||
case class TranscriptUpdatedEvtMsg(header: BbbClientMsgHeader, body: TranscriptUpdatedEvtMsgBody) extends BbbCoreMsg
|
||||
case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String)
|
||||
case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String, result: Boolean)
|
||||
|
@ -90,20 +90,23 @@ case class UserJoinedMeetingEvtMsg(
|
||||
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,
|
||||
color: String,
|
||||
clientType: String
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
role: String,
|
||||
guest: Boolean,
|
||||
authed: Boolean,
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
reactionEmoji: String,
|
||||
raiseHand: Boolean,
|
||||
away: Boolean,
|
||||
pin: Boolean,
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
color: String,
|
||||
clientType: String
|
||||
)
|
||||
|
||||
/**
|
||||
@ -206,6 +209,48 @@ 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 changing RaiseHand.
|
||||
*/
|
||||
object ChangeUserRaiseHandReqMsg { val NAME = "ChangeUserRaiseHandReqMsg" }
|
||||
case class ChangeUserRaiseHandReqMsg(header: BbbClientMsgHeader, body: ChangeUserRaiseHandReqMsgBody) extends StandardMsg
|
||||
case class ChangeUserRaiseHandReqMsgBody(userId: String, raiseHand: Boolean)
|
||||
|
||||
/**
|
||||
* Sent to all clients about a user changing RaiseHand.
|
||||
*/
|
||||
object UserRaiseHandChangedEvtMsg { val NAME = "UserRaiseHandChangedEvtMsg" }
|
||||
case class UserRaiseHandChangedEvtMsg(header: BbbClientMsgHeader, body: UserRaiseHandChangedEvtMsgBody) extends BbbCoreMsg
|
||||
case class UserRaiseHandChangedEvtMsgBody(userId: String, raiseHand: Boolean)
|
||||
|
||||
/**
|
||||
* Sent from client about a user changing Away.
|
||||
*/
|
||||
object ChangeUserAwayReqMsg { val NAME = "ChangeUserAwayReqMsg" }
|
||||
case class ChangeUserAwayReqMsg(header: BbbClientMsgHeader, body: ChangeUserAwayReqMsgBody) extends StandardMsg
|
||||
case class ChangeUserAwayReqMsgBody(userId: String, away: Boolean)
|
||||
|
||||
/**
|
||||
* Sent to all clients about a user changing Away.
|
||||
*/
|
||||
object UserAwayChangedEvtMsg { val NAME = "UserAwayChangedEvtMsg" }
|
||||
case class UserAwayChangedEvtMsg(header: BbbClientMsgHeader, body: UserAwayChangedEvtMsgBody) extends BbbCoreMsg
|
||||
case class UserAwayChangedEvtMsgBody(userId: String, away: Boolean)
|
||||
|
||||
/**
|
||||
* Sent from client about a user changing ReactionEmoji.
|
||||
*/
|
||||
object ChangeUserReactionEmojiReqMsg { val NAME = "ChangeUserReactionEmojiReqMsg" }
|
||||
case class ChangeUserReactionEmojiReqMsg(header: BbbClientMsgHeader, body: ChangeUserReactionEmojiReqMsgBody) extends StandardMsg
|
||||
case class ChangeUserReactionEmojiReqMsgBody(userId: String, reactionEmoji: String)
|
||||
|
||||
/**
|
||||
* Sent to all clients about a user changing ReactionEmoji.
|
||||
*/
|
||||
object UserReactionEmojiChangedEvtMsg { val NAME = "UserReactionEmojiChangedEvtMsg" }
|
||||
case class UserReactionEmojiChangedEvtMsg(header: BbbClientMsgHeader, body: UserReactionEmojiChangedEvtMsgBody) extends BbbCoreMsg
|
||||
case class UserReactionEmojiChangedEvtMsgBody(userId: String, reactionEmoji: String)
|
||||
|
||||
/**
|
||||
* Sent from meteor about a user reaction's expiration.
|
||||
*/
|
||||
@ -465,3 +510,11 @@ case class SelectRandomViewerReqMsgBody(requestedBy: String)
|
||||
object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" }
|
||||
case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg
|
||||
case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: String)
|
||||
|
||||
object SetUserSpeechLocaleReqMsg { val NAME = "SetUserSpeechLocaleReqMsg" }
|
||||
case class SetUserSpeechLocaleReqMsg(header: BbbClientMsgHeader, body: SetUserSpeechLocaleReqMsgBody) extends StandardMsg
|
||||
case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String)
|
||||
|
||||
object UserSpeechLocaleChangedEvtMsg { val NAME = "UserSpeechLocaleChangedEvtMsg" }
|
||||
case class UserSpeechLocaleChangedEvtMsg(header: BbbClientMsgHeader, body: UserSpeechLocaleChangedEvtMsgBody) extends BbbCoreMsg
|
||||
case class UserSpeechLocaleChangedEvtMsgBody(locale: String, provider: String)
|
||||
|
@ -11,7 +11,7 @@ val compileSettings = Seq(
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:11",
|
||||
"-release:17",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
@ -110,5 +110,7 @@ libraryDependencies ++= Seq(
|
||||
"org.hibernate" % "hibernate-core" % "5.6.1.Final",
|
||||
"org.flywaydb" % "flyway-core" % "7.8.2",
|
||||
"com.zaxxer" % "HikariCP" % "4.0.3",
|
||||
"commons-validator" % "commons-validator" % "1.7"
|
||||
"commons-validator" % "commons-validator" % "1.7",
|
||||
"org.apache.tika" % "tika-core" % "2.8.0",
|
||||
"org.apache.tika" % "tika-parsers-standard-package" % "2.8.0"
|
||||
)
|
||||
|
@ -23,6 +23,7 @@ object Dependencies {
|
||||
// Office and document conversion
|
||||
val apachePoi = "5.1.0"
|
||||
val nuProcess = "2.0.6"
|
||||
val tika = "2.8.0"
|
||||
|
||||
// Server
|
||||
val servlet = "4.0.1"
|
||||
@ -56,6 +57,7 @@ object Dependencies {
|
||||
|
||||
val poiXml = "org.apache.poi" % "poi-ooxml" % Versions.apachePoi
|
||||
val nuProcess = "com.zaxxer" % "nuprocess" % Versions.nuProcess
|
||||
val tika = "org.apache.tika" % "tika-core" % Versions.tika
|
||||
|
||||
val servletApi = "javax.servlet" % "javax.servlet-api" % Versions.servlet
|
||||
|
||||
@ -93,6 +95,7 @@ object Dependencies {
|
||||
Compile.apacheHttpAsync,
|
||||
Compile.poiXml,
|
||||
Compile.nuProcess,
|
||||
Compile.tika,
|
||||
Compile.servletApi,
|
||||
Compile.apacheLang,
|
||||
Compile.apacheIo,
|
||||
|
@ -48,7 +48,7 @@ public class LearningDashboardService {
|
||||
File jsonFile = this.getJsonDataFile(meetingId,learningDashboardAccessToken);
|
||||
|
||||
FileOutputStream fileOutput = new FileOutputStream(jsonFile);
|
||||
fileOutput.write(activityJson.getBytes());
|
||||
fileOutput.write(activityJson.getBytes("UTF-8"));
|
||||
|
||||
fileOutput.close();
|
||||
|
||||
|
@ -11,6 +11,8 @@ public class MimeTypeUtils {
|
||||
private static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||
private static final String PPT = "application/vnd.ms-powerpoint";
|
||||
private static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
||||
private static final String TIKA_MSOFFICE = "application/x-tika-msoffice";
|
||||
private static final String TIKA_MSOFFICE_X = "application/x-tika-ooxml";
|
||||
private static final String ODT = "application/vnd.oasis.opendocument.text";
|
||||
private static final String RTF = "application/rtf";
|
||||
private static final String TXT = "text/plain";
|
||||
@ -21,46 +23,45 @@ public class MimeTypeUtils {
|
||||
private static final String PNG = "image/png";
|
||||
private static final String SVG = "image/svg+xml";
|
||||
|
||||
private static final HashMap<String,String> EXTENSIONS_MIME = new HashMap<String,String>(16) {
|
||||
private static final HashMap<String, List<String>> EXTENSIONS_MIME = new HashMap<String, List<String>>(16) {
|
||||
{
|
||||
// Add all the supported files
|
||||
put(FileTypeConstants.XLS, XLS);
|
||||
put(FileTypeConstants.XLSX, XLSX);
|
||||
put(FileTypeConstants.DOC, DOC);
|
||||
put(FileTypeConstants.DOCX, DOCX);
|
||||
put(FileTypeConstants.PPT, PPT);
|
||||
put(FileTypeConstants.PPTX, PPTX);
|
||||
put(FileTypeConstants.ODT, ODT);
|
||||
put(FileTypeConstants.RTF, RTF);
|
||||
put(FileTypeConstants.TXT, TXT);
|
||||
put(FileTypeConstants.ODS, ODS);
|
||||
put(FileTypeConstants.ODP, ODP);
|
||||
put(FileTypeConstants.PDF, PDF);
|
||||
put(FileTypeConstants.JPG, JPEG);
|
||||
put(FileTypeConstants.JPEG, JPEG);
|
||||
put(FileTypeConstants.PNG, PNG);
|
||||
put(FileTypeConstants.SVG, SVG);
|
||||
put(FileTypeConstants.DOC, Arrays.asList(DOC, DOCX, TIKA_MSOFFICE, TIKA_MSOFFICE_X));
|
||||
put(FileTypeConstants.XLS, Arrays.asList(XLS, XLSX, TIKA_MSOFFICE, TIKA_MSOFFICE_X));
|
||||
put(FileTypeConstants.PPT, Arrays.asList(PPT, PPTX, TIKA_MSOFFICE, TIKA_MSOFFICE_X));
|
||||
put(FileTypeConstants.DOCX, Arrays.asList(DOC, DOCX, TIKA_MSOFFICE, TIKA_MSOFFICE_X));
|
||||
put(FileTypeConstants.PPTX, Arrays.asList(PPT, PPTX, TIKA_MSOFFICE, TIKA_MSOFFICE_X));
|
||||
put(FileTypeConstants.XLSX, Arrays.asList(XLS, XLSX, TIKA_MSOFFICE, TIKA_MSOFFICE_X));
|
||||
put(FileTypeConstants.ODT, Arrays.asList(ODT));
|
||||
put(FileTypeConstants.RTF, Arrays.asList(RTF));
|
||||
put(FileTypeConstants.TXT, Arrays.asList(TXT));
|
||||
put(FileTypeConstants.ODS, Arrays.asList(ODS));
|
||||
put(FileTypeConstants.ODP, Arrays.asList(ODP));
|
||||
put(FileTypeConstants.PDF, Arrays.asList(PDF));
|
||||
put(FileTypeConstants.JPG, Arrays.asList(JPEG));
|
||||
put(FileTypeConstants.JPEG, Arrays.asList(JPEG));
|
||||
put(FileTypeConstants.PNG, Arrays.asList(PNG));
|
||||
put(FileTypeConstants.SVG, Arrays.asList(SVG));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public Boolean extensionMatchMimeType(String mimeType, String finalExtension) {
|
||||
if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase()) &&
|
||||
EXTENSIONS_MIME.get(finalExtension.toLowerCase()).equalsIgnoreCase(mimeType)) {
|
||||
return true;
|
||||
} else if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase() + 'x') &&
|
||||
EXTENSIONS_MIME.get(finalExtension.toLowerCase() + 'x').equalsIgnoreCase(mimeType)) {
|
||||
//Exception for MS Office files named with old extension but using internally the new mime type
|
||||
//e.g. a file named with extension `ppt` but has the content of a `pptx`
|
||||
return true;
|
||||
}
|
||||
finalExtension = finalExtension.toLowerCase();
|
||||
|
||||
if (EXTENSIONS_MIME.containsKey(finalExtension)) {
|
||||
for (String validMimeType : EXTENSIONS_MIME.get(finalExtension)) {
|
||||
if (validMimeType.equalsIgnoreCase(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<String> getValidMimeTypes() {
|
||||
List<String> validMimeTypes = Arrays.asList(XLS, XLSX,
|
||||
DOC, DOCX, PPT, PPTX, ODT, RTF, TXT, ODS, ODP,
|
||||
PDF, JPEG, PNG, SVG
|
||||
PDF, JPEG, PNG, SVG, TIKA_MSOFFICE, TIKA_MSOFFICE_X
|
||||
);
|
||||
return validMimeTypes;
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.bigbluebutton.presentation.FileTypeConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import org.apache.tika.Tika;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
@ -71,7 +71,7 @@ public final class SupportedFileTypes {
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns if the office file is supported.
|
||||
* Returns if the Office file is supported.
|
||||
*/
|
||||
public static boolean isOfficeFile(String fileExtension) {
|
||||
return OFFICE_FILE_LIST.contains(fileExtension.toLowerCase());
|
||||
@ -82,62 +82,27 @@ public final class SupportedFileTypes {
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns if the iamge file is supported
|
||||
* Returns if the image file is supported
|
||||
*/
|
||||
public static boolean isImageFile(String fileExtension) {
|
||||
return IMAGE_FILE_LIST.contains(fileExtension.toLowerCase());
|
||||
}
|
||||
|
||||
/*
|
||||
* It was tested native java methods to detect mimetypes, such as:
|
||||
* - URLConnection.guessContentTypeFromStream(InputStream is);
|
||||
* - Files.probeContentType(Path path);
|
||||
* - FileNameMap fileNameMap.getContentTypeFor(String file.getName());
|
||||
* - MimetypesFileTypeMap fileTypeMap.getContentType(File file);
|
||||
* But none of them was as successful as the linux based command
|
||||
*/
|
||||
public static String detectMimeType(File pres) {
|
||||
try {
|
||||
if (pres == null) throw new NullPointerException("Presentation is null");
|
||||
if (!pres.isFile()) throw new RuntimeException("Presentation is not a file");
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
processBuilder.command("bash", "-c", "file -b --mime-type \"" + pres.getAbsolutePath() + "\"");
|
||||
Process process = processBuilder.start();
|
||||
StringBuilder output = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line + "\n");
|
||||
}
|
||||
int exitVal = process.waitFor();
|
||||
if (exitVal == 0) {
|
||||
return output.toString().trim();
|
||||
} else {
|
||||
String executedCommand = processBuilder.command().toArray(new String[0])[2];
|
||||
|
||||
//Read error stream
|
||||
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
StringBuilder errorString = new StringBuilder();
|
||||
while (stdError.ready()) {
|
||||
errorString.append(stdError.readLine());
|
||||
if (stdError.ready()) {
|
||||
errorString.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
log.error("Error while executing command '{}': {}", executedCommand, errorString);
|
||||
|
||||
if (exitVal == 127) {
|
||||
// 127 - command not found
|
||||
// use Java method to detect in this case (based on file name)
|
||||
return URLConnection.getFileNameMap().getContentTypeFor(pres.getAbsolutePath());
|
||||
} else {
|
||||
throw new RuntimeException(errorString.toString());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error while executing detectMimeType: {}", e.getMessage());
|
||||
if (pres == null) {
|
||||
log.error("Presentation is null");
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!pres.isFile()) {
|
||||
log.error("Presentation is not a file");
|
||||
return "";
|
||||
}
|
||||
|
||||
try (InputStream presStream = new FileInputStream(pres)) {
|
||||
return (new Tika()).detect(presStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Error while executing detectMimeType: {}", e);
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -146,7 +111,7 @@ public final class SupportedFileTypes {
|
||||
public static Boolean isPresentationMimeTypeValid(File pres, String fileExtension) {
|
||||
String mimeType = detectMimeType(pres);
|
||||
|
||||
if (mimeType == null || mimeType.equals("")) {
|
||||
if (mimeType.equals("")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ public class SlidesGenerationProgressNotifier {
|
||||
pres.getTemporaryPresentationId(),
|
||||
pres.getName(),
|
||||
pres.getAuthzToken(),
|
||||
"IVALID_MIME_TYPE",
|
||||
"INVALID_MIME_TYPE",
|
||||
fileMime,
|
||||
fileExtension
|
||||
);
|
||||
|
@ -88,6 +88,8 @@ class ReceivedJsonMsgHdlrActor(val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEvent
|
||||
route[UserRoleChangedEvtMsg](envelope, jsonNode)
|
||||
case UserLockedInMeetingEvtMsg.NAME =>
|
||||
route[UserLockedInMeetingEvtMsg](envelope, jsonNode)
|
||||
case UserSpeechLocaleChangedEvtMsg.NAME =>
|
||||
route[UserSpeechLocaleChangedEvtMsg](envelope, jsonNode)
|
||||
case CreateBreakoutRoomSysCmdMsg.NAME =>
|
||||
route[CreateBreakoutRoomSysCmdMsg](envelope, jsonNode)
|
||||
case PresentationUploadTokenSysPubMsg.NAME =>
|
||||
|
4
bbb-export-annotations/package-lock.json
generated
4
bbb-export-annotations/package-lock.json
generated
@ -22,8 +22,8 @@
|
||||
"eslint-config-google": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.16.0",
|
||||
"npm": "^8.5.0"
|
||||
"node": ">=16",
|
||||
"npm": ">=8.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
|
@ -13,7 +13,7 @@ val compileSettings = Seq(
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:11",
|
||||
"-release:17",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
|
@ -37,7 +37,7 @@ systemctl restart nginx
|
||||
#wget https://graphql-engine-cdn.hasura.io/server/latest/linux-amd64 -O /usr/local/bin/hasura-graphql-engine
|
||||
#chmod +x /usr/local/bin/hasura-graphql-engine
|
||||
|
||||
git clone --branch v2.27.0 https://github.com/iMDT/hasura-graphql-engine.git
|
||||
git clone --branch v2.28.0 https://github.com/iMDT/hasura-graphql-engine.git
|
||||
cat hasura-graphql-engine/hasura-graphql.part-a* > hasura-graphql
|
||||
rm -rf hasura-graphql-engine/
|
||||
chmod +x hasura-graphql
|
||||
|
1
bbb-transcription-controller.placeholder.sh
Executable file
1
bbb-transcription-controller.placeholder.sh
Executable file
@ -0,0 +1 @@
|
||||
git clone --branch v0.1.0 --depth 1 https://github.com/bigbluebutton/bbb-transcription-controller bbb-transcription-controller
|
@ -14,6 +14,7 @@
|
||||
<load module="mod_commands"/>
|
||||
<load module="mod_conference"/>
|
||||
<load module="mod_dptools"/>
|
||||
<load module="mod_audio_fork"/>
|
||||
|
||||
<!-- Dialplan Interfaces -->
|
||||
<load module="mod_dialplan_xml"/>
|
||||
|
@ -476,11 +476,15 @@ display_bigbluebutton_status () {
|
||||
units="$units bbb-rap-starter"
|
||||
fi
|
||||
|
||||
if [ -f /usr/lib/systemd/system/bbb-transcription-controller.service ]; then
|
||||
units="$units bbb-transcription-controller"
|
||||
fi
|
||||
|
||||
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
|
||||
TOMCAT_SERVICE=$TOMCAT_USER
|
||||
fi
|
||||
|
||||
line='——————————————————————►'
|
||||
line='—————————————————————————————►'
|
||||
for unit in $units; do
|
||||
status=$(systemctl is-active "$unit")
|
||||
if [ "$status" = "active" ]; then
|
||||
@ -1706,7 +1710,9 @@ if [ -n "$HOST" ]; then
|
||||
|
||||
sudo yq e -i ".freeswitch.esl_password = \"$ESL_PASSWORD\"" /etc/bigbluebutton/bbb-webrtc-sfu/production.yml
|
||||
sudo xmlstarlet edit --inplace --update 'configuration/settings//param[@name="password"]/@value' --value $ESL_PASSWORD /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml
|
||||
|
||||
if [ -f /usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml ]; then
|
||||
sudo yq w -i /usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml freeswitch.esl_password "$ESL_PASSWORD"
|
||||
fi
|
||||
|
||||
echo "Restarting BigBlueButton $BIGBLUEBUTTON_RELEASE ..."
|
||||
stop_bigbluebutton
|
||||
|
@ -3,7 +3,7 @@ import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function updateTranscript(transcriptId, start, end, text, transcript, locale) {
|
||||
export default function updateTranscript(transcriptId, start, end, text, transcript, locale, isFinal) {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
@ -19,6 +19,7 @@ export default function updateTranscript(transcriptId, start, end, text, transcr
|
||||
check(text, String);
|
||||
check(transcript, String);
|
||||
check(locale, String);
|
||||
check(isFinal, Boolean);
|
||||
|
||||
// Ignore irrelevant updates
|
||||
if (start !== -1 && end !== -1) {
|
||||
@ -29,6 +30,7 @@ export default function updateTranscript(transcriptId, start, end, text, transcr
|
||||
text,
|
||||
transcript,
|
||||
locale,
|
||||
result: isFinal,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
filterValidIceCandidates,
|
||||
analyzeSdp,
|
||||
logSelectedCandidate,
|
||||
forceDisableStereo,
|
||||
} from '/imports/utils/sdpUtils';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import VoiceCallStates from '/imports/api/voice-call-states';
|
||||
@ -25,6 +26,7 @@ import {
|
||||
filterSupportedConstraints,
|
||||
doGUM,
|
||||
} from '/imports/api/audio/client/bridge/service';
|
||||
import SpeechService from '/imports/ui/components/audio/captions/speech/service';
|
||||
|
||||
const MEDIA = Meteor.settings.public.media;
|
||||
const MEDIA_TAG = MEDIA.mediaTag;
|
||||
@ -708,12 +710,25 @@ class SIPSession {
|
||||
const target = SIP.UserAgent.makeURI(`sip:${callExtension}@${hostname}`);
|
||||
|
||||
const matchConstraints = getAudioConstraints({ deviceId: this.inputDeviceId });
|
||||
const sessionDescriptionHandlerModifiers = [];
|
||||
const iceModifiers = [
|
||||
filterValidIceCandidates.bind(this, this.validIceCandidates),
|
||||
];
|
||||
|
||||
if (!SIPJS_ALLOW_MDNS) iceModifiers.push(stripMDnsCandidates);
|
||||
|
||||
// The current Vosk provider does not support stereo when transcribing
|
||||
// microphone streams, so we need to make sure it is forcefully disabled
|
||||
// via SDP munging. Having it disabled on server side FS _does not suffice_
|
||||
// because the stereo parameter is client-mandated (ie replicated in the
|
||||
// answer)
|
||||
if (SpeechService.stereoUnsupported()) {
|
||||
logger.debug({
|
||||
logCode: 'sipjs_transcription_disable_stereo',
|
||||
}, 'Transcription provider does not support stereo, forcing stereo=0');
|
||||
sessionDescriptionHandlerModifiers.push(forceDisableStereo);
|
||||
}
|
||||
|
||||
const inviterOptions = {
|
||||
sessionDescriptionHandlerOptions: {
|
||||
constraints: {
|
||||
@ -724,6 +739,7 @@ class SIPSession {
|
||||
},
|
||||
iceGatheringTimeout: ICE_GATHERING_TIMEOUT,
|
||||
},
|
||||
sessionDescriptionHandlerModifiers,
|
||||
sessionDescriptionHandlerModifiersPostICEGathering: iceModifiers,
|
||||
delegate: {
|
||||
onSessionDescriptionHandler:
|
||||
|
@ -16,7 +16,7 @@ import { initCaptions } from '/imports/api/captions/server/helpers';
|
||||
import { addAnnotationsStreamer } from '/imports/api/annotations/server/streamer';
|
||||
import { addCursorStreamer } from '/imports/api/cursor/server/streamer';
|
||||
import { addExternalVideoStreamer } from '/imports/api/external-videos/server/streamer';
|
||||
import { addUserReactionsObserver } from '/imports/api/user-reaction/server/helpers';
|
||||
import addUserReactionsObserver from '/imports/api/user-reaction/server/helpers';
|
||||
import { LAYOUT_TYPE } from '/imports/ui/components/layout/enums';
|
||||
|
||||
const addExternalVideo = async (meetingId) => {
|
||||
|
@ -12,8 +12,8 @@ const PAGE_COUNT_EXCEEDED_KEY = 'PAGE_COUNT_EXCEEDED';
|
||||
const PDF_HAS_BIG_PAGE_KEY = 'PDF_HAS_BIG_PAGE';
|
||||
const GENERATED_SLIDE_KEY = 'GENERATED_SLIDE';
|
||||
const FILE_TOO_LARGE_KEY = 'FILE_TOO_LARGE';
|
||||
const CONVERSION_TIMEOUT_KEY = "CONVERSION_TIMEOUT";
|
||||
const IVALID_MIME_TYPE_KEY = "IVALID_MIME_TYPE";
|
||||
const CONVERSION_TIMEOUT_KEY = 'CONVERSION_TIMEOUT';
|
||||
const INVALID_MIME_TYPE_KEY = 'INVALID_MIME_TYPE';
|
||||
const NO_CONTENT = '204';
|
||||
// const GENERATING_THUMBNAIL_KEY = 'GENERATING_THUMBNAIL';
|
||||
// const GENERATED_THUMBNAIL_KEY = 'GENERATED_THUMBNAIL';
|
||||
@ -52,7 +52,7 @@ export default async function handlePresentationConversionUpdate({ body }, meeti
|
||||
statusModifier['conversion.maxFileSize'] = body.maxFileSize;
|
||||
case UNSUPPORTED_DOCUMENT_KEY:
|
||||
case OFFICE_DOC_CONVERSION_FAILED_KEY:
|
||||
case IVALID_MIME_TYPE_KEY:
|
||||
case INVALID_MIME_TYPE_KEY:
|
||||
statusModifier['conversion.error'] = true;
|
||||
statusModifier['conversion.fileMime'] = body.fileMime;
|
||||
statusModifier['conversion.fileExtension'] = body.fileExtension;
|
||||
|
@ -1,6 +1,4 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleSetUserReaction from './handlers/setUserReaction';
|
||||
import handleClearUsersEmoji from './handlers/clearUsersEmoji';
|
||||
|
||||
RedisPubSub.on('UserEmojiChangedEvtMsg', handleSetUserReaction);
|
||||
RedisPubSub.on('ClearedAllUsersEmojiEvtMsg', handleClearUsersEmoji);
|
||||
RedisPubSub.on('UserReactionEmojiChangedEvtMsg', handleSetUserReaction);
|
||||
|
@ -1,15 +1,7 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import UsersReaction from '/imports/api/users';
|
||||
import addUserReaction from '../modifiers/addUserReaction';
|
||||
import addUserEmoji from '../modifiers/addUserEmoji';
|
||||
|
||||
export default function handleSetUserReaction({ body }, meetingId) {
|
||||
const { userId, emoji } = body;
|
||||
const { userId, reactionEmoji } = body;
|
||||
|
||||
if (emoji == 'none' || emoji == 'raiseHand' || emoji == 'away' || emoji == 'notAway') {
|
||||
addUserEmoji(meetingId, userId, emoji);
|
||||
} else {
|
||||
addUserReaction(meetingId, userId, emoji);
|
||||
}
|
||||
addUserReaction(meetingId, userId, reactionEmoji);
|
||||
}
|
||||
|
@ -3,19 +3,7 @@ import UserReactions from '/imports/api/user-reaction';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
const expireSeconds = Meteor.settings.public.userReaction.expire;
|
||||
const expireMilliseconds = expireSeconds * 1000
|
||||
|
||||
const addUserReactionsObserver = (meetingId) => {
|
||||
const meetingUserReactions = UserReactions.find({ meetingId });
|
||||
return meetingUserReactions.observe({
|
||||
removed(document) {
|
||||
const isExpirationTriggeredRemoval = (Date.now() - Date.parse(document.creationDate)) >= expireMilliseconds
|
||||
if (isExpirationTriggeredRemoval) {
|
||||
notifyExpiredReaction(meetingId, document.userId);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const expireMilliseconds = expireSeconds * 1000;
|
||||
|
||||
const notifyExpiredReaction = (meetingId, userId) => {
|
||||
try {
|
||||
@ -40,8 +28,18 @@ const notifyExpiredReaction = (meetingId, userId) => {
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method resetUserReaction ${err.stack}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
addUserReactionsObserver,
|
||||
};
|
||||
const addUserReactionsObserver = (meetingId) => {
|
||||
const meetingUserReactions = UserReactions.find({ meetingId });
|
||||
return meetingUserReactions.observe({
|
||||
removed(document) {
|
||||
const isExpirationTriggeredRemoval = (Date.now() - Date.parse(document.creationDate)) >= expireMilliseconds;
|
||||
if (isExpirationTriggeredRemoval) {
|
||||
notifyExpiredReaction(meetingId, document.userId);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default addUserReactionsObserver;
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import setUserReaction from './methods/setUserReaction';
|
||||
import clearAllUsersEmoji from './methods/clearAllUsersEmoji';
|
||||
|
||||
Meteor.methods({
|
||||
setUserReaction,
|
||||
clearAllUsersEmoji,
|
||||
});
|
||||
|
@ -4,25 +4,25 @@ import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function setUserReaction(emoji, userId = undefined) {
|
||||
export default function setUserReaction(reactionEmoji, userId = undefined) {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ChangeUserEmojiCmdMsg';
|
||||
const EVENT_NAME = 'ChangeUserReactionEmojiReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(emoji, String);
|
||||
check(reactionEmoji, String);
|
||||
|
||||
const payload = {
|
||||
emoji,
|
||||
userId: userId ? userId : requesterUserId,
|
||||
reactionEmoji,
|
||||
userId: userId || requesterUserId,
|
||||
};
|
||||
|
||||
Logger.verbose('User emoji status updated', {
|
||||
emoji, requesterUserId, meetingId,
|
||||
Logger.verbose('User reactionEmoji status updated', {
|
||||
reactionEmoji, requesterUserId, meetingId,
|
||||
});
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
|
@ -23,6 +23,9 @@ export default async function addUserPersistentData(user) {
|
||||
waitingForAcceptance: Match.Maybe(Boolean),
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
reactionEmoji: String,
|
||||
raiseHand: Boolean,
|
||||
away: Boolean,
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
|
@ -4,17 +4,27 @@ import handleUserJoined from './handlers/userJoined';
|
||||
import handleUserLeftFlagUpdated from './handlers/userLeftFlagUpdated';
|
||||
import handleValidateAuthToken from './handlers/validateAuthToken';
|
||||
import handlePresenterAssigned from './handlers/presenterAssigned';
|
||||
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';
|
||||
import handleChangeMobileFlag from './handlers/changeMobileFlag';
|
||||
import handleChangeRaiseHand from './handlers/changeRaiseHand';
|
||||
import handleAway from './handlers/changeAway';
|
||||
import handleClearUsersEmoji from './handlers/clearUsersEmoji';
|
||||
import handleUserSpeechLocaleChanged from './handlers/userSpeechLocaleChanged';
|
||||
|
||||
RedisPubSub.on('PresenterAssignedEvtMsg', handlePresenterAssigned);
|
||||
RedisPubSub.on('UserJoinedMeetingEvtMsg', handleUserJoined);
|
||||
RedisPubSub.on('UserLeftMeetingEvtMsg', handleRemoveUser);
|
||||
RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken);
|
||||
RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus);
|
||||
RedisPubSub.on('ClearedAllUsersEmojiEvtMsg', handleClearUsersEmoji);
|
||||
RedisPubSub.on('UserRaiseHandChangedEvtMsg', handleChangeRaiseHand);
|
||||
RedisPubSub.on('UserAwayChangedEvtMsg', handleAway);
|
||||
RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole);
|
||||
RedisPubSub.on('UserMobileFlagChangedEvtMsg', handleChangeMobileFlag);
|
||||
RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated);
|
||||
RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged);
|
||||
RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect);
|
||||
RedisPubSub.on('UserSpeechLocaleChangedEvtMsg', handleUserSpeechLocaleChanged);
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { check } from 'meteor/check';
|
||||
import changeAway from '/imports/api/users/server/modifiers/changeAway';
|
||||
|
||||
export default async function handleAway(payload, meetingId) {
|
||||
check(payload.body, Object);
|
||||
check(meetingId, String);
|
||||
|
||||
const { userId: requesterUserId, away } = payload.body;
|
||||
|
||||
await changeAway(meetingId, requesterUserId, away);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { check } from 'meteor/check';
|
||||
import changeRaiseHand from '/imports/api/users/server/modifiers/changeRaiseHand';
|
||||
|
||||
export default async function handleChangeRaiseHand(payload, meetingId) {
|
||||
check(payload.body, Object);
|
||||
check(meetingId, String);
|
||||
|
||||
const { userId: requesterUserId, raiseHand } = payload.body;
|
||||
|
||||
await changeRaiseHand(meetingId, requesterUserId, raiseHand);
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/users';
|
||||
import sendStatusChatMsg from './sendStatusChatMsg';
|
||||
|
||||
export default function handleEmojiStatus(meetingId, userId, emoji) {
|
||||
export default async function handleEmojiStatus({ body }, meetingId) {
|
||||
const { userId, emoji } = body;
|
||||
|
||||
check(userId, String);
|
||||
check(emoji, String);
|
||||
@ -21,11 +21,7 @@ export default function handleEmojiStatus(meetingId, userId, emoji) {
|
||||
};
|
||||
|
||||
try {
|
||||
// must be called before modifying the users collection, because it
|
||||
// needs to be consulted in order to know the previous emoji
|
||||
sendStatusChatMsg(meetingId, userId, emoji);
|
||||
|
||||
const numberAffected = Users.update(selector, modifier);
|
||||
const numberAffected = await Users.updateAsync(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.info(`Assigned user emoji status ${emoji} id=${userId} meeting=${meetingId}`);
|
@ -0,0 +1,32 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default async function handleReactionEmoji({ body }, meetingId) {
|
||||
const { userId, reactionEmoji } = body;
|
||||
aaa
|
||||
check(userId, String);
|
||||
check(reactionEmoji, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
reactionEmojiTime: (new Date()).getTime(),
|
||||
reactionEmoji,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const numberAffected = await Users.updateAsync(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.info(`Assigned user rectionEmoji ${reactionEmoji} id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Assigning user reactionEmoji: ${err}`);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { check } from 'meteor/check';
|
||||
import updateSpeechLocale from '../modifiers/updateSpeechLocale';
|
||||
|
||||
export default function handleUserSpeechLocaleChanged({ body, header }, meetingId) {
|
||||
const { locale } = body;
|
||||
const { userId } = header;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(locale, String);
|
||||
|
||||
return updateSpeechLocale(meetingId, userId, locale);
|
||||
}
|
@ -2,6 +2,9 @@ import { Meteor } from 'meteor/meteor';
|
||||
import validateAuthToken from './methods/validateAuthToken';
|
||||
import setSpeechLocale from './methods/setSpeechLocale';
|
||||
import setMobileUser from './methods/setMobileUser';
|
||||
import setEmojiStatus from './methods/setEmojiStatus';
|
||||
import changeAway from './methods/changeAway';
|
||||
import changeRaiseHand from './methods/changeRaiseHand';
|
||||
import assignPresenter from './methods/assignPresenter';
|
||||
import changeRole from './methods/changeRole';
|
||||
import removeUser from './methods/removeUser';
|
||||
@ -12,10 +15,15 @@ import userLeftMeeting from './methods/userLeftMeeting';
|
||||
import changePin from './methods/changePin';
|
||||
import setRandomUser from './methods/setRandomUser';
|
||||
import setExitReason from './methods/setExitReason';
|
||||
import clearAllUsersEmoji from './methods/clearAllUsersEmoji';
|
||||
|
||||
Meteor.methods({
|
||||
setSpeechLocale,
|
||||
setMobileUser,
|
||||
setEmojiStatus,
|
||||
clearAllUsersEmoji,
|
||||
changeAway,
|
||||
changeRaiseHand,
|
||||
assignPresenter,
|
||||
changeRole,
|
||||
removeUser,
|
||||
|
@ -0,0 +1,31 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
|
||||
export default async function changeAway(away) {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ChangeUserAwayReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(away, Boolean);
|
||||
|
||||
const payload = {
|
||||
userId: requesterUserId,
|
||||
away,
|
||||
};
|
||||
|
||||
Logger.verbose('Updated away status for user', {
|
||||
meetingId, requesterUserId, away,
|
||||
});
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method changeAway ${err.stack}`);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
|
||||
export default async function changeRaiseHand(raiseHand, userId = undefined) {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ChangeUserRaiseHandReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(raiseHand, Boolean);
|
||||
|
||||
const payload = {
|
||||
userId: userId || requesterUserId,
|
||||
raiseHand,
|
||||
};
|
||||
|
||||
Logger.verbose('Updated raiseHand status for user', {
|
||||
meetingId, requesterUserId, raiseHand,
|
||||
});
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method changeRaiseHand ${err.stack}`);
|
||||
}
|
||||
}
|
@ -5,13 +5,12 @@ import Users from '/imports/api/users';
|
||||
import addSystemMsg from '/imports/api/group-chat-msg/server/modifiers/addSystemMsg';
|
||||
|
||||
const ROLE_VIEWER = Meteor.settings.public.user.role_viewer;
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
const CHAT_USER_STATUS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_status_message;
|
||||
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
|
||||
|
||||
export default function sendStatusChatMsg(meetingId, userId, emoji) {
|
||||
export default function sendAwayStatusChatMsg(meetingId, userId, newAwayStatus) {
|
||||
const user = Users.findOne(
|
||||
{ meetingId, userId },
|
||||
{
|
||||
@ -19,7 +18,7 @@ export default function sendStatusChatMsg(meetingId, userId, emoji) {
|
||||
name: 1,
|
||||
role: 1,
|
||||
locked: 1,
|
||||
emoji: 1,
|
||||
away: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
@ -44,9 +43,9 @@ export default function sendStatusChatMsg(meetingId, userId, emoji) {
|
||||
|
||||
// Send message if previous emoji or actual emoji is 'away'
|
||||
let status;
|
||||
if (user.emoji === 'away') {
|
||||
if (user.away && !newAwayStatus) {
|
||||
status = 'notAway';
|
||||
} else if (emoji === 'away') {
|
||||
} else if (!user.away && newAwayStatus) {
|
||||
status = 'away';
|
||||
} else {
|
||||
return null;
|
@ -0,0 +1,33 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function setEmojiStatus(userId, status) {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ChangeUserEmojiCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
check(status, String);
|
||||
|
||||
const payload = {
|
||||
emoji: status,
|
||||
userId,
|
||||
};
|
||||
|
||||
Logger.verbose('User emoji status updated', {
|
||||
userId, status, requesterUserId, meetingId,
|
||||
});
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method setEmojiStatus ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,20 +1,30 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import updateSpeechLocale from '../modifiers/updateSpeechLocale';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
const LANGUAGES = Meteor.settings.public.app.audioCaptions.language.available;
|
||||
|
||||
export default async function setSpeechLocale(locale) {
|
||||
export default function setSpeechLocale(locale, provider) {
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SetUserSpeechLocaleReqMsg';
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(locale, String);
|
||||
check(provider, String);
|
||||
|
||||
const payload = {
|
||||
locale,
|
||||
provider: provider !== 'webspeech' ? provider : '',
|
||||
};
|
||||
|
||||
if (LANGUAGES.includes(locale) || locale === '') {
|
||||
await updateSpeechLocale(meetingId, requesterUserId, locale);
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method setSpeechLocale ${err.stack}`);
|
||||
|
@ -24,6 +24,9 @@ export default async function addUser(meetingId, userData) {
|
||||
waitingForAcceptance: Match.Maybe(Boolean),
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
reactionEmoji: String,
|
||||
raiseHand: Boolean,
|
||||
away: Boolean,
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
|
@ -0,0 +1,30 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
import sendAwayStatusChatMsg from '../methods/sendAwayStatusChatMsg';
|
||||
|
||||
export default async function changeAway(meetingId, userId, away) {
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
away,
|
||||
awayTime: away ? (new Date()).getTime() : 0,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
// must be called before modifying the users collection, because it
|
||||
// needs to be consulted in order to know the previous emoji
|
||||
sendAwayStatusChatMsg(meetingId, userId, away);
|
||||
|
||||
const numberAffected = await Users.updateAsync(selector, modifier);
|
||||
if (numberAffected) {
|
||||
Logger.info(`Assigned away=${away} user id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Assigning away user: ${err}`);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default async function changeRaiseHand(meetingId, userId, raiseHand) {
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
raiseHand,
|
||||
raiseHandTime: raiseHand ? (new Date()).getTime() : 0,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const numberAffected = await Users.updateAsync(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.info(`Assigned raiseHand=${raiseHand} user id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Assigning raiseHand user: ${err}`);
|
||||
}
|
||||
}
|
@ -12,7 +12,11 @@ export default function clearUsersEmoji(meetingId) {
|
||||
const numberAffected = Users.update(selector, {
|
||||
$set: {
|
||||
emojiTime: (new Date()).getTime(),
|
||||
emoji: "none",
|
||||
emoji: 'none',
|
||||
awayTime: 0,
|
||||
away: false,
|
||||
raiseHandTime: 0,
|
||||
raiseHand: false,
|
||||
},
|
||||
}, { multi: true });
|
||||
|
@ -29,11 +29,6 @@ process.on('uncaughtException', (err) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
Logger.error(`uncaughtException: ${err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`
|
||||
|
||||
const serverHealth = () => {
|
||||
|
@ -11,11 +11,7 @@ import TimerService from '/imports/ui/components/timer/service';
|
||||
import { colorPrimary } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { PANELS, ACTIONS, LAYOUT_TYPE } from '../../layout/enums';
|
||||
import { uniqueId } from '/imports/utils/string-utils';
|
||||
import {
|
||||
isPresentationEnabled,
|
||||
isLayoutsEnabled,
|
||||
isTimerFeatureEnabled,
|
||||
} from '/imports/ui/services/features';
|
||||
import { isPresentationEnabled, isLayoutsEnabled } from '/imports/ui/services/features';
|
||||
import VideoPreviewContainer from '/imports/ui/components/video-preview/container';
|
||||
import { screenshareHasEnded } from '/imports/ui/components/screenshare/service';
|
||||
|
||||
@ -35,6 +31,8 @@ const propTypes = {
|
||||
setMeetingLayout: PropTypes.func.isRequired,
|
||||
setPushLayout: PropTypes.func.isRequired,
|
||||
showPushLayout: PropTypes.bool.isRequired,
|
||||
isTimerFeatureEnabled: PropTypes.bool.isRequired,
|
||||
isCameraAsContentEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@ -191,35 +189,30 @@ class ActionsDropdown extends PureComponent {
|
||||
isMobile,
|
||||
hasCameraAsContent,
|
||||
isCameraAsContentEnabled,
|
||||
isTimerFeatureEnabled,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
pollBtnLabel,
|
||||
presentationLabel,
|
||||
takePresenter,
|
||||
} = intlMessages;
|
||||
const { pollBtnLabel, presentationLabel, takePresenter } = intlMessages;
|
||||
|
||||
const {
|
||||
formatMessage,
|
||||
} = intl;
|
||||
const { formatMessage } = intl;
|
||||
|
||||
const actions = [];
|
||||
|
||||
if (amIPresenter && isPresentationEnabled()) {
|
||||
actions.push({
|
||||
icon: "upload",
|
||||
dataTest: "managePresentations",
|
||||
icon: 'upload',
|
||||
dataTest: 'managePresentations',
|
||||
label: formatMessage(presentationLabel),
|
||||
key: this.presentationItemId,
|
||||
onClick: handlePresentationClick,
|
||||
dividerTop: this.props?.presentations?.length > 1 ? true : false,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (amIPresenter && isPollingEnabled) {
|
||||
actions.push({
|
||||
icon: "polling",
|
||||
dataTest: "polling",
|
||||
icon: 'polling',
|
||||
dataTest: 'polling',
|
||||
label: formatMessage(pollBtnLabel),
|
||||
key: this.pollId,
|
||||
onClick: () => {
|
||||
@ -236,12 +229,12 @@ class ActionsDropdown extends PureComponent {
|
||||
});
|
||||
Session.set('forcePollOpen', true);
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (!amIPresenter && amIModerator) {
|
||||
actions.push({
|
||||
icon: "presentation",
|
||||
icon: 'presentation',
|
||||
label: formatMessage(takePresenter),
|
||||
key: this.takePresenterId,
|
||||
onClick: () => handleTakePresenter(),
|
||||
@ -250,29 +243,31 @@ class ActionsDropdown extends PureComponent {
|
||||
|
||||
if (amIPresenter && allowExternalVideo) {
|
||||
actions.push({
|
||||
icon: !isSharingVideo ? "external-video" : "external-video_off",
|
||||
label: !isSharingVideo ? intl.formatMessage(intlMessages.startExternalVideoLabel)
|
||||
icon: !isSharingVideo ? 'external-video' : 'external-video_off',
|
||||
label: !isSharingVideo
|
||||
? intl.formatMessage(intlMessages.startExternalVideoLabel)
|
||||
: intl.formatMessage(intlMessages.stopExternalVideoLabel),
|
||||
key: "external-video",
|
||||
key: 'external-video',
|
||||
onClick: isSharingVideo ? stopExternalVideoShare : this.handleExternalVideoClick,
|
||||
dataTest: "shareExternalVideo",
|
||||
})
|
||||
dataTest: 'shareExternalVideo',
|
||||
});
|
||||
}
|
||||
|
||||
if (amIPresenter && isSelectRandomUserEnabled) {
|
||||
actions.push({
|
||||
icon: "user",
|
||||
icon: 'user',
|
||||
label: intl.formatMessage(intlMessages.selectRandUserLabel),
|
||||
key: this.selectUserRandId,
|
||||
onClick: () => this.setRandomUserSelectModalIsOpen(true),
|
||||
dataTest: "selectRandomUser",
|
||||
})
|
||||
dataTest: 'selectRandomUser',
|
||||
});
|
||||
}
|
||||
|
||||
if (amIModerator && isTimerEnabled && isTimerFeatureEnabled()) {
|
||||
if (amIModerator && isTimerEnabled && isTimerFeatureEnabled) {
|
||||
actions.push({
|
||||
icon: 'time',
|
||||
label: isTimerActive ? intl.formatMessage(intlMessages.deactivateTimerLabel)
|
||||
label: isTimerActive
|
||||
? intl.formatMessage(intlMessages.deactivateTimerLabel)
|
||||
: intl.formatMessage(intlMessages.activateTimerLabel),
|
||||
key: this.timerId,
|
||||
onClick: () => this.handleTimerClick(),
|
||||
@ -289,7 +284,7 @@ class ActionsDropdown extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
if (isLayoutsEnabled()){
|
||||
if (isLayoutsEnabled()) {
|
||||
actions.push({
|
||||
icon: 'send',
|
||||
label: intl.formatMessage(intlMessages.layoutModal),
|
||||
@ -309,9 +304,9 @@ class ActionsDropdown extends PureComponent {
|
||||
onClick: hasCameraAsContent
|
||||
? screenshareHasEnded
|
||||
: () => {
|
||||
screenshareHasEnded();
|
||||
this.setCameraAsContentModalIsOpen(true);
|
||||
},
|
||||
screenshareHasEnded();
|
||||
this.setCameraAsContentModalIsOpen(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -319,11 +314,7 @@ class ActionsDropdown extends PureComponent {
|
||||
}
|
||||
|
||||
makePresentationItems() {
|
||||
const {
|
||||
presentations,
|
||||
setPresentation,
|
||||
podIds,
|
||||
} = this.props;
|
||||
const { presentations, setPresentation, podIds } = this.props;
|
||||
|
||||
if (!podIds || podIds.length < 1) return [];
|
||||
|
||||
@ -332,57 +323,60 @@ class ActionsDropdown extends PureComponent {
|
||||
const { podId } = podIds[0];
|
||||
|
||||
const presentationItemElements = presentations
|
||||
.sort((a, b) => (a.name.localeCompare(b.name)))
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((p) => {
|
||||
const customStyles = { color: colorPrimary };
|
||||
|
||||
return (
|
||||
{
|
||||
customStyles: p.current ? customStyles : null,
|
||||
icon: "file",
|
||||
iconRight: p.current ? 'check' : null,
|
||||
selected: p.current ? true : false,
|
||||
label: p.name,
|
||||
description: "uploaded presentation file",
|
||||
key: `uploaded-presentation-${p.id}`,
|
||||
onClick: () => {
|
||||
setPresentation(p.id, podId);
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
customStyles: p.current ? customStyles : null,
|
||||
icon: 'file',
|
||||
iconRight: p.current ? 'check' : null,
|
||||
selected: p.current ? true : false,
|
||||
label: p.name,
|
||||
description: 'uploaded presentation file',
|
||||
key: `uploaded-presentation-${p.id}`,
|
||||
onClick: () => {
|
||||
setPresentation(p.id, podId);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return presentationItemElements;
|
||||
}
|
||||
|
||||
setExternalVideoModalIsOpen(value) {
|
||||
this.setState({isExternalVideoModalOpen: value});
|
||||
this.setState({ isExternalVideoModalOpen: value });
|
||||
}
|
||||
|
||||
setRandomUserSelectModalIsOpen(value) {
|
||||
this.setState({isRandomUserSelectModalOpen: value});
|
||||
this.setState({ isRandomUserSelectModalOpen: value });
|
||||
}
|
||||
|
||||
setLayoutModalIsOpen(value) {
|
||||
this.setState({isLayoutModalOpen: value});
|
||||
this.setState({ isLayoutModalOpen: value });
|
||||
}
|
||||
|
||||
setCameraAsContentModalIsOpen(value) {
|
||||
this.setState({isCameraAsContentModalOpen: value});
|
||||
this.setState({ isCameraAsContentModalOpen: value });
|
||||
}
|
||||
|
||||
setPropsToPassModal(value) {
|
||||
this.setState({propsToPassModal: value});
|
||||
this.setState({ propsToPassModal: value });
|
||||
}
|
||||
setForceOpen(value){
|
||||
this.setState({forceOpen: value});
|
||||
setForceOpen(value) {
|
||||
this.setState({ forceOpen: value });
|
||||
}
|
||||
|
||||
renderModal(isOpen, setIsOpen, priority, Component) {
|
||||
return isOpen ? <Component
|
||||
{...{
|
||||
onRequestClose: () => setIsOpen(false),
|
||||
priority,
|
||||
setIsOpen,
|
||||
isOpen
|
||||
}}
|
||||
/> : null
|
||||
return isOpen ? (
|
||||
<Component
|
||||
{...{
|
||||
onRequestClose: () => setIsOpen(false),
|
||||
priority,
|
||||
setIsOpen,
|
||||
isOpen,
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -408,12 +402,12 @@ class ActionsDropdown extends PureComponent {
|
||||
const availableActions = this.getAvailableActions();
|
||||
const availablePresentations = this.makePresentationItems();
|
||||
const children = availablePresentations.length > 1 && amIPresenter
|
||||
? availablePresentations.concat(availableActions) : availableActions;
|
||||
? availablePresentations.concat(availableActions)
|
||||
: availableActions;
|
||||
|
||||
const customStyles = { top: '-1rem' };
|
||||
|
||||
if (availableActions.length === 0
|
||||
|| !isMeteorConnected) {
|
||||
if (availableActions.length === 0 || !isMeteorConnected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -438,24 +432,41 @@ class ActionsDropdown extends PureComponent {
|
||||
}
|
||||
actions={children}
|
||||
opts={{
|
||||
id: "actions-dropdown-menu",
|
||||
id: 'actions-dropdown-menu',
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 3,
|
||||
getcontentanchorel: null,
|
||||
fullwidth: "true",
|
||||
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)}
|
||||
{this.renderModal(isCameraAsContentModalOpen, this.setCameraAsContentModalIsOpen,
|
||||
'low', () => (
|
||||
{this.renderModal(
|
||||
isExternalVideoModalOpen,
|
||||
this.setExternalVideoModalIsOpen,
|
||||
'low',
|
||||
ExternalVideoModal,
|
||||
)}
|
||||
{amIPresenter && isSelectRandomUserEnabled
|
||||
? this.renderModal(
|
||||
isRandomUserSelectModalOpen,
|
||||
this.setRandomUserSelectModalIsOpen,
|
||||
'low',
|
||||
RandomUserSelectContainer,
|
||||
)
|
||||
: null}
|
||||
{this.renderModal(
|
||||
isLayoutModalOpen,
|
||||
this.setLayoutModalIsOpen,
|
||||
'low',
|
||||
LayoutModalContainer,
|
||||
)}
|
||||
{this.renderModal(
|
||||
isCameraAsContentModalOpen,
|
||||
this.setCameraAsContentModalIsOpen,
|
||||
'low',
|
||||
() => (
|
||||
<VideoPreviewContainer
|
||||
cameraAsContent
|
||||
amIPresenter
|
||||
@ -470,7 +481,8 @@ class ActionsDropdown extends PureComponent {
|
||||
}}
|
||||
{...propsToPassModal}
|
||||
/>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import PresentationPodService from '/imports/ui/components/presentation-pod/serv
|
||||
import ActionsDropdown from './component';
|
||||
import { layoutSelectInput, layoutDispatch, layoutSelect } from '../../layout/context';
|
||||
import { SMALL_VIEWPORT_BREAKPOINT } from '../../layout/enums';
|
||||
import { isCameraAsContentEnabled, isTimerFeatureEnabled } from '/imports/ui/services/features';
|
||||
|
||||
const ActionsDropdownContainer = (props) => {
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
@ -16,27 +17,27 @@ const ActionsDropdownContainer = (props) => {
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return (
|
||||
<ActionsDropdown {...{
|
||||
layoutContextDispatch,
|
||||
sidebarContent,
|
||||
sidebarNavigation,
|
||||
isMobile,
|
||||
isRTL,
|
||||
...props,
|
||||
}}
|
||||
<ActionsDropdown
|
||||
{...{
|
||||
layoutContextDispatch,
|
||||
sidebarContent,
|
||||
sidebarNavigation,
|
||||
isMobile,
|
||||
isRTL,
|
||||
...props,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ENABLE_CAMERA_AS_CONTENT = Meteor.settings.public.app.enableCameraAsContent;
|
||||
|
||||
export default withTracker(() => {
|
||||
const presentations = Presentations.find({ 'conversion.done': true }).fetch();
|
||||
return ({
|
||||
return {
|
||||
presentations,
|
||||
isTimerFeatureEnabled: isTimerFeatureEnabled(),
|
||||
isDropdownOpen: Session.get('dropdownOpen'),
|
||||
setPresentation: PresentationUploaderService.setPresentation,
|
||||
podIds: PresentationPodService.getPresentationPodIds(),
|
||||
isCameraAsContentEnabled: ENABLE_CAMERA_AS_CONTENT,
|
||||
});
|
||||
isCameraAsContentEnabled: isCameraAsContentEnabled(),
|
||||
};
|
||||
})(ActionsDropdownContainer);
|
||||
|
@ -30,11 +30,13 @@ class ActionsBar extends PureComponent {
|
||||
}
|
||||
|
||||
renderRaiseHand() {
|
||||
const { isInteractionsButtonEnabled, isRaiseHandButtonEnabled } = this.props;
|
||||
const {
|
||||
isInteractionsButtonEnabled, isRaiseHandButtonEnabled, setEmojiStatus, currentUser, intl,
|
||||
} = this.props;
|
||||
|
||||
return (<>
|
||||
{isInteractionsButtonEnabled ? <InteractionsButtonContainer /> :
|
||||
isRaiseHandButtonEnabled ? <RaiseHandDropdownContainer {...{setEmojiStatus, currentUser, intl}}/>
|
||||
{isInteractionsButtonEnabled ? <InteractionsButtonContainer />
|
||||
: isRaiseHandButtonEnabled ? <RaiseHandDropdownContainer {...{ setEmojiStatus, currentUser, intl }} />
|
||||
: null}
|
||||
</>);
|
||||
}
|
||||
@ -106,7 +108,7 @@ class ActionsBar extends PureComponent {
|
||||
{isCaptionsAvailable
|
||||
? (
|
||||
<>
|
||||
<CaptionsButtonContainer {...{ intl,
|
||||
<CaptionsButtonContainer {...{ intl,
|
||||
setIsOpen: this.setCaptionsReaderMenuModalIsOpen,}} />
|
||||
{
|
||||
isCaptionsReaderMenuModalOpen ? <CaptionsReaderMenuContainer
|
||||
|
@ -12,7 +12,13 @@ import Styled from '../styles';
|
||||
|
||||
const InteractionsButton = (props) => {
|
||||
const {
|
||||
userId, emoji, intl, isMobile, isRTL,
|
||||
userId,
|
||||
emoji,
|
||||
away,
|
||||
raiseHand,
|
||||
intl,
|
||||
isMobile,
|
||||
isRTL,
|
||||
} = props;
|
||||
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
@ -76,12 +82,11 @@ const InteractionsButton = (props) => {
|
||||
key: 'raise-hand',
|
||||
dataTest: 'raise-hand',
|
||||
icon: 'hand',
|
||||
label:
|
||||
emoji === 'raiseHand'
|
||||
? intl.formatMessage(intlMessages.notRaiseHandLabel)
|
||||
: intl.formatMessage(intlMessages.raiseHandLabel),
|
||||
label: raiseHand
|
||||
? intl.formatMessage(intlMessages.notRaiseHandLabel)
|
||||
: intl.formatMessage(intlMessages.raiseHandLabel),
|
||||
onClick: () => {
|
||||
UserListService.setEmojiStatus(userId, emoji === 'raiseHand' ? 'none' : 'raiseHand');
|
||||
UserListService.setUserRaiseHand(userId, !raiseHand);
|
||||
},
|
||||
});
|
||||
|
||||
@ -141,11 +146,11 @@ const InteractionsButton = (props) => {
|
||||
);
|
||||
|
||||
const handlePresent = () => {
|
||||
UserListService.setEmojiStatus(userId, 'none');
|
||||
UserListService.setUserAway(userId, false);
|
||||
};
|
||||
|
||||
const handleAFK = () => {
|
||||
UserListService.setEmojiStatus(userId, 'away');
|
||||
UserListService.setUserAway(userId, true);
|
||||
};
|
||||
|
||||
const buttonStatus = () => (
|
||||
@ -155,18 +160,18 @@ const InteractionsButton = (props) => {
|
||||
onClick={() => handlePresent()}
|
||||
id="btn"
|
||||
icon="user"
|
||||
disabled={emoji !== 'away'}
|
||||
disabled={!away}
|
||||
size="md"
|
||||
color={emoji !== 'away' ? 'primary' : 'default'}
|
||||
color={!away ? 'primary' : 'default'}
|
||||
/>
|
||||
<Button
|
||||
label={intl.formatMessage(intlMessages.awayLabel)}
|
||||
onClick={() => handleAFK()}
|
||||
id="btn"
|
||||
icon="clear_status"
|
||||
disabled={emoji === 'away'}
|
||||
disabled={away}
|
||||
size="md"
|
||||
color={emoji === 'away' ? 'primary' : 'default'}
|
||||
color={away ? 'primary' : 'default'}
|
||||
/>
|
||||
</Styled.ButtonContainer>
|
||||
);
|
||||
@ -176,7 +181,7 @@ const InteractionsButton = (props) => {
|
||||
return intl.formatMessage(intlMessages.interactionsAdvancedButton);
|
||||
}
|
||||
|
||||
return emoji === 'raiseHand'
|
||||
return raiseHand
|
||||
? intl.formatMessage(intlMessages.notRaiseHandLabel)
|
||||
: intl.formatMessage(intlMessages.raiseHandLabel);
|
||||
};
|
||||
@ -187,7 +192,7 @@ const InteractionsButton = (props) => {
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
UserListService.setEmojiStatus(userId, emoji === 'raiseHand' ? 'none' : 'raiseHand');
|
||||
UserListService.setUserRaiseHand(userId, !raiseHand);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -199,10 +204,10 @@ const InteractionsButton = (props) => {
|
||||
icon="hand"
|
||||
label={handleButtonLabel()}
|
||||
description="Interactions"
|
||||
ghost={emoji !== 'raiseHand'}
|
||||
ghost={!raiseHand}
|
||||
onKeyPress={() => {}}
|
||||
onClick={handleInteractionsButtonClick}
|
||||
color={emoji === 'raiseHand' ? 'primary' : 'default'}
|
||||
color={raiseHand ? 'primary' : 'default'}
|
||||
hideLabel
|
||||
circle
|
||||
size="lg"
|
||||
|
@ -27,6 +27,8 @@ export default injectIntl(withTracker(() => {
|
||||
return {
|
||||
userId: currentUser.userId,
|
||||
emoji: currentUser.emoji,
|
||||
away: currentUser.away,
|
||||
raiseHand: currentUser.raiseHand,
|
||||
};
|
||||
})(InteractionsButtonContainer));
|
||||
|
||||
|
@ -27,15 +27,17 @@ const intlMessages = defineMessages({
|
||||
id: 'app.actionsBar.emojiMenu.noneLabel',
|
||||
description: 'label for status clearing',
|
||||
},
|
||||
raiseHandLabel: {
|
||||
id: 'app.actionsBar.emojiMenu.raiseHandLabel',
|
||||
description: 'label for raise hand status',
|
||||
},
|
||||
lowerHandLabel: {
|
||||
id: 'app.actionsBar.emojiMenu.lowerHandLabel',
|
||||
description: 'label for lower hand',
|
||||
},
|
||||
});
|
||||
|
||||
class RaiseHandDropdown extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isHandRaised: false,
|
||||
};
|
||||
}
|
||||
|
||||
getAvailableActions() {
|
||||
const {
|
||||
@ -43,7 +45,6 @@ class RaiseHandDropdown extends PureComponent {
|
||||
getEmojiList,
|
||||
setEmojiStatus,
|
||||
intl,
|
||||
currentUser,
|
||||
} = this.props;
|
||||
|
||||
const actions = [];
|
||||
@ -54,11 +55,6 @@ class RaiseHandDropdown extends PureComponent {
|
||||
key: s,
|
||||
label: intl.formatMessage({ id: `app.actionsBar.emojiMenu.${s}Label` }),
|
||||
onClick: () => {
|
||||
if (currentUser.emoji === 'raiseHand') {
|
||||
this.setState({
|
||||
isHandRaised: true,
|
||||
});
|
||||
}
|
||||
setEmojiStatus(userId, s);
|
||||
},
|
||||
icon: getEmojiList[s],
|
||||
@ -75,26 +71,24 @@ class RaiseHandDropdown extends PureComponent {
|
||||
shortcuts,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isHandRaised,
|
||||
} = this.state;
|
||||
|
||||
const label = currentUser.emoji !== 'raiseHand' && currentUser.emoji !== 'none'
|
||||
? intlMessages.clearStatusLabel
|
||||
: {id: `app.actionsBar.emojiMenu.${
|
||||
currentUser.emoji === 'raiseHand'
|
||||
? 'lowerHandLabel'
|
||||
: 'raiseHandLabel'
|
||||
}`,
|
||||
};
|
||||
let btnLabel;
|
||||
let btnEmoji;
|
||||
if (currentUser.emoji === 'none') {
|
||||
btnEmoji = 'raiseHand';
|
||||
btnLabel = intlMessages.raiseHandLabel;
|
||||
} else if (currentUser.emoji === 'raiseHand') {
|
||||
btnEmoji = 'none';
|
||||
btnLabel = intlMessages.lowerHandLabel;
|
||||
} else {
|
||||
btnEmoji = 'none';
|
||||
btnLabel = intlMessages.clearStatusLabel;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={EMOJI_STATUSES[currentUser.emoji === 'none'
|
||||
? 'raiseHand' : currentUser.emoji]}
|
||||
label={intl.formatMessage(
|
||||
label,
|
||||
)}
|
||||
label={intl.formatMessage(btnLabel)}
|
||||
accessKey={shortcuts.raisehand}
|
||||
color={currentUser.emoji !== 'none' ? 'primary' : 'default'}
|
||||
data-test={currentUser.emoji === 'raiseHand' ? 'lowerHandLabel' : 'raiseHandLabel'}
|
||||
@ -105,21 +99,7 @@ class RaiseHandDropdown extends PureComponent {
|
||||
size="lg"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (currentUser.emoji !== 'none'
|
||||
&& currentUser.emoji !== 'raiseHand') {
|
||||
setEmojiStatus(
|
||||
currentUser.userId,
|
||||
isHandRaised ? 'raiseHand' : 'none',
|
||||
);
|
||||
} else {
|
||||
this.setState({
|
||||
isHandRaised: false,
|
||||
});
|
||||
setEmojiStatus(
|
||||
currentUser.userId,
|
||||
currentUser.emoji === 'raiseHand' ? 'none' : 'raiseHand',
|
||||
);
|
||||
}
|
||||
setEmojiStatus(currentUser.userId, btnEmoji);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -52,7 +52,14 @@ export default {
|
||||
amIModerator,
|
||||
isMe,
|
||||
currentUser: () => Users.findOne({ meetingId: Auth.meetingID, userId: Auth.userID },
|
||||
{ fields: { userId: 1, emoji: 1 } }),
|
||||
{
|
||||
fields: {
|
||||
userId: 1,
|
||||
emoji: 1,
|
||||
away: 1,
|
||||
raiseHand: 1,
|
||||
},
|
||||
}),
|
||||
meetingName: () => Meetings.findOne({ meetingId: Auth.meetingID },
|
||||
{ fields: { 'meetingProp.name': 1 } }).meetingProp.name,
|
||||
users: () => Users.find({
|
||||
|
@ -17,7 +17,7 @@ import NotificationsBarContainer from '../notifications-bar/container';
|
||||
import AudioContainer from '../audio/container';
|
||||
import ChatAlertContainer from '../chat/alert/container';
|
||||
import BannerBarContainer from '/imports/ui/components/banner-bar/container';
|
||||
import StatusNotifier from '/imports/ui/components/status-notifier/container';
|
||||
import RaiseHandNotifier from '/imports/ui/components/raisehand-notifier/container';
|
||||
import ManyWebcamsNotifier from '/imports/ui/components/video-provider/many-users-notify/container';
|
||||
import AudioCaptionsSpeechContainer from '/imports/ui/components/audio/captions/speech/container';
|
||||
import UploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
|
||||
@ -87,6 +87,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.toast.setEmoji.lowerHand',
|
||||
description: 'toast message for lowered hand notification',
|
||||
},
|
||||
away: {
|
||||
id: 'app.toast.setEmoji.away',
|
||||
description: 'toast message for set away notification',
|
||||
},
|
||||
notAway: {
|
||||
id: 'app.toast.setEmoji.notAway',
|
||||
description: 'toast message for remove away notification',
|
||||
},
|
||||
meetingMuteOn: {
|
||||
id: 'app.toast.meetingMuteOn.label',
|
||||
description: 'message used when meeting has been muted',
|
||||
@ -223,6 +231,8 @@ class App extends Component {
|
||||
const {
|
||||
notify,
|
||||
currentUserEmoji,
|
||||
currentUserAway,
|
||||
currentUserRaiseHand,
|
||||
intl,
|
||||
deviceType,
|
||||
mountRandomUserModal,
|
||||
@ -237,30 +247,42 @@ class App extends Component {
|
||||
|
||||
if (mountRandomUserModal) this.setRandomUserSelectModalIsOpen(true);
|
||||
|
||||
if (prevProps.currentUserEmoji.status !== currentUserEmoji.status) {
|
||||
if (prevProps.currentUserEmoji.status !== currentUserEmoji.status
|
||||
&& currentUserEmoji.status !== 'raiseHand'
|
||||
&& currentUserEmoji.status !== 'away'
|
||||
) {
|
||||
const formattedEmojiStatus = intl.formatMessage({ id: `app.actionsBar.emojiMenu.${currentUserEmoji.status}Label` })
|
||||
|| currentUserEmoji.status;
|
||||
|
||||
const raisedHand = currentUserEmoji.status === 'raiseHand';
|
||||
|
||||
let statusLabel = '';
|
||||
if (currentUserEmoji.status === 'none') {
|
||||
statusLabel = prevProps.currentUserEmoji.status === 'raiseHand'
|
||||
? intl.formatMessage(intlMessages.loweredHand)
|
||||
: intl.formatMessage(intlMessages.clearedEmoji);
|
||||
notify(
|
||||
intl.formatMessage(intlMessages.clearedEmoji),
|
||||
'info',
|
||||
'clear_status',
|
||||
);
|
||||
} else {
|
||||
statusLabel = raisedHand
|
||||
? intl.formatMessage(intlMessages.raisedHand)
|
||||
: intl.formatMessage(intlMessages.setEmoji, ({ 0: formattedEmojiStatus }));
|
||||
notify(
|
||||
intl.formatMessage(intlMessages.setEmoji, ({ 0: formattedEmojiStatus })),
|
||||
'info',
|
||||
'user',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
notify(
|
||||
statusLabel,
|
||||
'info',
|
||||
currentUserEmoji.status === 'none'
|
||||
? 'clear_status'
|
||||
: 'user',
|
||||
);
|
||||
if (prevProps.currentUserAway !== currentUserAway) {
|
||||
if (currentUserAway === true) {
|
||||
notify(intl.formatMessage(intlMessages.away), 'info', 'user');
|
||||
} else {
|
||||
notify(intl.formatMessage(intlMessages.notAway), 'info', 'clear_status');
|
||||
}
|
||||
}
|
||||
|
||||
if (prevProps.currentUserRaiseHand !== currentUserRaiseHand) {
|
||||
if (currentUserRaiseHand === true) {
|
||||
notify(intl.formatMessage(intlMessages.raisedHand), 'info', 'user');
|
||||
} else {
|
||||
notify(intl.formatMessage(intlMessages.loweredHand), 'info', 'clear_status');
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceType === null || prevProps.deviceType !== deviceType) this.throttledDeviceType();
|
||||
@ -521,7 +543,7 @@ class App extends Component {
|
||||
setAudioModalIsOpen(value) {
|
||||
this.setState({isAudioModalOpen: value});
|
||||
}
|
||||
|
||||
|
||||
setVideoPreviewModalIsOpen(value) {
|
||||
this.setState({isVideoPreviewModalOpen: value});
|
||||
}
|
||||
@ -606,14 +628,14 @@ class App extends Component {
|
||||
pushAlertEnabled={pushAlertEnabled}
|
||||
/>
|
||||
)}
|
||||
<StatusNotifier status="raiseHand" />
|
||||
<RaiseHandNotifier />
|
||||
<ManyWebcamsNotifier />
|
||||
<PollingContainer />
|
||||
<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
|
||||
{isRandomUserSelectModalOpen ? <RandomUserSelectContainer
|
||||
{...{
|
||||
onRequestClose: () => this.setRandomUserSelectModalIsOpen(false),
|
||||
priority: "low",
|
||||
|
@ -226,7 +226,7 @@ export default withTracker(() => {
|
||||
{
|
||||
fields:
|
||||
{
|
||||
approved: 1, emoji: 1, userId: 1, presenter: 1, role: 1,
|
||||
approved: 1, emoji: 1, raiseHand: 1, away: 1, userId: 1, presenter: 1, role: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
@ -288,6 +288,8 @@ export default withTracker(() => {
|
||||
isPhone: deviceInfo.isPhone,
|
||||
isRTL: document.documentElement.getAttribute('dir') === 'rtl',
|
||||
currentUserEmoji: currentUserEmoji(currentUser),
|
||||
currentUserAway: currentUser.away,
|
||||
currentUserRaiseHand: currentUser.raiseHand,
|
||||
randomlySelectedUser,
|
||||
currentUserId: currentUser?.userId,
|
||||
isPresenter,
|
||||
|
@ -600,7 +600,7 @@ class AudioModal extends Component {
|
||||
const { content } = this.state;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<>
|
||||
{showPermissionsOvelay ? <PermissionsOverlay closeModal={closeModal} /> : null}
|
||||
<Styled.AudioModal
|
||||
modalName="AUDIO"
|
||||
@ -638,7 +638,7 @@ class AudioModal extends Component {
|
||||
{this.renderContent()}
|
||||
</Styled.Content>
|
||||
</Styled.AudioModal>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import { unique } from 'radash';
|
||||
const THROTTLE_TIMEOUT = 1000;
|
||||
|
||||
const CONFIG = Meteor.settings.public.app.audioCaptions;
|
||||
const ENABLED = CONFIG.enabled;
|
||||
const PROVIDER = CONFIG.provider;
|
||||
const LANGUAGES = CONFIG.language.available;
|
||||
const VALID_ENVIRONMENT = !deviceInfo.isMobile || CONFIG.mobile;
|
||||
|
||||
@ -32,15 +34,17 @@ const setSpeechVoices = () => {
|
||||
setSpeechVoices();
|
||||
|
||||
const getSpeechVoices = () => {
|
||||
const voices = Session.get('speechVoices') || [];
|
||||
if (!isWebSpeechApi()) return LANGUAGES;
|
||||
|
||||
const voices = Session.get('speechVoices') || [];
|
||||
return voices.filter((v) => LANGUAGES.includes(v));
|
||||
};
|
||||
|
||||
const setSpeechLocale = (value) => {
|
||||
const voices = getSpeechVoices();
|
||||
|
||||
if (voices.includes(value) || value === '') {
|
||||
makeCall('setSpeechLocale', value);
|
||||
makeCall('setSpeechLocale', value, CONFIG.provider);
|
||||
} else {
|
||||
logger.error({
|
||||
logCode: 'captions_speech_locale',
|
||||
@ -51,7 +55,7 @@ const setSpeechLocale = (value) => {
|
||||
const useFixedLocale = () => isEnabled() && CONFIG.language.forceLocale;
|
||||
|
||||
const initSpeechRecognition = () => {
|
||||
if (!isEnabled()) return null;
|
||||
if (!isEnabled() || !isWebSpeechApi()) return null;
|
||||
if (hasSpeechRecognitionSupport()) {
|
||||
// Effectivate getVoices
|
||||
setSpeechVoices();
|
||||
@ -77,7 +81,7 @@ const initSpeechRecognition = () => {
|
||||
|
||||
let prevId = '';
|
||||
let prevTranscript = '';
|
||||
const updateTranscript = (id, transcript, locale) => {
|
||||
const updateTranscript = (id, transcript, locale, isFinal) => {
|
||||
// If it's a new sentence
|
||||
if (id !== prevId) {
|
||||
prevId = id;
|
||||
@ -98,7 +102,7 @@ const updateTranscript = (id, transcript, locale) => {
|
||||
// Stores current transcript as previous
|
||||
prevTranscript = transcript;
|
||||
|
||||
makeCall('updateTranscript', id, start, end, text, transcript, locale);
|
||||
makeCall('updateTranscript', id, start, end, text, transcript, locale, isFinal);
|
||||
};
|
||||
|
||||
const throttledTranscriptUpdate = throttle(updateTranscript, THROTTLE_TIMEOUT, {
|
||||
@ -107,12 +111,12 @@ const throttledTranscriptUpdate = throttle(updateTranscript, THROTTLE_TIMEOUT, {
|
||||
});
|
||||
|
||||
const updateInterimTranscript = (id, transcript, locale) => {
|
||||
throttledTranscriptUpdate(id, transcript, locale);
|
||||
throttledTranscriptUpdate(id, transcript, locale, false);
|
||||
};
|
||||
|
||||
const updateFinalTranscript = (id, transcript, locale) => {
|
||||
throttledTranscriptUpdate.cancel();
|
||||
updateTranscript(id, transcript, locale);
|
||||
updateTranscript(id, transcript, locale, true);
|
||||
};
|
||||
|
||||
const getSpeechLocale = (userId = Auth.userID) => {
|
||||
@ -129,7 +133,15 @@ const isLocaleValid = (locale) => LANGUAGES.includes(locale);
|
||||
|
||||
const isEnabled = () => isLiveTranscriptionEnabled();
|
||||
|
||||
const isActive = () => isEnabled() && hasSpeechRecognitionSupport() && hasSpeechLocale();
|
||||
const isWebSpeechApi = () => PROVIDER === 'webspeech';
|
||||
|
||||
const isVosk = () => PROVIDER === 'vosk';
|
||||
|
||||
const isWhispering = () => PROVIDER === 'whisper';
|
||||
|
||||
const isDeepSpeech = () => PROVIDER === 'deepSpeech'
|
||||
|
||||
const isActive = () => isEnabled() && ((isWebSpeechApi() && hasSpeechLocale()) || isVosk() || isWhispering() || isDeepSpeech());
|
||||
|
||||
const getStatus = () => {
|
||||
const active = isActive();
|
||||
@ -156,6 +168,8 @@ const getLocale = () => {
|
||||
return locale;
|
||||
};
|
||||
|
||||
const stereoUnsupported = () => isActive() && isVosk() && !!getSpeechLocale();
|
||||
|
||||
export default {
|
||||
LANGUAGES,
|
||||
hasSpeechRecognitionSupport,
|
||||
@ -172,4 +186,5 @@ export default {
|
||||
getStatus,
|
||||
generateId,
|
||||
useFixedLocale,
|
||||
stereoUnsupported,
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ const WriterMenuContainer = (props) => {
|
||||
return amIModerator && <WriterMenu {...{ layoutContextDispatch, ...props }} />;
|
||||
};
|
||||
|
||||
export default withTracker(() => ({
|
||||
export default withTracker(({ setIsOpen }) => ({
|
||||
closeModal: () => setIsOpen(false),
|
||||
availableLocales: Service.getAvailableLocales(),
|
||||
}))(WriterMenuContainer);
|
||||
|
@ -55,9 +55,9 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploader.export.originalLabel',
|
||||
description: 'Label to identify original presentation exported',
|
||||
},
|
||||
annotated: {
|
||||
id: 'app.presentationUploader.export.withAnnotationsLabel',
|
||||
description: 'Label to identify annotated presentation exported',
|
||||
currentState: {
|
||||
id: 'app.presentationUploader.export.inCurrentStateLabel',
|
||||
description: 'Label to identify in current state presentation exported',
|
||||
},
|
||||
});
|
||||
|
||||
@ -337,11 +337,11 @@ const removePackagedClassAttribute = (classnames, attribute) => {
|
||||
};
|
||||
|
||||
const getExportedPresentationString = (fileURI, filename, intl, typeOfExport) => {
|
||||
const intlTypeOfExport = typeOfExport === 'Original' ? intlMessages.original : intlMessages.annotated;
|
||||
const intlTypeOfExport = typeOfExport === 'Original' ? intlMessages.original : intlMessages.currentState;
|
||||
const warningIcon = '<i class="icon-bbb-warning"></i>';
|
||||
const label = `<span>${intl.formatMessage(intlMessages.download)}</span>`;
|
||||
const notAccessibleWarning = `<span title="${intl.formatMessage(intlMessages.notAccessibleWarning)}">${warningIcon}</span>`;
|
||||
const link = `<a aria-label="${intl.formatMessage(intlMessages.notAccessibleWarning)}" href=${fileURI} type="application/pdf" rel="noopener, noreferrer" download>${label} ${notAccessibleWarning}</a>`;
|
||||
const link = `<a aria-label="${intl.formatMessage(intlMessages.notAccessibleWarning)}" href=${fileURI} type="application/pdf" target="_blank" rel="noopener, noreferrer" download>${label} ${notAccessibleWarning}</a>`;
|
||||
const name = `<span>${filename} (${intl.formatMessage(intlTypeOfExport)})</span>`;
|
||||
return `${name}</br>${link}`;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
colorWhite,
|
||||
colorPrimary
|
||||
colorPrimary,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import { mdPaddingX } from '/imports/ui/stylesheets/styled-components/general';
|
||||
@ -52,6 +52,9 @@ const Chat = styled.div`
|
||||
|
||||
@media ${smallOnly} {
|
||||
transform: none !important;
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -1,17 +1,25 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import Styled from './styles';
|
||||
|
||||
const BaseModal = (props) => {
|
||||
const { setIsOpen, modalName, children,
|
||||
isOpen, onRequestClose, className, overlayClassName,
|
||||
const BaseModal = (props) => {
|
||||
const {
|
||||
setIsOpen,
|
||||
modalName,
|
||||
children,
|
||||
isOpen,
|
||||
onRequestClose,
|
||||
className,
|
||||
overlayClassName,
|
||||
dataTest,
|
||||
priority,
|
||||
} = props;
|
||||
|
||||
const closeEventHandler = useCallback (() => {
|
||||
setIsOpen(false);
|
||||
} , []);
|
||||
useEffect( () => {
|
||||
const closeEventHandler = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
// Only add event listener if name is specified
|
||||
if(!modalName) return;
|
||||
if (!modalName) return;
|
||||
|
||||
const closeEventName = `CLOSE_MODAL_${modalName.toUpperCase()}`;
|
||||
|
||||
@ -23,18 +31,25 @@ const BaseModal = (props) => {
|
||||
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}
|
||||
shouldReturnFocusAfterClose={false}
|
||||
>
|
||||
{children}
|
||||
</Styled.BaseModal>
|
||||
)}
|
||||
const priorityValue = priority || 'low';
|
||||
|
||||
return (
|
||||
<Styled.BaseModal
|
||||
portalClassName={`modal-${priorityValue}`}
|
||||
parentSelector={() => document.querySelector('#modals-container')}
|
||||
isOpen={isOpen}
|
||||
onRequestClose={onRequestClose}
|
||||
className={className}
|
||||
overlayClassName={overlayClassName}
|
||||
shouldReturnFocusAfterClose={false}
|
||||
data={{
|
||||
test: dataTest,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Styled.BaseModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default { BaseModal };
|
||||
|
@ -84,9 +84,7 @@ class ModalSimple extends Component {
|
||||
className={className}
|
||||
onRequestClose={handleRequestClose}
|
||||
contentLabel={title || contentLabel}
|
||||
data={{
|
||||
test: dataTest ?? null,
|
||||
}}
|
||||
dataTest={dataTest}
|
||||
{...otherProps}
|
||||
>
|
||||
<Styled.Header
|
||||
|
@ -7,7 +7,6 @@ import { ACTIONS, CAMERADOCK_POSITION, PANELS } from '../enums';
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
import { defaultsDeep } from '/imports/utils/array-utils';
|
||||
import { isPresentationEnabled } from '/imports/ui/services/features';
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
@ -78,8 +77,7 @@ const CustomLayout = (props) => {
|
||||
|
||||
const calculatesDropAreas = (sidebarNavWidth, sidebarContentWidth, cameraDockBounds) => {
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
const mediaAreaHeight = windowHeight()
|
||||
- (DEFAULT_VALUES.navBarHeight + actionBarHeight);
|
||||
const mediaAreaHeight = windowHeight() - (DEFAULT_VALUES.navBarHeight + actionBarHeight);
|
||||
const mediaAreaWidth = windowWidth() - (sidebarNavWidth + sidebarContentWidth);
|
||||
const DROP_ZONE_DEFAUL_SIZE = 100;
|
||||
const dropZones = {};
|
||||
@ -97,16 +95,13 @@ const CustomLayout = (props) => {
|
||||
dropZones[CAMERADOCK_POSITION.CONTENT_RIGHT] = {
|
||||
top: DEFAULT_VALUES.navBarHeight + DROP_ZONE_DEFAUL_SIZE,
|
||||
left: !isRTL ? windowWidth() - DROP_ZONE_DEFAUL_SIZE : 0,
|
||||
height: mediaAreaHeight
|
||||
- (2 * DROP_ZONE_DEFAUL_SIZE),
|
||||
height: mediaAreaHeight - 2 * DROP_ZONE_DEFAUL_SIZE,
|
||||
width: DROP_ZONE_DEFAUL_SIZE,
|
||||
zIndex: cameraDockBounds.zIndex,
|
||||
};
|
||||
|
||||
dropZones[CAMERADOCK_POSITION.CONTENT_BOTTOM] = {
|
||||
top: DEFAULT_VALUES.navBarHeight
|
||||
+ mediaAreaHeight
|
||||
- DROP_ZONE_DEFAUL_SIZE,
|
||||
top: DEFAULT_VALUES.navBarHeight + mediaAreaHeight - DROP_ZONE_DEFAUL_SIZE,
|
||||
left: !isRTL ? sidebarSize : null,
|
||||
right: isRTL ? sidebarSize : null,
|
||||
width: mediaAreaWidth,
|
||||
@ -118,8 +113,7 @@ const CustomLayout = (props) => {
|
||||
top: DEFAULT_VALUES.navBarHeight + DROP_ZONE_DEFAUL_SIZE,
|
||||
left: !isRTL ? sidebarSize : null,
|
||||
right: isRTL ? sidebarSize : null,
|
||||
height: mediaAreaHeight
|
||||
- (2 * DROP_ZONE_DEFAUL_SIZE),
|
||||
height: mediaAreaHeight - 2 * DROP_ZONE_DEFAUL_SIZE,
|
||||
width: DROP_ZONE_DEFAUL_SIZE,
|
||||
zIndex: cameraDockBounds.zIndex,
|
||||
};
|
||||
@ -137,82 +131,90 @@ const CustomLayout = (props) => {
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
sidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
sidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
sidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
sidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
}
|
||||
Session.set('layoutReady', true);
|
||||
@ -225,16 +227,20 @@ const CustomLayout = (props) => {
|
||||
const { hasScreenShare } = screenShareInput;
|
||||
const { isPinned: isSharedNotesPinned } = sharedNotesInput;
|
||||
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0
|
||||
const isGeneralMediaOff = !hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0;
|
||||
const isGeneralMediaOff =
|
||||
!hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
|
||||
let sidebarContentHeight = 0;
|
||||
if (sidebarContentInput.isOpen) {
|
||||
if (isMobile) {
|
||||
sidebarContentHeight = windowHeight() - DEFAULT_VALUES.navBarHeight;
|
||||
} else if (cameraDockInput.numCameras > 0
|
||||
&& cameraDockInput.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM
|
||||
&& isOpen && !isGeneralMediaOff) {
|
||||
} else if (
|
||||
cameraDockInput.numCameras > 0 &&
|
||||
cameraDockInput.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM &&
|
||||
isOpen &&
|
||||
!isGeneralMediaOff
|
||||
) {
|
||||
sidebarContentHeight = windowHeight() - cameraDockHeight;
|
||||
} else {
|
||||
sidebarContentHeight = windowHeight();
|
||||
@ -282,14 +288,13 @@ const CustomLayout = (props) => {
|
||||
|
||||
const stoppedResizing = prevIsResizing && !isResizing;
|
||||
if (stoppedResizing) {
|
||||
const isCameraTopOrBottom = cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_TOP
|
||||
|| cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_BOTTOM;
|
||||
const isCameraTopOrBottom =
|
||||
cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_TOP ||
|
||||
cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_BOTTOM;
|
||||
|
||||
Storage.setItem('webcamSize', {
|
||||
width: isCameraTopOrBottom || isCameraSidebar
|
||||
? lastWidth : cameraDockInput.width,
|
||||
height: isCameraTopOrBottom || isCameraSidebar
|
||||
? cameraDockInput.height : lastHeight,
|
||||
width: isCameraTopOrBottom || isCameraSidebar ? lastWidth : cameraDockInput.width,
|
||||
height: isCameraTopOrBottom || isCameraSidebar ? cameraDockInput.height : lastHeight,
|
||||
});
|
||||
|
||||
const updatedLastSize = Storage.getItem('webcamSize');
|
||||
@ -300,14 +305,14 @@ const CustomLayout = (props) => {
|
||||
if (isCameraTop || isCameraBottom) {
|
||||
if ((lastHeight === 0 && !isResizing) || (isCameraTop && isMobile)) {
|
||||
cameraDockHeight = min(
|
||||
max((mediaAreaBounds.height * 0.2), cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - cameraDockMinHeight),
|
||||
max(mediaAreaBounds.height * 0.2, cameraDockMinHeight),
|
||||
mediaAreaBounds.height - cameraDockMinHeight
|
||||
);
|
||||
} else {
|
||||
const height = isResizing ? cameraDockInput.height : lastHeight;
|
||||
cameraDockHeight = min(
|
||||
max(height, cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - cameraDockMinHeight),
|
||||
mediaAreaBounds.height - cameraDockMinHeight
|
||||
);
|
||||
}
|
||||
|
||||
@ -322,7 +327,7 @@ const CustomLayout = (props) => {
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
|
||||
if (isCameraBottom) {
|
||||
cameraDockBounds.top += (mediaAreaBounds.height - cameraDockHeight);
|
||||
cameraDockBounds.top += mediaAreaBounds.height - cameraDockHeight;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
@ -331,14 +336,14 @@ const CustomLayout = (props) => {
|
||||
if (isCameraLeft || isCameraRight) {
|
||||
if (lastWidth === 0 && !isResizing) {
|
||||
cameraDockWidth = min(
|
||||
max((mediaAreaBounds.width * 0.2), cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - cameraDockMinWidth),
|
||||
max(mediaAreaBounds.width * 0.2, cameraDockMinWidth),
|
||||
mediaAreaBounds.width - cameraDockMinWidth
|
||||
);
|
||||
} else {
|
||||
const width = isResizing ? cameraDockInput.width : lastWidth;
|
||||
cameraDockWidth = min(
|
||||
max(width, cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - cameraDockMinWidth),
|
||||
mediaAreaBounds.width - cameraDockMinWidth
|
||||
);
|
||||
}
|
||||
|
||||
@ -346,9 +351,8 @@ const CustomLayout = (props) => {
|
||||
cameraDockBounds.minWidth = cameraDockMinWidth;
|
||||
cameraDockBounds.width = cameraDockWidth;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.presenterMaxWidth = mediaAreaBounds.width
|
||||
- presentationToolbarMinWidth
|
||||
- camerasMargin;
|
||||
cameraDockBounds.presenterMaxWidth =
|
||||
mediaAreaBounds.width - presentationToolbarMinWidth - camerasMargin;
|
||||
cameraDockBounds.minHeight = cameraDockMinHeight;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
@ -356,12 +360,12 @@ const CustomLayout = (props) => {
|
||||
cameraDockBounds.height -= 20;
|
||||
|
||||
if (isCameraRight) {
|
||||
const sizeValue = (mediaAreaBounds.left + mediaAreaBounds.width) - cameraDockWidth;
|
||||
const sizeValue = mediaAreaBounds.left + mediaAreaBounds.width - cameraDockWidth;
|
||||
cameraDockBounds.left = !isRTL ? sizeValue - camerasMargin : 0;
|
||||
cameraDockBounds.right = isRTL ? sizeValue + sidebarSize - camerasMargin : null;
|
||||
} else if (isCameraLeft) {
|
||||
cameraDockBounds.left = mediaAreaBounds.left + camerasMargin;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + camerasMargin * 2 : null;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
@ -370,14 +374,14 @@ const CustomLayout = (props) => {
|
||||
if (isCameraSidebar) {
|
||||
if (lastHeight === 0 && !isResizing) {
|
||||
cameraDockHeight = min(
|
||||
max((windowHeight() * 0.2), cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
max(windowHeight() * 0.2, cameraDockMinHeight),
|
||||
windowHeight() - cameraDockMinHeight
|
||||
);
|
||||
} else {
|
||||
const height = isResizing ? cameraDockInput.height : lastHeight;
|
||||
cameraDockHeight = min(
|
||||
max(height, cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
windowHeight() - cameraDockMinHeight
|
||||
);
|
||||
}
|
||||
|
||||
@ -401,15 +405,16 @@ const CustomLayout = (props) => {
|
||||
const { isPinned: isSharedNotesPinned } = sharedNotesInput;
|
||||
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
const mediaAreaHeight = windowHeight()
|
||||
- (DEFAULT_VALUES.navBarHeight + actionBarHeight + bannerAreaHeight());
|
||||
const mediaAreaHeight =
|
||||
windowHeight() - (DEFAULT_VALUES.navBarHeight + actionBarHeight + bannerAreaHeight());
|
||||
const mediaAreaWidth = windowWidth() - (sidebarNavWidth + sidebarContentWidth);
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
const { navBarHeight, camerasMargin } = DEFAULT_VALUES;
|
||||
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0
|
||||
const isGeneralMediaOff = !hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0;
|
||||
const isGeneralMediaOff =
|
||||
!hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
|
||||
if (!isOpen || isGeneralMediaOff) {
|
||||
mediaBounds.width = 0;
|
||||
@ -421,7 +426,11 @@ const CustomLayout = (props) => {
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
if (fullscreenElement === 'Presentation' || fullscreenElement === 'Screenshare' || fullscreenElement === 'ExternalVideo') {
|
||||
if (
|
||||
fullscreenElement === 'Presentation' ||
|
||||
fullscreenElement === 'Screenshare' ||
|
||||
fullscreenElement === 'ExternalVideo'
|
||||
) {
|
||||
mediaBounds.width = windowWidth();
|
||||
mediaBounds.height = windowHeight();
|
||||
mediaBounds.top = 0;
|
||||
@ -438,17 +447,18 @@ const CustomLayout = (props) => {
|
||||
case CAMERADOCK_POSITION.CONTENT_TOP: {
|
||||
mediaBounds.width = mediaAreaWidth;
|
||||
mediaBounds.height = mediaAreaHeight - cameraDockBounds.height - camerasMargin;
|
||||
mediaBounds.top = navBarHeight + cameraDockBounds.height + camerasMargin + bannerAreaHeight();
|
||||
mediaBounds.top =
|
||||
navBarHeight + cameraDockBounds.height + camerasMargin + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_RIGHT: {
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - (camerasMargin * 2);
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - camerasMargin * 2;
|
||||
mediaBounds.height = mediaAreaHeight;
|
||||
mediaBounds.top = navBarHeight + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize - (camerasMargin * 2) : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize - camerasMargin * 2 : null;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_BOTTOM: {
|
||||
@ -460,11 +470,11 @@ const CustomLayout = (props) => {
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_LEFT: {
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - (camerasMargin * 2);
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - camerasMargin * 2;
|
||||
mediaBounds.height = mediaAreaHeight;
|
||||
mediaBounds.top = navBarHeight + bannerAreaHeight();
|
||||
const sizeValue = sidebarNavWidth
|
||||
+ sidebarContentWidth + mediaAreaWidth - mediaBounds.width;
|
||||
const sizeValue =
|
||||
sidebarNavWidth + sidebarContentWidth + mediaAreaWidth - mediaBounds.width;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
break;
|
||||
@ -491,7 +501,7 @@ const CustomLayout = (props) => {
|
||||
}
|
||||
|
||||
return mediaBounds;
|
||||
}
|
||||
};
|
||||
|
||||
const calculatesLayout = () => {
|
||||
const {
|
||||
@ -513,16 +523,27 @@ const CustomLayout = (props) => {
|
||||
const sidebarContentWidth = calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width
|
||||
);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const cameraDockBounds = calculatesCameraDockBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width, mediaAreaBounds,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width,
|
||||
mediaAreaBounds
|
||||
);
|
||||
const dropZoneAreas = calculatesDropAreas(
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width,
|
||||
cameraDockBounds
|
||||
);
|
||||
const dropZoneAreas = calculatesDropAreas(sidebarNavWidth.width, sidebarContentWidth.width, cameraDockBounds);
|
||||
const sidebarContentHeight = calculatesSidebarContentHeight(cameraDockBounds.height);
|
||||
const mediaBounds = calculatesMediaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width, cameraDockBounds,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width,
|
||||
cameraDockBounds
|
||||
);
|
||||
const sidebarSize = sidebarContentWidth.width + sidebarNavWidth.width;
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
@ -530,7 +551,7 @@ const CustomLayout = (props) => {
|
||||
let horizontalCameraDiff = 0;
|
||||
|
||||
if (cameraPosition === CAMERADOCK_POSITION.CONTENT_LEFT) {
|
||||
horizontalCameraDiff = cameraDockBounds.width + (camerasMargin * 2);
|
||||
horizontalCameraDiff = cameraDockBounds.width + camerasMargin * 2;
|
||||
}
|
||||
|
||||
if (cameraPosition === CAMERADOCK_POSITION.CONTENT_RIGHT) {
|
||||
@ -568,9 +589,9 @@ const CustomLayout = (props) => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAPTIONS_OUTPUT,
|
||||
value: {
|
||||
left: !isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
right: isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
maxWidth: mediaAreaBounds.width - (captionsMargin * 2),
|
||||
left: !isRTL ? sidebarSize + captionsMargin : null,
|
||||
right: isRTL ? sidebarSize + captionsMargin : null,
|
||||
maxWidth: mediaAreaBounds.width - captionsMargin * 2,
|
||||
},
|
||||
});
|
||||
|
||||
@ -655,14 +676,17 @@ const CustomLayout = (props) => {
|
||||
tabOrder: 4,
|
||||
isDraggable: !isMobile && !isTablet && presentationInput.isOpen,
|
||||
resizableEdge: {
|
||||
top: (input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_BOTTOM)
|
||||
|| (input.cameraDock.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM
|
||||
&& input.sidebarContent.isOpen),
|
||||
right: (!isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_LEFT)
|
||||
|| (isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_RIGHT),
|
||||
top:
|
||||
input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_BOTTOM ||
|
||||
(input.cameraDock.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM &&
|
||||
input.sidebarContent.isOpen),
|
||||
right:
|
||||
(!isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_LEFT) ||
|
||||
(isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_RIGHT),
|
||||
bottom: input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_TOP,
|
||||
left: (!isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_RIGHT)
|
||||
|| (isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_LEFT),
|
||||
left:
|
||||
(!isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_RIGHT) ||
|
||||
(isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_LEFT),
|
||||
},
|
||||
zIndex: cameraDockBounds.zIndex,
|
||||
focusedId: input.cameraDock.focusedId,
|
||||
@ -682,7 +706,7 @@ const CustomLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
tabOrder: DEFAULT_VALUES.presentationTabOrder,
|
||||
isResizable: false,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
@ -696,7 +720,7 @@ const CustomLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -708,7 +732,7 @@ const CustomLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
|
||||
@ -719,7 +743,7 @@ const CustomLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -727,4 +751,4 @@ const CustomLayout = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CustomLayout;
|
||||
export default CustomLayout;
|
@ -76,76 +76,82 @@ const PresentationFocusLayout = (props) => {
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
const init = () => {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
}
|
||||
Session.set('layoutReady', true);
|
||||
@ -158,13 +164,11 @@ const PresentationFocusLayout = (props) => {
|
||||
const { hasScreenShare } = screenShareInput;
|
||||
const { isPinned: isSharedNotesPinned } = sharedNotesInput;
|
||||
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0
|
||||
const isGeneralMediaOff = !hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0;
|
||||
const isGeneralMediaOff =
|
||||
!hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
|
||||
const {
|
||||
navBarHeight,
|
||||
sidebarContentMinHeight,
|
||||
} = DEFAULT_VALUES;
|
||||
const { navBarHeight, sidebarContentMinHeight } = DEFAULT_VALUES;
|
||||
let height = 0;
|
||||
let minHeight = 0;
|
||||
let maxHeight = 0;
|
||||
@ -175,10 +179,9 @@ const PresentationFocusLayout = (props) => {
|
||||
maxHeight = height;
|
||||
} else if (cameraDockInput.numCameras > 0 && isOpen && !isGeneralMediaOff) {
|
||||
if (sidebarContentInput.height === 0) {
|
||||
height = (windowHeight() * 0.75) - bannerAreaHeight();
|
||||
height = windowHeight() * 0.75 - bannerAreaHeight();
|
||||
} else {
|
||||
height = min(max(sidebarContentInput.height, sidebarContentMinHeight),
|
||||
windowHeight());
|
||||
height = min(max(sidebarContentInput.height, sidebarContentMinHeight), windowHeight());
|
||||
}
|
||||
minHeight = windowHeight() * 0.25 - bannerAreaHeight();
|
||||
maxHeight = windowHeight() * 0.75 - bannerAreaHeight();
|
||||
@ -200,7 +203,7 @@ const PresentationFocusLayout = (props) => {
|
||||
mediaAreaBounds,
|
||||
sidebarNavWidth,
|
||||
sidebarContentWidth,
|
||||
sidebarContentHeight,
|
||||
sidebarContentHeight
|
||||
) => {
|
||||
const { baseCameraDockBounds } = props;
|
||||
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
|
||||
@ -231,15 +234,16 @@ const PresentationFocusLayout = (props) => {
|
||||
} else {
|
||||
if (cameraDockInput.height === 0) {
|
||||
cameraDockHeight = min(
|
||||
max((windowHeight() - sidebarContentHeight), cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
max(windowHeight() - sidebarContentHeight, cameraDockMinHeight),
|
||||
windowHeight() - cameraDockMinHeight
|
||||
);
|
||||
const bannerAreaDiff = windowHeight() - sidebarContentHeight - cameraDockHeight - bannerAreaHeight();
|
||||
const bannerAreaDiff =
|
||||
windowHeight() - sidebarContentHeight - cameraDockHeight - bannerAreaHeight();
|
||||
cameraDockHeight += bannerAreaDiff;
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(cameraDockInput.height, cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
windowHeight() - cameraDockMinHeight
|
||||
);
|
||||
}
|
||||
cameraDockBounds.top = windowHeight() - cameraDockHeight - bannerAreaHeight();
|
||||
@ -260,7 +264,11 @@ const PresentationFocusLayout = (props) => {
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
|
||||
if (fullscreenElement === 'Presentation' || fullscreenElement === 'Screenshare' || fullscreenElement === 'ExternalVideo') {
|
||||
if (
|
||||
fullscreenElement === 'Presentation' ||
|
||||
fullscreenElement === 'Screenshare' ||
|
||||
fullscreenElement === 'ExternalVideo'
|
||||
) {
|
||||
mediaBounds.width = windowWidth();
|
||||
mediaBounds.height = windowHeight();
|
||||
mediaBounds.top = 0;
|
||||
@ -304,7 +312,8 @@ const PresentationFocusLayout = (props) => {
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width
|
||||
);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
@ -316,7 +325,7 @@ const PresentationFocusLayout = (props) => {
|
||||
mediaAreaBounds,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width,
|
||||
sidebarContentHeight.height,
|
||||
sidebarContentHeight.height
|
||||
);
|
||||
const { isOpen } = presentationInput;
|
||||
|
||||
@ -351,9 +360,9 @@ const PresentationFocusLayout = (props) => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAPTIONS_OUTPUT,
|
||||
value: {
|
||||
left: !isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
right: isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
maxWidth: mediaAreaBounds.width - (captionsMargin * 2),
|
||||
left: !isRTL ? sidebarSize + captionsMargin : null,
|
||||
right: isRTL ? sidebarSize + captionsMargin : null,
|
||||
maxWidth: mediaAreaBounds.width - captionsMargin * 2,
|
||||
},
|
||||
});
|
||||
|
||||
@ -502,4 +511,4 @@ const PresentationFocusLayout = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default PresentationFocusLayout;
|
||||
export default PresentationFocusLayout;
|
@ -41,8 +41,10 @@ const SmartLayout = (props) => {
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
|
||||
const throttledCalculatesLayout = throttle(() => calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
const throttledCalculatesLayout = throttle(() => calculatesLayout(), 50, {
|
||||
trailing: true,
|
||||
leading: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
@ -69,82 +71,89 @@ const SmartLayout = (props) => {
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
const init = () => {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: externalVideoInput.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: screenShareInput.hasScreenShare,
|
||||
width: screenShareInput.width,
|
||||
height: screenShareInput.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: externalVideoInput.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: screenShareInput.hasScreenShare,
|
||||
width: screenShareInput.width,
|
||||
height: screenShareInput.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: externalVideoInput.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: screenShareInput.hasScreenShare,
|
||||
width: screenShareInput.width,
|
||||
height: screenShareInput.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: externalVideoInput.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: screenShareInput.hasScreenShare,
|
||||
width: screenShareInput.width,
|
||||
height: screenShareInput.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotesInput.isPinned,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
}
|
||||
Session.set('layoutReady', true);
|
||||
@ -181,13 +190,14 @@ const SmartLayout = (props) => {
|
||||
|
||||
cameraDockBounds.isCameraHorizontal = false;
|
||||
|
||||
const mediaBoundsWidth = (mediaBounds.width > presentationToolbarMinWidth && !isMobile)
|
||||
? mediaBounds.width
|
||||
: presentationToolbarMinWidth;
|
||||
const mediaBoundsWidth =
|
||||
mediaBounds.width > presentationToolbarMinWidth && !isMobile
|
||||
? mediaBounds.width
|
||||
: presentationToolbarMinWidth;
|
||||
|
||||
cameraDockBounds.top = navBarHeight;
|
||||
cameraDockBounds.left = mediaAreaBounds.left;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + camerasMargin * 2 : null;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
|
||||
if (mediaBounds.width < mediaAreaBounds.width) {
|
||||
@ -197,7 +207,7 @@ const SmartLayout = (props) => {
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.left += camerasMargin;
|
||||
cameraDockBounds.width -= (camerasMargin * 2);
|
||||
cameraDockBounds.width -= camerasMargin * 2;
|
||||
cameraDockBounds.isCameraHorizontal = true;
|
||||
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_LEFT;
|
||||
// button size in vertical position
|
||||
@ -208,7 +218,7 @@ const SmartLayout = (props) => {
|
||||
cameraDockBounds.height = mediaAreaBounds.height - mediaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
cameraDockBounds.top += camerasMargin;
|
||||
cameraDockBounds.height -= (camerasMargin * 2);
|
||||
cameraDockBounds.height -= camerasMargin * 2;
|
||||
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_TOP;
|
||||
}
|
||||
|
||||
@ -265,16 +275,17 @@ const SmartLayout = (props) => {
|
||||
width: screeShareWidth,
|
||||
height: screeShareHeight,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const calculatesMediaBounds = (mediaAreaBounds, slideSize, sidebarSize, screenShareSize) => {
|
||||
const { isOpen, slidesLength } = presentationInput;
|
||||
const { hasExternalVideo } = externalVideoInput;
|
||||
const { hasScreenShare } = screenShareInput;
|
||||
const { isPinned: isSharedNotesPinned } = sharedNotesInput;
|
||||
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0
|
||||
const isGeneralMediaOff = !hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0;
|
||||
const isGeneralMediaOff =
|
||||
!hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
@ -289,7 +300,11 @@ const SmartLayout = (props) => {
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
if (fullscreenElement === 'Presentation' || fullscreenElement === 'Screenshare' || fullscreenElement === 'ExternalVideo') {
|
||||
if (
|
||||
fullscreenElement === 'Presentation' ||
|
||||
fullscreenElement === 'Screenshare' ||
|
||||
fullscreenElement === 'ExternalVideo'
|
||||
) {
|
||||
mediaBounds.width = windowWidth();
|
||||
mediaBounds.height = windowHeight();
|
||||
mediaBounds.top = 0;
|
||||
@ -304,26 +319,24 @@ const SmartLayout = (props) => {
|
||||
if (cameraDockInput.numCameras > 0 && !cameraDockInput.isDragging) {
|
||||
if (mediaContentSize.width !== 0 && mediaContentSize.height !== 0 && !hasExternalVideo) {
|
||||
if (mediaContentSize.width < mediaAreaBounds.width && !isMobile) {
|
||||
if (mediaContentSize.width < (mediaAreaBounds.width * 0.8)) {
|
||||
if (mediaContentSize.width < mediaAreaBounds.width * 0.8) {
|
||||
mediaBounds.width = mediaContentSize.width;
|
||||
} else {
|
||||
mediaBounds.width = mediaAreaBounds.width * 0.8;
|
||||
}
|
||||
mediaBounds.height = mediaAreaBounds.height;
|
||||
mediaBounds.top = mediaAreaBounds.top;
|
||||
const sizeValue = mediaAreaBounds.left
|
||||
+ (mediaAreaBounds.width - mediaBounds.width);
|
||||
const sizeValue = mediaAreaBounds.left + (mediaAreaBounds.width - mediaBounds.width);
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
} else {
|
||||
if (mediaContentSize.height < (mediaAreaBounds.height * 0.8)) {
|
||||
if (mediaContentSize.height < mediaAreaBounds.height * 0.8) {
|
||||
mediaBounds.height = mediaContentSize.height;
|
||||
} else {
|
||||
mediaBounds.height = mediaAreaBounds.height * 0.8;
|
||||
}
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.top = mediaAreaBounds.top
|
||||
+ (mediaAreaBounds.height - mediaBounds.height);
|
||||
mediaBounds.top = mediaAreaBounds.top + (mediaAreaBounds.height - mediaBounds.height);
|
||||
const sizeValue = mediaAreaBounds.left;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
@ -331,8 +344,7 @@ const SmartLayout = (props) => {
|
||||
} else {
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.height = mediaAreaBounds.height * 0.8;
|
||||
mediaBounds.top = mediaAreaBounds.top
|
||||
+ (mediaAreaBounds.height - mediaBounds.height);
|
||||
mediaBounds.top = mediaAreaBounds.top + (mediaAreaBounds.height - mediaBounds.height);
|
||||
const sizeValue = mediaAreaBounds.left;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
@ -370,7 +382,10 @@ const SmartLayout = (props) => {
|
||||
const sidebarContentHeight = calculatesSidebarContentHeight();
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width
|
||||
);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const slideSize = calculatesSlideSize(mediaAreaBounds);
|
||||
@ -380,11 +395,11 @@ const SmartLayout = (props) => {
|
||||
mediaAreaBounds,
|
||||
slideSize,
|
||||
sidebarSize,
|
||||
screenShareSize,
|
||||
screenShareSize
|
||||
);
|
||||
const cameraDockBounds = calculatesCameraDockBounds(mediaAreaBounds, mediaBounds, sidebarSize);
|
||||
const horizontalCameraDiff = cameraDockBounds.isCameraHorizontal
|
||||
? cameraDockBounds.width + (camerasMargin * 2)
|
||||
? cameraDockBounds.width + camerasMargin * 2
|
||||
: 0;
|
||||
|
||||
layoutContextDispatch({
|
||||
@ -418,9 +433,9 @@ const SmartLayout = (props) => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAPTIONS_OUTPUT,
|
||||
value: {
|
||||
left: !isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
right: isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
maxWidth: mediaAreaBounds.width - (captionsMargin * 2),
|
||||
left: !isRTL ? sidebarSize + captionsMargin : null,
|
||||
right: isRTL ? sidebarSize + captionsMargin : null,
|
||||
maxWidth: mediaAreaBounds.width - captionsMargin * 2,
|
||||
},
|
||||
});
|
||||
|
||||
@ -522,7 +537,7 @@ const SmartLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
tabOrder: DEFAULT_VALUES.presentationTabOrder,
|
||||
isResizable: false,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
@ -536,7 +551,7 @@ const SmartLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -548,7 +563,7 @@ const SmartLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
|
||||
@ -559,7 +574,7 @@ const SmartLayout = (props) => {
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -567,4 +582,4 @@ const SmartLayout = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default SmartLayout;
|
||||
export default SmartLayout;
|
@ -4,7 +4,7 @@ import {
|
||||
layoutDispatch,
|
||||
layoutSelect,
|
||||
layoutSelectInput,
|
||||
layoutSelectOutput
|
||||
layoutSelectOutput,
|
||||
} from '/imports/ui/components/layout/context';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { INITIAL_INPUT_STATE } from '/imports/ui/components/layout/initState';
|
||||
@ -49,8 +49,10 @@ const VideoFocusLayout = (props) => {
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
|
||||
const throttledCalculatesLayout = throttle(() => calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
const throttledCalculatesLayout = throttle(() => calculatesLayout(), 50, {
|
||||
trailing: true,
|
||||
leading: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
@ -77,53 +79,15 @@ const VideoFocusLayout = (props) => {
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
const init = () => {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
},
|
||||
INITIAL_INPUT_STATE,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen: input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
@ -151,7 +115,45 @@ const VideoFocusLayout = (props) => {
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
},
|
||||
INITIAL_INPUT_STATE,
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
} else {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen:
|
||||
input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: sidebarContentPanel !== PANELS.NONE,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: input.externalVideo.hasExternalVideo,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: input.screenShare.hasScreenShare,
|
||||
width: input.screenShare.width,
|
||||
height: input.screenShare.height,
|
||||
},
|
||||
},
|
||||
INITIAL_INPUT_STATE
|
||||
),
|
||||
});
|
||||
}
|
||||
@ -165,8 +167,9 @@ const VideoFocusLayout = (props) => {
|
||||
const { hasScreenShare } = screenShareInput;
|
||||
const { isPinned: isSharedNotesPinned } = sharedNotesInput;
|
||||
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0
|
||||
const isGeneralMediaOff = !hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
const hasPresentation = isPresentationEnabled() && slidesLength !== 0;
|
||||
const isGeneralMediaOff =
|
||||
!hasPresentation && !hasExternalVideo && !hasScreenShare && !isSharedNotesPinned;
|
||||
|
||||
let minHeight = 0;
|
||||
let height = 0;
|
||||
@ -250,12 +253,16 @@ const VideoFocusLayout = (props) => {
|
||||
cameraDockBounds,
|
||||
sidebarNavWidth,
|
||||
sidebarContentWidth,
|
||||
sidebarContentHeight,
|
||||
sidebarContentHeight
|
||||
) => {
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
|
||||
if (fullscreenElement === 'Presentation' || fullscreenElement === 'Screenshare' || fullscreenElement === 'ExternalVideo') {
|
||||
if (
|
||||
fullscreenElement === 'Presentation' ||
|
||||
fullscreenElement === 'Screenshare' ||
|
||||
fullscreenElement === 'ExternalVideo'
|
||||
) {
|
||||
mediaBounds.width = windowWidth();
|
||||
mediaBounds.height = windowHeight();
|
||||
mediaBounds.top = 0;
|
||||
@ -307,7 +314,8 @@ const VideoFocusLayout = (props) => {
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width
|
||||
);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
@ -319,7 +327,7 @@ const VideoFocusLayout = (props) => {
|
||||
cameraDockBounds,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width,
|
||||
sidebarContentHeight.height,
|
||||
sidebarContentHeight.height
|
||||
);
|
||||
|
||||
layoutContextDispatch({
|
||||
@ -353,9 +361,9 @@ const VideoFocusLayout = (props) => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAPTIONS_OUTPUT,
|
||||
value: {
|
||||
left: !isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
right: isRTL ? (sidebarSize + captionsMargin) : null,
|
||||
maxWidth: mediaAreaBounds.width - (captionsMargin * 2),
|
||||
left: !isRTL ? sidebarSize + captionsMargin : null,
|
||||
right: isRTL ? sidebarSize + captionsMargin : null,
|
||||
maxWidth: mediaAreaBounds.width - captionsMargin * 2,
|
||||
},
|
||||
});
|
||||
|
||||
@ -513,4 +521,4 @@ const VideoFocusLayout = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default VideoFocusLayout;
|
||||
export default VideoFocusLayout;
|
@ -2,13 +2,14 @@ import Presentations from '/imports/api/presentations';
|
||||
import { isScreenBroadcasting, isCameraAsContentBroadcasting } from '/imports/ui/components/screenshare/service';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import { isExternalVideoEnabled, isScreenSharingEnabled } from '/imports/ui/services/features';
|
||||
import {
|
||||
isExternalVideoEnabled, isScreenSharingEnabled, isCameraAsContentEnabled, isPresentationEnabled,
|
||||
} from '/imports/ui/services/features';
|
||||
import { ACTIONS } from '../layout/enums';
|
||||
import UserService from '/imports/ui/components/user-list/service';
|
||||
import NotesService from '/imports/ui/components/notes/service';
|
||||
import { getVideoUrl } from '/imports/ui/components/external-video-player/service';
|
||||
import VideoStreams from '/imports/api/video-streams';
|
||||
import { isPresentationEnabled } from '/imports/ui/services/features';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
|
||||
const LAYOUT_CONFIG = Meteor.settings.public.layout;
|
||||
@ -31,7 +32,8 @@ function shouldShowWhiteboard() {
|
||||
|
||||
function shouldShowScreenshare() {
|
||||
const { viewScreenshare } = Settings.dataSaving;
|
||||
return isScreenSharingEnabled() && (viewScreenshare || UserService.isUserPresenter())
|
||||
return (isScreenSharingEnabled() || isCameraAsContentEnabled())
|
||||
&& (viewScreenshare || UserService.isUserPresenter())
|
||||
&& (isScreenBroadcasting() || isCameraAsContentBroadcasting());
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ const TalkingIndicatorButton = styled(Button)`
|
||||
font-size: ${fontSizeXS};
|
||||
}
|
||||
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
margin-left: ${talkerMarginSm};
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ const TalkingIndicatorButton = styled(Button)`
|
||||
font-size: ${fontSizeXS};
|
||||
}
|
||||
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
right: calc(${talkerMarginSm} * -1);
|
||||
}
|
||||
}
|
||||
@ -93,7 +93,8 @@ const TalkingIndicatorButton = styled(Button)`
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
${({ $spoke }) => $spoke && `
|
||||
${({ $spoke }) => $spoke
|
||||
&& `
|
||||
opacity: ${spokeOpacity};
|
||||
|
||||
[dir="rtl"] & {
|
||||
@ -101,7 +102,8 @@ const TalkingIndicatorButton = styled(Button)`
|
||||
}
|
||||
`}
|
||||
|
||||
${({ $muted }) => $muted && `
|
||||
${({ $muted }) => $muted
|
||||
&& `
|
||||
cursor: default;
|
||||
|
||||
i {
|
||||
@ -109,7 +111,8 @@ const TalkingIndicatorButton = styled(Button)`
|
||||
}
|
||||
`}
|
||||
|
||||
${({ $isViewer }) => $isViewer && `
|
||||
${({ $isViewer }) => $isViewer
|
||||
&& `
|
||||
cursor: default;
|
||||
`}
|
||||
`;
|
||||
@ -118,19 +121,16 @@ const CCIcon = styled(Icon)`
|
||||
align-self: center;
|
||||
color: ${colorWhite};
|
||||
margin: 0 ${borderRadius};
|
||||
font-size: calc(${fontSizeSmall} * .85);
|
||||
opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`)
|
||||
|| '1;'};
|
||||
font-size: calc(${fontSizeSmall} * 0.85);
|
||||
opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`) || '1;'};
|
||||
`;
|
||||
|
||||
const TalkingIndicatorWrapper = styled.div`
|
||||
border-radius: ${talkerBorderRadius} ${talkerBorderRadius};
|
||||
display: flex;
|
||||
margin: 0 ${borderRadius};
|
||||
opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`)
|
||||
|| '1;'};
|
||||
background: ${({ muted, talking, floor }) => ((muted || !talking || !floor) && `${colorBackground};`)
|
||||
|| `${colorSuccess}`}
|
||||
opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`) || '1;'};
|
||||
background: ${({ muted, talking, floor }) => ((muted || !talking || !floor) && `${colorBackground};`) || `${colorSuccess}`};
|
||||
`;
|
||||
|
||||
const Hidden = styled.div`
|
||||
@ -141,7 +141,6 @@ const IsTalkingWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
margin-top: ${talkerMarginSm};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
@ -65,6 +65,7 @@ const Notes = ({
|
||||
shouldShowSharedNotesOnPresentationArea,
|
||||
}) => {
|
||||
const [shouldRenderNotes, setShouldRenderNotes] = useState(false);
|
||||
|
||||
const { isChrome } = browserInfo;
|
||||
const isOnMediaArea = area === 'media';
|
||||
const style = isOnMediaArea ? {
|
||||
@ -73,9 +74,9 @@ const Notes = ({
|
||||
} : {};
|
||||
|
||||
const isHidden = (isOnMediaArea && (style.width === 0 || style.height === 0))
|
||||
|| (!isToSharedNotesBeShow
|
||||
&& !sidebarContentToIgnoreDelay.includes(sidebarContent.sidebarContentPanel))
|
||||
|| shouldShowSharedNotesOnPresentationArea;
|
||||
|| (!isToSharedNotesBeShow
|
||||
&& !sidebarContentToIgnoreDelay.includes(sidebarContent.sidebarContentPanel))
|
||||
|| shouldShowSharedNotesOnPresentationArea;
|
||||
|
||||
if (isHidden && !isOnMediaArea) {
|
||||
style.padding = 0;
|
||||
@ -89,7 +90,7 @@ const Notes = ({
|
||||
timoutRef = setTimeout(() => {
|
||||
setShouldRenderNotes(false);
|
||||
}, (sidebarContentToIgnoreDelay.includes(sidebarContent.sidebarContentPanel)
|
||||
|| shouldShowSharedNotesOnPresentationArea)
|
||||
|| shouldShowSharedNotesOnPresentationArea)
|
||||
? 0 : DELAY_UNMOUNT_SHARED_NOTES);
|
||||
}
|
||||
return () => clearTimeout(timoutRef);
|
||||
@ -115,7 +116,6 @@ const Notes = ({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
@ -141,17 +141,14 @@ const Notes = ({
|
||||
value: Session.get('presentationLastState'),
|
||||
});
|
||||
};
|
||||
}
|
||||
if(shouldShowSharedNotesOnPresentationArea) {
|
||||
} else {
|
||||
if (shouldShowSharedNotesOnPresentationArea) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NOTES_IS_PINNED,
|
||||
value: true,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderHeaderOnMedia = () => {
|
||||
@ -171,7 +168,11 @@ const Notes = ({
|
||||
};
|
||||
|
||||
return (shouldRenderNotes || shouldShowSharedNotesOnPresentationArea) && (
|
||||
<Styled.Notes data-test="notes" isChrome={isChrome} style={style}>
|
||||
<Styled.Notes
|
||||
data-test="notes"
|
||||
isChrome={isChrome}
|
||||
style={style}
|
||||
>
|
||||
{!isOnMediaArea ? (
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
|
@ -21,6 +21,9 @@ const Notes = styled.div`
|
||||
|
||||
@media ${smallOnly} {
|
||||
transform: none !important;
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -19,6 +19,7 @@ import { colorContentBackground } from '/imports/ui/stylesheets/styled-component
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import { addNewAlert } from '../screenreader-alert/service';
|
||||
import { clearCursors } from '/imports/ui/components/whiteboard/cursors/service';
|
||||
import { debounce } from 'radash';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
presentationLabel: {
|
||||
@ -85,13 +86,13 @@ class Presentation extends PureComponent {
|
||||
|
||||
this.getSvgRef = this.getSvgRef.bind(this);
|
||||
this.setFitToWidth = this.setFitToWidth.bind(this);
|
||||
this.zoomChanger = this.zoomChanger.bind(this);
|
||||
this.zoomChanger = debounce({ delay: 200 }, this.zoomChanger.bind(this));
|
||||
this.updateLocalPosition = this.updateLocalPosition.bind(this);
|
||||
this.panAndZoomChanger = this.panAndZoomChanger.bind(this);
|
||||
this.fitToWidthHandler = this.fitToWidthHandler.bind(this);
|
||||
this.onFullscreenChange = this.onFullscreenChange.bind(this);
|
||||
this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleResize = debounce({ delay: 200 }, this.handleResize.bind(this));
|
||||
this.setTldrawAPI = this.setTldrawAPI.bind(this);
|
||||
this.setIsPanning = this.setIsPanning.bind(this);
|
||||
this.setIsToolbarVisible = this.setIsToolbarVisible.bind(this);
|
||||
|
@ -54,7 +54,7 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploder.upload.413',
|
||||
description: 'error that file exceed the size limit',
|
||||
},
|
||||
IVALID_MIME_TYPE: {
|
||||
INVALID_MIME_TYPE: {
|
||||
id: 'app.presentationUploder.conversion.invalidMimeType',
|
||||
description: 'warns user that the file\'s mime type is not supported or it doesn\'t match the extension',
|
||||
},
|
||||
@ -151,7 +151,7 @@ function renderPresentationItemStatus(item, intl) {
|
||||
case 'PDF_HAS_BIG_PAGE':
|
||||
constraint['0'] = (item.conversion.bigPageSize / 1000 / 1000).toFixed(2);
|
||||
break;
|
||||
case 'IVALID_MIME_TYPE':
|
||||
case 'INVALID_MIME_TYPE':
|
||||
constraint['0'] = item.conversion.fileExtension;
|
||||
constraint['1'] = item.conversion.fileMime;
|
||||
break;
|
||||
|
@ -15,9 +15,9 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploader.disableOriginalPresentationDownload',
|
||||
description: 'Send original presentation to chat',
|
||||
},
|
||||
sendAnnotatedDocument: {
|
||||
id: 'app.presentationUploader.exportAnnotatedPresentation',
|
||||
description: 'Send presentation to chat with annotations label',
|
||||
sendCurrentStateDocument: {
|
||||
id: 'app.presentationUploader.exportCurrentStatePresentation',
|
||||
description: 'Send presentation to chat in the current state label',
|
||||
},
|
||||
copySuccess: {
|
||||
id: 'app.chat.copySuccess',
|
||||
@ -53,7 +53,6 @@ const propTypes = {
|
||||
uploadTimestamp: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
hasAnnotations: PropTypes.bool.isRequired,
|
||||
isRTL: PropTypes.bool.isRequired,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
@ -77,7 +76,6 @@ class PresentationDownloadDropdown extends PureComponent {
|
||||
isDownloadable,
|
||||
item,
|
||||
closeModal,
|
||||
hasAnnotations,
|
||||
} = this.props;
|
||||
|
||||
this.menuItems = [];
|
||||
@ -91,69 +89,51 @@ class PresentationDownloadDropdown extends PureComponent {
|
||||
};
|
||||
|
||||
if (!isDownloadable) {
|
||||
this.menuItems.push(
|
||||
{
|
||||
key: this.actionsKey[0],
|
||||
dataTest: 'enableOriginalPresentationDownload',
|
||||
label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload),
|
||||
onClick: () => toggleDownloadOriginalPresentation(true),
|
||||
},
|
||||
);
|
||||
this.menuItems.push({
|
||||
key: this.actionsKey[0],
|
||||
dataTest: 'enableOriginalPresentationDownload',
|
||||
label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload),
|
||||
onClick: () => toggleDownloadOriginalPresentation(true),
|
||||
});
|
||||
} else {
|
||||
this.menuItems.push(
|
||||
{
|
||||
key: this.actionsKey[0],
|
||||
dataTest: 'disableOriginalPresentationDownload',
|
||||
label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload),
|
||||
onClick: () => toggleDownloadOriginalPresentation(false),
|
||||
},
|
||||
);
|
||||
this.menuItems.push({
|
||||
key: this.actionsKey[0],
|
||||
dataTest: 'disableOriginalPresentationDownload',
|
||||
label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload),
|
||||
onClick: () => toggleDownloadOriginalPresentation(false),
|
||||
});
|
||||
}
|
||||
|
||||
if (hasAnnotations) {
|
||||
this.menuItems.push(
|
||||
{
|
||||
key: this.actionsKey[1],
|
||||
id: 'sendAnnotatedDocument',
|
||||
dataTest: 'sendAnnotatedDocument',
|
||||
label: intl.formatMessage(intlMessages.sendAnnotatedDocument),
|
||||
onClick: () => {
|
||||
closeModal();
|
||||
handleDownloadingOfPresentation('Annotated');
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.menuItems.push({
|
||||
key: this.actionsKey[1],
|
||||
id: 'sendCurrentStateDocument',
|
||||
dataTest: 'sendCurrentStateDocument',
|
||||
label: intl.formatMessage(intlMessages.sendCurrentStateDocument),
|
||||
onClick: () => {
|
||||
closeModal();
|
||||
handleDownloadingOfPresentation('Annotated');
|
||||
},
|
||||
});
|
||||
return this.menuItems;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
isRTL,
|
||||
disabled,
|
||||
} = this.props;
|
||||
const { intl, isRTL, disabled } = this.props;
|
||||
|
||||
const customStyles = { zIndex: 9999 };
|
||||
|
||||
return (
|
||||
<PresentationDownloadDropdownWrapper
|
||||
disabled={disabled}
|
||||
>
|
||||
<PresentationDownloadDropdownWrapper disabled={disabled}>
|
||||
<BBBMenu
|
||||
customStyles={customStyles}
|
||||
trigger={
|
||||
(
|
||||
<Trigger
|
||||
data-test="presentationOptionsDownload"
|
||||
icon="more"
|
||||
label={intl.formatMessage(intlMessages.options)}
|
||||
aria-label={intl.formatMessage(intlMessages.options)}
|
||||
onClick={() => null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
trigger={(
|
||||
<Trigger
|
||||
data-test="presentationOptionsDownload"
|
||||
icon="more"
|
||||
label={intl.formatMessage(intlMessages.options)}
|
||||
aria-label={intl.formatMessage(intlMessages.options)}
|
||||
onClick={() => null}
|
||||
/>
|
||||
)}
|
||||
opts={{
|
||||
id: 'presentation-download-dropdown',
|
||||
keepMounted: true,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user