Merge branch 'develop' of github.com:bigbluebutton/bigbluebutton into 2.4-into-develop

This commit is contained in:
Anton Georgiev 2021-10-22 19:12:58 +00:00
commit 1622425cbc
122 changed files with 1998 additions and 2165 deletions

View File

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

View File

@ -4,6 +4,7 @@
<condition field="${sip_via_protocol}" expression="^wss?$" break="on-false">
<action application="set" data="bbb_authorized=true"/>
<action application="set" data="jb_use_timestamps=true"/>
<action application="set" data="include_external_ip=true"/>
<action application="transfer" data="${destination_number} XML default"/>
</condition>
</extension>

View File

@ -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,15 @@ 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/ui/local-collections/meetings-collection/meetings';
import '/imports/ui/local-collections/breakouts-collection/breakouts';
import '/imports/ui/local-collections/guest-users-collection/guest-users';
import '/imports/ui/local-collections/users-collection/users';
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) => {

View File

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

View File

@ -305,3 +305,5 @@ export default class KurentoAudioBridge extends BaseAudioBridge {
return Promise.resolve();
}
}
module.exports = KurentoAudioBridge;

View File

@ -1532,3 +1532,5 @@ export default class SIPBridge extends BaseAudioBridge {
return this.activeSession.updateAudioConstraints(constraints);
}
}
module.exports = SIPBridge;

View File

@ -3,10 +3,11 @@ import Breakouts from '/imports/api/breakouts';
import Users from '/imports/api/users';
import Logger from '/imports/startup/server/logger';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
import { publicationSafeGuard } from '/imports/api/common/server/helpers';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
function breakouts(role) {
function breakouts() {
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
@ -25,7 +26,19 @@ function breakouts(role) {
{ breakoutId: meetingId },
],
};
// Monitor this publication and stop it when user is not a moderator anymore
const comparisonFunc = () => {
const user = Users.findOne({ userId, meetingId }, { fields: { role: 1, userId: 1 } });
const condition = user.role === ROLE_MODERATOR;
if (!condition) {
Logger.info(`conditions aren't filled anymore in publication ${this._name}:
user.role === ROLE_MODERATOR :${condition}, user.role: ${user.role} ROLE_MODERATOR: ${ROLE_MODERATOR}`);
}
return condition;
};
publicationSafeGuard(comparisonFunc, this);
return Breakouts.find(presenterSelector);
}

View File

