Merge branch 'v2.6.x-release' into issue-15536
This commit is contained in:
commit
aefc5c6c6a
17
.github/workflows/automated-tests.yml
vendored
17
.github/workflows/automated-tests.yml
vendored
@ -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)
|
||||
}
|
||||
|
@ -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],
|
||||
)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,15 +378,15 @@ start_bigbluebutton () {
|
||||
|
||||
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
|
||||
TOMCAT_SERVICE=$TOMCAT_USER
|
||||
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"
|
||||
}
|
||||
[ -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 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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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';
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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';
|
||||
|
||||
|
@ -77,8 +77,6 @@ class Presentation extends PureComponent {
|
||||
fitToWidth: false,
|
||||
isFullscreen: false,
|
||||
tldrawAPI: null,
|
||||
isZoomed: false,
|
||||
hadPresentation: false,
|
||||
isPanning: false,
|
||||
};
|
||||
|
||||
@ -94,8 +92,9 @@ 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);
|
||||
|
||||
@ -129,6 +128,12 @@ class Presentation extends PureComponent {
|
||||
return stateChange;
|
||||
}
|
||||
|
||||
setIsPanning() {
|
||||
this.setState({
|
||||
isPanning: !this.state.isPanning,
|
||||
});
|
||||
}
|
||||
|
||||
handlePanShortcut(e) {
|
||||
const { userIsPresenter } = this.props;
|
||||
if (e.keyCode === SPACE && userIsPresenter) {
|
||||
@ -187,7 +192,7 @@ class Presentation extends PureComponent {
|
||||
clearFakeAnnotations,
|
||||
} = this.props;
|
||||
|
||||
const { presentationWidth, presentationHeight, isZoomed, isPanning } = this.state;
|
||||
const { presentationWidth, presentationHeight, zoom, isPanning } = this.state;
|
||||
const {
|
||||
numCameras: prevNumCameras,
|
||||
presentationBounds: prevPresentationBounds,
|
||||
@ -289,7 +294,7 @@ class Presentation extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
if (!isZoomed && isPanning || !userIsPresenter && prevProps.userIsPresenter) {
|
||||
if (zoom <= HUNDRED_PERCENT && isPanning || !userIsPresenter && prevProps.userIsPresenter) {
|
||||
this.setIsPanning();
|
||||
}
|
||||
}
|
||||
@ -321,11 +326,6 @@ class Presentation extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
setIsZoomed(isZoomed) {
|
||||
this.setState({
|
||||
isZoomed,
|
||||
});
|
||||
}
|
||||
|
||||
setIsPanning() {
|
||||
this.setState({
|
||||
@ -553,7 +553,6 @@ class Presentation extends PureComponent {
|
||||
const {
|
||||
zoom,
|
||||
fitToWidth,
|
||||
isZoomed,
|
||||
} = this.state;
|
||||
|
||||
if (!userIsPresenter && !multiUser) {
|
||||
@ -604,8 +603,6 @@ class Presentation extends PureComponent {
|
||||
physicalSlideHeight={physicalDimensions.height}
|
||||
zoom={zoom}
|
||||
zoomChanger={this.zoomChanger}
|
||||
setIsZoomed={this.setIsZoomed}
|
||||
isZoomed={isZoomed}
|
||||
/>
|
||||
</PresentationOverlayContainer>
|
||||
);
|
||||
@ -782,8 +779,6 @@ class Presentation extends PureComponent {
|
||||
}}
|
||||
setIsPanning={this.setIsPanning}
|
||||
isPanning={this.state.isPanning}
|
||||
isZoomed={this.state.isZoomed}
|
||||
tldrawAPI={this.state.tldrawAPI}
|
||||
curPageId={this.state.tldrawAPI?.getPage()?.id}
|
||||
currentSlideNum={currentSlide.num}
|
||||
presentationId={currentSlide.presentationId}
|
||||
@ -927,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;
|
||||
@ -972,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 (
|
||||
<></>
|
||||
@ -1004,27 +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}
|
||||
isPanning={this.state.isPanning}
|
||||
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";
|
||||
|
@ -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`
|
||||
|
@ -4,8 +4,6 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
|
||||
import {
|
||||
HUNDRED_PERCENT,
|
||||
MAX_PERCENT,
|
||||
STEP,
|
||||
} from '/imports/utils/slideCalcUtils';
|
||||
import Styled from './styles';
|
||||
@ -13,8 +11,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',
|
||||
@ -109,6 +105,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);
|
||||
}
|
||||
@ -258,11 +259,9 @@ class PresentationToolbar extends PureComponent {
|
||||
startPoll,
|
||||
currentSlide,
|
||||
slidePosition,
|
||||
tldrawAPI,
|
||||
toolbarWidth,
|
||||
multiUserSize,
|
||||
multiUser,
|
||||
isZoomed,
|
||||
setIsPanning,
|
||||
isPanning,
|
||||
} = this.props;
|
||||
@ -394,12 +393,10 @@ 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}
|
||||
@ -408,7 +405,7 @@ class PresentationToolbar extends PureComponent {
|
||||
data-test="panButton"
|
||||
aria-label={intl.formatMessage(intlMessages.pan)}
|
||||
color="light"
|
||||
disabled={!isZoomed}
|
||||
disabled={(zoom <= HUNDRED_PERCENT)}
|
||||
icon="hand"
|
||||
size="md"
|
||||
circle
|
||||
@ -435,8 +432,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) => {
|
||||
|
@ -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
|
||||
/>
|
||||
|
@ -319,31 +319,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;
|
||||
}).filter((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')
|
||||
});
|
||||
}
|
||||
|
||||
@ -825,7 +839,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>
|
||||
@ -843,7 +857,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);
|
||||
|
||||
@ -1201,7 +1215,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);
|
||||
}
|
||||
@ -1215,13 +1229,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;
|
||||
|
@ -70,7 +70,7 @@ const dispatchTogglePresentationDownloadable = (presentation, newState) => {
|
||||
|
||||
const observePresentationConversion = (
|
||||
meetingId,
|
||||
tmpPresId,
|
||||
temporaryPresentationId,
|
||||
onConversion,
|
||||
) => new Promise((resolve) => {
|
||||
const conversionTimeout = setTimeout(() => {
|
||||
@ -91,7 +91,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 +100,7 @@ const observePresentationConversion = (
|
||||
}
|
||||
},
|
||||
changed: (newDoc) => {
|
||||
if (newDoc.temporaryPresentationId !== tmpPresId) return;
|
||||
if (newDoc.temporaryPresentationId !== temporaryPresentationId) return;
|
||||
|
||||
onConversion(newDoc.conversion);
|
||||
|
||||
@ -119,12 +119,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 +134,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 +167,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 +185,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 +199,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();
|
||||
});
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const ScreenReaderAlertCollection = new Mongo.Collection('Screenreader-alert', { connection: null });
|
||||
|
||||
export default ScreenReaderAlertCollection;
|
||||
|
@ -354,13 +354,13 @@ class VideoPreview extends Component {
|
||||
};
|
||||
|
||||
// Resolves into true if the background switch is successful, false otherwise
|
||||
handleVirtualBgSelected(type, name) {
|
||||
handleVirtualBgSelected(type, name, customParams) {
|
||||
const { sharedDevices } = this.props;
|
||||
const { webcamDeviceId } = this.state;
|
||||
const shared = sharedDevices.includes(webcamDeviceId);
|
||||
|
||||
if (type !== EFFECT_TYPES.NONE_TYPE) {
|
||||
return this.startVirtualBackground(this.currentVideoStream, type, name).then((switched) => {
|
||||
return this.startVirtualBackground(this.currentVideoStream, type, name, customParams).then((switched) => {
|
||||
// If it's not shared we don't have to update here because
|
||||
// it will be updated in the handleStartSharing method.
|
||||
if (switched && shared) this.updateVirtualBackgroundInfo();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import _ from "lodash";
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
import Cursors from "./cursors/container";
|
||||
import { TldrawApp, Tldraw } from "@tldraw/tldraw";
|
||||
import {
|
||||
@ -8,8 +9,7 @@ import {
|
||||
SizeStyle,
|
||||
TDShapeType,
|
||||
} from "@tldraw/tldraw";
|
||||
import { Utils } from "@tldraw/core";
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
import SlideCalcUtil, {HUNDRED_PERCENT} from '/imports/utils/slideCalcUtils';
|
||||
|
||||
function usePrevious(value) {
|
||||
const ref = React.useRef();
|
||||
@ -57,11 +57,13 @@ export default function Whiteboard(props) {
|
||||
presentationWidth,
|
||||
presentationHeight,
|
||||
isViewersCursorLocked,
|
||||
setIsZoomed,
|
||||
zoomChanger,
|
||||
isZoomed,
|
||||
isMultiUserActive,
|
||||
isRTL,
|
||||
fitToWidth,
|
||||
zoomValue,
|
||||
width,
|
||||
height,
|
||||
isPanning,
|
||||
} = props;
|
||||
|
||||
@ -77,29 +79,21 @@ export default function Whiteboard(props) {
|
||||
});
|
||||
const [tldrawAPI, setTLDrawAPI] = React.useState(null);
|
||||
const [forcePanning, setForcePanning] = React.useState(false);
|
||||
const [cameraFitSlide, setCameraFitSlide] = React.useState({point: [0, 0], zoom: 0});
|
||||
const [zoom, setZoom] = React.useState(HUNDRED_PERCENT);
|
||||
const [isMounting, setIsMounting] = React.useState(true);
|
||||
const prevShapes = usePrevious(shapes);
|
||||
const prevSlidePosition = usePrevious(slidePosition);
|
||||
const prevFitToWidth = usePrevious(fitToWidth);
|
||||
|
||||
const calculateCameraFitSlide = () => {
|
||||
let zoom =
|
||||
Math.min(
|
||||
(presentationWidth) / slidePosition.width,
|
||||
(presentationHeight) / slidePosition.height
|
||||
);
|
||||
const calculateZoom = (width, height) => {
|
||||
let zoom = fitToWidth
|
||||
? presentationWidth / width
|
||||
: Math.min(
|
||||
(presentationWidth) / width,
|
||||
(presentationHeight) / height
|
||||
);
|
||||
|
||||
zoom = Utils.clamp(zoom, 0.1, 5);
|
||||
|
||||
let point = [0, 0];
|
||||
if ((presentationWidth / presentationHeight) >
|
||||
(slidePosition.width / slidePosition.height))
|
||||
{
|
||||
point[0] = (presentationWidth - (slidePosition.width * zoom)) / 2 / zoom
|
||||
} else {
|
||||
point[1] = (presentationHeight - (slidePosition.height * zoom)) / 2 / zoom
|
||||
}
|
||||
|
||||
isPresenter && zoomChanger(zoom);
|
||||
return {point, zoom}
|
||||
return zoom;
|
||||
}
|
||||
|
||||
const doc = React.useMemo(() => {
|
||||
@ -123,7 +117,7 @@ export default function Whiteboard(props) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (next.pages[curPageId] && !next.pages[curPageId].shapes["slide-background-shape"]) {
|
||||
if (curPageId && next.pages[curPageId] && !next.pages[curPageId].shapes["slide-background-shape"]) {
|
||||
next.assets[`slide-background-asset-${curPageId}`] = {
|
||||
id: `slide-background-asset-${curPageId}`,
|
||||
size: [slidePosition?.width || 0, slidePosition?.height || 0],
|
||||
@ -181,13 +175,28 @@ export default function Whiteboard(props) {
|
||||
}, [shapes, tldrawAPI, curPageId, slidePosition]);
|
||||
|
||||
// when presentationSizes change, update tldraw camera
|
||||
// to fit slide on center if zoomed out
|
||||
React.useEffect(() => {
|
||||
if (curPageId && slidePosition) {
|
||||
const camera = calculateCameraFitSlide();
|
||||
setCameraFitSlide(camera);
|
||||
if (!isZoomed) {
|
||||
tldrawAPI?.setCamera(camera.point, camera.zoom);
|
||||
if (curPageId && slidePosition && tldrawAPI && presentationWidth !== 0 && presentationHeight !== 0) {
|
||||
if (prevFitToWidth !== null && fitToWidth !== prevFitToWidth) {
|
||||
const zoom = calculateZoom(slidePosition.width, slidePosition.height)
|
||||
tldrawAPI?.setCamera([0, 0], zoom);
|
||||
const viewedRegionH = SlideCalcUtil.calcViewedRegionHeight(tldrawAPI?.viewport.width, slidePosition.height);
|
||||
zoomSlide(parseInt(curPageId), podId, HUNDRED_PERCENT, viewedRegionH, 0, 0);
|
||||
} else {
|
||||
const currentAspectRatio = Math.round((presentationWidth / presentationHeight) * 100) / 100;
|
||||
const previousAspectRatio = Math.round((slidePosition.viewBoxWidth / slidePosition.viewBoxHeight) * 100) / 100;
|
||||
if (isMounting && currentAspectRatio !== previousAspectRatio) {
|
||||
// wee need this to ensure tldraw updates the viewport size
|
||||
// after re-mounting
|
||||
setTimeout(() => {
|
||||
const zoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight);
|
||||
tldrawAPI.setCamera([slidePosition.x, slidePosition.y], zoom, 'zoomed');
|
||||
setIsMounting(false);
|
||||
}, 50);
|
||||
} else {
|
||||
const zoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight);
|
||||
tldrawAPI?.setCamera([slidePosition.x, slidePosition.y], zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [presentationWidth, presentationHeight, curPageId, document?.documentElement?.dir]);
|
||||
@ -195,32 +204,48 @@ export default function Whiteboard(props) {
|
||||
// change tldraw page when presentation page changes
|
||||
React.useEffect(() => {
|
||||
if (tldrawAPI && curPageId) {
|
||||
const previousPageZoom = tldrawAPI.getPageState()?.camera?.zoom;
|
||||
tldrawAPI.changePage(curPageId);
|
||||
//change zoom of the new page to follow the previous one
|
||||
if (!isZoomed && cameraFitSlide.zoom !== 0) {
|
||||
tldrawAPI?.setCamera(cameraFitSlide.point, cameraFitSlide.zoom, "zoomed");
|
||||
} else {
|
||||
previousPageZoom &&
|
||||
slidePosition &&
|
||||
tldrawAPI.setCamera([slidePosition.xCamera, slidePosition.yCamera], previousPageZoom, "zoomed");
|
||||
}
|
||||
let zoom = prevSlidePosition
|
||||
? calculateZoom(prevSlidePosition.viewBoxWidth, prevSlidePosition.viewBoxHeight)
|
||||
: calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight)
|
||||
tldrawAPI?.setCamera([slidePosition.x, slidePosition.y], zoom, 'zoomed_previous_page');
|
||||
}
|
||||
}, [curPageId]);
|
||||
|
||||
// change tldraw camera when slidePosition changes
|
||||
React.useEffect(() => {
|
||||
if (tldrawAPI && !isPresenter && curPageId && slidePosition) {
|
||||
if (slidePosition.zoom === 0 && slidePosition.xCamera === 0 && slidePosition.yCamera === 0) {
|
||||
tldrawAPI?.setCamera(cameraFitSlide.point, cameraFitSlide.zoom);
|
||||
setIsZoomed(false);
|
||||
} else {
|
||||
tldrawAPI?.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom);
|
||||
setIsZoomed(true);
|
||||
}
|
||||
const zoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight)
|
||||
tldrawAPI?.setCamera([slidePosition.x, slidePosition.y], zoom, 'zoomed');
|
||||
}
|
||||
}, [curPageId, slidePosition]);
|
||||
|
||||
// update zoom according to toolbar
|
||||
React.useEffect(() => {
|
||||
if (tldrawAPI && isPresenter && curPageId && slidePosition && zoom !== zoomValue) {
|
||||
const zoomFitSlide = calculateZoom(slidePosition.width, slidePosition.height);
|
||||
const zoomCamera = (zoomFitSlide * zoomValue) / HUNDRED_PERCENT;
|
||||
tldrawAPI?.zoomTo(zoomCamera);
|
||||
}
|
||||
}, [zoomValue]);
|
||||
|
||||
// update zoom when presenter changes
|
||||
React.useEffect(() => {
|
||||
if (tldrawAPI && isPresenter && curPageId && slidePosition) {
|
||||
const currentAspectRatio = Math.round((presentationWidth / presentationHeight) * 100) / 100;
|
||||
const previousAspectRatio = Math.round((slidePosition.viewBoxWidth / slidePosition.viewBoxHeight) * 100) / 100;
|
||||
if (previousAspectRatio !== currentAspectRatio && fitToWidth) {
|
||||
const zoom = calculateZoom(slidePosition.width, slidePosition.height)
|
||||
tldrawAPI?.setCamera([0, 0], zoom);
|
||||
const viewedRegionH = SlideCalcUtil.calcViewedRegionHeight(tldrawAPI?.viewport.width, slidePosition.height);
|
||||
zoomSlide(parseInt(curPageId), podId, HUNDRED_PERCENT, viewedRegionH, 0, 0);
|
||||
} else {
|
||||
const zoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight)
|
||||
tldrawAPI.setCamera([slidePosition.x, slidePosition.y], zoom, 'zoomed');
|
||||
}
|
||||
}
|
||||
}, [isPresenter]);
|
||||
|
||||
const hasWBAccess = props?.hasMultiUserAccess(props.whiteboardId, props.currentUser.userId);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -294,54 +319,70 @@ export default function Whiteboard(props) {
|
||||
|
||||
if (curPageId) {
|
||||
app.changePage(curPageId);
|
||||
if (slidePosition.zoom === 0) {
|
||||
// first load, center the view to fit slide
|
||||
const cameraFitSlide = calculateCameraFitSlide();
|
||||
app.setCamera(cameraFitSlide.point, cameraFitSlide.zoom);
|
||||
setCameraFitSlide(cameraFitSlide);
|
||||
} else {
|
||||
app.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom);
|
||||
setIsZoomed(true);
|
||||
if (presentationWidth > 0 && presentationHeight > 0) {
|
||||
const zoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight);
|
||||
// wee need this to ensure tldraw updates the viewport size
|
||||
// after re-mounting
|
||||
setTimeout(() => {
|
||||
app.setCamera([slidePosition.x, slidePosition.y], zoom, 'zoomed');
|
||||
setIsMounting(false);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPatch = (e, t, reason) => {
|
||||
if (reason && isPresenter && (reason.includes("zoomed") || reason.includes("panned"))) {
|
||||
if (cameraFitSlide.zoom === 0) {
|
||||
//can happen when the slide finish uploading
|
||||
const cameraFitSlide = calculateCameraFitSlide();
|
||||
tldrawAPI?.setCamera(cameraFitSlide.point, cameraFitSlide.zoom);
|
||||
setIsZoomed(false);
|
||||
setCameraFitSlide(cameraFitSlide);
|
||||
return;
|
||||
}
|
||||
const camera = tldrawAPI.getPageState()?.camera;
|
||||
//don't allow zoom out more than fit
|
||||
if (camera.zoom <= cameraFitSlide.zoom) {
|
||||
tldrawAPI?.setCamera(cameraFitSlide.point, cameraFitSlide.zoom);
|
||||
setIsZoomed(false);
|
||||
zoomSlide(parseInt(curPageId), podId, 0, 0, 0);
|
||||
} else {
|
||||
zoomSlide(parseInt(curPageId), podId, camera.zoom, camera.point[0], camera.point[1]);
|
||||
setIsZoomed(true);
|
||||
|
||||
// limit bounds
|
||||
if (tldrawAPI?.viewport.maxX > slidePosition.width) {
|
||||
camera.point[0] = camera.point[0] + (tldrawAPI?.viewport.maxX - slidePosition.width);
|
||||
}
|
||||
if (tldrawAPI?.viewport.maxY > slidePosition.height) {
|
||||
camera.point[1] = camera.point[1] + (tldrawAPI?.viewport.maxY - slidePosition.height);
|
||||
}
|
||||
if (camera.point[0] > 0 || tldrawAPI?.viewport.minX < 0) {
|
||||
camera.point[0] = 0;
|
||||
}
|
||||
if (camera.point[1] > 0 || tldrawAPI?.viewport.minY < 0) {
|
||||
camera.point[1] = 0;
|
||||
}
|
||||
const zoomFitSlide = calculateZoom(slidePosition.width, slidePosition.height);
|
||||
if (camera.zoom < zoomFitSlide) {
|
||||
camera.zoom = zoomFitSlide;
|
||||
}
|
||||
|
||||
tldrawAPI?.setCamera([camera.point[0], camera.point[1]], camera.zoom);
|
||||
|
||||
const zoomToolbar = Math.round((HUNDRED_PERCENT * camera.zoom) / zoomFitSlide * 100) / 100;
|
||||
if (zoom !== zoomToolbar) {
|
||||
setZoom(zoomToolbar);
|
||||
isPresenter && zoomChanger(zoomToolbar);
|
||||
}
|
||||
|
||||
let viewedRegionW = SlideCalcUtil.calcViewedRegionWidth(tldrawAPI?.viewport.height, slidePosition.width);
|
||||
let viewedRegionH = SlideCalcUtil.calcViewedRegionHeight(tldrawAPI?.viewport.width, slidePosition.height);
|
||||
|
||||
if (!fitToWidth && camera.zoom === zoomFitSlide) {
|
||||
viewedRegionW = HUNDRED_PERCENT;
|
||||
viewedRegionH = HUNDRED_PERCENT;
|
||||
}
|
||||
|
||||
zoomSlide(parseInt(curPageId), podId, viewedRegionW, viewedRegionH, camera.point[0], camera.point[1]);
|
||||
}
|
||||
//don't allow non-presenters to pan&zoom
|
||||
if (slidePosition && reason && !isPresenter && (reason.includes("zoomed") || reason.includes("panned"))) {
|
||||
if (slidePosition.zoom === 0 && slidePosition.xCamera === 0 && slidePosition.yCamera === 0) {
|
||||
tldrawAPI?.setCamera(cameraFitSlide.point, cameraFitSlide.zoom);
|
||||
setIsZoomed(false);
|
||||
} else {
|
||||
tldrawAPI?.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom);
|
||||
setIsZoomed(true);
|
||||
}
|
||||
const zoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight)
|
||||
tldrawAPI?.setCamera([slidePosition.x, slidePosition.y], zoom);
|
||||
}
|
||||
};
|
||||
|
||||
const webcams = document.getElementById('cameraDock');
|
||||
const dockPos = webcams?.getAttribute("data-position");
|
||||
const editableWB = (
|
||||
<Tldraw
|
||||
key={`wb-${document?.documentElement?.dir}-${document.getElementById('Navbar')?.style?.width}-${forcePanning}`}
|
||||
key={`wb-${isRTL}-${width}-${height}-${dockPos}-${forcePanning}`}
|
||||
document={doc}
|
||||
// disable the ability to drag and drop files onto the whiteboard
|
||||
// until we handle saving of assets in akka.
|
||||
@ -397,11 +438,6 @@ export default function Whiteboard(props) {
|
||||
}
|
||||
}}
|
||||
|
||||
onChangePage={(app, s, b, a) => {
|
||||
if (app.getPage()?.id !== curPageId) {
|
||||
skipToSlide(Number.parseInt(app.getPage()?.id), podId)
|
||||
}
|
||||
}}
|
||||
onCommand={(e, s, g) => {
|
||||
if (s?.id.includes('move_to_page')) {
|
||||
let groupShapes = [];
|
||||
@ -428,6 +464,13 @@ export default function Whiteboard(props) {
|
||||
persistShape(e.getShape(c), newWhiteboardId)
|
||||
});
|
||||
});
|
||||
if (isPresenter) {
|
||||
// change slide for others
|
||||
skipToSlide(Number.parseInt(e.getPage()?.id), podId)
|
||||
} else {
|
||||
// ignore, stay on same page
|
||||
e.changePage(curPageId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -557,7 +600,6 @@ export default function Whiteboard(props) {
|
||||
isViewersCursorLocked={isViewersCursorLocked}
|
||||
isMultiUserActive={isMultiUserActive}
|
||||
isPanning={isPanning}
|
||||
|
||||
>
|
||||
{hasWBAccess || isPresenter ? editableWB : readOnlyWB}
|
||||
<TldrawGlobalStyle hideContextMenu={!hasWBAccess && !isPresenter} />
|
||||
|
@ -15,7 +15,7 @@ const WhiteboardContainer = (props) => {
|
||||
const { users } = usingUsersContext;
|
||||
const currentUser = users[Auth.meetingID][Auth.userID];
|
||||
const isPresenter = currentUser.presenter;
|
||||
return <Whiteboard {...{isPresenter, currentUser, isRTL, width, height}} {...props} meetingId={Auth.meetingID} />
|
||||
return <Whiteboard {...{ isPresenter, currentUser, isRTL, width, height }} {...props} meetingId={Auth.meetingID} />
|
||||
};
|
||||
|
||||
export default withTracker(({ whiteboardId, curPageId, intl, zoomChanger }) => {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { _ } from "lodash";
|
||||
|
||||
const RESIZE_HANDLE_HEIGHT = 8;
|
||||
const RESIZE_HANDLE_WIDTH = 18;
|
||||
const XS_OFFSET = 8;
|
||||
const SMALL_OFFSET = 18;
|
||||
const XL_OFFSET = 85;
|
||||
const BOTTOM_CAM_HANDLE_HEIGHT = 10;
|
||||
const PRES_TOOLBAR_HEIGHT = 35;
|
||||
|
||||
function usePrevious(value) {
|
||||
const ref = React.useRef();
|
||||
@ -133,8 +134,8 @@ export default function Cursors(props) {
|
||||
|
||||
const end = () => {
|
||||
publishCursorUpdate({
|
||||
xPercent: -1.0,
|
||||
yPercent: -1.0,
|
||||
xPercent: 0,
|
||||
yPercent: 0,
|
||||
whiteboardId: whiteboardId,
|
||||
});
|
||||
setActive(false);
|
||||
@ -142,89 +143,110 @@ export default function Cursors(props) {
|
||||
|
||||
const moved = (event) => {
|
||||
const { type, x, y } = event;
|
||||
// If the presentation container is the full screen element we don't need any offsets
|
||||
const fsEl = document?.webkitFullscreenElement || document?.fullscreenElement;
|
||||
if (fsEl?.getAttribute('data-test') === "presentationContainer") {
|
||||
return setPos({ x, y });
|
||||
}
|
||||
const nav = document.getElementById('Navbar');
|
||||
let yOffset = parseFloat(nav?.style?.height);
|
||||
const getSibling = (el) => el?.previousSibling || null;
|
||||
const panel = getSibling(nav);
|
||||
const webcams = document.getElementById('cameraDock');
|
||||
const subPanel = panel && getSibling(panel);
|
||||
let xOffset = (parseFloat(panel?.style?.width) || 0) + (parseFloat(subPanel?.style?.width) || 0);
|
||||
const camPosition = document.getElementById('layout')?.getAttribute('data-cam-position') || null;
|
||||
const sl = document.getElementById('layout')?.getAttribute('data-layout');
|
||||
const presentationContainer = document.querySelector('[data-test="presentationContainer"]');
|
||||
const presentation = document.getElementById('currentSlideText')?.parentElement;
|
||||
let yOffset = 0;
|
||||
let xOffset = 0;
|
||||
const calcPresOffset = () => {
|
||||
yOffset += (parseFloat(presentationContainer?.style?.height) - (parseFloat(presentation?.style?.height) + (currentUser.presenter ? PRES_TOOLBAR_HEIGHT : 0))) / 2;
|
||||
xOffset += (parseFloat(presentationContainer?.style?.width) - parseFloat(presentation?.style?.width)) / 2;
|
||||
}
|
||||
// If the presentation container is the full screen element we don't need any offsets
|
||||
const fsEl = document?.webkitFullscreenElement || document?.fullscreenElement;
|
||||
if (fsEl?.getAttribute('data-test') === "presentationContainer") {
|
||||
calcPresOffset();
|
||||
return setPos({ x: x - xOffset, y: y - yOffset });
|
||||
}
|
||||
if (nav) yOffset += parseFloat(nav?.style?.height);
|
||||
if (panel) xOffset += parseFloat(panel?.style?.width);
|
||||
if (subPanel) xOffset += parseFloat(subPanel?.style?.width);
|
||||
|
||||
// disable native tldraw eraser animation
|
||||
const eraserLine = document.getElementsByClassName('tl-erase-line')[0];
|
||||
if (eraserLine) eraserLine.style.display = `none`;
|
||||
|
||||
if (type === 'touchmove') {
|
||||
calcPresOffset();
|
||||
!active && setActive(true);
|
||||
return setPos({ x: event?.changedTouches[0]?.clientX - xOffset, y: event?.changedTouches[0]?.clientY - yOffset });
|
||||
}
|
||||
|
||||
const handleCustomYOffsets = () => {
|
||||
if (camPosition === 'contentTop' || !camPosition) {
|
||||
yOffset += (parseFloat(webcams?.style?.height) + RESIZE_HANDLE_HEIGHT);
|
||||
}
|
||||
if (camPosition === 'contentBottom') {
|
||||
yOffset -= BOTTOM_CAM_HANDLE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
if (document?.documentElement?.dir === 'rtl') {
|
||||
if (document?.documentElement?.dir === 'rtl') {
|
||||
xOffset = 0;
|
||||
if (webcams && sl?.includes('custom')) {
|
||||
handleCustomYOffsets();
|
||||
if (camPosition === 'contentRight') {
|
||||
xOffset += (parseFloat(webcams?.style?.width) + RESIZE_HANDLE_WIDTH);
|
||||
if (presentationContainer && presentation) {
|
||||
calcPresOffset();
|
||||
}
|
||||
if (sl.includes('custom')) {
|
||||
if (webcams) {
|
||||
if (camPosition === 'contentTop' || !camPosition) {
|
||||
yOffset += (parseFloat(webcams?.style?.height || 0) + BOTTOM_CAM_HANDLE_HEIGHT);
|
||||
}
|
||||
if (camPosition === 'contentBottom') {
|
||||
yOffset -= BOTTOM_CAM_HANDLE_HEIGHT;
|
||||
}
|
||||
if (camPosition === 'contentRight') {
|
||||
xOffset += (parseFloat(webcams?.style?.width || 0) + SMALL_OFFSET);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (webcams && sl?.includes('smart')) {
|
||||
if (sl?.includes('smart')) {
|
||||
if (panel || subPanel) {
|
||||
const dockPos = webcams?.getAttribute("data-position");
|
||||
if (dockPos === 'contentRight') {
|
||||
xOffset += (parseFloat(webcams?.style?.width) + RESIZE_HANDLE_WIDTH);
|
||||
}
|
||||
if (dockPos === 'contentTop') {
|
||||
yOffset += (parseFloat(webcams?.style?.height) + RESIZE_HANDLE_WIDTH);
|
||||
yOffset += (parseFloat(webcams?.style?.height || 0) + SMALL_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
if (!panel && !subPanel) {
|
||||
xOffset = 0;
|
||||
}
|
||||
}
|
||||
if (webcams && sl?.includes('videoFocus')) {
|
||||
xOffset = parseFloat(nav?.style?.width);
|
||||
yOffset = parseFloat(panel?.style?.height);
|
||||
xOffset += parseFloat(nav?.style?.width);
|
||||
yOffset += (parseFloat(panel?.style?.height || 0) - XL_OFFSET);
|
||||
}
|
||||
} else {
|
||||
if (webcams && sl?.includes('custom')) {
|
||||
handleCustomYOffsets();
|
||||
if (camPosition === 'contentLeft') {
|
||||
xOffset += (parseFloat(webcams?.style?.width) + RESIZE_HANDLE_WIDTH);
|
||||
if (sl.includes('custom')) {
|
||||
if (webcams) {
|
||||
if (camPosition === 'contentTop' || !camPosition) {
|
||||
yOffset += (parseFloat(webcams?.style?.height) || 0) + XS_OFFSET;
|
||||
}
|
||||
if (camPosition === 'contentBottom') {
|
||||
yOffset -= BOTTOM_CAM_HANDLE_HEIGHT;
|
||||
}
|
||||
if (camPosition === 'contentLeft') {
|
||||
xOffset += (parseFloat(webcams?.style?.width) || 0) + SMALL_OFFSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (webcams && sl?.includes('smart')) {
|
||||
if (panel || subPanel) {
|
||||
const dockPos = webcams?.getAttribute("data-position");
|
||||
if (dockPos === 'contentLeft') {
|
||||
xOffset += (parseFloat(webcams?.style?.width) + RESIZE_HANDLE_WIDTH);
|
||||
}
|
||||
if (dockPos === 'contentTop') {
|
||||
yOffset += (parseFloat(webcams?.style?.height) + RESIZE_HANDLE_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
if (!panel && !subPanel) {
|
||||
xOffset = (parseFloat(webcams?.style?.width) + RESIZE_HANDLE_WIDTH);
|
||||
|
||||
if (sl.includes('smart')) {
|
||||
if (panel || subPanel) {
|
||||
const dockPos = webcams?.getAttribute("data-position");
|
||||
if (dockPos === 'contentLeft') {
|
||||
xOffset += (parseFloat(webcams?.style?.width || 0) + SMALL_OFFSET);
|
||||
}
|
||||
if (dockPos === 'contentTop') {
|
||||
yOffset += (parseFloat(webcams?.style?.height || 0) + SMALL_OFFSET);
|
||||
}
|
||||
}
|
||||
if (!panel && !subPanel) {
|
||||
if (webcams) {
|
||||
xOffset = parseFloat(webcams?.style?.width || 0) + SMALL_OFFSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (webcams && sl?.includes('videoFocus')) {
|
||||
xOffset = parseFloat(subPanel?.style?.width);
|
||||
yOffset = parseFloat(panel?.style?.height);
|
||||
if (sl?.includes('videoFocus')) {
|
||||
if (webcams) {
|
||||
xOffset = parseFloat(subPanel?.style?.width);
|
||||
yOffset = parseFloat(panel?.style?.height);
|
||||
}
|
||||
}
|
||||
if (presentationContainer && presentation) {
|
||||
calcPresOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +285,7 @@ export default function Cursors(props) {
|
||||
const multiUserAccess = hasMultiUserAccess(whiteboardId, currentUser?.userId);
|
||||
let cursorType = multiUserAccess || currentUser?.presenter ? "none" : "default";
|
||||
if (isPanning) cursorType = 'grab';
|
||||
|
||||
|
||||
return (
|
||||
<span ref={(r) => (cursorWrapper = r)}>
|
||||
<div style={{ height: "100%", cursor: cursorType }}>
|
||||
|
@ -22,7 +22,7 @@ const clearPreview = (annotation) => {
|
||||
|
||||
const clearFakeAnnotations = () => {
|
||||
UnsentAnnotations.remove({});
|
||||
Annotations.remove({ id: /-fake/g });
|
||||
Annotations.remove({ id: /-fake/g, annotationType: { $ne: 'text' } });
|
||||
}
|
||||
|
||||
function handleAddedAnnotation({
|
||||
|
@ -3,7 +3,6 @@ import styled from 'styled-components';
|
||||
import {
|
||||
toolbarButtonWidth,
|
||||
toolbarButtonHeight,
|
||||
toolbarButtonBorderRadius,
|
||||
toolbarItemTrianglePadding,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import { createVirtualBackgroundService } from '/imports/ui/services/virtual-background';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
||||
const BLUR_FILENAME = 'blur.jpg';
|
||||
const EFFECT_TYPES = {
|
||||
|
196
bigbluebutton-html5/package-lock.json
generated
196
bigbluebutton-html5/package-lock.json
generated
@ -424,81 +424,137 @@
|
||||
}
|
||||
},
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.12.0.tgz",
|
||||
"integrity": "sha512-0/wm9b7brUD40kx7KSE0S532T8EfH06Zc41rGlinoNyYXnuusR6ull2x63iFJgVXgwahm42hAW7dcYdZ+llZzA==",
|
||||
"requires": {
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/intl-localematcher": "0.2.31",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/fast-memoize": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.6.tgz",
|
||||
"integrity": "sha512-9CWZ3+wCkClKHX+i5j+NyoBVqGf0pIskTo6Xl6ihGokYM2yqSSS68JIgeo+99UIHc+7vi9L3/SDSz/dWI9SNlA==",
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
|
||||
"integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.7.tgz",
|
||||
"integrity": "sha512-KM4ikG5MloXMulqn39Js3ypuVzpPKq/DDplvl01PE2qD9rAzFO8YtaUCC9vr9j3sRXwdHPeTe8r3J/8IJgvYEQ==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.6",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.13",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
|
||||
"integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.13.tgz",
|
||||
"integrity": "sha512-qb1kxnA4ep76rV+d9JICvZBThBpK5X+nh1dLmmIReX72QyglicsaOmKEcdcbp7/giCWfhVs6CXPVA2JJ5/ZvAw==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.2.1.tgz",
|
||||
"integrity": "sha512-vgvyUOOrzqVaOFYzTf2d3+ToSkH2JpR7x/4U1RyoHQLmvEaTQvXJ7A2qm1Iy3brGNXC/+/7bUlc3lpH+h/LOJA==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.4.0.tgz",
|
||||
"integrity": "sha512-6b3ex1nz9ZET+Jx/ApYhX62Q/SvZvNK81qG1XAFUKWylJUIFd/bm8hG6CMgh7QVzAeFPa3LIf1y0BkNAQ9VYEw==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/fast-memoize": "1.2.1",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||
"@formatjs/intl-displaynames": "5.4.3",
|
||||
"@formatjs/intl-listformat": "6.5.3",
|
||||
"intl-messageformat": "9.13.0",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"@formatjs/fast-memoize": "1.2.6",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.7",
|
||||
"@formatjs/intl-displaynames": "6.1.2",
|
||||
"@formatjs/intl-listformat": "7.1.2",
|
||||
"intl-messageformat": "10.1.4",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-displaynames": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-5.4.3.tgz",
|
||||
"integrity": "sha512-4r12A3mS5dp5hnSaQCWBuBNfi9Amgx2dzhU4lTFfhSxgb5DOAiAbMpg6+7gpWZgl4ahsj3l2r/iHIjdmdXOE2Q==",
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.1.2.tgz",
|
||||
"integrity": "sha512-JbZANoYwNXNy+6NlicVe1/tpiyp+NKJD0WTHgp14aQbMaAg66VVPNAruFmhfhkIABF4jGvc4tdKYAANmWD8W6w==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"@formatjs/intl-localematcher": "0.2.31",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-listformat": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-6.5.3.tgz",
|
||||
"integrity": "sha512-ozpz515F/+3CU+HnLi5DYPsLa6JoCfBggBSSg/8nOB5LYSFW9+ZgNQJxJ8tdhKYeODT+4qVHX27EeJLoxLGLNg==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.1.2.tgz",
|
||||
"integrity": "sha512-WfWkJ8k41jZIhXgBtC2T1SpTSKYig99g9MVqrVRco4kduv/6GUWq1eMjk84qZfbU4rwdwc8qct+/gB6DTS17+w==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"@formatjs/intl-localematcher": "0.2.31",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-localematcher": {
|
||||
"version": "0.2.25",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||
"version": "0.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.31.tgz",
|
||||
"integrity": "sha512-9QTjdSBpQ7wHShZgsNzNig5qT3rCPvmZogS/wXZzKotns5skbXgs0I7J8cuN0PPqXyynvNVuN+iOKhNS2eb+ZA==",
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
@ -3606,14 +3662,21 @@
|
||||
}
|
||||
},
|
||||
"intl-messageformat": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
|
||||
"integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
|
||||
"version": "10.1.4",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.1.4.tgz",
|
||||
"integrity": "sha512-tXCmWCXhbeHOF28aIf5b9ce3kwdwGyIiiSXVZsyDwksMiGn5Tp0MrMvyeuHuz4uN1UL+NfGOztHmE+6aLFp1wQ==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/fast-memoize": "1.2.1",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"@formatjs/fast-memoize": "1.2.6",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.7",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"invariant": {
|
||||
@ -6028,20 +6091,27 @@
|
||||
}
|
||||
},
|
||||
"react-intl": {
|
||||
"version": "5.25.1",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz",
|
||||
"integrity": "sha512-pkjdQDvpJROoXLMltkP/5mZb0/XqrqLoPGKUCfbdkP8m6U9xbK40K51Wu+a4aQqTEvEK5lHBk0fWzUV72SJ3Hg==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.1.0.tgz",
|
||||
"integrity": "sha512-aNy7wX/ZfKgpTv2x27E1sio50fnTrSWD96yfQdVfUfvZrIed6rVPccVxfAtLnIK/M9L1TGrckne3kwFj3Ipikg==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||
"@formatjs/intl": "2.2.1",
|
||||
"@formatjs/intl-displaynames": "5.4.3",
|
||||
"@formatjs/intl-listformat": "6.5.3",
|
||||
"@formatjs/ecma402-abstract": "1.12.0",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.7",
|
||||
"@formatjs/intl": "2.4.0",
|
||||
"@formatjs/intl-displaynames": "6.1.2",
|
||||
"@formatjs/intl-listformat": "7.1.2",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/react": "16 || 17 || 18",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"intl-messageformat": "9.13.0",
|
||||
"tslib": "^2.1.0"
|
||||
"intl-messageformat": "10.1.4",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
|
@ -67,7 +67,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.5",
|
||||
"react-dropzone": "^7.0.1",
|
||||
"react-intl": "^5.25.1",
|
||||
"react-intl": "^6.1.0",
|
||||
"react-loading-skeleton": "^3.0.3",
|
||||
"react-modal": "^3.15.1",
|
||||
"react-player": "^2.10.0",
|
||||
|
@ -236,6 +236,10 @@ public:
|
||||
- critical
|
||||
- danger
|
||||
- warning
|
||||
# Whether the fallback mechanism should be used
|
||||
# when the locale string is empty. If false, the empty
|
||||
# string will be returned.
|
||||
fallbackOnEmptyLocaleString: true
|
||||
externalVideoPlayer:
|
||||
enabled: true
|
||||
kurento:
|
||||
|
@ -108,6 +108,7 @@ exports.etherpadEditable = 'body[id="innerdocbody"]';
|
||||
|
||||
// Notifications
|
||||
exports.smallToastMsg = 'div[data-test="toastSmallMsg"]';
|
||||
exports.currentPresentationToast = 'div[data-test="toastSmallMsg"] > div';
|
||||
exports.notificationsTab = 'span[id="notificationTab"]';
|
||||
exports.chatPopupAlertsBtn = 'input[data-test="chatPopupAlertsBtn"]';
|
||||
exports.hasUnreadMessages = 'button[data-test="hasUnreadMessages"]';
|
||||
@ -307,3 +308,7 @@ exports.pencil = 'button[data-test="pencilTool"]';
|
||||
exports.showMoreSharedNotesButton = 'span[class="show-more-icon-btn"]'
|
||||
exports.exportSharedNotesButton = 'button[aria-label="Import/Export from/to different file formats"]';
|
||||
exports.exportPlainButton = 'span[id="exportplain"]';
|
||||
|
||||
// About modal
|
||||
exports.showAboutModalButton = 'li[data-test="aboutModal"]';
|
||||
exports.aboutModal = 'div[data-test="aboutModalTitleLabel"]';
|
@ -35,7 +35,7 @@ class Page {
|
||||
const joinUrl = helpers.getJoinURL(this.meetingId, this.initParameters, isModerator, customParameter);
|
||||
const response = await this.page.goto(joinUrl);
|
||||
await expect(response.ok()).toBeTruthy();
|
||||
const hasErrorLabel = await this.page.evaluate(checkElement, [e.errorMessageLabel]);
|
||||
const hasErrorLabel = await this.checkElement(e.errorMessageLabel);
|
||||
await expect(hasErrorLabel, 'Getting error when joining. Check if the BBB_URL and BBB_SECRET are set correctly').toBeFalsy();
|
||||
this.settings = await generateSettingsData(this.page);
|
||||
const { autoJoinAudioModal } = this.settings;
|
||||
|
@ -2,6 +2,15 @@ const { expect } = require("@playwright/test");
|
||||
|
||||
// Common
|
||||
function checkElement([element, index = 0]) {
|
||||
/* Because this function is passed to a page.evaluate, it can only
|
||||
* take a single argument; that's why we pass it an array. It's so
|
||||
* easy to pass it a string by mistake that we check to make sure
|
||||
* the second argument is an integer and not a character from a
|
||||
* destructured string.
|
||||
*/
|
||||
if (typeof index != "number") {
|
||||
throw Error("Assert failed: index not a number");
|
||||
}
|
||||
return document.querySelectorAll(element)[index] !== undefined;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { MultiUsers } = require("../user/multiusers");
|
||||
const util = require('./util');
|
||||
const e = require('../core/elements');
|
||||
const { openSettings } = require('../settings/util');
|
||||
const { openSettings } = require('../options/util');
|
||||
const { sleep } = require("../core/helpers");
|
||||
|
||||
class ChatNotifications extends MultiUsers {
|
||||
|
@ -2,7 +2,7 @@ const { test } = require('@playwright/test');
|
||||
const { MultiUsers } = require('../user/multiusers');
|
||||
const e = require('../core/elements');
|
||||
const util = require('./util');
|
||||
const { openSettings } = require('../settings/util');
|
||||
const { openSettings } = require('../options/util');
|
||||
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
|
||||
const { getSettings } = require('../core/settings');
|
||||
|
||||
|
@ -81,7 +81,9 @@ test.describe.parallel('Notifications', () => {
|
||||
await presenterNotifications.fileUploaderNotification();
|
||||
});
|
||||
|
||||
test('Screenshare notification', async ({ browser, context, page }) => {
|
||||
test('Screenshare notification', async ({ browser, browserName, context, page }) => {
|
||||
test.skip(browserName === 'firefox' && process.env.DISPLAY === undefined,
|
||||
"Screenshare tests not able in Firefox browser without desktop");
|
||||
const presenterNotifications = new PresenterNotifications(browser, context);
|
||||
await presenterNotifications.initModPage(page);
|
||||
await presenterNotifications.screenshareToast();
|
||||
|
@ -2,7 +2,6 @@ const { expect } = require('@playwright/test');
|
||||
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
|
||||
const e = require('../core/elements');
|
||||
const { sleep } = require('../core/helpers');
|
||||
const { checkElement } = require('../core/util');
|
||||
|
||||
async function enableChatPopup(test) {
|
||||
await test.waitAndClick(e.notificationsTab);
|
||||
@ -55,9 +54,9 @@ async function waitAndClearNotification(testPage) {
|
||||
}
|
||||
|
||||
async function waitAndClearDefaultPresentationNotification(testPage) {
|
||||
const hasPresentationUploaded = await testPage.page.evaluate(checkElement, e.whiteboard);
|
||||
if (!hasPresentationUploaded) {
|
||||
await testPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME);
|
||||
await testPage.waitForSelector(e.whiteboard,ELEMENT_WAIT_LONGER_TIME);
|
||||
const hasCurrentPresentationToast = await testPage.checkElement(e.currentPresentationToast);
|
||||
if (hasCurrentPresentationToast) {
|
||||
await waitAndClearNotification(testPage);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
const Page = require('../core/page');
|
||||
const { openSettings, getLocaleValues } = require('./util');
|
||||
const { openAboutModal, openSettings, getLocaleValues } = require('./util');
|
||||
const e = require('../core/elements');
|
||||
|
||||
class Language extends Page {
|
||||
|
||||
class Options extends Page {
|
||||
constructor(browser, page) {
|
||||
super(browser, page);
|
||||
}
|
||||
|
||||
async test() {
|
||||
async openedAboutModal() {
|
||||
await openAboutModal(this);
|
||||
await this.hasElement(e.closeModal);
|
||||
}
|
||||
|
||||
async localesTest() {
|
||||
const selectedKeysBySelector = {
|
||||
[e.messageTitle]: 'app.userList.messagesTitle',
|
||||
[e.notesTitle]: 'app.userList.notesTitle',
|
||||
@ -42,4 +48,4 @@ class Language extends Page {
|
||||
}
|
||||
}
|
||||
|
||||
exports.Language = Language;
|
||||
exports.Options = Options;
|
20
bigbluebutton-tests/playwright/options/options.spec.js
Normal file
20
bigbluebutton-tests/playwright/options/options.spec.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { test } = require('@playwright/test');
|
||||
const { Options } = require('./options');
|
||||
|
||||
test.describe.parallel('Options', () => {
|
||||
test('Open about modal', async ({ browser, page }) => {
|
||||
const about = new Options(browser, page);
|
||||
await about.init(true, true);
|
||||
await about.openedAboutModal();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.parallel('Settings', () => {
|
||||
// https://docs.bigbluebutton.org/2.6/release-tests.html#application-settings
|
||||
test(`Locales`, async ({ browser, page }) => {
|
||||
test.slow();
|
||||
const language = new Options(browser, page);
|
||||
await language.init(true, true);
|
||||
await language.localesTest();
|
||||
});
|
||||
});
|
@ -29,5 +29,11 @@ async function getLocaleValues(elements, locale) {
|
||||
return currentValues;
|
||||
}
|
||||
|
||||
async function openAboutModal(test) {
|
||||
await test.waitAndClick(e.optionsButton);
|
||||
await test.waitAndClick(e.showAboutModalButton);
|
||||
}
|
||||
|
||||
exports.openAboutModal = openAboutModal;
|
||||
exports.openSettings = openSettings;
|
||||
exports.getLocaleValues = getLocaleValues;
|
@ -4,7 +4,8 @@
|
||||
"test:filter": "npx playwright test -g",
|
||||
"test:headed": "npx playwright test --headed",
|
||||
"test:debug": "npx playwright test --debug -g",
|
||||
"test-ci": "export CI='true' && npx playwright test --project=chromium --grep @ci"
|
||||
"test-chromium-ci": "export CI='true' && npx playwright test --project=chromium --grep @ci",
|
||||
"test-firefox-ci": "export CI='true' && npx playwright test --project=firefox --grep @ci"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.19.2",
|
||||
@ -13,4 +14,4 @@
|
||||
"dotenv": "^16.0.0",
|
||||
"sha1": "^1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ const { ScreenShare } = require('./screenshare');
|
||||
|
||||
test.describe.parallel('Screenshare', () => {
|
||||
// https://docs.bigbluebutton.org/2.6/release-tests.html#sharing-screen-in-full-screen-mode-automated
|
||||
test('Share screen @ci', async ({ browser, page }) => {
|
||||
test('Share screen @ci', async ({ browser, browserName, page }) => {
|
||||
test.skip(browserName === 'firefox' && process.env.DISPLAY === undefined,
|
||||
"Screenshare tests not able in Firefox browser without desktop");
|
||||
const screenshare = new ScreenShare(browser, page);
|
||||
await screenshare.init(true, true);
|
||||
await screenshare.startSharing();
|
||||
|
@ -1,12 +0,0 @@
|
||||
const { test } = require('@playwright/test');
|
||||
const { Language } = require('./language');
|
||||
|
||||
test.describe.parallel('Settings', () => {
|
||||
// https://docs.bigbluebutton.org/2.6/release-tests.html#application-settings
|
||||
test(`Locales`, async ({ browser, page }) => {
|
||||
test.slow();
|
||||
const language = new Language(browser, page);
|
||||
await language.init(true, true);
|
||||
await language.test();
|
||||
});
|
||||
});
|
@ -425,4 +425,4 @@ endWhenNoModeratorDelayInMinutes=1
|
||||
notifyRecordingIsOn=false
|
||||
|
||||
# Allow endpoint with current BigBlueButton version
|
||||
allowRevealOfBBBVersion=false
|
||||
allowRevealOfBBBVersion=false
|
@ -1327,7 +1327,11 @@ class ApiController {
|
||||
requestBody = StringUtils.isEmpty(requestBody) ? null : requestBody;
|
||||
Boolean isDefaultPresentationCurrent = false;
|
||||
def listOfPresentation = []
|
||||
def presentationListHasCurrent = false
|
||||
|
||||
// This part of the code is responsible for organize the presentations in a certain order
|
||||
// It selects the one that has the current=true, and put it in the 0th place.
|
||||
// Afterwards, the 0th presentation is going to be uploaded first, which spares processing time
|
||||
if (requestBody == null) {
|
||||
if (isFromInsertAPI){
|
||||
log.warn("Insert Document API called without a payload - ignoring")
|
||||
@ -1363,6 +1367,7 @@ class ApiController {
|
||||
}
|
||||
}
|
||||
}
|
||||
presentationListHasCurrent = hasCurrent;
|
||||
}
|
||||
|
||||
listOfPresentation.eachWithIndex { document, index ->
|
||||
@ -1382,10 +1387,14 @@ class ApiController {
|
||||
}
|
||||
// The array has already been processed to let the first be the current. (This way it is
|
||||
// ensured that only one document is current)
|
||||
if (index == 0) {
|
||||
if (index == 0 && isFromInsertAPI) {
|
||||
if (presentationListHasCurrent) {
|
||||
isCurrent = true
|
||||
}
|
||||
} else if (index == 0 && !isFromInsertAPI){
|
||||
isCurrent = true
|
||||
}
|
||||
isCurrent = isCurrent && !isFromInsertAPI
|
||||
|
||||
// Verifying whether the document is a base64 encoded or a url to download.
|
||||
if (!StringUtils.isEmpty(document.@url.toString())) {
|
||||
def fileName;
|
||||
|
@ -7,6 +7,7 @@ PACKAGE=$(echo $TARGET | cut -d'_' -f1)
|
||||
VERSION=$(echo $TARGET | cut -d'_' -f2)
|
||||
DISTRO=$(echo $TARGET | cut -d'_' -f3)
|
||||
TAG=$(echo $TARGET | cut -d'_' -f4)
|
||||
BUILD=$1
|
||||
|
||||
#
|
||||
# Clean up directories
|
||||
@ -17,7 +18,7 @@ rm -rf staging
|
||||
|
||||
# New format
|
||||
if [ -f private/config/settings.yml ]; then
|
||||
sed -i "s/HTML5_CLIENT_VERSION/$(($1))/" private/config/settings.yml
|
||||
sed -i "s/HTML5_CLIENT_VERSION/$(($BUILD))/" private/config/settings.yml
|
||||
fi
|
||||
|
||||
mkdir -p staging/usr/share/bigbluebutton/nginx
|
||||
@ -97,10 +98,13 @@ cp bbb-html5-frontend@.service staging/usr/lib/systemd/system
|
||||
|
||||
mkdir -p staging/usr/share
|
||||
|
||||
# replace v=VERSION with build number in head and css files
|
||||
if [ -f staging/usr/share/meteor/bundle/programs/web.browser/head.html ]; then
|
||||
sed -i "s/VERSION/$(($BUILD))/" staging/usr/share/meteor/bundle/programs/web.browser/head.html
|
||||
sed -i "s/VERSION/$(($BUILD))/g" staging/usr/share/meteor/bundle/programs/web.browser/head.html
|
||||
fi
|
||||
|
||||
find staging/usr/share/meteor/bundle/programs/web.browser -name '*.css' -exec sed -i "s/VERSION/$(($BUILD))/g" '{}' \;
|
||||
|
||||
# Compress CSS, Javascript and tensorflow WASM binaries used for virtual backgrounds. Keep the
|
||||
# uncompressed versions as well so it works with mismatched nginx location blocks
|
||||
find staging/usr/share/meteor/bundle/programs/web.browser -name '*.js' -exec gzip -k -f -9 '{}' \;
|
||||
|
@ -1093,11 +1093,5 @@ module BigBlueButton
|
||||
return false
|
||||
end
|
||||
|
||||
# Check if doc has tldraw events
|
||||
def self.check_for_tldraw_events(events)
|
||||
return !(events.xpath("recording/event[@eventname='TldrawCameraChangedEvent']").empty? &&
|
||||
events.xpath("recording/event[@eventname='AddTldrawShapeEvent']").empty?)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -503,14 +503,19 @@ def svg_render_image(svg, slide, shapes, tldraw, tldraw_shapes)
|
||||
end
|
||||
end
|
||||
|
||||
def panzoom_viewbox(panzoom)
|
||||
def panzoom_viewbox(panzoom, tldraw)
|
||||
if panzoom[:deskshare]
|
||||
panzoom[:x_offset] = panzoom[:y_offset] = 0.0
|
||||
panzoom[:width_ratio] = panzoom[:height_ratio] = 100.0
|
||||
end
|
||||
|
||||
x = (-panzoom[:x_offset] * MAGIC_MYSTERY_NUMBER / 100.0 * panzoom[:width]).round(5)
|
||||
y = (-panzoom[:y_offset] * MAGIC_MYSTERY_NUMBER / 100.0 * panzoom[:height]).round(5)
|
||||
if tldraw
|
||||
x = panzoom[:x_offset]
|
||||
y = panzoom[:y_offset]
|
||||
else
|
||||
x = (-panzoom[:x_offset] * MAGIC_MYSTERY_NUMBER / 100.0 * panzoom[:width]).round(5)
|
||||
y = (-panzoom[:y_offset] * MAGIC_MYSTERY_NUMBER / 100.0 * panzoom[:height]).round(5)
|
||||
end
|
||||
w = shape_scale_width(panzoom, panzoom[:width_ratio])
|
||||
h = shape_scale_height(panzoom, panzoom[:height_ratio])
|
||||
|
||||
@ -521,15 +526,9 @@ def panzooms_emit_event(rec, panzoom, tldraw)
|
||||
panzoom_in = panzoom[:in]
|
||||
return if panzoom_in == panzoom[:out]
|
||||
|
||||
if !tldraw
|
||||
rec.event(timestamp: panzoom_in) do
|
||||
x, y, w, h = panzoom_viewbox(panzoom)
|
||||
rec.viewBox("#{x} #{y} #{w} #{h}")
|
||||
end
|
||||
else
|
||||
rec.event(timestamp: panzoom_in) do
|
||||
rec.cameraAndZoom("#{panzoom[:x_camera]} #{panzoom[:y_camera]} #{panzoom[:zoom]}")
|
||||
end
|
||||
rec.event(timestamp: panzoom_in) do
|
||||
x, y, w, h = panzoom_viewbox(panzoom, tldraw)
|
||||
rec.viewBox("#{x} #{y} #{w} #{h}")
|
||||
end
|
||||
end
|
||||
|
||||
@ -927,12 +926,6 @@ def process_presentation(package_dir)
|
||||
current_height_ratio = event.at_xpath('heightRatio').text.to_f
|
||||
panzoom_changed = true
|
||||
|
||||
when 'TldrawCameraChangedEvent'
|
||||
current_x_camera = event.at_xpath('xCamera').text.to_f
|
||||
current_y_camera = event.at_xpath('yCamera').text.to_f
|
||||
current_zoom = event.at_xpath('zoom').text.to_f
|
||||
panzoom_changed = true
|
||||
|
||||
when 'DeskshareStartedEvent', 'StartWebRTCDesktopShareEvent'
|
||||
deskshare = slide_changed = true if @presentation_props['include_deskshare']
|
||||
|
||||
@ -1009,19 +1002,13 @@ def process_presentation(package_dir)
|
||||
slide_width = slide[:width]
|
||||
slide_height = slide[:height]
|
||||
if panzoom &&
|
||||
(panzoom[:deskshare] == deskshare) &&
|
||||
((!tldraw &&
|
||||
(panzoom[:x_offset] == current_x_offset) &&
|
||||
(panzoom[:y_offset] == current_y_offset) &&
|
||||
(panzoom[:width_ratio] == current_width_ratio) &&
|
||||
(panzoom[:height_ratio] == current_height_ratio) &&
|
||||
(panzoom[:width] == slide_width) &&
|
||||
(panzoom[:height] == slide_height)) ||
|
||||
(tldraw &&
|
||||
(panzoom[:x_camera] == current_x_camera) &&
|
||||
(panzoom[:y_camera] == current_y_camera) &&
|
||||
(panzoom[:zoom] == current_zoom))
|
||||
)
|
||||
(panzoom[:height] == slide_height) &&
|
||||
(panzoom[:deskshare] == deskshare)
|
||||
BigBlueButton.logger.info('Panzoom: skipping, no changes')
|
||||
panzoom_changed = false
|
||||
else
|
||||
@ -1029,28 +1016,17 @@ def process_presentation(package_dir)
|
||||
panzoom[:out] = timestamp
|
||||
panzooms_emit_event(panzooms_rec, panzoom, tldraw)
|
||||
end
|
||||
if !tldraw
|
||||
BigBlueButton.logger.info("Panzoom: #{current_x_offset} #{current_y_offset} #{current_width_ratio} #{current_height_ratio} (#{slide_width}x#{slide_height})")
|
||||
panzoom = {
|
||||
x_offset: current_x_offset,
|
||||
y_offset: current_y_offset,
|
||||
width_ratio: current_width_ratio,
|
||||
height_ratio: current_height_ratio,
|
||||
width: slide[:width],
|
||||
height: slide[:height],
|
||||
in: timestamp,
|
||||
deskshare: deskshare,
|
||||
}
|
||||
else
|
||||
BigBlueButton.logger.info("Panzoom: #{current_x_camera} #{current_y_camera} #{current_zoom} (#{slide_width}x#{slide_height})")
|
||||
panzoom = {
|
||||
x_camera: current_x_camera,
|
||||
y_camera: current_y_camera,
|
||||
zoom: current_zoom,
|
||||
in: timestamp,
|
||||
deskshare: deskshare,
|
||||
}
|
||||
end
|
||||
BigBlueButton.logger.info("Panzoom: #{current_x_offset} #{current_y_offset} #{current_width_ratio} #{current_height_ratio} (#{slide_width}x#{slide_height})")
|
||||
panzoom = {
|
||||
x_offset: current_x_offset,
|
||||
y_offset: current_y_offset,
|
||||
width_ratio: current_width_ratio,
|
||||
height_ratio: current_height_ratio,
|
||||
width: slide[:width],
|
||||
height: slide[:height],
|
||||
in: timestamp,
|
||||
deskshare: deskshare,
|
||||
}
|
||||
panzooms << panzoom
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user