Merge branch 'v2.6.x-release' into issue-15536

This commit is contained in:
Joao Victor 2022-08-31 10:47:58 -03:00
commit aefc5c6c6a
84 changed files with 871 additions and 664 deletions

View File

@ -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:

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
)

View File

@ -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

View File

@ -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],
)

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)
}

View File

@ -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">

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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,
};

View File

@ -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);

View File

@ -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 });

View File

@ -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);

View File

@ -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,

View File

@ -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,
};

View File

@ -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);

View File

@ -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 {

View File

@ -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 = {

View File

@ -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 {

View File

@ -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>
)

View File

@ -55,6 +55,7 @@ const AboutComponent = ({ intl, settings }) => {
return (
<Modal
data-test="aboutModalTitleLabel"
title={intl.formatMessage(intlMessages.title)}
dismiss={{
label: intl.formatMessage(intlMessages.dismissLabel),

View File

@ -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,

View File

@ -306,7 +306,7 @@ class MessageForm extends PureComponent {
showEmojiPicker: !prevState.showEmojiPicker,
}))}
icon="happy"
color="dark"
color="light"
ghost
type="button"
circle

View File

@ -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`

View File

@ -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 = {

View File

@ -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>

View File

@ -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';

View File

@ -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>

View File

@ -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]);
}

View File

@ -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 />),

View File

@ -1,8 +1,4 @@
import styled from 'styled-components';
import {
borderSizeLarge,
borderSize,
} from '/imports/ui/stylesheets/styled-components/general';
import {
colorWhite,
colorGrayDark,

View File

@ -1,4 +1,3 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Service from './service';

View File

@ -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

View File

@ -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";

View File

@ -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`

View File

@ -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>

View File

@ -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';

View File

@ -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) => {

View File

@ -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
/>

View File

@ -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;

View File

@ -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();
});

View File

@ -1,5 +1,3 @@
import { Meteor } from 'meteor/meteor';
const ScreenReaderAlertCollection = new Mongo.Collection('Screenreader-alert', { connection: null });
export default ScreenReaderAlertCollection;

View File

@ -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();

View File

@ -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} />

View File

@ -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 }) => {

View File

@ -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 }}>

View File

@ -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({

View File

@ -3,7 +3,6 @@ import styled from 'styled-components';
import {
toolbarButtonWidth,
toolbarButtonHeight,
toolbarButtonBorderRadius,
toolbarItemTrianglePadding,
} from '/imports/ui/stylesheets/styled-components/general';
import {

View File

@ -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 = {

View File

@ -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": {

View File

@ -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",

View File

@ -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:

View File

@ -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"]';

View File

@ -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;

View File

@ -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;
}

View File

@ -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 {

View File

@ -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');

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;

View 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();
});
});

View File

@ -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;

View File

@ -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"
}
}
}

View File

@ -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();

View File

@ -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();
});
});

View File

@ -425,4 +425,4 @@ endWhenNoModeratorDelayInMinutes=1
notifyRecordingIsOn=false
# Allow endpoint with current BigBlueButton version
allowRevealOfBBBVersion=false
allowRevealOfBBBVersion=false

View File

@ -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;

View File

@ -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 '{}' \;

View File

@ -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

View File

@ -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