This commit is contained in:
Bobak Oftadeh 2019-01-09 14:14:47 -08:00
commit 5afba4cf67
11 changed files with 164 additions and 91 deletions

View File

@ -11,20 +11,19 @@ export default function clearGroupChatMsg(meetingId, chatId) {
if (chatId) { if (chatId) {
GroupChatMsg.remove({ meetingId, chatId }, () => { GroupChatMsg.remove({ meetingId, chatId }, () => {
Logger.info(`Cleared GroupChatMsg (${meetingId}, ${chatId})`); Logger.info(`Cleared GroupChatMsg (${meetingId}, ${chatId})`);
const clearMsg = {
color: '0',
timestamp: Date.now(),
correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`,
sender: {
id: PUBLIC_CHAT_SYSTEM_ID,
name: '',
},
message: CHAT_CLEAR_MESSAGE,
};
return addGroupChatMsg(meetingId, PUBLIC_GROUP_CHAT_ID, clearMsg);
}); });
const clearMsg = {
color: '0',
timestamp: Date.now(),
correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`,
sender: {
id: PUBLIC_CHAT_SYSTEM_ID,
name: '',
},
message: CHAT_CLEAR_MESSAGE,
};
return addGroupChatMsg(meetingId, PUBLIC_GROUP_CHAT_ID, clearMsg);
} }
if (meetingId) { if (meetingId) {

View File

@ -45,11 +45,16 @@ class Base extends Component {
this.updateLoadingState = this.updateLoadingState.bind(this); this.updateLoadingState = this.updateLoadingState.bind(this);
} }
componentWillUpdate() { componentDidUpdate(prevProps) {
const { approved } = this.props; const { ejected, approved } = this.props;
const { loading } = this.state; const { loading } = this.state;
if (approved && loading) this.updateLoadingState(false); if (approved && loading) this.updateLoadingState(false);
if (prevProps.ejected || ejected) {
Session.set('codeError', '403');
Session.set('isMeetingEnded', true);
}
} }
updateLoadingState(loading = false) { updateLoadingState(loading = false) {
@ -173,6 +178,7 @@ const BaseContainer = withTracker(() => {
const subscriptionsReady = subscriptionsHandlers.every(handler => handler.ready()); const subscriptionsReady = subscriptionsHandlers.every(handler => handler.ready());
return { return {
approved: Users.findOne({ userId: Auth.userID, approved: true, guest: true }), approved: Users.findOne({ userId: Auth.userID, approved: true, guest: true }),
ejected: Users.findOne({ userId: Auth.userID, ejected: true }),
meetingEnded: Session.get('isMeetingEnded'), meetingEnded: Session.get('isMeetingEnded'),
locale, locale,
subscriptionsReady, subscriptionsReady,

View File

@ -15,6 +15,9 @@ import ChatAlertContainer from '../chat/alert/container';
import { styles } from './styles'; import { styles } from './styles';
const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
const APP_CONFIG = Meteor.settings.public.app;
const DESKTOP_FONT_SIZE = APP_CONFIG.desktopFontSize;
const MOBILE_FONT_SIZE = APP_CONFIG.mobileFontSize;
const intlMessages = defineMessages({ const intlMessages = defineMessages({
userListLabel: { userListLabel: {
@ -36,7 +39,6 @@ const intlMessages = defineMessages({
}); });
const propTypes = { const propTypes = {
fontSize: PropTypes.string,
navbar: PropTypes.element, navbar: PropTypes.element,
sidebar: PropTypes.element, sidebar: PropTypes.element,
media: PropTypes.element, media: PropTypes.element,
@ -49,7 +51,6 @@ const propTypes = {
}; };
const defaultProps = { const defaultProps = {
fontSize: '16px',
navbar: null, navbar: null,
sidebar: null, sidebar: null,
media: null, media: null,
@ -70,13 +71,14 @@ class App extends Component {
} }
componentDidMount() { componentDidMount() {
const { locale, fontSize } = this.props; const { locale } = this.props;
const BROWSER_RESULTS = browser();
const isMobileBrowser = BROWSER_RESULTS.mobile || BROWSER_RESULTS.os.includes('Android');
Modal.setAppElement('#app'); Modal.setAppElement('#app');
document.getElementsByTagName('html')[0].lang = locale; document.getElementsByTagName('html')[0].lang = locale;
document.getElementsByTagName('html')[0].style.fontSize = fontSize; document.getElementsByTagName('html')[0].style.fontSize = isMobileBrowser ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
const BROWSER_RESULTS = browser();
const body = document.getElementsByTagName('body')[0]; const body = document.getElementsByTagName('body')[0];
if (BROWSER_RESULTS && BROWSER_RESULTS.name) { if (BROWSER_RESULTS && BROWSER_RESULTS.name) {
body.classList.add(`browser-${BROWSER_RESULTS.name}`); body.classList.add(`browser-${BROWSER_RESULTS.name}`);
@ -192,14 +194,14 @@ class App extends Component {
render() { render() {
const { const {
customStyle, customStyleUrl, micsLocked, customStyle, customStyleUrl, micsLocked, openPanel,
} = this.props; } = this.props;
return ( return (
<main className={styles.main}> <main className={styles.main}>
<NotificationsBarContainer /> <NotificationsBarContainer />
<section className={styles.wrapper}> <section className={styles.wrapper}>
<div className={styles.content}> <div className={openPanel ? styles.content : styles.noPanelContent}>
{this.renderNavBar()} {this.renderNavBar()}
{this.renderMedia()} {this.renderMedia()}
{this.renderActionsBar()} {this.renderActionsBar()}

View File

@ -9,6 +9,7 @@
--bars-padding: calc(var(--lg-padding-x) - .45rem); // -.45 so user-list and chat title is aligned with the presentation title --bars-padding: calc(var(--lg-padding-x) - .45rem); // -.45 so user-list and chat title is aligned with the presentation title
--userlist-handle-width: 5px; // 5px so user-list and chat resize handle render as the same size --userlist-handle-width: 5px; // 5px so user-list and chat resize handle render as the same size
--poll-pane-min-width: 20em; --poll-pane-min-width: 20em;
--panel-margin-left: 0.1em;
} }
.main { .main {
@ -57,7 +58,7 @@
} }
} }
.content { .content, .noPanelContent {
@extend %full-page; @extend %full-page;
order: 3; order: 3;
@ -89,13 +90,16 @@
} }
} }
.content{
margin-left: var(--panel-margin-left);
}
.userList { .userList {
@extend %full-page; @extend %full-page;
@extend %text-elipsis; @extend %text-elipsis;
z-index: 2; z-index: 2;
overflow: visible; overflow: visible;
order: 1; order: 1;
@include mq($small-only) { @include mq($small-only) {
@ -177,7 +181,7 @@
order: 2; order: 2;
flex-direction: row; flex-direction: row;
position: relative; position: relative;
margin-left: var(--panel-margin-right);
@include mq($portrait) { @include mq($portrait) {
flex-direction: column; flex-direction: column;
} }

View File

@ -18,6 +18,7 @@ const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id; const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const PRIVATE_CHAT_TYPE = CHAT_CONFIG.type_private; const PRIVATE_CHAT_TYPE = CHAT_CONFIG.type_private;
const PUBLIC_CHAT_USER_ID = CHAT_CONFIG.system_userid; const PUBLIC_CHAT_USER_ID = CHAT_CONFIG.system_userid;
const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.system_messages_keys.chat_clear;
const ScrollCollection = new Mongo.Collection(null); const ScrollCollection = new Mongo.Collection(null);
@ -248,8 +249,27 @@ const htmlDecode = (input) => {
}; };
// Export the chat as [Hour:Min] user: message // Export the chat as [Hour:Min] user: message
const exportChat = messageList => ( const exportChat = (messageList) => {
messageList.map((message) => { const { welcomeProp } = getMeeting();
const { logTime } = getUser(Auth.userID);
const { welcomeMsg } = welcomeProp;
const clearMessage = messageList.filter(message => message.message === PUBLIC_CHAT_CLEAR);
const hasClearMessage = clearMessage.length;
if (!hasClearMessage || (hasClearMessage && clearMessage[0].timestamp < logTime)) {
messageList.push({
timestamp: logTime,
message: welcomeMsg,
type: SYSTEM_CHAT_TYPE,
sender: PUBLIC_CHAT_USER_ID,
});
}
messageList.sort((a, b) => a.timestamp - b.timestamp);
return messageList.map((message) => {
const date = new Date(message.timestamp); const date = new Date(message.timestamp);
const hour = date.getHours().toString().padStart(2, 0); const hour = date.getHours().toString().padStart(2, 0);
const min = date.getMinutes().toString().padStart(2, 0); const min = date.getMinutes().toString().padStart(2, 0);
@ -259,8 +279,8 @@ const exportChat = messageList => (
} }
const userName = message.sender === PUBLIC_CHAT_USER_ID ? '' : `${getUser(message.sender).name} :`; const userName = message.sender === PUBLIC_CHAT_USER_ID ? '' : `${getUser(message.sender).name} :`;
return `${hourMin} ${userName} ${htmlDecode(message.message)}`; return `${hourMin} ${userName} ${htmlDecode(message.message)}`;
}).join('\n') }).join('\n');
); };
const setNotified = (chatType, item) => { const setNotified = (chatType, item) => {
const notified = Storage.getItem('notified'); const notified = Storage.getItem('notified');

View File

@ -7,7 +7,6 @@ import { notify } from '/imports/ui/services/notification';
import VideoService from '/imports/ui/components/video-provider/service'; import VideoService from '/imports/ui/components/video-provider/service';
import getFromUserSettings from '/imports/ui/services/users-settings'; import getFromUserSettings from '/imports/ui/services/users-settings';
import { withModalMounter } from '/imports/ui/components/modal/service'; import { withModalMounter } from '/imports/ui/components/modal/service';
import VideoPreviewContainer from '/imports/ui/components/video-preview/container';
import Media from './component'; import Media from './component';
import MediaService, { getSwapLayout } from './service'; import MediaService, { getSwapLayout } from './service';
import PresentationPodsContainer from '../presentation-pod/container'; import PresentationPodsContainer from '../presentation-pod/container';
@ -80,8 +79,9 @@ class MediaContainer extends Component {
const chromeErrorElement = ( const chromeErrorElement = (
<div> <div>
{intl.formatMessage(intlMessages.chromeExtensionError)}{' '} {intl.formatMessage(intlMessages.chromeExtensionError)}
<a href={CHROME_EXTENSION_LINK} target="_blank"> {' '}
<a href={CHROME_EXTENSION_LINK} target="_blank" rel="noopener noreferrer">
{intl.formatMessage(intlMessages.chromeExtensionErrorLink)} {intl.formatMessage(intlMessages.chromeExtensionErrorLink)}
</a> </a>
</div> </div>
@ -99,7 +99,7 @@ class MediaContainer extends Component {
} }
} }
export default withModalMounter(withTracker(({ mountModal }) => { export default withModalMounter(withTracker(() => {
const { dataSaving } = Settings; const { dataSaving } = Settings;
const { viewParticipantsWebcams, viewScreenshare } = dataSaving; const { viewParticipantsWebcams, viewScreenshare } = dataSaving;
@ -118,7 +118,7 @@ export default withModalMounter(withTracker(({ mountModal }) => {
} }
const usersVideo = VideoService.getAllUsersVideo(); const usersVideo = VideoService.getAllUsersVideo();
if (MediaService.shouldShowOverlay() && usersVideo.length) { if (MediaService.shouldShowOverlay() && usersVideo.length && viewParticipantsWebcams) {
data.floatingOverlay = usersVideo.length < 2; data.floatingOverlay = usersVideo.length < 2;
data.hideOverlay = usersVideo.length === 0; data.hideOverlay = usersVideo.length === 0;
} }

View File

@ -1,10 +1,12 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import Modal from '/imports/ui/components/modal/fullscreen/component'; import Modal from '/imports/ui/components/modal/fullscreen/component';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import {
Tab, Tabs, TabList, TabPanel,
} from 'react-tabs';
import { defineMessages, injectIntl, intlShape } from 'react-intl'; import { defineMessages, injectIntl, intlShape } from 'react-intl';
import ClosedCaptions from '/imports/ui/components/settings/submenus/closed-captions/component'; import ClosedCaptions from '/imports/ui/components/settings/submenus/closed-captions/component';
import DataSaving from '/imports/ui/components/settings/submenus/data-saving/component'; import DataSaving from '/imports/ui/components/settings/submenus/data-saving/component';
import Application from '/imports/ui/components/settings/submenus/application/container'; import Application from '/imports/ui/components/settings/submenus/application/component';
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -61,20 +63,43 @@ const intlMessages = defineMessages({
const propTypes = { const propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired,
dataSaving: PropTypes.object.isRequired, dataSaving: PropTypes.shape({
application: PropTypes.object.isRequired, viewParticipantsWebcams: PropTypes.bool,
cc: PropTypes.object.isRequired, viewScreenshare: PropTypes.bool,
participants: PropTypes.object.isRequired, }).isRequired,
application: PropTypes.shape({
chatAudioAlerts: PropTypes.bool,
chatPushAlerts: PropTypes.bool,
fallbackLocale: PropTypes.string,
fontSize: PropTypes.string,
locale: PropTypes.string,
}).isRequired,
cc: PropTypes.shape({
backgroundColor: PropTypes.string,
enabled: PropTypes.bool,
fontColor: PropTypes.string,
fontFamily: PropTypes.string,
fontSize: PropTypes.string,
takeOwnership: PropTypes.bool,
}).isRequired,
participants: PropTypes.shape({
layout: PropTypes.bool,
lockAll: PropTypes.bool,
microphone: PropTypes.bool,
muteAll: PropTypes.bool,
privateChat: PropTypes.bool,
publicChat: PropTypes.bool,
}).isRequired,
updateSettings: PropTypes.func.isRequired, updateSettings: PropTypes.func.isRequired,
availableLocales: PropTypes.object.isRequired, availableLocales: PropTypes.objectOf(PropTypes.array).isRequired,
mountModal: PropTypes.func.isRequired, mountModal: PropTypes.func.isRequired,
locales: PropTypes.array.isRequired,
}; };
class Settings extends Component { class Settings extends Component {
static setHtmlFontSize(size) { static setHtmlFontSize(size) {
document.getElementsByTagName('html')[0].style.fontSize = size; document.getElementsByTagName('html')[0].style.fontSize = size;
} }
constructor(props) { constructor(props) {
super(props); super(props);
@ -104,7 +129,8 @@ class Settings extends Component {
} }
componentWillMount() { componentWillMount() {
this.props.availableLocales.then((locales) => { const { availableLocales } = this.props;
availableLocales.then((locales) => {
this.setState({ availableLocales: locales }); this.setState({ availableLocales: locales });
}); });
} }
@ -123,12 +149,17 @@ class Settings extends Component {
renderModalContent() { renderModalContent() {
const { intl } = this.props; const { intl } = this.props;
const {
selectedTab,
availableLocales,
current,
locales,
} = this.state;
return ( return (
<Tabs <Tabs
className={styles.tabs} className={styles.tabs}
onSelect={this.handleSelectTab} onSelect={this.handleSelectTab}
selectedIndex={this.state.selectedTab} selectedIndex={selectedTab}
role="presentation" role="presentation"
selectedTabPanelClassName={styles.selectedTab} selectedTabPanelClassName={styles.selectedTab}
> >
@ -170,9 +201,9 @@ class Settings extends Component {
</TabList> </TabList>
<TabPanel className={styles.tabPanel}> <TabPanel className={styles.tabPanel}>
<Application <Application
availableLocales={this.state.availableLocales} availableLocales={availableLocales}
handleUpdateSettings={this.handleUpdateSettings} handleUpdateSettings={this.handleUpdateSettings}
settings={this.state.current.application} settings={current.application}
/> />
</TabPanel> </TabPanel>
{/* <TabPanel className={styles.tabPanel}> */} {/* <TabPanel className={styles.tabPanel}> */}
@ -183,14 +214,14 @@ class Settings extends Component {
{/* </TabPanel> */} {/* </TabPanel> */}
<TabPanel className={styles.tabPanel}> <TabPanel className={styles.tabPanel}>
<ClosedCaptions <ClosedCaptions
settings={this.state.current.cc} settings={current.cc}
handleUpdateSettings={this.handleUpdateSettings} handleUpdateSettings={this.handleUpdateSettings}
locales={this.props.locales} locales={locales}
/> />
</TabPanel> </TabPanel>
<TabPanel className={styles.tabPanel}> <TabPanel className={styles.tabPanel}>
<DataSaving <DataSaving
settings={this.state.current.dataSaving} settings={current.dataSaving}
handleUpdateSettings={this.handleUpdateSettings} handleUpdateSettings={this.handleUpdateSettings}
/> />
</TabPanel> </TabPanel>
@ -205,18 +236,22 @@ class Settings extends Component {
</Tabs> </Tabs>
); );
} }
render() { render() {
const { const {
intl, intl,
mountModal, mountModal,
} = this.props; } = this.props;
const {
current,
saved,
} = this.state;
return ( return (
<Modal <Modal
title={intl.formatMessage(intlMessages.SettingsLabel)} title={intl.formatMessage(intlMessages.SettingsLabel)}
confirm={{ confirm={{
callback: () => { callback: () => {
this.updateSettings(this.state.current); this.updateSettings(current);
// router.push(location.pathname); // TODO 4767 // router.push(location.pathname); // TODO 4767
/* We need to use mountModal(null) here to prevent submenu state updates, /* We need to use mountModal(null) here to prevent submenu state updates,
* from re-opening the modal. * from re-opening the modal.
@ -228,7 +263,7 @@ class Settings extends Component {
}} }}
dismiss={{ dismiss={{
callback: () => { callback: () => {
Settings.setHtmlFontSize(this.state.saved.application.fontSize); Settings.setHtmlFontSize(saved.application.fontSize);
mountModal(null); mountModal(null);
}, },
label: intl.formatMessage(intlMessages.CancelLabel), label: intl.formatMessage(intlMessages.CancelLabel),

View File

@ -1,13 +1,12 @@
import React from 'react'; import React from 'react';
import Button from '/imports/ui/components/button/component';
import cx from 'classnames'; import cx from 'classnames';
import Button from '/imports/ui/components/button/component';
import Toggle from '/imports/ui/components/switch/component'; import Toggle from '/imports/ui/components/switch/component';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import BaseMenu from '../base/component'; import BaseMenu from '../base/component';
import { styles } from '../styles'; import { styles } from '../styles';
const MIN_FONTSIZE = 0; const MIN_FONTSIZE = 0;
const MAX_FONTSIZE = 4;
const intlMessages = defineMessages({ const intlMessages = defineMessages({
applicationSectionTitle: { applicationSectionTitle: {
@ -61,6 +60,10 @@ const intlMessages = defineMessages({
}); });
class ApplicationMenu extends BaseMenu { class ApplicationMenu extends BaseMenu {
static setHtmlFontSize(size) {
document.getElementsByTagName('html')[0].style.fontSize = size;
}
constructor(props) { constructor(props) {
super(props); super(props);
@ -69,41 +72,66 @@ class ApplicationMenu extends BaseMenu {
settings: props.settings, settings: props.settings,
isLargestFontSize: false, isLargestFontSize: false,
isSmallestFontSize: false, isSmallestFontSize: false,
fontSizes: [
'12px',
'14px',
'16px',
'18px',
'20px',
],
}; };
} }
componentDidMount() {
this.setInitialFontSize();
}
setInitialFontSize() {
const { fontSizes } = this.state;
const clientFont = document.getElementsByTagName('html')[0].style.fontSize;
const hasFont = fontSizes.includes(clientFont);
if (!hasFont) {
fontSizes.push(clientFont);
fontSizes.sort();
}
const fontIndex = fontSizes.indexOf(clientFont);
this.changeFontSize(clientFont);
this.setState({
isSmallestFontSize: fontIndex <= MIN_FONTSIZE,
isLargestFontSize: fontIndex >= (fontSizes.length - 1),
fontSizes,
});
}
handleUpdateFontSize(size) { handleUpdateFontSize(size) {
const obj = this.state; const obj = this.state;
obj.settings.fontSize = size; obj.settings.fontSize = size;
this.handleUpdateSettings(this.state.settingsName, obj.settings); this.handleUpdateSettings(this.state.settingsName, obj.settings);
} }
setHtmlFontSize(size) {
document.getElementsByTagName('html')[0].style.fontSize = size;
}
changeFontSize(size) { changeFontSize(size) {
const obj = this.state; const obj = this.state;
obj.settings.fontSize = size; obj.settings.fontSize = size;
this.setState(obj, () => { this.setState(obj, () => {
this.setHtmlFontSize(this.state.settings.fontSize); ApplicationMenu.setHtmlFontSize(this.state.settings.fontSize);
this.handleUpdateFontSize(this.state.settings.fontSize); this.handleUpdateFontSize(this.state.settings.fontSize);
}); });
} }
handleIncreaseFontSize() { handleIncreaseFontSize() {
const currentFontSize = this.state.settings.fontSize; const currentFontSize = this.state.settings.fontSize;
const availableFontSizes = this.props.fontSizes; const availableFontSizes = this.state.fontSizes;
const canIncreaseFontSize = availableFontSizes.indexOf(currentFontSize) < MAX_FONTSIZE; const maxFontSize = availableFontSizes.length - 1;
const fs = canIncreaseFontSize ? availableFontSizes.indexOf(currentFontSize) + 1 : MAX_FONTSIZE; const canIncreaseFontSize = availableFontSizes.indexOf(currentFontSize) < maxFontSize;
const fs = canIncreaseFontSize ? availableFontSizes.indexOf(currentFontSize) + 1 : maxFontSize;
this.changeFontSize(availableFontSizes[fs]); this.changeFontSize(availableFontSizes[fs]);
if (fs === MAX_FONTSIZE) this.setState({ isLargestFontSize: true }); if (fs === maxFontSize) this.setState({ isLargestFontSize: true });
this.setState({ isSmallestFontSize: false }); this.setState({ isSmallestFontSize: false });
} }
handleDecreaseFontSize() { handleDecreaseFontSize() {
const currentFontSize = this.state.settings.fontSize; const currentFontSize = this.state.settings.fontSize;
const availableFontSizes = this.props.fontSizes; const availableFontSizes = this.state.fontSizes;
const canDecreaseFontSize = availableFontSizes.indexOf(currentFontSize) > MIN_FONTSIZE; const canDecreaseFontSize = availableFontSizes.indexOf(currentFontSize) > MIN_FONTSIZE;
const fs = canDecreaseFontSize ? availableFontSizes.indexOf(currentFontSize) - 1 : MIN_FONTSIZE; const fs = canDecreaseFontSize ? availableFontSizes.indexOf(currentFontSize) - 1 : MIN_FONTSIZE;
this.changeFontSize(availableFontSizes[fs]); this.changeFontSize(availableFontSizes[fs]);

View File

@ -1,20 +0,0 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Application from './component';
const ApplicationContainer = ({ children, ...props }) => (
<Application {...props}>
{children}
</Application>
);
export default withTracker(() => ({
fontSizes: [
'12px',
'14px',
'16px',
'18px',
'20px',
],
}))(ApplicationContainer);

View File

@ -1,7 +1,7 @@
public: public:
app: app:
mobileFont: 16 mobileFontSize: 16px
desktopFont: 14 desktopFontSize: 14px
audioChatNotification: false audioChatNotification: false
autoJoin: true autoJoin: true
showParticipantsOnLogin: true showParticipantsOnLogin: true
@ -20,7 +20,6 @@ public:
application: application:
chatAudioAlerts: false chatAudioAlerts: false
chatPushAlerts: false chatPushAlerts: false
fontSize: 16px
fallbackLocale: en fallbackLocale: en
audio: audio:
inputDeviceId: undefined inputDeviceId: undefined

View File

@ -431,7 +431,7 @@
"app.sfu.noAvailableCodec2203": "Error 2203: Server could not find an appropriate codec", "app.sfu.noAvailableCodec2203": "Error 2203: Server could not find an appropriate codec",
"app.meeting.endNotification.ok.label": "OK", "app.meeting.endNotification.ok.label": "OK",
"app.whiteboard.toolbar.tools": "Tools", "app.whiteboard.toolbar.tools": "Tools",
"app.whiteboard.toolbar.tools.hand": "Hand", "app.whiteboard.toolbar.tools.hand": "Pan",
"app.whiteboard.toolbar.tools.pencil": "Pencil", "app.whiteboard.toolbar.tools.pencil": "Pencil",
"app.whiteboard.toolbar.tools.rectangle": "Rectangle", "app.whiteboard.toolbar.tools.rectangle": "Rectangle",
"app.whiteboard.toolbar.tools.triangle": "Triangle", "app.whiteboard.toolbar.tools.triangle": "Triangle",
@ -456,8 +456,8 @@
"app.whiteboard.toolbar.color.silver": "Silver", "app.whiteboard.toolbar.color.silver": "Silver",
"app.whiteboard.toolbar.undo": "Undo annotation", "app.whiteboard.toolbar.undo": "Undo annotation",
"app.whiteboard.toolbar.clear": "Clear all annotations", "app.whiteboard.toolbar.clear": "Clear all annotations",
"app.whiteboard.toolbar.multiUserOn": "Turn multi-user mode on", "app.whiteboard.toolbar.multiUserOn": "Turn multi-user whiteboard on",
"app.whiteboard.toolbar.multiUserOff": "Turn multi-user mode off", "app.whiteboard.toolbar.multiUserOff": "Turn multi-user whiteboard off",
"app.whiteboard.toolbar.fontSize": "Font Size List", "app.whiteboard.toolbar.fontSize": "Font Size List",
"app.feedback.title": "You have logged out of the conference", "app.feedback.title": "You have logged out of the conference",
"app.feedback.subtitle": "We'd love to hear about your experience with BigBlueButton (optional)", "app.feedback.subtitle": "We'd love to hear about your experience with BigBlueButton (optional)",