@ -1,4 +1,5 @@
import Users from '/imports/api/users';
import Logger from '/imports/startup/server/logger';
const MSG_DIRECT_TYPE = 'DIRECT';
const NODE_USER = 'nodeJSapp';
@ -21,7 +22,7 @@ export const indexOf = [].indexOf || function (item) {
return -1;
};
export const processForHTML5ServerOnly = fn => (message, ...args) => {
export const processForHTML5ServerOnly = (fn) => (message, ...args) => {
const { envelope } = message;
const { routing } = envelope;
const { msgType, meetingId, userId } = routing;
@ -45,3 +46,23 @@ export const extractCredentials = (credentials) => {
const requesterUserId = credentialsArray[1];
return { meetingId, requesterUserId };
};
// Creates a background job to periodically check the result of the provided function.
// The provided function is publication-specific and must check the "survival condition" of the publication.
export const publicationSafeGuard = function (fn, self) {
let stopped = false;
const periodicCheck = function () {
if (stopped) return;
if (!fn()) {
self.added(self._name, 'publication-stop-marker', { id: 'publication-stop-marker', stopped: true });
self.stop();
} else Meteor.setTimeout(periodicCheck, 1000);
};
self.onStop(() => {
stopped = true;
Logger.info(`Publication ${self._name} has stopped in server side`);
});
periodicCheck();
};

View File

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

View File

@ -40,9 +40,7 @@ export default function sendGroupChatMsg(chatId, message) {
check(requesterUserId, String);
check(chatId, String);
check(message, Object);
const parsedMessage = parseMessage(message.message);
message.message = parsedMessage;
const payload = {

View File

@ -3,10 +3,11 @@ import Users from '/imports/api/users';
import { Meteor } from 'meteor/meteor';
import Logger from '/imports/startup/server/logger';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
import { publicationSafeGuard } from '/imports/api/common/server/helpers';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
function guestUsers(role) {
function guestUsers() {
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
@ -22,9 +23,22 @@ function guestUsers(role) {
'Publishing GuestUser was requested by non-moderator connection',
{ meetingId, userId, connectionId: this.connection.id },
);
return GuestUsers.find({ meetingId: '' });
}
// Monitor this publication and stop it when user is not a moderator anymore
const comparisonFunc = () => {
const user = Users.findOne({ userId, meetingId }, { fields: { role: 1, userId: 1 } });
const condition = user.role === ROLE_MODERATOR;
if (!condition) {
Logger.info(`conditions aren't filled anymore in publication ${this._name}:
user.role === ROLE_MODERATOR :${condition}, user.role: ${user.role} ROLE_MODERATOR: ${ROLE_MODERATOR}`);
}
return condition;
};
publicationSafeGuard(comparisonFunc, this);
Logger.debug(`Publishing GuestUsers for ${meetingId} ${userId}`);
return GuestUsers.find({ meetingId });

View File

@ -6,11 +6,12 @@ import Meetings, {
} from '/imports/api/meetings';
import Users from '/imports/api/users';
import Logger from '/imports/startup/server/logger';
import { publicationSafeGuard } from '/imports/api/common/server/helpers';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
function meetings(role) {
function meetings() {
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
@ -28,14 +29,26 @@ function meetings(role) {
],
};
const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } });
const User = Users.findOne({ userId, meetingId }, { fields: { userId: 1, role: 1 } });
if (!!User && User.role === ROLE_MODERATOR) {
selector.$or.push({
'meetingProp.isBreakout': true,
'breakoutProps.parentId': meetingId,
});
}
// Monitor this publication and stop it when user is not a moderator anymore
const comparisonFunc = () => {
const user = Users.findOne({ userId, meetingId }, { fields: { role: 1, userId: 1 } });
const condition = user.role === ROLE_MODERATOR;
if (!condition) {
Logger.info(`conditions aren't filled anymore in publication ${this._name}:
user.role === ROLE_MODERATOR :${condition}, user.role: ${user.role} ROLE_MODERATOR: ${ROLE_MODERATOR}`);
}
return condition;
};
publicationSafeGuard(comparisonFunc, this);
}
const options = {
fields: {
password: false,

View File

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

View File

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

View File

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

View File

@ -2,14 +2,15 @@ import Users from '/imports/api/users';
import { Meteor } from 'meteor/meteor';
import Logger from '/imports/startup/server/logger';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
import { extractCredentials } from '/imports/api/common/server/helpers';
import { extractCredentials, publicationSafeGuard } from '/imports/api/common/server/helpers';
import { check } from 'meteor/check';
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) {
@ -67,6 +68,18 @@ function users(role) {
'breakoutProps.isBreakoutUser': true,
'breakoutProps.parentId': meetingId,
});
// Monitor this publication and stop it when user is not a moderator anymore
const comparisonFunc = () => {
const user = Users.findOne({ userId, meetingId }, { fields: { role: 1, userId: 1 } });
const condition = user.role === ROLE_MODERATOR;
if (!condition) {
Logger.info(`conditions aren't filled anymore in publication ${this._name}:
user.role === ROLE_MODERATOR :${condition}, user.role: ${user.role} ROLE_MODERATOR: ${ROLE_MODERATOR}`);
}
return condition;
};
publicationSafeGuard(comparisonFunc, this);
}
const options = {

View File

@ -8,18 +8,19 @@ import MeetingEnded from '/imports/ui/components/meeting-ended/component';
import LoadingScreen from '/imports/ui/components/loading-screen/component';
import Settings from '/imports/ui/services/settings';
import logger from '/imports/startup/client/logger';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import { Session } from 'meteor/session';
import { FormattedMessage } from 'react-intl';
import { Meteor } from 'meteor/meteor';
import Meetings, { RecordMeetings } from '../../api/meetings';
import { RecordMeetings } from '../../api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import AppService from '/imports/ui/components/app/service';
import Breakouts from '/imports/api/breakouts';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
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';
@ -183,7 +184,7 @@ class Base extends Component {
isMeteorConnected,
subscriptionsReady,
layoutContextDispatch,
layoutContextState,
sidebarContentPanel,
usersVideo,
} = this.props;
const {
@ -191,10 +192,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,
@ -377,7 +374,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;
@ -510,6 +515,4 @@ const BaseContainer = withTracker(() => {
codeError,
usersVideo,
};
})(LayoutContextFunc.withContext(Base));
export default BaseContainer;
})(BaseContainer);

View File

@ -1,17 +1,16 @@
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';
import getFromUserSettings from '/imports/ui/services/users-settings';
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 {...{

View File

@ -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 { isVideoBroadcasting } from '/imports/ui/components/screenshare/service';
import MediaService, {
@ -21,12 +21,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 };

View File

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

View File

@ -1,8 +1,8 @@
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import { makeCall } from '/imports/ui/services/api';
import Meetings from '/imports/api/meetings';
import Breakouts from '/imports/api/breakouts';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
import { getVideoUrl } from '/imports/ui/components/external-video-player/service';
const USER_CONFIG = Meteor.settings.public.user;

View File

@ -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';
@ -426,22 +421,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,
@ -452,11 +431,12 @@ class App extends Component {
shouldShowScreenshare,
shouldShowExternalVideo,
isPresenter,
layoutType,
} = this.props;
return (
<>
{this.renderLayoutManager()}
<LayoutEngine layoutType={layoutType} />
<div
id="layout"
className={styles.layout}

View File

@ -1,20 +1,25 @@
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';
import Auth from '/imports/ui/services/auth';
import AuthTokenValidation from '/imports/api/auth-token-validation';
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import { notify } from '/imports/ui/services/notification';
import CaptionsContainer from '/imports/ui/components/captions/container';
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,9 +56,6 @@ const endMeeting = (code) => {
};
const AppContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const {
actionsbar,
meetingLayout,
@ -64,19 +66,19 @@ const AppContainer = (props) => {
presentationRestoreOnUpdate,
...otherProps
} = props;
const {
input,
output,
layoutType,
deviceType,
} = layoutContextState;
const { sidebarContent, sidebarNavigation, presentation } = input;
const { actionBar: actionsBarStyle, captions: captionsStyle } = output;
const { sidebarNavPanel } = sidebarNavigation;
const { sidebarContentPanel } = sidebarContent;
const sidebarNavigationIsOpen = sidebarNavigation.isOpen;
const sidebarContentIsOpen = sidebarContent.isOpen;
const presentationIsOpen = presentation.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);

View File

@ -1,5 +1,5 @@
import Breakouts from '/imports/api/breakouts';
import Meetings from '/imports/api/meetings';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Settings from '/imports/ui/services/settings';
import Auth from '/imports/ui/services/auth/index';
import deviceInfo from '/imports/utils/deviceInfo';

View File

@ -5,7 +5,7 @@ import deviceInfo from '/imports/utils/deviceInfo';
import browserInfo from '/imports/utils/browserInfo';
import getFromUserSettings from '/imports/ui/services/users-settings';
import AudioModal from './component';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
import AudioError from '/imports/ui/services/audio-manager/error-codes';

View File

@ -5,7 +5,7 @@ import { Session } from 'meteor/session';
import { withModalMounter } from '/imports/ui/components/modal/service';
import { injectIntl, defineMessages } from 'react-intl';
import _ from 'lodash';
import Breakouts from '/imports/api/breakouts';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
import AppService from '/imports/ui/components/app/service';
import { notify } from '/imports/ui/services/notification';
import getFromUserSettings from '/imports/ui/services/users-settings';

View File

@ -1,8 +1,8 @@
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import { debounce, throttle } from 'lodash';
import AudioManager from '/imports/ui/services/audio-manager';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import { makeCall } from '/imports/ui/services/api';
import VoiceUsers from '/imports/api/voice-users';
import logger from '/imports/startup/client/logger';

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Breakouts from '/imports/api/breakouts';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
import Auth from '/imports/ui/services/auth';
import { makeCall } from '/imports/ui/services/api';
import breakoutService from '/imports/ui/components/breakout-room/service';

View File

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

View File

@ -1,8 +1,9 @@
import Breakouts from '/imports/api/breakouts';
import Meetings, { MeetingTimeRemaining } from '/imports/api/meetings';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
import { MeetingTimeRemaining } from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import { makeCall } from '/imports/ui/services/api';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import UserListService from '/imports/ui/components/user-list/service';
import fp from 'lodash/fp';

View File

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

View File

@ -1,6 +1,6 @@
import _ from 'lodash';
import Captions from '/imports/api/captions';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import { makeCall } from '/imports/ui/services/api';
import { Meteor } from 'meteor/meteor';

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
import ChatDropdown from './component';

View File

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

View File

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

View File

@ -2,8 +2,8 @@ import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import { UsersTyping } from '/imports/api/group-chat-msg';
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import TypingIndicator from './component';
const CHAT_CONFIG = Meteor.settings.public.chat;

View File

@ -1,5 +1,5 @@
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import GroupChat from '/imports/api/group-chat';
import Auth from '/imports/ui/services/auth';
import UnreadMessages from '/imports/ui/services/unread-messages';

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import React, {
useReducer,
} from 'react';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import Storage from '/imports/ui/services/storage/session';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';

View File

@ -1,5 +1,6 @@
import { useContext, useEffect } from 'react';
import Users from '/imports/api/users';
import { CurrentUser } from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/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 +9,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 +22,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 +30,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 +56,18 @@ const Adapter = () => {
});
},
});
CurrentUserCursor.observe({
added: (obj) => {
ChatLogger.debug('usersAdapter::observe::current-user::added', obj);
dispatch({
type: ACTIONS.ADDED,
value: {
user: obj,
},
});
},
});
}, []);
return null;

