Merge remote-tracking branch 'upstream/master' into Remove-finally
This commit is contained in:
commit
77417c63f7
@ -133,10 +133,11 @@ muteButton.id = 'muteButton';
|
||||
|
||||
function getInitialState() {
|
||||
document.getElementById('client-content').contentWindow.postMessage('c_recording_status', '*');
|
||||
document.getElementById('client-content').contentWindow.postMessage('c_mute_status', '*');
|
||||
document.getElementById('client-content').contentWindow.postMessage('get_audio_joined_status', '*');
|
||||
}
|
||||
|
||||
function handleMessage(e) {
|
||||
let neverJoinedAudio = true;
|
||||
switch (e) {
|
||||
case 'readyToConnect': {
|
||||
// get initial state
|
||||
@ -160,11 +161,18 @@ function handleMessage(e) {
|
||||
case 'notInAudio': {
|
||||
muteButton.innerHTML = 'Not in audio';
|
||||
document.getElementById('muteButton').disabled = true;
|
||||
if (neverJoinedAudio) {
|
||||
// poll every 1 sec to check if we joined audio
|
||||
setTimeout(function(){
|
||||
document.getElementById('client-content').contentWindow.postMessage('get_audio_joined_status', '*');
|
||||
}, 1000);}
|
||||
break;
|
||||
}
|
||||
case 'joinedAudio': {
|
||||
neverJoinedAudio = false;
|
||||
muteButton.innerHTML = '';
|
||||
document.getElementById('muteButton').disabled = false; getInitialState();
|
||||
document.getElementById('muteButton').disabled = false;
|
||||
document.getElementById('client-content').contentWindow.postMessage('c_mute_status', '*');
|
||||
break;
|
||||
}
|
||||
default: console.log('neither', { e });
|
||||
|
4
bigbluebutton-html5/imports/api/meetings/server/methods.js
Normal file → Executable file
4
bigbluebutton-html5/imports/api/meetings/server/methods.js
Normal file → Executable file
@ -2,9 +2,13 @@ import { Meteor } from 'meteor/meteor';
|
||||
import endMeeting from './methods/endMeeting';
|
||||
import toggleRecording from './methods/toggleRecording';
|
||||
import transferUser from './methods/transferUser';
|
||||
import toggleLockSettings from './methods/toggleLockSettings';
|
||||
import toggleWebcamsOnlyForModerator from './methods/toggleWebcamsOnlyForModerator';
|
||||
|
||||
Meteor.methods({
|
||||
endMeeting,
|
||||
toggleRecording,
|
||||
toggleLockSettings,
|
||||
transferUser,
|
||||
toggleWebcamsOnlyForModerator,
|
||||
});
|
||||
|
@ -0,0 +1,37 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
|
||||
export default function toggleLockSettings(credentials, meeting) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ChangeLockSettingsInMeetingCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(meeting.lockSettingsProp, {
|
||||
disableCam: Boolean,
|
||||
disableMic: Boolean,
|
||||
disablePrivChat: Boolean,
|
||||
disablePubChat: Boolean,
|
||||
lockedLayout: Boolean,
|
||||
lockOnJoin: Boolean,
|
||||
lockOnJoinConfigurable: Boolean,
|
||||
setBy: String,
|
||||
});
|
||||
|
||||
const payload = {
|
||||
disableCam: meeting.lockSettingsProp.disableCam,
|
||||
disableMic: meeting.lockSettingsProp.disableMic,
|
||||
disablePrivChat: meeting.lockSettingsProp.disablePrivChat,
|
||||
disablePubChat: meeting.lockSettingsProp.disablePubChat,
|
||||
lockedLayout: meeting.lockSettingsProp.lockedLayout,
|
||||
lockOnJoin: meeting.lockSettingsProp.lockOnJoin,
|
||||
lockOnJoinConfigurable: meeting.lockSettingsProp.lockOnJoinConfigurable,
|
||||
setBy: requesterUserId,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
|
||||
export default function toggleWebcamsOnlyForModerator(credentials, meeting) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'UpdateWebcamsOnlyForModeratorCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(meeting.usersProp.webcamsOnlyForModerator, Boolean);
|
||||
|
||||
const payload = {
|
||||
webcamsOnlyForModerator: meeting.usersProp.webcamsOnlyForModerator,
|
||||
setBy: requesterUserId,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
@ -69,10 +69,22 @@ export default function addMeeting(meeting) {
|
||||
meetingId,
|
||||
};
|
||||
|
||||
const lockSettingsProp = {
|
||||
disableCam: false,
|
||||
disableMic: false,
|
||||
disablePrivChat: false,
|
||||
disablePubChat: false,
|
||||
lockOnJoin: true,
|
||||
lockOnJoinConfigurable: false,
|
||||
lockedLayout: false,
|
||||
setBy: 'temp',
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
{ meetingId },
|
||||
flat(meeting, { safe: true }),
|
||||
{ lockSettingsProp },
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
import Users from '/imports/api/users';
|
||||
import PresentationPods from '/imports/api/presentation-pods';
|
||||
import changeRole from '/imports/api/users/server/modifiers/changeRole';
|
||||
import assignPresenter from '../methods/assignPresenter';
|
||||
|
||||
export default function handlePresenterAssigned({ body }, meetingId) {
|
||||
export default function handlePresenterAssigned(credentials, meetingId) {
|
||||
const USER_CONFIG = Meteor.settings.public.user;
|
||||
const ROLE_PRESENTER = USER_CONFIG.role_presenter;
|
||||
|
||||
const { body } = credentials;
|
||||
const { presenterId, assignedBy } = body;
|
||||
|
||||
changeRole(ROLE_PRESENTER, true, presenterId, meetingId, assignedBy);
|
||||
@ -15,11 +18,34 @@ export default function handlePresenterAssigned({ body }, meetingId) {
|
||||
presenter: true,
|
||||
};
|
||||
|
||||
const defaultPodSelector = {
|
||||
podId: 'DEFAULT_PRESENTATION_POD',
|
||||
};
|
||||
|
||||
const prevPresenter = Users.findOne(selector);
|
||||
|
||||
// no previous presenters
|
||||
// The below code is responsible for set Meeting presenter to be default pod presenter as well.
|
||||
// It's been handled here because right now akka-apps don't handle all cases scenarios.
|
||||
if (!prevPresenter) {
|
||||
return true;
|
||||
const currentDefaultPodPresenter = PresentationPods.findOne(defaultPodSelector);
|
||||
|
||||
const { currentPresenterId } = currentDefaultPodPresenter;
|
||||
|
||||
const podPresenterCredentials = {
|
||||
meetingId,
|
||||
requesterUserId: assignedBy,
|
||||
};
|
||||
|
||||
if (currentDefaultPodPresenter.currentPresenterId !== '') {
|
||||
const oldPresenter = Users.findOne({ userId: currentPresenterId });
|
||||
|
||||
if (oldPresenter.connectionStatus === 'offline') {
|
||||
return assignPresenter(podPresenterCredentials, presenterId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return assignPresenter(podPresenterCredentials, presenterId);
|
||||
}
|
||||
|
||||
return changeRole(ROLE_PRESENTER, false, prevPresenter.userId, meetingId, assignedBy);
|
||||
|
0
bigbluebutton-html5/imports/startup/server/logger.js
Executable file → Normal file
0
bigbluebutton-html5/imports/startup/server/logger.js
Executable file → Normal file
@ -8,6 +8,7 @@ import Resizable from 're-resizable';
|
||||
import browser from 'browser-detect';
|
||||
import BreakoutRoomContainer from '/imports/ui/components/breakout-room/container';
|
||||
import PollingContainer from '/imports/ui/components/polling/container';
|
||||
import PollContainer from '/imports/ui/components/poll/container';
|
||||
import ToastContainer from '../toast/container';
|
||||
import ModalContainer from '../modal/container';
|
||||
import NotificationsBarContainer from '../notifications-bar/container';
|
||||
@ -16,7 +17,7 @@ import ChatAlertContainer from '../chat/alert/container';
|
||||
import { styles } from './styles';
|
||||
import UserListContainer from '../user-list/container';
|
||||
import ChatContainer from '../chat/container';
|
||||
import PollContainer from '/imports/ui/components/poll/container';
|
||||
|
||||
|
||||
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
||||
const USERLIST_COMPACT_WIDTH = 50;
|
||||
@ -327,7 +328,7 @@ class App extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
userListIsOpen, customStyle, customStyleUrl,
|
||||
userListIsOpen, customStyle, customStyleUrl, micsLocked,
|
||||
} = this.props;
|
||||
const { enableResize } = this.state;
|
||||
|
||||
@ -349,11 +350,11 @@ class App extends Component {
|
||||
</section>
|
||||
<PollingContainer />
|
||||
<ModalContainer />
|
||||
<AudioContainer />
|
||||
{micsLocked ? null : <AudioContainer />}
|
||||
<ToastContainer />
|
||||
<ChatAlertContainer />
|
||||
{ customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null }
|
||||
{ customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null }
|
||||
{customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null}
|
||||
{customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ const AppContainer = (props) => {
|
||||
|
||||
export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => {
|
||||
const currentUser = Users.findOne({ userId: Auth.userID });
|
||||
const meeting = Meetings.findOne({ meetingId: Auth.meetingID });
|
||||
const isMeetingBreakout = meetingIsBreakout();
|
||||
|
||||
if (!currentUser.approved) {
|
||||
@ -117,6 +118,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
||||
pollIsOpen: Session.get('isPollOpen') && Session.get('isUserListOpen'),
|
||||
customStyle: getFromUserSettings('customStyle', false),
|
||||
customStyleUrl: getFromUserSettings('customStyleUrl', false),
|
||||
micsLocked: (currentUser.locked && meeting.lockSettingsProp.disableMic),
|
||||
};
|
||||
})(AppContainer)));
|
||||
|
||||
|
@ -3,6 +3,9 @@ import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { withModalMounter } from '/imports/ui/components/modal/service';
|
||||
import AudioManager from '/imports/ui/services/audio-manager';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import Users from '/imports/api/users/';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import AudioControls from './component';
|
||||
import AudioModalContainer from '../audio-modal/container';
|
||||
import Service from '../service';
|
||||
@ -15,11 +18,12 @@ const processToggleMuteFromOutside = (e) => {
|
||||
makeCall('toggleSelfVoice');
|
||||
break;
|
||||
}
|
||||
case 'get_audio_joined_status': {
|
||||
const audioJoinedState = AudioManager.isConnected ? 'joinedAudio' : 'notInAudio';
|
||||
this.window.parent.postMessage({ response: audioJoinedState }, '*');
|
||||
break;
|
||||
}
|
||||
case 'c_mute_status': {
|
||||
if (!AudioManager.isUsingAudio()) {
|
||||
this.window.parent.postMessage({ response: 'notInAudio' }, '*');
|
||||
return;
|
||||
}
|
||||
const muteState = AudioManager.isMuted ? 'selfMuted' : 'selfUnmuted';
|
||||
this.window.parent.postMessage({ response: muteState }, '*');
|
||||
break;
|
||||
@ -39,6 +43,12 @@ export default withModalMounter(withTracker(({ mountModal }) =>
|
||||
disable: Service.isConnecting() || Service.isHangingUp(),
|
||||
glow: Service.isTalking() && !Service.isMuted(),
|
||||
handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(),
|
||||
handleJoinAudio: () => mountModal(<AudioModalContainer />),
|
||||
handleJoinAudio: () => {
|
||||
const meeting = Meetings.findOne({ meetingId: Auth.meetingID });
|
||||
const currentUser = Users.findOne({ userId: Auth.userID });
|
||||
const micsLocked = (currentUser.locked && meeting.lockSettingsProp.disableMic);
|
||||
|
||||
return micsLocked ? Service.joinListenOnly() : mountModal(<AudioModalContainer />);
|
||||
},
|
||||
handleLeaveAudio: () => Service.exitAudio(),
|
||||
}))(AudioControlsContainer));
|
||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import logoutRouteHandler from '/imports/utils/logoutRouteHandler';
|
||||
import { styles } from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -66,7 +67,7 @@ class ErrorScreen extends React.PureComponent {
|
||||
<div className={styles.content}>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => Session.set('isMeetingEnded', true)}
|
||||
onClick={logoutRouteHandler}
|
||||
label={intl.formatMessage(intlMessages.leave)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -71,6 +71,7 @@ class JoinHandler extends Component {
|
||||
return resp;
|
||||
};
|
||||
|
||||
const setLogoutURL = (url) => Auth.logoutURL = url;
|
||||
const setLogoURL = (resp) => {
|
||||
setCustomLogoUrl(resp.customLogoURL);
|
||||
return resp;
|
||||
@ -94,11 +95,12 @@ class JoinHandler extends Component {
|
||||
.then(response => response.json())
|
||||
.then(({ response }) => response)
|
||||
.then((resp) => {
|
||||
setLogoutURL(resp.logoutURL);
|
||||
if (resp.returncode !== 'FAILED') {
|
||||
logger.info(`User successfully went through main.joinRouteHandler with [${resp}].`);
|
||||
return resolve(resp);
|
||||
}
|
||||
const e = new Error('Session not found');
|
||||
const e = new Error('Session not found');
|
||||
logger.error(`User faced [${e}] on main.joinRouteHandler. Error was:`, JSON.stringify(resp));
|
||||
return reject(e);
|
||||
});
|
||||
|
251
bigbluebutton-html5/imports/ui/components/lock-viewers/component.jsx
Executable file
251
bigbluebutton-html5/imports/ui/components/lock-viewers/component.jsx
Executable file
@ -0,0 +1,251 @@
|
||||
import React, { Component } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Toggle from '/imports/ui/components/switch/component';
|
||||
import cx from 'classnames';
|
||||
import ModalBase from '/imports/ui/components/modal/base/component';
|
||||
import { styles } from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
lockViewersTitle: {
|
||||
id: 'app.lock-viewers.title',
|
||||
description: 'lock-viewers title',
|
||||
},
|
||||
closeLabel: {
|
||||
id: 'app.shortcut-help.closeLabel',
|
||||
description: 'label for close button',
|
||||
},
|
||||
closeDesc: {
|
||||
id: 'app.shortcut-help.closeDesc',
|
||||
description: 'description for close button',
|
||||
},
|
||||
lockViewersDescription: {
|
||||
id: 'app.lock-viewers.description',
|
||||
description: 'description for lock viewers feature',
|
||||
},
|
||||
featuresLable: {
|
||||
id: 'app.lock-viewers.featuresLable',
|
||||
description: 'features label',
|
||||
},
|
||||
lockStatusLabel: {
|
||||
id: 'app.lock-viewers.lockStatusLabel',
|
||||
description: 'description for close button',
|
||||
},
|
||||
webcamLabel: {
|
||||
id: 'app.lock-viewers.webcamLabel',
|
||||
description: 'description for close button',
|
||||
},
|
||||
otherViewersWebcamLabel: {
|
||||
id: 'app.lock-viewers.otherViewersWebcamLabel',
|
||||
description: 'description for close button',
|
||||
},
|
||||
microphoneLable: {
|
||||
id: 'app.lock-viewers.microphoneLable',
|
||||
description: 'description for close button',
|
||||
},
|
||||
publicChatLabel: {
|
||||
id: 'app.lock-viewers.PublicChatLabel',
|
||||
description: 'description for close button',
|
||||
},
|
||||
privateChatLable: {
|
||||
id: 'app.lock-viewers.PrivateChatLable',
|
||||
description: 'description for close button',
|
||||
},
|
||||
layoutLable: {
|
||||
id: 'app.lock-viewers.Layout',
|
||||
description: 'description for close button',
|
||||
},
|
||||
});
|
||||
|
||||
class LockViewersComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const {
|
||||
closeModal,
|
||||
toggleLockSettings,
|
||||
toggleWebcamsOnlyForModerator,
|
||||
} = props;
|
||||
|
||||
this.closeModal = closeModal;
|
||||
this.toggleLockSettings = toggleLockSettings;
|
||||
this.toggleWebcamsOnlyForModerator = toggleWebcamsOnlyForModerator;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, meeting } = this.props;
|
||||
|
||||
return (
|
||||
<ModalBase
|
||||
overlayClassName={styles.overlay}
|
||||
className={styles.modal}
|
||||
onRequestClose={this.closeModal}
|
||||
>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title}>{intl.formatMessage(intlMessages.lockViewersTitle)}</div>
|
||||
<Button
|
||||
data-test="modalBaseCloseButton"
|
||||
className={styles.closeBtn}
|
||||
label={intl.formatMessage(intlMessages.closeLabel)}
|
||||
icon="close"
|
||||
size="md"
|
||||
hideLabel
|
||||
onClick={this.closeModal}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
{`${intl.formatMessage(intlMessages.lockViewersDescription)}`}
|
||||
</div>
|
||||
|
||||
<div className={styles.form}>
|
||||
<header className={styles.subHeader}>
|
||||
<div className={styles.bold}>{intl.formatMessage(intlMessages.featuresLable)}</div>
|
||||
<div className={styles.bold}>{intl.formatMessage(intlMessages.lockStatusLabel)}</div>
|
||||
</header>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.formElement}>
|
||||
<div className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.webcamLabel)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
defaultChecked={meeting.lockSettingsProp.disableCam}
|
||||
onChange={() => {
|
||||
meeting.lockSettingsProp.disableCam =
|
||||
!meeting.lockSettingsProp.disableCam;
|
||||
this.toggleLockSettings(meeting);
|
||||
}}
|
||||
ariaLabel={intl.formatMessage(intlMessages.webcamLabel)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.formElement}>
|
||||
<div className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.otherViewersWebcamLabel)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
defaultChecked={meeting.usersProp.webcamsOnlyForModerator}
|
||||
onChange={() => {
|
||||
meeting.usersProp.webcamsOnlyForModerator =
|
||||
!meeting.usersProp.webcamsOnlyForModerator;
|
||||
this.toggleWebcamsOnlyForModerator(meeting);
|
||||
}}
|
||||
ariaLabel={intl.formatMessage(intlMessages.otherViewersWebcamLabel)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.formElement}>
|
||||
<div className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.microphoneLable)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
defaultChecked={meeting.lockSettingsProp.disableMic}
|
||||
onChange={() => {
|
||||
meeting.lockSettingsProp.disableMic =
|
||||
!meeting.lockSettingsProp.disableMic;
|
||||
this.toggleLockSettings(meeting);
|
||||
}}
|
||||
ariaLabel={intl.formatMessage(intlMessages.microphoneLable)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.formElement}>
|
||||
<div className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.publicChatLabel)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
defaultChecked={meeting.lockSettingsProp.disablePubChat}
|
||||
onChange={() => {
|
||||
meeting.lockSettingsProp.disablePubChat =
|
||||
!meeting.lockSettingsProp.disablePubChat;
|
||||
this.toggleLockSettings(meeting);
|
||||
}}
|
||||
ariaLabel={intl.formatMessage(intlMessages.publicChatLabel)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.formElement}>
|
||||
<div className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.privateChatLable)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
defaultChecked={meeting.lockSettingsProp.disablePrivChat}
|
||||
onChange={() => {
|
||||
meeting.lockSettingsProp.disablePrivChat =
|
||||
!meeting.lockSettingsProp.disablePrivChat;
|
||||
this.toggleLockSettings(meeting);
|
||||
}}
|
||||
ariaLabel={intl.formatMessage(intlMessages.privateChatLable)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.formElement}>
|
||||
<div className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.layoutLable)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
defaultChecked={meeting.lockSettingsProp.lockedLayout}
|
||||
onChange={() => {
|
||||
meeting.lockSettingsProp.lockedLayout =
|
||||
!meeting.lockSettingsProp.lockedLayout;
|
||||
this.toggleLockSettings(meeting);
|
||||
}}
|
||||
ariaLabel={intl.formatMessage(intlMessages.layoutLable)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(LockViewersComponent);
|
24
bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx
Executable file
24
bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx
Executable file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { withModalMounter } from '/imports/ui/components/modal/service';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import LockViewersComponent from './component';
|
||||
|
||||
const LockViewersContainer = props => <LockViewersComponent {...props} />;
|
||||
|
||||
export default withModalMounter(withTracker(({ mountModal }) => ({
|
||||
closeModal() {
|
||||
mountModal(null);
|
||||
},
|
||||
|
||||
toggleLockSettings(meeting) {
|
||||
makeCall('toggleLockSettings', meeting);
|
||||
},
|
||||
|
||||
toggleWebcamsOnlyForModerator(meeting) {
|
||||
makeCall('toggleWebcamsOnlyForModerator', meeting);
|
||||
},
|
||||
meeting: (Meetings.findOne({ meetingId: Auth.meetingID })),
|
||||
}))(LockViewersContainer));
|
118
bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss
Executable file
118
bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss
Executable file
@ -0,0 +1,118 @@
|
||||
@import '/imports/ui/stylesheets/mixins/focus';
|
||||
@import '/imports/ui/stylesheets/variables/_all';
|
||||
@import "/imports/ui/components/modal/simple/styles";
|
||||
|
||||
:root {
|
||||
--modal-margin: 3rem;
|
||||
--title-position-left: 2.2rem;
|
||||
--closeBtn-position-left: 2.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
left: var(--title-position-left);
|
||||
color: var(--color-gray-dark);
|
||||
font-weight: bold;
|
||||
font-size: var(--font-size-large);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: var(--modal-margin);
|
||||
margin-bottom: var(--lg-padding-x);
|
||||
}
|
||||
|
||||
.subHeader {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
color: var(--color-gray-label);
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: var(--title-position-left);
|
||||
}
|
||||
|
||||
.modal {
|
||||
@extend .modal;
|
||||
padding: var(--jumbo-padding-y);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@extend .overlay;
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
color: var(--color-gray);
|
||||
margin-bottom: var(--jumbo-padding-y)
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--md-padding-x);
|
||||
}
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
margin-right: var(--md-padding-x);
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--color-gray-label);
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: var(--lg-padding-y);
|
||||
}
|
||||
|
||||
.formElement {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.pullContentRight {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
position: relative;
|
||||
background-color: var(--color-white);
|
||||
left: var(--closeBtn-position-left);
|
||||
bottom: var(--closeBtn-position-left);
|
||||
|
||||
i {
|
||||
color: var(--color-gray-light);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background-color: var(--color-gray-lighter);
|
||||
i {
|
||||
color: var(--color-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
line-height: var(--title-position-left);
|
||||
margin-bottom: var(--lg-padding-y);
|
||||
}
|
@ -5,6 +5,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import logoutRouteHandler from '/imports/utils/logoutRouteHandler';
|
||||
import Rating from './rating/component';
|
||||
import { styles } from './styles';
|
||||
|
||||
@ -77,19 +78,6 @@ class MeetingEnded extends React.PureComponent {
|
||||
const comment = textarea.value;
|
||||
return comment;
|
||||
}
|
||||
|
||||
static logoutRouteHandler() {
|
||||
Auth.logout()
|
||||
.then((logoutURL = window.location.origin) => {
|
||||
const protocolPattern = /^((http|https):\/\/)/;
|
||||
|
||||
window.location.href =
|
||||
protocolPattern.test(logoutURL) ?
|
||||
logoutURL :
|
||||
`http://${logoutURL}`;
|
||||
});
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -116,7 +104,7 @@ class MeetingEnded extends React.PureComponent {
|
||||
} = this.state;
|
||||
|
||||
if (selected <= 0) {
|
||||
MeetingEnded.logoutRouteHandler();
|
||||
logoutRouteHandler();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -138,7 +126,7 @@ class MeetingEnded extends React.PureComponent {
|
||||
|
||||
fetch(url, options)
|
||||
.finally(() => {
|
||||
MeetingEnded.logoutRouteHandler();
|
||||
logoutRouteHandler();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -121,6 +121,11 @@
|
||||
color: var(--color-gray-light);
|
||||
}
|
||||
|
||||
.userListColumn {
|
||||
@extend %flex-column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.enter,
|
||||
.appear {
|
||||
opacity: 0.01;
|
||||
|
@ -12,7 +12,8 @@
|
||||
.container{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: .3rem;
|
||||
margin-bottom: var(--lg-padding-y);
|
||||
margin-top: var(--sm-padding-x);
|
||||
}
|
||||
|
||||
.scrollableList {
|
||||
|
@ -175,7 +175,7 @@ class UserParticipants extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.userListColumn}>
|
||||
{
|
||||
!compact ?
|
||||
<div className={styles.container}>
|
||||
|
@ -9,6 +9,7 @@ import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
||||
import DropdownContent from '/imports/ui/components/dropdown/content/component';
|
||||
import DropdownList from '/imports/ui/components/dropdown/list/component';
|
||||
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
||||
import LockViewersContainer from '/imports/ui/components/lock-viewers/container';
|
||||
import { styles } from './styles';
|
||||
|
||||
const propTypes = {
|
||||
@ -19,7 +20,6 @@ const propTypes = {
|
||||
toggleMuteAllUsers: PropTypes.func.isRequired,
|
||||
toggleMuteAllUsersExceptPresenter: PropTypes.func.isRequired,
|
||||
toggleStatus: PropTypes.func.isRequired,
|
||||
toggleLockView: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -83,7 +83,8 @@ class UserOptions extends Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { intl, isMeetingMuted } = this.props;
|
||||
const { intl, isMeetingMuted, mountModal } = this.props;
|
||||
|
||||
this.menuItems = _.compact([
|
||||
(<DropdownListItem
|
||||
key={_.uniqueId('list-item-')}
|
||||
@ -111,7 +112,7 @@ class UserOptions extends Component {
|
||||
icon="lock"
|
||||
label={intl.formatMessage(intlMessages.lockViewersLabel)}
|
||||
description={intl.formatMessage(intlMessages.lockViewersDesc)}
|
||||
onClick={this.props.toggleLockView}
|
||||
onClick={() => mountModal(<LockViewersContainer />)}
|
||||
/>),
|
||||
]);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import mapUser from '/imports/ui/services/user/mapUser';
|
||||
import Users from '/imports/api/users/';
|
||||
@ -26,7 +25,6 @@ export default class UserOptionsContainer extends Component {
|
||||
|
||||
this.muteMeeting = this.muteMeeting.bind(this);
|
||||
this.muteAllUsersExceptPresenter = this.muteAllUsersExceptPresenter.bind(this);
|
||||
this.handleLockView = this.handleLockView.bind(this);
|
||||
this.handleClearStatus = this.handleClearStatus.bind(this);
|
||||
}
|
||||
|
||||
@ -44,11 +42,6 @@ export default class UserOptionsContainer extends Component {
|
||||
muteAllExceptPresenter(currentUser.userId);
|
||||
}
|
||||
|
||||
handleLockView() {
|
||||
// Temporary lock method, will be changed in future PR
|
||||
logger.info('handleLockView function');
|
||||
}
|
||||
|
||||
handleClearStatus() {
|
||||
const { users, setEmojiStatus } = this.props;
|
||||
|
||||
@ -71,7 +64,6 @@ export default class UserOptionsContainer extends Component {
|
||||
<UserOptions
|
||||
toggleMuteAllUsers={this.muteMeeting}
|
||||
toggleMuteAllUsersExceptPresenter={this.muteAllUsersExceptPresenter}
|
||||
toggleLockView={this.handleLockView}
|
||||
toggleStatus={this.handleClearStatus}
|
||||
isMeetingMuted={this.state.meetingMuted}
|
||||
/> : null
|
||||
|
@ -388,7 +388,7 @@ class VideoProvider extends Component {
|
||||
}
|
||||
|
||||
async createWebRTCPeer(id, shareWebcam) {
|
||||
const { meetingId, sessionToken } = this.props;
|
||||
const { meetingId, sessionToken, voiceBridge } = this.props;
|
||||
let iceServers = [];
|
||||
|
||||
try {
|
||||
@ -448,6 +448,7 @@ class VideoProvider extends Component {
|
||||
sdpOffer: offerSdp,
|
||||
cameraId: id,
|
||||
meetingId,
|
||||
voiceBridge,
|
||||
};
|
||||
this.sendMessage(message);
|
||||
|
||||
|
@ -14,4 +14,5 @@ export default withTracker(() => ({
|
||||
sessionToken: VideoService.sessionToken(),
|
||||
userName: VideoService.userName(),
|
||||
enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats),
|
||||
voiceBridge: VideoService.voiceBridge(),
|
||||
}))(VideoProviderContainer);
|
||||
|
@ -126,6 +126,11 @@ class VideoService {
|
||||
return Auth.sessionToken;
|
||||
}
|
||||
|
||||
voiceBridge() {
|
||||
const voiceBridge = Meetings.findOne({ meetingId: Auth.meetingID }).voiceProp.voiceConf;
|
||||
return voiceBridge;
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
return this.isConnected;
|
||||
}
|
||||
@ -157,4 +162,5 @@ export default {
|
||||
meetingId: () => videoService.meetingId(),
|
||||
getAllUsersVideo: () => videoService.getAllUsersVideo(),
|
||||
sessionToken: () => videoService.sessionToken(),
|
||||
voiceBridge: () => videoService.voiceBridge(),
|
||||
};
|
||||
|
15
bigbluebutton-html5/imports/utils/logoutRouteHandler.js
Normal file
15
bigbluebutton-html5/imports/utils/logoutRouteHandler.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
||||
const logoutRouteHandler = () => {
|
||||
Auth.logout()
|
||||
.then((logoutURL = window.location.origin) => {
|
||||
const protocolPattern = /^((http|https):\/\/)/;
|
||||
|
||||
window.location.href =
|
||||
protocolPattern.test(logoutURL) ?
|
||||
logoutURL :
|
||||
`http://${logoutURL}`;
|
||||
});
|
||||
};
|
||||
|
||||
export default logoutRouteHandler;
|
@ -359,6 +359,16 @@
|
||||
"app.shortcut-help.closePrivateChat": "Close Private Chat",
|
||||
"app.shortcut-help.openActions": "Open Actions Menu",
|
||||
"app.shortcut-help.openStatus": "Open Status Menu",
|
||||
"app.lock-viewers.title": "Lock Viewers",
|
||||
"app.lock-viewers.description": "These options enable you to restrict certain features available to viewers, such as locking out their ability to use private chat. (These restrictions do no apply to moderators)",
|
||||
"app.lock-viewers.featuresLable": "Feature",
|
||||
"app.lock-viewers.lockStatusLabel": "Locked Status",
|
||||
"app.lock-viewers.webcamLabel": "Webcam",
|
||||
"app.lock-viewers.otherViewersWebcamLabel": "See other viewsers webcams",
|
||||
"app.lock-viewers.microphoneLable": "Microphone",
|
||||
"app.lock-viewers.PublicChatLabel": "Public Chat",
|
||||
"app.lock-viewers.PrivateChatLable": "Private Chat",
|
||||
"app.lock-viewers.Layout": "Layout",
|
||||
"app.videoPreview.cameraLabel": "Camera",
|
||||
"app.videoPreview.cancelLabel": "Cancel",
|
||||
"app.videoPreview.closeLabel": "Close",
|
||||
|
@ -8,8 +8,17 @@ if [[ $files = *"bigbluebutton-html5"* ]]; then
|
||||
meteor npm install
|
||||
if [ $1 = linter ]
|
||||
then
|
||||
html5_files=""
|
||||
list=$(echo $files | tr " " "\n")
|
||||
for file in $list
|
||||
do
|
||||
if [[ $file = bigbluebutton-html5* ]] ; then
|
||||
html5_files+=" $file"
|
||||
fi
|
||||
done
|
||||
|
||||
cd ..
|
||||
bigbluebutton-html5/node_modules/.bin/eslint --ext .jsx,.js $files
|
||||
bigbluebutton-html5/node_modules/.bin/eslint --ext .jsx,.js $html5_files
|
||||
elif [ $1 = acceptance_tests ]
|
||||
then
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user