Merge branch 'bigbluebutton:develop' into develop
This commit is contained in:
commit
7d1bf9b4dd
@ -11,7 +11,7 @@ stages:
|
||||
|
||||
# define which docker image to use for builds
|
||||
default:
|
||||
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2021-08-10
|
||||
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2021-09-30
|
||||
|
||||
# This stage uses git to find out since when each package has been unmodified.
|
||||
# it then checks an API endpoint on the package server to find out for which of
|
||||
|
@ -1,5 +1,8 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
object ScreenshareModel {
|
||||
def resetDesktopSharingParams(status: ScreenshareModel) = {
|
||||
status.broadcastingRTMP = false
|
||||
@ -88,6 +91,24 @@ object ScreenshareModel {
|
||||
def getHasAudio(status: ScreenshareModel): Boolean = {
|
||||
status.hasAudio
|
||||
}
|
||||
|
||||
def stop(outGW: OutMsgRouter, liveMeeting: LiveMeeting): Unit = {
|
||||
if (isBroadcastingRTMP(liveMeeting.screenshareModel)) {
|
||||
this.resetDesktopSharingParams(liveMeeting.screenshareModel)
|
||||
|
||||
val event = MsgBuilder.buildStopScreenshareRtmpBroadcastEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
getVoiceConf(liveMeeting.screenshareModel),
|
||||
getScreenshareConf(liveMeeting.screenshareModel),
|
||||
getRTMPBroadcastingUrl(liveMeeting.screenshareModel),
|
||||
getScreenshareVideoWidth(liveMeeting.screenshareModel),
|
||||
getScreenshareVideoHeight(liveMeeting.screenshareModel),
|
||||
getTimestamp(liveMeeting.screenshareModel)
|
||||
)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ScreenshareModel {
|
||||
|
@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.models.{ Users2x, Roles }
|
||||
|
||||
trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
@ -19,15 +20,22 @@ trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
|
||||
for {
|
||||
model <- state.breakout
|
||||
room <- model.find(msg.body.breakoutId)
|
||||
requesterUser <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
BreakoutHdlrHelpers.sendJoinURL(
|
||||
liveMeeting,
|
||||
outGW,
|
||||
msg.body.userId,
|
||||
room.externalId,
|
||||
room.sequence.toString(),
|
||||
room.id
|
||||
)
|
||||
if (requesterUser.role == Roles.MODERATOR_ROLE || room.freeJoin) {
|
||||
BreakoutHdlrHelpers.sendJoinURL(
|
||||
liveMeeting,
|
||||
outGW,
|
||||
msg.body.userId,
|
||||
room.externalId,
|
||||
room.sequence.toString(),
|
||||
room.id
|
||||
)
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to request breakout room URL for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait, ExternalVideoModel }
|
||||
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait, ScreenshareModel }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
this: ExternalVideoApp2x =>
|
||||
@ -28,6 +28,10 @@ trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
val reason = "You need to be the presenter to start external videos"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
|
||||
//Stop ScreenShare if it's running
|
||||
ScreenshareModel.stop(bus.outGW, liveMeeting)
|
||||
|
||||
ExternalVideoModel.setURL(liveMeeting.externalVideoModel, msg.body.externalVideoUrl)
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait, ExternalVideoModel }
|
||||
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait StopExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
this: ExternalVideoApp2x =>
|
||||
@ -11,25 +12,16 @@ trait StopExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
def handle(msg: StopExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.info("Received StopExternalVideoPubMsgr meetingId={}", liveMeeting.props.meetingProp.intId)
|
||||
|
||||
def broadcastEvent() {
|
||||
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(StopExternalVideoEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(StopExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = StopExternalVideoEvtMsgBody()
|
||||
val event = StopExternalVideoEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to stop external video"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
ExternalVideoModel.clear(liveMeeting.externalVideoModel)
|
||||
broadcastEvent()
|
||||
|
||||
//broadcastEvent
|
||||
val msgEvent = MsgBuilder.buildStopExternalVideoEvtMsg(liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,32 +4,13 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.ScreenshareModel
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsgHdlr {
|
||||
this: ScreenshareApp2x =>
|
||||
|
||||
def handle(msg: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
def broadcastEvent(voiceConf: String, screenshareConf: String,
|
||||
stream: String, vidWidth: Int, vidHeight: Int,
|
||||
timestamp: String): BbbCommonEnvCoreMsg = {
|
||||
|
||||
val routing = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, "not-used"
|
||||
)
|
||||
val envelope = BbbCoreEnvelope(ScreenshareRtmpBroadcastStoppedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(
|
||||
ScreenshareRtmpBroadcastStoppedEvtMsg.NAME,
|
||||
liveMeeting.props.meetingProp.intId, "not-used"
|
||||
)
|
||||
|
||||
val body = ScreenshareRtmpBroadcastStoppedEvtMsgBody(voiceConf, screenshareConf,
|
||||
stream, vidWidth, vidHeight, timestamp)
|
||||
val event = ScreenshareRtmpBroadcastStoppedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
log.info("handleScreenshareRTMPBroadcastStoppedRequest: isBroadcastingRTMP=" +
|
||||
ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel) + " URL:" +
|
||||
ScreenshareModel.getRTMPBroadcastingUrl(liveMeeting.screenshareModel))
|
||||
@ -40,8 +21,12 @@ trait ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsgHdlr {
|
||||
ScreenshareModel.broadcastingRTMPStopped(liveMeeting.screenshareModel)
|
||||
|
||||
// notify viewers that RTMP broadcast stopped
|
||||
val msgEvent = broadcastEvent(msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream,
|
||||
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp)
|
||||
val msgEvent = MsgBuilder.buildStopScreenshareRtmpBroadcastEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream,
|
||||
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp
|
||||
)
|
||||
|
||||
bus.outGW.send(msgEvent)
|
||||
} else {
|
||||
log.info("STOP broadcast NOT ALLOWED when isBroadcastingRTMP=false")
|
||||
|
@ -12,7 +12,16 @@ trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleChangeUserEmojiCmdMsg(msg: ChangeUserEmojiCmdMsg) {
|
||||
if (msg.header.userId != msg.body.userId && permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
// Usually only moderators are allowed to change someone else's emoji status
|
||||
// Exceptional case: Viewers who are presenter are allowed to lower someone else's raised hand:
|
||||
val isViewerProhibitedFromLoweringOthersHand =
|
||||
!(Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId).get.emoji.equals("raiseHand") &&
|
||||
msg.body.emoji.equals("none")) ||
|
||||
permissionFailed(PermissionCheck.VIEWER_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)
|
||||
|
||||
if (msg.header.userId != msg.body.userId &&
|
||||
permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) &&
|
||||
isViewerProhibitedFromLoweringOthersHand) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to clear change user emoji status."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -339,7 +339,8 @@ class MeetingActor(
|
||||
state = state.update(tracker)
|
||||
}
|
||||
} else {
|
||||
if (state.expiryTracker.moderatorHasJoined == true) {
|
||||
if (state.expiryTracker.moderatorHasJoined == true &&
|
||||
state.expiryTracker.lastModeratorLeftOnInMs == 0) {
|
||||
log.info("All moderators have left. Setting setLastModeratorLeftOn(). meetingId=" + props.meetingProp.intId)
|
||||
val tracker = state.expiryTracker.setLastModeratorLeftOn(TimeUtil.timeNowInMs())
|
||||
state = state.update(tracker)
|
||||
|
@ -160,17 +160,32 @@ object MsgBuilder {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildStopExternalVideoEvtMsg(meetingId: String): BbbCommonEnvCoreMsg = {
|
||||
def buildStopExternalVideoEvtMsg(meetingId: String, userId: String = "not-used"): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(StopExternalVideoEvtMsg.NAME, routing)
|
||||
|
||||
val body = StopExternalVideoEvtMsgBody()
|
||||
val header = BbbClientMsgHeader(StopExternalVideoEvtMsg.NAME, meetingId, "not-used")
|
||||
val header = BbbClientMsgHeader(StopExternalVideoEvtMsg.NAME, meetingId, userId)
|
||||
val event = StopExternalVideoEvtMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildStopScreenshareRtmpBroadcastEvtMsg(
|
||||
meetingId: String,
|
||||
voiceConf: String, screenshareConf: String,
|
||||
stream: String, vidWidth: Int, vidHeight: Int,
|
||||
timestamp: String
|
||||
): BbbCommonEnvCoreMsg = {
|
||||
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
|
||||
val envelope = BbbCoreEnvelope(ScreenshareRtmpBroadcastStoppedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(ScreenshareRtmpBroadcastStoppedEvtMsg.NAME, meetingId, "not-used")
|
||||
val body = ScreenshareRtmpBroadcastStoppedEvtMsgBody(voiceConf, screenshareConf, stream, vidWidth, vidHeight, timestamp)
|
||||
val event = ScreenshareRtmpBroadcastStoppedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildMeetingCreatedEvtMsg(meetingId: String, props: DefaultProps): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(MeetingCreatedEvtMsg.NAME, routing)
|
||||
|
@ -10,7 +10,6 @@ import java.lang.annotation.Target;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@NotEmpty(message = "You must provide your password")
|
||||
@Size(min = 2, max = 64, message = "Password must be between 8 and 20 characters")
|
||||
@Constraint(validatedBy = {})
|
||||
@Target(FIELD)
|
||||
|
@ -7,6 +7,7 @@ import org.bigbluebutton.api.model.shared.Checksum;
|
||||
import org.bigbluebutton.api.model.shared.ModeratorPassword;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.Map;
|
||||
|
||||
public class EndMeeting extends RequestWithChecksum<EndMeeting.Params> {
|
||||
@ -27,6 +28,7 @@ public class EndMeeting extends RequestWithChecksum<EndMeeting.Params> {
|
||||
private String meetingID;
|
||||
|
||||
@PasswordConstraint
|
||||
@NotEmpty(message = "You must provide the moderator password")
|
||||
private String password;
|
||||
|
||||
@Valid
|
||||
|
@ -35,6 +35,7 @@ public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
|
||||
private String fullName;
|
||||
|
||||
@PasswordConstraint
|
||||
@NotEmpty(message = "You must provide either the moderator or attendee password")
|
||||
private String password;
|
||||
|
||||
@IsBooleanConstraint(message = "Guest must be a boolean value (true or false)")
|
||||
|
@ -67,6 +67,10 @@ public class PageToConvert {
|
||||
return pres.getId();
|
||||
}
|
||||
|
||||
public String getMeetingId() {
|
||||
return pres.getMeetingId();
|
||||
}
|
||||
|
||||
public PageToConvert convert() {
|
||||
|
||||
// Only create SWF files if the configuration requires it
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
@ -46,6 +46,7 @@ public class PdfToSwfSlidesGenerationService {
|
||||
PageConvertProgressMessage msg = new PageConvertProgressMessage(
|
||||
pageToConvert.getPageNumber(),
|
||||
pageToConvert.getPresId(),
|
||||
pageToConvert.getMeetingId(),
|
||||
new ArrayList<>());
|
||||
presentationConversionCompletionService.handle(msg);
|
||||
pageToConvert.getPageFile().delete();
|
||||
|
@ -36,11 +36,16 @@ public class PresentationConversionCompletionService {
|
||||
if (msg instanceof PresentationConvertMessage) {
|
||||
PresentationConvertMessage m = (PresentationConvertMessage) msg;
|
||||
PresentationToConvert p = new PresentationToConvert(m.pres);
|
||||
presentationsToConvert.put(p.getKey(), p);
|
||||
} else if (msg instanceof PageConvertProgressMessage) {
|
||||
|
||||
String presentationToConvertKey = p.getKey() + "_" + m.pres.getMeetingId();
|
||||
|
||||
presentationsToConvert.put(presentationToConvertKey, p);
|
||||
} else if (msg instanceof PageConvertProgressMessage) {
|
||||
PageConvertProgressMessage m = (PageConvertProgressMessage) msg;
|
||||
PresentationToConvert p = presentationsToConvert.get(m.presId);
|
||||
|
||||
String presentationToConvertKey = m.presId + "_" + m.meetingId;
|
||||
|
||||
PresentationToConvert p = presentationsToConvert.get(presentationToConvertKey);
|
||||
if (p != null) {
|
||||
p.incrementPagesCompleted();
|
||||
notifier.sendConversionUpdateMessage(p.getPagesCompleted(), p.pres, m.page);
|
||||
@ -52,7 +57,9 @@ public class PresentationConversionCompletionService {
|
||||
}
|
||||
|
||||
private void handleEndProcessing(PresentationToConvert p) {
|
||||
presentationsToConvert.remove(p.getKey());
|
||||
String presentationToConvertKey = p.getKey() + "_" + p.pres.getMeetingId();
|
||||
|
||||
presentationsToConvert.remove(presentationToConvertKey);
|
||||
|
||||
Map<String, Object> logData = new HashMap<String, Object>();
|
||||
logData = new HashMap<String, Object>();
|
||||
|
@ -7,9 +7,11 @@ public class PageConvertProgressMessage implements IPresentationCompletionMessag
|
||||
public final String presId;
|
||||
public final int page;
|
||||
public final List<String> errors;
|
||||
public final String meetingId;
|
||||
|
||||
public PageConvertProgressMessage(int page, String presId, List<String> errors) {
|
||||
public PageConvertProgressMessage(int page, String presId, String meetingId, List<String> errors) {
|
||||
this.presId = presId;
|
||||
this.meetingId = meetingId;
|
||||
this.page = page;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
29634
bbb-learning-dashboard/package-lock.json
generated
29634
bbb-learning-dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -36,3 +36,17 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.col-text-left {
|
||||
text-align: left;
|
||||
}
|
||||
[dir="rtl"] .col-text-left {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.col-text-right {
|
||||
text-align: right;
|
||||
}
|
||||
[dir="rtl"] .col-text-right {
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -6,29 +6,38 @@ import Card from './components/Card';
|
||||
import UsersTable from './components/UsersTable';
|
||||
import StatusTable from './components/StatusTable';
|
||||
import PollsTable from './components/PollsTable';
|
||||
import ErrorMessage from './components/ErrorMessage';
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
activitiesJson: {},
|
||||
tab: 'overview',
|
||||
meetingId: '',
|
||||
learningDashboardAccessToken: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchActivitiesJson();
|
||||
this.setDashboardParams();
|
||||
setInterval(() => {
|
||||
this.fetchActivitiesJson();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
fetchActivitiesJson() {
|
||||
setDashboardParams() {
|
||||
let learningDashboardAccessToken = '';
|
||||
let meetingId = '';
|
||||
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
if (typeof params.meeting === 'undefined') return;
|
||||
|
||||
let learningDashboardAccessToken = '';
|
||||
if (typeof params.meeting !== 'undefined') {
|
||||
meetingId = params.meeting;
|
||||
}
|
||||
|
||||
if (typeof params.report !== 'undefined') {
|
||||
learningDashboardAccessToken = params.report;
|
||||
} else {
|
||||
@ -38,22 +47,43 @@ class App extends React.Component {
|
||||
cArr.forEach((val) => {
|
||||
if (val.indexOf(`${cookieName}=`) === 0) learningDashboardAccessToken = val.substring((`${cookieName}=`).length);
|
||||
});
|
||||
|
||||
// Extend AccessToken lifetime by 30d (in each access)
|
||||
if (learningDashboardAccessToken !== '') {
|
||||
const cookieExpiresDate = new Date();
|
||||
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + (3600000 * 24 * 30));
|
||||
document.cookie = `learningDashboardAccessToken-${meetingId}=${learningDashboardAccessToken}; expires=${cookieExpiresDate.toGMTString()}; path=/;SameSite=None;Secure`;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ learningDashboardAccessToken, meetingId }, this.fetchActivitiesJson);
|
||||
}
|
||||
|
||||
fetchActivitiesJson() {
|
||||
const { learningDashboardAccessToken, meetingId } = this.state;
|
||||
|
||||
if (learningDashboardAccessToken !== '') {
|
||||
fetch(`${params.meeting}/${learningDashboardAccessToken}/learning_dashboard_data.json`)
|
||||
fetch(`${meetingId}/${learningDashboardAccessToken}/learning_dashboard_data.json`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
this.setState({ activitiesJson: json });
|
||||
this.setState({ activitiesJson: json, loading: false });
|
||||
document.title = `Learning Dashboard - ${json.name}`;
|
||||
}).catch(() => {
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
} else {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activitiesJson, tab } = this.state;
|
||||
const {
|
||||
activitiesJson, tab, learningDashboardAccessToken, loading,
|
||||
} = this.state;
|
||||
const { intl } = this.props;
|
||||
|
||||
document.title = `${intl.formatMessage({ id: 'app.learningDashboard.dashboardTitle', defaultMessage: 'Learning Dashboard' })} - ${activitiesJson.name}`;
|
||||
|
||||
function totalOfRaiseHand() {
|
||||
if (activitiesJson && activitiesJson.users) {
|
||||
return Object.values(activitiesJson.users)
|
||||
@ -131,6 +161,15 @@ class App extends React.Component {
|
||||
return meetingAveragePoints;
|
||||
}
|
||||
|
||||
function getErrorMessage() {
|
||||
if (learningDashboardAccessToken === '') {
|
||||
return intl.formatMessage({ id: 'app.learningDashboard.errors.invalidToken', defaultMessage: 'Invalid session token' });
|
||||
}
|
||||
return intl.formatMessage({ id: 'app.learningDashboard.errors.dataUnavailable', defaultMessage: 'Data is no longer available' });
|
||||
}
|
||||
|
||||
if (loading === false && typeof activitiesJson.name === 'undefined') return <ErrorMessage message={getErrorMessage()} />;
|
||||
|
||||
return (
|
||||
<div className="mx-10">
|
||||
<div className="flex items-start justify-between pb-3">
|
||||
@ -139,27 +178,35 @@ class App extends React.Component {
|
||||
<br />
|
||||
<span className="text-sm font-medium">{activitiesJson.name || ''}</span>
|
||||
</h1>
|
||||
<div className="mt-3 text-right px-4 py-1 text-gray-500 inline-block">
|
||||
<div className="mt-3 col-text-right py-1 text-gray-500 inline-block">
|
||||
<p className="font-bold">
|
||||
<FormattedDate
|
||||
value={activitiesJson.createdOn}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
<div className="inline">
|
||||
<FormattedDate
|
||||
value={activitiesJson.createdOn}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
activitiesJson.endedOn > 0
|
||||
? (
|
||||
<span className="px-2 py-1 ml-3 font-semibold leading-tight text-red-700 bg-red-100 rounded-full">
|
||||
<FormattedMessage id="app.learningDashboard.indicators.meetingStatusEnded" defaultMessage="Ended" />
|
||||
</span>
|
||||
)
|
||||
: (
|
||||
<span className="px-2 py-1 ml-3 font-semibold leading-tight text-green-700 bg-green-100 rounded-full">
|
||||
<FormattedMessage id="app.learningDashboard.indicators.meetingStatusActive" defaultMessage="Active" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
activitiesJson.endedOn > 0
|
||||
? (
|
||||
<span className="px-2 py-1 font-semibold leading-tight text-red-700 bg-red-100 rounded-full">
|
||||
<FormattedMessage id="app.learningDashboard.indicators.meetingStatusEnded" defaultMessage="Ended" />
|
||||
</span>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{
|
||||
activitiesJson.endedOn === 0
|
||||
? (
|
||||
<span className="px-2 py-1 font-semibold leading-tight text-green-700 bg-green-100 rounded-full">
|
||||
<FormattedMessage id="app.learningDashboard.indicators.meetingStatusActive" defaultMessage="Active" />
|
||||
</span>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage id="app.learningDashboard.indicators.duration" defaultMessage="Duration" />
|
||||
@ -174,8 +221,8 @@ class App extends React.Component {
|
||||
<Card
|
||||
name={
|
||||
activitiesJson.endedOn === 0
|
||||
? intl.formatMessage({ id: 'app.learningDashboard.indicators.participantsOnline', defaultMessage: 'Active Participants' })
|
||||
: intl.formatMessage({ id: 'app.learningDashboard.indicators.participantsTotal', defaultMessage: 'Total Number Of Participants' })
|
||||
? intl.formatMessage({ id: 'app.learningDashboard.indicators.usersOnline', defaultMessage: 'Active Users' })
|
||||
: intl.formatMessage({ id: 'app.learningDashboard.indicators.usersTotal', defaultMessage: 'Total Number Of Users' })
|
||||
}
|
||||
number={Object.values(activitiesJson.users || {})
|
||||
.filter((u) => activitiesJson.endedOn > 0 || u.leftOn === 0).length}
|
||||
@ -282,7 +329,7 @@ class App extends React.Component {
|
||||
</div>
|
||||
<h1 className="block my-1 pr-2 text-xl font-semibold">
|
||||
{ tab === 'overview' || tab === 'overview_activityscore'
|
||||
? <FormattedMessage id="app.learningDashboard.participantsTable.title" defaultMessage="Overview" />
|
||||
? <FormattedMessage id="app.learningDashboard.usersTable.title" defaultMessage="Overview" />
|
||||
: null }
|
||||
{ tab === 'status_timeline'
|
||||
? <FormattedMessage id="app.learningDashboard.statusTimelineTable.title" defaultMessage="Status Timeline" />
|
||||
|
22
bbb-learning-dashboard/src/components/ErrorMessage.jsx
Normal file
22
bbb-learning-dashboard/src/components/ErrorMessage.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
function ErrorMessage(props) {
|
||||
const { message } = props;
|
||||
|
||||
return (
|
||||
<div className="container flex flex-col items-center px-6 mx-auto">
|
||||
<svg className="w-12 h-12 my-8 text-gray-700" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<h1 className="text-xl font-semibold text-gray-700 dark:text-gray-200">
|
||||
{message}
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorMessage;
|
@ -15,11 +15,11 @@ class PollsTable extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="w-full whitespace-no-wrap">
|
||||
<table className="w-full whitespace-nowrap">
|
||||
<thead>
|
||||
<tr className="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b bg-gray-100">
|
||||
<tr className="text-xs font-semibold tracking-wide col-text-left text-gray-500 uppercase border-b bg-gray-100">
|
||||
<th className="px-4 py-3">
|
||||
<FormattedMessage id="app.learningDashboard.pollsTable.colParticipant" defaultMessage="Participant" />
|
||||
<FormattedMessage id="app.learningDashboard.user" defaultMessage="User" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
@ -39,13 +39,21 @@ class PollsTable extends React.Component {
|
||||
{ typeof allUsers === 'object' && Object.values(allUsers || {}).length > 0 ? (
|
||||
Object.values(allUsers || {})
|
||||
.filter((user) => Object.values(user.answers).length > 0)
|
||||
.sort((a, b) => {
|
||||
if (a.isModerator === false && b.isModerator === true) return 1;
|
||||
if (a.isModerator === true && b.isModerator === false) return -1;
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
||||
return 0;
|
||||
})
|
||||
.map((user) => (
|
||||
<tr className="text-gray-700">
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="relative hidden w-8 h-8 mr-3 rounded-full md:block">
|
||||
<div className="relative hidden w-8 h-8 rounded-full md:block">
|
||||
<UserAvatar user={user} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-semibold">{user.name}</p>
|
||||
</div>
|
||||
|
@ -29,11 +29,11 @@ class StatusTable extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="w-full whitespace-no-wrap">
|
||||
<table className="w-full whitespace-nowrap">
|
||||
<thead>
|
||||
<tr className="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b bg-gray-100">
|
||||
<th className="px-4 py-3">
|
||||
<FormattedMessage id="app.learningDashboard.statusTimelineTable.colParticipant" defaultMessage="Participant" />
|
||||
<tr className="text-xs font-semibold tracking-wide text-gray-500 uppercase border-b bg-gray-100">
|
||||
<th className="px-4 py-3 col-text-left">
|
||||
<FormattedMessage id="app.learningDashboard.user" defaultMessage="User" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
@ -44,19 +44,27 @@ class StatusTable extends React.Component {
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 13l-5 5m0 0l-5-5m5 5V6" />
|
||||
</svg>
|
||||
</th>
|
||||
{ periods.map((period) => <th className="px-4 py-3 text-left">{ `${tsToHHmmss(period - firstRegisteredOnTime)}` }</th>) }
|
||||
{ periods.map((period) => <th className="px-4 py-3 col-text-left">{ `${tsToHHmmss(period - firstRegisteredOnTime)}` }</th>) }
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y">
|
||||
{ typeof allUsers === 'object' && Object.values(allUsers || {}).length > 0 ? (
|
||||
Object.values(allUsers || {})
|
||||
.sort((a, b) => {
|
||||
if (a.isModerator === false && b.isModerator === true) return 1;
|
||||
if (a.isModerator === true && b.isModerator === false) return -1;
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
||||
return 0;
|
||||
})
|
||||
.map((user) => (
|
||||
<tr className="text-gray-700">
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="relative hidden w-8 h-8 mr-3 rounded-full md:block">
|
||||
<div className="relative hidden w-8 h-8 rounded-full md:block">
|
||||
<UserAvatar user={user} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-semibold">{user.name}</p>
|
||||
</div>
|
||||
@ -68,7 +76,7 @@ class StatusTable extends React.Component {
|
||||
period,
|
||||
period + spanMinutes);
|
||||
return (
|
||||
<td className="px-4 py-3 text-sm text-left">
|
||||
<td className="px-4 py-3 text-sm col-text-left">
|
||||
{
|
||||
user.registeredOn > period && user.registeredOn < period + spanMinutes
|
||||
? (
|
||||
|
@ -82,11 +82,11 @@ class UsersTable extends React.Component {
|
||||
});
|
||||
|
||||
return (
|
||||
<table className="w-full whitespace-no-wrap">
|
||||
<table className="w-full whitespace-nowrap">
|
||||
<thead>
|
||||
<tr className="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b bg-gray-100">
|
||||
<th className="px-4 py-3">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colParticipant" defaultMessage="Participant" />
|
||||
<th className="px-4 py-3 col-text-left">
|
||||
<FormattedMessage id="app.learningDashboard.user" defaultMessage="User" />
|
||||
{
|
||||
tab === 'overview'
|
||||
? (
|
||||
@ -104,25 +104,25 @@ class UsersTable extends React.Component {
|
||||
}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colOnline" defaultMessage="Online time" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colOnline" defaultMessage="Online time" />
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colTalk" defaultMessage="Talk time" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colTalk" defaultMessage="Talk time" />
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colWebcam" defaultMessage="Webcam Time" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colWebcam" defaultMessage="Webcam Time" />
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colMessages" defaultMessage="Messages" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colMessages" defaultMessage="Messages" />
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colEmojis" defaultMessage="Emojis" />
|
||||
<th className="px-4 py-3 col-text-left">
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colEmojis" defaultMessage="Emojis" />
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colRaiseHands" defaultMessage="Raise Hand" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colRaiseHands" defaultMessage="Raise Hand" />
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colActivityScore" defaultMessage="Activity Score" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colActivityScore" defaultMessage="Activity Score" />
|
||||
{
|
||||
tab === 'overview_activityscore'
|
||||
? (
|
||||
@ -139,8 +139,8 @@ class UsersTable extends React.Component {
|
||||
: null
|
||||
}
|
||||
</th>
|
||||
<th className="px-4 py-3">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.colStatus" defaultMessage="Status" />
|
||||
<th className="px-4 py-3 text-center">
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.colStatus" defaultMessage="Status" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -158,84 +158,83 @@ class UsersTable extends React.Component {
|
||||
})
|
||||
.map((user) => (
|
||||
<tr key={user} className="text-gray-700">
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="relative hidden w-8 h-8 mr-3 rounded-full md:block">
|
||||
{/* <img className="object-cover w-full h-full rounded-full" */}
|
||||
{/* src="" */}
|
||||
{/* alt="" loading="lazy" /> */}
|
||||
<UserAvatar user={user} />
|
||||
<div
|
||||
className="absolute inset-0 rounded-full shadow-inner"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">
|
||||
{user.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedDate
|
||||
value={user.registeredOn}
|
||||
month="short"
|
||||
day="numeric"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
second="2-digit"
|
||||
<td className="px-4 py-3 col-text-left text-sm">
|
||||
<div className="inline-block relative w-8 h-8 rounded-full">
|
||||
{/* <img className="object-cover w-full h-full rounded-full" */}
|
||||
{/* src="" */}
|
||||
{/* alt="" loading="lazy" /> */}
|
||||
<UserAvatar user={user} />
|
||||
<div
|
||||
className="absolute inset-0 rounded-full shadow-inner"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="inline-block">
|
||||
<p className="font-semibold">
|
||||
{user.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
|
||||
/>
|
||||
</p>
|
||||
{
|
||||
user.leftOn > 0
|
||||
? (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<FormattedDate
|
||||
value={user.leftOn}
|
||||
month="short"
|
||||
day="numeric"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
second="2-digit"
|
||||
</svg>
|
||||
<FormattedDate
|
||||
value={user.registeredOn}
|
||||
month="short"
|
||||
day="numeric"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
second="2-digit"
|
||||
/>
|
||||
</p>
|
||||
{
|
||||
user.leftOn > 0
|
||||
? (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</svg>
|
||||
|
||||
<FormattedDate
|
||||
value={user.leftOn}
|
||||
month="short"
|
||||
day="numeric"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
second="2-digit"
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-center items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 mr-1 inline"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@ -247,6 +246,7 @@ class UsersTable extends React.Component {
|
||||
d="M5.636 18.364a9 9 0 010-12.728m12.728 0a9 9 0 010 12.728m-9.9-2.829a5 5 0 010-7.07m7.072 0a5 5 0 010 7.07M13 12a1 1 0 11-2 0 1 1 0 012 0z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{ tsToHHmmss(
|
||||
(user.leftOn > 0
|
||||
? user.leftOn
|
||||
@ -271,7 +271,7 @@ class UsersTable extends React.Component {
|
||||
<span className="text-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 mr-1 inline"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@ -283,6 +283,7 @@ class UsersTable extends React.Component {
|
||||
d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{ tsToHHmmss(user.talk.totalTime) }
|
||||
</span>
|
||||
) : null }
|
||||
@ -293,7 +294,7 @@ class UsersTable extends React.Component {
|
||||
<span className="text-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 mr-1 inline"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@ -305,6 +306,7 @@ class UsersTable extends React.Component {
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{ tsToHHmmss(getSumOfTime(user.webcams)) }
|
||||
</span>
|
||||
) : null }
|
||||
@ -315,7 +317,7 @@ class UsersTable extends React.Component {
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 mr-1 inline"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@ -327,14 +329,15 @@ class UsersTable extends React.Component {
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{user.totalOfMessages}
|
||||
</span>
|
||||
) : null }
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-left">
|
||||
<td className="px-4 py-3 text-sm col-text-left">
|
||||
{
|
||||
Object.keys(usersEmojisSummary[user.intId] || {}).map((emoji) => (
|
||||
<div className="text-xs">
|
||||
<div className="text-xs whitespace-nowrap">
|
||||
<i className={`${emojiConfigs[emoji].icon} text-sm`} />
|
||||
|
||||
{ usersEmojisSummary[user.intId][emoji] }
|
||||
@ -353,7 +356,7 @@ class UsersTable extends React.Component {
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 mr-1 inline"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@ -365,6 +368,7 @@ class UsersTable extends React.Component {
|
||||
d="M7 11.5V14m0-2.5v-6a1.5 1.5 0 113 0m-3 6a1.5 1.5 0 00-3 0v2a7.5 7.5 0 0015 0v-5a1.5 1.5 0 00-3 0m-6-3V11m0-5.5v-1a1.5 1.5 0 013 0v1m0 0V11m0-5.5a1.5 1.5 0 013 0v3m0 0V11"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{user.emojis.filter((emoji) => emoji.name === 'raiseHand').length}
|
||||
</span>
|
||||
) : null }
|
||||
@ -381,23 +385,23 @@ class UsersTable extends React.Component {
|
||||
<rect width="12" height="12" x="70" fill={usersActivityScore[user.intId] === 10 ? '#047857' : '#e4e4e7'} />
|
||||
</svg>
|
||||
|
||||
<span className="text-xs bg-gray-200 rounded-full px-2 ml-1">
|
||||
<span className="text-xs bg-gray-200 rounded-full px-2">
|
||||
<FormattedNumber value={usersActivityScore[user.intId]} minimumFractionDigits="0" maximumFractionDigits="1" />
|
||||
</span>
|
||||
</td>
|
||||
) : <td />
|
||||
}
|
||||
<td className="px-4 py-3 text-xs">
|
||||
<td className="px-4 py-3 text-xs text-center">
|
||||
{
|
||||
user.leftOn > 0
|
||||
? (
|
||||
<span className="px-2 py-1 font-semibold leading-tight text-red-700 bg-red-100 rounded-full">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.userStatusOffline" defaultMessage="Offline" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.userStatusOffline" defaultMessage="Offline" />
|
||||
</span>
|
||||
)
|
||||
: (
|
||||
<span className="px-2 py-1 font-semibold leading-tight text-green-700 bg-green-100 rounded-full">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.userStatusOnline" defaultMessage="Online" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.userStatusOnline" defaultMessage="Online" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -407,7 +411,7 @@ class UsersTable extends React.Component {
|
||||
) : (
|
||||
<tr className="text-gray-700">
|
||||
<td colSpan="8" className="px-4 py-3 text-sm text-center">
|
||||
<FormattedMessage id="app.learningDashboard.participantsTable.noUsers" defaultMessage="No users" />
|
||||
<FormattedMessage id="app.learningDashboard.usersTable.noUsers" defaultMessage="No users" />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
@ -5,6 +5,8 @@ import { IntlProvider } from 'react-intl';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he'];
|
||||
|
||||
function getLanguage() {
|
||||
let { language } = navigator;
|
||||
|
||||
@ -27,6 +29,7 @@ class Dashboard extends React.Component {
|
||||
};
|
||||
|
||||
this.setMessages();
|
||||
this.setRtl();
|
||||
}
|
||||
|
||||
setMessages() {
|
||||
@ -54,6 +57,14 @@ class Dashboard extends React.Component {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
setRtl() {
|
||||
const { intlLocale } = this.state;
|
||||
|
||||
if (RTL_LANGUAGES.includes(intlLocale)) {
|
||||
document.body.parentNode.setAttribute('dir', 'rtl');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intlLocale, intlMessages } = this.state;
|
||||
|
||||
|
@ -37,7 +37,7 @@ then
|
||||
fi
|
||||
|
||||
cp "${source}" "$tempDir/file"
|
||||
sudo /usr/bin/docker run --rm --network none --env="HOME=/tmp/" -w /tmp/ --user=$(printf %05d `id -u`) -v "$tempDir/":/data/ -v /usr/share/fonts/:/usr/share/fonts/:ro --rm bbb-soffice sh -c "/usr/bin/soffice -env:UserInstallation=file:///tmp/ $convertToParam --outdir /data /data/file"
|
||||
sudo /usr/bin/docker run --rm --memory=1g --memory-swap=1g --network none --env="HOME=/tmp/" -w /tmp/ --user=$(printf %05d `id -u`) -v "$tempDir/":/data/ -v /usr/share/fonts/:/usr/share/fonts/:ro --rm bbb-soffice sh -c "/usr/bin/soffice -env:UserInstallation=file:///tmp/ $convertToParam --outdir /data /data/file"
|
||||
cp "$tempDir/file.$convertTo" "${dest}"
|
||||
rm -r "$tempDir/"
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
bigbluebutton ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-bigbluebutton/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --outdir /data /data/file
|
||||
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --writer --outdir /data /data/file
|
||||
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to odt --writer --outdir /data /data/file
|
||||
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to doc --outdir /data /data/file
|
||||
bigbluebutton ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --memory=1g --memory-swap=1g --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-bigbluebutton/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --outdir /data /data/file
|
||||
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --memory=1g --memory-swap=1g --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --writer --outdir /data /data/file
|
||||
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --memory=1g --memory-swap=1g --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to odt --writer --outdir /data /data/file
|
||||
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --memory=1g --memory-swap=1g --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to doc --outdir /data /data/file
|
||||
|
@ -1 +1 @@
|
||||
BIGBLUEBUTTON_RELEASE=2.4-beta-4
|
||||
BIGBLUEBUTTON_RELEASE=2.4-rc-1
|
||||
|
@ -3,15 +3,15 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-base@1.4.0
|
||||
meteor-base@1.5.1
|
||||
mobile-experience@1.1.0
|
||||
mongo@1.10.0
|
||||
mongo@1.12.0
|
||||
reactive-var@1.0.11
|
||||
|
||||
standard-minifier-css@1.6.0
|
||||
standard-minifier-js@2.6.0
|
||||
standard-minifier-css@1.7.3
|
||||
standard-minifier-js@2.6.1
|
||||
es5-shim@4.8.0
|
||||
ecmascript@0.14.3
|
||||
ecmascript@0.15.3
|
||||
shell-server@0.5.0
|
||||
|
||||
static-html
|
||||
|
@ -1 +1 @@
|
||||
METEOR@1.10.2
|
||||
METEOR@2.3.6
|
||||
|
@ -1,81 +1,81 @@
|
||||
allow-deny@1.1.0
|
||||
autoupdate@1.6.0
|
||||
babel-compiler@7.5.3
|
||||
autoupdate@1.7.0
|
||||
babel-compiler@7.7.0
|
||||
babel-runtime@1.5.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.7.0
|
||||
blaze-tools@1.1.2
|
||||
boilerplate-generator@1.7.1
|
||||
caching-compiler@1.2.2
|
||||
caching-html-compiler@1.1.3
|
||||
callback-hook@1.3.0
|
||||
caching-html-compiler@1.2.1
|
||||
callback-hook@1.3.1
|
||||
cfs:reactive-list@0.0.9
|
||||
check@1.3.1
|
||||
ddp@1.4.0
|
||||
ddp-client@2.3.3
|
||||
ddp-client@2.5.0
|
||||
ddp-common@1.4.0
|
||||
ddp-server@2.3.2
|
||||
ddp-server@2.4.1
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.5.2
|
||||
ecmascript@0.14.3
|
||||
dynamic-import@0.7.1
|
||||
ecmascript@0.15.3
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.10.0
|
||||
ecmascript-runtime-server@0.9.0
|
||||
ecmascript-runtime-client@0.11.1
|
||||
ecmascript-runtime-server@0.10.1
|
||||
ejson@1.1.1
|
||||
es5-shim@4.8.0
|
||||
fetch@0.1.1
|
||||
geojson-utils@1.0.10
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.4.2
|
||||
id-map@1.1.0
|
||||
html-tools@1.1.2
|
||||
htmljs@1.1.1
|
||||
http@1.4.4
|
||||
id-map@1.1.1
|
||||
inter-process-messaging@0.1.1
|
||||
launch-screen@1.2.0
|
||||
livedata@1.0.18
|
||||
launch-screen@1.3.0
|
||||
lmieulet:meteor-coverage@3.2.0
|
||||
logging@1.1.20
|
||||
logging@1.2.0
|
||||
meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
meteor-base@1.5.1
|
||||
meteortesting:browser-tests@1.3.4
|
||||
meteortesting:mocha@2.0.1
|
||||
meteortesting:mocha@2.0.2
|
||||
meteortesting:mocha-core@8.0.1
|
||||
minifier-css@1.5.1
|
||||
minifier-js@2.6.0
|
||||
minimongo@1.6.0
|
||||
minifier-css@1.5.4
|
||||
minifier-js@2.6.1
|
||||
minimongo@1.7.0
|
||||
mobile-experience@1.1.0
|
||||
mobile-status-bar@1.1.0
|
||||
modern-browsers@0.1.5
|
||||
modules@0.15.0
|
||||
modules@0.16.0
|
||||
modules-runtime@0.12.0
|
||||
mongo@1.10.0
|
||||
mongo-decimal@0.1.1
|
||||
mongo@1.12.0
|
||||
mongo-decimal@0.1.2
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
mongo-id@1.0.8
|
||||
nathantreid:css-modules@4.1.0
|
||||
npm-mongo@3.7.1
|
||||
npm-mongo@3.9.1
|
||||
ordered-dict@1.1.0
|
||||
promise@0.11.2
|
||||
promise@0.12.0
|
||||
random@1.2.0
|
||||
react-fast-refresh@0.1.1
|
||||
react-meteor-data@0.2.16
|
||||
reactive-dict@1.3.0
|
||||
reactive-var@1.0.11
|
||||
reload@1.3.0
|
||||
reload@1.3.1
|
||||
retry@1.1.0
|
||||
rocketchat:streamer@1.1.0
|
||||
routepolicy@1.1.0
|
||||
routepolicy@1.1.1
|
||||
session@1.2.0
|
||||
shell-server@0.5.0
|
||||
socket-stream-client@0.3.0
|
||||
spacebars-compiler@1.1.3
|
||||
standard-minifier-css@1.6.0
|
||||
standard-minifier-js@2.6.0
|
||||
static-html@1.2.2
|
||||
templating-tools@1.1.2
|
||||
socket-stream-client@0.4.0
|
||||
spacebars-compiler@1.3.0
|
||||
standard-minifier-css@1.7.3
|
||||
standard-minifier-js@2.6.1
|
||||
static-html@1.3.2
|
||||
templating-tools@1.2.1
|
||||
tmeasday:check-npm-versions@0.3.2
|
||||
tracker@1.2.0
|
||||
underscore@1.0.10
|
||||
url@1.3.1
|
||||
webapp@1.9.1
|
||||
webapp-hashing@1.0.9
|
||||
url@1.3.2
|
||||
webapp@1.11.1
|
||||
webapp-hashing@1.1.0
|
||||
|
@ -74,6 +74,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
[hidden]:not([hidden="false"]) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
textarea::-webkit-input-placeholder,
|
||||
input::-webkit-input-placeholder {
|
||||
color: var(--palette-placeholder-text);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('gesturestart', function (e) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
/* eslint no-unused-vars: 0 */
|
||||
|
||||
import './wdyr';
|
||||
import '../imports/ui/services/collection-hooks/collection-hooks';
|
||||
|
||||
import React from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
@ -33,6 +34,10 @@ import ChatAdapter from '/imports/ui/components/components-data/chat-context/ada
|
||||
import UsersAdapter from '/imports/ui/components/components-data/users-context/adapter';
|
||||
import GroupChatAdapter from '/imports/ui/components/components-data/group-chat-context/adapter';
|
||||
|
||||
import('/imports/api/audio/client/bridge/bridge-whitelist').catch(() => {
|
||||
// bridge loading
|
||||
});
|
||||
|
||||
Meteor.startup(() => {
|
||||
// Logs all uncaught exceptions to the client logger
|
||||
window.addEventListener('error', (e) => {
|
||||
|
@ -45,7 +45,7 @@ export ROOT_URL=http://127.0.0.1/html5client
|
||||
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
|
||||
export MONGO_URL=mongodb://127.0.1.1/meteor
|
||||
export NODE_ENV=production
|
||||
export NODE_VERSION=node-v12.16.1-linux-x64
|
||||
export NODE_VERSION=node-v14.17.6-linux-x64
|
||||
export SERVER_WEBSOCKET_COMPRESSION=0
|
||||
export BIND_IP=127.0.0.1
|
||||
PORT=$PORT /usr/share/$NODE_VERSION/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=$INSTANCE_ID
|
||||
|
@ -45,7 +45,7 @@ export ROOT_URL=http://127.0.0.1/html5client
|
||||
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
|
||||
export MONGO_URL=mongodb://127.0.1.1/meteor
|
||||
export NODE_ENV=production
|
||||
export NODE_VERSION=node-v12.16.1-linux-x64
|
||||
export NODE_VERSION=node-v14.17.6-linux-x64
|
||||
export SERVER_WEBSOCKET_COMPRESSION=0
|
||||
export BIND_IP=127.0.0.1
|
||||
PORT=$PORT /usr/share/$NODE_VERSION/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js
|
||||
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Bridge whitelist, needed for dynamically importing bridges (as modules).
|
||||
*
|
||||
* The code is intentionally unreachable, but its trigger Meteor's static
|
||||
* analysis, which makes bridge module available to build process.
|
||||
*
|
||||
* For new bridges, we must append an import statement here.
|
||||
*
|
||||
* More information here:
|
||||
*https://docs.meteor.com/packages/dynamic-import.html
|
||||
*/
|
||||
|
||||
throw new Error();
|
||||
|
||||
/* eslint-disable no-unreachable */
|
||||
// BRIDGES LIST
|
@ -303,3 +303,5 @@ export default class KurentoAudioBridge extends BaseAudioBridge {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = KurentoAudioBridge;
|
||||
|
@ -1495,3 +1495,5 @@ export default class SIPBridge extends BaseAudioBridge {
|
||||
return this.activeSession.updateAudioConstraints(constraints);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SIPBridge;
|
||||
|
@ -15,24 +15,51 @@ export default function handleBreakoutJoinURL({ body }) {
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
// only keep each users' last invitation
|
||||
const newUsers = [];
|
||||
|
||||
const currentBreakout = Breakouts.findOne({ breakoutId }, { fields: { users: 1 } });
|
||||
|
||||
currentBreakout.users.forEach((item) => {
|
||||
if (item.userId !== userId) {
|
||||
newUsers.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
newUsers.push({
|
||||
userId,
|
||||
redirectToHtml5JoinURL,
|
||||
insertedTime: new Date().getTime(),
|
||||
});
|
||||
|
||||
const modifier = {
|
||||
$push: {
|
||||
users: {
|
||||
userId,
|
||||
redirectToHtml5JoinURL,
|
||||
insertedTime: new Date().getTime(),
|
||||
},
|
||||
$set: {
|
||||
users: newUsers,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { insertedId, numberAffected } = Breakouts.upsert(selector, modifier);
|
||||
const ATTEMPT_EVERY_MS = 1000;
|
||||
|
||||
if (insertedId) {
|
||||
Logger.info(`Added breakout id=${breakoutId}`);
|
||||
} else if (numberAffected) {
|
||||
let numberAffected = 0;
|
||||
|
||||
const updateBreakout = Meteor.bindEnvironment(() => {
|
||||
numberAffected = Breakouts.update(selector, modifier);
|
||||
});
|
||||
|
||||
const updateBreakoutPromise = new Promise((resolve) => {
|
||||
const updateBreakoutInterval = setInterval(() => {
|
||||
updateBreakout();
|
||||
|
||||
if (numberAffected) {
|
||||
resolve(clearInterval(updateBreakoutInterval));
|
||||
}
|
||||
}, ATTEMPT_EVERY_MS);
|
||||
});
|
||||
|
||||
updateBreakoutPromise.then(() => {
|
||||
Logger.info(`Upserted breakout id=${breakoutId}`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
Logger.error(`Adding breakout to collection: ${err}`);
|
||||
}
|
||||
|
@ -8,4 +8,9 @@ if (Meteor.isServer) {
|
||||
UsersTyping._ensureIndex({ meetingId: 1, isTypingTo: 1 });
|
||||
}
|
||||
|
||||
// As we store chat in context, skip adding to mini mongo
|
||||
if (Meteor.isClient) {
|
||||
GroupChatMsg.onAdded = () => false;
|
||||
}
|
||||
|
||||
export { GroupChatMsg, UsersTyping };
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Polls = new Mongo.Collection('polls');
|
||||
export const CurrentPoll = new Mongo.Collection('current-poll');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// We can have just one active poll per meeting
|
||||
|
@ -14,11 +14,11 @@ function currentPoll(secretPoll) {
|
||||
});
|
||||
|
||||
if (
|
||||
!tokenValidation ||
|
||||
tokenValidation.validationStatus !== ValidationStates.VALIDATED
|
||||
!tokenValidation
|
||||
|| tokenValidation.validationStatus !== ValidationStates.VALIDATED
|
||||
) {
|
||||
Logger.warn(
|
||||
`Publishing Polls was requested by unauth connection ${this.connection.id}`
|
||||
`Publishing Polls was requested by unauth connection ${this.connection.id}`,
|
||||
);
|
||||
return Polls.find({ meetingId: '' });
|
||||
}
|
||||
@ -41,15 +41,16 @@ function currentPoll(secretPoll) {
|
||||
if ((hasPoll && hasPoll.secretPoll) || secretPoll) {
|
||||
options.fields.responses = 0;
|
||||
}
|
||||
|
||||
return Polls.find(selector, options);
|
||||
Mongo.Collection._publishCursor(Polls.find(selector, options), this, 'current-poll');
|
||||
return this.ready();
|
||||
}
|
||||
|
||||
Logger.warn(
|
||||
'Publishing current-poll was requested by non-moderator connection',
|
||||
{ meetingId, userId, connectionId: this.connection.id },
|
||||
);
|
||||
return Polls.find({ meetingId: '' });
|
||||
Mongo.Collection._publishCursor(Polls.find({ meetingId: '' }), this, 'current-poll');
|
||||
return this.ready();
|
||||
}
|
||||
|
||||
function publishCurrentPoll(...args) {
|
||||
@ -65,11 +66,11 @@ function polls() {
|
||||
});
|
||||
|
||||
if (
|
||||
!tokenValidation ||
|
||||
tokenValidation.validationStatus !== ValidationStates.VALIDATED
|
||||
!tokenValidation
|
||||
|| tokenValidation.validationStatus !== ValidationStates.VALIDATED
|
||||
) {
|
||||
Logger.warn(
|
||||
`Publishing Polls was requested by unauth connection ${this.connection.id}`
|
||||
`Publishing Polls was requested by unauth connection ${this.connection.id}`,
|
||||
);
|
||||
return Polls.find({ meetingId: '' });
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Users = new Mongo.Collection('users');
|
||||
export const CurrentUser = new Mongo.Collection('current-user');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the users:
|
||||
|
@ -9,7 +9,8 @@ const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
function currentUser() {
|
||||
if (!this.userId) {
|
||||
return Users.find({ meetingId: '' });
|
||||
Mongo.Collection._publishCursor(Users.find({ meetingId: '' }), this, 'current-user');
|
||||
return this.ready();
|
||||
}
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
@ -28,8 +29,8 @@ function currentUser() {
|
||||
authToken: false, // Not asking for authToken from client side but also not exposing it
|
||||
},
|
||||
};
|
||||
|
||||
return Users.find(selector, options);
|
||||
Mongo.Collection._publishCursor(Users.find(selector, options), this, 'current-user');
|
||||
return this.ready();
|
||||
}
|
||||
|
||||
function publishCurrentUser(...args) {
|
||||
@ -39,7 +40,7 @@ function publishCurrentUser(...args) {
|
||||
|
||||
Meteor.publish('current-user', publishCurrentUser);
|
||||
|
||||
function users(role) {
|
||||
function users() {
|
||||
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
|
||||
|
||||
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
|
||||
|
@ -19,7 +19,7 @@ import AudioService from '/imports/ui/components/audio/service';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import { LayoutContextFunc } from '../../ui/components/layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../../ui/components/layout/context';
|
||||
import VideoService from '/imports/ui/components/video-provider/service';
|
||||
import DebugWindow from '/imports/ui/components/debug-window/component';
|
||||
import { ACTIONS, PANELS } from '../../ui/components/layout/enums';
|
||||
@ -78,7 +78,12 @@ class Base extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { animations } = this.props;
|
||||
const { animations, usersVideo, layoutContextDispatch } = this.props;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NUM_CAMERAS,
|
||||
value: usersVideo.length,
|
||||
});
|
||||
|
||||
const {
|
||||
userID: localUserId,
|
||||
@ -178,7 +183,7 @@ class Base extends Component {
|
||||
isMeteorConnected,
|
||||
subscriptionsReady,
|
||||
layoutContextDispatch,
|
||||
layoutContextState,
|
||||
sidebarContentPanel,
|
||||
usersVideo,
|
||||
} = this.props;
|
||||
const {
|
||||
@ -186,10 +191,6 @@ class Base extends Component {
|
||||
meetingExisted,
|
||||
} = this.state;
|
||||
|
||||
const { input } = layoutContextState;
|
||||
const { sidebarContent } = input;
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
|
||||
if (usersVideo !== prevProps.usersVideo) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NUM_CAMERAS,
|
||||
@ -372,7 +373,15 @@ class Base extends Component {
|
||||
Base.propTypes = propTypes;
|
||||
Base.defaultProps = defaultProps;
|
||||
|
||||
const BaseContainer = withTracker(() => {
|
||||
const BaseContainer = (props) => {
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return <Base {...{ sidebarContentPanel, layoutContextDispatch, ...props }} />;
|
||||
};
|
||||
|
||||
export default withTracker(() => {
|
||||
const {
|
||||
animations,
|
||||
} = Settings.application;
|
||||
@ -505,6 +514,4 @@ const BaseContainer = withTracker(() => {
|
||||
codeError,
|
||||
usersVideo,
|
||||
};
|
||||
})(LayoutContextFunc.withContext(Base));
|
||||
|
||||
export default BaseContainer;
|
||||
})(BaseContainer);
|
||||
|
@ -1,16 +1,15 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import PresentationUploaderService from '/imports/ui/components/presentation/presentation-uploader/service';
|
||||
import PresentationPodService from '/imports/ui/components/presentation-pod/service';
|
||||
import ActionsDropdown from './component';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../../layout/context';
|
||||
|
||||
const ActionsDropdownContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { input } = layoutContextState;
|
||||
const { sidebarContent, sidebarNavigation } = input;
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const sidebarNavigation = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return (
|
||||
<ActionsDropdown {...{
|
||||
|
@ -12,7 +12,7 @@ import Service from './service';
|
||||
import UserListService from '/imports/ui/components/user-list/service';
|
||||
import ExternalVideoService from '/imports/ui/components/external-video-player/service';
|
||||
import CaptionsService from '/imports/ui/components/captions/service';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelectOutput, layoutDispatch } from '../layout/context';
|
||||
|
||||
import MediaService, {
|
||||
getSwapLayout,
|
||||
@ -20,12 +20,11 @@ import MediaService, {
|
||||
} from '../media/service';
|
||||
|
||||
const ActionsBarContainer = (props) => {
|
||||
const actionsBarStyle = layoutSelectOutput((i) => i.actionBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { output } = layoutContextState;
|
||||
const { actionBar: actionsBarStyle } = output;
|
||||
|
||||
const currentUser = { userId: Auth.userID, emoji: users[Auth.meetingID][Auth.userID].emoji };
|
||||
|
||||
|
@ -127,7 +127,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.createBreakoutRoom.minimumDurationWarnBreakout',
|
||||
description: 'minimum duration warning message label',
|
||||
},
|
||||
|
||||
roomNameInputDesc: {
|
||||
id: 'app.createBreakoutRoom.roomNameInputDesc',
|
||||
description: 'aria description for room name change',
|
||||
}
|
||||
});
|
||||
|
||||
const BREAKOUT_LIM = Meteor.settings.public.app.breakouts.breakoutRoomLimit;
|
||||
@ -253,7 +256,6 @@ class BreakoutRoom extends PureComponent {
|
||||
roomList.removeEventListener('keydown', this.handleMoveEvent, true);
|
||||
}
|
||||
}
|
||||
this.handleDismiss();
|
||||
}
|
||||
|
||||
handleShiftUser(activeListSibling) {
|
||||
@ -373,7 +375,7 @@ class BreakoutRoom extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ preventClosing: false });
|
||||
this.handleDismiss();
|
||||
|
||||
const rooms = _.range(1, numberOfRooms + 1).map((seq) => ({
|
||||
users: this.getUserByRoom(seq).map((u) => u.userId),
|
||||
@ -403,7 +405,7 @@ class BreakoutRoom extends PureComponent {
|
||||
breakoutUsers.forEach((user) => sendInvitation(breakoutId, user.userId));
|
||||
});
|
||||
|
||||
this.setState({ preventClosing: false });
|
||||
this.handleDismiss();
|
||||
}
|
||||
|
||||
onAssignRandomly() {
|
||||
@ -617,8 +619,8 @@ class BreakoutRoom extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className={styles.boxContainer} key="rooms-grid-" ref={(r) => { this.listOfUsers = r; }}>
|
||||
<div className={!leastOneUserIsValid ? styles.changeToWarn : null}>
|
||||
<p className={styles.freeJoinLabel}>
|
||||
<div role="alert" className={!leastOneUserIsValid ? styles.changeToWarn : null}>
|
||||
<span className={styles.freeJoinLabel}>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
@ -627,7 +629,7 @@ class BreakoutRoom extends PureComponent {
|
||||
intl.formatMessage(intlMessages.notAssigned, { 0: this.getUserByRoom(0).length })
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</span>
|
||||
<div className={styles.breakoutBox} onDrop={drop(0)} onDragOver={allowDrop} tabIndex={0}>
|
||||
{this.renderUserItemByRoom(0)}
|
||||
</div>
|
||||
@ -638,7 +640,7 @@ class BreakoutRoom extends PureComponent {
|
||||
{
|
||||
_.range(1, rooms + 1).map((value) => (
|
||||
<div key={`room-${value}`}>
|
||||
<p className={styles.freeJoinLabel}>
|
||||
<span className={styles.freeJoinLabel}>
|
||||
<input
|
||||
type="text"
|
||||
maxLength="255"
|
||||
@ -648,9 +650,13 @@ class BreakoutRoom extends PureComponent {
|
||||
value={this.getRoomName(value)}
|
||||
onChange={changeRoomName(value)}
|
||||
onBlur={changeRoomName(value)}
|
||||
aria-label={intl.formatMessage(intlMessages.duration)}
|
||||
aria-label={`${this.getRoomName(value)}`}
|
||||
aria-describedby={this.getRoomName(value).length === 0 ? `room-error-${value}` : `room-input-${value}`}
|
||||
/>
|
||||
</p>
|
||||
<div aria-hidden id={`room-input-${value}`} className={"sr-only"}>
|
||||
{intl.formatMessage(intlMessages.roomNameInputDesc)}
|
||||
</div>
|
||||
</span>
|
||||
<div className={styles.breakoutBox} onDrop={drop(value)} onDragOver={allowDrop} tabIndex={0}>
|
||||
{this.renderUserItemByRoom(value)}
|
||||
{isInvitation && this.renderJoinedUsers(value)}
|
||||
@ -661,7 +667,7 @@ class BreakoutRoom extends PureComponent {
|
||||
</span>
|
||||
) : null}
|
||||
{this.getRoomName(value).length === 0 ? (
|
||||
<span className={styles.spanWarn}>
|
||||
<span aria-hidden id={`room-error-${value}`} className={styles.spanWarn}>
|
||||
{intl.formatMessage(intlMessages.roomNameEmptyIsValid)}
|
||||
</span>
|
||||
) : null}
|
||||
@ -787,7 +793,7 @@ class BreakoutRoom extends PureComponent {
|
||||
>
|
||||
{intl.formatMessage(intlMessages.numberOfRoomsIsValid)}
|
||||
</span>
|
||||
<span id="randomlyAssignDesc" className="sr-only">
|
||||
<span aria-hidden id="randomlyAssignDesc" className="sr-only">
|
||||
{intl.formatMessage(intlMessages.randomlyAssignDesc)}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import QuickPollDropdown from './component';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutDispatch } from '../../layout/context';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
|
||||
const QuickPollDropdownContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextDispatch } = layoutContext;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return <QuickPollDropdown {...{ layoutContextDispatch, ...props }} />;
|
||||
};
|
||||
|
||||
|
@ -175,6 +175,7 @@ const ScreenshareButton = ({
|
||||
className={cx(isVideoBroadcasting || styles.btn)}
|
||||
disabled={(!isMeteorConnected && !isVideoBroadcasting) || !screenshareDataSavingSetting}
|
||||
icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'}
|
||||
data-test={isVideoBroadcasting ? 'stopScreenShare' : 'startScreenShare'}
|
||||
label={intl.formatMessage(vLabel)}
|
||||
description={intl.formatMessage(vDescr)}
|
||||
color={isVideoBroadcasting ? 'primary' : 'default'}
|
||||
|
@ -29,16 +29,11 @@ import PresentationAreaContainer from '../presentation/presentation-area/contain
|
||||
import ScreenshareContainer from '../screenshare/container';
|
||||
import ExternalVideoContainer from '../external-video-player/container';
|
||||
import { styles } from './styles';
|
||||
import {
|
||||
LAYOUT_TYPE, DEVICE_TYPE, ACTIONS,
|
||||
} from '../layout/enums';
|
||||
import { DEVICE_TYPE, ACTIONS } from '../layout/enums';
|
||||
import {
|
||||
isMobile, isTablet, isTabletPortrait, isTabletLandscape, isDesktop,
|
||||
} from '../layout/utils';
|
||||
import CustomLayout from '../layout/layout-manager/customLayout';
|
||||
import SmartLayout from '../layout/layout-manager/smartLayout';
|
||||
import PresentationFocusLayout from '../layout/layout-manager/presentationFocusLayout';
|
||||
import VideoFocusLayout from '../layout/layout-manager/videoFocusLayout';
|
||||
import LayoutEngine from '../layout/layout-manager/layoutEngine';
|
||||
import NavBarContainer from '../nav-bar/container';
|
||||
import SidebarNavigationContainer from '../sidebar-navigation/container';
|
||||
import SidebarContentContainer from '../sidebar-content/container';
|
||||
@ -47,6 +42,7 @@ import ConnectionStatusService from '/imports/ui/components/connection-status/se
|
||||
import { NAVBAR_HEIGHT, LARGE_NAVBAR_HEIGHT } from '/imports/ui/components/layout/defaultValues';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import LayoutService from '/imports/ui/components/layout/service';
|
||||
import { registerTitleView } from '/imports/utils/dom-utils';
|
||||
|
||||
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
@ -99,6 +95,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.whiteboard.annotations.poll',
|
||||
description: 'message displayed when a poll is published',
|
||||
},
|
||||
defaultViewLabel: {
|
||||
id: 'app.title.defaultViewLabel',
|
||||
description: 'view name apended to document title',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
@ -153,12 +153,14 @@ class App extends Component {
|
||||
const { browserName } = browserInfo;
|
||||
const { osName } = deviceInfo;
|
||||
|
||||
registerTitleView(intl.formatMessage(intlMessages.defaultViewLabel));
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_IS_RTL,
|
||||
value: isRTL,
|
||||
});
|
||||
|
||||
MediaService.setSwapLayout();
|
||||
MediaService.setSwapLayout(layoutContextDispatch);
|
||||
Modal.setAppElement('#app');
|
||||
|
||||
const fontSize = isMobile() ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
|
||||
@ -443,22 +445,6 @@ class App extends Component {
|
||||
) : null);
|
||||
}
|
||||
|
||||
renderLayoutManager() {
|
||||
const { layoutType } = this.props;
|
||||
switch (layoutType) {
|
||||
case LAYOUT_TYPE.CUSTOM_LAYOUT:
|
||||
return <CustomLayout />;
|
||||
case LAYOUT_TYPE.SMART_LAYOUT:
|
||||
return <SmartLayout />;
|
||||
case LAYOUT_TYPE.PRESENTATION_FOCUS:
|
||||
return <PresentationFocusLayout />;
|
||||
case LAYOUT_TYPE.VIDEO_FOCUS:
|
||||
return <VideoFocusLayout />;
|
||||
default:
|
||||
return <CustomLayout />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
customStyle,
|
||||
@ -469,11 +455,12 @@ class App extends Component {
|
||||
shouldShowScreenshare,
|
||||
shouldShowExternalVideo,
|
||||
isPresenter,
|
||||
layoutType,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderLayoutManager()}
|
||||
<LayoutEngine layoutType={layoutType} />
|
||||
<div
|
||||
id="layout"
|
||||
className={styles.layout}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
@ -12,9 +12,14 @@ import CaptionsService from '/imports/ui/components/captions/service';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import UserInfos from '/imports/api/users-infos';
|
||||
import LayoutContext from '../layout/context';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import {
|
||||
layoutSelect,
|
||||
layoutSelectInput,
|
||||
layoutSelectOutput,
|
||||
layoutDispatch
|
||||
} from '../layout/context';
|
||||
|
||||
import {
|
||||
getFontSize,
|
||||
@ -51,29 +56,31 @@ const endMeeting = (code) => {
|
||||
};
|
||||
|
||||
const AppContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
|
||||
const {
|
||||
actionsbar,
|
||||
meetingLayout,
|
||||
settingsLayout,
|
||||
pushLayoutToEveryone,
|
||||
currentUserId,
|
||||
shouldShowPresentation: propsShouldShowPresentation,
|
||||
presentationRestoreOnUpdate,
|
||||
...otherProps
|
||||
} = props;
|
||||
const {
|
||||
input,
|
||||
output,
|
||||
layoutType,
|
||||
deviceType,
|
||||
} = layoutContextState;
|
||||
const { sidebarContent, sidebarNavigation } = input;
|
||||
const { actionBar: actionsBarStyle, captions: captionsStyle } = output;
|
||||
const { sidebarNavPanel } = sidebarNavigation;
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
const sidebarNavigationIsOpen = sidebarNavigation.isOpen;
|
||||
const sidebarContentIsOpen = sidebarContent.isOpen;
|
||||
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const sidebarNavigation = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const actionsBarStyle = layoutSelectOutput((i) => i.actionBar);
|
||||
const captionsStyle = layoutSelectOutput((i) => i.captions);
|
||||
const presentation = layoutSelectInput((i) => i.presentation);
|
||||
const layoutType = layoutSelect((i) => i.layoutType);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const { sidebarContentPanel, isOpen: sidebarContentIsOpen } = sidebarContent;
|
||||
const { sidebarNavPanel, isOpen: sidebarNavigationIsOpen } = sidebarNavigation;
|
||||
const { isOpen: presentationIsOpen } = presentation;
|
||||
const shouldShowPresentation = propsShouldShowPresentation
|
||||
&& (presentationIsOpen || presentationRestoreOnUpdate);
|
||||
|
||||
return currentUserId
|
||||
? (
|
||||
@ -93,6 +100,7 @@ const AppContainer = (props) => {
|
||||
sidebarNavigationIsOpen,
|
||||
sidebarContentPanel,
|
||||
sidebarContentIsOpen,
|
||||
shouldShowPresentation,
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
@ -162,7 +170,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
||||
const { viewScreenshare } = Settings.dataSaving;
|
||||
const shouldShowExternalVideo = MediaService.shouldShowExternalVideo();
|
||||
const shouldShowScreenshare = MediaService.shouldShowScreenshare()
|
||||
&& (viewScreenshare || MediaService.isUserPresenter()) && !shouldShowExternalVideo;
|
||||
&& (viewScreenshare || MediaService.isUserPresenter());
|
||||
let customStyleUrl = getFromUserSettings('bbb_custom_style_url', false);
|
||||
|
||||
if (!customStyleUrl && CUSTOM_STYLE_URL) {
|
||||
@ -195,6 +203,10 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
||||
shouldShowPresentation: !shouldShowScreenshare && !shouldShowExternalVideo,
|
||||
shouldShowExternalVideo,
|
||||
isLargeFont: Session.get('isLargeFont'),
|
||||
presentationRestoreOnUpdate: getFromUserSettings(
|
||||
'bbb_force_restore_presentation_on_new_events',
|
||||
Meteor.settings.public.presentation.restoreOnUpdate,
|
||||
),
|
||||
};
|
||||
})(AppContainer)));
|
||||
|
||||
|
@ -79,6 +79,7 @@ class AudioControls extends PureComponent {
|
||||
hideLabel
|
||||
aria-label={intl.formatMessage(intlMessages.joinAudio)}
|
||||
label={intl.formatMessage(intlMessages.joinAudio)}
|
||||
data-test="joinAudio"
|
||||
color="default"
|
||||
ghost
|
||||
icon="audio_off"
|
||||
|
@ -290,6 +290,7 @@ class InputStreamLiveSelector extends Component {
|
||||
aria-label={intl.formatMessage(intlMessages.leaveAudio)}
|
||||
label={intl.formatMessage(intlMessages.leaveAudio)}
|
||||
accessKey={shortcuts.leaveaudio}
|
||||
data-test="leaveAudio"
|
||||
hideLabel
|
||||
color="primary"
|
||||
icon={isListenOnly ? 'listen' : 'audio_on'}
|
||||
|
@ -187,7 +187,7 @@ class AudioModal extends Component {
|
||||
|
||||
if (autoplayBlocked !== prevProps.autoplayBlocked) {
|
||||
if (autoplayBlocked) {
|
||||
this.setContent('autoplayBlocked');
|
||||
this.setContent({ content: 'autoplayBlocked' });
|
||||
} else {
|
||||
closeModal();
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { Session } from 'meteor/session';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import BannerComponent from './component';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../layout/context';
|
||||
|
||||
const BannerContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { input } = layoutContextState;
|
||||
const { bannerBar } = input;
|
||||
const bannerBar = layoutSelectInput((i) => i.bannerBar);
|
||||
const { hasBanner } = bannerBar;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return <BannerComponent {...{ hasBanner, layoutContextDispatch, ...props }} />;
|
||||
};
|
||||
|
@ -119,6 +119,10 @@ class BreakoutRoom extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.panel) this.panel.firstChild.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const {
|
||||
breakoutRoomUser,
|
||||
@ -167,6 +171,7 @@ class BreakoutRoom extends PureComponent {
|
||||
this.setState(
|
||||
{
|
||||
waiting: true,
|
||||
generated: false,
|
||||
requestedBreakoutId: breakoutId,
|
||||
},
|
||||
() => requestJoinURL(breakoutId),
|
||||
@ -309,7 +314,7 @@ class BreakoutRoom extends PureComponent {
|
||||
<Button
|
||||
label={this.getBreakoutLabel(breakoutId)}
|
||||
data-test="breakoutJoin"
|
||||
aria-label={`${intl.formatMessage(intlMessages.breakoutJoin)} ${number}`}
|
||||
aria-label={`${this.getBreakoutLabel(breakoutId)} ${this.props.breakoutRooms[number - 1]?.shortName }`}
|
||||
onClick={() => {
|
||||
this.getBreakoutURL(breakoutId);
|
||||
// leave main room's audio,
|
||||
@ -513,7 +518,7 @@ class BreakoutRoom extends PureComponent {
|
||||
amIModerator,
|
||||
} = this.props;
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.panel} ref={(n) => this.panel = n}>
|
||||
<Button
|
||||
icon="left_arrow"
|
||||
label={intl.formatMessage(intlMessages.breakoutTitle)}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import AudioManager from '/imports/ui/services/audio-manager';
|
||||
import BreakoutComponent from './component';
|
||||
import Service from './service';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutDispatch } from '../layout/context';
|
||||
|
||||
const BreakoutContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextDispatch } = layoutContext;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return <BreakoutComponent {...{ layoutContextDispatch, ...props }} />;
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { Session } from 'meteor/session';
|
||||
import CaptionsService from '/imports/ui/components/captions/service';
|
||||
import Pad from './component';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../../layout/context';
|
||||
import { ACTIONS, PANELS } from '../../layout/enums';
|
||||
|
||||
const PadContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextDispatch, layoutContextState } = layoutContext;
|
||||
const { input } = layoutContextState;
|
||||
const { cameraDock } = input;
|
||||
const cameraDock = layoutSelectInput((i) => i.cameraDock);
|
||||
const { isResizing } = cameraDock;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const {
|
||||
amIModerator,
|
||||
|
@ -3,15 +3,15 @@ import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { withModalMounter } from '/imports/ui/components/modal/service';
|
||||
import CaptionsService from '/imports/ui/components/captions/service';
|
||||
import WriterMenu from './component';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutDispatch } from '../../layout/context';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
const WriterMenuContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextDispatch } = layoutContext;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
const currentUser = users[Auth.meetingID][Auth.userID];
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ChatAlert from './component';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutSelect, layoutSelectInput, layoutDispatch } from '../../layout/context';
|
||||
import { PANELS } from '../../layout/enums';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import { ChatContext } from '/imports/ui/components/components-data/chat-context/context';
|
||||
@ -17,11 +17,11 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const ChatAlertContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { idChatOpen, input } = layoutContextState;
|
||||
const { sidebarContent } = input;
|
||||
const idChatOpen = layoutSelect((i) => i.idChatOpen);
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const { audioAlertEnabled, pushAlertEnabled } = props;
|
||||
|
||||
let idChat = idChatOpen;
|
||||
|
@ -5,6 +5,8 @@ import _ from 'lodash';
|
||||
import BBBMenu from "/imports/ui/components/menu/component";
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
|
||||
import { alertScreenReader } from '/imports/utils/dom-utils';
|
||||
|
||||
import ChatService from '../service';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -20,6 +22,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.chat.dropdown.copy',
|
||||
description: 'Copy button label',
|
||||
},
|
||||
copySuccess: {
|
||||
id: 'app.chat.copySuccess',
|
||||
description: 'aria success alert',
|
||||
},
|
||||
copyErr: {
|
||||
id: 'app.chat.copyErr',
|
||||
description: 'aria error alert',
|
||||
},
|
||||
options: {
|
||||
id: 'app.chat.dropdown.options',
|
||||
description: 'Chat Options',
|
||||
@ -92,7 +102,11 @@ class ChatDropdown extends PureComponent {
|
||||
label: intl.formatMessage(intlMessages.copy),
|
||||
onClick: () => {
|
||||
let chatHistory = ChatService.exportChat(timeWindowsValues, users, intl);
|
||||
navigator.clipboard.writeText(chatHistory);
|
||||
navigator.clipboard.writeText(chatHistory).then(() => {
|
||||
alertScreenReader(intl.formatMessage(intlMessages.copySuccess));
|
||||
}).catch(() => {
|
||||
alertScreenReader(intl.formatMessage(intlMessages.copyErr));
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -121,6 +135,7 @@ class ChatDropdown extends PureComponent {
|
||||
|
||||
if (!amIModerator && !ENABLE_SAVE_AND_COPY_PUBLIC_CHAT) return null;
|
||||
return (
|
||||
<>
|
||||
<BBBMenu
|
||||
trigger={
|
||||
<Button
|
||||
@ -148,6 +163,7 @@ class ChatDropdown extends PureComponent {
|
||||
}}
|
||||
actions={this.getAvailableActions()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
|
||||
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
|
||||
import Chat from '/imports/ui/components/chat/component';
|
||||
import ChatService from './service';
|
||||
import { LayoutContextFunc } from '../layout/context';
|
||||
import { layoutSelect, layoutDispatch } from '../layout/context';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
||||
@ -71,11 +71,12 @@ const ChatContainer = (props) => {
|
||||
isChatLockedPublic,
|
||||
isChatLockedPrivate,
|
||||
users: propUsers,
|
||||
layoutContextState,
|
||||
layoutContextDispatch,
|
||||
...restProps
|
||||
} = props;
|
||||
const { idChatOpen } = layoutContextState;
|
||||
|
||||
const idChatOpen = layoutSelect((i) => i.idChatOpen);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const isPublicChat = idChatOpen === PUBLIC_CHAT_KEY;
|
||||
|
||||
const chatID = idChatOpen;
|
||||
@ -264,4 +265,4 @@ export default lockContextContainer(injectIntl(withTracker(({ intl, userLocks })
|
||||
handleClosePrivateChat: ChatService.closePrivateChat,
|
||||
},
|
||||
};
|
||||
})(LayoutContextFunc.withConsumer(ChatContainer))));
|
||||
})(ChatContainer)));
|
||||
|
@ -1,17 +1,16 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import MessageForm from './component';
|
||||
import ChatService from '/imports/ui/components/chat/service';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutSelect } from '../../layout/context';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const START_TYPING_THROTTLE_INTERVAL = 2000;
|
||||
|
||||
const MessageFormContainer = (props) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState } = layoutContext;
|
||||
const { idChatOpen } = layoutContextState;
|
||||
const idChatOpen = layoutSelect((i) => i.idChatOpen);
|
||||
|
||||
const handleSendMessage = (message) => {
|
||||
ChatService.setUserSentMessage(true);
|
||||
return ChatService.sendGroupMessage(message, idChatOpen);
|
||||
|
@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||
import TimeWindowChatItem from './component';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import ChatService from '../../service';
|
||||
import LayoutContext from '../../../layout/context';
|
||||
import { layoutSelect } from '../../../layout/context';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
||||
@ -12,9 +12,9 @@ const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
const TimeWindowChatItemContainer = (props) => {
|
||||
const { message, messageId } = props;
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState } = layoutContext;
|
||||
const { idChatOpen } = layoutContextState;
|
||||
|
||||
const idChatOpen = layoutSelect((i) => i.idChatOpen);
|
||||
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
const {
|
||||
|
@ -125,7 +125,7 @@
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
flex-basis: 3.5rem;
|
||||
color: var(--color-gray-light);
|
||||
color: var(--palette-placeholder-text);
|
||||
text-transform: uppercase;
|
||||
font-size: 75%;
|
||||
margin: 0 0 0 calc(var(--line-height-computed) / 2);
|
||||
|
@ -5,6 +5,7 @@ import { UsersContext } from '../users-context/context';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import CollectionEventsBroker from '/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks';
|
||||
|
||||
let prevUserData = {};
|
||||
let currentUserData = {};
|
||||
@ -119,23 +120,19 @@ const Adapter = () => {
|
||||
});
|
||||
}, 1000, { trailing: true, leading: true });
|
||||
|
||||
Meteor.connection._stream.socket.addEventListener('message', (msg) => {
|
||||
if (msg.data.indexOf('{"msg":"added","collection":"group-chat-msg"') !== -1) {
|
||||
const parsedMsg = JSON.parse(msg.data);
|
||||
if (parsedMsg.msg === 'added') {
|
||||
const { fields } = parsedMsg;
|
||||
if (fields.id === `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`) {
|
||||
messageQueue = [];
|
||||
dispatch({
|
||||
type: ACTIONS.REMOVED,
|
||||
});
|
||||
}
|
||||
|
||||
messageQueue.push(fields);
|
||||
throttledDispatch();
|
||||
}
|
||||
const insertToContext = (fields) => {
|
||||
if (fields.id === `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`) {
|
||||
messageQueue = [];
|
||||
dispatch({
|
||||
type: ACTIONS.REMOVED,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
messageQueue.push(fields);
|
||||
throttledDispatch();
|
||||
};
|
||||
|
||||
CollectionEventsBroker.addListener('group-chat-msg', 'added', insertToContext);
|
||||
}, [Meteor.status().connected, Meteor.connection._lastSessionId]);
|
||||
|
||||
return null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import Users from '/imports/api/users';
|
||||
import Users, { CurrentUser } from '/imports/api/users';
|
||||
import UsersPersistentData from '/imports/api/users-persistent-data';
|
||||
import { UsersContext, ACTIONS } from './context';
|
||||
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
|
||||
@ -8,11 +8,11 @@ const Adapter = () => {
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { dispatch } = usingUsersContext;
|
||||
|
||||
useEffect(()=> {
|
||||
useEffect(() => {
|
||||
const usersPersistentDataCursor = UsersPersistentData.find({}, { sort: { timestamp: 1 } });
|
||||
usersPersistentDataCursor.observe({
|
||||
added: (obj) => {
|
||||
ChatLogger.debug("usersAdapter::observe::added_persistent_user", obj);
|
||||
ChatLogger.debug('usersAdapter::observe::added_persistent_user', obj);
|
||||
dispatch({
|
||||
type: ACTIONS.ADDED_USER_PERSISTENT_DATA,
|
||||
value: {
|
||||
@ -21,7 +21,7 @@ const Adapter = () => {
|
||||
});
|
||||
},
|
||||
changed: (obj) => {
|
||||
ChatLogger.debug("usersAdapter::observe::changed_persistent_user", obj);
|
||||
ChatLogger.debug('usersAdapter::observe::changed_persistent_user', obj);
|
||||
dispatch({
|
||||
type: ACTIONS.CHANGED_USER_PERSISTENT_DATA,
|
||||
value: {
|
||||
@ -29,15 +29,16 @@ const Adapter = () => {
|
||||
},
|
||||
});
|
||||
},
|
||||
removed: (obj) => {},
|
||||
removed: () => {},
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const usersCursor = Users.find({}, { sort: { timestamp: 1 } });
|
||||
const CurrentUserCursor = CurrentUser.find({});
|
||||
usersCursor.observe({
|
||||
added: (obj) => {
|
||||
ChatLogger.debug("usersAdapter::observe::added", obj);
|
||||
ChatLogger.debug('usersAdapter::observe::added', obj);
|
||||
dispatch({
|
||||
type: ACTIONS.ADDED,
|
||||
value: {
|
||||
@ -54,6 +55,18 @@ const Adapter = () => {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
CurrentUserCursor.observe({
|
||||
added: (obj) => {
|
||||
ChatLogger.debug('usersAdapter::observe::current-user::added', obj);
|
||||
dispatch({
|
||||
type: ACTIONS.ADDED,
|
||||
value: {
|
||||
user: obj,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
|
@ -321,7 +321,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
{conn.offline ? ` (${intl.formatMessage(intlMessages.offline)})` : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.status}>
|
||||
<div aria-label={`${intl.formatMessage(intlMessages.title)} ${conn.level}`} className={styles.status}>
|
||||
<div className={styles.icon}>
|
||||
<Icon level={conn.level} />
|
||||
</div>
|
||||
|
@ -2,12 +2,13 @@ import React from 'react';
|
||||
import { ChatContextProvider } from '/imports/ui/components/components-data/chat-context/context';
|
||||
import { UsersContextProvider } from '/imports/ui/components/components-data/users-context/context';
|
||||
import { GroupChatContextProvider } from '/imports/ui/components/components-data/group-chat-context/context';
|
||||
|
||||
import { LayoutContextProvider } from '/imports/ui/components/layout/context';
|
||||
|
||||
const providersList = [
|
||||
ChatContextProvider,
|
||||
GroupChatContextProvider,
|
||||
UsersContextProvider,
|
||||
LayoutContextProvider,
|
||||
];
|
||||
|
||||
const ContextProvidersComponent = props => providersList.reduce((acc, Component) => (
|
||||
|
@ -1,18 +1,17 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { Session } from 'meteor/session';
|
||||
import { getVideoUrl } from './service';
|
||||
import ExternalVideoComponent from './component';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelectInput, layoutSelectOutput, layoutDispatch } from '../layout/context';
|
||||
import MediaService, { getSwapLayout } from '/imports/ui/components/media/service';
|
||||
|
||||
const ExternalVideoContainer = (props) => {
|
||||
const layoutManager = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutManager;
|
||||
const { output, input } = layoutContextState;
|
||||
const { externalVideo } = output;
|
||||
const { cameraDock } = input;
|
||||
const externalVideo = layoutSelectOutput((i) => i.externalVideo);
|
||||
const cameraDock = layoutSelectInput((i) => i.cameraDock);
|
||||
const { isResizing } = cameraDock;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return (
|
||||
<ExternalVideoComponent
|
||||
{
|
||||
|
@ -1,17 +1,16 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import FullscreenButtonComponent from './component';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelect, layoutDispatch } from '../layout/context';
|
||||
|
||||
const FullscreenButtonContainer = (props) => <FullscreenButtonComponent {...props} />;
|
||||
|
||||
export default (props) => {
|
||||
const isIphone = !!(navigator.userAgent.match(/iPhone/i));
|
||||
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { fullscreen } = layoutContextState;
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const { element: currentElement, group: currentGroup } = fullscreen;
|
||||
const isFullscreen = !!currentElement;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return (
|
||||
<FullscreenButtonContainer
|
||||
|
@ -7,7 +7,7 @@ import { setCustomLogoUrl, setModeratorOnlyMessage } from '/imports/ui/component
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import LoadingScreen from '/imports/ui/components/loading-screen/component';
|
||||
import Users from '/imports/api/users';
|
||||
import { CurrentUser } from '/imports/api/users';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
@ -162,7 +162,7 @@ class JoinHandler extends Component {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (customdata.length) {
|
||||
makeCall('addUserSettings', customdata).then(r => resolve(r));
|
||||
makeCall('addUserSettings', customdata).then((r) => resolve(r));
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
@ -190,8 +190,8 @@ class JoinHandler extends Component {
|
||||
setModOnlyMessage(response);
|
||||
|
||||
Tracker.autorun(async (cd) => {
|
||||
const user = Users.findOne({ userId: Auth.userID, approved: true }, { fields: { _id: 1 } });
|
||||
|
||||
const user = CurrentUser
|
||||
.findOne({ userId: Auth.userID, approved: true }, { fields: { _id: 1 } });
|
||||
if (user) {
|
||||
await setCustomData(response);
|
||||
cd.stop();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { createContext, useReducer } from 'react';
|
||||
import React, { useReducer } from 'react';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ACTIONS } from '/imports/ui/components/layout/enums';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
@ -24,7 +25,7 @@ const providerPropTypes = {
|
||||
]).isRequired,
|
||||
};
|
||||
|
||||
const LayoutContext = createContext();
|
||||
const LayoutContextSelector = createContext();
|
||||
|
||||
const initState = {
|
||||
deviceType: null,
|
||||
@ -333,12 +334,12 @@ const reducer = (state, action) => {
|
||||
} = action.value;
|
||||
const { sidebarNavigation } = state.output;
|
||||
if (sidebarNavigation.display === display
|
||||
&& sidebarNavigation.minWidth === width
|
||||
&& sidebarNavigation.maxWidth === width
|
||||
&& sidebarNavigation.minWidth === minWidth
|
||||
&& sidebarNavigation.maxWidth === maxWidth
|
||||
&& sidebarNavigation.width === width
|
||||
&& sidebarNavigation.minHeight === height
|
||||
&& sidebarNavigation.minHeight === minHeight
|
||||
&& sidebarNavigation.height === height
|
||||
&& sidebarNavigation.maxHeight === height
|
||||
&& sidebarNavigation.maxHeight === maxHeight
|
||||
&& sidebarNavigation.top === top
|
||||
&& sidebarNavigation.left === left
|
||||
&& sidebarNavigation.right === right
|
||||
@ -1129,37 +1130,40 @@ const reducer = (state, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
const ContextProvider = (props) => {
|
||||
const LayoutContextProvider = (props) => {
|
||||
const [layoutContextState, layoutContextDispatch] = useReducer(reducer, initState);
|
||||
const { children } = props;
|
||||
return (
|
||||
<LayoutContext.Provider value={{
|
||||
layoutContextState,
|
||||
layoutContextDispatch,
|
||||
}}
|
||||
<LayoutContextSelector.Provider value={
|
||||
[
|
||||
layoutContextState,
|
||||
layoutContextDispatch,
|
||||
]
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</LayoutContext.Provider>
|
||||
</LayoutContextSelector.Provider>
|
||||
);
|
||||
};
|
||||
ContextProvider.propTypes = providerPropTypes;
|
||||
LayoutContextProvider.propTypes = providerPropTypes;
|
||||
|
||||
const withProvider = (Component) => (props) => (
|
||||
<ContextProvider>
|
||||
<Component {...props} />
|
||||
</ContextProvider>
|
||||
);
|
||||
|
||||
const withConsumer = (Component) => (props) => (
|
||||
<LayoutContext.Consumer>
|
||||
{(contexts) => <Component {...props} {...contexts} />}
|
||||
</LayoutContext.Consumer>
|
||||
);
|
||||
|
||||
export default LayoutContext;
|
||||
|
||||
export const LayoutContextFunc = {
|
||||
withProvider,
|
||||
withConsumer,
|
||||
withContext: (Component) => withProvider(withConsumer(Component)),
|
||||
const layoutSelect = (selector) => {
|
||||
return useContextSelector(LayoutContextSelector, layout => selector(layout[0]));
|
||||
};
|
||||
const layoutSelectInput = (selector) => {
|
||||
return useContextSelector(LayoutContextSelector, layout => selector(layout[0].input));
|
||||
};
|
||||
const layoutSelectOutput = (selector) => {
|
||||
return useContextSelector(LayoutContextSelector, layout => selector(layout[0].output));
|
||||
};
|
||||
const layoutDispatch = () => {
|
||||
return useContextSelector(LayoutContextSelector, layout => layout[1]);
|
||||
};
|
||||
|
||||
export {
|
||||
LayoutContextProvider,
|
||||
layoutSelect,
|
||||
layoutSelectInput,
|
||||
layoutSelectOutput,
|
||||
layoutDispatch,
|
||||
}
|
||||
|
@ -1,28 +1,47 @@
|
||||
import { Component } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { LayoutContextFunc } from '/imports/ui/components/layout/context';
|
||||
import { layoutSelect, layoutSelectInput, layoutDispatch } from '/imports/ui/components/layout/context';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { INITIAL_INPUT_STATE } from '/imports/ui/components/layout/initState';
|
||||
import {
|
||||
DEVICE_TYPE, ACTIONS, CAMERADOCK_POSITION, PANELS,
|
||||
} from '../enums';
|
||||
import { ACTIONS, CAMERADOCK_POSITION, PANELS } from '../enums';
|
||||
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
const min = (value1, value2) => (value1 <= value2 ? value1 : value2);
|
||||
const max = (value1, value2) => (value1 >= value2 ? value1 : value2);
|
||||
|
||||
class CustomLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const CustomLayout = (props) => {
|
||||
const { bannerAreaHeight, calculatesActionbarHeight, isMobile } = props;
|
||||
|
||||
this.throttledCalculatesLayout = _.throttle(() => this.calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
function usePrevious(value) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.init();
|
||||
const { layoutContextDispatch } = this.props;
|
||||
const input = layoutSelect((i) => i.input);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const fontSize = layoutSelect((i) => i.fontSize);
|
||||
const currentPanelType = layoutSelect((i) => i.currentPanelType);
|
||||
|
||||
const presentationInput = layoutSelectInput((i) => i.presentation);
|
||||
const sidebarNavigationInput = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const actionbarInput = layoutSelectInput((i) => i.actionBar);
|
||||
const navbarInput = layoutSelectInput((i) => i.navBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
|
||||
const throttledCalculatesLayout = _.throttle(() => calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_BROWSER_SIZE,
|
||||
@ -32,43 +51,22 @@ class CustomLayout extends Component {
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
return layoutContextState.input !== nextProps.layoutContextState.input
|
||||
|| layoutContextState.deviceType !== nextProps.layoutContextState.deviceType
|
||||
|| layoutContextState.isRTL !== nextProps.layoutContextState.isRTL
|
||||
|| layoutContextState.fontSize !== nextProps.layoutContextState.fontSize
|
||||
|| layoutContextState.fullscreen !== nextProps.layoutContextState.fullscreen;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType } = layoutContextState;
|
||||
|
||||
if (prevProps.layoutContextState.deviceType !== deviceType) {
|
||||
this.init();
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
// not all options is supported in all devices
|
||||
init();
|
||||
} else {
|
||||
this.throttledCalculatesLayout();
|
||||
throttledCalculatesLayout();
|
||||
}
|
||||
}
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
bannerAreaHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input } = layoutContextState;
|
||||
const { bannerBar, notificationsBar } = input;
|
||||
|
||||
const bannerHeight = bannerBar.hasBanner ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
const notificationHeight = notificationsBar.hasNotification ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
|
||||
return bannerHeight + notificationHeight;
|
||||
}
|
||||
|
||||
calculatesDropAreas(sidebarNavWidth, sidebarContentWidth, cameraDockBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { isRTL } = layoutContextState;
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
const calculatesDropAreas = (sidebarNavWidth, sidebarContentWidth, cameraDockBounds) => {
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
const mediaAreaHeight = windowHeight()
|
||||
- (DEFAULT_VALUES.navBarHeight + actionBarHeight);
|
||||
const mediaAreaWidth = windowWidth() - (sidebarNavWidth + sidebarContentWidth);
|
||||
@ -125,40 +123,38 @@ class CustomLayout extends Component {
|
||||
};
|
||||
|
||||
return dropZones;
|
||||
}
|
||||
};
|
||||
|
||||
init() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
const init = () => {
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: _.defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: input.sidebarNavigation.sidebarNavPanel,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: input.sidebarContent.sidebarContentPanel,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
sidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = input.sidebarContent;
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
@ -174,463 +170,171 @@ class CustomLayout extends Component {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
});
|
||||
}
|
||||
this.throttledCalculatesLayout();
|
||||
}
|
||||
throttledCalculatesLayout();
|
||||
};
|
||||
|
||||
reset() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
calculatesNavbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { isRTL } = layoutContextState;
|
||||
|
||||
return {
|
||||
width: mediaAreaBounds.width,
|
||||
height: DEFAULT_VALUES.navBarHeight,
|
||||
top: DEFAULT_VALUES.navBarTop + this.bannerAreaHeight(),
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { fontSize } = layoutContextState;
|
||||
|
||||
const BASE_FONT_SIZE = 14; // 90% font size
|
||||
const BASE_HEIGHT = DEFAULT_VALUES.actionBarHeight;
|
||||
const PADDING = DEFAULT_VALUES.actionBarPadding;
|
||||
|
||||
const actionBarHeight = ((BASE_HEIGHT / BASE_FONT_SIZE) * fontSize);
|
||||
|
||||
return {
|
||||
height: actionBarHeight + (PADDING * 2),
|
||||
innerHeight: actionBarHeight,
|
||||
padding: PADDING,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input, isRTL } = layoutContextState;
|
||||
|
||||
const actionBarHeight = this.calculatesActionbarHeight();
|
||||
|
||||
return {
|
||||
display: input.actionBar.hasActionBar,
|
||||
width: mediaAreaBounds.width,
|
||||
height: actionBarHeight.height,
|
||||
innerHeight: actionBarHeight.innerHeight,
|
||||
padding: actionBarHeight.padding,
|
||||
top: windowHeight() - actionBarHeight.height,
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarNavMinWidth,
|
||||
sidebarNavMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarNavigation.width === 0) {
|
||||
width = min(max((windowWidth() * 0.2), sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
} else {
|
||||
width = min(max(input.sidebarNavigation.width, sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
}
|
||||
minWidth = sidebarNavMinWidth;
|
||||
maxWidth = sidebarNavMaxWidth;
|
||||
}
|
||||
} else {
|
||||
minWidth = 0;
|
||||
width = 0;
|
||||
maxWidth = 0;
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
let sidebarNavHeight = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
sidebarNavHeight = windowHeight() - DEFAULT_VALUES.navBarHeight;
|
||||
} else {
|
||||
sidebarNavHeight = windowHeight();
|
||||
}
|
||||
sidebarNavHeight -= this.bannerAreaHeight();
|
||||
}
|
||||
return sidebarNavHeight;
|
||||
}
|
||||
|
||||
calculatesSidebarNavBounds() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { sidebarNavTop, navBarHeight, sidebarNavLeft } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
top = navBarHeight + this.bannerAreaHeight();
|
||||
}
|
||||
|
||||
return {
|
||||
top,
|
||||
left: !isRTL ? sidebarNavLeft : null,
|
||||
right: isRTL ? sidebarNavLeft : null,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 10 : 2,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarContentMinWidth,
|
||||
sidebarContentMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarContent.width === 0) {
|
||||
width = min(
|
||||
max((windowWidth() * 0.2), sidebarContentMinWidth), sidebarContentMaxWidth,
|
||||
);
|
||||
} else {
|
||||
width = min(max(input.sidebarContent.width, sidebarContentMinWidth),
|
||||
sidebarContentMaxWidth);
|
||||
}
|
||||
minWidth = sidebarContentMinWidth;
|
||||
maxWidth = sidebarContentMaxWidth;
|
||||
}
|
||||
} else {
|
||||
minWidth = 0;
|
||||
width = 0;
|
||||
maxWidth = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentHeight(cameraDockHeight) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const { presentation } = input;
|
||||
const { isOpen } = presentation;
|
||||
const calculatesSidebarContentHeight = (cameraDockHeight) => {
|
||||
const { isOpen } = presentationInput;
|
||||
let sidebarContentHeight = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
if (sidebarContentInput.isOpen) {
|
||||
if (isMobile) {
|
||||
sidebarContentHeight = windowHeight() - DEFAULT_VALUES.navBarHeight;
|
||||
} else if (input.cameraDock.numCameras > 0
|
||||
&& input.cameraDock.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM
|
||||
} else if (cameraDockInput.numCameras > 0
|
||||
&& cameraDockInput.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM
|
||||
&& isOpen) {
|
||||
sidebarContentHeight = windowHeight() - cameraDockHeight;
|
||||
} else {
|
||||
sidebarContentHeight = windowHeight();
|
||||
}
|
||||
sidebarContentHeight -= this.bannerAreaHeight();
|
||||
sidebarContentHeight -= bannerAreaHeight();
|
||||
}
|
||||
return sidebarContentHeight;
|
||||
}
|
||||
};
|
||||
|
||||
calculatesSidebarContentBounds(sidebarNavWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
|
||||
let top = DEFAULT_VALUES.sidebarNavTop + this.bannerAreaHeight();
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
top = DEFAULT_VALUES.navBarHeight + this.bannerAreaHeight();
|
||||
}
|
||||
|
||||
let left = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
left = !isRTL ? left : null;
|
||||
|
||||
let right = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
right = isRTL ? right : null;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 11 : 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesMediaAreaBounds(sidebarNavWidth, sidebarContentWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
let left = 0;
|
||||
let width = 0;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
left = 0;
|
||||
width = windowWidth();
|
||||
} else {
|
||||
left = !isRTL ? sidebarNavWidth + sidebarContentWidth : 0;
|
||||
width = windowWidth() - sidebarNavWidth - sidebarContentWidth;
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + this.bannerAreaHeight()),
|
||||
top: DEFAULT_VALUES.navBarHeight + this.bannerAreaHeight(),
|
||||
left,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesCameraDockBounds(sidebarNavWidth, sidebarContentWidth, mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
input, fullscreen, isRTL, deviceType,
|
||||
} = layoutContextState;
|
||||
const { presentation } = input;
|
||||
const { isOpen } = presentation;
|
||||
const { camerasMargin } = DEFAULT_VALUES;
|
||||
const calculatesCameraDockBounds = (sidebarNavWidth, sidebarContentWidth, mediaAreaBounds) => {
|
||||
const { baseCameraDockBounds } = props;
|
||||
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
|
||||
|
||||
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
|
||||
// do not proceed if using values from LayoutEngine
|
||||
if (Object.keys(baseBounds).length > 0) {
|
||||
return baseBounds;
|
||||
}
|
||||
|
||||
const {
|
||||
camerasMargin,
|
||||
cameraDockMinHeight,
|
||||
cameraDockMinWidth,
|
||||
navBarHeight,
|
||||
presentationToolbarMinWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
|
||||
const cameraDockBounds = {};
|
||||
|
||||
if (input.cameraDock.numCameras > 0) {
|
||||
if (!isOpen) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
|
||||
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
let cameraDockHeight = 0;
|
||||
let cameraDockWidth = 0;
|
||||
|
||||
if (cameraDockInput.isDragging) cameraDockBounds.zIndex = 99;
|
||||
else cameraDockBounds.zIndex = 1;
|
||||
|
||||
const isCameraTop = cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_TOP;
|
||||
const isCameraBottom = cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_BOTTOM;
|
||||
const isCameraLeft = cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_LEFT;
|
||||
const isCameraRight = cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_RIGHT;
|
||||
const isCameraSidebar = cameraDockInput.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM;
|
||||
|
||||
if (isCameraTop || isCameraBottom) {
|
||||
if (cameraDockInput.height === 0 || (isCameraTop && isMobile)) {
|
||||
cameraDockHeight = min(
|
||||
max((mediaAreaBounds.height * 0.2), cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
let cameraDockLeft = 0;
|
||||
let cameraDockHeight = 0;
|
||||
let cameraDockWidth = 0;
|
||||
switch (input.cameraDock.position) {
|
||||
case CAMERADOCK_POSITION.CONTENT_TOP: {
|
||||
cameraDockLeft = mediaAreaBounds.left;
|
||||
|
||||
if (input.cameraDock.height === 0 || deviceType === DEVICE_TYPE.MOBILE) {
|
||||
if (input.presentation.isOpen) {
|
||||
cameraDockHeight = min(
|
||||
max((mediaAreaBounds.height * 0.2), DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
cameraDockHeight = mediaAreaBounds.height;
|
||||
}
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(input.cameraDock.height, DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
|
||||
cameraDockBounds.left = cameraDockLeft;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.minHeight = DEFAULT_VALUES.cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight - camerasMargin;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_RIGHT: {
|
||||
if (input.cameraDock.width === 0) {
|
||||
if (input.presentation.isOpen) {
|
||||
cameraDockWidth = min(
|
||||
max((mediaAreaBounds.width * 0.2), DEFAULT_VALUES.cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - DEFAULT_VALUES.cameraDockMinWidth),
|
||||
);
|
||||
} else {
|
||||
cameraDockWidth = mediaAreaBounds.width;
|
||||
}
|
||||
} else {
|
||||
cameraDockWidth = min(
|
||||
max(input.cameraDock.width, DEFAULT_VALUES.cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - DEFAULT_VALUES.cameraDockMinWidth),
|
||||
);
|
||||
}
|
||||
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
|
||||
const sizeValue = input.presentation.isOpen
|
||||
? (mediaAreaBounds.left + mediaAreaBounds.width) - cameraDockWidth
|
||||
: mediaAreaBounds.left;
|
||||
cameraDockBounds.left = !isRTL ? sizeValue + camerasMargin : 0;
|
||||
cameraDockBounds.right = isRTL ? sizeValue + sidebarSize + camerasMargin : null;
|
||||
cameraDockBounds.minWidth = DEFAULT_VALUES.cameraDockMinWidth;
|
||||
cameraDockBounds.width = cameraDockWidth - (camerasMargin * 2);
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.presenterMaxWidth = mediaAreaBounds.width
|
||||
- DEFAULT_VALUES.presentationToolbarMinWidth
|
||||
- camerasMargin;
|
||||
cameraDockBounds.minHeight = DEFAULT_VALUES.cameraDockMinHeight;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_BOTTOM: {
|
||||
cameraDockLeft = mediaAreaBounds.left;
|
||||
|
||||
if (input.cameraDock.height === 0) {
|
||||
if (input.presentation.isOpen) {
|
||||
cameraDockHeight = min(
|
||||
max((mediaAreaBounds.height * 0.2), DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
cameraDockHeight = mediaAreaBounds.height;
|
||||
}
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(input.cameraDock.height, DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight
|
||||
+ mediaAreaBounds.height - cameraDockHeight;
|
||||
cameraDockBounds.left = cameraDockLeft;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.minHeight = DEFAULT_VALUES.cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight - camerasMargin;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_LEFT: {
|
||||
if (input.cameraDock.width === 0) {
|
||||
if (input.presentation.isOpen) {
|
||||
cameraDockWidth = min(
|
||||
max((mediaAreaBounds.width * 0.2), DEFAULT_VALUES.cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - DEFAULT_VALUES.cameraDockMinWidth),
|
||||
);
|
||||
} else {
|
||||
cameraDockWidth = mediaAreaBounds.width;
|
||||
}
|
||||
} else {
|
||||
cameraDockWidth = min(
|
||||
max(input.cameraDock.width, DEFAULT_VALUES.cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - DEFAULT_VALUES.cameraDockMinWidth),
|
||||
);
|
||||
}
|
||||
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
|
||||
cameraDockBounds.left = mediaAreaBounds.left + camerasMargin;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
|
||||
cameraDockBounds.minWidth = DEFAULT_VALUES.cameraDockMinWidth;
|
||||
cameraDockBounds.width = cameraDockWidth - (camerasMargin * 2);
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.presenterMaxWidth = mediaAreaBounds.width
|
||||
- DEFAULT_VALUES.presentationToolbarMinWidth
|
||||
- camerasMargin;
|
||||
cameraDockBounds.minHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM: {
|
||||
if (input.cameraDock.height === 0) {
|
||||
cameraDockHeight = min(
|
||||
max((windowHeight() * 0.2), DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(windowHeight() - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(input.cameraDock.height, DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(windowHeight() - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
|
||||
cameraDockBounds.top = windowHeight() - cameraDockHeight;
|
||||
cameraDockBounds.left = !isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.minWidth = sidebarContentWidth;
|
||||
cameraDockBounds.width = sidebarContentWidth;
|
||||
cameraDockBounds.maxWidth = sidebarContentWidth;
|
||||
cameraDockBounds.minHeight = DEFAULT_VALUES.cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight;
|
||||
cameraDockBounds.maxHeight = windowHeight() * 0.8;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log('default');
|
||||
}
|
||||
}
|
||||
|
||||
if (fullscreen.group === 'webcams') {
|
||||
cameraDockBounds.width = windowWidth();
|
||||
cameraDockBounds.minWidth = windowWidth();
|
||||
cameraDockBounds.maxWidth = windowWidth();
|
||||
cameraDockBounds.height = windowHeight();
|
||||
cameraDockBounds.minHeight = windowHeight();
|
||||
cameraDockBounds.maxHeight = windowHeight();
|
||||
cameraDockBounds.top = 0;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.right = 0;
|
||||
cameraDockBounds.zIndex = 99;
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
if (input.cameraDock.isDragging) cameraDockBounds.zIndex = 99;
|
||||
else cameraDockBounds.zIndex = 1;
|
||||
cameraDockHeight = min(
|
||||
max(cameraDockInput.height, cameraDockMinHeight),
|
||||
(mediaAreaBounds.height - cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
cameraDockBounds.width = 0;
|
||||
cameraDockBounds.height = 0;
|
||||
|
||||
cameraDockBounds.top = navBarHeight + bannerAreaHeight();
|
||||
cameraDockBounds.left = mediaAreaBounds.left;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.minHeight = cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
|
||||
if (isCameraBottom) {
|
||||
cameraDockBounds.top += (mediaAreaBounds.height - cameraDockHeight);
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
}
|
||||
if (isCameraLeft || isCameraRight) {
|
||||
if (cameraDockInput.width === 0) {
|
||||
cameraDockWidth = min(
|
||||
max((mediaAreaBounds.width * 0.2), cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - cameraDockMinWidth),
|
||||
);
|
||||
} else {
|
||||
cameraDockWidth = min(
|
||||
max(cameraDockInput.width, cameraDockMinWidth),
|
||||
(mediaAreaBounds.width - cameraDockMinWidth),
|
||||
);
|
||||
}
|
||||
|
||||
calculatesMediaBounds(sidebarNavWidth, sidebarContentWidth, cameraDockBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input, fullscreen, isRTL } = layoutContextState;
|
||||
const { presentation } = input;
|
||||
const { isOpen } = presentation;
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
cameraDockBounds.top = navBarHeight + bannerAreaHeight();
|
||||
cameraDockBounds.minWidth = cameraDockMinWidth;
|
||||
cameraDockBounds.width = cameraDockWidth;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.presenterMaxWidth = mediaAreaBounds.width
|
||||
- presentationToolbarMinWidth
|
||||
- camerasMargin;
|
||||
cameraDockBounds.minHeight = cameraDockMinHeight;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
// button size in vertical position
|
||||
cameraDockBounds.height -= 20;
|
||||
|
||||
if (isCameraRight) {
|
||||
const sizeValue = (mediaAreaBounds.left + mediaAreaBounds.width) - cameraDockWidth;
|
||||
cameraDockBounds.left = !isRTL ? sizeValue - camerasMargin : 0;
|
||||
cameraDockBounds.right = isRTL ? sizeValue + sidebarSize - camerasMargin : null;
|
||||
} else if (isCameraLeft) {
|
||||
cameraDockBounds.left = mediaAreaBounds.left + camerasMargin;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
if (isCameraSidebar) {
|
||||
if (cameraDockInput.height === 0) {
|
||||
cameraDockHeight = min(
|
||||
max((windowHeight() * 0.2), cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(cameraDockInput.height, cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
|
||||
cameraDockBounds.top = windowHeight() - cameraDockHeight;
|
||||
cameraDockBounds.left = !isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.minWidth = sidebarContentWidth;
|
||||
cameraDockBounds.width = sidebarContentWidth;
|
||||
cameraDockBounds.maxWidth = sidebarContentWidth;
|
||||
cameraDockBounds.minHeight = cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight;
|
||||
cameraDockBounds.maxHeight = windowHeight() * 0.8;
|
||||
}
|
||||
return cameraDockBounds;
|
||||
};
|
||||
|
||||
const calculatesMediaBounds = (sidebarNavWidth, sidebarContentWidth, cameraDockBounds) => {
|
||||
const { isOpen } = presentationInput;
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
const mediaAreaHeight = windowHeight()
|
||||
- (DEFAULT_VALUES.navBarHeight + actionBarHeight);
|
||||
- (DEFAULT_VALUES.navBarHeight + actionBarHeight + bannerAreaHeight());
|
||||
const mediaAreaWidth = windowWidth() - (sidebarNavWidth + sidebarContentWidth);
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
@ -658,20 +362,20 @@ class CustomLayout extends Component {
|
||||
|
||||
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
|
||||
|
||||
if (input.cameraDock.numCameras > 0 && !input.cameraDock.isDragging) {
|
||||
switch (input.cameraDock.position) {
|
||||
if (cameraDockInput.numCameras > 0 && !cameraDockInput.isDragging) {
|
||||
switch (cameraDockInput.position) {
|
||||
case CAMERADOCK_POSITION.CONTENT_TOP: {
|
||||
mediaBounds.width = mediaAreaWidth;
|
||||
mediaBounds.height = mediaAreaHeight - cameraDockBounds.height - camerasMargin;
|
||||
mediaBounds.top = navBarHeight + cameraDockBounds.height + camerasMargin;
|
||||
mediaBounds.top = navBarHeight + cameraDockBounds.height + camerasMargin + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_RIGHT: {
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - camerasMargin;
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - (camerasMargin * 2);
|
||||
mediaBounds.height = mediaAreaHeight;
|
||||
mediaBounds.top = navBarHeight;
|
||||
mediaBounds.top = navBarHeight + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize - (camerasMargin * 2) : null;
|
||||
break;
|
||||
@ -679,15 +383,15 @@ class CustomLayout extends Component {
|
||||
case CAMERADOCK_POSITION.CONTENT_BOTTOM: {
|
||||
mediaBounds.width = mediaAreaWidth;
|
||||
mediaBounds.height = mediaAreaHeight - cameraDockBounds.height - camerasMargin;
|
||||
mediaBounds.top = navBarHeight - camerasMargin;
|
||||
mediaBounds.top = navBarHeight - camerasMargin + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
break;
|
||||
}
|
||||
case CAMERADOCK_POSITION.CONTENT_LEFT: {
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - camerasMargin;
|
||||
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - (camerasMargin * 2);
|
||||
mediaBounds.height = mediaAreaHeight;
|
||||
mediaBounds.top = navBarHeight;
|
||||
mediaBounds.top = navBarHeight + bannerAreaHeight();
|
||||
const sizeValue = sidebarNavWidth
|
||||
+ sidebarContentWidth + mediaAreaWidth - mediaBounds.width;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
@ -697,7 +401,7 @@ class CustomLayout extends Component {
|
||||
case CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM: {
|
||||
mediaBounds.width = mediaAreaWidth;
|
||||
mediaBounds.height = mediaAreaHeight;
|
||||
mediaBounds.top = navBarHeight;
|
||||
mediaBounds.top = navBarHeight + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
break;
|
||||
@ -710,7 +414,7 @@ class CustomLayout extends Component {
|
||||
} else {
|
||||
mediaBounds.width = mediaAreaWidth;
|
||||
mediaBounds.height = mediaAreaHeight;
|
||||
mediaBounds.top = DEFAULT_VALUES.navBarHeight + this.bannerAreaHeight();
|
||||
mediaBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? sidebarSize : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
}
|
||||
@ -718,34 +422,38 @@ class CustomLayout extends Component {
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
calculatesLayout() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input, isRTL } = layoutContextState;
|
||||
const { cameraDock } = input;
|
||||
const { position: cameraPosition } = cameraDock;
|
||||
const calculatesLayout = () => {
|
||||
const {
|
||||
calculatesNavbarBounds,
|
||||
calculatesActionbarBounds,
|
||||
calculatesSidebarNavWidth,
|
||||
calculatesSidebarNavHeight,
|
||||
calculatesSidebarNavBounds,
|
||||
calculatesSidebarContentWidth,
|
||||
calculatesSidebarContentBounds,
|
||||
calculatesMediaAreaBounds,
|
||||
isTablet,
|
||||
} = props;
|
||||
const { position: cameraPosition } = cameraDockInput;
|
||||
const { camerasMargin, captionsMargin } = DEFAULT_VALUES;
|
||||
|
||||
const sidebarNavWidth = this.calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = this.calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = this.calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = this
|
||||
.calculatesSidebarNavBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const sidebarContentBounds = this
|
||||
.calculatesSidebarContentBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const mediaAreaBounds = this
|
||||
.calculatesMediaAreaBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const navbarBounds = this.calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = this.calculatesActionbarBounds(mediaAreaBounds);
|
||||
const cameraDockBounds = this.calculatesCameraDockBounds(
|
||||
const sidebarNavWidth = calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const cameraDockBounds = calculatesCameraDockBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width, mediaAreaBounds,
|
||||
);
|
||||
const dropZoneAreas = this
|
||||
.calculatesDropAreas(sidebarNavWidth.width, sidebarContentWidth.width, cameraDockBounds);
|
||||
const sidebarContentHeight = this.calculatesSidebarContentHeight(cameraDockBounds.height);
|
||||
const mediaBounds = this.calculatesMediaBounds(
|
||||
const dropZoneAreas = calculatesDropAreas(sidebarNavWidth.width, sidebarContentWidth.width, cameraDockBounds);
|
||||
const sidebarContentHeight = calculatesSidebarContentHeight(cameraDockBounds.height);
|
||||
const mediaBounds = calculatesMediaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width, cameraDockBounds,
|
||||
);
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
|
||||
let horizontalCameraDiff = 0;
|
||||
|
||||
@ -760,19 +468,20 @@ class CustomLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NAVBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.navBar.hasNavBar,
|
||||
display: navbarInput.hasNavBar,
|
||||
width: navbarBounds.width,
|
||||
height: navbarBounds.height,
|
||||
top: navbarBounds.top,
|
||||
left: navbarBounds.left,
|
||||
tabOrder: DEFAULT_VALUES.navBarTabOrder,
|
||||
zIndex: navbarBounds.zIndex,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ACTIONBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.actionBar.hasActionBar,
|
||||
display: actionbarInput.hasActionBar,
|
||||
width: actionbarBounds.width,
|
||||
height: actionbarBounds.height,
|
||||
innerHeight: actionbarBounds.innerHeight,
|
||||
@ -796,7 +505,7 @@ class CustomLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_NAVIGATION_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarNavigation.isOpen,
|
||||
display: sidebarNavigationInput.isOpen,
|
||||
minWidth: sidebarNavWidth.minWidth,
|
||||
width: sidebarNavWidth.width,
|
||||
maxWidth: sidebarNavWidth.maxWidth,
|
||||
@ -805,8 +514,7 @@ class CustomLayout extends Component {
|
||||
left: sidebarNavBounds.left,
|
||||
right: sidebarNavBounds.right,
|
||||
tabOrder: DEFAULT_VALUES.sidebarNavTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarNavBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -824,7 +532,7 @@ class CustomLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarContent.isOpen,
|
||||
display: sidebarContentInput.isOpen,
|
||||
minWidth: sidebarContentWidth.minWidth,
|
||||
width: sidebarContentWidth.width,
|
||||
maxWidth: sidebarContentWidth.maxWidth,
|
||||
@ -832,10 +540,9 @@ class CustomLayout extends Component {
|
||||
top: sidebarContentBounds.top,
|
||||
left: sidebarContentBounds.left,
|
||||
right: sidebarContentBounds.right,
|
||||
currentPanelType: input.currentPanelType,
|
||||
currentPanelType,
|
||||
tabOrder: DEFAULT_VALUES.sidebarContentTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarContentBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -861,8 +568,8 @@ class CustomLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAMERA_DOCK_OUTPUT,
|
||||
value: {
|
||||
display: input.cameraDock.numCameras > 0,
|
||||
position: input.cameraDock.position,
|
||||
display: cameraDockInput.numCameras > 0,
|
||||
position: cameraDockInput.position,
|
||||
minWidth: cameraDockBounds.minWidth,
|
||||
width: cameraDockBounds.width,
|
||||
maxWidth: cameraDockBounds.maxWidth,
|
||||
@ -874,16 +581,15 @@ class CustomLayout extends Component {
|
||||
left: cameraDockBounds.left,
|
||||
right: cameraDockBounds.right,
|
||||
tabOrder: 4,
|
||||
isDraggable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isDraggable: !isMobile && !isTablet,
|
||||
resizableEdge: {
|
||||
top: input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_BOTTOM
|
||||
|| input.cameraDock.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM,
|
||||
right: (!isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_LEFT)
|
||||
|| (isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_RIGHT),
|
||||
bottom: input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_TOP,
|
||||
left: (!isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_RIGHT)
|
||||
|| (isRTL && input.cameraDock.position === CAMERADOCK_POSITION.CONTENT_LEFT),
|
||||
top: cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_BOTTOM
|
||||
|| cameraDockInput.position === CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM,
|
||||
right: (!isRTL && cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_LEFT)
|
||||
|| (isRTL && cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_RIGHT),
|
||||
bottom: cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_TOP,
|
||||
left: (!isRTL && cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_RIGHT)
|
||||
|| (isRTL && cameraDockInput.position === CAMERADOCK_POSITION.CONTENT_LEFT),
|
||||
},
|
||||
zIndex: cameraDockBounds.zIndex,
|
||||
},
|
||||
@ -897,7 +603,7 @@ class CustomLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_OUTPUT,
|
||||
value: {
|
||||
display: input.presentation.isOpen,
|
||||
display: presentationInput.isOpen,
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
@ -931,11 +637,9 @@ class CustomLayout extends Component {
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LayoutContextFunc.withConsumer(CustomLayout);
|
||||
export default CustomLayout;
|
||||
|
@ -0,0 +1,302 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { layoutSelect, layoutSelectInput } from '/imports/ui/components/layout/context';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { LAYOUT_TYPE, DEVICE_TYPE } from '/imports/ui/components/layout/enums';
|
||||
|
||||
import CustomLayout from '/imports/ui/components/layout/layout-manager/customLayout';
|
||||
import SmartLayout from '/imports/ui/components/layout/layout-manager/smartLayout';
|
||||
import PresentationFocusLayout from '/imports/ui/components/layout/layout-manager/presentationFocusLayout';
|
||||
import VideoFocusLayout from '/imports/ui/components/layout/layout-manager/videoFocusLayout';
|
||||
|
||||
const propTypes = {
|
||||
layoutType: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const LayoutEngine = ({ layoutType }) => {
|
||||
const bannerBarInput = layoutSelectInput((i) => i.bannerBar);
|
||||
const notificationsBarInput = layoutSelectInput((i) => i.notificationsBar);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const presentationInput = layoutSelectInput((i) => i.presentation);
|
||||
const actionbarInput = layoutSelectInput((i) => i.actionBar);
|
||||
const sidebarNavigationInput = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
|
||||
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
const fontSize = layoutSelect((i) => i.fontSize);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
|
||||
const isMobile = deviceType === DEVICE_TYPE.MOBILE;
|
||||
const isTablet = deviceType === DEVICE_TYPE.TABLET;
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
const min = (value1, value2) => (value1 <= value2 ? value1 : value2);
|
||||
const max = (value1, value2) => (value1 >= value2 ? value1 : value2);
|
||||
|
||||
const bannerAreaHeight = () => {
|
||||
const { hasNotification } = notificationsBarInput;
|
||||
const { hasBanner } = bannerBarInput;
|
||||
const bannerHeight = hasBanner ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
const notificationHeight = hasNotification ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
|
||||
return bannerHeight + notificationHeight;
|
||||
};
|
||||
|
||||
const baseCameraDockBounds = (mediaAreaBounds, sidebarSize) => {
|
||||
const { isOpen } = presentationInput;
|
||||
|
||||
const cameraDockBounds = {};
|
||||
|
||||
if (cameraDockInput.numCameras === 0) {
|
||||
cameraDockBounds.width = 0;
|
||||
cameraDockBounds.height = 0;
|
||||
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
if (!isOpen) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
|
||||
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
}
|
||||
|
||||
if (fullscreen.group === 'webcams') {
|
||||
cameraDockBounds.width = windowWidth();
|
||||
cameraDockBounds.minWidth = windowWidth();
|
||||
cameraDockBounds.maxWidth = windowWidth();
|
||||
cameraDockBounds.height = windowHeight();
|
||||
cameraDockBounds.minHeight = windowHeight();
|
||||
cameraDockBounds.maxHeight = windowHeight();
|
||||
cameraDockBounds.top = 0;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.right = 0;
|
||||
cameraDockBounds.zIndex = 99;
|
||||
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
};
|
||||
|
||||
const calculatesNavbarBounds = (mediaAreaBounds) => {
|
||||
const { navBarHeight, navBarTop } = DEFAULT_VALUES;
|
||||
|
||||
return {
|
||||
width: mediaAreaBounds.width,
|
||||
height: navBarHeight,
|
||||
top: navBarTop + bannerAreaHeight(),
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesActionbarHeight = () => {
|
||||
const { actionBarHeight, actionBarPadding } = DEFAULT_VALUES;
|
||||
|
||||
const BASE_FONT_SIZE = 14; // 90% font size
|
||||
const height = ((actionBarHeight / BASE_FONT_SIZE) * fontSize);
|
||||
|
||||
return {
|
||||
height: height + (actionBarPadding * 2),
|
||||
innerHeight: height,
|
||||
padding: actionBarPadding,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesActionbarBounds = (mediaAreaBounds) => {
|
||||
const actionBarHeight = calculatesActionbarHeight();
|
||||
|
||||
return {
|
||||
display: actionbarInput.hasActionBar,
|
||||
width: mediaAreaBounds.width,
|
||||
height: actionBarHeight.height,
|
||||
innerHeight: actionBarHeight.innerHeight,
|
||||
padding: actionBarHeight.padding,
|
||||
top: windowHeight() - actionBarHeight.height,
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesSidebarNavWidth = () => {
|
||||
const {
|
||||
sidebarNavMinWidth,
|
||||
sidebarNavMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
|
||||
const { isOpen, width: sidebarNavWidth } = sidebarNavigationInput;
|
||||
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (isOpen) {
|
||||
if (isMobile) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (sidebarNavWidth === 0) {
|
||||
width = min(max((windowWidth() * 0.2), sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
} else {
|
||||
width = min(max(sidebarNavWidth, sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
}
|
||||
minWidth = sidebarNavMinWidth;
|
||||
maxWidth = sidebarNavMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesSidebarNavHeight = () => {
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
const { isOpen } = sidebarNavigationInput;
|
||||
|
||||
let sidebarNavHeight = 0;
|
||||
if (isOpen) {
|
||||
if (isMobile) {
|
||||
sidebarNavHeight = windowHeight() - navBarHeight - bannerAreaHeight();
|
||||
} else {
|
||||
sidebarNavHeight = windowHeight() - bannerAreaHeight();
|
||||
}
|
||||
}
|
||||
return sidebarNavHeight;
|
||||
};
|
||||
|
||||
const calculatesSidebarNavBounds = () => {
|
||||
const { sidebarNavTop, navBarHeight, sidebarNavLeft } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + bannerAreaHeight();
|
||||
|
||||
if (isMobile) {
|
||||
top = navBarHeight + bannerAreaHeight();
|
||||
}
|
||||
|
||||
return {
|
||||
top,
|
||||
left: !isRTL ? sidebarNavLeft : null,
|
||||
right: isRTL ? sidebarNavLeft : null,
|
||||
zIndex: isMobile ? 11 : 2,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesSidebarContentWidth = () => {
|
||||
const {
|
||||
sidebarContentMinWidth,
|
||||
sidebarContentMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
|
||||
const { isOpen, width: sidebarContentWidth } = sidebarContentInput;
|
||||
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
|
||||
if (isOpen) {
|
||||
if (isMobile) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (sidebarContentWidth === 0) {
|
||||
width = min(
|
||||
max((windowWidth() * 0.2), sidebarContentMinWidth), sidebarContentMaxWidth,
|
||||
);
|
||||
} else {
|
||||
width = min(max(sidebarContentWidth, sidebarContentMinWidth),
|
||||
sidebarContentMaxWidth);
|
||||
}
|
||||
minWidth = sidebarContentMinWidth;
|
||||
maxWidth = sidebarContentMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesSidebarContentBounds = (sidebarNavWidth) => {
|
||||
const { navBarHeight, sidebarNavTop } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + bannerAreaHeight();
|
||||
|
||||
if (isMobile) top = navBarHeight + bannerAreaHeight();
|
||||
|
||||
let left = isMobile ? 0 : sidebarNavWidth;
|
||||
let right = isMobile ? 0 : sidebarNavWidth;
|
||||
left = !isRTL ? left : null;
|
||||
right = isRTL ? right : null;
|
||||
|
||||
const zIndex = isMobile ? 11 : 1;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
zIndex,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesMediaAreaBounds = (sidebarNavWidth, sidebarContentWidth) => {
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
let left = 0;
|
||||
let width = 0;
|
||||
if (isMobile) {
|
||||
width = windowWidth();
|
||||
} else {
|
||||
left = !isRTL ? sidebarNavWidth + sidebarContentWidth : 0;
|
||||
width = windowWidth() - sidebarNavWidth - sidebarContentWidth;
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + bannerAreaHeight()),
|
||||
top: navBarHeight + bannerAreaHeight(),
|
||||
left,
|
||||
};
|
||||
};
|
||||
|
||||
const common = {
|
||||
bannerAreaHeight,
|
||||
baseCameraDockBounds,
|
||||
calculatesNavbarBounds,
|
||||
calculatesActionbarHeight,
|
||||
calculatesActionbarBounds,
|
||||
calculatesSidebarNavWidth,
|
||||
calculatesSidebarNavHeight,
|
||||
calculatesSidebarNavBounds,
|
||||
calculatesSidebarContentWidth,
|
||||
calculatesSidebarContentBounds,
|
||||
calculatesMediaAreaBounds,
|
||||
isMobile,
|
||||
isTablet,
|
||||
};
|
||||
|
||||
switch (layoutType) {
|
||||
case LAYOUT_TYPE.CUSTOM_LAYOUT:
|
||||
return <CustomLayout {...common} />;
|
||||
case LAYOUT_TYPE.SMART_LAYOUT:
|
||||
return <SmartLayout {...common} />;
|
||||
case LAYOUT_TYPE.PRESENTATION_FOCUS:
|
||||
return <PresentationFocusLayout {...common} />;
|
||||
case LAYOUT_TYPE.VIDEO_FOCUS:
|
||||
return <VideoFocusLayout {...common} />;
|
||||
default:
|
||||
return <CustomLayout {...common} />;
|
||||
}
|
||||
};
|
||||
|
||||
LayoutEngine.propTypes = propTypes;
|
||||
|
||||
export default LayoutEngine;
|
@ -1,26 +1,47 @@
|
||||
import React, { Component } from 'react';
|
||||
import { throttle, defaultsDeep } from 'lodash';
|
||||
import { LayoutContextFunc } from '/imports/ui/components/layout/context';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { layoutDispatch, layoutSelect, layoutSelectInput } from '/imports/ui/components/layout/context';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { INITIAL_INPUT_STATE } from '/imports/ui/components/layout/initState';
|
||||
import { DEVICE_TYPE, ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
import { ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
const min = (value1, value2) => (value1 <= value2 ? value1 : value2);
|
||||
const max = (value1, value2) => (value1 >= value2 ? value1 : value2);
|
||||
|
||||
class PresentationFocusLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const PresentationFocusLayout = (props) => {
|
||||
const { bannerAreaHeight, isMobile } = props;
|
||||
|
||||
this.throttledCalculatesLayout = throttle(() => this.calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
function usePrevious(value) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.init();
|
||||
const { layoutContextDispatch } = this.props;
|
||||
const input = layoutSelect((i) => i.input);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const fontSize = layoutSelect((i) => i.fontSize);
|
||||
const currentPanelType = layoutSelect((i) => i.currentPanelType);
|
||||
|
||||
const presentationInput = layoutSelectInput((i) => i.presentation);
|
||||
const sidebarNavigationInput = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const actionbarInput = layoutSelectInput((i) => i.actionBar);
|
||||
const navbarInput = layoutSelectInput((i) => i.navBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
|
||||
const throttledCalculatesLayout = _.throttle(() => calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_BROWSER_SIZE,
|
||||
@ -30,73 +51,54 @@ class PresentationFocusLayout extends Component {
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
return layoutContextState.input !== nextProps.layoutContextState.input
|
||||
|| layoutContextState.deviceType !== nextProps.layoutContextState.deviceType
|
||||
|| layoutContextState.isRTL !== nextProps.layoutContextState.isRTL
|
||||
|| layoutContextState.fontSize !== nextProps.layoutContextState.fontSize
|
||||
|| layoutContextState.fullscreen !== nextProps.layoutContextState.fullscreen;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType } = layoutContextState;
|
||||
if (prevProps.layoutContextState.deviceType !== deviceType) {
|
||||
this.init();
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
// not all options is supported in all devices
|
||||
init();
|
||||
} else {
|
||||
this.throttledCalculatesLayout();
|
||||
throttledCalculatesLayout();
|
||||
}
|
||||
}
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
bannerAreaHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input } = layoutContextState;
|
||||
const { bannerBar, notificationsBar } = input;
|
||||
|
||||
const bannerHeight = bannerBar.hasBanner ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
const notificationHeight = notificationsBar.hasNotification ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
|
||||
return bannerHeight + notificationHeight;
|
||||
}
|
||||
|
||||
init() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
const init = () => {
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
value: _.defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: input.sidebarNavigation.sidebarNavPanel,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: input.sidebarContent.sidebarContentPanel,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = input.sidebarContent;
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep({
|
||||
value: _.defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: true,
|
||||
},
|
||||
@ -108,174 +110,23 @@ class PresentationFocusLayout extends Component {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
});
|
||||
}
|
||||
this.throttledCalculatesLayout();
|
||||
}
|
||||
throttledCalculatesLayout();
|
||||
};
|
||||
|
||||
reset() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
calculatesNavbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { isRTL } = layoutContextState;
|
||||
|
||||
return {
|
||||
width: mediaAreaBounds.width,
|
||||
height: DEFAULT_VALUES.navBarHeight,
|
||||
top: DEFAULT_VALUES.navBarTop + this.bannerAreaHeight(),
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { fontSize } = layoutContextState;
|
||||
|
||||
const BASE_FONT_SIZE = 14; // 90% font size
|
||||
const BASE_HEIGHT = DEFAULT_VALUES.actionBarHeight;
|
||||
const PADDING = DEFAULT_VALUES.actionBarPadding;
|
||||
|
||||
const actionBarHeight = ((BASE_HEIGHT / BASE_FONT_SIZE) * fontSize);
|
||||
|
||||
return {
|
||||
height: actionBarHeight + (PADDING * 2),
|
||||
innerHeight: actionBarHeight,
|
||||
padding: PADDING,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input, isRTL } = layoutContextState;
|
||||
|
||||
const actionBarHeight = this.calculatesActionbarHeight();
|
||||
|
||||
return {
|
||||
display: input.actionBar.hasActionBar,
|
||||
width: mediaAreaBounds.width,
|
||||
height: actionBarHeight.height,
|
||||
innerHeight: actionBarHeight.innerHeight,
|
||||
padding: actionBarHeight.padding,
|
||||
top: windowHeight() - actionBarHeight.height,
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarNavMinWidth,
|
||||
sidebarNavMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarNavigation.width === 0) {
|
||||
width = min(max((windowWidth() * 0.2), sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
} else {
|
||||
width = min(max(input.sidebarNavigation.width, sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
}
|
||||
minWidth = sidebarNavMinWidth;
|
||||
maxWidth = sidebarNavMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
let sidebarNavHeight = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
sidebarNavHeight = windowHeight() - navBarHeight - this.bannerAreaHeight();
|
||||
} else {
|
||||
sidebarNavHeight = windowHeight() - this.bannerAreaHeight();
|
||||
}
|
||||
}
|
||||
return sidebarNavHeight;
|
||||
}
|
||||
|
||||
calculatesSidebarNavBounds() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { sidebarNavTop, navBarHeight, sidebarNavLeft } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) top = navBarHeight + this.bannerAreaHeight();
|
||||
|
||||
return {
|
||||
top,
|
||||
left: !isRTL ? sidebarNavLeft : null,
|
||||
right: isRTL ? sidebarNavLeft : null,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 11 : 2,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarContentMinWidth,
|
||||
sidebarContentMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarContent.width === 0) {
|
||||
width = min(
|
||||
max((windowWidth() * 0.2), sidebarContentMinWidth), sidebarContentMaxWidth,
|
||||
);
|
||||
} else {
|
||||
width = min(max(input.sidebarContent.width, sidebarContentMinWidth),
|
||||
sidebarContentMaxWidth);
|
||||
}
|
||||
minWidth = sidebarContentMinWidth;
|
||||
maxWidth = sidebarContentMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const calculatesSidebarContentHeight = () => {
|
||||
const { isOpen } = presentationInput;
|
||||
const {
|
||||
navBarHeight,
|
||||
sidebarContentMinHeight,
|
||||
@ -283,22 +134,22 @@ class PresentationFocusLayout extends Component {
|
||||
let height = 0;
|
||||
let minHeight = 0;
|
||||
let maxHeight = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
height = windowHeight() - navBarHeight - this.bannerAreaHeight();
|
||||
if (sidebarContentInput.isOpen) {
|
||||
if (isMobile) {
|
||||
height = windowHeight() - navBarHeight - bannerAreaHeight();
|
||||
minHeight = height;
|
||||
maxHeight = height;
|
||||
} else if (input.cameraDock.numCameras > 0) {
|
||||
if (input.sidebarContent.height === 0) {
|
||||
height = (windowHeight() * 0.75) - this.bannerAreaHeight();
|
||||
} else if (cameraDockInput.numCameras > 0 && isOpen) {
|
||||
if (sidebarContentInput.height === 0) {
|
||||
height = (windowHeight() * 0.75) - bannerAreaHeight();
|
||||
} else {
|
||||
height = min(max(input.sidebarContent.height, sidebarContentMinHeight),
|
||||
height = min(max(sidebarContentInput.height, sidebarContentMinHeight),
|
||||
windowHeight());
|
||||
}
|
||||
minHeight = windowHeight() * 0.25 - this.bannerAreaHeight();
|
||||
maxHeight = windowHeight() * 0.75 - this.bannerAreaHeight();
|
||||
minHeight = windowHeight() * 0.25 - bannerAreaHeight();
|
||||
maxHeight = windowHeight() * 0.75 - bannerAreaHeight();
|
||||
} else {
|
||||
height = windowHeight() - this.bannerAreaHeight();
|
||||
height = windowHeight() - bannerAreaHeight();
|
||||
minHeight = height;
|
||||
maxHeight = height;
|
||||
}
|
||||
@ -308,130 +159,68 @@ class PresentationFocusLayout extends Component {
|
||||
minHeight,
|
||||
maxHeight,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
calculatesSidebarContentBounds(sidebarNavWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { navBarHeight, sidebarNavTop } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) top = navBarHeight + this.bannerAreaHeight();
|
||||
|
||||
let left = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
let right = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
left = !isRTL ? left : null;
|
||||
right = isRTL ? right : null;
|
||||
|
||||
const zIndex = deviceType === DEVICE_TYPE.MOBILE ? 11 : 1;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
zIndex,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesMediaAreaBounds(sidebarNavWidth, sidebarContentWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
let left = 0;
|
||||
let width = 0;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
left = 0;
|
||||
width = windowWidth();
|
||||
} else {
|
||||
left = !isRTL ? sidebarNavWidth + sidebarContentWidth : 0;
|
||||
width = windowWidth() - sidebarNavWidth - sidebarContentWidth;
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + this.bannerAreaHeight()),
|
||||
top: navBarHeight + this.bannerAreaHeight(),
|
||||
left,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesCameraDockBounds(
|
||||
const calculatesCameraDockBounds = (
|
||||
mediaBounds,
|
||||
mediaAreaBounds,
|
||||
sidebarNavWidth,
|
||||
sidebarContentWidth,
|
||||
sidebarContentHeight,
|
||||
) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
deviceType, input, fullscreen, isRTL,
|
||||
} = layoutContextState;
|
||||
) => {
|
||||
const { baseCameraDockBounds } = props;
|
||||
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
|
||||
|
||||
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
|
||||
// do not proceed if using values from LayoutEngine
|
||||
if (Object.keys(baseBounds).length > 0) {
|
||||
return baseBounds;
|
||||
}
|
||||
|
||||
const { cameraDockMinHeight } = DEFAULT_VALUES;
|
||||
|
||||
const cameraDockBounds = {};
|
||||
|
||||
if (input.cameraDock.numCameras > 0) {
|
||||
let cameraDockHeight = 0;
|
||||
let cameraDockHeight = 0;
|
||||
|
||||
if (fullscreen.group === 'webcams') {
|
||||
cameraDockBounds.width = windowWidth();
|
||||
cameraDockBounds.minWidth = windowWidth();
|
||||
cameraDockBounds.maxWidth = windowWidth();
|
||||
cameraDockBounds.height = windowHeight();
|
||||
cameraDockBounds.minHeight = windowHeight();
|
||||
cameraDockBounds.maxHeight = windowHeight();
|
||||
cameraDockBounds.top = 0;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.right = 0;
|
||||
cameraDockBounds.zIndex = 99;
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
cameraDockBounds.top = mediaAreaBounds.top + mediaBounds.height;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.right = 0;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.minHeight = DEFAULT_VALUES.cameraDockMinHeight;
|
||||
cameraDockBounds.height = mediaAreaBounds.height - mediaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height - mediaBounds.height;
|
||||
} else {
|
||||
if (input.cameraDock.height === 0) {
|
||||
cameraDockHeight = min(
|
||||
max((windowHeight() - sidebarContentHeight), DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(windowHeight() - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(input.cameraDock.height, DEFAULT_VALUES.cameraDockMinHeight),
|
||||
(windowHeight() - DEFAULT_VALUES.cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
cameraDockBounds.top = windowHeight() - cameraDockHeight;
|
||||
cameraDockBounds.left = !isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.minWidth = sidebarContentWidth;
|
||||
cameraDockBounds.width = sidebarContentWidth;
|
||||
cameraDockBounds.maxWidth = sidebarContentWidth;
|
||||
cameraDockBounds.minHeight = DEFAULT_VALUES.cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight;
|
||||
cameraDockBounds.maxHeight = windowHeight() - sidebarContentHeight;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
}
|
||||
if (isMobile) {
|
||||
cameraDockBounds.top = mediaAreaBounds.top + mediaBounds.height;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.right = 0;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.minHeight = cameraDockMinHeight;
|
||||
cameraDockBounds.height = mediaAreaBounds.height - mediaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height - mediaBounds.height;
|
||||
} else {
|
||||
cameraDockBounds.width = 0;
|
||||
cameraDockBounds.height = 0;
|
||||
if (cameraDockInput.height === 0) {
|
||||
cameraDockHeight = min(
|
||||
max((windowHeight() - sidebarContentHeight), cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
);
|
||||
} else {
|
||||
cameraDockHeight = min(
|
||||
max(cameraDockInput.height, cameraDockMinHeight),
|
||||
(windowHeight() - cameraDockMinHeight),
|
||||
);
|
||||
}
|
||||
cameraDockBounds.top = windowHeight() - cameraDockHeight;
|
||||
cameraDockBounds.left = !isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarNavWidth : 0;
|
||||
cameraDockBounds.minWidth = sidebarContentWidth;
|
||||
cameraDockBounds.width = sidebarContentWidth;
|
||||
cameraDockBounds.maxWidth = sidebarContentWidth;
|
||||
cameraDockBounds.minHeight = cameraDockMinHeight;
|
||||
cameraDockBounds.height = cameraDockHeight;
|
||||
cameraDockBounds.maxHeight = windowHeight() - sidebarContentHeight;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
}
|
||||
return cameraDockBounds;
|
||||
}
|
||||
};
|
||||
|
||||
calculatesMediaBounds(mediaAreaBounds, sidebarSize) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
deviceType, input, fullscreen, isRTL,
|
||||
} = layoutContextState;
|
||||
const calculatesMediaBounds = (mediaAreaBounds, sidebarSize) => {
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
|
||||
@ -445,43 +234,48 @@ class PresentationFocusLayout extends Component {
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE && input.cameraDock.numCameras > 0) {
|
||||
if (isMobile && cameraDockInput.numCameras > 0) {
|
||||
mediaBounds.height = mediaAreaBounds.height * 0.7;
|
||||
} else {
|
||||
mediaBounds.height = mediaAreaBounds.height;
|
||||
}
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.top = DEFAULT_VALUES.navBarHeight + this.bannerAreaHeight();
|
||||
mediaBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? mediaAreaBounds.left : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
mediaBounds.zIndex = 1;
|
||||
|
||||
return mediaBounds;
|
||||
}
|
||||
};
|
||||
|
||||
calculatesLayout() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input, isRTL } = layoutContextState;
|
||||
const calculatesLayout = () => {
|
||||
const {
|
||||
calculatesNavbarBounds,
|
||||
calculatesActionbarBounds,
|
||||
calculatesSidebarNavWidth,
|
||||
calculatesSidebarNavHeight,
|
||||
calculatesSidebarNavBounds,
|
||||
calculatesSidebarContentWidth,
|
||||
calculatesSidebarContentBounds,
|
||||
calculatesMediaAreaBounds,
|
||||
isTablet,
|
||||
} = props;
|
||||
const { captionsMargin } = DEFAULT_VALUES;
|
||||
|
||||
const sidebarNavWidth = this.calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = this.calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = this.calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = this.calculatesSidebarNavBounds(
|
||||
const sidebarNavWidth = calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
);
|
||||
const sidebarContentBounds = this.calculatesSidebarContentBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
);
|
||||
const mediaAreaBounds = this.calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
);
|
||||
const navbarBounds = this.calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = this.calculatesActionbarBounds(mediaAreaBounds);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const sidebarSize = sidebarContentWidth.width + sidebarNavWidth.width;
|
||||
const mediaBounds = this.calculatesMediaBounds(mediaAreaBounds, sidebarSize);
|
||||
const sidebarContentHeight = this.calculatesSidebarContentHeight();
|
||||
const cameraDockBounds = this.calculatesCameraDockBounds(
|
||||
const mediaBounds = calculatesMediaBounds(mediaAreaBounds, sidebarSize);
|
||||
const sidebarContentHeight = calculatesSidebarContentHeight();
|
||||
const cameraDockBounds = calculatesCameraDockBounds(
|
||||
mediaBounds,
|
||||
mediaAreaBounds,
|
||||
sidebarNavWidth.width,
|
||||
@ -492,7 +286,7 @@ class PresentationFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NAVBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.navBar.hasNavBar,
|
||||
display: navbarInput.hasNavBar,
|
||||
width: navbarBounds.width,
|
||||
height: navbarBounds.height,
|
||||
top: navbarBounds.top,
|
||||
@ -505,7 +299,7 @@ class PresentationFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ACTIONBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.actionBar.hasActionBar,
|
||||
display: actionbarInput.hasActionBar,
|
||||
width: actionbarBounds.width,
|
||||
height: actionbarBounds.height,
|
||||
innerHeight: actionbarBounds.innerHeight,
|
||||
@ -529,7 +323,7 @@ class PresentationFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_NAVIGATION_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarNavigation.isOpen,
|
||||
display: sidebarNavigationInput.isOpen,
|
||||
minWidth: sidebarNavWidth.minWidth,
|
||||
width: sidebarNavWidth.width,
|
||||
maxWidth: sidebarNavWidth.maxWidth,
|
||||
@ -538,8 +332,7 @@ class PresentationFocusLayout extends Component {
|
||||
left: sidebarNavBounds.left,
|
||||
right: sidebarNavBounds.right,
|
||||
tabOrder: DEFAULT_VALUES.sidebarNavTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarNavBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -557,7 +350,7 @@ class PresentationFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarContent.isOpen,
|
||||
display: sidebarContentInput.isOpen,
|
||||
minWidth: sidebarContentWidth.minWidth,
|
||||
width: sidebarContentWidth.width,
|
||||
maxWidth: sidebarContentWidth.maxWidth,
|
||||
@ -567,10 +360,9 @@ class PresentationFocusLayout extends Component {
|
||||
top: sidebarContentBounds.top,
|
||||
left: sidebarContentBounds.left,
|
||||
right: sidebarContentBounds.right,
|
||||
currentPanelType: input.currentPanelType,
|
||||
currentPanelType,
|
||||
tabOrder: DEFAULT_VALUES.sidebarContentTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarContentBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -580,7 +372,7 @@ class PresentationFocusLayout extends Component {
|
||||
value: {
|
||||
top: false,
|
||||
right: !isRTL,
|
||||
bottom: input.cameraDock.numCameras > 0,
|
||||
bottom: cameraDockInput.numCameras > 0,
|
||||
left: isRTL,
|
||||
},
|
||||
});
|
||||
@ -596,7 +388,7 @@ class PresentationFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAMERA_DOCK_OUTPUT,
|
||||
value: {
|
||||
display: input.cameraDock.numCameras > 0,
|
||||
display: cameraDockInput.numCameras > 0,
|
||||
minWidth: cameraDockBounds.minWidth,
|
||||
width: cameraDockBounds.width,
|
||||
maxWidth: cameraDockBounds.maxWidth,
|
||||
@ -621,7 +413,7 @@ class PresentationFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_OUTPUT,
|
||||
value: {
|
||||
display: input.presentation.isOpen,
|
||||
display: presentationInput.isOpen,
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
@ -655,13 +447,9 @@ class PresentationFocusLayout extends Component {
|
||||
right: mediaBounds.right,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LayoutContextFunc.withConsumer(PresentationFocusLayout);
|
||||
export default PresentationFocusLayout;
|
||||
|
@ -1,26 +1,45 @@
|
||||
import React, { Component } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { LayoutContextFunc } from '/imports/ui/components/layout/context';
|
||||
import { layoutDispatch, layoutSelect, layoutSelectInput } from '/imports/ui/components/layout/context';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { INITIAL_INPUT_STATE } from '/imports/ui/components/layout/initState';
|
||||
import { DEVICE_TYPE, ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
import { ACTIONS, PANELS, CAMERADOCK_POSITION } from '/imports/ui/components/layout/enums';
|
||||
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
const min = (value1, value2) => (value1 <= value2 ? value1 : value2);
|
||||
const max = (value1, value2) => (value1 >= value2 ? value1 : value2);
|
||||
|
||||
class SmartLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const SmartLayout = (props) => {
|
||||
const { bannerAreaHeight, isMobile } = props;
|
||||
|
||||
this.throttledCalculatesLayout = _.throttle(() => this.calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
function usePrevious(value) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.init();
|
||||
const { layoutContextDispatch } = this.props;
|
||||
const input = layoutSelect((i) => i.input);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const fontSize = layoutSelect((i) => i.fontSize);
|
||||
const currentPanelType = layoutSelect((i) => i.currentPanelType);
|
||||
|
||||
const presentationInput = layoutSelectInput((i) => i.presentation);
|
||||
const sidebarNavigationInput = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const actionbarInput = layoutSelectInput((i) => i.actionBar);
|
||||
const navbarInput = layoutSelectInput((i) => i.navBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
|
||||
const throttledCalculatesLayout = _.throttle(() => calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_BROWSER_SIZE,
|
||||
@ -30,69 +49,50 @@ class SmartLayout extends Component {
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
return layoutContextState.input !== nextProps.layoutContextState.input
|
||||
|| layoutContextState.deviceType !== nextProps.layoutContextState.deviceType
|
||||
|| layoutContextState.isRTL !== nextProps.layoutContextState.isRTL
|
||||
|| layoutContextState.fontSize !== nextProps.layoutContextState.fontSize
|
||||
|| layoutContextState.fullscreen !== nextProps.layoutContextState.fullscreen;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType } = layoutContextState;
|
||||
if (prevProps.layoutContextState.deviceType !== deviceType) {
|
||||
this.init();
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
// not all options is supported in all devices
|
||||
init();
|
||||
} else {
|
||||
this.throttledCalculatesLayout();
|
||||
throttledCalculatesLayout();
|
||||
}
|
||||
}
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
bannerAreaHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input } = layoutContextState;
|
||||
const { bannerBar, notificationsBar } = input;
|
||||
|
||||
const bannerHeight = bannerBar.hasBanner ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
const notificationHeight = notificationsBar.hasNotification ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
|
||||
return bannerHeight + notificationHeight;
|
||||
}
|
||||
|
||||
init() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
const init = () => {
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: _.defaultsDeep({
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: input.sidebarNavigation.sidebarNavPanel,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: input.sidebarContent.sidebarContentPanel,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = input.sidebarContent;
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
@ -108,304 +108,89 @@ class SmartLayout extends Component {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
}, INITIAL_INPUT_STATE),
|
||||
});
|
||||
}
|
||||
this.throttledCalculatesLayout();
|
||||
}
|
||||
throttledCalculatesLayout();
|
||||
};
|
||||
|
||||
reset() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
calculatesNavbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { isRTL } = layoutContextState;
|
||||
|
||||
return {
|
||||
width: mediaAreaBounds.width,
|
||||
height: DEFAULT_VALUES.navBarHeight,
|
||||
top: DEFAULT_VALUES.navBarTop + this.bannerAreaHeight(),
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { fontSize } = layoutContextState;
|
||||
|
||||
const BASE_FONT_SIZE = 14; // 90% font size
|
||||
const BASE_HEIGHT = DEFAULT_VALUES.actionBarHeight;
|
||||
const PADDING = DEFAULT_VALUES.actionBarPadding;
|
||||
|
||||
const actionBarHeight = ((BASE_HEIGHT / BASE_FONT_SIZE) * fontSize);
|
||||
|
||||
return {
|
||||
height: actionBarHeight + (PADDING * 2),
|
||||
innerHeight: actionBarHeight,
|
||||
padding: PADDING,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input, isRTL } = layoutContextState;
|
||||
|
||||
const actionBarHeight = this.calculatesActionbarHeight();
|
||||
|
||||
return {
|
||||
display: input.actionBar.hasActionBar,
|
||||
width: mediaAreaBounds.width,
|
||||
height: actionBarHeight.height,
|
||||
innerHeight: actionBarHeight.innerHeight,
|
||||
padding: actionBarHeight.padding,
|
||||
top: windowHeight() - actionBarHeight.height,
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarNavMinWidth,
|
||||
sidebarNavMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarNavigation.width === 0) {
|
||||
width = min(max((windowWidth() * 0.2), sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
} else {
|
||||
width = min(max(input.sidebarNavigation.width, sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
}
|
||||
minWidth = sidebarNavMinWidth;
|
||||
maxWidth = sidebarNavMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
let sidebarNavHeight = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
sidebarNavHeight = windowHeight() - DEFAULT_VALUES.navBarHeight;
|
||||
} else {
|
||||
sidebarNavHeight = windowHeight();
|
||||
}
|
||||
sidebarNavHeight -= this.bannerAreaHeight();
|
||||
}
|
||||
return sidebarNavHeight;
|
||||
}
|
||||
|
||||
calculatesSidebarNavBounds() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { sidebarNavTop, navBarHeight, sidebarNavLeft } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) top = navBarHeight + this.bannerAreaHeight();
|
||||
|
||||
return {
|
||||
top,
|
||||
left: !isRTL ? sidebarNavLeft : null,
|
||||
right: isRTL ? sidebarNavLeft : null,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 11 : 2,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarContentMinWidth,
|
||||
sidebarContentMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarContent.width === 0) {
|
||||
width = min(
|
||||
max((windowWidth() * 0.2), sidebarContentMinWidth), sidebarContentMaxWidth,
|
||||
);
|
||||
} else {
|
||||
width = min(max(input.sidebarContent.width, sidebarContentMinWidth),
|
||||
sidebarContentMaxWidth);
|
||||
}
|
||||
minWidth = sidebarContentMinWidth;
|
||||
maxWidth = sidebarContentMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const calculatesSidebarContentHeight = () => {
|
||||
let sidebarContentHeight = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
if (sidebarContentInput.isOpen) {
|
||||
if (isMobile) {
|
||||
sidebarContentHeight = windowHeight() - DEFAULT_VALUES.navBarHeight;
|
||||
} else {
|
||||
sidebarContentHeight = windowHeight();
|
||||
}
|
||||
sidebarContentHeight -= this.bannerAreaHeight();
|
||||
sidebarContentHeight -= bannerAreaHeight();
|
||||
}
|
||||
return sidebarContentHeight;
|
||||
}
|
||||
};
|
||||
|
||||
calculatesSidebarContentBounds(sidebarNavWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { sidebarNavTop, navBarHeight } = DEFAULT_VALUES;
|
||||
const calculatesCameraDockBounds = (mediaAreaBounds, mediaBounds, sidebarSize) => {
|
||||
const { baseCameraDockBounds } = props;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) top = navBarHeight + this.bannerAreaHeight();
|
||||
|
||||
let left = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
left = !isRTL ? left : null;
|
||||
|
||||
let right = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
right = isRTL ? right : null;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 11 : 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesMediaAreaBounds(sidebarNavWidth, sidebarContentWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
let left = 0;
|
||||
let width = 0;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
left = 0;
|
||||
width = windowWidth();
|
||||
} else {
|
||||
left = !isRTL ? sidebarNavWidth + sidebarContentWidth : 0;
|
||||
width = windowWidth() - sidebarNavWidth - sidebarContentWidth;
|
||||
// do not proceed if using values from LayoutEngine
|
||||
if (Object.keys(baseBounds).length > 0) {
|
||||
baseBounds.isCameraHorizontal = false;
|
||||
return baseBounds;
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + this.bannerAreaHeight()),
|
||||
top: navBarHeight + this.bannerAreaHeight(),
|
||||
left,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesCameraDockBounds(mediaAreaBounds, mediaBounds, sidebarSize) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
input, fullscreen, isRTL, deviceType,
|
||||
} = layoutContextState;
|
||||
const { presentation } = input;
|
||||
const { isOpen } = presentation;
|
||||
const { camerasMargin, presentationToolbarMinWidth } = DEFAULT_VALUES;
|
||||
|
||||
const cameraDockBounds = {};
|
||||
|
||||
cameraDockBounds.isCameraHorizontal = false;
|
||||
const mediaBoundsWidth = (mediaBounds.width > presentationToolbarMinWidth
|
||||
&& deviceType !== DEVICE_TYPE.MOBILE)
|
||||
|
||||
const mediaBoundsWidth = (mediaBounds.width > presentationToolbarMinWidth && !isMobile)
|
||||
? mediaBounds.width
|
||||
: presentationToolbarMinWidth;
|
||||
|
||||
if (input.cameraDock.numCameras > 0) {
|
||||
cameraDockBounds.top = mediaAreaBounds.top;
|
||||
cameraDockBounds.left = mediaAreaBounds.left;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
cameraDockBounds.top = mediaAreaBounds.top;
|
||||
cameraDockBounds.left = mediaAreaBounds.left;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
|
||||
if (!isOpen) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
} else if (mediaBounds.width < mediaAreaBounds.width) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width - mediaBoundsWidth;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.left += camerasMargin;
|
||||
cameraDockBounds.width -= (camerasMargin * 2);
|
||||
cameraDockBounds.isCameraHorizontal = true;
|
||||
} else {
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height - mediaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
cameraDockBounds.top += camerasMargin;
|
||||
cameraDockBounds.height -= (camerasMargin * 2);
|
||||
}
|
||||
|
||||
cameraDockBounds.minWidth = cameraDockBounds.width;
|
||||
cameraDockBounds.minHeight = cameraDockBounds.height;
|
||||
|
||||
if (fullscreen.group === 'webcams') {
|
||||
cameraDockBounds.width = windowWidth();
|
||||
cameraDockBounds.minWidth = windowWidth();
|
||||
cameraDockBounds.maxWidth = windowWidth();
|
||||
cameraDockBounds.height = windowHeight();
|
||||
cameraDockBounds.minHeight = windowHeight();
|
||||
cameraDockBounds.maxHeight = windowHeight();
|
||||
cameraDockBounds.top = 0;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.right = 0;
|
||||
cameraDockBounds.zIndex = 99;
|
||||
}
|
||||
if (mediaBounds.width < mediaAreaBounds.width) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width - mediaBoundsWidth;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.left += camerasMargin;
|
||||
cameraDockBounds.width -= (camerasMargin * 2);
|
||||
cameraDockBounds.isCameraHorizontal = true;
|
||||
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_LEFT;
|
||||
// button size in vertical position
|
||||
cameraDockBounds.height -= 20;
|
||||
} else {
|
||||
cameraDockBounds.width = 0;
|
||||
cameraDockBounds.height = 0;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height - mediaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
cameraDockBounds.top += camerasMargin;
|
||||
cameraDockBounds.height -= (camerasMargin * 2);
|
||||
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_TOP;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
}
|
||||
cameraDockBounds.minWidth = cameraDockBounds.width;
|
||||
cameraDockBounds.minHeight = cameraDockBounds.height;
|
||||
|
||||
calculatesSlideSize(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input } = layoutContextState;
|
||||
const { presentation } = input;
|
||||
const { currentSlide } = presentation;
|
||||
return cameraDockBounds;
|
||||
};
|
||||
|
||||
const calculatesSlideSize = (mediaAreaBounds) => {
|
||||
const { currentSlide } = presentationInput;
|
||||
|
||||
if (currentSlide.size.width === 0 && currentSlide.size.height === 0) {
|
||||
return {
|
||||
@ -429,15 +214,10 @@ class SmartLayout extends Component {
|
||||
width: slideWidth,
|
||||
height: slideHeight,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
calculatesMediaBounds(mediaAreaBounds, slideSize, sidebarSize) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
input, fullscreen, isRTL, deviceType,
|
||||
} = layoutContextState;
|
||||
const { presentation } = input;
|
||||
const { isOpen } = presentation;
|
||||
const calculatesMediaBounds = (mediaAreaBounds, slideSize, sidebarSize) => {
|
||||
const { isOpen } = presentationInput;
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
|
||||
@ -461,9 +241,9 @@ class SmartLayout extends Component {
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
if (input.cameraDock.numCameras > 0 && !input.cameraDock.isDragging) {
|
||||
if (cameraDockInput.numCameras > 0 && !cameraDockInput.isDragging) {
|
||||
if (slideSize.width !== 0 && slideSize.height !== 0) {
|
||||
if (slideSize.width < mediaAreaBounds.width && deviceType !== DEVICE_TYPE.MOBILE) {
|
||||
if (slideSize.width < mediaAreaBounds.width && !isMobile) {
|
||||
if (slideSize.width < (mediaAreaBounds.width * 0.8)) {
|
||||
mediaBounds.width = slideSize.width;
|
||||
} else {
|
||||
@ -508,30 +288,35 @@ class SmartLayout extends Component {
|
||||
mediaBounds.zIndex = 1;
|
||||
|
||||
return mediaBounds;
|
||||
}
|
||||
};
|
||||
|
||||
calculatesLayout() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input, isRTL } = layoutContextState;
|
||||
const calculatesLayout = () => {
|
||||
const {
|
||||
calculatesNavbarBounds,
|
||||
calculatesActionbarBounds,
|
||||
calculatesSidebarNavWidth,
|
||||
calculatesSidebarNavHeight,
|
||||
calculatesSidebarNavBounds,
|
||||
calculatesSidebarContentWidth,
|
||||
calculatesSidebarContentBounds,
|
||||
calculatesMediaAreaBounds,
|
||||
isTablet,
|
||||
} = props;
|
||||
const { camerasMargin, captionsMargin } = DEFAULT_VALUES;
|
||||
|
||||
const sidebarNavWidth = this.calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = this.calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = this.calculatesSidebarContentWidth();
|
||||
const sidebarContentHeight = this.calculatesSidebarContentHeight();
|
||||
const sidebarNavBounds = this
|
||||
.calculatesSidebarNavBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const sidebarContentBounds = this
|
||||
.calculatesSidebarContentBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const mediaAreaBounds = this
|
||||
.calculatesMediaAreaBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const navbarBounds = this.calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = this.calculatesActionbarBounds(mediaAreaBounds);
|
||||
const slideSize = this.calculatesSlideSize(mediaAreaBounds);
|
||||
const sidebarNavWidth = calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = calculatesSidebarContentWidth();
|
||||
const sidebarContentHeight = calculatesSidebarContentHeight();
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(sidebarNavWidth.width, sidebarContentWidth.width);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const slideSize = calculatesSlideSize(mediaAreaBounds);
|
||||
const sidebarSize = sidebarContentWidth.width + sidebarNavWidth.width;
|
||||
const mediaBounds = this.calculatesMediaBounds(mediaAreaBounds, slideSize, sidebarSize);
|
||||
const cameraDockBounds = this
|
||||
.calculatesCameraDockBounds(mediaAreaBounds, mediaBounds, sidebarSize);
|
||||
const mediaBounds = calculatesMediaBounds(mediaAreaBounds, slideSize, sidebarSize);
|
||||
const cameraDockBounds = calculatesCameraDockBounds(mediaAreaBounds, mediaBounds, sidebarSize);
|
||||
const horizontalCameraDiff = cameraDockBounds.isCameraHorizontal
|
||||
? cameraDockBounds.width + (camerasMargin * 2)
|
||||
: 0;
|
||||
@ -539,7 +324,7 @@ class SmartLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NAVBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.navBar.hasNavBar,
|
||||
display: navbarInput.hasNavBar,
|
||||
width: navbarBounds.width,
|
||||
height: navbarBounds.height,
|
||||
top: navbarBounds.top,
|
||||
@ -552,7 +337,7 @@ class SmartLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ACTIONBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.actionBar.hasActionBar,
|
||||
display: actionbarInput.hasActionBar,
|
||||
width: actionbarBounds.width,
|
||||
height: actionbarBounds.height,
|
||||
innerHeight: actionbarBounds.innerHeight,
|
||||
@ -576,7 +361,7 @@ class SmartLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_NAVIGATION_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarNavigation.isOpen,
|
||||
display: sidebarNavigationInput.isOpen,
|
||||
minWidth: sidebarNavWidth.minWidth,
|
||||
width: sidebarNavWidth.width,
|
||||
maxWidth: sidebarNavWidth.maxWidth,
|
||||
@ -585,8 +370,7 @@ class SmartLayout extends Component {
|
||||
left: sidebarNavBounds.left,
|
||||
right: sidebarNavBounds.right,
|
||||
tabOrder: DEFAULT_VALUES.sidebarNavTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarNavBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -604,7 +388,7 @@ class SmartLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarContent.isOpen,
|
||||
display: sidebarContentInput.isOpen,
|
||||
minWidth: sidebarContentWidth.minWidth,
|
||||
width: sidebarContentWidth.width,
|
||||
maxWidth: sidebarContentWidth.maxWidth,
|
||||
@ -612,10 +396,9 @@ class SmartLayout extends Component {
|
||||
top: sidebarContentBounds.top,
|
||||
left: sidebarContentBounds.left,
|
||||
right: sidebarContentBounds.right,
|
||||
currentPanelType: input.currentPanelType,
|
||||
currentPanelType,
|
||||
tabOrder: DEFAULT_VALUES.sidebarContentTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarContentBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -641,7 +424,8 @@ class SmartLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAMERA_DOCK_OUTPUT,
|
||||
value: {
|
||||
display: input.cameraDock.numCameras > 0,
|
||||
display: cameraDockInput.numCameras > 0,
|
||||
position: cameraDockBounds.position,
|
||||
minWidth: cameraDockBounds.minWidth,
|
||||
width: cameraDockBounds.width,
|
||||
maxWidth: cameraDockBounds.maxWidth,
|
||||
@ -666,7 +450,7 @@ class SmartLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_OUTPUT,
|
||||
value: {
|
||||
display: input.presentation.isOpen,
|
||||
display: presentationInput.isOpen,
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
@ -700,13 +484,9 @@ class SmartLayout extends Component {
|
||||
right: isRTL ? (mediaBounds.right + horizontalCameraDiff) : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LayoutContextFunc.withConsumer(SmartLayout);
|
||||
export default SmartLayout;
|
||||
|
@ -1,26 +1,52 @@
|
||||
import React, { Component } from 'react';
|
||||
import { throttle, defaultsDeep } from 'lodash';
|
||||
import { LayoutContextFunc } from '/imports/ui/components/layout/context';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
layoutDispatch,
|
||||
layoutSelect,
|
||||
layoutSelectInput,
|
||||
layoutSelectOutput
|
||||
} from '/imports/ui/components/layout/context';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { INITIAL_INPUT_STATE } from '/imports/ui/components/layout/initState';
|
||||
import { DEVICE_TYPE, ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
import { ACTIONS, PANELS } from '/imports/ui/components/layout/enums';
|
||||
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
const min = (value1, value2) => (value1 <= value2 ? value1 : value2);
|
||||
const max = (value1, value2) => (value1 >= value2 ? value1 : value2);
|
||||
|
||||
class VideoFocusLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const VideoFocusLayout = (props) => {
|
||||
const { bannerAreaHeight, isMobile } = props;
|
||||
|
||||
this.throttledCalculatesLayout = throttle(() => this.calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
function usePrevious(value) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.init();
|
||||
const { layoutContextDispatch } = this.props;
|
||||
const input = layoutSelect((i) => i.input);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const fontSize = layoutSelect((i) => i.fontSize);
|
||||
const currentPanelType = layoutSelect((i) => i.currentPanelType);
|
||||
|
||||
const presentationInput = layoutSelectInput((i) => i.presentation);
|
||||
const sidebarNavigationInput = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const actionbarInput = layoutSelectInput((i) => i.actionBar);
|
||||
const navbarInput = layoutSelectInput((i) => i.navBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const sidebarContentOutput = layoutSelectOutput((i) => i.sidebarContent);
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
|
||||
const throttledCalculatesLayout = _.throttle(() => calculatesLayout(),
|
||||
50, { trailing: true, leading: true });
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_BROWSER_SIZE,
|
||||
@ -30,77 +56,57 @@ class VideoFocusLayout extends Component {
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
return layoutContextState.input !== nextProps.layoutContextState.input
|
||||
|| layoutContextState.deviceType !== nextProps.layoutContextState.deviceType
|
||||
|| layoutContextState.isRTL !== nextProps.layoutContextState.isRTL
|
||||
|| layoutContextState.fontSize !== nextProps.layoutContextState.fontSize
|
||||
|| layoutContextState.fullscreen !== nextProps.layoutContextState.fullscreen;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return;
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType } = layoutContextState;
|
||||
if (prevProps.layoutContextState.deviceType !== deviceType) {
|
||||
this.init();
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
// not all options is supported in all devices
|
||||
init();
|
||||
} else {
|
||||
this.throttledCalculatesLayout();
|
||||
throttledCalculatesLayout();
|
||||
}
|
||||
}
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen]);
|
||||
|
||||
bannerAreaHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input } = layoutContextState;
|
||||
const { bannerBar, notificationsBar } = input;
|
||||
|
||||
const bannerHeight = bannerBar.hasBanner ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
const notificationHeight = notificationsBar.hasNotification ? DEFAULT_VALUES.bannerHeight : 0;
|
||||
|
||||
return bannerHeight + notificationHeight;
|
||||
}
|
||||
|
||||
init() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { input } = layoutContextState;
|
||||
const { deviceType } = layoutContextState;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
const init = () => {
|
||||
if (isMobile) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep(
|
||||
value: _.defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
sidebarNavPanel: input.sidebarNavigation.sidebarNavPanel,
|
||||
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel: input.sidebarContent.sidebarContentPanel,
|
||||
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
},
|
||||
INITIAL_INPUT_STATE,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
const { sidebarContentPanel } = input.sidebarContent;
|
||||
const { sidebarContentPanel } = sidebarContentInput;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: defaultsDeep(
|
||||
value: _.defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen: true,
|
||||
@ -113,206 +119,52 @@ class VideoFocusLayout extends Component {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
slidesLength: input.presentation.slidesLength,
|
||||
isOpen: presentationInput.isOpen,
|
||||
slidesLength: presentationInput.slidesLength,
|
||||
currentSlide: {
|
||||
...input.presentation.currentSlide,
|
||||
...presentationInput.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: input.cameraDock.numCameras,
|
||||
numCameras: cameraDockInput.numCameras,
|
||||
},
|
||||
},
|
||||
INITIAL_INPUT_STATE,
|
||||
),
|
||||
});
|
||||
}
|
||||
this.throttledCalculatesLayout();
|
||||
}
|
||||
throttledCalculatesLayout();
|
||||
};
|
||||
|
||||
reset() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
calculatesNavbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { isRTL } = layoutContextState;
|
||||
|
||||
return {
|
||||
width: mediaAreaBounds.width,
|
||||
height: DEFAULT_VALUES.navBarHeight,
|
||||
top: DEFAULT_VALUES.navBarTop + this.bannerAreaHeight(),
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { fontSize } = layoutContextState;
|
||||
|
||||
const BASE_FONT_SIZE = 14; // 90% font size
|
||||
const BASE_HEIGHT = DEFAULT_VALUES.actionBarHeight;
|
||||
const PADDING = DEFAULT_VALUES.actionBarPadding;
|
||||
|
||||
const actionBarHeight = ((BASE_HEIGHT / BASE_FONT_SIZE) * fontSize);
|
||||
|
||||
return {
|
||||
height: actionBarHeight + (PADDING * 2),
|
||||
innerHeight: actionBarHeight,
|
||||
padding: PADDING,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesActionbarBounds(mediaAreaBounds) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { input, isRTL } = layoutContextState;
|
||||
|
||||
const actionBarHeight = this.calculatesActionbarHeight();
|
||||
|
||||
return {
|
||||
display: input.actionBar.hasActionBar,
|
||||
width: mediaAreaBounds.width,
|
||||
height: actionBarHeight.height,
|
||||
innerHeight: actionBarHeight.innerHeight,
|
||||
padding: actionBarHeight.padding,
|
||||
top: windowHeight() - actionBarHeight.height,
|
||||
left: !isRTL ? mediaAreaBounds.left : 0,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarNavMinWidth,
|
||||
sidebarNavMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarNavigation.width === 0) {
|
||||
width = min(max((windowWidth() * 0.2), sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
} else {
|
||||
width = min(max(input.sidebarNavigation.width, sidebarNavMinWidth), sidebarNavMaxWidth);
|
||||
}
|
||||
minWidth = sidebarNavMinWidth;
|
||||
maxWidth = sidebarNavMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarNavHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
let sidebarNavHeight = 0;
|
||||
if (input.sidebarNavigation.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
sidebarNavHeight = windowHeight() - DEFAULT_VALUES.navBarHeight;
|
||||
} else {
|
||||
sidebarNavHeight = windowHeight();
|
||||
}
|
||||
sidebarNavHeight -= this.bannerAreaHeight();
|
||||
}
|
||||
return sidebarNavHeight;
|
||||
}
|
||||
|
||||
calculatesSidebarNavBounds() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { sidebarNavTop, navBarHeight, sidebarNavLeft } = DEFAULT_VALUES;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) top = navBarHeight + this.bannerAreaHeight();
|
||||
|
||||
return {
|
||||
top,
|
||||
left: !isRTL ? sidebarNavLeft : null,
|
||||
right: isRTL ? sidebarNavLeft : null,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 10 : 2,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentWidth() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input } = layoutContextState;
|
||||
const {
|
||||
sidebarContentMinWidth,
|
||||
sidebarContentMaxWidth,
|
||||
} = DEFAULT_VALUES;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
let maxWidth = 0;
|
||||
if (input.sidebarContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
minWidth = windowWidth();
|
||||
width = windowWidth();
|
||||
maxWidth = windowWidth();
|
||||
} else {
|
||||
if (input.sidebarContent.width === 0) {
|
||||
width = min(
|
||||
max((windowWidth() * 0.2), sidebarContentMinWidth), sidebarContentMaxWidth,
|
||||
);
|
||||
} else {
|
||||
width = min(max(input.sidebarContent.width, sidebarContentMinWidth),
|
||||
sidebarContentMaxWidth);
|
||||
}
|
||||
minWidth = sidebarContentMinWidth;
|
||||
maxWidth = sidebarContentMaxWidth;
|
||||
}
|
||||
}
|
||||
return {
|
||||
minWidth,
|
||||
width,
|
||||
maxWidth,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesSidebarContentHeight() {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, input, output } = layoutContextState;
|
||||
const { sidebarContent: inputContent, presentation } = input;
|
||||
const { sidebarContent: outputContent } = output;
|
||||
const calculatesSidebarContentHeight = () => {
|
||||
let minHeight = 0;
|
||||
let height = 0;
|
||||
let maxHeight = 0;
|
||||
if (inputContent.isOpen) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
height = windowHeight() - DEFAULT_VALUES.navBarHeight - this.bannerAreaHeight();
|
||||
if (sidebarContentInput.isOpen) {
|
||||
if (isMobile) {
|
||||
height = windowHeight() - DEFAULT_VALUES.navBarHeight - bannerAreaHeight();
|
||||
minHeight = height;
|
||||
maxHeight = height;
|
||||
} else if (input.cameraDock.numCameras > 0 && presentation.isOpen) {
|
||||
if (inputContent.height > 0 && inputContent.height < windowHeight()) {
|
||||
height = inputContent.height - this.bannerAreaHeight();
|
||||
} else if (cameraDockInput.numCameras > 0 && presentationInput.isOpen) {
|
||||
if (sidebarContentInput.height > 0 && sidebarContentInput.height < windowHeight()) {
|
||||
height = sidebarContentInput.height - bannerAreaHeight();
|
||||
} else {
|
||||
const { size: slideSize } = input.presentation.currentSlide;
|
||||
let calculatedHeight = (windowHeight() - this.bannerAreaHeight()) * 0.3;
|
||||
const { size: slideSize } = presentationInput.currentSlide;
|
||||
let calculatedHeight = (windowHeight() - bannerAreaHeight()) * 0.3;
|
||||
|
||||
if (slideSize.height > 0 && slideSize.width > 0) {
|
||||
calculatedHeight = (slideSize.height * outputContent.width) / slideSize.width;
|
||||
calculatedHeight = (slideSize.height * sidebarContentOutput.width) / slideSize.width;
|
||||
}
|
||||
height = windowHeight() - calculatedHeight - this.bannerAreaHeight();
|
||||
height = windowHeight() - calculatedHeight - bannerAreaHeight();
|
||||
}
|
||||
maxHeight = windowHeight() * 0.75 - this.bannerAreaHeight();
|
||||
minHeight = windowHeight() * 0.25 - this.bannerAreaHeight();
|
||||
maxHeight = windowHeight() * 0.75 - bannerAreaHeight();
|
||||
minHeight = windowHeight() * 0.25 - bannerAreaHeight();
|
||||
|
||||
if (height > maxHeight) {
|
||||
height = maxHeight;
|
||||
}
|
||||
} else {
|
||||
height = windowHeight() - this.bannerAreaHeight();
|
||||
height = windowHeight() - bannerAreaHeight();
|
||||
maxHeight = height;
|
||||
minHeight = height;
|
||||
}
|
||||
@ -322,118 +174,50 @@ class VideoFocusLayout extends Component {
|
||||
height,
|
||||
maxHeight,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
calculatesSidebarContentBounds(sidebarNavWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { sidebarNavTop, navBarHeight } = DEFAULT_VALUES;
|
||||
const calculatesCameraDockBounds = (mediaAreaBounds, sidebarSize) => {
|
||||
const { baseCameraDockBounds } = props;
|
||||
|
||||
let top = sidebarNavTop + this.bannerAreaHeight();
|
||||
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) top = navBarHeight + this.bannerAreaHeight();
|
||||
|
||||
let left = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
let right = deviceType === DEVICE_TYPE.MOBILE ? 0 : sidebarNavWidth;
|
||||
left = !isRTL ? left : null;
|
||||
right = isRTL ? right : null;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
zIndex: deviceType === DEVICE_TYPE.MOBILE ? 11 : 1,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesMediaAreaBounds(sidebarNavWidth, sidebarContentWidth) {
|
||||
const { layoutContextState } = this.props;
|
||||
const { deviceType, isRTL } = layoutContextState;
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
const { height: actionBarHeight } = this.calculatesActionbarHeight();
|
||||
let left = 0;
|
||||
let width = 0;
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
left = 0;
|
||||
width = windowWidth();
|
||||
} else {
|
||||
left = !isRTL ? sidebarNavWidth + sidebarContentWidth : 0;
|
||||
width = windowWidth() - sidebarNavWidth - sidebarContentWidth;
|
||||
// do not proceed if using values from LayoutEngine
|
||||
if (Object.keys(baseBounds).length > 0) {
|
||||
return baseBounds;
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + this.bannerAreaHeight()),
|
||||
top: navBarHeight + this.bannerAreaHeight(),
|
||||
left,
|
||||
};
|
||||
}
|
||||
|
||||
calculatesCameraDockBounds(mediaAreaBounds, sidebarSize) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
deviceType, input, fullscreen, isRTL,
|
||||
} = layoutContextState;
|
||||
const { cameraDock } = input;
|
||||
const { numCameras } = cameraDock;
|
||||
const { navBarHeight } = DEFAULT_VALUES;
|
||||
|
||||
const cameraDockBounds = {};
|
||||
|
||||
if (numCameras > 0) {
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
cameraDockBounds.minHeight = mediaAreaBounds.height * 0.7;
|
||||
cameraDockBounds.height = mediaAreaBounds.height * 0.7;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.7;
|
||||
} else {
|
||||
cameraDockBounds.minHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
}
|
||||
|
||||
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
|
||||
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : null;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
|
||||
if (fullscreen.group === 'webcams') {
|
||||
cameraDockBounds.width = windowWidth();
|
||||
cameraDockBounds.minWidth = windowWidth();
|
||||
cameraDockBounds.maxWidth = windowWidth();
|
||||
cameraDockBounds.height = windowHeight();
|
||||
cameraDockBounds.minHeight = windowHeight();
|
||||
cameraDockBounds.maxHeight = windowHeight();
|
||||
cameraDockBounds.top = 0;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.zIndex = 99;
|
||||
}
|
||||
|
||||
return cameraDockBounds;
|
||||
if (isMobile) {
|
||||
cameraDockBounds.minHeight = mediaAreaBounds.height * 0.7;
|
||||
cameraDockBounds.height = mediaAreaBounds.height * 0.7;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.7;
|
||||
} else {
|
||||
cameraDockBounds.minHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
}
|
||||
|
||||
cameraDockBounds.top = 0;
|
||||
cameraDockBounds.left = 0;
|
||||
cameraDockBounds.minWidth = 0;
|
||||
cameraDockBounds.height = 0;
|
||||
cameraDockBounds.width = 0;
|
||||
cameraDockBounds.maxWidth = 0;
|
||||
cameraDockBounds.zIndex = 0;
|
||||
return cameraDockBounds;
|
||||
}
|
||||
cameraDockBounds.top = navBarHeight;
|
||||
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : null;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
cameraDockBounds.minWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
|
||||
calculatesMediaBounds(
|
||||
return cameraDockBounds;
|
||||
};
|
||||
|
||||
const calculatesMediaBounds = (
|
||||
mediaAreaBounds,
|
||||
cameraDockBounds,
|
||||
sidebarNavWidth,
|
||||
sidebarContentWidth,
|
||||
sidebarContentHeight,
|
||||
) {
|
||||
const { layoutContextState } = this.props;
|
||||
const {
|
||||
deviceType, input, fullscreen, isRTL,
|
||||
} = layoutContextState;
|
||||
) => {
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
|
||||
@ -448,12 +232,12 @@ class VideoFocusLayout extends Component {
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
if (deviceType === DEVICE_TYPE.MOBILE) {
|
||||
if (isMobile) {
|
||||
mediaBounds.height = mediaAreaBounds.height - cameraDockBounds.height;
|
||||
mediaBounds.left = mediaAreaBounds.left;
|
||||
mediaBounds.top = mediaAreaBounds.top + cameraDockBounds.height;
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
} else if (input.cameraDock.numCameras > 0) {
|
||||
} else if (cameraDockInput.numCameras > 0) {
|
||||
mediaBounds.height = windowHeight() - sidebarContentHeight;
|
||||
mediaBounds.left = !isRTL ? sidebarNavWidth : 0;
|
||||
mediaBounds.right = isRTL ? sidebarNavWidth : 0;
|
||||
@ -463,50 +247,55 @@ class VideoFocusLayout extends Component {
|
||||
} else {
|
||||
mediaBounds.height = mediaAreaBounds.height;
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.top = DEFAULT_VALUES.navBarHeight + this.bannerAreaHeight();
|
||||
mediaBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
|
||||
mediaBounds.left = !isRTL ? mediaAreaBounds.left : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
mediaBounds.zIndex = 1;
|
||||
}
|
||||
|
||||
return mediaBounds;
|
||||
}
|
||||
};
|
||||
|
||||
calculatesLayout() {
|
||||
const { layoutContextState, layoutContextDispatch } = this.props;
|
||||
const { deviceType, input, isRTL } = layoutContextState;
|
||||
const calculatesLayout = () => {
|
||||
const {
|
||||
calculatesNavbarBounds,
|
||||
calculatesActionbarBounds,
|
||||
calculatesSidebarNavWidth,
|
||||
calculatesSidebarNavHeight,
|
||||
calculatesSidebarNavBounds,
|
||||
calculatesSidebarContentWidth,
|
||||
calculatesSidebarContentBounds,
|
||||
calculatesMediaAreaBounds,
|
||||
isTablet,
|
||||
} = props;
|
||||
const { captionsMargin } = DEFAULT_VALUES;
|
||||
|
||||
const sidebarNavWidth = this.calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = this.calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = this.calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = this.calculatesSidebarNavBounds(
|
||||
const sidebarNavWidth = calculatesSidebarNavWidth();
|
||||
const sidebarNavHeight = calculatesSidebarNavHeight();
|
||||
const sidebarContentWidth = calculatesSidebarContentWidth();
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
);
|
||||
const sidebarContentBounds = this.calculatesSidebarContentBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
);
|
||||
const mediaAreaBounds = this.calculatesMediaAreaBounds(
|
||||
sidebarNavWidth.width, sidebarContentWidth.width,
|
||||
);
|
||||
const navbarBounds = this.calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = this.calculatesActionbarBounds(mediaAreaBounds);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const sidebarSize = sidebarContentWidth.width + sidebarNavWidth.width;
|
||||
const cameraDockBounds = this.calculatesCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
const sidebarContentHeight = this.calculatesSidebarContentHeight();
|
||||
const mediaBounds = this.calculatesMediaBounds(
|
||||
const cameraDockBounds = calculatesCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
const sidebarContentHeight = calculatesSidebarContentHeight();
|
||||
const mediaBounds = calculatesMediaBounds(
|
||||
mediaAreaBounds,
|
||||
cameraDockBounds,
|
||||
sidebarNavWidth.width,
|
||||
sidebarContentWidth.width,
|
||||
sidebarContentHeight.height,
|
||||
);
|
||||
const isBottomResizable = input.cameraDock.numCameras > 0;
|
||||
const isBottomResizable = cameraDockInput.numCameras > 0;
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NAVBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.navBar.hasNavBar,
|
||||
display: navbarInput.hasNavBar,
|
||||
width: navbarBounds.width,
|
||||
height: navbarBounds.height,
|
||||
top: navbarBounds.top,
|
||||
@ -519,7 +308,7 @@ class VideoFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ACTIONBAR_OUTPUT,
|
||||
value: {
|
||||
display: input.actionBar.hasActionBar,
|
||||
display: actionbarInput.hasActionBar,
|
||||
width: actionbarBounds.width,
|
||||
height: actionbarBounds.height,
|
||||
innerHeight: actionbarBounds.innerHeight,
|
||||
@ -543,7 +332,7 @@ class VideoFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_NAVIGATION_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarNavigation.isOpen,
|
||||
display: sidebarNavigationInput.isOpen,
|
||||
minWidth: sidebarNavWidth.minWidth,
|
||||
width: sidebarNavWidth.width,
|
||||
maxWidth: sidebarNavWidth.maxWidth,
|
||||
@ -552,8 +341,7 @@ class VideoFocusLayout extends Component {
|
||||
left: sidebarNavBounds.left,
|
||||
right: sidebarNavBounds.right,
|
||||
tabOrder: DEFAULT_VALUES.sidebarNavTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarNavBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -571,7 +359,7 @@ class VideoFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
|
||||
value: {
|
||||
display: input.sidebarContent.isOpen,
|
||||
display: sidebarContentInput.isOpen,
|
||||
minWidth: sidebarContentWidth.minWidth,
|
||||
width: sidebarContentWidth.width,
|
||||
maxWidth: sidebarContentWidth.maxWidth,
|
||||
@ -581,10 +369,9 @@ class VideoFocusLayout extends Component {
|
||||
top: sidebarContentBounds.top,
|
||||
left: sidebarContentBounds.left,
|
||||
right: sidebarContentBounds.right,
|
||||
currentPanelType: input.currentPanelType,
|
||||
currentPanelType,
|
||||
tabOrder: DEFAULT_VALUES.sidebarContentTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: sidebarContentBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -610,7 +397,7 @@ class VideoFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAMERA_DOCK_OUTPUT,
|
||||
value: {
|
||||
display: input.cameraDock.numCameras > 0,
|
||||
display: cameraDockInput.numCameras > 0,
|
||||
minWidth: cameraDockBounds.minWidth,
|
||||
width: cameraDockBounds.width,
|
||||
maxWidth: cameraDockBounds.maxWidth,
|
||||
@ -635,15 +422,14 @@ class VideoFocusLayout extends Component {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_OUTPUT,
|
||||
value: {
|
||||
display: input.presentation.isOpen,
|
||||
display: presentationInput.isOpen,
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? mediaBounds.right : null,
|
||||
tabOrder: DEFAULT_VALUES.presentationTabOrder,
|
||||
isResizable: deviceType !== DEVICE_TYPE.MOBILE
|
||||
&& deviceType !== DEVICE_TYPE.TABLET,
|
||||
isResizable: !isMobile && !isTablet,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
},
|
||||
});
|
||||
@ -680,11 +466,9 @@ class VideoFocusLayout extends Component {
|
||||
right: isRTL ? mediaBounds.right : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LayoutContextFunc.withConsumer(VideoFocusLayout);
|
||||
export default VideoFocusLayout;
|
||||
|
@ -30,7 +30,7 @@ const getLearningDashboardAccessToken = () => ((
|
||||
|
||||
const openLearningDashboardUrl = (lang) => {
|
||||
const cookieExpiresDate = new Date();
|
||||
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + 3600000);
|
||||
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + (3600000 * 24 * 30)); // keep cookie 30d
|
||||
document.cookie = `learningDashboardAccessToken-${Auth.meetingID}=${getLearningDashboardAccessToken()}; expires=${cookieExpiresDate.toGMTString()}; path=/`;
|
||||
window.open(`/learning-dashboard/?meeting=${Auth.meetingID}&lang=${lang}`, '_blank');
|
||||
};
|
||||
|
@ -47,9 +47,14 @@ const swapLayout = {
|
||||
tracker: new Tracker.Dependency(),
|
||||
};
|
||||
|
||||
const setSwapLayout = () => {
|
||||
const setSwapLayout = (layoutContextDispatch) => {
|
||||
swapLayout.value = getFromUserSettings('bbb_auto_swap_layout', LAYOUT_CONFIG.autoSwapLayout);
|
||||
swapLayout.tracker.changed();
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: !swapLayout.value,
|
||||
});
|
||||
};
|
||||
|
||||
const toggleSwapLayout = (layoutContextDispatch) => {
|
||||
|
@ -9,6 +9,8 @@ import { Divider } from "@material-ui/core";
|
||||
import Icon from "/imports/ui/components/icon/component";
|
||||
import Button from "/imports/ui/components/button/component";
|
||||
|
||||
import { ENTER, SPACE } from "/imports/utils/keyCodes";
|
||||
|
||||
import { styles } from "./styles";
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -106,6 +108,11 @@ class BBBMenu extends React.Component {
|
||||
this.opts.autoFocus = !(['mouse', 'touch'].includes(e.nativeEvent.pointerType));
|
||||
this.handleClick(e);
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
e.persist();
|
||||
if (e.which !== ENTER) return null;
|
||||
this.handleClick(e);
|
||||
}}
|
||||
accessKey={this.props?.accessKey}
|
||||
>
|
||||
{trigger}
|
||||
@ -117,6 +124,7 @@ class BBBMenu extends React.Component {
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={this.handleClose}
|
||||
className={menuClasses.join(' ')}
|
||||
style={{ zIndex: 9999 }}
|
||||
>
|
||||
{actionsItems}
|
||||
{anchorEl && window.innerWidth < MAX_WIDTH &&
|
||||
|
@ -10,6 +10,11 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin-right: .5rem;
|
||||
margin-left: 1.65rem;
|
||||
}
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactModal from 'react-modal';
|
||||
import { styles } from './styles.scss';
|
||||
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
|
||||
|
||||
const propTypes = {
|
||||
overlayClassName: PropTypes.string.isRequired,
|
||||
@ -19,6 +20,15 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
export default class ModalBase extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
registerTitleView(this.props.contentLabel);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterTitleView();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.isOpen) return null;
|
||||
|
||||
@ -27,8 +37,8 @@ export default class ModalBase extends Component {
|
||||
{...this.props}
|
||||
parentSelector={() => {
|
||||
if (document.fullscreenElement &&
|
||||
document.fullscreenElement.nodeName &&
|
||||
document.fullscreenElement.nodeName.toLowerCase() === 'div')
|
||||
document.fullscreenElement.nodeName &&
|
||||
document.fullscreenElement.nodeName.toLowerCase() === 'div')
|
||||
return document.fullscreenElement;
|
||||
else return document.body;
|
||||
}}
|
||||
@ -55,12 +65,12 @@ export const withModalState = ComponentToWrap =>
|
||||
this.show = this.show.bind(this);
|
||||
}
|
||||
|
||||
hide(cb = () => {}) {
|
||||
hide(cb = () => { }) {
|
||||
Promise.resolve(cb())
|
||||
.then(() => this.setState({ isOpen: false }));
|
||||
}
|
||||
|
||||
show(cb = () => {}) {
|
||||
show(cb = () => { }) {
|
||||
Promise.resolve(cb())
|
||||
.then(() => this.setState({ isOpen: true }));
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { UsersContext } from '/imports/ui/components/components-data/users-conte
|
||||
import NoteService from '/imports/ui/components/note/service';
|
||||
import Service from './service';
|
||||
import NavBar from './component';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelectInput, layoutSelectOutput, layoutDispatch } from '../layout/context';
|
||||
|
||||
const PUBLIC_CONFIG = Meteor.settings.public;
|
||||
const ROLE_MODERATOR = PUBLIC_CONFIG.user.role_moderator;
|
||||
@ -28,8 +28,6 @@ const checkUnreadMessages = ({
|
||||
};
|
||||
|
||||
const NavBarContainer = ({ children, ...props }) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const usingChatContext = useContext(ChatContext);
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const usingGroupChatContext = useContext(GroupChatContext);
|
||||
@ -37,13 +35,15 @@ const NavBarContainer = ({ children, ...props }) => {
|
||||
const { users } = usingUsersContext;
|
||||
const { groupChat: groupChats } = usingGroupChatContext;
|
||||
const { ...rest } = props;
|
||||
const {
|
||||
input, output,
|
||||
} = layoutContextState;
|
||||
const { sidebarContent, sidebarNavigation } = input;
|
||||
const { sidebarNavPanel } = sidebarNavigation;
|
||||
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const sidebarNavigation = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const navBar = layoutSelectOutput((i) => i.navBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
const { navBar } = output;
|
||||
const { sidebarNavPanel } = sidebarNavigation;
|
||||
|
||||
const hasUnreadNotes = NoteService.hasUnreadNotes(sidebarContentPanel);
|
||||
const hasUnreadMessages = checkUnreadMessages(
|
||||
{ groupChatsMessages, groupChats, users: users[Auth.meetingID] },
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import VoiceUsers from '/imports/api/voice-users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
@ -7,7 +7,7 @@ import TalkingIndicator from './component';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import { meetingIsBreakout } from '/imports/ui/components/app/service';
|
||||
import Service from './service';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../../layout/context';
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const { enableTalkingIndicator } = APP_CONFIG;
|
||||
@ -15,12 +15,13 @@ const TALKING_INDICATOR_MUTE_INTERVAL = 500;
|
||||
|
||||
const TalkingIndicatorContainer = (props) => {
|
||||
if (!enableTalkingIndicator) return null;
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { input } = layoutContextState;
|
||||
const { sidebarContent, sidebarNavigation } = input;
|
||||
const { sidebarNavPanel } = sidebarNavigation;
|
||||
|
||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
const sidebarNavigation = layoutSelectInput((i) => i.sidebarNavigation);
|
||||
const { sidebarNavPanel } = sidebarNavigation;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const sidebarNavigationIsOpen = sidebarNavigation.isOpen;
|
||||
const sidebarContentIsOpen = sidebarContent.isOpen;
|
||||
return (
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Note from './component';
|
||||
import NoteService from './service';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../layout/context';
|
||||
|
||||
const NoteContainer = ({ children, ...props }) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextDispatch, layoutContextState } = layoutContext;
|
||||
const { input } = layoutContextState;
|
||||
const { cameraDock } = input;
|
||||
const cameraDock = layoutSelectInput((i) => i.cameraDock);
|
||||
const { isResizing } = cameraDock;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
return (
|
||||
<Note {...{ layoutContextDispatch, isResizing, ...props }}>
|
||||
{children}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import _ from 'lodash';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Meetings, { MeetingTimeRemaining } from '/imports/api/meetings';
|
||||
import BreakoutRemainingTime from '/imports/ui/components/breakout-room/breakout-remaining-time/container';
|
||||
import { styles } from './styles.scss';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch } from '../layout/context';
|
||||
import { ACTIONS } from '../layout/enums';
|
||||
|
||||
import breakoutService from '/imports/ui/components/breakout-room/service';
|
||||
@ -76,10 +76,10 @@ const intlMessages = defineMessages({
|
||||
|
||||
const NotificationsBarContainer = (props) => {
|
||||
const { message, color } = props;
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { input } = layoutContextState;
|
||||
const { notificationsBar } = input;
|
||||
|
||||
const notificationsBar = layoutSelectInput((i) => i.notificationsBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const { hasNotification } = notificationsBar;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -11,6 +11,7 @@ import LiveResult from './live-result/component';
|
||||
import { styles } from './styles.scss';
|
||||
import { PANELS, ACTIONS } from '../layout/enums';
|
||||
import DragAndDrop from './dragAndDrop/component';
|
||||
import { alertScreenReader } from '/imports/utils/dom-utils';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
pollPaneTitle: {
|
||||
@ -185,6 +186,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.switch.offLabel',
|
||||
description: 'label for toggle switch off state',
|
||||
},
|
||||
removePollOpt: {
|
||||
id: 'app.poll.removePollOpt',
|
||||
description: 'screen reader alert for removed poll option',
|
||||
},
|
||||
emptyPollOpt: {
|
||||
id: 'app.poll.emptyPollOpt',
|
||||
description: 'screen reader for blank poll option',
|
||||
},
|
||||
});
|
||||
|
||||
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||
@ -295,10 +304,15 @@ class Poll extends Component {
|
||||
}
|
||||
|
||||
handleRemoveOption(index) {
|
||||
const { intl } = this.props;
|
||||
const { optList } = this.state;
|
||||
const list = [...optList];
|
||||
const removed = list[index];
|
||||
list.splice(index, 1);
|
||||
this.setState({ optList: list });
|
||||
this.setState({ optList: list }, () => {
|
||||
alertScreenReader(`${intl.formatMessage(intlMessages.removePollOpt,
|
||||
{ 0: removed.val || intl.formatMessage(intlMessages.emptyPollOpt) })}`);
|
||||
});
|
||||
}
|
||||
|
||||
handleAddOption() {
|
||||
@ -395,7 +409,10 @@ class Poll extends Component {
|
||||
this.handleRemoveOption(i);
|
||||
}}
|
||||
/>
|
||||
<span className="sr-only" id={`option-${i}`}>{intl.formatMessage(intlMessages.deleteRespDesc, { 0: o.val })}</span>
|
||||
<span className="sr-only" id={`option-${i}`}>
|
||||
{intl.formatMessage(intlMessages.deleteRespDesc,
|
||||
{ 0: (o.val || intl.formatMessage(intlMessages.emptyPollOpt)) })}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
: <div style={{ width: '40px' }} />}
|
||||
|
@ -8,14 +8,14 @@ import { Session } from 'meteor/session';
|
||||
import Service from './service';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { UsersContext } from '../components-data/users-context/context';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutDispatch } from '../layout/context';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
||||
|
||||
const PollContainer = ({ ...props }) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextDispatch } = layoutContext;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Polls from '/imports/api/polls';
|
||||
import { CurrentPoll } from '/imports/api/polls';
|
||||
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
@ -217,7 +217,7 @@ export default {
|
||||
{ fields: { presenter: 1 } },
|
||||
).presenter,
|
||||
pollTypes,
|
||||
currentPoll: () => Polls.findOne({ meetingId: Auth.meetingID }),
|
||||
currentPoll: () => CurrentPoll.findOne({ meetingId: Auth.meetingID }),
|
||||
pollAnswerIds,
|
||||
POLL_AVATAR_COLOR,
|
||||
isDefaultPoll,
|
||||
|
@ -761,6 +761,7 @@ class Presentation extends PureComponent {
|
||||
<span className={styles.toastDownload}>
|
||||
<div className={toastStyles.separator} />
|
||||
<a
|
||||
data-test="toastDownload"
|
||||
className={styles.downloadBtn}
|
||||
aria-label={`${intl.formatMessage(intlMessages.downloadLabel)} ${currentPresentation.name}`}
|
||||
href={downloadPresentationUri}
|
||||
|
@ -2,7 +2,6 @@ import React, { useContext } from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import MediaService, { getSwapLayout, shouldEnableSwapLayout } from '/imports/ui/components/media/service';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import { Session } from 'meteor/session';
|
||||
import PresentationService from './service';
|
||||
import { Slides } from '/imports/api/slides';
|
||||
import Presentation from '/imports/ui/components/presentation/component';
|
||||
@ -11,25 +10,32 @@ import { UsersContext } from '../components-data/users-context/context';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import LayoutContext from '../layout/context';
|
||||
import {
|
||||
layoutSelect,
|
||||
layoutSelectInput,
|
||||
layoutSelectOutput,
|
||||
layoutDispatch,
|
||||
} from '../layout/context';
|
||||
import WhiteboardService from '/imports/ui/components/whiteboard/service';
|
||||
import { DEVICE_TYPE } from '../layout/enums';
|
||||
|
||||
const ROLE_VIEWER = Meteor.settings.public.user.role_viewer;
|
||||
|
||||
const PresentationContainer = ({ presentationPodIds, mountPresentation, ...props }) => {
|
||||
const fullscreenElementId = 'Presentation';
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const {
|
||||
input, output, layoutType, fullscreen, deviceType,
|
||||
} = layoutContextState;
|
||||
const { cameraDock } = input;
|
||||
const { numCameras } = cameraDock;
|
||||
const { presentation } = output;
|
||||
const { element } = fullscreen;
|
||||
const fullscreenContext = (element === fullscreenElementId);
|
||||
const { layoutSwapped, podId } = props;
|
||||
|
||||
const cameraDock = layoutSelectInput((i) => i.cameraDock);
|
||||
const presentation = layoutSelectOutput((i) => i.presentation);
|
||||
const layoutType = layoutSelect((i) => i.layoutType);
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const { numCameras } = cameraDock;
|
||||
const { element } = fullscreen;
|
||||
const fullscreenElementId = 'Presentation';
|
||||
const fullscreenContext = (element === fullscreenElementId);
|
||||
|
||||
const isIphone = !!(navigator.userAgent.match(/iPhone/i));
|
||||
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
|
@ -37,6 +37,7 @@ const DownloadPresentationButton = ({
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<Button
|
||||
data-test="presentationDownload"
|
||||
color="default"
|
||||
icon="template_download"
|
||||
size="sm"
|
||||
|
@ -1,12 +1,9 @@
|
||||
import React, { useContext } from 'react';
|
||||
import LayoutContext from '../../layout/context';
|
||||
import React from 'react';
|
||||
import { layoutSelectOutput } from '../../layout/context';
|
||||
import PresentationArea from './component';
|
||||
|
||||
const PresentationAreaContainer = () => {
|
||||
const layoutManager = useContext(LayoutContext);
|
||||
const { layoutContextState } = layoutManager;
|
||||
const { output } = layoutContextState;
|
||||
const { presentation } = output;
|
||||
const presentation = layoutSelectOutput((i) => i.presentation);
|
||||
|
||||
return <PresentationArea {...{ ...presentation }} />;
|
||||
};
|
||||
|
@ -23,6 +23,7 @@ const PresentationPlaceholder = ({
|
||||
<div
|
||||
ref={(ref) => setPresentationRef(ref)}
|
||||
className={styles.presentationPlaceholder}
|
||||
data-test="presentationPlaceholder"
|
||||
style={{
|
||||
top,
|
||||
left,
|
||||
|
@ -13,6 +13,7 @@ import logger from '/imports/startup/client/logger';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import { toast } from 'react-toastify';
|
||||
import _ from 'lodash';
|
||||
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
|
||||
import { styles } from './styles';
|
||||
|
||||
const { isMobile } = deviceInfo;
|
||||
@ -214,6 +215,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploder.clearErrorsDesc',
|
||||
description: 'aria description for button clearing upload error',
|
||||
},
|
||||
uploadViewTitle: {
|
||||
id: 'app.presentationUploder.uploadViewTitle',
|
||||
description: 'view name apended to document title',
|
||||
}
|
||||
});
|
||||
|
||||
class PresentationUploader extends Component {
|
||||
@ -251,10 +256,16 @@ class PresentationUploader extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isOpen, presentations: propPresentations } = this.props;
|
||||
const { isOpen, presentations: propPresentations, intl } = this.props;
|
||||
const { presentations } = this.state;
|
||||
|
||||
if (!isOpen && prevProps.isOpen) {
|
||||
unregisterTitleView();
|
||||
}
|
||||
|
||||
// Updates presentation list when chat modal opens to avoid missing presentations
|
||||
if (isOpen && !prevProps.isOpen) {
|
||||
registerTitleView(intl.formatMessage(intlMessages.uploadViewTitle));
|
||||
const focusableElements =
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||||
const modal = document.getElementById('upload-modal');
|
||||
@ -728,6 +739,7 @@ class PresentationUploader extends Component {
|
||||
const {
|
||||
intl,
|
||||
selectedToBeNextCurrent,
|
||||
allowDownloadable
|
||||
} = this.props;
|
||||
|
||||
const isActualCurrent = selectedToBeNextCurrent ? item.id === selectedToBeNextCurrent : item.isCurrent;
|
||||
@ -747,6 +759,10 @@ class PresentationUploader extends Component {
|
||||
[styles.tableItemError]: hasError,
|
||||
[styles.tableItemAnimated]: isProcessing,
|
||||
};
|
||||
|
||||
const itemActions = {
|
||||
[styles.notDownloadable]: !allowDownloadable,
|
||||
};
|
||||
|
||||
const formattedDownloadableLabel = !item.isDownloadable
|
||||
? intl.formatMessage(intlMessages.isDownloadable)
|
||||
@ -784,17 +800,21 @@ class PresentationUploader extends Component {
|
||||
{this.renderPresentationItemStatus(item)}
|
||||
</td>
|
||||
{hasError ? null : (
|
||||
<td className={styles.tableItemActions}>
|
||||
<Button
|
||||
disabled={disableActions}
|
||||
className={isDownloadableStyle}
|
||||
label={formattedDownloadableLabel}
|
||||
aria-label={formattedDownloadableAriaLabel}
|
||||
hideLabel
|
||||
size="sm"
|
||||
icon={item.isDownloadable ? 'download' : 'download-off'}
|
||||
onClick={() => this.handleToggleDownloadable(item)}
|
||||
/>
|
||||
<td className={cx(styles.tableItemActions, itemActions)}>
|
||||
{allowDownloadable ? (
|
||||
<Button
|
||||
disabled={disableActions}
|
||||
className={isDownloadableStyle}
|
||||
label={formattedDownloadableLabel}
|
||||
data-test={item.isDownloadable ? 'disallowPresentationDownload' : 'allowPresentationDownload'}
|
||||
aria-label={formattedDownloadableAriaLabel}
|
||||
hideLabel
|
||||
size="sm"
|
||||
icon={item.isDownloadable ? 'download' : 'download-off'}
|
||||
onClick={() => this.handleToggleDownloadable(item)}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
<Checkbox
|
||||
ariaLabel={`${intl.formatMessage(intlMessages.setAsCurrentPresentation)} ${item.filename}`}
|
||||
checked={item.isCurrent}
|
||||
@ -807,6 +827,7 @@ class PresentationUploader extends Component {
|
||||
disabled={disableActions}
|
||||
className={cx(styles.itemAction, styles.itemActionRemove)}
|
||||
label={intl.formatMessage(intlMessages.removePresentation)}
|
||||
data-test="removePresentation"
|
||||
aria-label={`${intl.formatMessage(intlMessages.removePresentation)} ${item.filename}`}
|
||||
size="sm"
|
||||
icon="delete"
|
||||
@ -995,6 +1016,7 @@ class PresentationUploader extends Component {
|
||||
/>
|
||||
<Button
|
||||
className={styles.confirm}
|
||||
data-test="confirmManagePresentation"
|
||||
color="primary"
|
||||
onClick={() => this.handleConfirm(hasNewUpload)}
|
||||
disabled={disableActions}
|
||||
|
@ -21,14 +21,15 @@ const PresentationUploaderContainer = (props) => (
|
||||
export default withTracker(() => {
|
||||
const currentPresentations = Service.getPresentations();
|
||||
const {
|
||||
dispatchDisableDownloadable,
|
||||
dispatchEnableDownloadable,
|
||||
dispatchTogglePresentationDownloadable,
|
||||
} = Service;
|
||||
dispatchDisableDownloadable,
|
||||
dispatchEnableDownloadable,
|
||||
dispatchTogglePresentationDownloadable,
|
||||
} = Service;
|
||||
|
||||
return {
|
||||
presentations: currentPresentations,
|
||||
fileValidMimeTypes: PRESENTATION_CONFIG.uploadValidMimeTypes,
|
||||
allowDownloadable: PRESENTATION_CONFIG.allowDownloadable,
|
||||
handleSave: (presentations) => Service.persistPresentationChanges(
|
||||
currentPresentations,
|
||||
presentations,
|
||||
|
@ -103,6 +103,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.notDownloadable {
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.tableItemIcon > i {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ class ScreenshareComponent extends React.Component {
|
||||
{
|
||||
isGloballyBroadcasting
|
||||
? (
|
||||
<div>
|
||||
<div data-test="isSharingScreen">
|
||||
{!switched
|
||||
&& ScreenshareComponent.renderScreenshareContainerInside(
|
||||
intl.formatMessage(intlMessages.presenterSharingLabel),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Users from '/imports/api/users/';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
@ -8,15 +8,15 @@ import {
|
||||
isGloballyBroadcasting,
|
||||
} from './service';
|
||||
import ScreenshareComponent from './component';
|
||||
import LayoutContext from '../layout/context';
|
||||
import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../layout/context';
|
||||
|
||||
const ScreenshareContainer = (props) => {
|
||||
const fullscreenElementId = 'Screenshare';
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { output, fullscreen } = layoutContextState;
|
||||
const { screenShare } = output;
|
||||
const screenShare = layoutSelectOutput((i) => i.screenShare);
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const { element } = fullscreen;
|
||||
const fullscreenElementId = 'Screenshare';
|
||||
const fullscreenContext = (element === fullscreenElementId);
|
||||
|
||||
if (isVideoBroadcasting()) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user