Merge branch 'v2.6.x-release' of github.com:bigbluebutton/bigbluebutton into merge-260a3

This commit is contained in:
Anton Georgiev 2022-09-08 02:26:21 +00:00
commit c1379bde13
174 changed files with 2463 additions and 1009 deletions

View File

@ -114,7 +114,7 @@ jobs:
run: |
sudo sh -c '
cd /root/ && wget -q https://ubuntu.bigbluebutton.org/bbb-install-2.6.sh -O bbb-install.sh
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-26-dev -s bbb-ci.test -d /certs/
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-26-dev -s bbb-ci.test -j -d /certs/
bbb-conf --salt bbbci
echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf
bbb-conf --restart
@ -134,7 +134,22 @@ jobs:
ACTIONS_RUNNER_DEBUG: true
BBB_URL: https://bbb-ci.test/bigbluebutton/api
BBB_SECRET: bbbci
run: npm run test-ci
run: npm run test-chromium-ci
- name: Run Firefox tests
working-directory: ./bigbluebutton-tests/playwright
if: ${{ contains(github.event.pull_request.labels.*.name, 'test Firefox')
|| contains(github.event.pull_request.labels.*.name, 'Test Firefox') }}
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
BBB_URL: https://bbb-ci.test/bigbluebutton/api
BBB_SECRET: bbbci
# patch playwright's firefox so that it uses the system's root certificate authority
run: |
sh -c '
find $HOME/.cache/ms-playwright -name libnssckbi.so -exec rm {} \; -exec ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so {} \;
npm run test-firefox-ci
'
- if: always()
uses: actions/upload-artifact@v3
with:

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

@ -1,8 +1,8 @@
[Unit]
Description=BigBlueButton Apps (Akka)
Requires=network.target
Wants=redis-server.service
After=redis-server.service
Wants=redis-server.service bbb-fsesl-akka.service
After=redis-server.service bbb-fsesl-akka.service
PartOf=bigbluebutton.target
[Service]
@ -13,7 +13,7 @@ ExecStart=/usr/share/bbb-apps-akka/bin/bbb-apps-akka
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=60
SuccessExitStatus=
SuccessExitStatus=143
TimeoutStopSec=5
User=bigbluebutton
ExecStartPre=/bin/mkdir -p /run/bbb-apps-akka

View File

@ -1,8 +1,8 @@
[Unit]
Description=BigBlueButton FS-ESL (Akka)
Requires=network.target
Wants=redis-server.service
After=redis-server.service
Wants=redis-server.service freeswitch.service
After=redis-server.service freeswitch.service
PartOf= bigbluebutton.target
[Service]
@ -13,7 +13,7 @@ ExecStart=/usr/share/bbb-fsesl-akka/bin/bbb-fsesl-akka
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=60
SuccessExitStatus=
SuccessExitStatus=143
TimeoutStopSec=5
User=bigbluebutton
ExecStartPre=/bin/mkdir -p /run/bbb-fsesl-akka

View File