View File

@ -1,6 +1,6 @@
import { defineMessages } from 'react-intl';
import ConnectionStatus from '/imports/api/connection-status';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import UsersPersistentData from '/imports/api/users-persistent-data';
import Auth from '/imports/ui/services/auth';
import Settings from '/imports/ui/services/settings';

View File

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

View File

@ -1,6 +1,6 @@
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
const getMeetingTitle = () => {
const meeting = Meetings.findOne({

View File

@ -1,19 +1,18 @@
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';
import getFromUserSettings from '/imports/ui/services/users-settings';
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
{

View File

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

View File

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

View File

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

View File

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

View File

@ -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,74 +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: {
isOpen: input.presentation.isOpen,
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: input.sidebarNavigation.isOpen || false,
},
@ -109,177 +110,23 @@ class PresentationFocusLayout extends Component {
isOpen: false,
},
presentation: {
isOpen: input.presentation.isOpen,
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 { presentation } = input;
const { isOpen } = presentation;
const calculatesSidebarContentHeight = () => {
const { isOpen } = presentationInput;
const {
navBarHeight,
sidebarContentMinHeight,
@ -287,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 && isOpen) {
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;
}
@ -312,143 +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 { presentation } = input;
const { isOpen } = presentation;
const cameraDockBounds = {};
) => {
const { baseCameraDockBounds } = props;
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
if (input.cameraDock.numCameras > 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;
}
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
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;
} else {
let cameraDockHeight = 0;
// do not proceed if using values from LayoutEngine
if (Object.keys(baseBounds).length > 0) {
return baseBounds;
}
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;
}
}
const { cameraDockMinHeight } = DEFAULT_VALUES;
const cameraDockBounds = {};
let cameraDockHeight = 0;
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;
@ -462,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,
@ -509,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,
@ -522,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,
@ -546,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,
@ -555,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,
},
});
@ -574,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,
@ -584,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,
},
});
@ -597,7 +372,7 @@ class PresentationFocusLayout extends Component {
value: {
top: false,
right: !isRTL,
bottom: input.cameraDock.numCameras > 0,
bottom: cameraDockInput.numCameras > 0,
left: isRTL,
},
});
@ -613,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,
@ -638,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,
@ -672,13 +447,9 @@ class PresentationFocusLayout extends Component {
right: mediaBounds.right,
},
});
}
};
render() {
return (
<></>
);
}
}
return null;
};
export default LayoutContextFunc.withConsumer(PresentationFocusLayout);
export default PresentationFocusLayout;

View File

