Merge branch 'v2.6.x-release' of github.com:bigbluebutton/bigbluebutton into merge-260a3
This commit is contained in:
commit
c1379bde13
19
.github/workflows/automated-tests.yml
vendored
19
.github/workflows/automated-tests.yml
vendored
@ -114,7 +114,7 @@ jobs:
|
||||
run: |
|
||||
sudo sh -c '
|
||||
cd /root/ && wget -q https://ubuntu.bigbluebutton.org/bbb-install-2.6.sh -O bbb-install.sh
|
||||
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-26-dev -s bbb-ci.test -d /certs/
|
||||
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-26-dev -s bbb-ci.test -j -d /certs/
|
||||
bbb-conf --salt bbbci
|
||||
echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf
|
||||
bbb-conf --restart
|
||||
@ -134,7 +134,22 @@ jobs:
|
||||
ACTIONS_RUNNER_DEBUG: true
|
||||
BBB_URL: https://bbb-ci.test/bigbluebutton/api
|
||||
BBB_SECRET: bbbci
|
||||
run: npm run test-ci
|
||||
run: npm run test-chromium-ci
|
||||
- name: Run Firefox tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'test Firefox')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'Test Firefox') }}
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
|
||||
ACTIONS_RUNNER_DEBUG: true
|
||||
BBB_URL: https://bbb-ci.test/bigbluebutton/api
|
||||
BBB_SECRET: bbbci
|
||||
# patch playwright's firefox so that it uses the system's root certificate authority
|
||||
run: |
|
||||
sh -c '
|
||||
find $HOME/.cache/ms-playwright -name libnssckbi.so -exec rm {} \; -exec ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so {} \;
|
||||
npm run test-firefox-ci
|
||||
'
|
||||
- if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
@ -20,8 +20,8 @@ trait PreuploadedPresentationsPubMsgHdlr {
|
||||
val pages = new collection.mutable.HashMap[String, PageVO]()
|
||||
|
||||
pres.pages.foreach { p =>
|
||||
val page = new PageVO(p.id, p.num, p.thumbUri, p.swfUri, p.txtUri, p.svgUri,
|
||||
p.current, p.xCamera, p.yCamera, p.zoom)
|
||||
val page = new PageVO(p.id, p.num, p.thumbUri, p.swfUri, p.txtUri, p.svgUri, p.current, p.xOffset, p.yOffset,
|
||||
p.widthRatio, p.heightRatio)
|
||||
pages += page.id -> page
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ trait PresentationPageCountErrorPubMsgHdlr {
|
||||
liveMeeting.props.meetingProp.intId, msg.header.userId
|
||||
)
|
||||
|
||||
val body = PresentationPageCountErrorEvtMsgBody(msg.body.podId, msg.body.messageKey, msg.body.code, msg.body.presentationId, msg.body.numberOfPages, msg.body.maxNumberPages, msg.body.presName)
|
||||
val body = PresentationPageCountErrorEvtMsgBody(msg.body.podId, msg.body.messageKey, msg.body.code, msg.body.presentationId, msg.body.numberOfPages, msg.body.maxNumberPages, msg.body.presName, msg.body.temporaryPresentationId)
|
||||
val event = PresentationPageCountErrorEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
|
@ -51,9 +51,10 @@ object PresentationPodsApp {
|
||||
txtUri = page.urls.getOrElse("text", ""),
|
||||
svgUri = page.urls.getOrElse("svg", ""),
|
||||
current = page.current,
|
||||
xCamera = page.xCamera,
|
||||
yCamera = page.yCamera,
|
||||
zoom = page.zoom,
|
||||
xOffset = page.xOffset,
|
||||
yOffset = page.yOffset,
|
||||
widthRatio = page.widthRatio,
|
||||
heightRatio = page.heightRatio
|
||||
)
|
||||
}
|
||||
|
||||
@ -83,9 +84,10 @@ object PresentationPodsApp {
|
||||
txtUri = page.urls.getOrElse("text", ""),
|
||||
svgUri = page.urls.getOrElse("svg", ""),
|
||||
current = page.current,
|
||||
xCamera = page.xCamera,
|
||||
yCamera = page.yCamera,
|
||||
zoom = page.zoom
|
||||
xOffset = page.xOffset,
|
||||
yOffset = page.yOffset,
|
||||
widthRatio = page.widthRatio,
|
||||
heightRatio = page.heightRatio
|
||||
)
|
||||
}
|
||||
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable, pres.removable)
|
||||
|
@ -19,7 +19,7 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
||||
val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PresentationUploadTokenPassRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.tmpPresId)
|
||||
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.temporaryPresentationId)
|
||||
val event = PresentationUploadTokenPassRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
|
@ -60,12 +60,13 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
val whiteboardId = s"${presId}/${pageNumber.toString}"
|
||||
val presentationPage: PresentationPage = currentPres.get.pages(whiteboardId)
|
||||
val xCamera: Double = presentationPage.xCamera
|
||||
val yCamera: Double = presentationPage.yCamera
|
||||
val zoom: Double = presentationPage.zoom
|
||||
val xOffset: Double = presentationPage.xOffset
|
||||
val yOffset: Double = presentationPage.yOffset
|
||||
val widthRatio: Double = presentationPage.widthRatio
|
||||
val heightRatio: Double = presentationPage.heightRatio
|
||||
val whiteboardHistory: Array[AnnotationVO] = liveMeeting.wbModel.getHistory(whiteboardId)
|
||||
|
||||
val page = new PresentationPageForExport(pageNumber, xCamera, yCamera, zoom, whiteboardHistory)
|
||||
val page = new PresentationPageForExport(pageNumber, xOffset, yOffset, widthRatio, heightRatio, whiteboardHistory)
|
||||
getPresentationPagesForExport(pages, pageCount, presId, currentPres, liveMeeting, storeAnnotationPages :+ page)
|
||||
} else {
|
||||
getPresentationPagesForExport(pages, pageCount, presId, currentPres, liveMeeting, storeAnnotationPages)
|
||||
|
@ -25,7 +25,7 @@ trait ResizeAndMovePagePubMsgHdlr extends RightsManagementTrait {
|
||||
)
|
||||
|
||||
val body = ResizeAndMovePageEvtMsgBody(podId, msg.body.presentationId, page.id,
|
||||
page.xCamera, page.yCamera, page.zoom)
|
||||
page.xOffset, page.yOffset, page.widthRatio, page.heightRatio)
|
||||
val event = ResizeAndMovePageEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
@ -41,13 +41,14 @@ trait ResizeAndMovePagePubMsgHdlr extends RightsManagementTrait {
|
||||
val podId: String = msg.body.podId
|
||||
val presentationId: String = msg.body.presentationId
|
||||
val pageId: String = msg.body.pageId
|
||||
val xCamera: Double = msg.body.xCamera
|
||||
val yCamera: Double = msg.body.yCamera
|
||||
val zoom: Double = msg.body.zoom
|
||||
val xOffset: Double = msg.body.xOffset
|
||||
val yOffset: Double = msg.body.yOffset
|
||||
val widthRatio: Double = msg.body.widthRatio
|
||||
val heightRatio: Double = msg.body.heightRatio
|
||||
|
||||
val newState = for {
|
||||
pod <- PresentationPodsApp.getPresentationPodIfPresenter(state, podId, msg.header.userId)
|
||||
(updatedPod, page) <- pod.resizePage(presentationId, pageId, xCamera, yCamera, zoom)
|
||||
(updatedPod, page) <- pod.resizePage(presentationId, pageId, xOffset, yOffset, widthRatio, heightRatio)
|
||||
} yield {
|
||||
broadcastEvent(msg, podId, page)
|
||||
|
||||
|
@ -25,9 +25,10 @@ case class PresentationPage(
|
||||
num: Int,
|
||||
urls: Map[String, String],
|
||||
current: Boolean = false,
|
||||
xCamera: Double = 0,
|
||||
yCamera: Double = 0,
|
||||
zoom: Double = 0D,
|
||||
xOffset: Double = 0,
|
||||
yOffset: Double = 0,
|
||||
widthRatio: Double = 100D,
|
||||
heightRatio: Double = 100D
|
||||
)
|
||||
|
||||
object PresentationInPod {
|
||||
@ -155,14 +156,22 @@ case class PresentationPod(id: String, currentPresenter: String,
|
||||
}
|
||||
|
||||
def resizePage(presentationId: String, pageId: String,
|
||||
xCamera: Double, yCamera: Double,
|
||||
zoom: Double): Option[(PresentationPod, PresentationPage)] = {
|
||||
xOffset: Double, yOffset: Double, widthRatio: Double,
|
||||
heightRatio: Double): Option[(PresentationPod, PresentationPage)] = {
|
||||
// Force coordinate that are out-of-bounds inside valid values
|
||||
// 0.25D is 400% zoom
|
||||
// 100D-checkedWidth is the maximum the page can be moved over
|
||||
val checkedWidth = Math.min(widthRatio, 100D) //if (widthRatio <= 100D) widthRatio else 100D
|
||||
val checkedHeight = Math.min(heightRatio, 100D)
|
||||
val checkedXOffset = Math.min(xOffset, 0D)
|
||||
val checkedYOffset = Math.min(yOffset, 0D)
|
||||
|
||||
for {
|
||||
pres <- presentations.get(presentationId)
|
||||
page <- pres.pages.get(pageId)
|
||||
} yield {
|
||||
val nPage = page.copy(xCamera = xCamera, yCamera = yCamera,
|
||||
zoom = zoom)
|
||||
val nPage = page.copy(xOffset = checkedXOffset, yOffset = checkedYOffset,
|
||||
widthRatio = checkedWidth, heightRatio = checkedHeight)
|
||||
val nPages = pres.pages + (nPage.id -> nPage)
|
||||
val newPres = pres.copy(pages = nPages)
|
||||
(addPresentation(newPres), nPage)
|
||||
|
@ -1,54 +0,0 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.core.record.events
|
||||
|
||||
class TldrawCameraChangedRecordEvent extends AbstractPresentationRecordEvent {
|
||||
import TldrawCameraChangedRecordEvent._
|
||||
|
||||
setEvent("TldrawCameraChangedEvent")
|
||||
|
||||
def setPresentationName(name: String) {
|
||||
eventMap.put(PRES_NAME, name)
|
||||
}
|
||||
|
||||
def setId(id: String) {
|
||||
eventMap.put(ID, id)
|
||||
}
|
||||
|
||||
def setXCamera(xCamera: Double) {
|
||||
eventMap.put(X_CAMERA, xCamera.toString)
|
||||
}
|
||||
|
||||
def setYCamera(yCamera: Double) {
|
||||
eventMap.put(Y_CAMERA, yCamera.toString)
|
||||
}
|
||||
|
||||
def setZoom(zoom: Double) {
|
||||
eventMap.put(ZOOM, zoom.toString)
|
||||
}
|
||||
}
|
||||
|
||||
object TldrawCameraChangedRecordEvent {
|
||||
protected final val PRES_NAME = "presentationName"
|
||||
protected final val ID = "id"
|
||||
protected final val X_CAMERA = "xCamera"
|
||||
protected final val Y_CAMERA = "yCamera"
|
||||
protected final val ZOOM = "zoom"
|
||||
}
|
@ -156,6 +156,15 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
|
||||
case UpdateExternalVideoEvtMsg.NAME =>
|
||||
msgSender.send("from-akka-apps-frontend-redis-channel", json)
|
||||
|
||||
case NotifyAllInMeetingEvtMsg.NAME =>
|
||||
msgSender.send("from-akka-apps-frontend-redis-channel", json)
|
||||
|
||||
case NotifyUserInMeetingEvtMsg.NAME =>
|
||||
msgSender.send("from-akka-apps-frontend-redis-channel", json)
|
||||
|
||||
case NotifyRoleInMeetingEvtMsg.NAME =>
|
||||
msgSender.send("from-akka-apps-frontend-redis-channel", json)
|
||||
|
||||
case _ =>
|
||||
msgSender.send(fromAkkaAppsRedisChannel, json)
|
||||
}
|
||||
|
@ -187,14 +187,15 @@ class RedisRecorderActor(
|
||||
}
|
||||
|
||||
private def handleResizeAndMovePageEvtMsg(msg: ResizeAndMovePageEvtMsg) {
|
||||
val ev = new TldrawCameraChangedRecordEvent()
|
||||
val ev = new ResizeAndMoveSlideRecordEvent()
|
||||
ev.setMeetingId(msg.header.meetingId)
|
||||
ev.setPodId(msg.body.podId)
|
||||
ev.setPresentationName(msg.body.presentationId)
|
||||
ev.setId(msg.body.pageId)
|
||||
ev.setXCamera(msg.body.xCamera)
|
||||
ev.setYCamera(msg.body.yCamera)
|
||||
ev.setZoom(msg.body.zoom)
|
||||
ev.setXOffset(msg.body.xOffset)
|
||||
ev.setYOffset(msg.body.yOffset)
|
||||
ev.setWidthRatio(msg.body.widthRatio)
|
||||
ev.setHeightRatio(msg.body.heightRatio)
|
||||
|
||||
record(msg.header.meetingId, ev.toMap.asJava)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
[Unit]
|
||||
Description=BigBlueButton Apps (Akka)
|
||||
Requires=network.target
|
||||
Wants=redis-server.service
|
||||
After=redis-server.service
|
||||
Wants=redis-server.service bbb-fsesl-akka.service
|
||||
After=redis-server.service bbb-fsesl-akka.service
|
||||
PartOf=bigbluebutton.target
|
||||
|
||||
[Service]
|
||||
@ -13,7 +13,7 @@ ExecStart=/usr/share/bbb-apps-akka/bin/bbb-apps-akka
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
RestartSec=60
|
||||
SuccessExitStatus=
|
||||
SuccessExitStatus=143
|
||||
TimeoutStopSec=5
|
||||
User=bigbluebutton
|
||||
ExecStartPre=/bin/mkdir -p /run/bbb-apps-akka
|
||||
|
@ -1,8 +1,8 @@
|
||||
[Unit]
|
||||
Description=BigBlueButton FS-ESL (Akka)
|
||||
Requires=network.target
|
||||
Wants=redis-server.service
|
||||
After=redis-server.service
|
||||
Wants=redis-server.service freeswitch.service
|
||||
After=redis-server.service freeswitch.service
|
||||
PartOf= bigbluebutton.target
|
||||
|
||||
[Service]
|
||||
@ -13,7 +13,7 @@ ExecStart=/usr/share/bbb-fsesl-akka/bin/bbb-fsesl-akka
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
RestartSec=60
|
||||
SuccessExitStatus=
|
||||
SuccessExitStatus=143
|
||||
TimeoutStopSec=5
|
||||
User=bigbluebutton
|
||||
ExecStartPre=/bin/mkdir -p /run/bbb-fsesl-akka
|
||||
|
@ -9,14 +9,16 @@ case class DurationProps(duration: Int, createdTime: Long, createdDate: String,
|
||||
endWhenNoModerator: Boolean, endWhenNoModeratorDelayInMinutes: Int)
|
||||
|
||||
case class MeetingProp(
|
||||
name: String,
|
||||
extId: String,
|
||||
intId: String,
|
||||
meetingCameraCap: Int,
|
||||
maxPinnedCameras: Int,
|
||||
isBreakout: Boolean,
|
||||
disabledFeatures: Vector[String],
|
||||
notifyRecordingIsOn: Boolean,
|
||||
name: String,
|
||||
extId: String,
|
||||
intId: String,
|
||||
meetingCameraCap: Int,
|
||||
maxPinnedCameras: Int,
|
||||
isBreakout: Boolean,
|
||||
disabledFeatures: Vector[String],
|
||||
notifyRecordingIsOn: Boolean,
|
||||
uploadExternalDescription: String,
|
||||
uploadExternalUrl: String,
|
||||
)
|
||||
|
||||
case class BreakoutProps(
|
||||
|
@ -4,8 +4,8 @@ case class PresentationVO(id: String, temporaryPresentationId: String, name: Str
|
||||
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean)
|
||||
|
||||
case class PageVO(id: String, num: Int, thumbUri: String = "", swfUri: String,
|
||||
txtUri: String, svgUri: String, current: Boolean = false, xCamera: Double = 0,
|
||||
yCamera: Double = 0, zoom: Double = 1D)
|
||||
txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0,
|
||||
yOffset: Double = 0, widthRatio: Double = 100D, heightRatio: Double = 100D)
|
||||
|
||||
case class PresentationPodVO(id: String, currentPresenter: String,
|
||||
presentations: Vector[PresentationVO])
|
||||
@ -18,11 +18,12 @@ case class PresentationPageConvertedVO(
|
||||
)
|
||||
|
||||
case class PresentationPageVO(
|
||||
id: String,
|
||||
num: Int,
|
||||
urls: Map[String, String],
|
||||
current: Boolean = false,
|
||||
xCamera: Double = 0,
|
||||
yCamera: Double = 0,
|
||||
zoom: Double = 1D
|
||||
id: String,
|
||||
num: Int,
|
||||
urls: Map[String, String],
|
||||
current: Boolean = false,
|
||||
xOffset: Double = 0,
|
||||
yOffset: Double = 0,
|
||||
widthRatio: Double = 100D,
|
||||
heightRatio: Double = 100D
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ case class RemovePresentationPodPubMsgBody(podId: String)
|
||||
|
||||
object PresentationUploadTokenReqMsg { val NAME = "PresentationUploadTokenReqMsg" }
|
||||
case class PresentationUploadTokenReqMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenReqMsgBody) extends StandardMsg
|
||||
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, tmpPresId: String)
|
||||
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, temporaryPresentationId: String)
|
||||
|
||||
object GetAllPresentationPodsReqMsg { val NAME = "GetAllPresentationPodsReqMsg" }
|
||||
case class GetAllPresentationPodsReqMsg(header: BbbClientMsgHeader, body: GetAllPresentationPodsReqMsgBody) extends StandardMsg
|
||||
@ -37,8 +37,8 @@ case class SetPresentationDownloadablePubMsgBody(podId: String, presentationId:
|
||||
|
||||
object ResizeAndMovePagePubMsg { val NAME = "ResizeAndMovePagePubMsg" }
|
||||
case class ResizeAndMovePagePubMsg(header: BbbClientMsgHeader, body: ResizeAndMovePagePubMsgBody) extends StandardMsg
|
||||
case class ResizeAndMovePagePubMsgBody(podId: String, presentationId: String, pageId: String, xCamera: Double,
|
||||
yCamera: Double, zoom: Double)
|
||||
case class ResizeAndMovePagePubMsgBody(podId: String, presentationId: String, pageId: String, xOffset: Double,
|
||||
yOffset: Double, widthRatio: Double, heightRatio: Double)
|
||||
|
||||
object SetCurrentPresentationPubMsg { val NAME = "SetCurrentPresentationPubMsg" }
|
||||
case class SetCurrentPresentationPubMsg(header: BbbClientMsgHeader, body: SetCurrentPresentationPubMsgBody) extends StandardMsg
|
||||
@ -65,7 +65,7 @@ case class PresentationPageCountErrorSysPubMsg(
|
||||
body: PresentationPageCountErrorSysPubMsgBody
|
||||
) extends StandardMsg
|
||||
case class PresentationPageCountErrorSysPubMsgBody(podId: String, messageKey: String, code: String, presentationId: String,
|
||||
numberOfPages: Int, maxNumberPages: Int, presName: String)
|
||||
numberOfPages: Int, maxNumberPages: Int, presName: String, temporaryPresentationId: String)
|
||||
|
||||
object PdfConversionInvalidErrorSysPubMsg { val NAME = "PdfConversionInvalidErrorSysPubMsg" }
|
||||
case class PdfConversionInvalidErrorSysPubMsg(
|
||||
@ -182,7 +182,7 @@ case class PdfConversionInvalidErrorEvtMsgBody(podId: String, messageKey: String
|
||||
|
||||
object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" }
|
||||
case class PresentationUploadTokenPassRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenPassRespMsgBody) extends StandardMsg
|
||||
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, tmpPresId: String)
|
||||
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, temporaryPresentationId: String)
|
||||
|
||||
object PresentationUploadTokenFailRespMsg { val NAME = "PresentationUploadTokenFailRespMsg" }
|
||||
case class PresentationUploadTokenFailRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenFailRespMsgBody) extends StandardMsg
|
||||
@ -194,7 +194,7 @@ case class PresentationConversionUpdateEvtMsgBody(podId: String, messageKey: Str
|
||||
|
||||
object PresentationPageCountErrorEvtMsg { val NAME = "PresentationPageCountErrorEvtMsg" }
|
||||
case class PresentationPageCountErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationPageCountErrorEvtMsgBody) extends BbbCoreMsg
|
||||
case class PresentationPageCountErrorEvtMsgBody(podId: String, messageKey: String, code: String, presentationId: String, numberOfPages: Int, maxNumberPages: Int, presName: String)
|
||||
case class PresentationPageCountErrorEvtMsgBody(podId: String, messageKey: String, code: String, presentationId: String, numberOfPages: Int, maxNumberPages: Int, presName: String, temporaryPresentationId: String)
|
||||
|
||||
object PresentationPageGeneratedEvtMsg { val NAME = "PresentationPageGeneratedEvtMsg" }
|
||||
case class PresentationPageGeneratedEvtMsg(header: BbbClientMsgHeader, body: PresentationPageGeneratedEvtMsgBody) extends BbbCoreMsg
|
||||
@ -288,8 +288,8 @@ case class SetPresentationDownloadableEvtMsgBody(podId: String, presentationId:
|
||||
|
||||
object ResizeAndMovePageEvtMsg { val NAME = "ResizeAndMovePageEvtMsg" }
|
||||
case class ResizeAndMovePageEvtMsg(header: BbbClientMsgHeader, body: ResizeAndMovePageEvtMsgBody) extends BbbCoreMsg
|
||||
case class ResizeAndMovePageEvtMsgBody(podId: String, presentationId: String, pageId: String, xCamera: Double,
|
||||
yCamera: Double, zoom: Double)
|
||||
case class ResizeAndMovePageEvtMsgBody(podId: String, presentationId: String, pageId: String, xOffset: Double,
|
||||
yOffset: Double, widthRatio: Double, heightRatio: Double)
|
||||
|
||||
object SetCurrentPresentationEvtMsg { val NAME = "SetCurrentPresentationEvtMsg" }
|
||||
case class SetCurrentPresentationEvtMsg(header: BbbClientMsgHeader, body: SetCurrentPresentationEvtMsgBody) extends BbbCoreMsg
|
||||
|
@ -5,9 +5,10 @@ case class AnnotationVO(id: String, annotationInfo: scala.collection.immutable.M
|
||||
|
||||
case class PresentationPageForExport(
|
||||
page: Int,
|
||||
xCamera: Double,
|
||||
yCamera: Double,
|
||||
zoom: Double,
|
||||
xOffset: Double,
|
||||
yOffset: Double,
|
||||
widthRatio: Double,
|
||||
heightRatio: Double,
|
||||
annotations: Array[AnnotationVO],
|
||||
)
|
||||
|
||||
|
@ -73,6 +73,9 @@ public class ApiParams {
|
||||
public static final String DISABLED_FEATURES = "disabledFeatures";
|
||||
public static final String NOTIFY_RECORDING_IS_ON = "notifyRecordingIsOn";
|
||||
|
||||
public static final String UPLOAD_EXTERNAL_DESCRIPTION = "uploadExternalDescription";
|
||||
public static final String UPLOAD_EXTERNAL_URL = "uploadExternalUrl";
|
||||
|
||||
public static final String BREAKOUT_ROOMS_ENABLED = "breakoutRoomsEnabled";
|
||||
public static final String BREAKOUT_ROOMS_RECORD = "breakoutRoomsRecord";
|
||||
public static final String BREAKOUT_ROOMS_PRIVATE_CHAT_ENABLED = "breakoutRoomsPrivateChatEnabled";
|
||||
|
@ -421,7 +421,8 @@ public class MeetingService implements MessageListener {
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
|
||||
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
|
||||
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
|
||||
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn());
|
||||
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
|
||||
m.getUploadExternalDescription(), m.getUploadExternalUrl());
|
||||
}
|
||||
|
||||
private String formatPrettyDate(Long timestamp) {
|
||||
@ -949,6 +950,13 @@ public class MeetingService implements MessageListener {
|
||||
User user = new User(message.userId, message.externalUserId,
|
||||
message.name, message.role, message.avatarURL, message.guest, message.guestStatus,
|
||||
message.clientType);
|
||||
|
||||
if(m.getMaxUsers() > 0 && m.getUsers().size() >= m.getMaxUsers()) {
|
||||
m.removeEnteredUser(user.getInternalUserId());
|
||||
gw.ejectDuplicateUser(message.meetingId, user.getInternalUserId(), user.getFullname(), user.getExternalUserId());
|
||||
return;
|
||||
}
|
||||
|
||||
m.userJoined(user);
|
||||
m.setGuestStatusWithId(user.getInternalUserId(), message.guestStatus);
|
||||
UserSession userSession = getUserSessionWithUserId(user.getInternalUserId());
|
||||
|
@ -101,6 +101,8 @@ public class ParamsProcessorUtil {
|
||||
private boolean defaultKeepEvents = false;
|
||||
private Boolean useDefaultLogo;
|
||||
private String defaultLogoURL;
|
||||
private String defaultUploadExternalDescription = "";
|
||||
private String defaultUploadExternalUrl = "";
|
||||
|
||||
private boolean defaultBreakoutRoomsEnabled = true;
|
||||
private boolean defaultBreakoutRoomsRecord;
|
||||
@ -618,6 +620,16 @@ public class ParamsProcessorUtil {
|
||||
guestPolicy = params.get(ApiParams.GUEST_POLICY);
|
||||
}
|
||||
|
||||
String uploadExternalDescription = defaultUploadExternalDescription;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.UPLOAD_EXTERNAL_DESCRIPTION))) {
|
||||
uploadExternalDescription = params.get(ApiParams.UPLOAD_EXTERNAL_DESCRIPTION);
|
||||
}
|
||||
|
||||
String uploadExternalUrl = defaultUploadExternalUrl;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.UPLOAD_EXTERNAL_URL))) {
|
||||
uploadExternalUrl = params.get(ApiParams.UPLOAD_EXTERNAL_URL);
|
||||
}
|
||||
|
||||
String meetingLayout = defaultMeetingLayout;
|
||||
|
||||
ArrayList<Group> groups = processGroupsParams(params);
|
||||
@ -701,6 +713,8 @@ public class ParamsProcessorUtil {
|
||||
.withGroups(groups)
|
||||
.withDisabledFeatures(listOfDisabledFeatures)
|
||||
.withNotifyRecordingIsOn(notifyRecordingIsOn)
|
||||
.withUploadExternalDescription(uploadExternalDescription)
|
||||
.withUploadExternalUrl(uploadExternalUrl)
|
||||
.build();
|
||||
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.MODERATOR_ONLY_MESSAGE))) {
|
||||
@ -1369,6 +1383,14 @@ public class ParamsProcessorUtil {
|
||||
this.defaultNotifyRecordingIsOn = notifyRecordingIsOn;
|
||||
}
|
||||
|
||||
public void setUploadExternalDescription(String uploadExternalDescription) {
|
||||
this.defaultUploadExternalDescription = uploadExternalDescription;
|
||||
}
|
||||
|
||||
public void setUploadExternalUrl(String uploadExternalUrl) {
|
||||
this.defaultUploadExternalUrl = uploadExternalUrl;
|
||||
}
|
||||
|
||||
public void setBbbVersion(String version) {
|
||||
this.bbbVersion = this.allowRevealOfBBBVersion ? version : "";
|
||||
}
|
||||
|
@ -95,6 +95,8 @@ public class Meeting {
|
||||
private Boolean allowRequestsWithoutSession = false;
|
||||
private Boolean allowModsToEjectCameras = false;
|
||||
private Boolean meetingKeepEvents;
|
||||
private String uploadExternalDescription;
|
||||
private String uploadExternalUrl;
|
||||
|
||||
private Integer meetingExpireIfNoUserJoinedInMinutes = 5;
|
||||
private Integer meetingExpireWhenLastUserLeftInMinutes = 1;
|
||||
@ -119,6 +121,8 @@ public class Meeting {
|
||||
intMeetingId = builder.internalId;
|
||||
disabledFeatures = builder.disabledFeatures;
|
||||
notifyRecordingIsOn = builder.notifyRecordingIsOn;
|
||||
uploadExternalDescription = builder.uploadExternalDescription;
|
||||
uploadExternalUrl = builder.uploadExternalUrl;
|
||||
if (builder.viewerPass == null){
|
||||
viewerPass = "";
|
||||
} else {
|
||||
@ -380,6 +384,13 @@ public class Meeting {
|
||||
return notifyRecordingIsOn;
|
||||
}
|
||||
|
||||
public String getUploadExternalDescription() {
|
||||
return uploadExternalDescription;
|
||||
}
|
||||
public String getUploadExternalUrl() {
|
||||
return uploadExternalUrl;
|
||||
}
|
||||
|
||||
public String getWelcomeMessageTemplate() {
|
||||
return welcomeMsgTemplate;
|
||||
}
|
||||
@ -810,6 +821,8 @@ public class Meeting {
|
||||
private String learningDashboardAccessToken;
|
||||
private ArrayList<String> disabledFeatures;
|
||||
private Boolean notifyRecordingIsOn;
|
||||
private String uploadExternalDescription;
|
||||
private String uploadExternalUrl;
|
||||
private int duration;
|
||||
private String webVoice;
|
||||
private String telVoice;
|
||||
@ -937,6 +950,16 @@ public class Meeting {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUploadExternalDescription(String d) {
|
||||
this.uploadExternalDescription = d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUploadExternalUrl(String u) {
|
||||
this.uploadExternalUrl = u;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWelcomeMessage(String w) {
|
||||
welcomeMsg = w;
|
||||
return this;
|
||||
|
@ -25,6 +25,8 @@ public class CreateMeetingMessage {
|
||||
public final String learningDashboardAccessToken;
|
||||
public final ArrayList<String> disabledFeatures;
|
||||
public final Boolean notifyRecordingIsOn;
|
||||
public final String uploadExternalDescription;
|
||||
public final String uploadExternalUrl;
|
||||
public final Long createTime;
|
||||
public final String createDate;
|
||||
public final Map<String, String> metadata;
|
||||
@ -36,6 +38,8 @@ public class CreateMeetingMessage {
|
||||
String viewerPass, String learningDashboardAccessToken,
|
||||
ArrayList<String> disabledFeatures,
|
||||
Boolean notifyRecordingIsOn,
|
||||
String uploadExternalDescription,
|
||||
String uploadExternalUrl,
|
||||
Long createTime, String createDate, Map<String, String> metadata) {
|
||||
this.id = id;
|
||||
this.externalId = externalId;
|
||||
@ -54,6 +58,8 @@ public class CreateMeetingMessage {
|
||||
this.learningDashboardAccessToken = learningDashboardAccessToken;
|
||||
this.disabledFeatures = disabledFeatures;
|
||||
this.notifyRecordingIsOn = notifyRecordingIsOn;
|
||||
this.uploadExternalDescription = uploadExternalDescription;
|
||||
this.uploadExternalUrl = uploadExternalUrl;
|
||||
this.createTime = createTime;
|
||||
this.createDate = createDate;
|
||||
this.metadata = metadata;
|
||||
|
@ -24,7 +24,7 @@ public class GuestPolicyValidator implements ConstraintValidator<GuestPolicyCons
|
||||
MeetingService meetingService = ServiceUtils.getMeetingService();
|
||||
UserSession userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
|
||||
|
||||
if(userSession == null || userSession.guestStatus.equals(GuestPolicy.DENY)) {
|
||||
if(userSession == null || !userSession.guestStatus.equals(GuestPolicy.ALLOW)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,8 @@ public class MaxParticipantsValidator implements ConstraintValidator<MaxParticip
|
||||
boolean rejoin = meeting.getUserById(userSession.internalUserId) != null;
|
||||
boolean reenter = meeting.getEnteredUserById(userSession.internalUserId) != null;
|
||||
int joinedUsers = meeting.getUsers().size();
|
||||
int enteredUsers = meeting.getEnteredUsers().size();
|
||||
|
||||
boolean reachedMax = (joinedUsers + enteredUsers) >= maxParticipants;
|
||||
boolean reachedMax = joinedUsers >= maxParticipants;
|
||||
if(enabled && !rejoin && !reenter && reachedMax) {
|
||||
return false;
|
||||
}
|
||||
|
@ -43,7 +43,9 @@ public interface IBbbWebApiGWApp {
|
||||
Integer html5InstanceId,
|
||||
ArrayList<Group> groups,
|
||||
ArrayList<String> disabledFeatures,
|
||||
Boolean notifyRecordingIsOn);
|
||||
Boolean notifyRecordingIsOn,
|
||||
String uploadExternalDescription,
|
||||
String uploadExternalUrl);
|
||||
|
||||
void registerUser(String meetingID, String internalUserId, String fullname, String role,
|
||||
String externUserID, String authToken, String avatarURL,
|
||||
|
@ -23,7 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bigbluebutton.api2.IBbbWebApiGWApp;
|
||||
import org.bigbluebutton.presentation.messages.OfficeDocConversionProgress;
|
||||
import org.bigbluebutton.presentation.messages.DocConversionProgress;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -52,7 +52,7 @@ public class OfficeToPdfConversionSuccessFilter {
|
||||
}
|
||||
|
||||
public void sendProgress(UploadedPresentation pres) {
|
||||
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(),
|
||||
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(),
|
||||
pres.getMeetingId(),
|
||||
pres.getId(),
|
||||
pres.getId(),
|
||||
|
@ -22,7 +22,7 @@ import java.io.File;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.bigbluebutton.api2.IBbbWebApiGWApp;
|
||||
import org.bigbluebutton.presentation.messages.OfficeDocConversionProgress;
|
||||
import org.bigbluebutton.presentation.messages.DocConversionProgress;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -59,7 +59,7 @@ public class SupportedDocumentFilter {
|
||||
}
|
||||
|
||||
if (gw != null) {
|
||||
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
pres.getId(), pres.getId(),
|
||||
pres.getName(), "notUsedYet", "notUsedYet",
|
||||
pres.isDownloadable(), pres.isRemovable(), msgKey);
|
||||
|
@ -206,7 +206,8 @@ public class PresentationFileProcessor {
|
||||
DocPageCountFailed progress = new DocPageCountFailed(pres.getPodId(), pres.getMeetingId(),
|
||||
pres.getId(), pres.getId(),
|
||||
pres.getName(), "notUsedYet", "notUsedYet",
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.PAGE_COUNT_FAILED_KEY);
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.PAGE_COUNT_FAILED_KEY,
|
||||
pres.getTemporaryPresentationId());
|
||||
|
||||
notifier.sendDocConversionProgress(progress);
|
||||
|
||||
@ -232,7 +233,7 @@ public class PresentationFileProcessor {
|
||||
pres.getId(), pres.getId(),
|
||||
pres.getName(), "notUsedYet", "notUsedYet",
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY,
|
||||
e.getPageCount(), e.getMaxNumberOfPages());
|
||||
e.getPageCount(), e.getMaxNumberOfPages(), pres.getTemporaryPresentationId());
|
||||
|
||||
notifier.sendDocConversionProgress(progress);
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public class SwfSlidesGenerationProgressNotifier {
|
||||
}
|
||||
|
||||
public void sendCreatingThumbnailsUpdateMessage(UploadedPresentation pres) {
|
||||
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
pres.getId(), pres.getId(),
|
||||
pres.getName(), "notUsedYet", "notUsedYet",
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.GENERATING_THUMBNAIL_KEY);
|
||||
@ -105,7 +105,7 @@ public class SwfSlidesGenerationProgressNotifier {
|
||||
}
|
||||
|
||||
public void sendCreatingTextFilesUpdateMessage(UploadedPresentation pres) {
|
||||
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
pres.getId(), pres.getId(),
|
||||
pres.getName(), "notUsedYet", "notUsedYet",
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.GENERATING_TEXTFILES_KEY);
|
||||
@ -113,7 +113,7 @@ public class SwfSlidesGenerationProgressNotifier {
|
||||
}
|
||||
|
||||
public void sendCreatingSvgImagesUpdateMessage(UploadedPresentation pres) {
|
||||
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
|
||||
pres.getId(), pres.getId(),
|
||||
pres.getName(), "notUsedYet", "notUsedYet",
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.GENERATING_SVGIMAGES_KEY);
|
||||
|
@ -1,29 +1,29 @@
|
||||
package org.bigbluebutton.presentation.messages;
|
||||
|
||||
public class OfficeDocConversionProgress implements IDocConversionMsg {
|
||||
public final String podId;
|
||||
public final String meetingId;
|
||||
public final String presId;
|
||||
public final String presInstance;
|
||||
public final String filename;
|
||||
public final String uploaderId;
|
||||
public final String authzToken;
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
public final String key;
|
||||
|
||||
public OfficeDocConversionProgress(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable, String key) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
this.presInstance = presInstance;
|
||||
this.filename = filename;
|
||||
this.uploaderId = uploaderId;
|
||||
this.authzToken = authzToken;
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
package org.bigbluebutton.presentation.messages;
|
||||
|
||||
public class DocConversionProgress implements IDocConversionMsg {
|
||||
public final String podId;
|
||||
public final String meetingId;
|
||||
public final String presId;
|
||||
public final String presInstance;
|
||||
public final String filename;
|
||||
public final String uploaderId;
|
||||
public final String authzToken;
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
public final String key;
|
||||
|
||||
public DocConversionProgress(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable, String key) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
this.presInstance = presInstance;
|
||||
this.filename = filename;
|
||||
this.uploaderId = uploaderId;
|
||||
this.authzToken = authzToken;
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
@ -13,11 +13,12 @@ public class DocPageCountExceeded implements IDocConversionMsg {
|
||||
public final String key;
|
||||
public final Integer numPages;
|
||||
public final Integer maxNumPages;
|
||||
public final String temporaryPresentationId;
|
||||
|
||||
public DocPageCountExceeded(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable, String key,
|
||||
Integer numPages, Integer maxNumPages) {
|
||||
Integer numPages, Integer maxNumPages, String temporaryPresentationId) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
@ -30,5 +31,6 @@ public class DocPageCountExceeded implements IDocConversionMsg {
|
||||
this.key = key;
|
||||
this.numPages = numPages;
|
||||
this.maxNumPages = maxNumPages;
|
||||
this.temporaryPresentationId = temporaryPresentationId;
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ public class DocPageCountFailed implements IDocConversionMsg {
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
public final String key;
|
||||
public final String temporaryPresentationId;
|
||||
|
||||
public DocPageCountFailed(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable, String key) {
|
||||
Boolean downloadable, Boolean removable, String key, String temporaryPresentationId) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
@ -25,5 +26,6 @@ public class DocPageCountFailed implements IDocConversionMsg {
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
this.key = key;
|
||||
this.temporaryPresentationId = temporaryPresentationId;
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
package org.bigbluebutton.presentation.messages;
|
||||
|
||||
import org.bigbluebutton.api.messaging.messages.IMessage;
|
||||
|
||||
public class OfficeDocConversionInvalid implements IMessage{
|
||||
public final String podId;
|
||||
public final String meetingId;
|
||||
public final String presId;
|
||||
public final String presInstance;
|
||||
public final String filename;
|
||||
public final String uploaderId;
|
||||
public final String authzToken;
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
|
||||
public OfficeDocConversionInvalid(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
this.presInstance = presInstance;
|
||||
this.filename = filename;
|
||||
this.uploaderId = uploaderId;
|
||||
this.authzToken = authzToken;
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.bigbluebutton.presentation.messages;
|
||||
|
||||
|
||||
import org.bigbluebutton.api.messaging.messages.IMessage;
|
||||
|
||||
public class OfficeDocConversionSuccess implements IMessage{
|
||||
public final String podId;
|
||||
public final String meetingId;
|
||||
public final String presId;
|
||||
public final String presInstance;
|
||||
public final String filename;
|
||||
public final String uploaderId;
|
||||
public final String authzToken;
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
|
||||
public OfficeDocConversionSuccess(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
this.presInstance = presInstance;
|
||||
this.filename = filename;
|
||||
this.uploaderId = uploaderId;
|
||||
this.authzToken = authzToken;
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.bigbluebutton.presentation.messages;
|
||||
|
||||
|
||||
import org.bigbluebutton.api.messaging.messages.IMessage;
|
||||
|
||||
public class OfficeDocConversionSupported implements IMessage{
|
||||
public final String podId;
|
||||
public final String meetingId;
|
||||
public final String presId;
|
||||
public final String presInstance;
|
||||
public final String filename;
|
||||
public final String uploaderId;
|
||||
public final String authzToken;
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
|
||||
public OfficeDocConversionSupported(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
this.presInstance = presInstance;
|
||||
this.filename = filename;
|
||||
this.uploaderId = uploaderId;
|
||||
this.authzToken = authzToken;
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.bigbluebutton.presentation.messages;
|
||||
|
||||
|
||||
import org.bigbluebutton.api.messaging.messages.IMessage;
|
||||
|
||||
public class OfficeDocConversionUnsupported implements IMessage{
|
||||
public final String podId;
|
||||
public final String meetingId;
|
||||
public final String presId;
|
||||
public final String presInstance;
|
||||
public final String filename;
|
||||
public final String uploaderId;
|
||||
public final String authzToken;
|
||||
public final Boolean downloadable;
|
||||
public final Boolean removable;
|
||||
|
||||
public OfficeDocConversionUnsupported(String podId, String meetingId, String presId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
this.presInstance = presInstance;
|
||||
this.filename = filename;
|
||||
this.uploaderId = uploaderId;
|
||||
this.authzToken = authzToken;
|
||||
this.downloadable = downloadable;
|
||||
this.removable = removable;
|
||||
}
|
||||
}
|
@ -148,7 +148,9 @@ class BbbWebApiGWApp(
|
||||
html5InstanceId: java.lang.Integer,
|
||||
groups: java.util.ArrayList[Group],
|
||||
disabledFeatures: java.util.ArrayList[String],
|
||||
notifyRecordingIsOn: java.lang.Boolean): Unit = {
|
||||
notifyRecordingIsOn: java.lang.Boolean,
|
||||
uploadExternalDescription: String,
|
||||
uploadExternalUrl: String): Unit = {
|
||||
|
||||
val disabledFeaturesAsVector: Vector[String] = disabledFeatures.asScala.toVector
|
||||
|
||||
@ -160,7 +162,9 @@ class BbbWebApiGWApp(
|
||||
maxPinnedCameras = maxPinnedCameras.intValue(),
|
||||
isBreakout = isBreakout.booleanValue(),
|
||||
disabledFeaturesAsVector,
|
||||
notifyRecordingIsOn
|
||||
notifyRecordingIsOn,
|
||||
uploadExternalDescription,
|
||||
uploadExternalUrl
|
||||
)
|
||||
|
||||
val durationProps = DurationProps(
|
||||
@ -308,8 +312,8 @@ class BbbWebApiGWApp(
|
||||
// Send new event with page urls
|
||||
val newEvent = MsgBuilder.buildPresentationPageConvertedSysMsg(msg.asInstanceOf[DocPageGeneratedProgress])
|
||||
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, newEvent))
|
||||
} else if (msg.isInstanceOf[OfficeDocConversionProgress]) {
|
||||
val event = MsgBuilder.buildPresentationConversionUpdateSysPubMsg(msg.asInstanceOf[OfficeDocConversionProgress])
|
||||
} else if (msg.isInstanceOf[DocConversionProgress]) {
|
||||
val event = MsgBuilder.buildPresentationConversionUpdateSysPubMsg(msg.asInstanceOf[DocConversionProgress])
|
||||
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
|
||||
} else if (msg.isInstanceOf[DocPageCompletedProgress]) {
|
||||
val event = MsgBuilder.buildPresentationConversionCompletedSysPubMsg(msg.asInstanceOf[DocPageCompletedProgress])
|
||||
|
@ -127,7 +127,7 @@ object MsgBuilder {
|
||||
BbbCommonEnvCoreMsg(envelope, req)
|
||||
}
|
||||
|
||||
def buildPresentationConversionUpdateSysPubMsg(msg: OfficeDocConversionProgress): BbbCommonEnvCoreMsg = {
|
||||
def buildPresentationConversionUpdateSysPubMsg(msg: DocConversionProgress): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-web")
|
||||
val envelope = BbbCoreEnvelope(PresentationConversionUpdateSysPubMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PresentationConversionUpdateSysPubMsg.NAME, msg.meetingId, msg.authzToken)
|
||||
@ -193,7 +193,7 @@ object MsgBuilder {
|
||||
val header = BbbClientMsgHeader(PresentationPageCountErrorSysPubMsg.NAME, msg.meetingId, msg.authzToken)
|
||||
|
||||
val body = PresentationPageCountErrorSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
|
||||
code = msg.key, msg.presId, 0, 0, msg.filename)
|
||||
code = msg.key, msg.presId, 0, 0, msg.filename, msg.temporaryPresentationId)
|
||||
val req = PresentationPageCountErrorSysPubMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, req)
|
||||
}
|
||||
@ -204,7 +204,7 @@ object MsgBuilder {
|
||||
val header = BbbClientMsgHeader(PresentationPageCountErrorSysPubMsg.NAME, msg.meetingId, msg.authzToken)
|
||||
|
||||
val body = PresentationPageCountErrorSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
|
||||
code = msg.key, msg.presId, msg.numPages.intValue(), msg.maxNumPages.intValue(), msg.filename)
|
||||
code = msg.key, msg.presId, msg.numPages.intValue(), msg.maxNumPages.intValue(), msg.filename, msg.temporaryPresentationId)
|
||||
val req = PresentationPageCountErrorSysPubMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, req)
|
||||
}
|
||||
|
@ -22,14 +22,9 @@
|
||||
"notifier": {
|
||||
"pod_id": "DEFAULT_PRESENTATION_POD",
|
||||
"is_downloadable": "false",
|
||||
"msgName": "NewPresAnnFileAvailableMsg",
|
||||
"protocol": "https",
|
||||
"host": "localhost"
|
||||
},
|
||||
"bbbWeb": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 8090
|
||||
"msgName": "NewPresAnnFileAvailableMsg"
|
||||
},
|
||||
"bbbWebAPI": "http://127.0.0.1:8090",
|
||||
"redis": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 6379,
|
||||
|
@ -2,7 +2,7 @@ const Logger = require('../lib/utils/logger');
|
||||
const config = require('../config');
|
||||
const fs = require('fs');
|
||||
const redis = require('redis');
|
||||
const {Worker, workerData, parentPort} = require('worker_threads');
|
||||
const {Worker, workerData} = require('worker_threads');
|
||||
const path = require('path');
|
||||
const cp = require('child_process');
|
||||
|
||||
@ -100,5 +100,3 @@ const exportJob = JSON.parse(job);
|
||||
|
||||
kickOffProcessWorker(exportJob.jobId);
|
||||
})();
|
||||
|
||||
parentPort.postMessage({message: workerData});
|
||||
|
@ -6,7 +6,7 @@ const redis = require('redis');
|
||||
const axios = require('axios').default;
|
||||
const path = require('path');
|
||||
|
||||
const {workerData, parentPort} = require('worker_threads');
|
||||
const {workerData} = require('worker_threads');
|
||||
|
||||
const [jobType, jobId, filename_with_extension] = workerData;
|
||||
|
||||
@ -27,7 +27,11 @@ async function notifyMeetingActor() {
|
||||
|
||||
await client.connect();
|
||||
client.on('error', (err) => logger.info('Redis Client Error', err));
|
||||
const link = `${config.notifier.protocol}://${config.notifier.host}/bigbluebutton/presentation/${exportJob.parentMeetingId}/${exportJob.parentMeetingId}/${exportJob.presId}/pdf/${jobId}/${filename_with_extension}`;
|
||||
|
||||
const link = path.join(`${path.sep}bigbluebutton`, 'presentation',
|
||||
exportJob.parentMeetingId, exportJob.parentMeetingId,
|
||||
exportJob.presId, 'pdf', jobId, filename_with_extension);
|
||||
|
||||
const notification = {
|
||||
envelope: {
|
||||
name: config.notifier.msgName,
|
||||
@ -57,7 +61,7 @@ async function notifyMeetingActor() {
|
||||
|
||||
/** Upload PDF to a BBB room */
|
||||
async function upload() {
|
||||
const callbackUrl = `http://${config.bbbWeb.host}:${config.bbbWeb.port}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload`;
|
||||
const callbackUrl = `${config.bbbWebAPI}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload`;
|
||||
const formData = new FormData();
|
||||
const file = `${exportJob.presLocation}/pdfs/${jobId}/${filename_with_extension}`;
|
||||
|
||||
@ -86,5 +90,3 @@ fs.rm(dropbox, {recursive: true}, (err) => {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
parentPort.postMessage({message: workerData});
|
||||
|
@ -3,7 +3,7 @@ const config = require('../config');
|
||||
const fs = require('fs');
|
||||
const {create} = require('xmlbuilder2', {encoding: 'utf-8'});
|
||||
const cp = require('child_process');
|
||||
const {Worker, workerData, parentPort} = require('worker_threads');
|
||||
const {Worker, workerData} = require('worker_threads');
|
||||
const path = require('path');
|
||||
const sanitize = require('sanitize-filename');
|
||||
const {getStroke, getStrokePoints} = require('perfect-freehand');
|
||||
@ -878,5 +878,3 @@ cp.spawnSync(config.shared.ghostscript, mergePDFs, {shell: false});
|
||||
logger.info(`Saved PDF at ${outputDir}/${jobId}/${filename_with_extension}`);
|
||||
|
||||
kickOffNotifierWorker(exportJob.jobType, filename_with_extension);
|
||||
|
||||
parentPort.postMessage({message: workerData});
|
||||
|
12
bbb-learning-dashboard/package-lock.json
generated
12
bbb-learning-dashboard/package-lock.json
generated
@ -16930,9 +16930,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.14.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
|
||||
"integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
|
||||
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
@ -30717,9 +30717,9 @@
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.14.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
|
||||
"integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
|
||||
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
|
@ -14,38 +14,38 @@ const UserDatailsComponent = (props) => {
|
||||
isOpen, dispatch, user, dataJson, intl,
|
||||
} = props;
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const modal = useRef();
|
||||
const closeButton = useRef();
|
||||
const modalRef = useRef();
|
||||
const closeButtonRef = useRef();
|
||||
const wasModalOpen = usePreviousValue(isOpen);
|
||||
|
||||
useEffect(() => {
|
||||
const keydownhandler = (e) => {
|
||||
const keydownHandler = (e) => {
|
||||
if (e.code === 'Escape') dispatch({ type: 'closeModal' });
|
||||
};
|
||||
|
||||
const focusHandler = () => {
|
||||
if (modal.current && document.activeElement) {
|
||||
if (!modal.current.contains(document.activeElement)) {
|
||||
closeButton.current.focus();
|
||||
if (modalRef.current && document.activeElement) {
|
||||
if (!modalRef.current.contains(document.activeElement)) {
|
||||
closeButtonRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', keydownhandler);
|
||||
window.addEventListener('keydown', keydownHandler);
|
||||
window.addEventListener('focus', focusHandler, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', keydownhandler);
|
||||
window.removeEventListener('keydown', keydownHandler);
|
||||
window.removeEventListener('focus', focusHandler, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!wasModalOpen) closeButton.current?.focus();
|
||||
if (!wasModalOpen) closeButtonRef.current?.focus();
|
||||
});
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const {
|
||||
createdOn, endedOn, polls, users,
|
||||
} = dataJson;
|
||||
@ -310,13 +310,13 @@ const UserDatailsComponent = (props) => {
|
||||
role="none"
|
||||
onClick={() => dispatch({ type: 'closeModal' })}
|
||||
/>
|
||||
<div ref={modal} className="overflow-auto w-full md:w-2/4 bg-gray-100 p-6">
|
||||
<div ref={modalRef} className="overflow-auto w-full md:w-2/4 bg-gray-100 p-6">
|
||||
<div className="text-right rtl:text-left">
|
||||
<button
|
||||
onClick={() => dispatch({ type: 'closeModal' })}
|
||||
type="button"
|
||||
aria-label="Close user details modal"
|
||||
ref={closeButton}
|
||||
ref={closeButtonRef}
|
||||
className="focus:rounded-md focus:outline-none focus:ring focus:ring-gray-500 focus:ring-opacity-50 hover:text-black/50 active:text-black/75"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM openjdk:11-jre-bullseye
|
||||
FROM openjdk:17-slim-bullseye
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN echo "deb http://deb.debian.org/debian bullseye-backports main" >> /etc/apt/sources.list
|
||||
|
@ -1 +1 @@
|
||||
git clone --branch v1.2.2 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
|
||||
git clone --branch v1.3.0 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
|
||||
|
@ -1 +1 @@
|
||||
git clone --branch v2.9.0-beta.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
|
||||
git clone --branch v2.9.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
|
||||
|
@ -5,10 +5,53 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BigBlueButton</title>
|
||||
<style>
|
||||
.error-div {
|
||||
background-color: #f2dede;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border-color: #ebccd1;
|
||||
}
|
||||
.error-div-title {
|
||||
font-size: 25px;
|
||||
}
|
||||
.error-message {
|
||||
color: #a94442;
|
||||
font-size: 15px;
|
||||
margin-top: 2px 0 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin:0">
|
||||
<div style="display: flex; height: 100vh; margin: 0; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<h1>Welcome to BigBlueButton!</h1>
|
||||
<h1 id="welcome-message">Welcome to BigBlueButton!</h1>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
const ERRORS_QUERY_LABLE = "errors"
|
||||
const queryString = decodeURI(window.location.search).slice(1);
|
||||
const positionError = queryString.indexOf(ERRORS_QUERY_LABLE);
|
||||
let jsonString;
|
||||
if (positionError !== -1) {
|
||||
jsonString = JSON.parse(queryString.slice(positionError + ERRORS_QUERY_LABLE.length + 1));
|
||||
const welcomeMessage = document.querySelector('#welcome-message');
|
||||
const errorContainer = document.createElement('div');
|
||||
errorContainer.classList.add("error-div");
|
||||
welcomeMessage.before(errorContainer);
|
||||
|
||||
for (i in jsonString) {
|
||||
const newError = document.createElement('p')
|
||||
newError.classList.add("error-message");
|
||||
newError.innerHTML = "<b>Error</b>: " + jsonString[i].message;
|
||||
errorContainer.appendChild(newError);
|
||||
}
|
||||
|
||||
welcomeMessage.remove();
|
||||
|
||||
const bigBlueButton = document.createElement('h1');
|
||||
bigBlueButton.innerHTML = "BigBlueButton";
|
||||
errorContainer.before(bigBlueButton);
|
||||
}
|
||||
|
||||
</script>
|
||||
</html>
|
@ -1 +1 @@
|
||||
BIGBLUEBUTTON_RELEASE=2.6.0-alpha.2
|
||||
BIGBLUEBUTTON_RELEASE=2.6.0-alpha.3
|
||||
|
@ -346,9 +346,10 @@ stop_bigbluebutton () {
|
||||
|
||||
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
|
||||
TOMCAT_SERVICE=$TOMCAT_USER
|
||||
systemctl stop $TOMCAT_SERVICE
|
||||
fi
|
||||
|
||||
systemctl stop $TOMCAT_SERVICE bigbluebutton.target
|
||||
systemctl stop bigbluebutton.target
|
||||
}
|
||||
|
||||
start_bigbluebutton () {
|
||||
@ -377,17 +378,17 @@ start_bigbluebutton () {
|
||||
|
||||
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
|
||||
TOMCAT_SERVICE=$TOMCAT_USER
|
||||
|
||||
[ -z "$TOMCAT_SERVICE" ] || systemctl start $TOMCAT_SERVICE || {
|
||||
echo
|
||||
echo "# Warning: $TOMCAT_SERVICE could not be started. Please, check BBB-LTI."
|
||||
echo "# Run the command:"
|
||||
echo "# sudo journalctl -u $TOMCAT_SERVICE"
|
||||
echo "# To better understand the ERROR"
|
||||
}
|
||||
fi
|
||||
|
||||
systemctl start $TOMCAT_SERVICE || {
|
||||
echo
|
||||
echo "# Warning: $TOMCAT_SERVICE could not be started. Please, check BBB-LTI."
|
||||
echo "# Run the command:"
|
||||
echo "# sudo journalctl -u $TOMCAT_SERVICE"
|
||||
echo "# To better understand the ERROR"
|
||||
}
|
||||
|
||||
systemctl start bigbluebutton.target nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-fsesl-akka bbb-rap-resque-worker bbb-rap-starter.service bbb-rap-caption-inbox.service $HTML5 $WEBHOOKS $ETHERPAD $PADS $BBB_WEB $BBB_LTI
|
||||
systemctl start bigbluebutton.target
|
||||
|
||||
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
|
||||
systemctl start mongod
|
||||
@ -434,7 +435,7 @@ display_bigbluebutton_status () {
|
||||
units="$units etherpad"
|
||||
fi
|
||||
|
||||
if [ -f /lib/systemd/system/bbb-web.service ]; then
|
||||
if [ -f /usr/lib/systemd/system/bbb-web.service ]; then
|
||||
units="$units bbb-web"
|
||||
fi
|
||||
|
||||
@ -442,7 +443,7 @@ display_bigbluebutton_status () {
|
||||
units="$units bbb-webhooks"
|
||||
fi
|
||||
|
||||
if [ -f /lib/systemd/system/bbb-lti.service ]; then
|
||||
if [ -f /usr/lib/systemd/system/bbb-lti.service ]; then
|
||||
units="$units bbb-lti"
|
||||
fi
|
||||
|
||||
@ -454,6 +455,18 @@ display_bigbluebutton_status () {
|
||||
units="$units bbb-export-annotations"
|
||||
fi
|
||||
|
||||
if [ -f /usr/lib/systemd/system/bbb-rap-caption-inbox.service ]; then
|
||||
units="$units bbb-rap-caption-inbox"
|
||||
fi
|
||||
|
||||
if [ -f /usr/lib/systemd/system/bbb-rap-resque-worker.service ]; then
|
||||
units="$units bbb-rap-resque-worker"
|
||||
fi
|
||||
|
||||
if [ -f /usr/lib/systemd/system/bbb-rap-starter.service ]; then
|
||||
units="$units bbb-rap-starter"
|
||||
fi
|
||||
|
||||
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
|
||||
TOMCAT_SERVICE=$TOMCAT_USER
|
||||
fi
|
||||
@ -885,14 +898,18 @@ check_state() {
|
||||
|
||||
if ! ss -ant | grep '8090' > /dev/null; then
|
||||
print_header
|
||||
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${TOMCAT_USER} or grails"
|
||||
if [ ! -z "$TOMCAT_SERVICE" ]; then
|
||||
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${TOMCAT_USER} or grails"
|
||||
fi
|
||||
else
|
||||
if ps aux | ps -aef | grep -v grep | grep grails | grep run-app > /dev/null; then
|
||||
print_header
|
||||
RUNNING_APPS="${RUNNING_APPS} Grails"
|
||||
echo "# ${TOMCAT_USER}: noticed you are running grails run-app instead of ${TOMCAT_USER}"
|
||||
else
|
||||
RUNNING_APPS="${RUNNING_APPS} ${TOMCAT_USER}"
|
||||
if [ ! -z "$TOMCAT_SERVICE" ]; then
|
||||
RUNNING_APPS="${RUNNING_APPS} ${TOMCAT_USER}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -13,7 +13,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -22,7 +22,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -31,7 +31,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro'), local('SourceSansPro-Regular'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Regular.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Regular.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -40,7 +40,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro'), local('SourceSansPro-Regular'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Regular.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Regular.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -49,7 +49,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro'), local('SourceSansPro-Regular'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Regular.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Regular.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -58,7 +58,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Semibold.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Semibold.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -67,7 +67,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Semibold.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Semibold.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -76,7 +76,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Semibold.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Semibold.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -85,7 +85,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Bold.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Bold.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -94,7 +94,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Bold.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Bold.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -103,7 +103,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Bold.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Bold.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -112,7 +112,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-LightItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-LightItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -121,7 +121,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-LightItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-LightItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -130,7 +130,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-LightItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-LightItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -139,7 +139,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Italic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Italic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -148,7 +148,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Italic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Italic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -157,7 +157,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Italic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-Italic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -166,7 +166,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro Semibold Italic'), local('SourceSansPro-SemiboldIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-SemiboldItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-SemiboldItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -175,7 +175,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro Semibold Italic'), local('SourceSansPro-SemiboldIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-SemiboldItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-SemiboldItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -184,7 +184,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro Semibold Italic'), local('SourceSansPro-SemiboldIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-SemiboldItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-SemiboldItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -193,7 +193,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-BoldItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-BoldItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -202,7 +202,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-BoldItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-BoldItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -211,6 +211,6 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldIt'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-BoldItalic.woff') format('woff');
|
||||
url('fonts/SourceSansPro/SourceSansPro-BoldItalic.woff?v=VERSION') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const DEFAULT_FULLAUDIO_MEDIA_SERVER = MEDIA.audio.fullAudioMediaServer;
|
||||
const LISTEN_ONLY_OFFERING = MEDIA.listenOnlyOffering;
|
||||
const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, '');
|
||||
const RECONNECT_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000;
|
||||
const { audio: NETWORK_PRIORITY } = MEDIA.networkPriorities || {};
|
||||
const SENDRECV_ROLE = 'sendrecv';
|
||||
const RECV_ROLE = 'recv';
|
||||
const BRIDGE_NAME = 'fullaudio';
|
||||
@ -316,6 +317,7 @@ export default class SFUAudioBridge extends BaseAudioBridge {
|
||||
offering: isListenOnly ? LISTEN_ONLY_OFFERING : true,
|
||||
signalCandidates: SIGNAL_CANDIDATES,
|
||||
traceLogs: TRACE_LOGS,
|
||||
networkPriority: NETWORK_PRIORITY,
|
||||
};
|
||||
|
||||
this.broker = new AudioBroker(
|
||||
|
@ -19,6 +19,22 @@ function breakouts() {
|
||||
const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } });
|
||||
Logger.debug('Publishing Breakouts', { meetingId, userId });
|
||||
|
||||
const fields = {
|
||||
fields: {
|
||||
[`url_${userId}`]: 1,
|
||||
breakoutId: 1,
|
||||
externalId: 1,
|
||||
freeJoin: 1,
|
||||
isDefaultName: 1,
|
||||
joinedUsers: 1,
|
||||
name: 1,
|
||||
parentMeetingId: 1,
|
||||
sequence: 1,
|
||||
shortName: 1,
|
||||
timeRemaining: 1,
|
||||
},
|
||||
};
|
||||
|
||||
if (!!User && User.role === ROLE_MODERATOR) {
|
||||
const presenterSelector = {
|
||||
$or: [
|
||||
@ -39,7 +55,7 @@ function breakouts() {
|
||||
return condition;
|
||||
};
|
||||
publicationSafeGuard(comparisonFunc, this);
|
||||
return Breakouts.find(presenterSelector);
|
||||
return Breakouts.find(presenterSelector, fields);
|
||||
}
|
||||
|
||||
const selector = {
|
||||
@ -58,22 +74,6 @@ function breakouts() {
|
||||
],
|
||||
};
|
||||
|
||||
const fields = {
|
||||
fields: {
|
||||
[`url_${userId}`]: 1,
|
||||
breakoutId: 1,
|
||||
externalId: 1,
|
||||
freeJoin: 1,
|
||||
isDefaultName: 1,
|
||||
joinedUsers: 1,
|
||||
name: 1,
|
||||
parentMeetingId: 1,
|
||||
sequence: 1,
|
||||
shortName: 1,
|
||||
timeRemaining: 1,
|
||||
},
|
||||
};
|
||||
|
||||
return Breakouts.find(selector, fields);
|
||||
}
|
||||
|
||||
|
@ -84,6 +84,8 @@ export default function addMeeting(meeting) {
|
||||
name: String,
|
||||
disabledFeatures: Array,
|
||||
notifyRecordingIsOn: Boolean,
|
||||
uploadExternalDescription: String,
|
||||
uploadExternalUrl: String,
|
||||
},
|
||||
usersProp: {
|
||||
webcamsOnlyForModerator: Boolean,
|
||||
|
@ -1,26 +1,25 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
|
||||
export default function handlePresentationUploadTokenPass({ body, header }, meetingId) {
|
||||
check(body, Object);
|
||||
|
||||
const { userId } = header;
|
||||
const { podId, authzToken, filename, tmpPresId } = body;
|
||||
const { podId, authzToken, filename, temporaryPresentationId } = body;
|
||||
|
||||
check(userId, String);
|
||||
check(podId, String);
|
||||
check(authzToken, String);
|
||||
check(filename, String);
|
||||
check(tmpPresId, String)
|
||||
check(temporaryPresentationId, String)
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
podId,
|
||||
userId,
|
||||
filename,
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
@ -29,7 +28,7 @@ export default function handlePresentationUploadTokenPass({ body, header }, meet
|
||||
userId,
|
||||
filename,
|
||||
authzToken,
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
failed: false,
|
||||
used: false,
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function requestPresentationUploadToken(podId, filename, tmpPresId) {
|
||||
export default function requestPresentationUploadToken(podId, filename, temporaryPresentationId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'PresentationUploadTokenReqMsg';
|
||||
@ -15,12 +15,12 @@ export default function requestPresentationUploadToken(podId, filename, tmpPresI
|
||||
check(requesterUserId, String);
|
||||
check(podId, String);
|
||||
check(filename, String);
|
||||
check(tmpPresId, String);
|
||||
check(temporaryPresentationId, String);
|
||||
|
||||
const payload = {
|
||||
podId,
|
||||
filename,
|
||||
tmpPresId
|
||||
temporaryPresentationId
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
|
@ -4,7 +4,7 @@ import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
|
||||
|
||||
function presentationUploadToken(podId, filename, tmpPresId) {
|
||||
function presentationUploadToken(podId, filename, temporaryPresentationId) {
|
||||
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
|
||||
|
||||
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
|
||||
@ -16,14 +16,14 @@ function presentationUploadToken(podId, filename, tmpPresId) {
|
||||
|
||||
check(podId, String);
|
||||
check(filename, String);
|
||||
check(tmpPresId, String);
|
||||
check(temporaryPresentationId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
podId,
|
||||
userId,
|
||||
filename,
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
};
|
||||
|
||||
Logger.debug('Publishing PresentationUploadToken', { meetingId, userId });
|
||||
|
@ -25,6 +25,7 @@ export default function handlePresentationConversionUpdate({ body }, meetingId)
|
||||
|
||||
const {
|
||||
presentationId, podId, messageKey: status, presName: presentationName,
|
||||
temporaryPresentationId
|
||||
} = body;
|
||||
|
||||
check(meetingId, String);
|
||||
@ -73,9 +74,17 @@ export default function handlePresentationConversionUpdate({ body }, meetingId)
|
||||
id: presentationId ?? body.presentationToken,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign({ meetingId, podId }, statusModifier),
|
||||
};
|
||||
let modifier
|
||||
if (temporaryPresentationId){
|
||||
modifier = {
|
||||
$set: Object.assign({ meetingId, podId, temporaryPresentationId, }, statusModifier),
|
||||
};
|
||||
} else {
|
||||
modifier = {
|
||||
$set: Object.assign({ meetingId, podId }, statusModifier),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const { insertedId } = Presentations.upsert(selector, modifier);
|
||||
|
@ -44,9 +44,10 @@ export default function addPresentation(meetingId, podId, presentation) {
|
||||
txtUri: String,
|
||||
svgUri: String,
|
||||
current: Boolean,
|
||||
xCamera: Number,
|
||||
yCamera: Number,
|
||||
zoom: Number,
|
||||
xOffset: Number,
|
||||
yOffset: Number,
|
||||
widthRatio: Number,
|
||||
heightRatio: Number,
|
||||
},
|
||||
],
|
||||
downloadable: Boolean,
|
||||
|
@ -12,6 +12,7 @@ const SFU_URL = SFU_CONFIG.wsUrl;
|
||||
const OFFERING = SFU_CONFIG.screenshare.subscriberOffering;
|
||||
const SIGNAL_CANDIDATES = Meteor.settings.public.kurento.signalCandidates;
|
||||
const TRACE_LOGS = Meteor.settings.public.kurento.traceLogs;
|
||||
const { screenshare: NETWORK_PRIORITY } = Meteor.settings.public.media.networkPriorities || {};
|
||||
|
||||
const BRIDGE_NAME = 'kurento'
|
||||
const SCREENSHARE_VIDEO_TAG = 'screenshareVideo';
|
||||
@ -307,6 +308,7 @@ export default class KurentoScreenshareBridge {
|
||||
signalCandidates: SIGNAL_CANDIDATES,
|
||||
forceRelay: shouldForceRelay(),
|
||||
traceLogs: TRACE_LOGS,
|
||||
networkPriority: NETWORK_PRIORITY,
|
||||
};
|
||||
|
||||
this.broker = new ScreenshareBroker(
|
||||
|
@ -8,8 +8,8 @@ const calculateSlideData = (slideData) => {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
x: ((-xOffset * 2) * width) / 100,
|
||||
y: ((-yOffset * 2) * height) / 100,
|
||||
x: xOffset,
|
||||
y: yOffset,
|
||||
viewBoxWidth: (width * widthRatio) / 100,
|
||||
viewBoxHeight: (height * heightRatio) / 100,
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function zoomSlide(slideNumber, podId, zoom, x, y) {
|
||||
export default function zoomSlide(slideNumber, podId, widthRatio, heightRatio, x, y) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ResizeAndMovePagePubMsg';
|
||||
@ -43,9 +43,10 @@ export default function zoomSlide(slideNumber, podId, zoom, x, y) {
|
||||
podId,
|
||||
presentationId: Presentation.id,
|
||||
pageId: Slide.id,
|
||||
xCamera: x,
|
||||
yCamera: y,
|
||||
zoom,
|
||||
xOffset: x,
|
||||
yOffset: y,
|
||||
widthRatio,
|
||||
heightRatio,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
|
@ -54,17 +54,19 @@ export default function addSlide(meetingId, podId, presentationId, slide) {
|
||||
txtUri: String,
|
||||
svgUri: String,
|
||||
current: Boolean,
|
||||
xCamera: Number,
|
||||
yCamera: Number,
|
||||
zoom: Number,
|
||||
xOffset: Number,
|
||||
yOffset: Number,
|
||||
widthRatio: Number,
|
||||
heightRatio: Number,
|
||||
content: String,
|
||||
});
|
||||
|
||||
const {
|
||||
id: slideId,
|
||||
xCamera,
|
||||
yCamera,
|
||||
zoom,
|
||||
xOffset,
|
||||
yOffset,
|
||||
widthRatio,
|
||||
heightRatio,
|
||||
...restSlide
|
||||
} = slide;
|
||||
|
||||
@ -101,13 +103,14 @@ export default function addSlide(meetingId, podId, presentationId, slide) {
|
||||
const slideData = {
|
||||
width,
|
||||
height,
|
||||
xCamera,
|
||||
yCamera,
|
||||
zoom,
|
||||
xOffset,
|
||||
yOffset,
|
||||
widthRatio,
|
||||
heightRatio,
|
||||
};
|
||||
//const slidePosition = calculateSlideData(slideData);
|
||||
const slidePosition = calculateSlideData(slideData);
|
||||
|
||||
addSlidePositions(meetingId, podId, presentationId, slideId, slideData);
|
||||
addSlidePositions(meetingId, podId, presentationId, slideId, slidePosition);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -18,9 +18,10 @@ export default function addSlidePositions(
|
||||
check(slidePosition, {
|
||||
width: Number,
|
||||
height: Number,
|
||||
xCamera: Number,
|
||||
yCamera: Number,
|
||||
zoom: Number,
|
||||
x: Number,
|
||||
y: Number,
|
||||
viewBoxWidth: Number,
|
||||
viewBoxHeight: Number,
|
||||
});
|
||||
|
||||
const selector = {
|
||||
|
@ -10,9 +10,10 @@ export default function resizeSlide(meetingId, slide) {
|
||||
podId,
|
||||
presentationId,
|
||||
pageId,
|
||||
zoom,
|
||||
xCamera,
|
||||
yCamera,
|
||||
widthRatio,
|
||||
heightRatio,
|
||||
xOffset,
|
||||
yOffset,
|
||||
} = slide;
|
||||
|
||||
const selector = {
|
||||
@ -36,13 +37,15 @@ export default function resizeSlide(meetingId, slide) {
|
||||
const slideData = {
|
||||
width,
|
||||
height,
|
||||
xCamera,
|
||||
yCamera,
|
||||
zoom,
|
||||
xOffset,
|
||||
yOffset,
|
||||
widthRatio,
|
||||
heightRatio,
|
||||
};
|
||||
const calculatedData = calculateSlideData(slideData);
|
||||
|
||||
const modifier = {
|
||||
$set: slideData,
|
||||
$set: calculatedData,
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import VideoStreams from '/imports/api/video-streams';
|
||||
import flat from 'flat';
|
||||
|
||||
export default function updateVideoStream(meetingId, videoStream) {
|
||||
check(meetingId, String);
|
||||
@ -23,7 +24,7 @@ export default function updateVideoStream(meetingId, videoStream) {
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
...videoStream,
|
||||
flat(videoStream),
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,7 @@ const propTypes = {
|
||||
|
||||
const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const CLIENT_VERSION = Meteor.settings.public.app.html5ClientBuild;
|
||||
const FALLBACK_ON_EMPTY_STRING = Meteor.settings.public.app.fallbackOnEmptyLocaleString;
|
||||
|
||||
const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he'];
|
||||
const LARGE_FONT_LANGUAGES = ['te', 'km'];
|
||||
@ -163,7 +164,7 @@ class IntlStartup extends Component {
|
||||
|
||||
{normalizedLocale
|
||||
&& (
|
||||
<IntlProvider locale={normalizedLocale} messages={messages}>
|
||||
<IntlProvider fallbackOnEmptyString={FALLBACK_ON_EMPTY_STRING} locale={normalizedLocale} messages={messages}>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
)
|
||||
|
@ -55,6 +55,7 @@ const AboutComponent = ({ intl, settings }) => {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-test="aboutModalTitleLabel"
|
||||
title={intl.formatMessage(intlMessages.title)}
|
||||
dismiss={{
|
||||
label: intl.formatMessage(intlMessages.dismissLabel),
|
||||
|
@ -29,6 +29,8 @@ const ActionsBarContainer = (props) => {
|
||||
|
||||
const amIPresenter = users[Auth.meetingID][Auth.userID].presenter;
|
||||
|
||||
if (actionsBarStyle.display === false) return null;
|
||||
|
||||
return (
|
||||
<ActionsBar {
|
||||
...{
|
||||
|
@ -48,6 +48,7 @@ import Notifications from '../notifications/container';
|
||||
import GlobalStyles from '/imports/ui/stylesheets/styled-components/globalStyles';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import ActionsBarContainer from '../actions-bar/container';
|
||||
import { updateSettings } from '/imports/ui/components/settings/service';
|
||||
|
||||
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
@ -313,7 +314,13 @@ class App extends Component {
|
||||
|
||||
this.renderDarkMode();
|
||||
|
||||
if (meetingLayout !== prevProps.meetingLayout) {
|
||||
const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
|
||||
const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
|
||||
const shouldSwitchLayout = isPresenter
|
||||
? meetingLayoutDidChange
|
||||
: (meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting;
|
||||
|
||||
if (shouldSwitchLayout) {
|
||||
|
||||
let contextLayout = meetingLayout;
|
||||
if (isMobile()) {
|
||||
@ -325,13 +332,21 @@ class App extends Component {
|
||||
value: contextLayout,
|
||||
});
|
||||
|
||||
Settings.application.selectedLayout = contextLayout;
|
||||
Settings.save();
|
||||
updateSettings({
|
||||
application: {
|
||||
...Settings.application,
|
||||
selectedLayout: contextLayout,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (pushLayoutMeeting !== prevProps.pushLayoutMeeting) {
|
||||
Settings.application.pushLayout = pushLayoutMeeting;
|
||||
Settings.save();
|
||||
if (pushLayoutMeetingDidChange) {
|
||||
updateSettings({
|
||||
application: {
|
||||
...Settings.application,
|
||||
pushLayout: pushLayoutMeeting,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (meetingLayout === "custom" && !isPresenter) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Users from '/imports/api/users';
|
||||
import Meetings, { LayoutMeetings } from '/imports/api/meetings';
|
||||
@ -45,8 +44,9 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const endMeeting = (code) => {
|
||||
const endMeeting = (code, ejectedReason) => {
|
||||
Session.set('codeError', code);
|
||||
Session.set('errorMessageDescription', ejectedReason);
|
||||
Session.set('isMeetingEnded', true);
|
||||
};
|
||||
|
||||
@ -189,8 +189,15 @@ const currentUserEmoji = (currentUser) => (currentUser
|
||||
|
||||
export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => {
|
||||
Users.find({ userId: Auth.userID, meetingId: Auth.meetingID }).observe({
|
||||
removed() {
|
||||
endMeeting('403');
|
||||
removed(userData) {
|
||||
// wait 3secs (before endMeeting), client will try to authenticate again
|
||||
const delayForReconnection = userData.ejected ? 0 : 3000;
|
||||
setTimeout(() => {
|
||||
const queryCurrentUser = Users.find({ userId: Auth.userID, meetingId: Auth.meetingID });
|
||||
if (queryCurrentUser.count() === 0) {
|
||||
endMeeting(403, userData.ejectedReason || null);
|
||||
}
|
||||
}, delayForReconnection);
|
||||
},
|
||||
});
|
||||
|
||||
@ -259,7 +266,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
||||
randomlySelectedUser,
|
||||
currentUserId: currentUser?.userId,
|
||||
isPresenter,
|
||||
isModerator: currentUser.role === ROLE_MODERATOR,
|
||||
isModerator: currentUser?.role === ROLE_MODERATOR,
|
||||
meetingLayout: layout,
|
||||
meetingLayoutUpdatedAt: layoutUpdatedAt,
|
||||
presentationIsOpen,
|
||||
|
@ -306,7 +306,7 @@ class MessageForm extends PureComponent {
|
||||
showEmojiPicker: !prevState.showEmojiPicker,
|
||||
}))}
|
||||
icon="happy"
|
||||
color="dark"
|
||||
color="light"
|
||||
ghost
|
||||
type="button"
|
||||
circle
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
import TextareaAutosize from 'react-autosize-textarea';
|
||||
import EmojiPickerComponent from '/imports/ui/components/emoji-picker/component';
|
||||
import Icon from '/imports/ui/components/common/icon/component';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
|
||||
const Form = styled.form`
|
||||
|
@ -11,7 +11,7 @@ const SIZES = [
|
||||
];
|
||||
|
||||
const COLORS = [
|
||||
'default', 'primary', 'danger', 'warning', 'success', 'dark', 'offline', 'muted', 'secondary',
|
||||
'default', 'primary', 'danger', 'warning', 'success', 'dark', 'light', 'offline', 'muted', 'secondary',
|
||||
];
|
||||
|
||||
const propTypes = {
|
||||
|
@ -43,6 +43,18 @@ const intlMessages = defineMessages({
|
||||
joined_another_window_reason: {
|
||||
id: 'app.error.joinedAnotherWindow',
|
||||
},
|
||||
user_inactivity_eject_reason: {
|
||||
id: 'app.meeting.logout.userInactivityEjectReason',
|
||||
},
|
||||
user_requested_eject_reason: {
|
||||
id: 'app.meeting.logout.ejectedFromMeeting',
|
||||
},
|
||||
duplicate_user_in_meeting_eject_reason: {
|
||||
id: 'app.meeting.logout.duplicateUserEjectReason',
|
||||
},
|
||||
not_enough_permission_eject_reason: {
|
||||
id: 'app.meeting.logout.permissionEjectReason',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
@ -90,7 +102,9 @@ class ErrorScreen extends PureComponent {
|
||||
{formatedMessage}
|
||||
</Styled.Message>
|
||||
{
|
||||
!errorMessageDescription || (
|
||||
!errorMessageDescription
|
||||
|| formatedMessage === errorMessageDescription
|
||||
|| (
|
||||
<Styled.SessionMessage>
|
||||
{errorMessageDescription}
|
||||
</Styled.SessionMessage>
|
||||
|
@ -9,8 +9,6 @@ import {
|
||||
layoutSelectOutput,
|
||||
layoutDispatch,
|
||||
} from '../layout/context';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
|
||||
const ExternalVideoContainer = (props) => {
|
||||
const fullscreenElementId = 'ExternalVideo';
|
||||
|
@ -11,7 +11,7 @@ const Slider = styled.div`
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
border-radius: 32px;
|
||||
|
||||
& > i {
|
||||
i {
|
||||
color: white;
|
||||
transition: 0.5s;
|
||||
font-size: 200%;
|
||||
|
@ -99,7 +99,7 @@ export const INITIAL_INPUT_STATE = {
|
||||
|
||||
export const INITIAL_OUTPUT_STATE = {
|
||||
navBar: {
|
||||
display: true,
|
||||
display: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
@ -108,7 +108,7 @@ export const INITIAL_OUTPUT_STATE = {
|
||||
zIndex: 1,
|
||||
},
|
||||
actionBar: {
|
||||
display: true,
|
||||
display: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
|
@ -172,7 +172,7 @@ class LockViewersComponent extends Component {
|
||||
<Styled.Bold>{intl.formatMessage(intlMessages.featuresLable)}</Styled.Bold>
|
||||
<Styled.Bold>{intl.formatMessage(intlMessages.lockStatusLabel)}</Styled.Bold>
|
||||
</Styled.SubHeader>
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockShareWebcamItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -197,7 +197,7 @@ class LockViewersComponent extends Component {
|
||||
</Styled.FormElementRight>
|
||||
</Styled.Col>
|
||||
</Styled.Row>
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockSeeOtherViewersWebcamItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -222,7 +222,7 @@ class LockViewersComponent extends Component {
|
||||
</Styled.FormElementRight>
|
||||
</Styled.Col>
|
||||
</Styled.Row>
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockShareMicrophoneItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -250,7 +250,7 @@ class LockViewersComponent extends Component {
|
||||
|
||||
{isChatEnabled() ? (
|
||||
<Fragment>
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockPublicChatItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -275,7 +275,7 @@ class LockViewersComponent extends Component {
|
||||
</Styled.FormElementRight>
|
||||
</Styled.Col>
|
||||
</Styled.Row>
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockPrivateChatItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -305,7 +305,7 @@ class LockViewersComponent extends Component {
|
||||
}
|
||||
{NotesService.isEnabled()
|
||||
? (
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockEditSharedNotesItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -333,7 +333,7 @@ class LockViewersComponent extends Component {
|
||||
)
|
||||
: null
|
||||
}
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="lockUserListItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
@ -359,7 +359,7 @@ class LockViewersComponent extends Component {
|
||||
</Styled.Col>
|
||||
</Styled.Row>
|
||||
|
||||
<Styled.Row>
|
||||
<Styled.Row data-test="hideViewersCursorItem">
|
||||
<Styled.Col aria-hidden="true">
|
||||
<Styled.FormElement>
|
||||
<Styled.Label>
|
||||
|
@ -179,7 +179,7 @@ class MeetingEnded extends PureComponent {
|
||||
}
|
||||
|
||||
getEndingMessage() {
|
||||
const { intl, code, endedReason } = this.props;
|
||||
const { intl, code, ejectedReason, endedReason } = this.props;
|
||||
|
||||
if (endedReason && endedReason === 'ENDED_DUE_TO_NO_MODERATOR') {
|
||||
return this.endWhenNoModeratorMinutes === 1
|
||||
@ -191,6 +191,10 @@ class MeetingEnded extends PureComponent {
|
||||
return intl.formatMessage(intlMessage.messageEndedByUser, { 0: this.meetingEndedBy });
|
||||
}
|
||||
|
||||
if (intlMessage[ejectedReason]) {
|
||||
return intl.formatMessage(intlMessage[ejectedReason]);
|
||||
}
|
||||
|
||||
if (intlMessage[code]) {
|
||||
return intl.formatMessage(intlMessage[code]);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ const NavBarContainer = ({ children, ...props }) => {
|
||||
|
||||
const hideNavBar = getFromUserSettings('bbb_hide_nav_bar', false);
|
||||
|
||||
if (hideNavBar) return null;
|
||||
if (hideNavBar || navBar.display === false) return null;
|
||||
|
||||
return (
|
||||
<NavBar
|
||||
|
@ -224,6 +224,7 @@ class SettingsDropdown extends PureComponent {
|
||||
{
|
||||
key: 'list-item-about',
|
||||
icon: 'about',
|
||||
dataTest: 'aboutModal',
|
||||
label: intl.formatMessage(intlMessages.aboutLabel),
|
||||
// description: intl.formatMessage(intlMessages.aboutDesc),
|
||||
onClick: () => mountModal(<AboutContainer />),
|
||||
|
@ -1,8 +1,4 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
borderSizeLarge,
|
||||
borderSize,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import {
|
||||
colorWhite,
|
||||
colorGrayDark,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Service from './service';
|
||||
|
||||
|
@ -4,6 +4,7 @@ import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteb
|
||||
import WhiteboardContainer from '/imports/ui/components/whiteboard/container';
|
||||
import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container';
|
||||
import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils';
|
||||
import { SPACE } from '/imports/utils/keyCodes';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Session } from 'meteor/session';
|
||||
@ -76,7 +77,7 @@ class Presentation extends PureComponent {
|
||||
fitToWidth: false,
|
||||
isFullscreen: false,
|
||||
tldrawAPI: null,
|
||||
isZoomed: false,
|
||||
isPanning: false,
|
||||
};
|
||||
|
||||
this.currentPresentationToastId = null;
|
||||
@ -91,8 +92,11 @@ class Presentation extends PureComponent {
|
||||
this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.setTldrawAPI = this.setTldrawAPI.bind(this);
|
||||
this.setIsPanning = this.setIsPanning.bind(this);
|
||||
this.handlePanShortcut = this.handlePanShortcut.bind(this);
|
||||
this.renderPresentationMenu = this.renderPresentationMenu.bind(this);
|
||||
this.setIsZoomed = this.setIsZoomed.bind(this);
|
||||
this.setIsPanning = this.setIsPanning.bind(this);
|
||||
this.handlePanShortcut = this.handlePanShortcut.bind(this);
|
||||
|
||||
this.onResize = () => setTimeout(this.handleResize.bind(this), 0);
|
||||
this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this);
|
||||
@ -124,8 +128,28 @@ class Presentation extends PureComponent {
|
||||
return stateChange;
|
||||
}
|
||||
|
||||
setIsPanning() {
|
||||
this.setState({
|
||||
isPanning: !this.state.isPanning,
|
||||
});
|
||||
}
|
||||
|
||||
handlePanShortcut(e) {
|
||||
const { userIsPresenter } = this.props;
|
||||
if (e.keyCode === SPACE && userIsPresenter) {
|
||||
switch(e.type) {
|
||||
case 'keyup':
|
||||
return this.state.isPanning && this.setIsPanning();
|
||||
case 'keydown':
|
||||
return !this.state.isPanning && this.setIsPanning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getInitialPresentationSizes();
|
||||
this.refPresentationContainer.addEventListener('keydown', this.handlePanShortcut);
|
||||
this.refPresentationContainer.addEventListener('keyup', this.handlePanShortcut);
|
||||
this.refPresentationContainer
|
||||
.addEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange);
|
||||
window.addEventListener('resize', this.onResize, false);
|
||||
@ -168,7 +192,7 @@ class Presentation extends PureComponent {
|
||||
clearFakeAnnotations,
|
||||
} = this.props;
|
||||
|
||||
const { presentationWidth, presentationHeight } = this.state;
|
||||
const { presentationWidth, presentationHeight, zoom, isPanning } = this.state;
|
||||
const {
|
||||
numCameras: prevNumCameras,
|
||||
presentationBounds: prevPresentationBounds,
|
||||
@ -269,6 +293,10 @@ class Presentation extends PureComponent {
|
||||
value: currentSlide.num,
|
||||
});
|
||||
}
|
||||
|
||||
if (zoom <= HUNDRED_PERCENT && isPanning || !userIsPresenter && prevProps.userIsPresenter) {
|
||||
this.setIsPanning();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -278,6 +306,8 @@ class Presentation extends PureComponent {
|
||||
window.removeEventListener('resize', this.onResize, false);
|
||||
this.refPresentationContainer
|
||||
.removeEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange);
|
||||
this.refPresentationContainer.removeEventListener('keydown', this.handlePanShortcut);
|
||||
this.refPresentationContainer.removeEventListener('keyup', this.handlePanShortcut);
|
||||
|
||||
if (fullscreenContext) {
|
||||
layoutContextDispatch({
|
||||
@ -293,13 +323,14 @@ class Presentation extends PureComponent {
|
||||
setTldrawAPI(api) {
|
||||
this.setState({
|
||||
tldrawAPI: api,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setIsZoomed(isZoomed) {
|
||||
|
||||
setIsPanning() {
|
||||
this.setState({
|
||||
isZoomed,
|
||||
})
|
||||
isPanning: !this.state.isPanning,
|
||||
});
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
@ -522,7 +553,6 @@ class Presentation extends PureComponent {
|
||||
const {
|
||||
zoom,
|
||||
fitToWidth,
|
||||
isZoomed,
|
||||
} = this.state;
|
||||
|
||||
if (!userIsPresenter && !multiUser) {
|
||||
@ -573,8 +603,6 @@ class Presentation extends PureComponent {
|
||||
physicalSlideHeight={physicalDimensions.height}
|
||||
zoom={zoom}
|
||||
zoomChanger={this.zoomChanger}
|
||||
setIsZoomed={this.setIsZoomed}
|
||||
isZoomed={isZoomed}
|
||||
/>
|
||||
</PresentationOverlayContainer>
|
||||
);
|
||||
@ -749,8 +777,8 @@ class Presentation extends PureComponent {
|
||||
layoutContextDispatch,
|
||||
presentationIsOpen,
|
||||
}}
|
||||
isZoomed={this.state.isZoomed}
|
||||
tldrawAPI={this.state.tldrawAPI}
|
||||
setIsPanning={this.setIsPanning}
|
||||
isPanning={this.state.isPanning}
|
||||
curPageId={this.state.tldrawAPI?.getPage()?.id}
|
||||
currentSlideNum={currentSlide.num}
|
||||
presentationId={currentSlide.presentationId}
|
||||
@ -894,15 +922,15 @@ class Presentation extends PureComponent {
|
||||
isViewersCursorLocked,
|
||||
fullscreenElementId,
|
||||
layoutContextDispatch,
|
||||
presentationIsOpen,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
showSlide,
|
||||
isFullscreen,
|
||||
localPosition,
|
||||
isZoomed,
|
||||
presentationWidth,
|
||||
presentationHeight,
|
||||
fitToWidth,
|
||||
zoom,
|
||||
} = this.state;
|
||||
|
||||
let viewBoxDimensions;
|
||||
@ -939,6 +967,10 @@ class Presentation extends PureComponent {
|
||||
? svgWidth
|
||||
: presentationToolbarMinWidth;
|
||||
|
||||
const slideContent = currentSlide?.content ? `${intl.formatMessage(intlMessages.slideContentStart)}
|
||||
${currentSlide.content}
|
||||
${intl.formatMessage(intlMessages.slideContentEnd)}` : intl.formatMessage(intlMessages.noSlideContent);
|
||||
|
||||
if (!currentPresentation && this.refPresentationContainer) {
|
||||
return (
|
||||
<></>
|
||||
@ -971,26 +1003,56 @@ class Presentation extends PureComponent {
|
||||
: null,
|
||||
}}
|
||||
>
|
||||
{this.renderPresentationMenu()}
|
||||
<WhiteboardContainer
|
||||
whiteboardId={currentSlide?.id}
|
||||
podId={podId}
|
||||
slidePosition={slidePosition}
|
||||
getSvgRef={this.getSvgRef}
|
||||
setTldrawAPI={this.setTldrawAPI}
|
||||
curPageId={currentSlide?.num.toString()}
|
||||
svgUri={currentSlide?.svgUri}
|
||||
intl={intl}
|
||||
presentationWidth={presentationWidth}
|
||||
presentationHeight={presentationHeight}
|
||||
isViewersCursorLocked={isViewersCursorLocked}
|
||||
isZoomed={isZoomed}
|
||||
setIsZoomed={this.setIsZoomed}
|
||||
zoomChanger={this.zoomChanger}
|
||||
/>
|
||||
{isFullscreen && <PollingContainer />}
|
||||
{this.renderPresentationToolbar()}
|
||||
{/*
|
||||
<Styled.Presentation ref={(ref) => { this.refPresentation = ref; }}>
|
||||
<Styled.SvgContainer
|
||||
style={{
|
||||
height: svgHeight + toolbarHeight,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: svgDimensions.width < 0 ? 0 : svgDimensions.width,
|
||||
height: svgDimensions.height < 0 ? 0 : svgDimensions.height,
|
||||
textAlign: 'center',
|
||||
display: !presentationIsOpen ? 'none' : 'block',
|
||||
}}
|
||||
>
|
||||
<Styled.VisuallyHidden id="currentSlideText">{slideContent}</Styled.VisuallyHidden>
|
||||
{this.renderPresentationMenu()}
|
||||
<WhiteboardContainer
|
||||
whiteboardId={currentSlide?.id}
|
||||
podId={podId}
|
||||
slidePosition={slidePosition}
|
||||
getSvgRef={this.getSvgRef}
|
||||
setTldrawAPI={this.setTldrawAPI}
|
||||
curPageId={currentSlide?.num.toString()}
|
||||
svgUri={currentSlide?.svgUri}
|
||||
intl={intl}
|
||||
presentationWidth={svgWidth}
|
||||
presentationHeight={svgHeight}
|
||||
isViewersCursorLocked={isViewersCursorLocked}
|
||||
isPanning={this.state.isPanning}
|
||||
zoomChanger={this.zoomChanger}
|
||||
fitToWidth={fitToWidth}
|
||||
zoomValue={zoom}
|
||||
/>
|
||||
{isFullscreen && <PollingContainer />}
|
||||
</div>
|
||||
<Styled.PresentationToolbar
|
||||
ref={(ref) => { this.refPresentationToolbar = ref; }}
|
||||
style={
|
||||
{
|
||||
width: containerWidth,
|
||||
}
|
||||
}
|
||||
>
|
||||
{this.renderPresentationToolbar(svgWidth)}
|
||||
</Styled.PresentationToolbar>
|
||||
{/*this.renderPresentationToolbar()*/}
|
||||
</Styled.SvgContainer>
|
||||
</Styled.Presentation>
|
||||
{/*
|
||||
<Styled.Presentation ref={(ref) => { this.refPresentation = ref; }}>
|
||||
<Styled.WhiteboardSizeAvailable ref={(ref) => { this.refWhiteboardArea = ref; }} />
|
||||
<Styled.SvgContainer
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component, useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withTracker } from "meteor/react-meteor-data";
|
||||
import Auth from "/imports/ui/services/auth";
|
||||
@ -12,8 +12,8 @@ const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
const CursorContainer = (props) => {
|
||||
const { cursorX, cursorY, presenter, uid, isViewersCursorLocked } = props;
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
if (cursorX > 0 && cursorY > 0) {
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
const role = users[Auth.meetingID][Auth.userID].role;
|
||||
const userId = users[Auth.meetingID][Auth.userID].userId;
|
||||
|
@ -171,6 +171,7 @@ const PresentationMenu = (props) => {
|
||||
{
|
||||
key: 'list-item-screenshot',
|
||||
label: intl.formatMessage(intlMessages.snapshotLabel),
|
||||
dataTest: "presentationSnapshot",
|
||||
onClick: async () => {
|
||||
setState({
|
||||
loading: true,
|
||||
@ -189,7 +190,7 @@ const PresentationMenu = (props) => {
|
||||
|
||||
try {
|
||||
const { copySvg, getShapes, currentPageId } = tldrawAPI;
|
||||
const svgString = copySvg(getShapes(currentPageId).map((shape) => shape.id));
|
||||
const svgString = await copySvg(getShapes(currentPageId).map((shape) => shape.id));
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = svgString;
|
||||
const svgElem = container.firstChild;
|
||||
|
@ -7,8 +7,6 @@ import {
|
||||
colorGrayDark,
|
||||
colorSuccess,
|
||||
colorGrayLightest,
|
||||
colorPrimary,
|
||||
colorWhite,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
borderSizeLarge,
|
||||
@ -16,9 +14,6 @@ import {
|
||||
statusIconSize,
|
||||
borderSize,
|
||||
statusInfoHeight,
|
||||
borderRadius,
|
||||
mdPaddingY,
|
||||
mdPaddingX,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
|
||||
const DropdownButton = styled.button`
|
||||
|
@ -13,8 +13,6 @@ import ZoomTool from './zoom-tool/component';
|
||||
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
|
||||
import KEY_CODES from '/imports/utils/keyCodes';
|
||||
|
||||
import ToolbarMenuItem from '/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
previousSlideLabel: {
|
||||
id: 'app.presentation.presentationToolbar.prevSlideLabel',
|
||||
@ -84,6 +82,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.whiteboard.toolbar.multiUserOff',
|
||||
description: 'Whiteboard toolbar turn multi-user off menu',
|
||||
},
|
||||
pan: {
|
||||
id: 'app.whiteboard.toolbar.tools.hand',
|
||||
description: 'presentation toolbar pan label',
|
||||
}
|
||||
});
|
||||
|
||||
class PresentationToolbar extends PureComponent {
|
||||
@ -105,6 +107,11 @@ class PresentationToolbar extends PureComponent {
|
||||
document.addEventListener('keydown', this.switchSlide);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { zoom, setIsPanning } = this.props;
|
||||
if (zoom <= HUNDRED_PERCENT && zoom !== prevProps.zoom) setIsPanning();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.switchSlide);
|
||||
}
|
||||
@ -254,11 +261,11 @@ class PresentationToolbar extends PureComponent {
|
||||
startPoll,
|
||||
currentSlide,
|
||||
slidePosition,
|
||||
tldrawAPI,
|
||||
toolbarWidth,
|
||||
multiUserSize,
|
||||
multiUser,
|
||||
isZoomed,
|
||||
setIsPanning,
|
||||
isPanning,
|
||||
} = this.props;
|
||||
|
||||
const { isMobile } = deviceInfo;
|
||||
@ -388,15 +395,27 @@ class PresentationToolbar extends PureComponent {
|
||||
zoomValue={zoom}
|
||||
currentSlideNum={currentSlideNum}
|
||||
change={this.change}
|
||||
minBound={0.1}
|
||||
maxBound={5}
|
||||
minBound={HUNDRED_PERCENT}
|
||||
maxBound={MAX_PERCENT}
|
||||
step={STEP}
|
||||
isMeteorConnected={isMeteorConnected}
|
||||
tldrawAPI={tldrawAPI}
|
||||
isZoomed={isZoomed}
|
||||
/>
|
||||
</TooltipContainer>
|
||||
) : null}
|
||||
<Styled.FitToWidthButton
|
||||
role="button"
|
||||
data-test="panButton"
|
||||
aria-label={intl.formatMessage(intlMessages.pan)}
|
||||
color="light"
|
||||
disabled={(zoom <= HUNDRED_PERCENT)}
|
||||
icon="hand"
|
||||
size="md"
|
||||
circle
|
||||
onClick={setIsPanning}
|
||||
label={intl.formatMessage(intlMessages.pan)}
|
||||
hideLabel
|
||||
panning={isPanning}
|
||||
/>
|
||||
<Styled.FitToWidthButton
|
||||
role="button"
|
||||
data-test="fitToWidthButton"
|
||||
@ -415,8 +434,11 @@ class PresentationToolbar extends PureComponent {
|
||||
icon="fit_to_width"
|
||||
size="md"
|
||||
circle
|
||||
onClick={() => this.props.tldrawAPI.zoomToFit()}
|
||||
label={intl.formatMessage(intlMessages.fitToPage)}
|
||||
onClick={fitToWidthHandler}
|
||||
label={fitToWidth
|
||||
? intl.formatMessage(intlMessages.fitToPage)
|
||||
: intl.formatMessage(intlMessages.fitToWidth)
|
||||
}
|
||||
hideLabel
|
||||
/>
|
||||
</Styled.PresentationZoomControls>
|
||||
|
@ -2,7 +2,6 @@ import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import PresentationService from '/imports/ui/components/presentation/service';
|
||||
import Service from '/imports/ui/components/actions-bar/service';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import PresentationToolbar from './component';
|
||||
|
@ -29,8 +29,8 @@ const nextSlide = (currentSlideNum, numberOfSlides, podId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const zoomSlide = throttle((currentSlideNum, podId, zoom, xCamera, yCamera) => {
|
||||
makeCall('zoomSlide', currentSlideNum, podId, zoom, xCamera, yCamera);
|
||||
const zoomSlide = throttle((currentSlideNum, podId, widthRatio, heightRatio, xOffset, yOffset) => {
|
||||
makeCall('zoomSlide', currentSlideNum, podId, widthRatio, heightRatio, xOffset, yOffset);
|
||||
}, PAN_ZOOM_INTERVAL);
|
||||
|
||||
const skipToSlide = (requestedSlideNum, podId) => {
|
||||
|
@ -89,13 +89,9 @@ const PresentationSlideControls = styled.div`
|
||||
`;
|
||||
|
||||
const PrevSlideButton = styled(Button)`
|
||||
|
||||
i {
|
||||
padding-left: 20%;
|
||||
}
|
||||
|
||||
& > i {
|
||||
font-size: 1rem;
|
||||
padding-left: 20%;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
@ -105,17 +101,12 @@ const PrevSlideButton = styled(Button)`
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const NextSlideButton = styled(Button)`
|
||||
|
||||
i {
|
||||
padding-left: 60%;
|
||||
}
|
||||
|
||||
& > i {
|
||||
font-size: 1rem;
|
||||
padding-left: 60%;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
@ -204,6 +195,12 @@ const FitToWidthButton = styled(Button)`
|
||||
background-color: ${colorOffWhite};
|
||||
border: 0;
|
||||
}
|
||||
|
||||
${({ panning }) => panning && `
|
||||
> span {
|
||||
background-color: #DCE4EC;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const MultiUserTool = styled.span`
|
||||
@ -213,7 +210,6 @@ const MultiUserTool = styled.span`
|
||||
height: 1rem;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
right: 1rem;
|
||||
bottom: 0.5rem;
|
||||
color: ${colorWhite};
|
||||
display: flex;
|
||||
@ -221,20 +217,35 @@ const MultiUserTool = styled.span`
|
||||
align-items: center;
|
||||
box-shadow: 1px 1px ${borderSizeLarge} ${colorGrayDark};
|
||||
font-size: ${smPaddingX};
|
||||
|
||||
[dir="ltr"] & {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] & {
|
||||
left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const MUTPlaceholder = styled.div`
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
position: relative;
|
||||
right: 1rem;
|
||||
bottom: 0.5rem;
|
||||
|
||||
[dir="ltr"] & {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] & {
|
||||
left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const WBAccessButton = styled(Button)`
|
||||
border: none !important;
|
||||
|
||||
& > i {
|
||||
i {
|
||||
font-size: 1.2rem;
|
||||
|
||||
[dir="rtl"] & {
|
||||
|
@ -6,7 +6,6 @@ import HoldButton from './holdButton/component';
|
||||
|
||||
const DELAY_MILLISECONDS = 200;
|
||||
const STEP_TIME = 100;
|
||||
const ZOOM_INCREMENT = .1;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
resetZoomLabel: {
|
||||
@ -53,11 +52,11 @@ class ZoomTool extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { zoomValue, tldrawAPI } = this.props;
|
||||
const { zoomValue } = this.props;
|
||||
const { stateZoomValue } = this.state;
|
||||
const isDifferent = zoomValue !== stateZoomValue;
|
||||
if (isDifferent) {
|
||||
this.onChanger(tldrawAPI?.getPageState()?.camera?.zoom);
|
||||
this.onChanger(zoomValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,9 +147,6 @@ class ZoomTool extends PureComponent {
|
||||
intl,
|
||||
isMeteorConnected,
|
||||
step,
|
||||
tldrawAPI,
|
||||
slidePosition,
|
||||
isZoomed,
|
||||
} = this.props;
|
||||
const { stateZoomValue } = this.state;
|
||||
|
||||
@ -165,10 +161,6 @@ class ZoomTool extends PureComponent {
|
||||
}
|
||||
|
||||
const stateZoomPct = intl.formatNumber((stateZoomValue / 100), { style: 'percent' });
|
||||
const tldrawZoomState = tldrawAPI?.getPageState()?.camera?.zoom;
|
||||
let displayZoom = zoomValue < tldrawZoomState && tldrawZoomState !== 1
|
||||
? tldrawZoomState : zoomValue;
|
||||
if (zoomValue === 1) displayZoom = tldrawZoomState;
|
||||
|
||||
return (
|
||||
[
|
||||
@ -188,10 +180,8 @@ class ZoomTool extends PureComponent {
|
||||
aria-label={zoomOutAriaLabel}
|
||||
label={intl.formatMessage(intlMessages.zoomOutLabel)}
|
||||
icon="substract"
|
||||
onClick={() => {
|
||||
tldrawAPI.zoomTo(tldrawAPI?.getPageState()?.camera?.zoom - ZOOM_INCREMENT);
|
||||
}}
|
||||
disabled={!isZoomed || !isMeteorConnected}
|
||||
onClick={() => { }}
|
||||
disabled={(zoomValue <= minBound) || !isMeteorConnected}
|
||||
hideLabel
|
||||
/>
|
||||
<div id="zoomOutDescription" hidden>{intl.formatMessage(intlMessages.zoomOutDesc)}</div>
|
||||
@ -204,9 +194,9 @@ class ZoomTool extends PureComponent {
|
||||
aria-describedby="resetZoomDescription"
|
||||
disabled={(stateZoomValue === minBound) || !isMeteorConnected}
|
||||
color="default"
|
||||
customIcon={`${parseInt(displayZoom * 100)}%`}
|
||||
customIcon={stateZoomPct}
|
||||
size="md"
|
||||
onClick={() => tldrawAPI?.zoomTo(1)}
|
||||
onClick={() => this.resetZoom()}
|
||||
label={intl.formatMessage(intlMessages.resetZoomLabel)}
|
||||
hideLabel
|
||||
/>
|
||||
@ -232,9 +222,7 @@ class ZoomTool extends PureComponent {
|
||||
label={intl.formatMessage(intlMessages.zoomInLabel)}
|
||||
data-test="zoomInBtn"
|
||||
icon="add"
|
||||
onClick={() => {
|
||||
tldrawAPI.zoomTo(tldrawAPI?.getPageState()?.camera?.zoom + ZOOM_INCREMENT);
|
||||
}}
|
||||
onClick={() => { }}
|
||||
disabled={(zoomValue >= maxBound) || !isMeteorConnected}
|
||||
hideLabel
|
||||
/>
|
||||
|
@ -74,6 +74,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploder.dropzoneLabel',
|
||||
description: 'message warning where drop files for upload',
|
||||
},
|
||||
externalUploadTitle: {
|
||||
id: 'app.presentationUploder.externalUploadTitle',
|
||||
description: 'title for external upload area',
|
||||
},
|
||||
externalUploadLabel: {
|
||||
id: 'app.presentationUploder.externalUploadLabel',
|
||||
description: 'message of external upload button',
|
||||
},
|
||||
dropzoneImagesLabel: {
|
||||
id: 'app.presentationUploder.dropzoneImagesLabel',
|
||||
description: 'message warning where drop images for upload',
|
||||
@ -294,6 +302,7 @@ class PresentationUploader extends Component {
|
||||
this.handleSendToChat = this.handleSendToChat.bind(this);
|
||||
// renders
|
||||
this.renderDropzone = this.renderDropzone.bind(this);
|
||||
this.renderExternalUpload = this.renderExternalUpload.bind(this);
|
||||
this.renderPicDropzone = this.renderPicDropzone.bind(this);
|
||||
this.renderPresentationList = this.renderPresentationList.bind(this);
|
||||
this.renderPresentationItem = this.renderPresentationItem.bind(this);
|
||||
@ -319,22 +328,45 @@ class PresentationUploader extends Component {
|
||||
...propPresentations,
|
||||
...presentations,
|
||||
});
|
||||
const presStateMapped = presState.map((presentation) => {
|
||||
propPresentations.forEach((propPres) => {
|
||||
if (propPres.id === presentation.id) {
|
||||
const prevPropPres = prevPropPresentations.find((pres) => pres.id === propPres.id);
|
||||
if (propPres.isCurrent !== prevPropPres?.isCurrent) {
|
||||
presentation.isCurrent = propPres.isCurrent;
|
||||
shouldUpdateState = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return presentation;
|
||||
const presStateFiltered = presState.filter((presentation) => {
|
||||
const currentPropPres = propPresentations.find((pres) => pres.id === presentation.id);
|
||||
const prevPropPres = prevPropPresentations.find((pres) => pres.id === presentation.id);
|
||||
const hasConversionError = presentation?.conversion?.error;
|
||||
const finishedConversion = presentation?.conversion?.done || currentPropPres?.conversion?.done;
|
||||
const hasTemporaryId = presentation.id.startsWith(presentation.filename);
|
||||
|
||||
if (hasConversionError || (!finishedConversion && hasTemporaryId)) return true;
|
||||
if (!currentPropPres) return false;
|
||||
|
||||
if(presentation?.conversion?.done !== finishedConversion) {
|
||||
shouldUpdateState = true;
|
||||
}
|
||||
|
||||
if (currentPropPres.isCurrent !== prevPropPres?.isCurrent) {
|
||||
presentation.isCurrent = currentPropPres.isCurrent;
|
||||
}
|
||||
|
||||
presentation.conversion = currentPropPres.conversion;
|
||||
presentation.isRemovable = currentPropPres.isRemovable;
|
||||
|
||||
return true;
|
||||
}).filter(presentation => {
|
||||
const duplicated = presentations.find(
|
||||
(pres) => pres.filename === presentation.filename
|
||||
&& pres.id !== presentation.id
|
||||
);
|
||||
if (duplicated
|
||||
&& duplicated.id.startsWith(presentation.filename)
|
||||
&& !presentation.id.startsWith(presentation.filename)
|
||||
&& presentation?.conversion?.done === duplicated?.conversion?.done) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (shouldUpdateState) {
|
||||
this.setState({
|
||||
presentations: presStateMapped,
|
||||
presentations: _.uniqBy(presStateFiltered, 'id')
|
||||
});
|
||||
}
|
||||
|
||||
@ -816,7 +848,7 @@ class PresentationUploader extends Component {
|
||||
</Styled.Head>
|
||||
</thead>
|
||||
<tbody>
|
||||
{presentationsSorted.map((item) => this.renderPresentationItem(item))}
|
||||
{_.uniqBy(presentationsSorted, 'id').map((item) => this.renderPresentationItem(item))}
|
||||
</tbody>
|
||||
</Styled.Table>
|
||||
</Styled.FileList>
|
||||
@ -834,7 +866,7 @@ class PresentationUploader extends Component {
|
||||
let converted = 0;
|
||||
|
||||
let presentationsSorted = presentations
|
||||
.filter((p) => (p.upload.progress || p.conversion.status) && p.file)
|
||||
.filter((p) => (p.upload.progress || p.upload.error || p.conversion.status) && p.file)
|
||||
.sort((a, b) => a.uploadTimestamp - b.uploadTimestamp)
|
||||
.sort((a, b) => a.conversion.done - b.conversion.done);
|
||||
|
||||
@ -1150,6 +1182,32 @@ class PresentationUploader extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderExternalUpload() {
|
||||
const { externalUploadData, intl } = this.props;
|
||||
|
||||
const { uploadExternalDescription, uploadExternalUrl } = externalUploadData;
|
||||
|
||||
if (!uploadExternalDescription || !uploadExternalUrl) return null;
|
||||
|
||||
return (
|
||||
<Styled.ExternalUpload>
|
||||
<div>
|
||||
<Styled.ExternalUploadTitle>
|
||||
{intl.formatMessage(intlMessages.externalUploadTitle)}
|
||||
</Styled.ExternalUploadTitle>
|
||||
|
||||
<p>{uploadExternalDescription}</p>
|
||||
</div>
|
||||
<Styled.ExternalUploadButton
|
||||
color="default"
|
||||
onClick={() => window.open(`${uploadExternalUrl}`)}
|
||||
label={intl.formatMessage(intlMessages.externalUploadLabel)}
|
||||
aria-describedby={intl.formatMessage(intlMessages.externalUploadLabel)}
|
||||
/>
|
||||
</Styled.ExternalUpload>
|
||||
)
|
||||
}
|
||||
|
||||
renderPicDropzone() {
|
||||
const {
|
||||
intl,
|
||||
@ -1192,7 +1250,7 @@ class PresentationUploader extends Component {
|
||||
}
|
||||
|
||||
renderPresentationItemStatus(item) {
|
||||
const { intl } = this.props;
|
||||
const { intl, fileSizeMax } = this.props;
|
||||
if (!item.upload.done && item.upload.progress === 0) {
|
||||
return intl.formatMessage(intlMessages.fileToUpload);
|
||||
}
|
||||
@ -1206,13 +1264,13 @@ class PresentationUploader extends Component {
|
||||
const constraint = {};
|
||||
|
||||
if (item.upload.done && item.upload.error) {
|
||||
if (item.conversion.status === 'FILE_TOO_LARGE') {
|
||||
constraint['0'] = ((item.conversion.maxFileSize) / 1000 / 1000).toFixed(2);
|
||||
}
|
||||
|
||||
if (item.upload.progress < 100) {
|
||||
const errorMessage = intlMessages.badConnectionError;
|
||||
return intl.formatMessage(errorMessage);
|
||||
if (item.conversion.status === 'FILE_TOO_LARGE' || item.upload.status === 413) {
|
||||
constraint['0'] = (fileSizeMax / 1000 / 1000).toFixed(2);
|
||||
} else {
|
||||
if (item.upload.progress < 100) {
|
||||
const errorMessage = intlMessages.badConnectionError;
|
||||
return intl.formatMessage(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
const errorMessage = intlMessages[item.upload.status] || intlMessages.genericError;
|
||||
@ -1302,6 +1360,7 @@ class PresentationUploader extends Component {
|
||||
</Styled.ExportHint>
|
||||
{isMobile ? this.renderPicDropzone() : null}
|
||||
{this.renderDropzone()}
|
||||
{this.renderExternalUpload()}
|
||||
</Styled.ModalInner>
|
||||
</Styled.UploaderModal>
|
||||
) : null;
|
||||
|
@ -52,5 +52,6 @@ export default withTracker(() => {
|
||||
exportPresentationToChat,
|
||||
isOpen: Session.get('showUploadPresentationView') || false,
|
||||
selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null,
|
||||
externalUploadData: Service.getExternalUploadData(),
|
||||
};
|
||||
})(PresentationUploaderContainer);
|
||||
|
@ -6,6 +6,7 @@ import { makeCall } from '/imports/ui/services/api';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import _ from 'lodash';
|
||||
import { Random } from 'meteor/random'
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
const CONVERSION_TIMEOUT = 300000;
|
||||
const TOKEN_TIMEOUT = 5000;
|
||||
@ -70,7 +71,7 @@ const dispatchTogglePresentationDownloadable = (presentation, newState) => {
|
||||
|
||||
const observePresentationConversion = (
|
||||
meetingId,
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
onConversion,
|
||||
) => new Promise((resolve) => {
|
||||
const conversionTimeout = setTimeout(() => {
|
||||
@ -91,7 +92,7 @@ const observePresentationConversion = (
|
||||
|
||||
query.observe({
|
||||
added: (doc) => {
|
||||
if (doc.temporaryPresentationId !== tmpPresId) return;
|
||||
if (doc.temporaryPresentationId !== temporaryPresentationId) return;
|
||||
|
||||
if (doc.conversion.status === 'FILE_TOO_LARGE' || doc.conversion.status === 'UNSUPPORTED_DOCUMENT') {
|
||||
onConversion(doc.conversion);
|
||||
@ -100,7 +101,7 @@ const observePresentationConversion = (
|
||||
}
|
||||
},
|
||||
changed: (newDoc) => {
|
||||
if (newDoc.temporaryPresentationId !== tmpPresId) return;
|
||||
if (newDoc.temporaryPresentationId !== temporaryPresentationId) return;
|
||||
|
||||
onConversion(newDoc.conversion);
|
||||
|
||||
@ -119,12 +120,12 @@ const observePresentationConversion = (
|
||||
});
|
||||
|
||||
const requestPresentationUploadToken = (
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
podId,
|
||||
meetingId,
|
||||
filename,
|
||||
) => new Promise((resolve, reject) => {
|
||||
makeCall('requestPresentationUploadToken', podId, filename, tmpPresId);
|
||||
makeCall('requestPresentationUploadToken', podId, filename, temporaryPresentationId);
|
||||
|
||||
let computation = null;
|
||||
const timeout = setTimeout(() => {
|
||||
@ -134,13 +135,13 @@ const requestPresentationUploadToken = (
|
||||
|
||||
Tracker.autorun((c) => {
|
||||
computation = c;
|
||||
const sub = Meteor.subscribe('presentation-upload-token', podId, filename, tmpPresId);
|
||||
const sub = Meteor.subscribe('presentation-upload-token', podId, filename, temporaryPresentationId);
|
||||
if (!sub.ready()) return;
|
||||
|
||||
const PresentationToken = PresentationUploadToken.findOne({
|
||||
podId,
|
||||
meetingId,
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
used: false,
|
||||
});
|
||||
|
||||
@ -167,13 +168,13 @@ const uploadAndConvertPresentation = (
|
||||
onProgress,
|
||||
onConversion,
|
||||
) => {
|
||||
const tmpPresId = _.uniqueId(Random.id(20))
|
||||
const temporaryPresentationId = _.uniqueId(Random.id(20))
|
||||
|
||||
const data = new FormData();
|
||||
data.append('fileUpload', file);
|
||||
data.append('conference', meetingId);
|
||||
data.append('room', meetingId);
|
||||
data.append('temporaryPresentationId', tmpPresId);
|
||||
data.append('temporaryPresentationId', temporaryPresentationId);
|
||||
|
||||
// TODO: Currently the uploader is not related to a POD so the id is fixed to the default
|
||||
data.append('pod_id', podId);
|
||||
@ -185,12 +186,12 @@ const uploadAndConvertPresentation = (
|
||||
body: data,
|
||||
};
|
||||
|
||||
return requestPresentationUploadToken(tmpPresId, podId, meetingId, file.name)
|
||||
return requestPresentationUploadToken(temporaryPresentationId, podId, meetingId, file.name)
|
||||
.then((token) => {
|
||||
makeCall('setUsedToken', token);
|
||||
return futch(endpoint.replace('upload', `${token}/upload`), opts, onProgress);
|
||||
})
|
||||
.then(() => observePresentationConversion(meetingId, tmpPresId, onConversion))
|
||||
.then(() => observePresentationConversion(meetingId, temporaryPresentationId, onConversion))
|
||||
// Trap the error so we can have parallel upload
|
||||
.catch((error) => {
|
||||
logger.debug({
|
||||
@ -199,7 +200,7 @@ const uploadAndConvertPresentation = (
|
||||
error,
|
||||
},
|
||||
}, 'Generic presentation upload exception catcher');
|
||||
observePresentationConversion(meetingId, tmpPresId, onConversion);
|
||||
observePresentationConversion(meetingId, temporaryPresentationId, onConversion);
|
||||
onUpload({ error: true, done: true, status: error.code });
|
||||
return Promise.resolve();
|
||||
});
|
||||
@ -270,6 +271,25 @@ const persistPresentationChanges = (oldState, newState, uploadEndpoint, podId) =
|
||||
.then(removePresentations.bind(null, presentationsToRemove, podId));
|
||||
};
|
||||
|
||||
const getExternalUploadData = () => {
|
||||
const { meetingProp } = Meetings.findOne(
|
||||
{ meetingId: Auth.meetingID },
|
||||
{
|
||||
fields: {
|
||||
'meetingProp.uploadExternalDescription': 1,
|
||||
'meetingProp.uploadExternalUrl': 1
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { uploadExternalDescription, uploadExternalUrl } = meetingProp;
|
||||
|
||||
return {
|
||||
uploadExternalDescription,
|
||||
uploadExternalUrl,
|
||||
}
|
||||
};
|
||||
|
||||
const exportPresentationToChat = (presentationId, observer) => {
|
||||
let lastStatus = {};
|
||||
|
||||
@ -307,5 +327,6 @@ export default {
|
||||
dispatchTogglePresentationDownloadable,
|
||||
setPresentation,
|
||||
requestPresentationUploadToken,
|
||||
getExternalUploadData,
|
||||
exportPresentationToChat,
|
||||
};
|
||||
|
@ -541,6 +541,7 @@ const DownloadButton = styled(Button)`
|
||||
color: ${colorBlueLight};
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 80%;
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorOffWhite} !important;
|
||||
@ -581,6 +582,33 @@ const ExtraHint = styled.div`
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const ExternalUpload = styled.div`
|
||||
background-color: ${colorOffWhite};
|
||||
border-radius: ${borderRadius};
|
||||
margin-top: 2rem;
|
||||
padding: ${lgPaddingX};
|
||||
color: ${colorText};
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
|
||||
& p {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const ExternalUploadTitle = styled.h4`
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const ExternalUploadButton = styled(Button)`
|
||||
height: 2rem;
|
||||
align-self: center;
|
||||
margin-left: 2rem;
|
||||
`;
|
||||
|
||||
const ExportHint = styled(ModalHint)`
|
||||
margin: 2rem 0;
|
||||
`;
|
||||
@ -671,6 +699,9 @@ export default {
|
||||
TableItemActions,
|
||||
DownloadButton,
|
||||
ExtraHint,
|
||||
ExternalUpload,
|
||||
ExternalUploadTitle,
|
||||
ExternalUploadButton,
|
||||
ExportHint,
|
||||
SetCurrentAction,
|
||||
Head,
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const ScreenReaderAlertCollection = new Mongo.Collection('Screenreader-alert', { connection: null });
|
||||
|
||||
export default ScreenReaderAlertCollection;
|
||||
|
@ -483,6 +483,7 @@ class ScreenshareComponent extends React.Component {
|
||||
ref={(ref) => {
|
||||
this.screenshareContainer = ref;
|
||||
}}
|
||||
id="screenshareContainer"
|
||||
>
|
||||
{loaded && this.renderFullscreenButton()}
|
||||
{this.renderVideo(true)}
|
||||
|
@ -75,7 +75,7 @@ const HoverToolbar = styled.div`
|
||||
${({ toolbarStyle }) => toolbarStyle === 'hoverToolbar' && `
|
||||
display: none;
|
||||
|
||||
:hover > & {
|
||||
#screenshareContainer:hover > & {
|
||||
display: flex;
|
||||
}
|
||||
`}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user