@ -9,14 +9,16 @@ case class DurationProps(duration: Int, createdTime: Long, createdDate: String,
endWhenNoModerator: Boolean, endWhenNoModeratorDelayInMinutes: Int)
case class MeetingProp(
name: String,
extId: String,
intId: String,
meetingCameraCap: Int,
maxPinnedCameras: Int,
isBreakout: Boolean,
disabledFeatures: Vector[String],
notifyRecordingIsOn: Boolean,
name: String,
extId: String,
intId: String,
meetingCameraCap: Int,
maxPinnedCameras: Int,
isBreakout: Boolean,
disabledFeatures: Vector[String],
notifyRecordingIsOn: Boolean,
uploadExternalDescription: String,
uploadExternalUrl: String,
)
case class BreakoutProps(

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

@ -73,6 +73,9 @@ public class ApiParams {
public static final String DISABLED_FEATURES = "disabledFeatures";
public static final String NOTIFY_RECORDING_IS_ON = "notifyRecordingIsOn";
public static final String UPLOAD_EXTERNAL_DESCRIPTION = "uploadExternalDescription";
public static final String UPLOAD_EXTERNAL_URL = "uploadExternalUrl";
public static final String BREAKOUT_ROOMS_ENABLED = "breakoutRoomsEnabled";
public static final String BREAKOUT_ROOMS_RECORD = "breakoutRoomsRecord";
public static final String BREAKOUT_ROOMS_PRIVATE_CHAT_ENABLED = "breakoutRoomsPrivateChatEnabled";

View File

@ -421,7 +421,8 @@ public class MeetingService implements MessageListener {
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn());
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getUploadExternalDescription(), m.getUploadExternalUrl());
}
private String formatPrettyDate(Long timestamp) {
@ -949,6 +950,13 @@ public class MeetingService implements MessageListener {
User user = new User(message.userId, message.externalUserId,
message.name, message.role, message.avatarURL, message.guest, message.guestStatus,
message.clientType);
if(m.getMaxUsers() > 0 && m.getUsers().size() >= m.getMaxUsers()) {
m.removeEnteredUser(user.getInternalUserId());
gw.ejectDuplicateUser(message.meetingId, user.getInternalUserId(), user.getFullname(), user.getExternalUserId());
return;
}
m.userJoined(user);
m.setGuestStatusWithId(user.getInternalUserId(), message.guestStatus);
UserSession userSession = getUserSessionWithUserId(user.getInternalUserId());

View File

@ -101,6 +101,8 @@ public class ParamsProcessorUtil {
private boolean defaultKeepEvents = false;
private Boolean useDefaultLogo;
private String defaultLogoURL;
private String defaultUploadExternalDescription = "";
private String defaultUploadExternalUrl = "";
private boolean defaultBreakoutRoomsEnabled = true;
private boolean defaultBreakoutRoomsRecord;
@ -618,6 +620,16 @@ public class ParamsProcessorUtil {
guestPolicy = params.get(ApiParams.GUEST_POLICY);
}
String uploadExternalDescription = defaultUploadExternalDescription;
if (!StringUtils.isEmpty(params.get(ApiParams.UPLOAD_EXTERNAL_DESCRIPTION))) {
uploadExternalDescription = params.get(ApiParams.UPLOAD_EXTERNAL_DESCRIPTION);
}
String uploadExternalUrl = defaultUploadExternalUrl;
if (!StringUtils.isEmpty(params.get(ApiParams.UPLOAD_EXTERNAL_URL))) {
uploadExternalUrl = params.get(ApiParams.UPLOAD_EXTERNAL_URL);
}
String meetingLayout = defaultMeetingLayout;
ArrayList<Group> groups = processGroupsParams(params);
@ -701,6 +713,8 @@ public class ParamsProcessorUtil {
.withGroups(groups)
.withDisabledFeatures(listOfDisabledFeatures)
.withNotifyRecordingIsOn(notifyRecordingIsOn)
.withUploadExternalDescription(uploadExternalDescription)
.withUploadExternalUrl(uploadExternalUrl)
.build();
if (!StringUtils.isEmpty(params.get(ApiParams.MODERATOR_ONLY_MESSAGE))) {
@ -1369,6 +1383,14 @@ public class ParamsProcessorUtil {
this.defaultNotifyRecordingIsOn = notifyRecordingIsOn;
}
public void setUploadExternalDescription(String uploadExternalDescription) {
this.defaultUploadExternalDescription = uploadExternalDescription;
}
public void setUploadExternalUrl(String uploadExternalUrl) {
this.defaultUploadExternalUrl = uploadExternalUrl;
}
public void setBbbVersion(String version) {
this.bbbVersion = this.allowRevealOfBBBVersion ? version : "";
}

View File

@ -95,6 +95,8 @@ public class Meeting {
private Boolean allowRequestsWithoutSession = false;
private Boolean allowModsToEjectCameras = false;
private Boolean meetingKeepEvents;
private String uploadExternalDescription;
private String uploadExternalUrl;
private Integer meetingExpireIfNoUserJoinedInMinutes = 5;
private Integer meetingExpireWhenLastUserLeftInMinutes = 1;
@ -119,6 +121,8 @@ public class Meeting {
intMeetingId = builder.internalId;
disabledFeatures = builder.disabledFeatures;
notifyRecordingIsOn = builder.notifyRecordingIsOn;
uploadExternalDescription = builder.uploadExternalDescription;
uploadExternalUrl = builder.uploadExternalUrl;
if (builder.viewerPass == null){
viewerPass = "";
} else {
@ -380,6 +384,13 @@ public class Meeting {
return notifyRecordingIsOn;
}
public String getUploadExternalDescription() {
return uploadExternalDescription;
}
public String getUploadExternalUrl() {
return uploadExternalUrl;
}
public String getWelcomeMessageTemplate() {
return welcomeMsgTemplate;
}
@ -810,6 +821,8 @@ public class Meeting {
private String learningDashboardAccessToken;
private ArrayList<String> disabledFeatures;
private Boolean notifyRecordingIsOn;
private String uploadExternalDescription;
private String uploadExternalUrl;
private int duration;
private String webVoice;
private String telVoice;
@ -937,6 +950,16 @@ public class Meeting {
return this;
}
public Builder withUploadExternalDescription(String d) {
this.uploadExternalDescription = d;
return this;
}
public Builder withUploadExternalUrl(String u) {
this.uploadExternalUrl = u;
return this;
}
public Builder withWelcomeMessage(String w) {
welcomeMsg = w;
return this;

View File

@ -25,6 +25,8 @@ public class CreateMeetingMessage {
public final String learningDashboardAccessToken;
public final ArrayList<String> disabledFeatures;
public final Boolean notifyRecordingIsOn;
public final String uploadExternalDescription;
public final String uploadExternalUrl;
public final Long createTime;
public final String createDate;
public final Map<String, String> metadata;
@ -36,6 +38,8 @@ public class CreateMeetingMessage {
String viewerPass, String learningDashboardAccessToken,
ArrayList<String> disabledFeatures,
Boolean notifyRecordingIsOn,
String uploadExternalDescription,
String uploadExternalUrl,
Long createTime, String createDate, Map<String, String> metadata) {
this.id = id;
this.externalId = externalId;
@ -54,6 +58,8 @@ public class CreateMeetingMessage {
this.learningDashboardAccessToken = learningDashboardAccessToken;
this.disabledFeatures = disabledFeatures;
this.notifyRecordingIsOn = notifyRecordingIsOn;
this.uploadExternalDescription = uploadExternalDescription;
this.uploadExternalUrl = uploadExternalUrl;
this.createTime = createTime;
this.createDate = createDate;
this.metadata = metadata;

View File

@ -24,7 +24,7 @@ public class GuestPolicyValidator implements ConstraintValidator<GuestPolicyCons
MeetingService meetingService = ServiceUtils.getMeetingService();
UserSession userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
if(userSession == null || userSession.guestStatus.equals(GuestPolicy.DENY)) {
if(userSession == null || !userSession.guestStatus.equals(GuestPolicy.ALLOW)) {
return false;
}

View File

@ -39,9 +39,8 @@ public class MaxParticipantsValidator implements ConstraintValidator<MaxParticip
boolean rejoin = meeting.getUserById(userSession.internalUserId) != null;
boolean reenter = meeting.getEnteredUserById(userSession.internalUserId) != null;
int joinedUsers = meeting.getUsers().size();
int enteredUsers = meeting.getEnteredUsers().size();
boolean reachedMax = (joinedUsers + enteredUsers) >= maxParticipants;
boolean reachedMax = joinedUsers >= maxParticipants;
if(enabled && !rejoin && !reenter && reachedMax) {
return false;
}

View File

@ -43,7 +43,9 @@ public interface IBbbWebApiGWApp {
Integer html5InstanceId,
ArrayList<Group> groups,
ArrayList<String> disabledFeatures,
Boolean notifyRecordingIsOn);
Boolean notifyRecordingIsOn,
String uploadExternalDescription,
String uploadExternalUrl);
void registerUser(String meetingID, String internalUserId, String fullname, String role,
String externUserID, String authToken, String avatarURL,

View File

@ -23,7 +23,7 @@ import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.presentation.messages.OfficeDocConversionProgress;
import org.bigbluebutton.presentation.messages.DocConversionProgress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -52,7 +52,7 @@ public class OfficeToPdfConversionSuccessFilter {
}
public void sendProgress(UploadedPresentation pres) {
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(),
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(),
pres.getMeetingId(),
pres.getId(),
pres.getId(),

View File

@ -22,7 +22,7 @@ import java.io.File;
import org.apache.commons.io.FilenameUtils;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.presentation.messages.OfficeDocConversionProgress;
import org.bigbluebutton.presentation.messages.DocConversionProgress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,7 +59,7 @@ public class SupportedDocumentFilter {
}
if (gw != null) {
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
pres.getId(), pres.getId(),
pres.getName(), "notUsedYet", "notUsedYet",
pres.isDownloadable(), pres.isRemovable(), msgKey);

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

@ -71,7 +71,7 @@ public class SwfSlidesGenerationProgressNotifier {
}
public void sendCreatingThumbnailsUpdateMessage(UploadedPresentation pres) {
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
pres.getId(), pres.getId(),
pres.getName(), "notUsedYet", "notUsedYet",
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.GENERATING_THUMBNAIL_KEY);
@ -105,7 +105,7 @@ public class SwfSlidesGenerationProgressNotifier {
}
public void sendCreatingTextFilesUpdateMessage(UploadedPresentation pres) {
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
pres.getId(), pres.getId(),
pres.getName(), "notUsedYet", "notUsedYet",
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.GENERATING_TEXTFILES_KEY);
@ -113,7 +113,7 @@ public class SwfSlidesGenerationProgressNotifier {
}
public void sendCreatingSvgImagesUpdateMessage(UploadedPresentation pres) {
OfficeDocConversionProgress progress = new OfficeDocConversionProgress(pres.getPodId(), pres.getMeetingId(),
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
pres.getId(), pres.getId(),
pres.getName(), "notUsedYet", "notUsedYet",
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.GENERATING_SVGIMAGES_KEY);

View File

@ -1,29 +1,29 @@
package org.bigbluebutton.presentation.messages;
public class OfficeDocConversionProgress implements IDocConversionMsg {
public final String podId;
public final String meetingId;
public final String presId;
public final String presInstance;
public final String filename;
public final String uploaderId;
public final String authzToken;
public final Boolean downloadable;
public final Boolean removable;
public final String key;
public OfficeDocConversionProgress(String podId, String meetingId, String presId, String presInstance,
String filename, String uploaderId, String authzToken,
Boolean downloadable, Boolean removable, String key) {
this.podId = podId;
this.meetingId = meetingId;
this.presId = presId;
this.presInstance = presInstance;
this.filename = filename;
this.uploaderId = uploaderId;
this.authzToken = authzToken;
this.downloadable = downloadable;
this.removable = removable;
this.key = key;
}
}
package org.bigbluebutton.presentation.messages;
public class DocConversionProgress implements IDocConversionMsg {
public final String podId;
public final String meetingId;
public final String presId;
public final String presInstance;
public final String filename;
public final String uploaderId;
public final String authzToken;
public final Boolean downloadable;
public final Boolean removable;
public final String key;
public DocConversionProgress(String podId, String meetingId, String presId, String presInstance,
String filename, String uploaderId, String authzToken,
Boolean downloadable, Boolean removable, String key) {
this.podId = podId;
this.meetingId = meetingId;
this.presId = presId;
this.presInstance = presInstance;
this.filename = filename;
this.uploaderId = uploaderId;
this.authzToken = authzToken;
this.downloadable = downloadable;
this.removable = removable;
this.key = key;
}
}

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

@ -1,29 +0,0 @@
package org.bigbluebutton.presentation.messages;
import org.bigbluebutton.api.messaging.messages.IMessage;
public class OfficeDocConversionInvalid implements IMessage{
public final String podId;
public final String meetingId;
public final String presId;
public final String presInstance;
public final String filename;
public final String uploaderId;
public final String authzToken;
public final Boolean downloadable;
public final Boolean removable;
public OfficeDocConversionInvalid(String podId, String meetingId, String presId, String presInstance,
String filename, String uploaderId, String authzToken,
Boolean downloadable, Boolean removable) {
this.podId = podId;
this.meetingId = meetingId;
this.presId = presId;
this.presInstance = presInstance;
this.filename = filename;
this.uploaderId = uploaderId;
this.authzToken = authzToken;
this.downloadable = downloadable;
this.removable = removable;
}
}

View File

@ -1,30 +0,0 @@
package org.bigbluebutton.presentation.messages;
import org.bigbluebutton.api.messaging.messages.IMessage;
public class OfficeDocConversionSuccess implements IMessage{
public final String podId;
public final String meetingId;
public final String presId;
public final String presInstance;
public final String filename;
public final String uploaderId;
public final String authzToken;
public final Boolean downloadable;
public final Boolean removable;
public OfficeDocConversionSuccess(String podId, String meetingId, String presId, String presInstance,
String filename, String uploaderId, String authzToken,
Boolean downloadable, Boolean removable) {
this.podId = podId;
this.meetingId = meetingId;
this.presId = presId;
this.presInstance = presInstance;
this.filename = filename;
this.uploaderId = uploaderId;
this.authzToken = authzToken;
this.downloadable = downloadable;
this.removable = removable;
}
}

View File

@ -1,30 +0,0 @@
package org.bigbluebutton.presentation.messages;
import org.bigbluebutton.api.messaging.messages.IMessage;
public class OfficeDocConversionSupported implements IMessage{
public final String podId;
public final String meetingId;
public final String presId;
public final String presInstance;
public final String filename;
public final String uploaderId;
public final String authzToken;
public final Boolean downloadable;
public final Boolean removable;
public OfficeDocConversionSupported(String podId, String meetingId, String presId, String presInstance,
String filename, String uploaderId, String authzToken,
Boolean downloadable, Boolean removable) {
this.podId = podId;
this.meetingId = meetingId;
this.presId = presId;
this.presInstance = presInstance;
this.filename = filename;
this.uploaderId = uploaderId;
this.authzToken = authzToken;
this.downloadable = downloadable;
this.removable = removable;
}
}

View File

@ -1,30 +0,0 @@
package org.bigbluebutton.presentation.messages;
import org.bigbluebutton.api.messaging.messages.IMessage;
public class OfficeDocConversionUnsupported implements IMessage{
public final String podId;
public final String meetingId;
public final String presId;
public final String presInstance;
public final String filename;
public final String uploaderId;
public final String authzToken;
public final Boolean downloadable;
public final Boolean removable;
public OfficeDocConversionUnsupported(String podId, String meetingId, String presId, String presInstance,
String filename, String uploaderId, String authzToken,
Boolean downloadable, Boolean removable) {
this.podId = podId;
this.meetingId = meetingId;
this.presId = presId;
this.presInstance = presInstance;
this.filename = filename;
this.uploaderId = uploaderId;
this.authzToken = authzToken;
this.downloadable = downloadable;
this.removable = removable;
}
}

View File

@ -148,7 +148,9 @@ class BbbWebApiGWApp(
html5InstanceId: java.lang.Integer,
groups: java.util.ArrayList[Group],
disabledFeatures: java.util.ArrayList[String],
notifyRecordingIsOn: java.lang.Boolean): Unit = {
notifyRecordingIsOn: java.lang.Boolean,
uploadExternalDescription: String,
uploadExternalUrl: String): Unit = {
val disabledFeaturesAsVector: Vector[String] = disabledFeatures.asScala.toVector
@ -160,7 +162,9 @@ class BbbWebApiGWApp(
maxPinnedCameras = maxPinnedCameras.intValue(),
isBreakout = isBreakout.booleanValue(),
disabledFeaturesAsVector,
notifyRecordingIsOn
notifyRecordingIsOn,
uploadExternalDescription,
uploadExternalUrl
)
val durationProps = DurationProps(
@ -308,8 +312,8 @@ class BbbWebApiGWApp(
// Send new event with page urls
val newEvent = MsgBuilder.buildPresentationPageConvertedSysMsg(msg.asInstanceOf[DocPageGeneratedProgress])
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, newEvent))
} else if (msg.isInstanceOf[OfficeDocConversionProgress]) {
val event = MsgBuilder.buildPresentationConversionUpdateSysPubMsg(msg.asInstanceOf[OfficeDocConversionProgress])
} else if (msg.isInstanceOf[DocConversionProgress]) {
val event = MsgBuilder.buildPresentationConversionUpdateSysPubMsg(msg.asInstanceOf[DocConversionProgress])
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
} else if (msg.isInstanceOf[DocPageCompletedProgress]) {
val event = MsgBuilder.buildPresentationConversionCompletedSysPubMsg(msg.asInstanceOf[DocPageCompletedProgress])

View File

@ -127,7 +127,7 @@ object MsgBuilder {
BbbCommonEnvCoreMsg(envelope, req)
}
def buildPresentationConversionUpdateSysPubMsg(msg: OfficeDocConversionProgress): BbbCommonEnvCoreMsg = {
def buildPresentationConversionUpdateSysPubMsg(msg: DocConversionProgress): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-web")
val envelope = BbbCoreEnvelope(PresentationConversionUpdateSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationConversionUpdateSysPubMsg.NAME, msg.meetingId, msg.authzToken)
@ -193,7 +193,7 @@ object MsgBuilder {
val header = BbbClientMsgHeader(PresentationPageCountErrorSysPubMsg.NAME, msg.meetingId, msg.authzToken)
val body = PresentationPageCountErrorSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
code = msg.key, msg.presId, 0, 0, msg.filename)
code = msg.key, msg.presId, 0, 0, msg.filename, msg.temporaryPresentationId)
val req = PresentationPageCountErrorSysPubMsg(header, body)
BbbCommonEnvCoreMsg(envelope, req)
}
@ -204,7 +204,7 @@ object MsgBuilder {
val header = BbbClientMsgHeader(PresentationPageCountErrorSysPubMsg.NAME, msg.meetingId, msg.authzToken)
val body = PresentationPageCountErrorSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
code = msg.key, msg.presId, msg.numPages.intValue(), msg.maxNumPages.intValue(), msg.filename)
code = msg.key, msg.presId, msg.numPages.intValue(), msg.maxNumPages.intValue(), msg.filename, msg.temporaryPresentationId)
val req = PresentationPageCountErrorSysPubMsg(header, body)
BbbCommonEnvCoreMsg(envelope, req)
}

View File

@ -22,14 +22,9 @@
"notifier": {
"pod_id": "DEFAULT_PRESENTATION_POD",
"is_downloadable": "false",
"msgName": "NewPresAnnFileAvailableMsg",
"protocol": "https",
"host": "localhost"
},
"bbbWeb": {
"host": "127.0.0.1",
"port": 8090
"msgName": "NewPresAnnFileAvailableMsg"
},
"bbbWebAPI": "http://127.0.0.1:8090",
"redis": {
"host": "127.0.0.1",
"port": 6379,

View File

@ -2,7 +2,7 @@ const Logger = require('../lib/utils/logger');
const config = require('../config');
const fs = require('fs');
const redis = require('redis');
const {Worker, workerData, parentPort} = require('worker_threads');
const {Worker, workerData} = require('worker_threads');
const path = require('path');
const cp = require('child_process');
@ -100,5 +100,3 @@ const exportJob = JSON.parse(job);
kickOffProcessWorker(exportJob.jobId);
})();
parentPort.postMessage({message: workerData});

View File

@ -6,7 +6,7 @@ const redis = require('redis');
const axios = require('axios').default;
const path = require('path');
const {workerData, parentPort} = require('worker_threads');
const {workerData} = require('worker_threads');
const [jobType, jobId, filename_with_extension] = workerData;
@ -27,7 +27,11 @@ async function notifyMeetingActor() {
await client.connect();
client.on('error', (err) => logger.info('Redis Client Error', err));
const link = `${config.notifier.protocol}://${config.notifier.host}/bigbluebutton/presentation/${exportJob.parentMeetingId}/${exportJob.parentMeetingId}/${exportJob.presId}/pdf/${jobId}/${filename_with_extension}`;
const link = path.join(`${path.sep}bigbluebutton`, 'presentation',
exportJob.parentMeetingId, exportJob.parentMeetingId,
exportJob.presId, 'pdf', jobId, filename_with_extension);
const notification = {
envelope: {
name: config.notifier.msgName,
@ -57,7 +61,7 @@ async function notifyMeetingActor() {
/** Upload PDF to a BBB room */
async function upload() {
const callbackUrl = `http://${config.bbbWeb.host}:${config.bbbWeb.port}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload`;
const callbackUrl = `${config.bbbWebAPI}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload`;
const formData = new FormData();
const file = `${exportJob.presLocation}/pdfs/${jobId}/${filename_with_extension}`;
@ -86,5 +90,3 @@ fs.rm(dropbox, {recursive: true}, (err) => {
throw err;
}
});
parentPort.postMessage({message: workerData});

View File

@ -3,7 +3,7 @@ const config = require('../config');
const fs = require('fs');
const {create} = require('xmlbuilder2', {encoding: 'utf-8'});
const cp = require('child_process');
const {Worker, workerData, parentPort} = require('worker_threads');
const {Worker, workerData} = require('worker_threads');
const path = require('path');
const sanitize = require('sanitize-filename');
const {getStroke, getStrokePoints} = require('perfect-freehand');
@ -878,5 +878,3 @@ cp.spawnSync(config.shared.ghostscript, mergePDFs, {shell: false});
logger.info(`Saved PDF at ${outputDir}/${jobId}/${filename_with_extension}`);
kickOffNotifierWorker(exportJob.jobType, filename_with_extension);
parentPort.postMessage({message: workerData});

View File

@ -16930,9 +16930,9 @@
}
},
"node_modules/terser": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
"integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
@ -30717,9 +30717,9 @@
}
},
"terser": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
"integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",

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

@ -1 +1 @@
git clone --branch v1.2.2 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
git clone --branch v1.3.0 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads

View File

@ -1 +1 @@
git clone --branch v2.9.0-beta.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
git clone --branch v2.9.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu

View File

@ -5,10 +5,53 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BigBlueButton</title>
<style>
.error-div {
background-color: #f2dede;
padding: 15px;
border-radius: 10px;
border-color: #ebccd1;
}
.error-div-title {
font-size: 25px;
}
.error-message {
color: #a94442;
font-size: 15px;
margin-top: 2px 0 0 0;
}
</style>
</head>
<body style="margin:0">
<div style="display: flex; height: 100vh; margin: 0; flex-direction: column; align-items: center; justify-content: center;">
<h1>Welcome to BigBlueButton!</h1>
<h1 id="welcome-message">Welcome to BigBlueButton!</h1>
</div>
</body>
<script>
const ERRORS_QUERY_LABLE = "errors"
const queryString = decodeURI(window.location.search).slice(1);
const positionError = queryString.indexOf(ERRORS_QUERY_LABLE);
let jsonString;
if (positionError !== -1) {
jsonString = JSON.parse(queryString.slice(positionError + ERRORS_QUERY_LABLE.length + 1));
const welcomeMessage = document.querySelector('#welcome-message');
const errorContainer = document.createElement('div');
errorContainer.classList.add("error-div");
welcomeMessage.before(errorContainer);
for (i in jsonString) {
const newError = document.createElement('p')
newError.classList.add("error-message");
newError.innerHTML = "<b>Error</b>: " + jsonString[i].message;
errorContainer.appendChild(newError);
}
welcomeMessage.remove();
const bigBlueButton = document.createElement('h1');
bigBlueButton.innerHTML = "BigBlueButton";
errorContainer.before(bigBlueButton);
}
</script>
</html>

View File

@ -1 +1 @@
BIGBLUEBUTTON_RELEASE=2.6.0-alpha.2
BIGBLUEBUTTON_RELEASE=2.6.0-alpha.3

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,17 +378,17 @@ start_bigbluebutton () {
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
TOMCAT_SERVICE=$TOMCAT_USER
[ -z "$TOMCAT_SERVICE" ] || systemctl start $TOMCAT_SERVICE || {
echo
echo "# Warning: $TOMCAT_SERVICE could not be started. Please, check BBB-LTI."
echo "# Run the command:"
echo "# sudo journalctl -u $TOMCAT_SERVICE"
echo "# To better understand the ERROR"
}
fi
systemctl start $TOMCAT_SERVICE || {
echo
echo "# Warning: $TOMCAT_SERVICE could not be started. Please, check BBB-LTI."
echo "# Run the command:"
echo "# sudo journalctl -u $TOMCAT_SERVICE"
echo "# To better understand the ERROR"
}
systemctl start bigbluebutton.target nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-fsesl-akka bbb-rap-resque-worker bbb-rap-starter.service bbb-rap-caption-inbox.service $HTML5 $WEBHOOKS $ETHERPAD $PADS $BBB_WEB $BBB_LTI
systemctl start bigbluebutton.target
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
systemctl start mongod
@ -434,7 +435,7 @@ display_bigbluebutton_status () {
units="$units etherpad"
fi
if [ -f /lib/systemd/system/bbb-web.service ]; then
if [ -f /usr/lib/systemd/system/bbb-web.service ]; then
units="$units bbb-web"
fi
@ -442,7 +443,7 @@ display_bigbluebutton_status () {
units="$units bbb-webhooks"
fi
if [ -f /lib/systemd/system/bbb-lti.service ]; then
if [ -f /usr/lib/systemd/system/bbb-lti.service ]; then
units="$units bbb-lti"
fi
@ -454,6 +455,18 @@ display_bigbluebutton_status () {
units="$units bbb-export-annotations"
fi
if [ -f /usr/lib/systemd/system/bbb-rap-caption-inbox.service ]; then
units="$units bbb-rap-caption-inbox"
fi
if [ -f /usr/lib/systemd/system/bbb-rap-resque-worker.service ]; then
units="$units bbb-rap-resque-worker"
fi
if [ -f /usr/lib/systemd/system/bbb-rap-starter.service ]; then
units="$units bbb-rap-starter"
fi
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
TOMCAT_SERVICE=$TOMCAT_USER
fi
@ -885,14 +898,18 @@ check_state() {
if ! ss -ant | grep '8090' > /dev/null; then
print_header
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${TOMCAT_USER} or grails"
if [ ! -z "$TOMCAT_SERVICE" ]; then
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${TOMCAT_USER} or grails"
fi
else
if ps aux | ps -aef | grep -v grep | grep grails | grep run-app > /dev/null; then
print_header
RUNNING_APPS="${RUNNING_APPS} Grails"
echo "# ${TOMCAT_USER}: noticed you are running grails run-app instead of ${TOMCAT_USER}"
else
RUNNING_APPS="${RUNNING_APPS} ${TOMCAT_USER}"
if [ ! -z "$TOMCAT_SERVICE" ]; then
RUNNING_APPS="${RUNNING_APPS} ${TOMCAT_USER}"
fi
fi
fi

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

@ -30,6 +30,7 @@ const DEFAULT_FULLAUDIO_MEDIA_SERVER = MEDIA.audio.fullAudioMediaServer;
const LISTEN_ONLY_OFFERING = MEDIA.listenOnlyOffering;
const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, '');
const RECONNECT_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000;
const { audio: NETWORK_PRIORITY } = MEDIA.networkPriorities || {};
const SENDRECV_ROLE = 'sendrecv';
const RECV_ROLE = 'recv';
const BRIDGE_NAME = 'fullaudio';
@ -316,6 +317,7 @@ export default class SFUAudioBridge extends BaseAudioBridge {
offering: isListenOnly ? LISTEN_ONLY_OFFERING : true,
signalCandidates: SIGNAL_CANDIDATES,
traceLogs: TRACE_LOGS,
networkPriority: NETWORK_PRIORITY,
};
this.broker = new AudioBroker(

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

@ -84,6 +84,8 @@ export default function addMeeting(meeting) {
name: String,
disabledFeatures: Array,
notifyRecordingIsOn: Boolean,
uploadExternalDescription: String,
uploadExternalUrl: String,
},
usersProp: {
webcamsOnlyForModerator: Boolean,

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

@ -12,6 +12,7 @@ const SFU_URL = SFU_CONFIG.wsUrl;
const OFFERING = SFU_CONFIG.screenshare.subscriberOffering;
const SIGNAL_CANDIDATES = Meteor.settings.public.kurento.signalCandidates;
const TRACE_LOGS = Meteor.settings.public.kurento.traceLogs;
const { screenshare: NETWORK_PRIORITY } = Meteor.settings.public.media.networkPriorities || {};
const BRIDGE_NAME = 'kurento'
const SCREENSHARE_VIDEO_TAG = 'screenshareVideo';
@ -307,6 +308,7 @@ export default class KurentoScreenshareBridge {
signalCandidates: SIGNAL_CANDIDATES,
forceRelay: shouldForceRelay(),
traceLogs: TRACE_LOGS,
networkPriority: NETWORK_PRIORITY,
};
this.broker = new ScreenshareBroker(

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

@ -1,6 +1,7 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import VideoStreams from '/imports/api/video-streams';
import flat from 'flat';
export default function updateVideoStream(meetingId, videoStream) {
check(meetingId, String);
@ -23,7 +24,7 @@ export default function updateVideoStream(meetingId, videoStream) {
const modifier = {
$set: Object.assign(
...videoStream,
flat(videoStream),
),
};

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

@ -29,6 +29,8 @@ const ActionsBarContainer = (props) => {
const amIPresenter = users[Auth.meetingID][Auth.userID].presenter;
if (actionsBarStyle.display === false) return null;
return (
<ActionsBar {
...{

View File

@ -48,6 +48,7 @@ import Notifications from '../notifications/container';
import GlobalStyles from '/imports/ui/stylesheets/styled-components/globalStyles';
import MediaService from '/imports/ui/components/media/service';
import ActionsBarContainer from '../actions-bar/container';
import { updateSettings } from '/imports/ui/components/settings/service';
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
const APP_CONFIG = Meteor.settings.public.app;
@ -313,7 +314,13 @@ class App extends Component {
this.renderDarkMode();
if (meetingLayout !== prevProps.meetingLayout) {
const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
const shouldSwitchLayout = isPresenter
? meetingLayoutDidChange
: (meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting;
if (shouldSwitchLayout) {
let contextLayout = meetingLayout;
if (isMobile()) {
@ -325,13 +332,21 @@ class App extends Component {
value: contextLayout,
});
Settings.application.selectedLayout = contextLayout;
Settings.save();
updateSettings({
application: {
...Settings.application,
selectedLayout: contextLayout,
},
});
}
if (pushLayoutMeeting !== prevProps.pushLayoutMeeting) {
Settings.application.pushLayout = pushLayoutMeeting;
Settings.save();
if (pushLayoutMeetingDidChange) {
updateSettings({
application: {
...Settings.application,
pushLayout: pushLayoutMeeting,
},
});
}
if (meetingLayout === "custom" && !isPresenter) {

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

@ -11,7 +11,7 @@ const Slider = styled.div`
background-color: rgba(0,0,0,0.5);
border-radius: 32px;
& > i {
i {
color: white;
transition: 0.5s;
font-size: 200%;

View File

@ -99,7 +99,7 @@ export const INITIAL_INPUT_STATE = {
export const INITIAL_OUTPUT_STATE = {
navBar: {
display: true,
display: false,
width: 0,
height: 0,
top: 0,
@ -108,7 +108,7 @@ export const INITIAL_OUTPUT_STATE = {
zIndex: 1,
},
actionBar: {
display: true,
display: false,
width: 0,
height: 0,
top: 0,

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

@ -57,7 +57,7 @@ const NavBarContainer = ({ children, ...props }) => {
const hideNavBar = getFromUserSettings('bbb_hide_nav_bar', false);
if (hideNavBar) return null;
if (hideNavBar || navBar.display === false) return null;
return (
<NavBar

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

@ -4,6 +4,7 @@ import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteb
import WhiteboardContainer from '/imports/ui/components/whiteboard/container';
import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container';
import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils';
import { SPACE } from '/imports/utils/keyCodes';
import { defineMessages, injectIntl } from 'react-intl';
import { toast } from 'react-toastify';
import { Session } from 'meteor/session';
@ -76,7 +77,7 @@ class Presentation extends PureComponent {
fitToWidth: false,
isFullscreen: false,
tldrawAPI: null,
isZoomed: false,
isPanning: false,
};
this.currentPresentationToastId = null;
@ -91,8 +92,11 @@ class Presentation extends PureComponent {
this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this);
this.handleResize = this.handleResize.bind(this);
this.setTldrawAPI = this.setTldrawAPI.bind(this);
this.setIsPanning = this.setIsPanning.bind(this);
this.handlePanShortcut = this.handlePanShortcut.bind(this);
this.renderPresentationMenu = this.renderPresentationMenu.bind(this);
this.setIsZoomed = this.setIsZoomed.bind(this);
this.setIsPanning = this.setIsPanning.bind(this);
this.handlePanShortcut = this.handlePanShortcut.bind(this);
this.onResize = () => setTimeout(this.handleResize.bind(this), 0);
this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this);
@ -124,8 +128,28 @@ class Presentation extends PureComponent {
return stateChange;
}
setIsPanning() {
this.setState({
isPanning: !this.state.isPanning,
});
}
handlePanShortcut(e) {
const { userIsPresenter } = this.props;
if (e.keyCode === SPACE && userIsPresenter) {
switch(e.type) {
case 'keyup':
return this.state.isPanning && this.setIsPanning();
case 'keydown':
return !this.state.isPanning && this.setIsPanning();
}
}
}
componentDidMount() {
this.getInitialPresentationSizes();
this.refPresentationContainer.addEventListener('keydown', this.handlePanShortcut);
this.refPresentationContainer.addEventListener('keyup', this.handlePanShortcut);
this.refPresentationContainer
.addEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange);
window.addEventListener('resize', this.onResize, false);
@ -168,7 +192,7 @@ class Presentation extends PureComponent {
clearFakeAnnotations,
} = this.props;
const { presentationWidth, presentationHeight } = this.state;
const { presentationWidth, presentationHeight, zoom, isPanning } = this.state;
const {
numCameras: prevNumCameras,
presentationBounds: prevPresentationBounds,
@ -269,6 +293,10 @@ class Presentation extends PureComponent {
value: currentSlide.num,
});
}
if (zoom <= HUNDRED_PERCENT && isPanning || !userIsPresenter && prevProps.userIsPresenter) {
this.setIsPanning();
}
}
componentWillUnmount() {
@ -278,6 +306,8 @@ class Presentation extends PureComponent {
window.removeEventListener('resize', this.onResize, false);
this.refPresentationContainer
.removeEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange);
this.refPresentationContainer.removeEventListener('keydown', this.handlePanShortcut);
this.refPresentationContainer.removeEventListener('keyup', this.handlePanShortcut);
if (fullscreenContext) {
layoutContextDispatch({
@ -293,13 +323,14 @@ class Presentation extends PureComponent {
setTldrawAPI(api) {
this.setState({
tldrawAPI: api,
})
});
}
setIsZoomed(isZoomed) {
setIsPanning() {
this.setState({
isZoomed,
})
isPanning: !this.state.isPanning,
});
}
handleResize() {
@ -522,7 +553,6 @@ class Presentation extends PureComponent {
const {
zoom,
fitToWidth,
isZoomed,
} = this.state;
if (!userIsPresenter && !multiUser) {
@ -573,8 +603,6 @@ class Presentation extends PureComponent {
physicalSlideHeight={physicalDimensions.height}
zoom={zoom}
zoomChanger={this.zoomChanger}
setIsZoomed={this.setIsZoomed}
isZoomed={isZoomed}
/>
</PresentationOverlayContainer>
);
@ -749,8 +777,8 @@ class Presentation extends PureComponent {
layoutContextDispatch,
presentationIsOpen,
}}
isZoomed={this.state.isZoomed}
tldrawAPI={this.state.tldrawAPI}
setIsPanning={this.setIsPanning}
isPanning={this.state.isPanning}
curPageId={this.state.tldrawAPI?.getPage()?.id}
currentSlideNum={currentSlide.num}
presentationId={currentSlide.presentationId}
@ -894,15 +922,15 @@ class Presentation extends PureComponent {
isViewersCursorLocked,
fullscreenElementId,
layoutContextDispatch,
presentationIsOpen,
} = this.props;
const {
showSlide,
isFullscreen,
localPosition,
isZoomed,
presentationWidth,
presentationHeight,
fitToWidth,
zoom,
} = this.state;
let viewBoxDimensions;
@ -939,6 +967,10 @@ class Presentation extends PureComponent {
? svgWidth
: presentationToolbarMinWidth;
const slideContent = currentSlide?.content ? `${intl.formatMessage(intlMessages.slideContentStart)}
${currentSlide.content}
${intl.formatMessage(intlMessages.slideContentEnd)}` : intl.formatMessage(intlMessages.noSlideContent);
if (!currentPresentation && this.refPresentationContainer) {
return (
<></>
@ -971,26 +1003,56 @@ class Presentation extends PureComponent {
: null,
}}
>
{this.renderPresentationMenu()}
<WhiteboardContainer
whiteboardId={currentSlide?.id}
podId={podId}
slidePosition={slidePosition}
getSvgRef={this.getSvgRef}
setTldrawAPI={this.setTldrawAPI}
curPageId={currentSlide?.num.toString()}
svgUri={currentSlide?.svgUri}
intl={intl}
presentationWidth={presentationWidth}
presentationHeight={presentationHeight}
isViewersCursorLocked={isViewersCursorLocked}
isZoomed={isZoomed}
setIsZoomed={this.setIsZoomed}
zoomChanger={this.zoomChanger}
/>
{isFullscreen && <PollingContainer />}
{this.renderPresentationToolbar()}
{/*
<Styled.Presentation ref={(ref) => { this.refPresentation = ref; }}>
<Styled.SvgContainer
style={{
height: svgHeight + toolbarHeight,
}}
>
<div
style={{
position: 'absolute',
width: svgDimensions.width < 0 ? 0 : svgDimensions.width,
height: svgDimensions.height < 0 ? 0 : svgDimensions.height,
textAlign: 'center',
display: !presentationIsOpen ? 'none' : 'block',
}}
>
<Styled.VisuallyHidden id="currentSlideText">{slideContent}</Styled.VisuallyHidden>
{this.renderPresentationMenu()}
<WhiteboardContainer
whiteboardId={currentSlide?.id}
podId={podId}
slidePosition={slidePosition}
getSvgRef={this.getSvgRef}
setTldrawAPI={this.setTldrawAPI}
curPageId={currentSlide?.num.toString()}
svgUri={currentSlide?.svgUri}
intl={intl}
presentationWidth={svgWidth}
presentationHeight={svgHeight}
isViewersCursorLocked={isViewersCursorLocked}
isPanning={this.state.isPanning}
zoomChanger={this.zoomChanger}
fitToWidth={fitToWidth}
zoomValue={zoom}
/>
{isFullscreen && <PollingContainer />}
</div>
<Styled.PresentationToolbar
ref={(ref) => { this.refPresentationToolbar = ref; }}
style={
{
width: containerWidth,
}
}
>
{this.renderPresentationToolbar(svgWidth)}
</Styled.PresentationToolbar>
{/*this.renderPresentationToolbar()*/}
</Styled.SvgContainer>
</Styled.Presentation>
{/*
<Styled.Presentation ref={(ref) => { this.refPresentation = ref; }}>
<Styled.WhiteboardSizeAvailable ref={(ref) => { this.refWhiteboardArea = ref; }} />
<Styled.SvgContainer

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";
@ -12,8 +12,8 @@ const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const CursorContainer = (props) => {
const { cursorX, cursorY, presenter, uid, isViewersCursorLocked } = props;
const usingUsersContext = useContext(UsersContext);
if (cursorX > 0 && cursorY > 0) {
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;
const role = users[Auth.meetingID][Auth.userID].role;
const userId = users[Auth.meetingID][Auth.userID].userId;

View File

@ -171,6 +171,7 @@ const PresentationMenu = (props) => {
{
key: 'list-item-screenshot',
label: intl.formatMessage(intlMessages.snapshotLabel),
dataTest: "presentationSnapshot",
onClick: async () => {
setState({
loading: true,
@ -189,7 +190,7 @@ const PresentationMenu = (props) => {
try {
const { copySvg, getShapes, currentPageId } = tldrawAPI;
const svgString = copySvg(getShapes(currentPageId).map((shape) => shape.id));
const svgString = await copySvg(getShapes(currentPageId).map((shape) => shape.id));
const container = document.createElement('div');
container.innerHTML = svgString;
const svgElem = container.firstChild;

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

@ -13,8 +13,6 @@ import ZoomTool from './zoom-tool/component';
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
import KEY_CODES from '/imports/utils/keyCodes';
import ToolbarMenuItem from '/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component';
const intlMessages = defineMessages({
previousSlideLabel: {
id: 'app.presentation.presentationToolbar.prevSlideLabel',
@ -84,6 +82,10 @@ const intlMessages = defineMessages({
id: 'app.whiteboard.toolbar.multiUserOff',
description: 'Whiteboard toolbar turn multi-user off menu',
},
pan: {
id: 'app.whiteboard.toolbar.tools.hand',
description: 'presentation toolbar pan label',
}
});
class PresentationToolbar extends PureComponent {
@ -105,6 +107,11 @@ class PresentationToolbar extends PureComponent {
document.addEventListener('keydown', this.switchSlide);
}
componentDidUpdate(prevProps, prevState) {
const { zoom, setIsPanning } = this.props;
if (zoom <= HUNDRED_PERCENT && zoom !== prevProps.zoom) setIsPanning();
}
componentWillUnmount() {
document.removeEventListener('keydown', this.switchSlide);
}
@ -254,11 +261,11 @@ class PresentationToolbar extends PureComponent {
startPoll,
currentSlide,
slidePosition,
tldrawAPI,
toolbarWidth,
multiUserSize,
multiUser,
isZoomed,
setIsPanning,
isPanning,
} = this.props;
const { isMobile } = deviceInfo;
@ -388,15 +395,27 @@ class PresentationToolbar extends PureComponent {
zoomValue={zoom}
currentSlideNum={currentSlideNum}
change={this.change}
minBound={0.1}
maxBound={5}
minBound={HUNDRED_PERCENT}
maxBound={MAX_PERCENT}
step={STEP}
isMeteorConnected={isMeteorConnected}
tldrawAPI={tldrawAPI}
isZoomed={isZoomed}
/>
</TooltipContainer>
) : null}
<Styled.FitToWidthButton
role="button"
data-test="panButton"
aria-label={intl.formatMessage(intlMessages.pan)}
color="light"
disabled={(zoom <= HUNDRED_PERCENT)}
icon="hand"
size="md"
circle
onClick={setIsPanning}
label={intl.formatMessage(intlMessages.pan)}
hideLabel
panning={isPanning}
/>
<Styled.FitToWidthButton
role="button"
data-test="fitToWidthButton"
@ -415,8 +434,11 @@ class PresentationToolbar extends PureComponent {
icon="fit_to_width"
size="md"
circle
onClick={() => this.props.tldrawAPI.zoomToFit()}
label={intl.formatMessage(intlMessages.fitToPage)}
onClick={fitToWidthHandler}
label={fitToWidth
? intl.formatMessage(intlMessages.fitToPage)
: intl.formatMessage(intlMessages.fitToWidth)
}
hideLabel
/>
</Styled.PresentationZoomControls>

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

@ -89,13 +89,9 @@ const PresentationSlideControls = styled.div`
`;
const PrevSlideButton = styled(Button)`
i {
padding-left: 20%;
}
& > i {
font-size: 1rem;
padding-left: 20%;
[dir="rtl"] & {
-webkit-transform: scale(-1, 1);
@ -105,17 +101,12 @@ const PrevSlideButton = styled(Button)`
transform: scale(-1, 1);
}
}
`;
const NextSlideButton = styled(Button)`
i {
padding-left: 60%;
}
& > i {
font-size: 1rem;
padding-left: 60%;
[dir="rtl"] & {
-webkit-transform: scale(-1, 1);
@ -204,6 +195,12 @@ const FitToWidthButton = styled(Button)`
background-color: ${colorOffWhite};
border: 0;
}
${({ panning }) => panning && `
> span {
background-color: #DCE4EC;
}
`}
`;
const MultiUserTool = styled.span`
@ -213,7 +210,6 @@ const MultiUserTool = styled.span`
height: 1rem;
position: relative;
z-index: 2;
right: 1rem;
bottom: 0.5rem;
color: ${colorWhite};
display: flex;
@ -221,20 +217,35 @@ const MultiUserTool = styled.span`
align-items: center;
box-shadow: 1px 1px ${borderSizeLarge} ${colorGrayDark};
font-size: ${smPaddingX};
[dir="ltr"] & {
right: 1rem;
}
[dir="rtl"] & {
left: 1rem;
}
`;
const MUTPlaceholder = styled.div`
width: 1rem;
height: 1rem;
position: relative;
right: 1rem;
bottom: 0.5rem;
[dir="ltr"] & {
right: 1rem;
}
[dir="rtl"] & {
left: 1rem;
}
`;
const WBAccessButton = styled(Button)`
border: none !important;
& > i {
i {
font-size: 1.2rem;
[dir="rtl"] & {

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

@ -74,6 +74,14 @@ const intlMessages = defineMessages({
id: 'app.presentationUploder.dropzoneLabel',
description: 'message warning where drop files for upload',
},
externalUploadTitle: {
id: 'app.presentationUploder.externalUploadTitle',
description: 'title for external upload area',
},
externalUploadLabel: {
id: 'app.presentationUploder.externalUploadLabel',
description: 'message of external upload button',
},
dropzoneImagesLabel: {
id: 'app.presentationUploder.dropzoneImagesLabel',
description: 'message warning where drop images for upload',
@ -294,6 +302,7 @@ class PresentationUploader extends Component {
this.handleSendToChat = this.handleSendToChat.bind(this);
// renders
this.renderDropzone = this.renderDropzone.bind(this);
this.renderExternalUpload = this.renderExternalUpload.bind(this);
this.renderPicDropzone = this.renderPicDropzone.bind(this);
this.renderPresentationList = this.renderPresentationList.bind(this);
this.renderPresentationItem = this.renderPresentationItem.bind(this);
@ -319,22 +328,45 @@ class PresentationUploader extends Component {
...propPresentations,
...presentations,
});
const presStateMapped = presState.map((presentation) => {
propPresentations.forEach((propPres) => {
if (propPres.id === presentation.id) {
const prevPropPres = prevPropPresentations.find((pres) => pres.id === propPres.id);
if (propPres.isCurrent !== prevPropPres?.isCurrent) {
presentation.isCurrent = propPres.isCurrent;
shouldUpdateState = true;
}
}
});
return presentation;
const presStateFiltered = presState.filter((presentation) => {
const currentPropPres = propPresentations.find((pres) => pres.id === presentation.id);
const prevPropPres = prevPropPresentations.find((pres) => pres.id === presentation.id);
const hasConversionError = presentation?.conversion?.error;
const finishedConversion = presentation?.conversion?.done || currentPropPres?.conversion?.done;
const hasTemporaryId = presentation.id.startsWith(presentation.filename);
if (hasConversionError || (!finishedConversion && hasTemporaryId)) return true;
if (!currentPropPres) return false;
if(presentation?.conversion?.done !== finishedConversion) {
shouldUpdateState = true;
}
if (currentPropPres.isCurrent !== prevPropPres?.isCurrent) {
presentation.isCurrent = currentPropPres.isCurrent;
}
presentation.conversion = currentPropPres.conversion;
presentation.isRemovable = currentPropPres.isRemovable;
return true;
}).filter(presentation => {
const duplicated = presentations.find(
(pres) => pres.filename === presentation.filename
&& pres.id !== presentation.id
);
if (duplicated
&& duplicated.id.startsWith(presentation.filename)
&& !presentation.id.startsWith(presentation.filename)
&& presentation?.conversion?.done === duplicated?.conversion?.done) {
return false;
}
return true;
});
if (shouldUpdateState) {
this.setState({
presentations: presStateMapped,
presentations: _.uniqBy(presStateFiltered, 'id')
});
}
@ -816,7 +848,7 @@ class PresentationUploader extends Component {
</Styled.Head>
</thead>
<tbody>
{presentationsSorted.map((item) => this.renderPresentationItem(item))}
{_.uniqBy(presentationsSorted, 'id').map((item) => this.renderPresentationItem(item))}
</tbody>
</Styled.Table>
</Styled.FileList>
@ -834,7 +866,7 @@ class PresentationUploader extends Component {
let converted = 0;
let presentationsSorted = presentations
.filter((p) => (p.upload.progress || p.conversion.status) && p.file)
.filter((p) => (p.upload.progress || p.upload.error || p.conversion.status) && p.file)
.sort((a, b) => a.uploadTimestamp - b.uploadTimestamp)
.sort((a, b) => a.conversion.done - b.conversion.done);
@ -1150,6 +1182,32 @@ class PresentationUploader extends Component {
);
}
renderExternalUpload() {
const { externalUploadData, intl } = this.props;
const { uploadExternalDescription, uploadExternalUrl } = externalUploadData;
if (!uploadExternalDescription || !uploadExternalUrl) return null;
return (
<Styled.ExternalUpload>
<div>
<Styled.ExternalUploadTitle>
{intl.formatMessage(intlMessages.externalUploadTitle)}
</Styled.ExternalUploadTitle>
<p>{uploadExternalDescription}</p>
</div>
<Styled.ExternalUploadButton
color="default"
onClick={() => window.open(`${uploadExternalUrl}`)}
label={intl.formatMessage(intlMessages.externalUploadLabel)}
aria-describedby={intl.formatMessage(intlMessages.externalUploadLabel)}
/>
</Styled.ExternalUpload>
)
}
renderPicDropzone() {
const {
intl,
@ -1192,7 +1250,7 @@ class PresentationUploader extends Component {
}
renderPresentationItemStatus(item) {
const { intl } = this.props;
const { intl, fileSizeMax } = this.props;
if (!item.upload.done && item.upload.progress === 0) {
return intl.formatMessage(intlMessages.fileToUpload);
}
@ -1206,13 +1264,13 @@ class PresentationUploader extends Component {
const constraint = {};
if (item.upload.done && item.upload.error) {
if (item.conversion.status === 'FILE_TOO_LARGE') {
constraint['0'] = ((item.conversion.maxFileSize) / 1000 / 1000).toFixed(2);
}
if (item.upload.progress < 100) {
const errorMessage = intlMessages.badConnectionError;
return intl.formatMessage(errorMessage);
if (item.conversion.status === 'FILE_TOO_LARGE' || item.upload.status === 413) {
constraint['0'] = (fileSizeMax / 1000 / 1000).toFixed(2);
} else {
if (item.upload.progress < 100) {
const errorMessage = intlMessages.badConnectionError;
return intl.formatMessage(errorMessage);
}
}
const errorMessage = intlMessages[item.upload.status] || intlMessages.genericError;
@ -1302,6 +1360,7 @@ class PresentationUploader extends Component {
</Styled.ExportHint>
{isMobile ? this.renderPicDropzone() : null}
{this.renderDropzone()}
{this.renderExternalUpload()}
</Styled.ModalInner>
</Styled.UploaderModal>
) : null;

View File

@ -52,5 +52,6 @@ export default withTracker(() => {
exportPresentationToChat,
isOpen: Session.get('showUploadPresentationView') || false,
selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null,
externalUploadData: Service.getExternalUploadData(),
};
})(PresentationUploaderContainer);

View File

@ -6,6 +6,7 @@ import { makeCall } from '/imports/ui/services/api';
import logger from '/imports/startup/client/logger';
import _ from 'lodash';
import { Random } from 'meteor/random'
import Meetings from '/imports/api/meetings';
const CONVERSION_TIMEOUT = 300000;
const TOKEN_TIMEOUT = 5000;
@ -70,7 +71,7 @@ const dispatchTogglePresentationDownloadable = (presentation, newState) => {
const observePresentationConversion = (
meetingId,
tmpPresId,
temporaryPresentationId,
onConversion,
) => new Promise((resolve) => {
const conversionTimeout = setTimeout(() => {
@ -91,7 +92,7 @@ const observePresentationConversion = (
query.observe({
added: (doc) => {
if (doc.temporaryPresentationId !== tmpPresId) return;
if (doc.temporaryPresentationId !== temporaryPresentationId) return;
if (doc.conversion.status === 'FILE_TOO_LARGE' || doc.conversion.status === 'UNSUPPORTED_DOCUMENT') {
onConversion(doc.conversion);
@ -100,7 +101,7 @@ const observePresentationConversion = (
}
},
changed: (newDoc) => {
if (newDoc.temporaryPresentationId !== tmpPresId) return;
if (newDoc.temporaryPresentationId !== temporaryPresentationId) return;
onConversion(newDoc.conversion);
@ -119,12 +120,12 @@ const observePresentationConversion = (
});
const requestPresentationUploadToken = (
tmpPresId,
temporaryPresentationId,
podId,
meetingId,
filename,
) => new Promise((resolve, reject) => {
makeCall('requestPresentationUploadToken', podId, filename, tmpPresId);
makeCall('requestPresentationUploadToken', podId, filename, temporaryPresentationId);
let computation = null;
const timeout = setTimeout(() => {
@ -134,13 +135,13 @@ const requestPresentationUploadToken = (
Tracker.autorun((c) => {
computation = c;
const sub = Meteor.subscribe('presentation-upload-token', podId, filename, tmpPresId);
const sub = Meteor.subscribe('presentation-upload-token', podId, filename, temporaryPresentationId);
if (!sub.ready()) return;
const PresentationToken = PresentationUploadToken.findOne({
podId,
meetingId,
tmpPresId,
temporaryPresentationId,
used: false,
});
@ -167,13 +168,13 @@ const uploadAndConvertPresentation = (
onProgress,
onConversion,
) => {
const tmpPresId = _.uniqueId(Random.id(20))
const temporaryPresentationId = _.uniqueId(Random.id(20))
const data = new FormData();
data.append('fileUpload', file);
data.append('conference', meetingId);
data.append('room', meetingId);
data.append('temporaryPresentationId', tmpPresId);
data.append('temporaryPresentationId', temporaryPresentationId);
// TODO: Currently the uploader is not related to a POD so the id is fixed to the default
data.append('pod_id', podId);
@ -185,12 +186,12 @@ const uploadAndConvertPresentation = (
body: data,
};
return requestPresentationUploadToken(tmpPresId, podId, meetingId, file.name)
return requestPresentationUploadToken(temporaryPresentationId, podId, meetingId, file.name)
.then((token) => {
makeCall('setUsedToken', token);
return futch(endpoint.replace('upload', `${token}/upload`), opts, onProgress);
})
.then(() => observePresentationConversion(meetingId, tmpPresId, onConversion))
.then(() => observePresentationConversion(meetingId, temporaryPresentationId, onConversion))
// Trap the error so we can have parallel upload
.catch((error) => {
logger.debug({
@ -199,7 +200,7 @@ const uploadAndConvertPresentation = (
error,
},
}, 'Generic presentation upload exception catcher');
observePresentationConversion(meetingId, tmpPresId, onConversion);
observePresentationConversion(meetingId, temporaryPresentationId, onConversion);
onUpload({ error: true, done: true, status: error.code });
return Promise.resolve();
});
@ -270,6 +271,25 @@ const persistPresentationChanges = (oldState, newState, uploadEndpoint, podId) =
.then(removePresentations.bind(null, presentationsToRemove, podId));
};
const getExternalUploadData = () => {
const { meetingProp } = Meetings.findOne(
{ meetingId: Auth.meetingID },
{
fields: {
'meetingProp.uploadExternalDescription': 1,
'meetingProp.uploadExternalUrl': 1
},
},
);
const { uploadExternalDescription, uploadExternalUrl } = meetingProp;
return {
uploadExternalDescription,
uploadExternalUrl,
}
};
const exportPresentationToChat = (presentationId, observer) => {
let lastStatus = {};
@ -307,5 +327,6 @@ export default {
dispatchTogglePresentationDownloadable,
setPresentation,
requestPresentationUploadToken,
getExternalUploadData,
exportPresentationToChat,
};

View File

@ -541,6 +541,7 @@ const DownloadButton = styled(Button)`
color: ${colorBlueLight};
cursor: pointer;
display: inline-block;
font-size: 80%;
&:hover {
background-color: ${colorOffWhite} !important;
@ -581,6 +582,33 @@ const ExtraHint = styled.div`
font-weight: bold;
`;
const ExternalUpload = styled.div`
background-color: ${colorOffWhite};
border-radius: ${borderRadius};
margin-top: 2rem;
padding: ${lgPaddingX};
color: ${colorText};
font-weight: normal;
display: flex;
justify-content: space-between;
flex-direction: row;
& p {
margin: 0;
}
`;
const ExternalUploadTitle = styled.h4`
font-size: 0.9rem;
margin: 0;
`;
const ExternalUploadButton = styled(Button)`
height: 2rem;
align-self: center;
margin-left: 2rem;
`;
const ExportHint = styled(ModalHint)`
margin: 2rem 0;
`;
@ -671,6 +699,9 @@ export default {
TableItemActions,
DownloadButton,
ExtraHint,
ExternalUpload,
ExternalUploadTitle,
ExternalUploadButton,
ExportHint,
SetCurrentAction,
Head,

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

@ -483,6 +483,7 @@ class ScreenshareComponent extends React.Component {
ref={(ref) => {
this.screenshareContainer = ref;
}}
id="screenshareContainer"
>
{loaded && this.renderFullscreenButton()}
{this.renderVideo(true)}

View File

@ -75,7 +75,7 @@ const HoverToolbar = styled.div`
${({ toolbarStyle }) => toolbarStyle === 'hoverToolbar' && `
display: none;
:hover > & {
#screenshareContainer:hover > & {
display: flex;
}
`}

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