@ -1,28 +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, CAMERADOCK_POSITION,
} 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,
@ -32,70 +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: {
isOpen: input.presentation.isOpen,
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,
@ -111,310 +108,89 @@ class SmartLayout extends Component {
isOpen: false,
},
presentation: {
isOpen: input.presentation.isOpen,
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;
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_TOP;
} 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;
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_LEFT;
// button size in vertical position
cameraDockBounds.height -= 20;
} 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.position = CAMERADOCK_POSITION.CONTENT_TOP;
}
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 {
@ -438,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;
@ -470,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 {
@ -517,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;
@ -548,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,
@ -561,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,
@ -585,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,
@ -594,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,
},
});
@ -613,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,
@ -621,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,
},
});
@ -650,7 +424,7 @@ 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,
@ -676,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,
@ -710,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;

View File

@ -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,78 +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: {
isOpen: input.presentation.isOpen,
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: input.sidebarNavigation.isOpen || false,
@ -114,207 +119,52 @@ class VideoFocusLayout extends Component {
isOpen: false,
},
presentation: {
isOpen: input.presentation.isOpen,
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;
}
@ -324,130 +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, presentation } = input;
const { isOpen } = presentation;
const { numCameras } = cameraDock;
const { navBarHeight } = DEFAULT_VALUES;
const cameraDockBounds = {};
if (numCameras > 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 (!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;
} else {
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;
}
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;
@ -462,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;
@ -477,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,
@ -533,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,
@ -557,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,
@ -566,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,
},
});
@ -585,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,
@ -595,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,
},
});
@ -624,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,
@ -649,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,
},
});
@ -694,11 +466,9 @@ class VideoFocusLayout extends Component {
right: isRTL ? mediaBounds.right : null,
},
});
}
};
render() {
return <></>;
}
}
return null;
};
export default LayoutContextFunc.withConsumer(VideoFocusLayout);
export default VideoFocusLayout;

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { withModalMounter } from '/imports/ui/components/modal/service';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import LockViewersComponent from './component';
import { updateLockSettings, updateWebcamsOnlyForModerator } from './service';

View File

@ -1,5 +1,5 @@
import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import { LockStruct } from './context';
import { withUsersConsumer } from '/imports/ui/components/components-data/users-context/context';

View File

@ -1,5 +1,5 @@
import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import LockViewersNotifyComponent from './component';

View File

@ -2,7 +2,7 @@ import Presentations from '/imports/api/presentations';
import { isVideoBroadcasting } from '/imports/ui/components/screenshare/service';
import { getVideoUrl } from '/imports/ui/components/external-video-player/service';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Settings from '/imports/ui/services/settings';
import getFromUserSettings from '/imports/ui/services/users-settings';
import { ACTIONS } from '../layout/enums';

View File

@ -11,8 +11,8 @@ import logoutRouteHandler from '/imports/utils/logoutRouteHandler';
import Rating from './rating/component';
import { styles } from './styles';
import logger from '/imports/startup/client/logger';
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import AudioManager from '/imports/ui/services/audio-manager';
import { meetingIsBreakout } from '/imports/ui/components/app/service';

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings';
import Users from '/imports/api/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import { withModalMounter } from '/imports/ui/components/modal/service';
import { makeCall } from '/imports/ui/services/api';

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import getFromUserSettings from '/imports/ui/services/users-settings';
import userListService from '/imports/ui/components/user-list/service';
@ -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] },

View File

@ -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';
@ -6,7 +6,7 @@ import { debounce } from 'lodash';
import TalkingIndicator from './component';
import { makeCall } from '/imports/ui/services/api';
import { meetingIsBreakout } from '/imports/ui/components/app/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_INDICATORS_MAX = 8;
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 (

View File

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

View File

@ -1,5 +1,5 @@
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Note from '/imports/api/note';
import { makeCall } from '/imports/ui/services/api';
import Auth from '/imports/ui/services/auth';

View File

@ -1,13 +1,14 @@
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 { MeetingTimeRemaining } from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/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 +77,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(() => {

View File

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

View File

@ -1,6 +1,6 @@
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/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,

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import PollingService from './service';
import PollService from '/imports/ui/components/poll/service';

View File

@ -5,34 +5,40 @@ import MediaService, {
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';
import PresentationToolbarService from './presentation-toolbar/service';
import { UsersContext } from '../components-data/users-context/context';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/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);

View File

@ -1,5 +1,5 @@
import Cursor from '/imports/ui/components/cursor/service';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
const getCurrentCursor = (cursorId) => {
const cursor = Cursor.findOne({ _id: cursorId });

View File

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

View File

@ -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';
@ -11,16 +11,16 @@ import {
isGloballyBroadcasting,
} from './service';
import ScreenshareComponent from './component';
import LayoutContext from '../layout/context';
import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../layout/context';
import getFromUserSettings from '/imports/ui/services/users-settings';
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()) {

View File

@ -4,7 +4,7 @@ import BridgeService from '/imports/api/screenshare/client/bridge/service';
import Settings from '/imports/ui/services/settings';
import logger from '/imports/startup/client/logger';
import { stopWatching } from '/imports/ui/components/external-video-player/service';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import UserListService from '/imports/ui/components/user-list/service';
import AudioService from '/imports/ui/components/audio/service';

View File

@ -1,8 +1,8 @@
import React, { useContext } from 'react';
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import SettingsService from '/imports/ui/services/settings';
import Settings from './component';
import LayoutContext from '../layout/context';
import { layoutDispatch } from '../layout/context';
import {
getUserRoles,
@ -12,8 +12,7 @@ import {
} from './service';
const SettingsContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextDispatch } = layoutContext;
const layoutContextDispatch = layoutDispatch();
return <Settings {...props} layoutContextDispatch={layoutContextDispatch} />;
};

View File

@ -1,4 +1,4 @@
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Auth from '/imports/ui/services/auth';
import Settings from '/imports/ui/services/settings';
import { notify } from '/imports/ui/services/notification';

View File

@ -1,25 +1,22 @@
import React from 'react';
import SidebarContent from './component';
import { LayoutContextFunc } from '../layout/context';
import { layoutSelectInput, layoutSelectOutput, layoutDispatch } from '../layout/context';
const SidebarContentContainer = (props) => {
const { layoutContextState, layoutContextDispatch } = props;
const {
output, input,
} = layoutContextState;
const { sidebarContent: sidebarContentInput } = input;
const SidebarContentContainer = () => {
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
const sidebarContentOutput = layoutSelectOutput((i) => i.sidebarContent);
const layoutContextDispatch = layoutDispatch();
const { sidebarContentPanel } = sidebarContentInput;
const { sidebarContent } = output;
if (sidebarContent.display === false) return null;
if (sidebarContentOutput.display === false) return null;
return (
<SidebarContent
{...sidebarContent}
{...sidebarContentOutput}
contextDispatch={layoutContextDispatch}
sidebarContentPanel={sidebarContentPanel}
/>
);
};
export default LayoutContextFunc.withConsumer(SidebarContentContainer);
export default SidebarContentContainer;

View File

@ -1,21 +1,19 @@
import React from 'react';
import { LayoutContextFunc } from '../layout/context';
import { layoutDispatch, layoutSelectOutput } from '../layout/context';
import SidebarNavigation from './component';
const SidebarNavigationContainer = (props) => {
const { layoutContextState, layoutContextDispatch, openPanel } = props;
const { output } = layoutContextState;
const { sidebarNavigation } = output;
const SidebarNavigationContainer = () => {
const sidebarNavigation = layoutSelectOutput((i) => i.sidebarNavigation);
const layoutContextDispatch = layoutDispatch();
if (sidebarNavigation.display === false) return null;
return (
<SidebarNavigation
{...sidebarNavigation}
openPanel={openPanel}
contextDispatch={layoutContextDispatch}
/>
);
};
export default LayoutContextFunc.withConsumer(SidebarNavigationContainer);
export default SidebarNavigationContainer;

View File

@ -49,10 +49,10 @@ class StatusNotifier extends Component {
componentDidUpdate(prevProps) {
const {
emojiUsers, raiseHandAudioAlert, raiseHandPushAlert, status, isViewer,
emojiUsers, raiseHandAudioAlert, raiseHandPushAlert, status, isViewer, isPresenter,
} = this.props;
if (isViewer) {
if (isViewer && !isPresenter) {
if (this.statusNotifierId) toast.dismiss(this.statusNotifierId);
return false;
}

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import Settings from '/imports/ui/services/settings';
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
import { makeCall } from '/imports/ui/services/api';
@ -14,10 +14,12 @@ const StatusNotifierContainer = (props) => {
const { users } = usingUsersContext;
const currentUser = users[Auth.meetingID][Auth.userID];
const isViewer = currentUser.role === ROLE_VIEWER;
const isPresenter = currentUser.presenter;
return (
<StatusNotifier {...{
...props,
isViewer,
isPresenter,
}}
/>
);

View File

@ -4,10 +4,13 @@ import Auth from '/imports/ui/services/auth';
import logger from '/imports/startup/client/logger';
import GroupChat from '/imports/api/group-chat';
import Annotations from '/imports/api/annotations';
import Users from '/imports/api/users';
import Users, { localUsersSync } from '/imports/ui/local-collections/users-collection/users';
import { localBreakoutsSync } from '/imports/ui/local-collections/breakouts-collection/breakouts';
import { localGuestUsersSync } from '/imports/ui/local-collections/guest-users-collection/guest-users';
import { localMeetingsSync } from '/imports/ui/local-collections/meetings-collection/meetings';
import AnnotationsTextService from '/imports/ui/components/whiteboard/annotations/text/service';
import { Annotations as AnnotationsLocal } from '/imports/ui/components/whiteboard/service';
import SubscriptionRegistry, { subscriptionReactivity } from '../../services/subscription-registry/subscriptionRegistry';
const CHAT_CONFIG = Meteor.settings.public.chat;
const CHAT_ENABLED = CHAT_CONFIG.enabled;
@ -19,12 +22,14 @@ const SUBSCRIPTIONS = [
'voiceUsers', 'whiteboard-multi-user', 'screenshare', 'group-chat',
'presentation-pods', 'users-settings', 'guestUser', 'users-infos', 'note', 'meeting-time-remaining',
'local-settings', 'users-typing', 'record-meetings', 'video-streams',
'connection-status', 'voice-call-states', 'external-video-meetings',
'connection-status', 'voice-call-states', 'external-video-meetings', 'breakouts',
];
const EVENT_NAME = 'bbb-group-chat-messages-subscription-has-stoppped';
const EVENT_NAME_SUBSCRIPTION_READY = 'bbb-group-chat-messages-subscriptions-ready';
let oldRole = '';
class Subscriptions extends Component {
componentDidUpdate() {
const { subscriptionsReady } = this.props;
@ -41,7 +46,6 @@ class Subscriptions extends Component {
}
}
export default withTracker(() => {
const { credentials } = Auth;
const { meetingId, requesterUserId } = credentials;
@ -51,8 +55,6 @@ export default withTracker(() => {
};
}
const currentUser = Users.findOne({ intId: requesterUserId }, { fields: { role: 1 } });
const subscriptionErrorHandler = {
onError: (error) => {
logger.error({
@ -63,24 +65,47 @@ export default withTracker(() => {
},
};
let subscriptionsHandlers = SUBSCRIPTIONS.map((name) => {
if ((!TYPING_INDICATOR_ENABLED && name.indexOf('typing') !== -1)
|| (!CHAT_ENABLED && name.indexOf('chat') !== -1)) return;
const currentUser = Users.findOne({ intId: requesterUserId }, { fields: { role: 1 } });
return Meteor.subscribe(name, subscriptionErrorHandler);
let subscriptionsHandlers = SUBSCRIPTIONS.map((name) => {
let subscriptionHandlers = subscriptionErrorHandler;
if ((!TYPING_INDICATOR_ENABLED && name.indexOf('typing') !== -1)
|| (!CHAT_ENABLED && name.indexOf('chat') !== -1)) return null;
if (name === 'users') {
subscriptionHandlers = {
...subscriptionHandlers,
onStop: () => {
const event = new Event(EVENT_NAME);
window.dispatchEvent(event);
},
};
}
return SubscriptionRegistry.createSubscription(name, subscriptionHandlers);
});
if (currentUser) {
subscriptionsHandlers.push(Meteor.subscribe('meetings', currentUser.role, subscriptionErrorHandler));
subscriptionsHandlers.push(Meteor.subscribe('users', currentUser.role, {
...subscriptionErrorHandler,
onStop: () => {
const event = new Event(EVENT_NAME);
window.dispatchEvent(event);
},
}));
subscriptionsHandlers.push(Meteor.subscribe('breakouts', currentUser.role, subscriptionErrorHandler));
subscriptionsHandlers.push(Meteor.subscribe('guestUser', currentUser.role, subscriptionErrorHandler));
if (currentUser && (oldRole !== currentUser?.role)) {
// stop subscription from the client-side as the server-side only watch moderators
if (oldRole === 'VIEWER' && currentUser?.role === 'MODERATOR') {
// let this withTracker re-execute when a subscription is stopped
subscriptionReactivity.depend();
// Prevent data being removed by subscription stop
localBreakoutsSync.setIgnoreDeletes(true);
localGuestUsersSync.setIgnoreDeletes(true);
localMeetingsSync.setIgnoreDeletes(true);
localUsersSync.setIgnoreDeletes(true);
// stop role dependent subscriptions
[
SubscriptionRegistry.getSubscription('meetings'),
SubscriptionRegistry.getSubscription('users'),
SubscriptionRegistry.getSubscription('breakouts'),
SubscriptionRegistry.getSubscription('guestUser'),
].forEach((item) => {
if (item) item.stop();
});
}
oldRole = currentUser?.role;
}
const annotationsHandler = Meteor.subscribe('annotations', {

View File

@ -1,12 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import ChatListItem from './component';
import LayoutContext from '../../layout/context';
import { layoutSelect, layoutSelectInput, layoutDispatch } from '../../layout/context';
const ChatListItemContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { input, idChatOpen } = layoutContextState;
const { sidebarContent } = input;
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
const idChatOpen = layoutSelect((i) => i.idChatOpen);
const layoutContextDispatch = layoutDispatch();
const { sidebarContentPanel } = sidebarContent;
const sidebarContentIsOpen = sidebarContent.isOpen;

View File

@ -1,8 +1,8 @@
import Users from '/imports/api/users';
import Users from '/imports/ui/local-collections/users-collection/users';
import VoiceUsers from '/imports/api/voice-users';
import GroupChat from '/imports/api/group-chat';
import Breakouts from '/imports/api/breakouts/';
import Meetings from '/imports/api/meetings';
import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import Storage from '/imports/ui/services/storage/session';
import { EMOJI_STATUSES } from '/imports/utils/statuses';

View File

@ -44,6 +44,8 @@ class UserContent extends PureComponent {
forcePollOpen,
hasBreakoutRoom,
pendingUsers,
isWaitingRoomEnabled,
isGuestLobbyMessageEnabled,
requestUserInformation,
currentClosedChats,
sidebarContentPanel,
@ -51,6 +53,9 @@ class UserContent extends PureComponent {
startedChats,
} = this.props;
const showWaitingRoom = (isGuestLobbyMessageEnabled && isWaitingRoomEnabled)
|| pendingUsers.length > 0;
return (
<div
data-test="userListContent"
@ -82,7 +87,7 @@ class UserContent extends PureComponent {
intl,
}}
/>
{pendingUsers.length > 0 && currentUser.role === ROLE_MODERATOR
{showWaitingRoom && currentUser.role === ROLE_MODERATOR
? (
<WaitingUsers
{...{

View File

@ -4,19 +4,20 @@ import { Session } from 'meteor/session';
import Auth from '/imports/ui/services/auth';
import Storage from '/imports/ui/services/storage/session';
import UserContent from './component';
import GuestUsers from '/imports/api/guest-users/';
import LayoutContext from '../../layout/context';
import GuestUsers from '/imports/ui/local-collections/guest-users-collection/guest-users';
import { layoutSelectInput, layoutDispatch } from '../../layout/context';
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
import WaitingUsersService from '/imports/ui/components/waiting-users/service';
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
const STARTED_CHAT_LIST_KEY = 'startedChatList';
const UserContentContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { input } = layoutContextState;
const { sidebarContent } = input;
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
const layoutContextDispatch = layoutDispatch();
const { sidebarContentPanel } = sidebarContent;
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;
const currentUser = {
@ -25,11 +26,14 @@ const UserContentContainer = (props) => {
locked: users[Auth.meetingID][Auth.userID].locked,
role: users[Auth.meetingID][Auth.userID].role,
};
const { isGuestLobbyMessageEnabled } = WaitingUsersService;
return (
<UserContent
{...{
layoutContextDispatch,
sidebarContentPanel,
isGuestLobbyMessageEnabled,
...props,
}}
currentUser={currentUser}
@ -47,4 +51,5 @@ export default withTracker(() => ({
approved: false,
denied: false,
}).fetch(),
isWaitingRoomEnabled: WaitingUsersService.isWaitingRoomEnabled(),
}))(UserContentContainer);

View File

@ -1,15 +1,14 @@
import React, { useContext } from 'react';
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import UserCaptionsItem from './component';
import CaptionsService from '/imports/ui/components/captions/service';
import LayoutContext from '../../../layout/context';
import { layoutSelectInput, layoutDispatch } from '../../../layout/context';
const UserCaptionsItemContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { input } = layoutContextState;
const { sidebarContent } = input;
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
const { sidebarContentPanel } = sidebarContent;
const layoutContextDispatch = layoutDispatch();
return <UserCaptionsItem {...{ sidebarContentPanel, layoutContextDispatch, ...props }} />;
};

View File

@ -1,16 +1,14 @@
import React, { useContext } from 'react';
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import NoteService from '/imports/ui/components/note/service';
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
import UserNotes from './component';
import LayoutContext from '../../../layout/context';
import { layoutSelectInput, layoutDispatch } from '../../../layout/context';
const UserNotesContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { input } = layoutContextState;
const { sidebarContent } = input;
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
const { sidebarContentPanel } = sidebarContent;
const layoutContextDispatch = layoutDispatch();
return <UserNotes {...{ layoutContextDispatch, sidebarContentPanel, ...props }} />;
};

View File

@ -1,15 +1,15 @@
import React, { useContext } from 'react';
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import BreakoutService from '/imports/ui/components/breakout-room/service';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import UserListItem from './component';
import UserListService from '/imports/ui/components/user-list/service';
import LayoutContext from '../../../../layout/context';
import { layoutDispatch } from '../../../../layout/context';
const UserListItemContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextDispatch } = layoutContext;
const layoutContextDispatch = layoutDispatch();
return <UserListItem {...{ layoutContextDispatch, ...props }} />;
};
const isMe = (intId) => intId === Auth.userID;

View File

@ -1,7 +1,7 @@
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import ActionsBarService from '/imports/ui/components/actions-bar/service';
import LearningDashboardService from '/imports/ui/components/learning-dashboard/service';
import UserListService from '/imports/ui/components/user-list/service';

View File

@ -60,11 +60,13 @@ const WaitingUsers = ({
>
<Icon iconName="user" />
<span>{intl.formatMessage(intlMessages.title)}</span>
<div className={styles.unreadMessages}>
<div className={styles.unreadMessagesText}>
{pendingUsers.length}
{pendingUsers.length > 0 && (
<div className={styles.unreadMessages}>
<div className={styles.unreadMessagesText}>
{pendingUsers.length}
</div>
</div>
</div>
)}
</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
import React from 'react';
import { withModalMounter } from '/imports/ui/components/modal/service';
import { withTracker } from 'meteor/react-meteor-data';
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import Service from './service';
import VideoPreview from './component';

View File

@ -1,5 +1,5 @@
import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings/';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users/';
import VideoStreams from '/imports/api/video-streams';

View File

@ -2,8 +2,8 @@ import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import Settings from '/imports/ui/services/settings';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
import Users from '/imports/api/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Users from '/imports/ui/local-collections/users-collection/users';
import VideoStreams from '/imports/api/video-streams';
import UserListService from '/imports/ui/components/user-list/service';
import { makeCall } from '/imports/ui/services/api';

View File

@ -1,14 +1,13 @@
import React, { useContext } from 'react';
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import VideoList from '/imports/ui/components/video-provider/video-list/component';
import VideoService from '/imports/ui/components/video-provider/service';
import LayoutContext from '../../layout/context';
import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../../layout/context';
const VideoListContainer = ({ children, ...props }) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { layoutType, output } = layoutContextState;
const { cameraDock } = output;
const layoutType = layoutSelect((i) => i.layoutType);
const cameraDock = layoutSelectOutput((i) => i.cameraDock);
const layoutContextDispatch = layoutDispatch();
const { streams } = props;
return (

View File

@ -1,17 +1,17 @@
import React, { useContext } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data';
import VoiceUsers from '/imports/api/voice-users/';
import VideoListItem from './component';
import LayoutContext from '/imports/ui/components/layout/context';
import { layoutSelect, layoutDispatch } from '/imports/ui/components/layout/context';
const VideoListItemContainer = (props) => {
const { cameraId } = props;
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { fullscreen } = layoutContextState;
const fullscreen = layoutSelect((i) => i.fullscreen);
const { element } = fullscreen;
const isFullscreenContext = (element === cameraId);
const layoutContextDispatch = layoutDispatch();
return (
<VideoListItem

View File

@ -1,22 +1,20 @@
import React, { useContext } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import GuestUsers from '/imports/api/guest-users/';
import GuestUsers from '/imports/ui/local-collections/guest-users-collection/guest-users';
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
import WaitingComponent from './component';
import LayoutContext from '../../layout/context';
import { layoutSelectInput, layoutDispatch } from '../../layout/context';
import { PANELS } from '../../layout/enums';
const USER_CONFIG = Meteor.settings.public.user;
const ROLE_MODERATOR = USER_CONFIG.role_moderator;
const WaitingContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextState, layoutContextDispatch } = layoutContext;
const { input } = layoutContextState;
const { sidebarContent } = input;
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
const { sidebarContentPanel } = sidebarContent;
const managementPanelIsOpen = sidebarContentPanel === PANELS.WAITING_USERS;
const layoutContextDispatch = layoutDispatch();
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;

View File

@ -45,6 +45,10 @@ const intlMessages = defineMessages({
id: 'app.userList.guest.pendingGuestUsers',
description: 'Title for the waiting users',
},
noPendingUsers: {
id: 'app.userList.guest.noPendingUsers',
description: 'Label for no users waiting',
},
rememberChoice: {
id: 'app.userList.guest.rememberChoice',
description: 'Remember label for checkbox',
@ -120,6 +124,14 @@ const renderGuestUserItem = (
</div>
);
const renderNoUserWaitingItem = (message) => (
<div className={styles.pendingUsers}>
<p className={styles.noPendingUsers}>
{message}
</p>
</div>
);
const renderPendingUsers = (message, usersArray, action, intl) => {
if (!usersArray.length) return null;
return (
@ -147,25 +159,6 @@ const renderPendingUsers = (message, usersArray, action, intl) => {
const WaitingUsers = (props) => {
const [rememberChoice, setRememberChoice] = useState(false);
useEffect(() => {
const {
authenticatedUsers,
guestUsers,
layoutContextDispatch,
} = props;
if (!authenticatedUsers.length && !guestUsers.length) {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
}
});
const {
intl,
authenticatedUsers,
@ -180,6 +173,28 @@ const WaitingUsers = (props) => {
allowRememberChoice,
} = props;
const existPendingUsers = authenticatedUsers.length > 0 || guestUsers.length > 0;
const closePanel = () => {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
};
useEffect(() => {
const {
isWaitingRoomEnabled,
} = props;
if (!isWaitingRoomEnabled && !existPendingUsers) {
closePanel();
}
});
const onCheckBoxChange = (e) => {
const { checked } = e.target;
setRememberChoice(checked);
@ -189,6 +204,7 @@ const WaitingUsers = (props) => {
if (shouldExecutePolicy) {
changeGuestPolicy(policyRule);
}
closePanel();
return cb();
};
@ -251,16 +267,7 @@ const WaitingUsers = (props) => {
className={styles.title}
>
<Button
onClick={() => {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
}}
onClick={() => closePanel()}
label={intl.formatMessage(intlMessages.title)}
icon="left_arrow"
className={styles.hideBtn}
@ -287,6 +294,7 @@ const WaitingUsers = (props) => {
</p>
</div>
) : null}
{existPendingUsers && (
<div>
<div>
<p className={styles.mainTitle}>{intl.formatMessage(intlMessages.optionTitle)}</p>
@ -307,6 +315,7 @@ const WaitingUsers = (props) => {
</div>
) : null}
</div>
)}
{renderPendingUsers(
intl.formatMessage(intlMessages.pendingUsers,
{ 0: authenticatedUsers.length }),
@ -321,6 +330,9 @@ const WaitingUsers = (props) => {
guestUsersCall,
intl,
)}
{!existPendingUsers && (
renderNoUserWaitingItem(intl.formatMessage(intlMessages.noPendingUsers))
)}
</div>
);
};

View File

@ -1,15 +1,15 @@
import React, { useContext } from 'react';
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import GuestUsers from '/imports/api/guest-users/';
import Meetings from '/imports/api/meetings';
import GuestUsers from '/imports/ui/local-collections/guest-users-collection/guest-users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Service from './service';
import WaitingComponent from './component';
import LayoutContext from '../layout/context';
import { layoutDispatch } from '../layout/context';
const WaitingContainer = (props) => {
const layoutContext = useContext(LayoutContext);
const { layoutContextDispatch } = layoutContext;
const layoutContextDispatch = layoutDispatch();
return <WaitingComponent {...{ layoutContextDispatch, ...props }} />;
};
@ -37,6 +37,7 @@ export default withTracker(() => {
guestUsers,
authenticatedUsers,
guestUsersCall: Service.guestUsersCall,
isWaitingRoomEnabled: Service.isWaitingRoomEnabled(),
changeGuestPolicy: Service.changeGuestPolicy,
isGuestLobbyMessageEnabled: Service.isGuestLobbyMessageEnabled,
setGuestLobbyMessage: Service.setGuestLobbyMessage,

View File

@ -1,10 +1,10 @@
import Meetings from '/imports/api/meetings';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import Auth from '/imports/ui/services/auth';
import { makeCall } from '/imports/ui/services/api';
const guestUsersCall = (guestsArray, status) => makeCall('allowPendingUsers', guestsArray, status);
const changeGuestPolicy = policyRule => makeCall('changeGuestPolicy', policyRule);
const changeGuestPolicy = (policyRule) => makeCall('changeGuestPolicy', policyRule);
const getGuestPolicy = () => {
const meeting = Meetings.findOne(
@ -15,6 +15,8 @@ const getGuestPolicy = () => {
return meeting.usersProp.guestPolicy;
};
const isWaitingRoomEnabled = () => getGuestPolicy() === 'ASK_MODERATOR';
const isGuestLobbyMessageEnabled = Meteor.settings.public.app.enableGuestLobbyMessage;
// We use the dynamicGuestPolicy rule for allowing the rememberChoice checkbox
@ -37,6 +39,7 @@ export default {
guestUsersCall,
changeGuestPolicy,
getGuestPolicy,
isWaitingRoomEnabled,
isGuestLobbyMessageEnabled,
getGuestLobbyMessage,
setGuestLobbyMessage,

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