From bad845e1959a9ed1461da7dd8c7934890ddceb56 Mon Sep 17 00:00:00 2001 From: Astagor Date: Mon, 4 May 2020 18:27:50 +0200 Subject: [PATCH 01/43] Browse passive participants --- app/src/RoomClient.js | 10 +++++++ app/src/Spotlights.js | 63 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 8dbc9d1..656f443 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -298,6 +298,16 @@ export default class RoomClient switch (key) { + case String.fromCharCode(37): + { + this._spotlights.setPrevAsSelected(); + break; + } + case String.fromCharCode(39): + { + this._spotlights.setNextAsSelected(); + break; + } case 'A': // Activate advanced mode { store.dispatch(settingsActions.toggleAdvancedMode()); diff --git a/app/src/Spotlights.js b/app/src/Spotlights.js index 30dc48a..dd119df 100644 --- a/app/src/Spotlights.js +++ b/app/src/Spotlights.js @@ -12,6 +12,7 @@ export default class Spotlights extends EventEmitter this._signalingSocket = signalingSocket; this._maxSpotlights = maxSpotlights; this._peerList = []; + this._unmutablePeerList = []; this._selectedSpotlights = []; this._currentSpotlights = []; this._started = false; @@ -45,6 +46,66 @@ export default class Spotlights extends EventEmitter } } + setNextAsSelected() + { + let peerId = null; + if (this._selectedSpotlights.length > 0) { + peerId = this._selectedSpotlights[0]; + } else if (this._unmutablePeerList.length > 0) { + peerId = this._unmutablePeerList[0]; + } + + if (peerId != null && this._currentSpotlights.length < this._unmutablePeerList.length) { + let oldIndex = this._unmutablePeerList.indexOf(peerId); + let index = oldIndex; + index++; + do { + if (index >= this._unmutablePeerList.length) { + index = 0; + } + let newSelectedPeer = this._unmutablePeerList[index]; + if (!this._currentSpotlights.includes(newSelectedPeer)) { + this.setPeerSpotlight(newSelectedPeer); + break; + } + index++; + if (index === oldIndex) { + break; + } + } while (true); + } + } + + setPrevAsSelected() + { + let peerId = null; + if (this._selectedSpotlights.length > 0) { + peerId = this._selectedSpotlights[0]; + } else if (this._unmutablePeerList.length > 0) { + peerId = this._unmutablePeerList[0]; + } + + if (peerId != null && this._currentSpotlights.length < this._unmutablePeerList.length) { + let oldIndex = this._unmutablePeerList.indexOf(peerId); + let index = oldIndex; + index--; + do { + if (index < 0) { + index = this._unmutablePeerList.length - 1; + } + let newSelectedPeer = this._unmutablePeerList[index]; + if (!this._currentSpotlights.includes(newSelectedPeer)) { + this.setPeerSpotlight(newSelectedPeer); + break; + } + index--; + if (index === oldIndex) { + break; + } + } while (true); + } + } + setPeerSpotlight(peerId) { logger.debug('setPeerSpotlight() [peerId:"%s"]', peerId); @@ -105,6 +166,7 @@ export default class Spotlights extends EventEmitter logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id); this._peerList.push(id); + this._unmutablePeerList.push(id); if (this._started) this._spotlightsUpdated(); @@ -117,6 +179,7 @@ export default class Spotlights extends EventEmitter 'room "peerClosed" event [peerId:%o]', id); this._peerList = this._peerList.filter((peer) => peer !== id); + this._unmutablePeerList = this._unmutablePeerList.filter((peer) => peer !== id); this._selectedSpotlights = this._selectedSpotlights.filter((peer) => peer !== id); From 7c134039212fd0b60eb7a84e5663ead46ce4f800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 6 May 2020 23:39:40 +0200 Subject: [PATCH 02/43] Add help and about modal --- app/src/actions/roomActions.js | 12 +++ app/src/components/Controls/About.js | 133 ++++++++++++++++++++++++++ app/src/components/Controls/Help.js | 99 +++++++++++++++++++ app/src/components/Controls/TopBar.js | 54 +++++++++++ app/src/components/Room.js | 9 ++ app/src/reducers/room.js | 16 ++++ 6 files changed, 323 insertions(+) create mode 100644 app/src/components/Controls/About.js create mode 100644 app/src/components/Controls/Help.js diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index b90bf1b..7c44835 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -70,6 +70,18 @@ export const setExtraVideoOpen = (extraVideoOpen) => payload : { extraVideoOpen } }); +export const setHelpOpen = (helpOpen) => + ({ + type : 'SET_HELP_OPEN', + payload : { helpOpen } + }); + +export const setAboutOpen = (aboutOpen) => + ({ + type : 'SET_ABOUT_OPEN', + payload : { aboutOpen } + }); + export const setSettingsTab = (tab) => ({ type : 'SET_SETTINGS_TAB', diff --git a/app/src/components/Controls/About.js b/app/src/components/Controls/About.js new file mode 100644 index 0000000..d361a8c --- /dev/null +++ b/app/src/components/Controls/About.js @@ -0,0 +1,133 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import { withRoomContext } from '../../RoomContext'; +import * as roomActions from '../../actions/roomActions'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import Link from '@material-ui/core/Link'; +import Button from '@material-ui/core/Button'; + +const styles = (theme) => + ({ + dialogPaper : + { + width : '30vw', + [theme.breakpoints.down('lg')] : + { + width : '40vw' + }, + [theme.breakpoints.down('md')] : + { + width : '50vw' + }, + [theme.breakpoints.down('sm')] : + { + width : '70vw' + }, + [theme.breakpoints.down('xs')] : + { + width : '90vw' + } + }, + logo : + { + marginRight : 'auto' + }, + link : + { + display : 'block', + textAlign : 'center' + } + }); + +const About = ({ + aboutOpen, + handleCloseAbout, + classes +}) => +{ + return ( + handleCloseAbout(false)} + classes={{ + paper : classes.dialogPaper + }} + > + + + + + + Contributions to this work were made on behalf of the GÉANT + project, a project that has received funding from the + European Union’s Horizon 2020 research and innovation + programme under Grant Agreement No. 731122 (GN4-2). + On behalf of GÉANT project, GÉANT Association is the sole + owner of the copyright in all material which was developed + by a member of the GÉANT project.
+
+ GÉANT Vereniging (Association) is registered with the + Chamber of Commerce in Amsterdam with registration number + 40535155 and operates in the UK as a branch of GÉANT + Vereniging. Registered office: Hoekenrode 3, 1102BR + Amsterdam, The Netherlands. UK branch address: City House, + 126-130 Hills Road, Cambridge CB2 1PQ, UK. +
+ + https://edumeet.org + +
+ + { window.config.logo && Logo } + + +
+ ); +}; + +About.propTypes = +{ + roomClient : PropTypes.object.isRequired, + aboutOpen : PropTypes.bool.isRequired, + handleCloseAbout : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + aboutOpen : state.room.aboutOpen + }); + +const mapDispatchToProps = { + handleCloseAbout : roomActions.setAboutOpen +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room.aboutOpen === next.room.aboutOpen + ); + } + } +)(withStyles(styles)(About))); \ No newline at end of file diff --git a/app/src/components/Controls/Help.js b/app/src/components/Controls/Help.js new file mode 100644 index 0000000..67be03f --- /dev/null +++ b/app/src/components/Controls/Help.js @@ -0,0 +1,99 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import { withRoomContext } from '../../RoomContext'; +import * as roomActions from '../../actions/roomActions'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import Button from '@material-ui/core/Button'; + +const styles = (theme) => + ({ + dialogPaper : + { + width : '30vw', + [theme.breakpoints.down('lg')] : + { + width : '40vw' + }, + [theme.breakpoints.down('md')] : + { + width : '50vw' + }, + [theme.breakpoints.down('sm')] : + { + width : '70vw' + }, + [theme.breakpoints.down('xs')] : + { + width : '90vw' + } + } + }); + +const Help = ({ + helpOpen, + handleCloseHelp, + classes +}) => +{ + return ( + { handleCloseHelp(false); }} + classes={{ + paper : classes.dialogPaper + }} + > + + + + + + + + ); +}; + +Help.propTypes = +{ + roomClient : PropTypes.object.isRequired, + helpOpen : PropTypes.bool.isRequired, + handleCloseHelp : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + helpOpen : state.room.helpOpen + }); + +const mapDispatchToProps = { + handleCloseHelp : roomActions.setHelpOpen +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room.helpOpen === next.room.helpOpen + ); + } + } +)(withStyles(styles)(Help))); \ No newline at end of file diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 6422788..028b883 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -36,6 +36,8 @@ import VideoCallIcon from '@material-ui/icons/VideoCall'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; import MoreIcon from '@material-ui/icons/MoreVert'; +import HelpIcon from '@material-ui/icons/Help'; +import InfoIcon from '@material-ui/icons/Info'; const styles = (theme) => ({ @@ -192,6 +194,8 @@ const TopBar = (props) => onFullscreen, setSettingsOpen, setExtraVideoOpen, + setHelpOpen, + setAboutOpen, setLockDialogOpen, toggleToolArea, openUsersTab, @@ -483,6 +487,46 @@ const TopBar = (props) => />

+ + { + handleMenuClose(); + setHelpOpen(!room.helpOpen); + }} + > + +

+ +

+
+ + { + handleMenuClose(); + setAboutOpen(!room.aboutOpen); + }} + > + +

+ +

+
} @@ -694,6 +738,8 @@ TopBar.propTypes = setToolbarsVisible : PropTypes.func.isRequired, setSettingsOpen : PropTypes.func.isRequired, setExtraVideoOpen : PropTypes.func.isRequired, + setHelpOpen : PropTypes.func.isRequired, + setAboutOpen : PropTypes.func.isRequired, setLockDialogOpen : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, openUsersTab : PropTypes.func.isRequired, @@ -741,6 +787,14 @@ const mapDispatchToProps = (dispatch) => { dispatch(roomActions.setExtraVideoOpen(extraVideoOpen)); }, + setHelpOpen : (helpOpen) => + { + dispatch(roomActions.setHelpOpen(helpOpen)); + }, + setAboutOpen : (aboutOpen) => + { + dispatch(roomActions.setAboutOpen(aboutOpen)); + }, setLockDialogOpen : (lockDialogOpen) => { dispatch(roomActions.setLockDialogOpen(lockDialogOpen)); diff --git a/app/src/components/Room.js b/app/src/components/Room.js index cdda66f..505fc69 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -25,6 +25,8 @@ import Settings from './Settings/Settings'; import TopBar from './Controls/TopBar'; import WakeLock from 'react-wakelock-react16'; import ExtraVideo from './Controls/ExtraVideo'; +import Help from './Controls/Help'; +import About from './Controls/About'; const TIMEOUT = 5 * 1000; @@ -222,6 +224,13 @@ class Room extends React.PureComponent { room.extraVideoOpen && } + { room.helpOpen && + + } + { room.aboutOpen && + + } + ); } diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 6d47d42..1a4b99e 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -22,6 +22,8 @@ const initialState = spotlights : [], settingsOpen : false, extraVideoOpen : false, + helpOpen : false, + aboutOpen : false, currentSettingsTab : 'media', // media, appearence, advanced lockDialogOpen : false, joined : false, @@ -130,6 +132,20 @@ const room = (state = initialState, action) => return { ...state, extraVideoOpen }; } + case 'SET_HELP_OPEN': + { + const { helpOpen } = action.payload; + + return { ...state, helpOpen }; + } + + case 'SET_ABOUT_OPEN': + { + const { aboutOpen } = action.payload; + + return { ...state, aboutOpen }; + } + case 'SET_SETTINGS_TAB': { const { tab } = action.payload; From 7e9160927684e85d73ab41dcf8359b1ce7f42617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 12:20:24 +0200 Subject: [PATCH 03/43] Have a global try-catch in the server --- server/package.json | 2 +- server/server.js | 101 ++++++++++++++++++++------------------------ 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/server/package.json b/server/package.json index ef96f36..d407d03 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,7 @@ "license": "MIT", "main": "lib/index.js", "scripts": { - "start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js", + "start": "node server.js", "connect": "node connect.js", "lint": "eslint -c .eslintrc.json --ext .js *.js lib/" }, diff --git a/server/server.js b/server/server.js index 5820413..fcde0b1 100755 --- a/server/server.js +++ b/server/server.js @@ -127,69 +127,58 @@ let oidcStrategy; async function run() { - // Open the interactive server. - await interactiveServer(rooms, peers); - - // start Prometheus exporter - if (config.prometheus) + try { - await promExporter(rooms, peers, config.prometheus); - } + // Open the interactive server. + await interactiveServer(rooms, peers); - if (typeof(config.auth) === 'undefined') - { - logger.warn('Auth is not configured properly!'); - } - else - { - await setupAuth(); - } - - // Run a mediasoup Worker. - await runMediasoupWorkers(); - - // Run HTTPS server. - await runHttpsServer(); - - // Run WebSocketServer. - await runWebSocketServer(); - - // eslint-disable-next-line no-unused-vars - function errorHandler(err, req, res, next) - { - const trackingId = uuidv4(); - - res.status(500).send( - `

Internal Server Error

-

If you report this error, please also report this - tracking ID which makes it possible to locate your session - in the logs which are available to the system administrator: - ${trackingId}

` - ); - logger.error( - 'Express error handler dump with tracking ID: %s, error dump: %o', - trackingId, err); - } - - app.use(errorHandler); - - // Log rooms status every 30 seconds. - setInterval(() => - { - for (const room of rooms.values()) + // start Prometheus exporter + if (config.prometheus) { - room.logStatus(); + await promExporter(rooms, peers, config.prometheus); } - }, 120000); - // check for deserted rooms - setInterval(() => - { - for (const room of rooms.values()) + if (typeof(config.auth) === 'undefined') { - room.checkEmpty(); + logger.warn('Auth is not configured properly!'); } - }, 10000); + else + { + await setupAuth(); + } + + // Run a mediasoup Worker. + await runMediasoupWorkers(); + + // Run HTTPS server. + await runHttpsServer(); + + // Run WebSocketServer. + await runWebSocketServer(); + + const errorHandler = (err, req, res, next) => + { + const trackingId = uuidv4(); + + res.status(500).send( + `

Internal Server Error

+

If you report this error, please also report this + tracking ID which makes it possible to locate your session + in the logs which are available to the system administrator: + ${trackingId}

` + ); + logger.error( + 'Express error handler dump with tracking ID: %s, error dump: %o', + trackingId, err); + }; + + // eslint-disable-next-line no-unused-vars + app.use(errorHandler); + } + catch (error) + { + logger.error('run() [error:"%o"]', error); + } } function statusLog() From cfdaceed223039a4d9a1d727610ae9b7f1ef1a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 13:35:29 +0200 Subject: [PATCH 04/43] Add posibility to have separate media buttons, fixes #309 --- app/public/config/config.example.js | 19 +- app/src/actions/settingsActions.js | 5 + app/src/components/Containers/Me.js | 212 ++++---- app/src/components/Containers/Peer.js | 470 +++++++++--------- .../components/Controls/ButtonControlBar.js | 256 ++++++++++ app/src/components/MeetingViews/Democratic.js | 52 +- app/src/components/Room.js | 9 + .../components/Settings/AppearenceSettings.js | 11 + app/src/components/Settings/MediaSettings.js | 6 +- app/src/reducers/settings.js | 8 + app/src/translations/cn.json | 4 + app/src/translations/cs.json | 4 + app/src/translations/de.json | 4 + app/src/translations/dk.json | 4 + app/src/translations/el.json | 4 + app/src/translations/en.json | 4 + app/src/translations/es.json | 4 + app/src/translations/fr.json | 4 + app/src/translations/hr.json | 4 + app/src/translations/hu.json | 4 + app/src/translations/it.json | 4 + app/src/translations/lv.json | 4 + app/src/translations/nb.json | 4 + app/src/translations/pl.json | 4 + app/src/translations/pt.json | 4 + app/src/translations/ro.json | 4 + app/src/translations/tr.json | 4 + app/src/translations/uk.json | 4 + 28 files changed, 739 insertions(+), 381 deletions(-) create mode 100644 app/src/components/Controls/ButtonControlBar.js diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index b591c16..38334f3 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -52,18 +52,21 @@ var config = noiseSuppression : true, sampleSize : 16 }, - background : 'images/background.jpg', - defaultLayout : 'democratic', // democratic, filmstrip - lastN : 4, - mobileLastN : 1, + background : 'images/background.jpg', + defaultLayout : 'democratic', // democratic, filmstrip + // If true, will show media control buttons in separate + // control bar, not in the ME container. + buttonControlBar : false, + lastN : 4, + mobileLastN : 1, // Highest number of speakers user can select - maxLastN : 5, + maxLastN : 5, // If truthy, users can NOT change number of speakers visible - lockLastN : false, + lockLastN : false, // Add file and uncomment for adding logo to appbar // logo : 'images/logo.svg', - title : 'Multiparty meeting', - theme : + title : 'Multiparty meeting', + theme : { palette : { diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 63c12bf..4b7dc6d 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -38,6 +38,11 @@ export const togglePermanentTopBar = () => type : 'TOGGLE_PERMANENT_TOPBAR' }); +export const toggleButtonControlBar = () => + ({ + type : 'TOGGLE_BUTTON_CONTROL_BAR' + }); + export const toggleShowNotifications = () => ({ type : 'TOGGLE_SHOW_NOTIFICATIONS' diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index f368089..8279a61 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -77,7 +77,29 @@ const styles = (theme) => { position : 'relative', width : '100%', - height : '100%' + height : '100%', + '& p' : + { + position : 'absolute', + float : 'left', + top : '50%', + left : '50%', + transform : 'translate(-50%, -50%)', + color : 'rgba(255, 255, 255, 0.5)', + fontSize : '7em', + zIndex : 30, + margin : 0, + opacity : 0, + transition : 'opacity 0.1s ease-in-out', + '&.hover' : + { + opacity : 1 + }, + '&.smallContainer' : + { + fontSize : '3em' + } + } }, controls : { @@ -100,27 +122,6 @@ const styles = (theme) => '&.hover' : { opacity : 1 - }, - '& p' : - { - position : 'absolute', - float : 'left', - top : '50%', - left : '50%', - transform : 'translate(-50%, -50%)', - color : 'rgba(255, 255, 255, 0.5)', - fontSize : '7em', - margin : 0, - opacity : 0, - transition : 'opacity 0.1s ease-in-out', - '&.hover' : - { - opacity : 1 - }, - '&.smallContainer' : - { - fontSize : '3em' - } } }, ptt : @@ -330,47 +331,46 @@ const Me = (props) => /> } -
setHover(true)} - onMouseOut={() => setHover(false)} - onTouchStart={() => - { - if (touchTimeout) - clearTimeout(touchTimeout); - - setHover(true); - }} - onTouchEnd={() => - { - if (touchTimeout) - clearTimeout(touchTimeout); - - touchTimeout = setTimeout(() => - { - setHover(false); - }, 2000); - }} > -

+

+ { !settings.buttonControlBar && +
- -

+ onMouseOver={() => setHover(true)} + onMouseOut={() => setHover(false)} + onTouchStart={() => + { + if (touchTimeout) + clearTimeout(touchTimeout); - - -
+ setHover(true); + }} + onTouchEnd={() => + { + if (touchTimeout) + clearTimeout(touchTimeout); + + touchTimeout = setTimeout(() => + { + setHover(false); + }, 2000); + }} + > + + { smallContainer ? } } -
-
- -
+ + { smallContainer ? } } -
-
- { me.browser.platform !== 'mobile' && - -
+ + { me.browser.platform !== 'mobile' && + { smallContainer ? } } -
-
- } -
-
+ + } + +
+ }

-
- { smallContainer ? - - { - roomClient.disableExtraVideo(producer.id); - }} - > - + { smallContainer ? + + { + roomClient.disableExtraVideo(producer.id); + }} + > + - - : - - { - roomClient.disableExtraVideo(producer.id); - }} - > - - - } -
+ + : + + { + roomClient.disableExtraVideo(producer.id); + }} + > + + + }
diff --git a/app/src/components/Containers/Peer.js b/app/src/components/Containers/Peer.js index 4fc495e..ab90a13 100644 --- a/app/src/components/Containers/Peer.js +++ b/app/src/components/Containers/Peer.js @@ -249,55 +249,53 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- { smallContainer ? - - { - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} - > - { micEnabled ? - - : - - } - - : - - { - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} - > - { micEnabled ? - - : - - } - - } -
+ { smallContainer ? + + { + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + + : + + { + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + + } { browser.platform !== 'mobile' && @@ -308,48 +306,46 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- { smallContainer ? - - { - toggleConsumerWindow(webcamConsumer); - }} - > - - - : - - { - toggleConsumerWindow(webcamConsumer); - }} - > - - - } -
+ { smallContainer ? + + { + toggleConsumerWindow(webcamConsumer); + }} + > + + + : + + { + toggleConsumerWindow(webcamConsumer); + }} + > + + + } } @@ -360,42 +356,40 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- { smallContainer ? - - { - toggleConsumerFullscreen(webcamConsumer); - }} - > - - - : - - { - toggleConsumerFullscreen(webcamConsumer); - }} - > - - - } -
+ { smallContainer ? + + { + toggleConsumerFullscreen(webcamConsumer); + }} + > + + + : + + { + toggleConsumerFullscreen(webcamConsumer); + }} + > + + + } @@ -507,48 +501,46 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- { smallContainer ? - - { - toggleConsumerWindow(consumer); - }} - > - - - : - - { - toggleConsumerWindow(consumer); - }} - > - - - } -
+ { smallContainer ? + + { + toggleConsumerWindow(consumer); + }} + > + + + : + + { + toggleConsumerWindow(consumer); + }} + > + + + } } @@ -559,42 +551,40 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- { smallContainer ? - - { - toggleConsumerFullscreen(consumer); - }} - > - - - : - - { - toggleConsumerFullscreen(consumer); - }} - > - - - } -
+ { smallContainer ? + + { + toggleConsumerFullscreen(consumer); + }} + > + + + : + + { + toggleConsumerFullscreen(consumer); + }} + > + + + } @@ -694,26 +684,24 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- - { - toggleConsumerWindow(screenConsumer); - }} - > - - -
+ + { + toggleConsumerWindow(screenConsumer); + }} + > + + } @@ -724,23 +712,21 @@ const Peer = (props) => })} placement={smallScreen ? 'top' : 'left'} > -
- - { - toggleConsumerFullscreen(screenConsumer); - }} - > - - -
+ + { + toggleConsumerFullscreen(screenConsumer); + }} + > + + + ({ + root : + { + position : 'fixed', + display : 'flex', + [theme.breakpoints.up('md')] : + { + top : '50%', + transform : 'translate(0%, -50%)', + flexDirection : 'column', + justifyContent : 'center', + alignItems : 'center', + left : theme.spacing(1) + }, + [theme.breakpoints.down('sm')] : + { + flexDirection : 'row', + bottom : theme.spacing(1), + left : '50%', + transform : 'translate(-50%, -0%)' + } + }, + fab : + { + margin : theme.spacing(1) + }, + show : + { + opacity : 1, + transition : 'opacity .5s' + }, + hide : + { + opacity : 0, + transition : 'opacity .5s' + } + }); + +const ButtonControlBar = (props) => +{ + const { + roomClient, + toolbarsVisible, + me, + micProducer, + webcamProducer, + screenProducer, + classes, + theme + } = props; + + let micState; + + let micTip; + + if (!me.canSendMic || !micProducer) + { + micState = 'unsupported'; + micTip = 'Audio unsupported'; + } + else if (!micProducer.locallyPaused && !micProducer.remotelyPaused) + { + micState = 'on'; + micTip = 'Mute audio'; + } + else + { + micState = 'off'; + micTip = 'Unmute audio'; + } + + let webcamState; + + let webcamTip; + + if (!me.canSendWebcam) + { + webcamState = 'unsupported'; + webcamTip = 'Video unsupported'; + } + else if (webcamProducer) + { + webcamState = 'on'; + webcamTip = 'Stop video'; + } + else + { + webcamState = 'off'; + webcamTip = 'Start video'; + } + + let screenState; + + let screenTip; + + if (!me.canShareScreen) + { + screenState = 'unsupported'; + screenTip = 'Screen sharing not supported'; + } + else if (screenProducer) + { + screenState = 'on'; + screenTip = 'Stop screen sharing'; + } + else + { + screenState = 'off'; + screenTip = 'Start screen sharing'; + } + + const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); + + return ( +
+ + + { + micState === 'on' ? + roomClient.muteMic() : + roomClient.unmuteMic(); + }} + > + { micState === 'on' ? + + : + + } + + + + + { + webcamState === 'on' ? + roomClient.disableWebcam() : + roomClient.enableWebcam(); + }} + > + { webcamState === 'on' ? + + : + + } + + + + + { + switch (screenState) + { + case 'on': + { + roomClient.disableScreenSharing(); + break; + } + case 'off': + { + roomClient.enableScreenSharing(); + break; + } + default: + { + break; + } + } + }} + > + { screenState === 'on' || screenState === 'unsupported' ? + + :null + } + { screenState === 'off' ? + + :null + } + + +
+ ); +}; + +ButtonControlBar.propTypes = +{ + roomClient : PropTypes.any.isRequired, + toolbarsVisible : PropTypes.bool.isRequired, + me : appPropTypes.Me.isRequired, + micProducer : appPropTypes.Producer, + webcamProducer : appPropTypes.Producer, + screenProducer : appPropTypes.Producer, + classes : PropTypes.object.isRequired, + theme : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + toolbarsVisible : state.room.toolbarsVisible, + ...meProducersSelector(state), + me : state.me + }); + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room.toolbarsVisible === next.room.toolbarsVisible && + prev.producers === next.producers && + prev.me === next.me + ); + } + } +)(withStyles(styles, { withTheme: true })(ButtonControlBar))); \ No newline at end of file diff --git a/app/src/components/MeetingViews/Democratic.js b/app/src/components/MeetingViews/Democratic.js index e2a703e..f2fff1a 100644 --- a/app/src/components/MeetingViews/Democratic.js +++ b/app/src/components/MeetingViews/Democratic.js @@ -11,10 +11,9 @@ import Peer from '../Containers/Peer'; import Me from '../Containers/Me'; const RATIO = 1.334; -const PADDING_V = 50; -const PADDING_H = 0; +const PADDING = 60; -const styles = () => +const styles = (theme) => ({ root : { @@ -36,6 +35,14 @@ const styles = () => { paddingTop : 60, transition : 'padding .5s' + }, + buttonControlBar : + { + paddingLeft : 60, + [theme.breakpoints.down('sm')] : + { + paddingLeft : 0 + } } }); @@ -66,9 +73,11 @@ class Democratic extends React.PureComponent return; } - const width = this.peersRef.current.clientWidth - PADDING_H; - const height = this.peersRef.current.clientHeight - - (this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H); + const width = + this.peersRef.current.clientWidth - (this.props.buttonControlBar ? PADDING : 0); + const height = + this.peersRef.current.clientHeight - + (this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING : 0); let x, y, space; @@ -130,6 +139,7 @@ class Democratic extends React.PureComponent spotlightsPeers, toolbarsVisible, permanentTopBar, + buttonControlBar, classes } = this.props; @@ -144,7 +154,8 @@ class Democratic extends React.PureComponent className={classnames( classes.root, toolbarsVisible || permanentTopBar ? - classes.showingToolBar : classes.hiddenToolBar + classes.showingToolBar : classes.hiddenToolBar, + buttonControlBar ? classes.buttonControlBar : null )} ref={this.peersRef} > @@ -172,21 +183,23 @@ class Democratic extends React.PureComponent Democratic.propTypes = { - advancedMode : PropTypes.bool, - boxes : PropTypes.number, - spotlightsPeers : PropTypes.array.isRequired, - toolbarsVisible : PropTypes.bool.isRequired, - permanentTopBar : PropTypes.bool, - classes : PropTypes.object.isRequired + advancedMode : PropTypes.bool, + boxes : PropTypes.number, + spotlightsPeers : PropTypes.array.isRequired, + toolbarsVisible : PropTypes.bool.isRequired, + permanentTopBar : PropTypes.bool.isRequired, + buttonControlBar : PropTypes.bool.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => { return { - boxes : videoBoxesSelector(state), - spotlightsPeers : spotlightPeersSelector(state), - toolbarsVisible : state.room.toolbarsVisible, - permanentTopBar : state.settings.permanentTopBar + boxes : videoBoxesSelector(state), + spotlightsPeers : spotlightPeersSelector(state), + toolbarsVisible : state.room.toolbarsVisible, + permanentTopBar : state.settings.permanentTopBar, + buttonControlBar : state.settings.buttonControlBar }; }; @@ -203,8 +216,9 @@ export default connect( prev.consumers === next.consumers && prev.room.spotlights === next.room.spotlights && prev.room.toolbarsVisible === next.room.toolbarsVisible && - prev.settings.permanentTopBar === next.settings.permanentTopBar + prev.settings.permanentTopBar === next.settings.permanentTopBar && + prev.settings.buttonControlBar === next.settings.buttonControlBar ); } } -)(withStyles(styles)(Democratic)); +)(withStyles(styles, { withTheme: true })(Democratic)); diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 7562ee4..c216f46 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -25,6 +25,7 @@ import Settings from './Settings/Settings'; import TopBar from './Controls/TopBar'; import WakeLock from 'react-wakelock-react16'; import ExtraVideo from './Controls/ExtraVideo'; +import ButtonControlBar from './Controls/ButtonControlBar'; const TIMEOUT = 5 * 1000; @@ -143,6 +144,7 @@ class Room extends React.PureComponent browser, advancedMode, showNotifications, + buttonControlBar, toolAreaOpen, toggleToolArea, classes, @@ -214,6 +216,10 @@ class Room extends React.PureComponent + { buttonControlBar && + + } + { room.lockDialogOpen && } @@ -236,6 +242,7 @@ Room.propTypes = browser : PropTypes.object.isRequired, advancedMode : PropTypes.bool.isRequired, showNotifications : PropTypes.bool.isRequired, + buttonControlBar : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, @@ -249,6 +256,7 @@ const mapStateToProps = (state) => browser : state.me.browser, advancedMode : state.settings.advancedMode, showNotifications : state.settings.showNotifications, + buttonControlBar : state.settings.buttonControlBar, toolAreaOpen : state.toolarea.toolAreaOpen }); @@ -276,6 +284,7 @@ export default connect( prev.me.browser === next.me.browser && prev.settings.advancedMode === next.settings.advancedMode && prev.settings.showNotifications === next.settings.showNotifications && + prev.settings.buttonControlBar === next.settings.buttonControlBar && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js index a34a5e1..a859ab4 100644 --- a/app/src/components/Settings/AppearenceSettings.js +++ b/app/src/components/Settings/AppearenceSettings.js @@ -30,6 +30,7 @@ const AppearenceSettings = ({ settings, onTogglePermanentTopBar, onToggleHiddenControls, + onToggleButtonControlBar, onToggleShowNotifications, handleChangeMode, classes @@ -102,6 +103,14 @@ const AppearenceSettings = ({ defaultMessage : 'Hidden media controls' })} /> + } + label={intl.formatMessage({ + id : 'settings.buttonControlBar', + defaultMessage : 'Separate media controls' + })} + /> } @@ -120,6 +129,7 @@ AppearenceSettings.propTypes = settings : PropTypes.object.isRequired, onTogglePermanentTopBar : PropTypes.func.isRequired, onToggleHiddenControls : PropTypes.func.isRequired, + onToggleButtonControlBar : PropTypes.func.isRequired, onToggleShowNotifications : PropTypes.func.isRequired, handleChangeMode : PropTypes.func.isRequired, classes : PropTypes.object.isRequired @@ -135,6 +145,7 @@ const mapDispatchToProps = { onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, onToggleHiddenControls : settingsActions.toggleHiddenControls, onToggleShowNotifications : settingsActions.toggleShowNotifications, + onToggleButtonControlBar : settingsActions.toggleButtonControlBar, handleChangeMode : roomActions.setDisplayMode }; diff --git a/app/src/components/Settings/MediaSettings.js b/app/src/components/Settings/MediaSettings.js index d215181..28a69a2 100644 --- a/app/src/components/Settings/MediaSettings.js +++ b/app/src/components/Settings/MediaSettings.js @@ -265,7 +265,7 @@ const MediaSettings = ({ />} label={intl.formatMessage({ id : 'settings.echoCancellation', - defaultMessage : 'Echo Cancellation' + defaultMessage : 'Echo cancellation' })} /> } label={intl.formatMessage({ id : 'settings.autoGainControl', - defaultMessage : 'Auto Gain Control' + defaultMessage : 'Auto gain control' })} /> } label={intl.formatMessage({ id : 'settings.noiseSuppression', - defaultMessage : 'Noise Suppression' + defaultMessage : 'Noise suppression' })} /> diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 7186fdc..f306726 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -18,6 +18,7 @@ const initialState = hiddenControls : false, showNotifications : true, notificationSounds : true, + buttonControlBar : window.config.buttonControlBar || false, ...window.config.defaultAudio }; @@ -145,6 +146,13 @@ const settings = (state = initialState, action) => return { ...state, permanentTopBar }; } + case 'TOGGLE_BUTTON_CONTROL_BAR': + { + const buttonControlBar = !state.buttonControlBar; + + return { ...state, buttonControlBar }; + } + case 'TOGGLE_HIDDEN_CONTROLS': { const hiddenControls = !state.hiddenControls; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 26724a4..7da15eb 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "无法保存文件", "filesharing.startingFileShare": "正在尝试共享文件", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index b80ab14..ee859d2 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -130,6 +130,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Není možné uložit soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 3eabb28..428d104 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index cab3586..c74026f 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.startingFileShare": "Forsøger at dele filen", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 5a83427..10047cf 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 2b40a30..475acd2 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -131,6 +131,10 @@ "settings.hiddenControls": "Hidden media controls", "settings.notificationSounds": "Notification sounds", "settings.showNotifications": "Show notifications", + "settings.buttonControlBar": "Separate media controls", + "settings.echoCancellation": "Echo cancellation", + "settings.autoGainControl": "Auto gain control", + "settings.noiseSuppression": "Noise suppression", "filesharing.saveFileError": "Unable to save file", "filesharing.startingFileShare": "Attempting to share file", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 63c2f7d..6c757cd 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 4471861..73493f5 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.startingFileShare": "Début du transfert de fichier", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 682dfbe..22db8fa 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -131,6 +131,10 @@ "settings.hiddenControls": "Skrivene kontrole medija", "settings.notificationSounds": "Zvuk obavijesti", "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 262f117..8c68590 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -131,6 +131,10 @@ "settings.hiddenControls": "Média Gombok automatikus elrejtése", "settings.notificationSounds": "Értesítések hangjelzéssel", "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 27b3194..ba849d9 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -130,6 +130,10 @@ "settings.hiddenControls": "Controlli media nascosti", "settings.notificationSounds": "Suoni di notifica", "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Impossibile salvare file", "filesharing.startingFileShare": "Tentativo di condivisione file", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index d355c84..6d4e97b 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -125,6 +125,10 @@ "settings.hiddenControls": "Slēpto mediju vadība", "settings.notificationSounds": "Paziņojumu skaņas", "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Nav iespējams saglabāt failu", "filesharing.startingFileShare": "Tiek mēģināts kopīgot failu", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 59edd6c..ea9b0b8 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -131,6 +131,10 @@ "settings.hiddenControls": "Skjul media knapper", "settings.notificationSounds": "Varslingslyder", "settings.showNotifications": "Vis varslinger", + "settings.buttonControlBar": "Separate media knapper", + "settings.echoCancellation": "Echokansellering", + "settings.autoGainControl": "Auto gain kontroll", + "settings.noiseSuppression": "Støy reduksjon", "filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.startingFileShare": "Starter fildeling", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 1413c73..859a29f 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 3b0a078..7faed7c 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index e666ddf..3188e91 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.startingFileShare": "Partajarea fișierului", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 16aaae0..aa6a8cb 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -128,6 +128,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index fe9dd52..4932fc4 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -131,6 +131,10 @@ "settings.hiddenControls": null, "settings.notificationSounds": null, "settings.showNotifications": null, + "settings.buttonControlBar": null, + "settings.echoCancellation": null, + "settings.autoGainControl": null, + "settings.noiseSuppression": null, "filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.startingFileShare": "Спроба поділитися файлом", From 4c6d9291bf767384a67fea6b449eb05d942e7511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 14:57:42 +0200 Subject: [PATCH 05/43] Fix regression on advanced mode, ref #309 --- app/src/components/Containers/Me.js | 43 +++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 8279a61..ead95b1 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -77,28 +77,28 @@ const styles = (theme) => { position : 'relative', width : '100%', - height : '100%', - '& p' : + height : '100%' + }, + meTag : + { + position : 'absolute', + float : 'left', + top : '50%', + left : '50%', + transform : 'translate(-50%, -50%)', + color : 'rgba(255, 255, 255, 0.5)', + fontSize : '7em', + zIndex : 30, + margin : 0, + opacity : 0, + transition : 'opacity 0.1s ease-in-out', + '&.hover' : { - position : 'absolute', - float : 'left', - top : '50%', - left : '50%', - transform : 'translate(-50%, -50%)', - color : 'rgba(255, 255, 255, 0.5)', - fontSize : '7em', - zIndex : 30, - margin : 0, - opacity : 0, - transition : 'opacity 0.1s ease-in-out', - '&.hover' : - { - opacity : 1 - }, - '&.smallContainer' : - { - fontSize : '3em' - } + opacity : 1 + }, + '&.smallContainer' : + { + fontSize : '3em' } }, controls : @@ -333,6 +333,7 @@ const Me = (props) => }

Date: Thu, 7 May 2020 15:15:44 +0200 Subject: [PATCH 06/43] Respect hide media buttons setting on button control bar, ref #309 --- app/public/config/config.example.js | 2 ++ app/src/components/Controls/ButtonControlBar.js | 10 +++++++++- app/src/components/Room.js | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index 38334f3..4bbc13f 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -57,6 +57,8 @@ var config = // If true, will show media control buttons in separate // control bar, not in the ME container. buttonControlBar : false, + // Timeout for autohiding topbar and button control bar + hideTimeout : 3000, lastN : 4, mobileLastN : 1, // Highest number of speakers user can select diff --git a/app/src/components/Controls/ButtonControlBar.js b/app/src/components/Controls/ButtonControlBar.js index a70c43e..2ee5b7e 100644 --- a/app/src/components/Controls/ButtonControlBar.js +++ b/app/src/components/Controls/ButtonControlBar.js @@ -60,6 +60,7 @@ const ButtonControlBar = (props) => const { roomClient, toolbarsVisible, + hiddenControls, me, micProducer, webcamProducer, @@ -133,7 +134,11 @@ const ButtonControlBar = (props) => return (

@@ -224,6 +229,7 @@ ButtonControlBar.propTypes = { roomClient : PropTypes.any.isRequired, toolbarsVisible : PropTypes.bool.isRequired, + hiddenControls : PropTypes.bool.isRequired, me : appPropTypes.Me.isRequired, micProducer : appPropTypes.Producer, webcamProducer : appPropTypes.Producer, @@ -235,6 +241,7 @@ ButtonControlBar.propTypes = const mapStateToProps = (state) => ({ toolbarsVisible : state.room.toolbarsVisible, + hiddenControls : state.settings.hiddenControls, ...meProducersSelector(state), me : state.me }); @@ -248,6 +255,7 @@ export default withRoomContext(connect( { return ( prev.room.toolbarsVisible === next.room.toolbarsVisible && + prev.settings.hiddenControls === next.settings.hiddenControls && prev.producers === next.producers && prev.me === next.me ); diff --git a/app/src/components/Room.js b/app/src/components/Room.js index c216f46..b6027ba 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -27,7 +27,7 @@ import WakeLock from 'react-wakelock-react16'; import ExtraVideo from './Controls/ExtraVideo'; import ButtonControlBar from './Controls/ButtonControlBar'; -const TIMEOUT = 5 * 1000; +const TIMEOUT = window.config.hideTimeout || 5000; const styles = (theme) => ({ From 2373bf44d8733e101d0f8cec4c948290d893cc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 21:03:07 +0200 Subject: [PATCH 07/43] Allow serving other than root of site, fixes #249 --- app/src/RoomClient.js | 8 ++++++-- app/src/index.js | 12 ++++++++++-- server/server.js | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index df276ae..a48a428 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -129,7 +129,8 @@ export default class RoomClient produce, forceTcp, displayName, - muted + muted, + basePath } = {}) { if (!peerId) @@ -152,6 +153,9 @@ export default class RoomClient // Whether we force TCP this._forceTcp = forceTcp; + // URL basepath + this._basePath = basePath; + // Use displayName if (displayName) store.dispatch(settingsActions.setDisplayName(displayName)); @@ -1719,7 +1723,7 @@ export default class RoomClient this._screenSharing = ScreenShare.create(this._device); - this._signalingSocket = io(this._signalingUrl); + this._signalingSocket = io(this._signalingUrl, { path: this._basePath }); this._spotlights = new Spotlights(this._maxSpotlights, this._signalingSocket); diff --git a/app/src/index.js b/app/src/index.js index cdefb40..e89d06d 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -115,6 +115,13 @@ function run() const forceTcp = parameters.get('forceTcp') === 'true'; const displayName = parameters.get('displayName'); const muted = parameters.get('muted') === 'true'; + + const { pathname } = window.location; + + let basePath = pathname.substring(0, pathname.lastIndexOf('/')); + + if (!basePath) + basePath = '/'; // Get current device. const device = deviceInfo(); @@ -134,7 +141,8 @@ function run() produce, forceTcp, displayName, - muted + muted, + basePath }); global.CLIENT = roomClient; @@ -146,7 +154,7 @@ function run() } persistor={persistor}> - + }> diff --git a/server/server.js b/server/server.js index fcde0b1..9fb775b 100755 --- a/server/server.js +++ b/server/server.js @@ -494,7 +494,7 @@ function isPathAlreadyTaken(url) */ async function runWebSocketServer() { - io = require('socket.io')(mainListener); + io = require('socket.io')(mainListener, { path: '/' }); io.use( sharedSession(session, { From ce8141eed7f5a97326df79806c184e45cc8b8bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 21:03:22 +0200 Subject: [PATCH 08/43] Cleanup --- server/lib/Peer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/Peer.js b/server/lib/Peer.js index 163e648..8e11ca2 100644 --- a/server/lib/Peer.js +++ b/server/lib/Peer.js @@ -64,10 +64,10 @@ class Peer extends EventEmitter // Iterate and close all mediasoup Transport associated to this Peer, so all // its Producers and Consumers will also be closed. - this.transports.forEach((transport) => + for (const transport of this.transports.values()) { transport.close(); - }); + } if (this.socket) this.socket.disconnect(true); From 16a59f1167aee0b561281b3dfb889d0f253b7e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 22:14:48 +0200 Subject: [PATCH 09/43] Don't show video mute icon for peer that does not have video visible. Fixes #302 --- .../MeetingDrawer/ParticipantList/ListPeer.js | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index d8b3fb3..7e163f0 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -126,7 +126,7 @@ const ListPeer = (props) => } - { screenConsumer && + { screenConsumer && spotlight && } - - - { - e.stopPropagation(); - - webcamEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : - roomClient.modifyPeerConsumer(peer.id, 'webcam', false); - }} + placement='bottom' > - { webcamEnabled ? - - : - - } - - + + { + e.stopPropagation(); + + webcamEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : + roomClient.modifyPeerConsumer(peer.id, 'webcam', false); + }} + > + { webcamEnabled ? + + : + + } + + + } Date: Thu, 7 May 2020 23:22:46 +0200 Subject: [PATCH 10/43] Revert server change, seems to be a problem with it. Ref #249 --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 9fb775b..fcde0b1 100755 --- a/server/server.js +++ b/server/server.js @@ -494,7 +494,7 @@ function isPathAlreadyTaken(url) */ async function runWebSocketServer() { - io = require('socket.io')(mainListener, { path: '/' }); + io = require('socket.io')(mainListener); io.use( sharedSession(session, { From 837aa1ace26b86b377d5394f9517a20ca9cfb60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 8 May 2020 00:11:39 +0200 Subject: [PATCH 11/43] Revert change for now, ref #249 --- app/src/RoomClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index a48a428..dba7c64 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1723,7 +1723,7 @@ export default class RoomClient this._screenSharing = ScreenShare.create(this._device); - this._signalingSocket = io(this._signalingUrl, { path: this._basePath }); + this._signalingSocket = io(this._signalingUrl); this._spotlights = new Spotlights(this._maxSpotlights, this._signalingSocket); From 3b779bd81d1a2c61400667618c5bb90f9255c8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Fri, 8 May 2020 00:20:35 +0200 Subject: [PATCH 12/43] Add shotcuts to help and add labels --- app/src/components/Controls/Help.js | 75 +++++++++++++++++++++++++-- app/src/components/Controls/TopBar.js | 8 +-- app/src/translations/cn.json | 5 +- app/src/translations/cs.json | 3 ++ app/src/translations/de.json | 3 ++ app/src/translations/dk.json | 3 ++ app/src/translations/el.json | 3 ++ app/src/translations/en.json | 3 ++ app/src/translations/es.json | 3 ++ app/src/translations/fr.json | 3 ++ app/src/translations/hr.json | 3 ++ app/src/translations/hu.json | 3 ++ app/src/translations/it.json | 5 +- app/src/translations/lv.json | 3 ++ app/src/translations/nb.json | 3 ++ app/src/translations/pl.json | 3 ++ app/src/translations/pt.json | 3 ++ app/src/translations/ro.json | 3 ++ app/src/translations/tr.json | 3 ++ app/src/translations/uk.json | 3 ++ 20 files changed, 132 insertions(+), 9 deletions(-) diff --git a/app/src/components/Controls/Help.js b/app/src/components/Controls/Help.js index 67be03f..3091ec4 100644 --- a/app/src/components/Controls/Help.js +++ b/app/src/components/Controls/Help.js @@ -4,13 +4,26 @@ import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../RoomContext'; import * as roomActions from '../../actions/roomActions'; import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; +import { useIntl, FormattedMessage } from 'react-intl'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; +const shortcuts=[ + { key: 'm', label: 'device.muteAudio', defaultMessage: 'Mute Audio' }, + { key: 'v', label: 'device.stopVideo', defaultMessage: 'Mute Video' }, + { key: '1', label: 'label.democratic', defaultMessage: 'Democratic View' }, + { key: '2', label: 'label.filmstrip', defaultMessage: 'Filmstrip View' }, + { key: 'space', label: 'me.mutedPTT', defaultMessage: 'Push SPACE to talk' }, + { key: 'a', label: 'label.advanced', defaultMessage: 'Show advanced information' } +]; const styles = (theme) => ({ dialogPaper : @@ -31,7 +44,27 @@ const styles = (theme) => [theme.breakpoints.down('xs')] : { width : '90vw' - } + }, + display : 'flex', + flexDirection : 'column' + }, + paper : { + padding : theme.spacing(1), + textAlign : 'center', + color : theme.palette.text.secondary, + whiteSpace : 'nowrap', + marginRight : theme.spacing(3), + marginBottom : theme.spacing(1), + minWidth : theme.spacing(8) + }, + shortcuts : { + display : 'flex', + flexDirection : 'row', + alignItems : 'center' + }, + tabsHeader : + { + flexGrow : 1 } }); @@ -41,6 +74,8 @@ const Help = ({ classes }) => { + const intl = useIntl(); + return ( + + + + + + {shortcuts.map((value, index) => + { + return ( +
+ + {value.key} + + +
+ ); + })} + +
+
); diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index 3e2c6a0..6c4fd1f 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -68,6 +68,18 @@ const peer = (state = {}, action) => return { ...state, roles }; } + case 'STOP_PEER_AUDIO_IN_PROGRESS': + return { + ...state, + stopPeerAudioInProgress : action.payload.flag + }; + + case 'STOP_PEER_VIDEO_IN_PROGRESS': + return { + ...state, + stopPeerVideoInProgress : action.payload.flag + }; + default: return state; } @@ -102,6 +114,8 @@ const peers = (state = {}, action) => case 'ADD_CONSUMER': case 'ADD_PEER_ROLE': case 'REMOVE_PEER_ROLE': + case 'STOP_PEER_AUDIO_IN_PROGRESS': + case 'STOP_PEER_VIDEO_IN_PROGRESS': { const oldPeer = state[action.payload.peerId]; diff --git a/server/lib/Room.js b/server/lib/Room.js index 02ba41c..77f00ee 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1369,6 +1369,29 @@ class Room extends EventEmitter break; } + case 'moderator:mute': + { + if ( + !peer.roles.some( + (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) + ) + ) + throw new Error('peer not authorized'); + + const { peerId } = request.data; + + const mutePeer = this._peers[peerId]; + + if (!mutePeer) + throw new Error(`peer with id "${peerId}" not found`); + + this._notification(mutePeer.socket, 'moderator:mute'); + + cb(); + + break; + } + case 'moderator:muteAll': { if ( @@ -1386,6 +1409,29 @@ class Room extends EventEmitter break; } + case 'moderator:stopVideo': + { + if ( + !peer.roles.some( + (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) + ) + ) + throw new Error('peer not authorized'); + + const { peerId } = request.data; + + const stopVideoPeer = this._peers[peerId]; + + if (!stopVideoPeer) + throw new Error(`peer with id "${peerId}" not found`); + + this._notification(stopVideoPeer.socket, 'moderator:stopVideo'); + + cb(); + + break; + } + case 'moderator:stopAllVideo': { if ( From 0de99d6da8a4cec323a6cd8dcfb63b79bbe181ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Fri, 8 May 2020 13:24:39 +0200 Subject: [PATCH 14/43] Update Hungarian language --- app/src/translations/hu.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 1b2db12..6ed3eac 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -133,11 +133,11 @@ "settings.lastn": "A látható videók száma", "settings.hiddenControls": "Média Gombok automatikus elrejtése", "settings.notificationSounds": "Értesítések hangjelzéssel", - "settings.showNotifications": null, - "settings.buttonControlBar": null, - "settings.echoCancellation": null, - "settings.autoGainControl": null, - "settings.noiseSuppression": null, + "settings.showNotifications": "Értesítések megjelenítése", + "settings.buttonControlBar": "Médiavezérlő gombok leválasztása", + "settings.echoCancellation": "Visszhangelnyomás", + "settings.autoGainControl": "Automatikus hangerősítés", + "settings.noiseSuppression": "Zajelnyomás", "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", From 220d4dd99dbcb4c821aab3351431d0921304c67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Fri, 8 May 2020 14:46:30 +0200 Subject: [PATCH 15/43] fix in lang hu --- app/src/translations/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 6ed3eac..be0342d 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -136,7 +136,7 @@ "settings.showNotifications": "Értesítések megjelenítése", "settings.buttonControlBar": "Médiavezérlő gombok leválasztása", "settings.echoCancellation": "Visszhangelnyomás", - "settings.autoGainControl": "Automatikus hangerősítés", + "settings.autoGainControl": "Automatikus hangerő", "settings.noiseSuppression": "Zajelnyomás", "filesharing.saveFileError": "A file-t nem sikerült elmenteni", From a49258e840b1813bf8aa21736d90db3241722381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 8 May 2020 16:19:55 +0200 Subject: [PATCH 16/43] New option for handling permissions in rooms. Set allowWhenRoleMissing to permit actions before a peer with that permission joins. Ref #303 --- app/src/RoomClient.js | 10 +- app/src/actions/roomActions.js | 12 +- .../AccessControl/LockDialog/ListLobbyPeer.js | 28 ++- .../AccessControl/LockDialog/LockDialog.js | 26 +- app/src/components/Containers/Me.js | 37 +-- app/src/components/Controls/TopBar.js | 56 +++-- .../MeetingDrawer/Chat/ChatInput.js | 28 ++- .../MeetingDrawer/Chat/ChatModerator.js | 26 +- .../MeetingDrawer/FileSharing/FileSharing.js | 28 ++- .../FileSharing/FileSharingModerator.js | 26 +- .../ParticipantList/ParticipantList.js | 33 +-- app/src/components/Selectors.js | 53 ++++ app/src/permissions.js | 20 ++ app/src/reducers/room.js | 26 +- server/access.js | 11 + server/config/config.example.js | 52 ++-- server/lib/Room.js | 233 ++++++++---------- server/permissions.js | 20 ++ 18 files changed, 437 insertions(+), 288 deletions(-) create mode 100644 app/src/permissions.js create mode 100644 server/access.js create mode 100644 server/permissions.js diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index cffdd2a..2b2b387 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2847,8 +2847,8 @@ export default class RoomClient roles, peers, tracker, - permissionsFromRoles, - userRoles, + roomPermissions, + allowWhenRoleMissing, chatHistory, fileHistory, lastNHistory, @@ -2874,8 +2874,10 @@ export default class RoomClient store.dispatch(meActions.loggedIn(authenticated)); - store.dispatch(roomActions.setUserRoles(userRoles)); - store.dispatch(roomActions.setPermissionsFromRoles(permissionsFromRoles)); + store.dispatch(roomActions.setRoomPermissions(roomPermissions)); + + if (allowWhenRoleMissing) + store.dispatch(roomActions.setAllowWhenRoleMissing(allowWhenRoleMissing)); const myRoles = store.getState().me.roles; diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index 7c44835..cbfde37 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -177,14 +177,14 @@ export const setClearFileSharingInProgress = (flag) => payload : { flag } }); -export const setUserRoles = (userRoles) => +export const setRoomPermissions = (roomPermissions) => ({ - type : 'SET_USER_ROLES', - payload : { userRoles } + type : 'SET_ROOM_PERMISSIONS', + payload : { roomPermissions } }); -export const setPermissionsFromRoles = (permissionsFromRoles) => +export const setAllowWhenRoleMissing = (allowWhenRoleMissing) => ({ - type : 'SET_PERMISSIONS_FROM_ROLES', - payload : { permissionsFromRoles } + type : 'SET_ALLOW_WHEN_ROLE_MISSING', + payload : { allowWhenRoleMissing } }); diff --git a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js index 9e73e82..050994d 100644 --- a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js +++ b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js @@ -5,6 +5,8 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; +import { permissions } from '../../../permissions'; +import { makePermissionSelector } from '../../Selectors'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import IconButton from '@material-ui/core/IconButton'; @@ -85,28 +87,32 @@ ListLobbyPeer.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state, { id }) => +const makeMapStateToProps = (initialState, { id }) => { - return { - peer : state.lobbyPeers[id], - promotionInProgress : state.room.lobbyPeersPromotionInProgress, - canPromote : - state.me.roles.some((role) => - state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) + const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER); + + const mapStateToProps = (state) => + { + return { + peer : state.lobbyPeers[id], + promotionInProgress : state.room.lobbyPeersPromotionInProgress, + canPromote : hasPermission(state) + }; }; + + return mapStateToProps; }; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, null, null, { areStatesEqual : (next, prev) => { return ( - prev.room.permissionsFromRoles === next.room.permissionsFromRoles && - prev.room.lobbyPeersPromotionInProgress === - next.room.lobbyPeersPromotionInProgress && + prev.room === next.room && + prev.peers === next.peers && // For checking permissions prev.me.roles === next.me.roles && prev.lobbyPeers === next.lobbyPeers ); diff --git a/app/src/components/AccessControl/LockDialog/LockDialog.js b/app/src/components/AccessControl/LockDialog/LockDialog.js index 4d6cd24..c3dbd6a 100644 --- a/app/src/components/AccessControl/LockDialog/LockDialog.js +++ b/app/src/components/AccessControl/LockDialog/LockDialog.js @@ -1,8 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; import { - lobbyPeersKeySelector + lobbyPeersKeySelector, + makePermissionSelector } from '../../Selectors'; +import { permissions } from '../../../permissions'; import * as appPropTypes from '../../appPropTypes'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; @@ -140,15 +142,20 @@ LockDialog.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => +const makeMapStateToProps = () => { - return { - room : state.room, - lobbyPeers : lobbyPeersKeySelector(state), - canPromote : - state.me.roles.some((role) => - state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) + const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER); + + const mapStateToProps = (state) => + { + return { + room : state.room, + lobbyPeers : lobbyPeersKeySelector(state), + canPromote : hasPermission(state) + }; }; + + return mapStateToProps; }; const mapDispatchToProps = { @@ -157,7 +164,7 @@ const mapDispatchToProps = { }; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, mapDispatchToProps, null, { @@ -166,6 +173,7 @@ export default withRoomContext(connect( return ( prev.room === next.room && prev.me.roles === next.me.roles && + prev.peers === next.peers && prev.lobbyPeers === next.lobbyPeers ); } diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index ead95b1..aa3a9a3 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -1,6 +1,10 @@ import React, { useState } from 'react'; import { connect } from 'react-redux'; -import { meProducersSelector } from '../Selectors'; +import { + meProducersSelector, + makePermissionSelector +} from '../Selectors'; +import { permissions } from '../../permissions'; import { withRoomContext } from '../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; import PropTypes from 'prop-types'; @@ -807,32 +811,37 @@ Me.propTypes = theme : PropTypes.object.isRequired }; -const mapStateToProps = (state) => +const makeMapStateToProps = () => { - return { - me : state.me, - ...meProducersSelector(state), - settings : state.settings, - activeSpeaker : state.me.id === state.room.activeSpeakerId, - canShareScreen : - state.me.roles.some((role) => - state.room.permissionsFromRoles.SHARE_SCREEN.includes(role)) + const hasPermission = makePermissionSelector(permissions.SHARE_SCREEN); + + const mapStateToProps = (state) => + { + return { + me : state.me, + ...meProducersSelector(state), + settings : state.settings, + activeSpeaker : state.me.id === state.room.activeSpeakerId, + canShareScreen : hasPermission(state) + }; }; + + return mapStateToProps; }; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, null, null, { areStatesEqual : (next, prev) => { return ( - prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.room === next.room && prev.me === next.me && + prev.peers === next.peers && prev.producers === next.producers && - prev.settings === next.settings && - prev.room.activeSpeakerId === next.room.activeSpeakerId + prev.settings === next.settings ); } } diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 43ee13f..b880c36 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -4,8 +4,10 @@ import PropTypes from 'prop-types'; import { lobbyPeersKeySelector, peersLengthSelector, - raisedHandsSelector + raisedHandsSelector, + makePermissionSelector } from '../Selectors'; +import { permissions } from '../../permissions'; import * as appPropTypes from '../appPropTypes'; import { withRoomContext } from '../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; @@ -751,27 +753,35 @@ TopBar.propTypes = theme : PropTypes.object.isRequired }; -const mapStateToProps = (state) => - ({ - room : state.room, - peersLength : peersLengthSelector(state), - lobbyPeers : lobbyPeersKeySelector(state), - permanentTopBar : state.settings.permanentTopBar, - loggedIn : state.me.loggedIn, - loginEnabled : state.me.loginEnabled, - myPicture : state.me.picture, - unread : state.toolarea.unreadMessages + - state.toolarea.unreadFiles + raisedHandsSelector(state), - canProduceExtraVideo : - state.me.roles.some((role) => - state.room.permissionsFromRoles.EXTRA_VIDEO.includes(role)), - canLock : - state.me.roles.some((role) => - state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)), - canPromote : - state.me.roles.some((role) => - state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) - }); +const makeMapStateToProps = () => +{ + const hasExtraVideoPermission = + makePermissionSelector(permissions.EXTRA_VIDEO); + + const hasLockPermission = + makePermissionSelector(permissions.CHANGE_ROOM_LOCK); + + const hasPromotionPermission = + makePermissionSelector(permissions.PROMOTE_PEER); + + const mapStateToProps = (state) => + ({ + room : state.room, + peersLength : peersLengthSelector(state), + lobbyPeers : lobbyPeersKeySelector(state), + permanentTopBar : state.settings.permanentTopBar, + loggedIn : state.me.loggedIn, + loginEnabled : state.me.loginEnabled, + myPicture : state.me.picture, + unread : state.toolarea.unreadMessages + + state.toolarea.unreadFiles + raisedHandsSelector(state), + canProduceExtraVideo : hasExtraVideoPermission(state), + canLock : hasLockPermission(state), + canPromote : hasPromotionPermission(state) + }); + + return mapStateToProps; +}; const mapDispatchToProps = (dispatch) => ({ @@ -811,7 +821,7 @@ const mapDispatchToProps = (dispatch) => }); export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, mapDispatchToProps, null, { diff --git a/app/src/components/MeetingDrawer/Chat/ChatInput.js b/app/src/components/MeetingDrawer/Chat/ChatInput.js index 480bb26..bb07f98 100644 --- a/app/src/components/MeetingDrawer/Chat/ChatInput.js +++ b/app/src/components/MeetingDrawer/Chat/ChatInput.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; +import { permissions } from '../../../permissions'; +import { makePermissionSelector } from '../../Selectors'; import Paper from '@material-ui/core/Paper'; import InputBase from '@material-ui/core/InputBase'; import IconButton from '@material-ui/core/IconButton'; @@ -119,26 +121,32 @@ ChatInput.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => - ({ - displayName : state.settings.displayName, - picture : state.me.picture, - canChat : - state.me.roles.some((role) => - state.room.permissionsFromRoles.SEND_CHAT.includes(role)) - }); +const makeMapStateToProps = () => +{ + const hasPermission = makePermissionSelector(permissions.SEND_CHAT); + + const mapStateToProps = (state) => + ({ + displayName : state.settings.displayName, + picture : state.me.picture, + canChat : hasPermission(state) + }); + + return mapStateToProps; +}; export default withRoomContext( connect( - mapStateToProps, + makeMapStateToProps, null, null, { areStatesEqual : (next, prev) => { return ( - prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.room === next.room && prev.me.roles === next.me.roles && + prev.peers === next.peers && prev.settings.displayName === next.settings.displayName && prev.me.picture === next.me.picture ); diff --git a/app/src/components/MeetingDrawer/Chat/ChatModerator.js b/app/src/components/MeetingDrawer/Chat/ChatModerator.js index a35675b..d7b1cad 100644 --- a/app/src/components/MeetingDrawer/Chat/ChatModerator.js +++ b/app/src/components/MeetingDrawer/Chat/ChatModerator.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import { withRoomContext } from '../../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; import { useIntl, FormattedMessage } from 'react-intl'; +import { permissions } from '../../../permissions'; +import { makePermissionSelector } from '../../Selectors'; import Button from '@material-ui/core/Button'; const styles = (theme) => @@ -76,16 +78,21 @@ ChatModerator.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => - ({ - isChatModerator : - state.me.roles.some((role) => - state.room.permissionsFromRoles.MODERATE_CHAT.includes(role)), - room : state.room - }); +const makeMapStateToProps = () => +{ + const hasPermission = makePermissionSelector(permissions.MODERATE_CHAT); + + const mapStateToProps = (state) => + ({ + isChatModerator : hasPermission(state), + room : state.room + }); + + return mapStateToProps; +}; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, null, null, { @@ -93,7 +100,8 @@ export default withRoomContext(connect( { return ( prev.room === next.room && - prev.me === next.me + prev.me === next.me && + prev.peers === next.peers ); } } diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js index 78ba569..8af1d8b 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js @@ -4,6 +4,8 @@ import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; +import { permissions } from '../../../permissions'; +import { makePermissionSelector } from '../../Selectors'; import FileList from './FileList'; import FileSharingModerator from './FileSharingModerator'; import Paper from '@material-ui/core/Paper'; @@ -131,30 +133,36 @@ FileSharing.propTypes = { classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => +const makeMapStateToProps = () => { - return { - canShareFiles : state.me.canShareFiles, - browser : state.me.browser, - tabOpen : state.toolarea.currentToolTab === 'files', - canShare : - state.me.roles.some((role) => - state.room.permissionsFromRoles.SHARE_FILE.includes(role)) + const hasPermission = makePermissionSelector(permissions.SHARE_FILE); + + const mapStateToProps = (state) => + { + return { + canShareFiles : state.me.canShareFiles, + browser : state.me.browser, + tabOpen : state.toolarea.currentToolTab === 'files', + canShare : hasPermission(state) + }; }; + + return mapStateToProps; }; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, null, null, { areStatesEqual : (next, prev) => { return ( - prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.room === next.room && prev.me.browser === next.me.browser && prev.me.roles === next.me.roles && prev.me.canShareFiles === next.me.canShareFiles && + prev.peers === next.peers && prev.toolarea.currentToolTab === next.toolarea.currentToolTab ); } diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js b/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js index e38e54c..05f35e8 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import { withRoomContext } from '../../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; import { useIntl, FormattedMessage } from 'react-intl'; +import { permissions } from '../../../permissions'; +import { makePermissionSelector } from '../../Selectors'; import Button from '@material-ui/core/Button'; const styles = (theme) => @@ -76,16 +78,21 @@ FileSharingModerator.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => - ({ - isFileSharingModerator : - state.me.roles.some((role) => - state.room.permissionsFromRoles.MODERATE_FILES.includes(role)), - room : state.room - }); +const makeMapStateToProps = () => +{ + const hasPermission = makePermissionSelector(permissions.MODERATE_FILES); + + const mapStateToProps = (state) => + ({ + isFileSharingModerator : hasPermission(state), + room : state.room + }); + + return mapStateToProps; +}; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, null, null, { @@ -93,7 +100,8 @@ export default withRoomContext(connect( { return ( prev.room === next.room && - prev.me === next.me + prev.me === next.me && + prev.peers === next.peers ); } } diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index 07e5d24..a416a64 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -1,8 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; import { - participantListSelector + participantListSelector, + makePermissionSelector } from '../../Selectors'; +import { permissions } from '../../../permissions'; import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; @@ -160,31 +162,34 @@ ParticipantList.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => +const makeMapStateToProps = () => { - return { - isModerator : - state.me.roles.some((role) => - state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)), - participants : participantListSelector(state), - spotlights : state.room.spotlights, - selectedPeerId : state.room.selectedPeerId + const hasPermission = makePermissionSelector(permissions.MODERATE_ROOM); + + const mapStateToProps = (state) => + { + return { + isModerator : hasPermission(state), + participants : participantListSelector(state), + spotlights : state.room.spotlights, + selectedPeerId : state.room.selectedPeerId + }; }; + + return mapStateToProps; }; const ParticipantListContainer = withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, null, null, { areStatesEqual : (next, prev) => { return ( - prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.room === next.room && prev.me.roles === next.me.roles && - prev.peers === next.peers && - prev.room.spotlights === next.room.spotlights && - prev.room.selectedPeerId === next.room.selectedPeerId + prev.peers === next.peers ); } } diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index b8e5e41..8ac1176 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -1,5 +1,8 @@ import { createSelector } from 'reselect'; +const meRolesSelect = (state) => state.me.roles; +const roomPermissionsSelect = (state) => state.room.roomPermissions; +const roomAllowWhenRoleMissing = (state) => state.room.allowWhenRoleMissing; const producersSelect = (state) => state.producers; const consumersSelect = (state) => state.consumers; const spotlightsSelector = (state) => state.room.spotlights; @@ -217,3 +220,53 @@ export const makePeerConsumerSelector = () => } ); }; + +// Very important that the Components that use this +// selector need to check at least these state changes: +// +// areStatesEqual : (next, prev) => +// { +// return ( +// prev.room.roomPermissions === next.room.roomPermissions && +// prev.room.allowWhenRoleMissing === next.room.allowWhenRoleMissing && +// prev.peers === next.peers && +// prev.me.roles === next.me.roles +// ); +// } +export const makePermissionSelector = (permission) => +{ + return createSelector( + meRolesSelect, + roomPermissionsSelect, + roomAllowWhenRoleMissing, + peersValueSelector, + (roles, roomPermissions, allowWhenRoleMissing, peers) => + { + if (!roomPermissions) + return false; + + const permitted = roles.some((role) => + roomPermissions[permission].includes(role) + ); + + if (permitted) + return true; + + if (!allowWhenRoleMissing) + return false; + + // Allow if config is set, and no one is present + if (allowWhenRoleMissing.includes(permission) && + peers.filter( + (peer) => + peer.roles.some( + (role) => roomPermissions[permission].includes(role) + ) + ).length === 0 + ) + return true; + + return false; + } + ); +}; \ No newline at end of file diff --git a/app/src/permissions.js b/app/src/permissions.js new file mode 100644 index 0000000..864bdbd --- /dev/null +++ b/app/src/permissions.js @@ -0,0 +1,20 @@ +export const permissions = { + // The role(s) have permission to lock/unlock a room + CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK', + // The role(s) have permission to promote a peer from the lobby + PROMOTE_PEER : 'PROMOTE_PEER', + // The role(s) have permission to send chat messages + SEND_CHAT : 'SEND_CHAT', + // The role(s) have permission to moderate chat + MODERATE_CHAT : 'MODERATE_CHAT', + // The role(s) have permission to share screen + SHARE_SCREEN : 'SHARE_SCREEN', + // The role(s) have permission to produce extra video + EXTRA_VIDEO : 'EXTRA_VIDEO', + // The role(s) have permission to share files + SHARE_FILE : 'SHARE_FILE', + // The role(s) have permission to moderate files + MODERATE_FILES : 'MODERATE_FILES', + // The role(s) have permission to moderate room (e.g. kick user) + MODERATE_ROOM : 'MODERATE_ROOM' +}; \ No newline at end of file diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 1a4b99e..595ca4e 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -33,18 +33,8 @@ const initialState = closeMeetingInProgress : false, clearChatInProgress : false, clearFileSharingInProgress : false, - userRoles : { NORMAL: 'normal' }, // Default role - permissionsFromRoles : { - CHANGE_ROOM_LOCK : [], - PROMOTE_PEER : [], - SEND_CHAT : [], - MODERATE_CHAT : [], - SHARE_SCREEN : [], - EXTRA_VIDEO : [], - SHARE_FILE : [], - MODERATE_FILES : [], - MODERATE_ROOM : [] - } + roomPermissions : null, + allowWhenRoleMissing : null }; const room = (state = initialState, action) => @@ -240,18 +230,18 @@ const room = (state = initialState, action) => case 'CLEAR_FILE_SHARING_IN_PROGRESS': return { ...state, clearFileSharingInProgress: action.payload.flag }; - case 'SET_USER_ROLES': + case 'SET_ROOM_PERMISSIONS': { - const { userRoles } = action.payload; + const { roomPermissions } = action.payload; - return { ...state, userRoles }; + return { ...state, roomPermissions }; } - case 'SET_PERMISSIONS_FROM_ROLES': + case 'SET_ALLOW_WHEN_ROLE_MISSING': { - const { permissionsFromRoles } = action.payload; + const { allowWhenRoleMissing } = action.payload; - return { ...state, permissionsFromRoles }; + return { ...state, allowWhenRoleMissing }; } default: diff --git a/server/access.js b/server/access.js new file mode 100644 index 0000000..479e9e2 --- /dev/null +++ b/server/access.js @@ -0,0 +1,11 @@ +module.exports = { + // The role(s) will gain access to the room + // even if it is locked (!) + BYPASS_ROOM_LOCK : 'BYPASS_ROOM_LOCK', + // The role(s) will gain access to the room without + // going into the lobby. If you want to restrict access to your + // server to only directly allow authenticated users, you could + // add the userRoles.AUTHENTICATED to the user in the userMapping + // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] + BYPASS_LOBBY : 'BYPASS_LOBBY' +}; \ No newline at end of file diff --git a/server/config/config.example.js b/server/config/config.example.js index dda9e2f..e08f910 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -1,5 +1,23 @@ const os = require('os'); const userRoles = require('../userRoles'); + +const { + BYPASS_ROOM_LOCK, + BYPASS_LOBBY +} = require('../access'); + +const { + CHANGE_ROOM_LOCK, + PROMOTE_PEER, + SEND_CHAT, + MODERATE_CHAT, + SHARE_SCREEN, + EXTRA_VIDEO, + SHARE_FILE, + MODERATE_FILES, + MODERATE_ROOM +} = require('../permissions'); + // const AwaitQueue = require('awaitqueue'); // const axios = require('axios'); @@ -216,44 +234,50 @@ module.exports = accessFromRoles : { // The role(s) will gain access to the room // even if it is locked (!) - BYPASS_ROOM_LOCK : [ userRoles.ADMIN ], + [BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ], // The role(s) will gain access to the room without // going into the lobby. If you want to restrict access to your // server to only directly allow authenticated users, you could // add the userRoles.AUTHENTICATED to the user in the userMapping // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] - BYPASS_LOBBY : [ userRoles.NORMAL ] + [BYPASS_LOBBY] : [ userRoles.NORMAL ] }, permissionsFromRoles : { // The role(s) have permission to lock/unlock a room - CHANGE_ROOM_LOCK : [ userRoles.NORMAL ], + [CHANGE_ROOM_LOCK] : [ userRoles.MODERATOR ], // The role(s) have permission to promote a peer from the lobby - PROMOTE_PEER : [ userRoles.NORMAL ], + [PROMOTE_PEER] : [ userRoles.NORMAL ], // The role(s) have permission to send chat messages - SEND_CHAT : [ userRoles.NORMAL ], + [SEND_CHAT] : [ userRoles.NORMAL ], // The role(s) have permission to moderate chat - MODERATE_CHAT : [ userRoles.MODERATOR ], + [MODERATE_CHAT] : [ userRoles.MODERATOR ], // The role(s) have permission to share screen - SHARE_SCREEN : [ userRoles.NORMAL ], + [SHARE_SCREEN] : [ userRoles.NORMAL ], // The role(s) have permission to produce extra video - EXTRA_VIDEO : [ userRoles.NORMAL ], + [EXTRA_VIDEO] : [ userRoles.NORMAL ], // The role(s) have permission to share files - SHARE_FILE : [ userRoles.NORMAL ], + [SHARE_FILE] : [ userRoles.NORMAL ], // The role(s) have permission to moderate files - MODERATE_FILES : [ userRoles.MODERATOR ], + [MODERATE_FILES] : [ userRoles.MODERATOR ], // The role(s) have permission to moderate room (e.g. kick user) - MODERATE_ROOM : [ userRoles.MODERATOR ] + [MODERATE_ROOM] : [ userRoles.MODERATOR ] }, + // Array of permissions. If no peer with the permission in question + // is in the room, all peers are permitted to do the action. The peers + // that are allowed because of this rule will not be able to do this + // action as soon as a peer with the permission joins. In this example + // everyone will be able to lock/unlock room until a MODERATOR joins. + allowWhenRoleMissing : [ CHANGE_ROOM_LOCK ], // When truthy, the room will be open to all users when as long as there // are allready users in the room - activateOnHostJoin : true, + activateOnHostJoin : true, // When set, maxUsersPerRoom defines how many users can join // a single room. If not set, there is no limit. // maxUsersPerRoom : 20, // Room size before spreading to new router - routerScaleSize : 40, + routerScaleSize : 40, // Mediasoup settings - mediasoup : + mediasoup : { numWorkers : Object.keys(os.cpus()).length, // mediasoup Worker settings. diff --git a/server/lib/Room.js b/server/lib/Room.js index 77f00ee..3ab281b 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -5,29 +5,47 @@ const Lobby = require('./Lobby'); const { v4: uuidv4 } = require('uuid'); const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); + +const { + BYPASS_ROOM_LOCK, + BYPASS_LOBBY +} = require('../access'); + +const permissions = require('../permissions'), { + CHANGE_ROOM_LOCK, + PROMOTE_PEER, + SEND_CHAT, + MODERATE_CHAT, + SHARE_SCREEN, + EXTRA_VIDEO, + SHARE_FILE, + MODERATE_FILES, + MODERATE_ROOM +} = permissions; + const config = require('../config/config'); const logger = new Logger('Room'); // In case they are not configured properly -const accessFromRoles = +const roomAccess = { - BYPASS_ROOM_LOCK : [ userRoles.ADMIN ], - BYPASS_LOBBY : [ userRoles.NORMAL ], + [BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ], + [BYPASS_LOBBY] : [ userRoles.NORMAL ], ...config.accessFromRoles }; -const permissionsFromRoles = +const roomPermissions = { - CHANGE_ROOM_LOCK : [ userRoles.NORMAL ], - PROMOTE_PEER : [ userRoles.NORMAL ], - SEND_CHAT : [ userRoles.NORMAL ], - MODERATE_CHAT : [ userRoles.MODERATOR ], - SHARE_SCREEN : [ userRoles.NORMAL ], - EXTRA_VIDEO : [ userRoles.NORMAL ], - SHARE_FILE : [ userRoles.NORMAL ], - MODERATE_FILES : [ userRoles.MODERATOR ], - MODERATE_ROOM : [ userRoles.MODERATOR ], + [CHANGE_ROOM_LOCK] : [ userRoles.NORMAL ], + [PROMOTE_PEER] : [ userRoles.NORMAL ], + [SEND_CHAT] : [ userRoles.NORMAL ], + [MODERATE_CHAT] : [ userRoles.MODERATOR ], + [SHARE_SCREEN] : [ userRoles.NORMAL ], + [EXTRA_VIDEO] : [ userRoles.NORMAL ], + [SHARE_FILE] : [ userRoles.NORMAL ], + [MODERATE_FILES] : [ userRoles.MODERATOR ], + [MODERATE_ROOM] : [ userRoles.MODERATOR ], ...config.permissionsFromRoles }; @@ -221,9 +239,8 @@ class Room extends EventEmitter // Returning user if (returning) this._peerJoining(peer, true); - else if ( // Has a role that is allowed to bypass room lock - peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) - ) + // Has a role that is allowed to bypass room lock + else if (this._hasAccess(peer, BYPASS_ROOM_LOCK)) this._peerJoining(peer); else if ( 'maxUsersPerRoom' in config && @@ -239,7 +256,7 @@ class Room extends EventEmitter else { // Has a role that is allowed to bypass lobby - peer.roles.some((role) => accessFromRoles.BYPASS_LOBBY.includes(role)) ? + this._hasAccess(peer, BYPASS_LOBBY) ? this._peerJoining(peer) : this._handleGuest(peer); } @@ -271,11 +288,7 @@ class Room extends EventEmitter this._peerJoining(promotedPeer); - for ( - const peer of this._getPeersWithPermission({ - permission : permissionsFromRoles.PROMOTE_PEER - }) - ) + for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id }); } @@ -283,9 +296,8 @@ class Room extends EventEmitter this._lobby.on('peerRolesChanged', (peer) => { - if ( // Has a role that is allowed to bypass room lock - peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) - ) + // Has a role that is allowed to bypass room lock + if (this._hasAccess(peer, BYPASS_ROOM_LOCK)) { this._lobby.promotePeer(peer.id); @@ -294,7 +306,7 @@ class Room extends EventEmitter if ( // Has a role that is allowed to bypass lobby !this._locked && - peer.roles.some((role) => accessFromRoles.BYPASS_LOBBY.includes(role)) + this._hasAccess(peer, BYPASS_LOBBY) ) { this._lobby.promotePeer(peer.id); @@ -307,11 +319,7 @@ class Room extends EventEmitter { const { id, displayName } = changedPeer; - for ( - const peer of this._getPeersWithPermission({ - permission : permissionsFromRoles.PROMOTE_PEER - }) - ) + for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName }); } @@ -321,11 +329,7 @@ class Room extends EventEmitter { const { id, picture } = changedPeer; - for ( - const peer of this._getPeersWithPermission({ - permission : permissionsFromRoles.PROMOTE_PEER - }) - ) + for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture }); } @@ -337,11 +341,7 @@ class Room extends EventEmitter const { id } = closedPeer; - for ( - const peer of this._getPeersWithPermission({ - permission : permissionsFromRoles.PROMOTE_PEER - }) - ) + for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:peerClosed', { peerId: id }); } @@ -401,7 +401,7 @@ class Room extends EventEmitter ); } - async dump() + dump() { return { roomId : this._roomId, @@ -447,11 +447,7 @@ class Room extends EventEmitter { this._lobby.parkPeer(parkPeer); - for ( - const peer of this._getPeersWithPermission({ - permission : permissionsFromRoles.PROMOTE_PEER - }) - ) + for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) { this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id }); } @@ -602,7 +598,7 @@ class Room extends EventEmitter // Got permission to promote peers, notify peer of // peers in lobby - if (permissionsFromRoles.PROMOTE_PEER.includes(newRole)) + if (roomPermissions.PROMOTE_PEER.includes(newRole)) { const lobbyPeers = this._lobby.peerList(); @@ -670,12 +666,9 @@ class Room extends EventEmitter .map((joinedPeer) => (joinedPeer.peerInfo)); let lobbyPeers = []; - - if ( // Allowed to promote peers, notify about lobbypeers - peer.roles.some((role) => - permissionsFromRoles.PROMOTE_PEER.includes(role) - ) - ) + + // Allowed to promote peers, notify about lobbypeers + if (this._hasPermission(peer, PROMOTE_PEER)) lobbyPeers = this._lobby.peerList(); cb(null, { @@ -683,8 +676,9 @@ class Room extends EventEmitter peers : peerInfos, tracker : config.fileTracker, authenticated : peer.authenticated, - permissionsFromRoles : permissionsFromRoles, + roomPermissions : roomPermissions, userRoles : userRoles, + allowWhenRoleMissing : config.allowWhenRoleMissing, chatHistory : this._chatHistory, fileHistory : this._fileHistory, lastNHistory : this._lastN, @@ -711,7 +705,7 @@ class Room extends EventEmitter } // Notify the new Peer to all other Peers. - for (const otherPeer of this._getJoinedPeers({ excludePeer: peer })) + for (const otherPeer of this._getJoinedPeers(peer)) { this._notification( otherPeer.socket, @@ -821,15 +815,13 @@ class Room extends EventEmitter if ( appData.source === 'screen' && - !peer.roles.some( - (role) => permissionsFromRoles.SHARE_SCREEN.includes(role)) + !this._hasPermission(peer, SHARE_SCREEN) ) throw new Error('peer not authorized'); if ( appData.source === 'extravideo' && - !peer.roles.some( - (role) => permissionsFromRoles.EXTRA_VIDEO.includes(role)) + !this._hasPermission(peer, EXTRA_VIDEO) ) throw new Error('peer not authorized'); @@ -882,7 +874,7 @@ class Room extends EventEmitter cb(null, { id: producer.id }); // Optimization: Create a server-side Consumer for each Peer. - for (const otherPeer of this._getJoinedPeers({ excludePeer: peer })) + for (const otherPeer of this._getJoinedPeers(peer)) { this._createConsumer( { @@ -1144,9 +1136,7 @@ class Room extends EventEmitter case 'chatMessage': { - if ( - !peer.roles.some((role) => permissionsFromRoles.SEND_CHAT.includes(role)) - ) + if (!this._hasPermission(peer, SEND_CHAT)) throw new Error('peer not authorized'); const { chatMessage } = request.data; @@ -1167,11 +1157,7 @@ class Room extends EventEmitter case 'moderator:clearChat': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_CHAT.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_CHAT)) throw new Error('peer not authorized'); this._chatHistory = []; @@ -1187,11 +1173,7 @@ class Room extends EventEmitter case 'lockRoom': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role) - ) - ) + if (!this._hasPermission(peer, CHANGE_ROOM_LOCK)) throw new Error('peer not authorized'); this._locked = true; @@ -1209,11 +1191,7 @@ class Room extends EventEmitter case 'unlockRoom': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role) - ) - ) + if (!this._hasPermission(peer, CHANGE_ROOM_LOCK)) throw new Error('peer not authorized'); this._locked = false; @@ -1271,11 +1249,7 @@ class Room extends EventEmitter case 'promotePeer': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.PROMOTE_PEER.includes(role) - ) - ) + if (!this._hasPermission(peer, PROMOTE_PEER)) throw new Error('peer not authorized'); const { peerId } = request.data; @@ -1290,11 +1264,7 @@ class Room extends EventEmitter case 'promoteAllPeers': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.PROMOTE_PEER.includes(role) - ) - ) + if (!this._hasPermission(peer, PROMOTE_PEER)) throw new Error('peer not authorized'); this._lobby.promoteAllPeers(); @@ -1307,11 +1277,7 @@ class Room extends EventEmitter case 'sendFile': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.SHARE_FILE.includes(role) - ) - ) + if (!this._hasPermission(peer, SHARE_FILE)) throw new Error('peer not authorized'); const { magnetUri } = request.data; @@ -1332,11 +1298,7 @@ class Room extends EventEmitter case 'moderator:clearFileSharing': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_FILES.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_FILES)) throw new Error('peer not authorized'); this._fileHistory = []; @@ -1371,11 +1333,7 @@ class Room extends EventEmitter case 'moderator:mute': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); const { peerId } = request.data; @@ -1394,11 +1352,7 @@ class Room extends EventEmitter case 'moderator:muteAll': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); // Spread to others @@ -1411,11 +1365,7 @@ class Room extends EventEmitter case 'moderator:stopVideo': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); const { peerId } = request.data; @@ -1434,11 +1384,7 @@ class Room extends EventEmitter case 'moderator:stopAllVideo': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); // Spread to others @@ -1451,11 +1397,7 @@ class Room extends EventEmitter case 'moderator:closeMeeting': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); this._notification(peer.socket, 'moderator:kick', null, true); @@ -1470,11 +1412,7 @@ class Room extends EventEmitter case 'moderator:kickPeer': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); const { peerId } = request.data; @@ -1495,11 +1433,7 @@ class Room extends EventEmitter case 'moderator:lowerHand': { - if ( - !peer.roles.some( - (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) - ) - ) + if (!this._hasPermission(peer, MODERATE_ROOM)) throw new Error('peer not authorized'); const { peerId } = request.data; @@ -1677,16 +1611,41 @@ class Room extends EventEmitter } } + _hasPermission(peer, permission) + { + const hasPermission = peer.roles.some((role) => + roomPermissions[permission].includes(role) + ); + + if (hasPermission) + return true; + + // Allow if config is set, and no one is present + if ( + 'allowWhenRoleMissing' in config && + config.allowWhenRoleMissing.includes(permission) && + this._getPeersWithPermission(permission).length === 0 + ) + return true; + + return false; + } + + _hasAccess(peer, access) + { + return peer.roles.some((role) => roomAccess[access].includes(role)); + } + /** * Helper to get the list of joined peers. */ - _getJoinedPeers({ excludePeer = undefined } = {}) + _getJoinedPeers(excludePeer = undefined) { return Object.values(this._peers) .filter((peer) => peer.joined && peer !== excludePeer); } - _getPeersWithPermission({ permission = null, excludePeer = undefined, joined = true }) + _getPeersWithPermission(permission = null, excludePeer = undefined, joined = true) { return Object.values(this._peers) .filter( @@ -1694,7 +1653,7 @@ class Room extends EventEmitter peer.joined === joined && peer !== excludePeer && peer.roles.some( - (role) => permission.includes(role) + (role) => roomPermissions[permission].includes(role) ) ); } diff --git a/server/permissions.js b/server/permissions.js new file mode 100644 index 0000000..dd3bdbb --- /dev/null +++ b/server/permissions.js @@ -0,0 +1,20 @@ +module.exports = { + // The role(s) have permission to lock/unlock a room + CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK', + // The role(s) have permission to promote a peer from the lobby + PROMOTE_PEER : 'PROMOTE_PEER', + // The role(s) have permission to send chat messages + SEND_CHAT : 'SEND_CHAT', + // The role(s) have permission to moderate chat + MODERATE_CHAT : 'MODERATE_CHAT', + // The role(s) have permission to share screen + SHARE_SCREEN : 'SHARE_SCREEN', + // The role(s) have permission to produce extra video + EXTRA_VIDEO : 'EXTRA_VIDEO', + // The role(s) have permission to share files + SHARE_FILE : 'SHARE_FILE', + // The role(s) have permission to moderate files + MODERATE_FILES : 'MODERATE_FILES', + // The role(s) have permission to moderate room (e.g. kick user) + MODERATE_ROOM : 'MODERATE_ROOM' +}; \ No newline at end of file From d09e7f5565c6aaf7d416b69e4a4ecee41374dbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 8 May 2020 16:20:15 +0200 Subject: [PATCH 17/43] Some error checks --- server/server.js | 68 +++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/server/server.js b/server/server.js index fcde0b1..27dc12c 100755 --- a/server/server.js +++ b/server/server.js @@ -368,38 +368,45 @@ async function setupAuth() app.get( '/auth/callback', passport.authenticate('oidc', { failureRedirect: '/auth/login' }), - async (req, res) => + async (req, res, next) => { - const state = JSON.parse(base64.decode(req.query.state)); - - const { peerId, roomId } = state; - - req.session.peerId = peerId; - req.session.roomId = roomId; - - let peer = peers.get(peerId); - - if (!peer) // User has no socket session yet, make temporary - peer = new Peer({ id: peerId, roomId }); - - if (peer.roomId !== roomId) // The peer is mischievous - throw new Error('peer authenticated with wrong room'); - - if (typeof config.userMapping === 'function') + try { - await config.userMapping({ - peer, - roomId, - userinfo : req.user._userinfo - }); + const state = JSON.parse(base64.decode(req.query.state)); + + const { peerId, roomId } = state; + + req.session.peerId = peerId; + req.session.roomId = roomId; + + let peer = peers.get(peerId); + + if (!peer) // User has no socket session yet, make temporary + peer = new Peer({ id: peerId, roomId }); + + if (peer.roomId !== roomId) // The peer is mischievous + throw new Error('peer authenticated with wrong room'); + + if (typeof config.userMapping === 'function') + { + await config.userMapping({ + peer, + roomId, + userinfo : req.user._userinfo + }); + } + + peer.authenticated = true; + + res.send(loginHelper({ + displayName : peer.displayName, + picture : peer.picture + })); + } + catch (error) + { + return next(error); } - - peer.authenticated = true; - - res.send(loginHelper({ - displayName : peer.displayName, - picture : peer.picture - })); } ); } @@ -586,7 +593,8 @@ async function runWebSocketServer() { logger.error('room creation or room joining failed [error:"%o"]', error); - socket.disconnect(true); + if (socket) + socket.disconnect(true); return; }); From 717c0053e54a9e9964571e7a376b53ff04bb48a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 8 May 2020 22:01:28 +0200 Subject: [PATCH 18/43] Better handling of reconnect. Clear the state properly and handle spotlights. --- app/src/RoomClient.js | 17 ++++++----------- app/src/Spotlights.js | 9 +++++++++ app/src/actions/consumerActions.js | 5 +++++ app/src/actions/peerActions.js | 5 +++++ app/src/actions/roomActions.js | 5 +++++ app/src/reducers/consumers.js | 5 +++++ app/src/reducers/peers.js | 11 +++++++++-- app/src/reducers/room.js | 5 +++++ 8 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 2b2b387..afbfdc9 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -940,14 +940,10 @@ export default class RoomClient { if (consumer.kind === 'video') { - if (spotlights.indexOf(consumer.appData.peerId) > -1) - { + if (spotlights.includes(consumer.appData.peerId)) await this._resumeConsumer(consumer); - } else - { await this._pauseConsumer(consumer); - } } } } @@ -1516,9 +1512,7 @@ export default class RoomClient if (consumer.appData.peerId === peerId && consumer.appData.source === type) { if (mute) - { await this._pauseConsumer(consumer); - } else await this._resumeConsumer(consumer); } @@ -1846,6 +1840,11 @@ export default class RoomClient this._recvTransport = null; } + this._spotlights.clearSpotlights(); + + store.dispatch(peerActions.clearPeers()); + store.dispatch(consumerActions.clearConsumers()); + store.dispatch(roomActions.clearSpotlights()); store.dispatch(roomActions.setRoomState('connecting')); }); @@ -2561,8 +2560,6 @@ export default class RoomClient case 'moderator:mute': { - // const { peerId } = notification.data; - if (this._micProducer && !this._micProducer.paused) { this.muteMic(); @@ -2581,8 +2578,6 @@ export default class RoomClient case 'moderator:stopVideo': { - // const { peerId } = notification.data; - this.disableWebcam(); this.disableScreenSharing(); diff --git a/app/src/Spotlights.js b/app/src/Spotlights.js index 30dc48a..3d0673f 100644 --- a/app/src/Spotlights.js +++ b/app/src/Spotlights.js @@ -95,6 +95,15 @@ export default class Spotlights extends EventEmitter }); } + clearSpotlights() + { + this._started = false; + + this._peerList = []; + this._selectedSpotlights = []; + this._currentSpotlights = []; + } + _newPeer(id) { logger.debug( diff --git a/app/src/actions/consumerActions.js b/app/src/actions/consumerActions.js index 249d156..659a609 100644 --- a/app/src/actions/consumerActions.js +++ b/app/src/actions/consumerActions.js @@ -10,6 +10,11 @@ export const removeConsumer = (consumerId, peerId) => payload : { consumerId, peerId } }); +export const clearConsumers = () => + ({ + type : 'CLEAR_CONSUMERS' + }); + export const setConsumerPaused = (consumerId, originator) => ({ type : 'SET_CONSUMER_PAUSED', diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index 738928c..5672b47 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -10,6 +10,11 @@ export const removePeer = (peerId) => payload : { peerId } }); +export const clearPeers = () => + ({ + type : 'CLEAR_PEERS' + }); + export const setPeerDisplayName = (displayName, peerId) => ({ type : 'SET_PEER_DISPLAY_NAME', diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index cbfde37..5ae45e3 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -130,6 +130,11 @@ export const setSpotlights = (spotlights) => payload : { spotlights } }); +export const clearSpotlights = () => + ({ + type : 'CLEAR_SPOTLIGHTS' + }); + export const toggleJoined = () => ({ type : 'TOGGLE_JOINED' diff --git a/app/src/reducers/consumers.js b/app/src/reducers/consumers.js index 68a4a4a..6be31ae 100644 --- a/app/src/reducers/consumers.js +++ b/app/src/reducers/consumers.js @@ -110,6 +110,11 @@ const consumers = (state = initialState, action) => return { ...state, [consumerId]: newConsumer }; } + case 'CLEAR_CONSUMERS': + { + return initialState; + } + default: return state; } diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index 6c4fd1f..32d6fef 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -1,4 +1,6 @@ -const peer = (state = {}, action) => +const initialState = {}; + +const peer = (state = initialState, action) => { switch (action.type) { @@ -85,7 +87,7 @@ const peer = (state = {}, action) => } }; -const peers = (state = {}, action) => +const peers = (state = initialState, action) => { switch (action.type) { @@ -139,6 +141,11 @@ const peers = (state = {}, action) => return { ...state, [oldPeer.id]: peer(oldPeer, action) }; } + case 'CLEAR_PEERS': + { + return initialState; + } + default: return state; } diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 595ca4e..d784219 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -212,6 +212,11 @@ const room = (state = initialState, action) => return { ...state, spotlights }; } + case 'CLEAR_SPOTLIGHTS': + { + return { ...state, spotlights: [] }; + } + case 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS': return { ...state, lobbyPeersPromotionInProgress: action.payload.flag }; From c73ee5c26b4e77a21a529d75c8d71f8c347a27ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 8 May 2020 22:02:06 +0200 Subject: [PATCH 19/43] Fix missing picture on peer in lobby. --- app/src/RoomClient.js | 18 ++++++++++++------ server/lib/Lobby.js | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index afbfdc9..bce2f03 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2124,15 +2124,21 @@ export default class RoomClient lobbyPeers.forEach((peer) => { store.dispatch( - lobbyPeerActions.addLobbyPeer(peer.peerId)); + lobbyPeerActions.addLobbyPeer(peer.id)); + store.dispatch( lobbyPeerActions.setLobbyPeerDisplayName( peer.displayName, - peer.peerId + peer.id ) ); + store.dispatch( - lobbyPeerActions.setLobbyPeerPicture(peer.picture)); + lobbyPeerActions.setLobbyPeerPicture( + peer.picture, + peer.id + ) + ); }); store.dispatch( @@ -2930,11 +2936,11 @@ export default class RoomClient (lobbyPeers.length > 0) && lobbyPeers.forEach((peer) => { store.dispatch( - lobbyPeerActions.addLobbyPeer(peer.peerId)); + lobbyPeerActions.addLobbyPeer(peer.id)); store.dispatch( - lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId)); + lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.id)); store.dispatch( - lobbyPeerActions.setLobbyPeerPicture(peer.picture)); + lobbyPeerActions.setLobbyPeerPicture(peer.picture, peer.id)); }); (accessCode != null) && store.dispatch( diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js index 0bcfb66..43fc30d 100644 --- a/server/lib/Lobby.js +++ b/server/lib/Lobby.js @@ -46,7 +46,7 @@ class Lobby extends EventEmitter return Object.values(this._peers).map((peer) => ({ - peerId : peer.id, + id : peer.id, displayName : peer.displayName, picture : peer.picture })); From a9e9a1c1fad7c7f9575b7f9873b5351e47fd3454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 8 May 2020 22:11:48 +0200 Subject: [PATCH 20/43] Cleanup of join logic, and making sure that lobbyPeers are sent to all peers if last peer with PROMOTE_PEER leaves and allowWhenRoleMissing contains PROMOTE_PEER, fixes #303 --- server/lib/Room.js | 255 ++++++++++++++++++++++++++++----------------- 1 file changed, 157 insertions(+), 98 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 3ab281b..74accc4 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1,4 +1,5 @@ const EventEmitter = require('events').EventEmitter; +const AwaitQueue = require('awaitqueue'); const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); @@ -49,6 +50,8 @@ const roomPermissions = ...config.permissionsFromRoles }; +const roomAllowWhenRoleMissing = config.allowWhenRoleMissing || []; + const ROUTER_SCALE_SIZE = config.routerScaleSize || 40; class Room extends EventEmitter @@ -115,6 +118,9 @@ class Room extends EventEmitter // Closed flag. this._closed = false; + // Joining queue + this._queue = new AwaitQueue(); + // Locked flag. this._locked = false; @@ -166,6 +172,10 @@ class Room extends EventEmitter this._closed = true; + this._queue.close(); + + this._queue = null; + if (this._selfDestructTimeout) clearTimeout(this._selfDestructTimeout); @@ -288,7 +298,7 @@ class Room extends EventEmitter this._peerJoining(promotedPeer); - for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) + for (const peer of this._getAllowedPeers(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id }); } @@ -319,7 +329,7 @@ class Room extends EventEmitter { const { id, displayName } = changedPeer; - for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) + for (const peer of this._getAllowedPeers(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName }); } @@ -329,7 +339,7 @@ class Room extends EventEmitter { const { id, picture } = changedPeer; - for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) + for (const peer of this._getAllowedPeers(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture }); } @@ -341,7 +351,7 @@ class Room extends EventEmitter const { id } = closedPeer; - for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) + for (const peer of this._getAllowedPeers(PROMOTE_PEER)) { this._notification(peer.socket, 'lobby:peerClosed', { peerId: id }); } @@ -447,114 +457,91 @@ class Room extends EventEmitter { this._lobby.parkPeer(parkPeer); - for (const peer of this._getPeersWithPermission(PROMOTE_PEER)) + for (const peer of this._getAllowedPeers(PROMOTE_PEER)) { this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id }); } } - async _peerJoining(peer, returning = false) + _peerJoining(peer, returning = false) { - peer.socket.join(this._roomId); - - // If we don't have this peer, add to end - !this._lastN.includes(peer.id) && this._lastN.push(peer.id); - - this._peers[peer.id] = peer; - - // Assign routerId - peer.routerId = await this._getRouterId(); - - this._handlePeer(peer); - - if (returning) + this._queue.push(async () => { - this._notification(peer.socket, 'roomBack'); - } - else - { - const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); + peer.socket.join(this._roomId); - peer.socket.handshake.session.token = token; + // If we don't have this peer, add to end + !this._lastN.includes(peer.id) && this._lastN.push(peer.id); - peer.socket.handshake.session.save(); + this._peers[peer.id] = peer; - let turnServers; - - if ('turnAPIURI' in config) + // Assign routerId + peer.routerId = await this._getRouterId(); + + this._handlePeer(peer); + + if (returning) { - try - { - const { data } = await axios.get( - config.turnAPIURI, - { - params : { - ...config.turnAPIparams, - 'api_key' : config.turnAPIKey, - 'ip' : peer.socket.request.connection.remoteAddress - } - }); - - turnServers = [ { - urls : data.uris, - username : data.username, - credential : data.password - } ]; - } - catch (error) - { - if ('backupTurnServers' in config) - turnServers = config.backupTurnServers; - - logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); - } + this._notification(peer.socket, 'roomBack'); } - else if ('backupTurnServers' in config) + else { - turnServers = config.backupTurnServers; + const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); + + peer.socket.handshake.session.token = token; + + peer.socket.handshake.session.save(); + + let turnServers; + + if ('turnAPIURI' in config) + { + try + { + const { data } = await axios.get( + config.turnAPIURI, + { + params : { + ...config.turnAPIparams, + 'api_key' : config.turnAPIKey, + 'ip' : peer.socket.request.connection.remoteAddress + } + }); + + turnServers = [ { + urls : data.uris, + username : data.username, + credential : data.password + } ]; + } + catch (error) + { + if ('backupTurnServers' in config) + turnServers = config.backupTurnServers; + + logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); + } + } + else if ('backupTurnServers' in config) + { + turnServers = config.backupTurnServers; + } + + this._notification(peer.socket, 'roomReady', { turnServers }); } - - this._notification(peer.socket, 'roomReady', { turnServers }); - } + }) + .catch((error) => + { + logger.error('_peerJoining() [error:"%o"]', error); + }); } _handlePeer(peer) { logger.debug('_handlePeer() [peer:"%s"]', peer.id); - peer.socket.on('request', (request, cb) => - { - logger.debug( - 'Peer "request" event [method:"%s", peerId:"%s"]', - request.method, peer.id); - - this._handleSocketRequest(peer, request, cb) - .catch((error) => - { - logger.error('"request" failed [error:"%o"]', error); - - cb(error); - }); - }); - peer.on('close', () => { - if (this._closed) - return; - - // If the Peer was joined, notify all Peers. - if (peer.joined) - this._notification(peer.socket, 'peerClosed', { peerId: peer.id }, true); - - // Remove from lastN - this._lastN = this._lastN.filter((id) => id !== peer.id); - - delete this._peers[peer.id]; - - // If this is the last Peer in the room and - // lobby is empty, close the room after a while. - if (this.checkEmpty() && this._lobby.checkEmpty()) - this.selfDestructCountdown(); + this._handlePeerClose(peer); }); peer.on('displayNameChanged', ({ oldDisplayName }) => @@ -620,6 +607,69 @@ class Room extends EventEmitter role : oldRole }, true, true); }); + + peer.socket.on('request', (request, cb) => + { + logger.debug( + 'Peer "request" event [method:"%s", peerId:"%s"]', + request.method, peer.id); + + this._handleSocketRequest(peer, request, cb) + .catch((error) => + { + logger.error('"request" failed [error:"%o"]', error); + + cb(error); + }); + }); + + // Peer left before we were done joining + if (peer.closed) + this._handlePeerClose(peer); + } + + _handlePeerClose(peer) + { + logger.debug('_handlePeerClose() [peer:"%s"]', peer.id); + + if (this._closed) + return; + + // If the Peer was joined, notify all Peers. + if (peer.joined) + this._notification(peer.socket, 'peerClosed', { peerId: peer.id }, true); + + // Remove from lastN + this._lastN = this._lastN.filter((id) => id !== peer.id); + + // Need this to know if this peer was the last with PROMOTE_PEER + const hasPromotePeer = peer.roles.some((role) => + roomPermissions[PROMOTE_PEER].includes(role) + ); + + delete this._peers[peer.id]; + + // No peers left with PROMOTE_PEER, might need to give + // lobbyPeers to peers that are left. + if ( + hasPromotePeer && + !this._lobby.checkEmpty() && + roomAllowWhenRoleMissing.includes(PROMOTE_PEER) && + this._getPeersWithPermission(PROMOTE_PEER).length === 0 + ) + { + const lobbyPeers = this._lobby.peerList(); + + for (const allowedPeer of this._getAllowedPeers(PROMOTE_PEER)) + { + this._notification(allowedPeer.socket, 'parkedPeers', { lobbyPeers }); + } + } + + // If this is the last Peer in the room and + // lobby is empty, close the room after a while. + if (this.checkEmpty() && this._lobby.checkEmpty()) + this.selfDestructCountdown(); } async _handleSocketRequest(peer, request, cb) @@ -656,13 +706,9 @@ class Room extends EventEmitter // Tell the new Peer about already joined Peers. // And also create Consumers for existing Producers. - const joinedPeers = - [ - ...this._getJoinedPeers() - ]; + const joinedPeers = this._getJoinedPeers(peer); const peerInfos = joinedPeers - .filter((joinedPeer) => joinedPeer.id !== peer.id) .map((joinedPeer) => (joinedPeer.peerInfo)); let lobbyPeers = []; @@ -678,7 +724,7 @@ class Room extends EventEmitter authenticated : peer.authenticated, roomPermissions : roomPermissions, userRoles : userRoles, - allowWhenRoleMissing : config.allowWhenRoleMissing, + allowWhenRoleMissing : roomAllowWhenRoleMissing, chatHistory : this._chatHistory, fileHistory : this._fileHistory, lastNHistory : this._lastN, @@ -1622,8 +1668,7 @@ class Room extends EventEmitter // Allow if config is set, and no one is present if ( - 'allowWhenRoleMissing' in config && - config.allowWhenRoleMissing.includes(permission) && + roomAllowWhenRoleMissing.includes(permission) && this._getPeersWithPermission(permission).length === 0 ) return true; @@ -1645,6 +1690,20 @@ class Room extends EventEmitter .filter((peer) => peer.joined && peer !== excludePeer); } + _getAllowedPeers(permission = null, excludePeer = undefined, joined = true) + { + const peers = this._getPeersWithPermission(permission, excludePeer, joined); + + if (peers.length > 0) + return peers; + + // Allow if config is set, and no one is present + if (roomAllowWhenRoleMissing.includes(permission)) + return Object.values(this._peers); + + return peers; + } + _getPeersWithPermission(permission = null, excludePeer = undefined, joined = true) { return Object.values(this._peers) From c5143de64786c5489d309605c3f281fde2b6cd93 Mon Sep 17 00:00:00 2001 From: Roman Drozd <37835902+roman-drozd-it@users.noreply.github.com> Date: Fri, 8 May 2020 22:26:45 +0200 Subject: [PATCH 21/43] Fix ptt break layout (#298) --- app/src/components/Containers/Me.js | 31 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index aa3a9a3..743c022 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -132,12 +132,12 @@ const styles = (theme) => { position : 'absolute', float : 'left', - top : '10%', + top : '25%', left : '50%', transform : 'translate(-50%, 0%)', color : 'rgba(255, 255, 255, 0.7)', - fontSize : '2vs', - backgroundColor : 'rgba(255, 0, 0, 0.5)', + fontSize : '1.3em', + backgroundColor : 'rgba(255, 0, 0, 0.9)', margin : '4px', padding : theme.spacing(2), zIndex : 31, @@ -322,19 +322,20 @@ const Me = (props) => }} style={spacingStyle} > + + { me.browser.platform !== 'mobile' && +
+ +
+ }
- { !smallContainer && -
- -
- }

Date: Sat, 9 May 2020 00:03:56 +0200 Subject: [PATCH 22/43] Option to make side drawer push videos to the side and be permanent, fixes #320 --- app/public/config/config.example.js | 3 + app/src/actions/settingsActions.js | 5 ++ app/src/components/Controls/TopBar.js | 49 ++++++++++- app/src/components/MeetingViews/Democratic.js | 8 +- app/src/components/MeetingViews/Filmstrip.js | 4 + app/src/components/Room.js | 83 +++++++++++++++---- .../components/Settings/AppearenceSettings.js | 17 ++++ app/src/reducers/settings.js | 8 ++ app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/lv.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + 26 files changed, 174 insertions(+), 21 deletions(-) diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index 4bbc13f..7909284 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -57,6 +57,9 @@ var config = // If true, will show media control buttons in separate // control bar, not in the ME container. buttonControlBar : false, + // If false, will push videos away to make room for side + // drawer. If true, will overlay side drawer over videos + drawerOverlayed : true, // Timeout for autohiding topbar and button control bar hideTimeout : 3000, lastN : 4, diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 4b7dc6d..21ff2fd 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -43,6 +43,11 @@ export const toggleButtonControlBar = () => type : 'TOGGLE_BUTTON_CONTROL_BAR' }); +export const toggleDrawerOverlayed = () => + ({ + type : 'TOGGLE_DRAWER_OVERLAYED' + }); + export const toggleShowNotifications = () => ({ type : 'TOGGLE_SHOW_NOTIFICATIONS' diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index b880c36..52b06cd 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -14,6 +14,7 @@ import { withStyles } from '@material-ui/core/styles'; import * as roomActions from '../../actions/roomActions'; import * as toolareaActions from '../../actions/toolareaActions'; import { useIntl, FormattedMessage } from 'react-intl'; +import classnames from 'classnames'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import MenuItem from '@material-ui/core/MenuItem'; @@ -43,6 +44,31 @@ import InfoIcon from '@material-ui/icons/Info'; const styles = (theme) => ({ + persistentDrawerOpen : + { + width : 'calc(100% - 30vw)', + marginLeft : '30vw', + [theme.breakpoints.down('lg')] : + { + width : 'calc(100% - 40vw)', + marginLeft : '40vw' + }, + [theme.breakpoints.down('md')] : + { + width : 'calc(100% - 50vw)', + marginLeft : '50vw' + }, + [theme.breakpoints.down('sm')] : + { + width : 'calc(100% - 70vw)', + marginLeft : '70vw' + }, + [theme.breakpoints.down('xs')] : + { + width : 'calc(100% - 90vw)', + marginLeft : '90vw' + } + }, menuButton : { margin : 0, @@ -188,6 +214,9 @@ const TopBar = (props) => peersLength, lobbyPeers, permanentTopBar, + drawerOverlayed, + toolAreaOpen, + isMobile, myPicture, loggedIn, loginEnabled, @@ -248,7 +277,12 @@ const TopBar = (props) => const mapStateToProps = (state) => ({ room : state.room, + isMobile : state.me.browser.platform === 'mobile', peersLength : peersLengthSelector(state), lobbyPeers : lobbyPeersKeySelector(state), permanentTopBar : state.settings.permanentTopBar, + drawerOverlayed : state.settings.drawerOverlayed, + toolAreaOpen : state.toolarea.toolAreaOpen, loggedIn : state.me.loggedIn, loginEnabled : state.me.loginEnabled, myPicture : state.me.picture, @@ -832,12 +872,15 @@ export default withRoomContext(connect( prev.peers === next.peers && prev.lobbyPeers === next.lobbyPeers && prev.settings.permanentTopBar === next.settings.permanentTopBar && + prev.settings.drawerOverlayed === next.settings.drawerOverlayed && prev.me.loggedIn === next.me.loggedIn && + prev.me.browser === next.me.browser && prev.me.loginEnabled === next.me.loginEnabled && prev.me.picture === next.me.picture && prev.me.roles === next.me.roles && prev.toolarea.unreadMessages === next.toolarea.unreadMessages && - prev.toolarea.unreadFiles === next.toolarea.unreadFiles + prev.toolarea.unreadFiles === next.toolarea.unreadFiles && + prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } } diff --git a/app/src/components/MeetingViews/Democratic.js b/app/src/components/MeetingViews/Democratic.js index f2fff1a..e2e774c 100644 --- a/app/src/components/MeetingViews/Democratic.js +++ b/app/src/components/MeetingViews/Democratic.js @@ -22,6 +22,7 @@ const styles = (theme) => display : 'flex', flexDirection : 'row', flexWrap : 'wrap', + overflow : 'hidden', justifyContent : 'center', alignItems : 'center', alignContent : 'center' @@ -189,6 +190,7 @@ Democratic.propTypes = toolbarsVisible : PropTypes.bool.isRequired, permanentTopBar : PropTypes.bool.isRequired, buttonControlBar : PropTypes.bool.isRequired, + toolAreaOpen : PropTypes.bool.isRequired, classes : PropTypes.object.isRequired }; @@ -199,7 +201,8 @@ const mapStateToProps = (state) => spotlightsPeers : spotlightPeersSelector(state), toolbarsVisible : state.room.toolbarsVisible, permanentTopBar : state.settings.permanentTopBar, - buttonControlBar : state.settings.buttonControlBar + buttonControlBar : state.settings.buttonControlBar, + toolAreaOpen : state.toolarea.toolAreaOpen }; }; @@ -217,7 +220,8 @@ export default connect( prev.room.spotlights === next.room.spotlights && prev.room.toolbarsVisible === next.room.toolbarsVisible && prev.settings.permanentTopBar === next.settings.permanentTopBar && - prev.settings.buttonControlBar === next.settings.buttonControlBar + prev.settings.buttonControlBar === next.settings.buttonControlBar && + prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } } diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js index d1cfba6..120e985 100644 --- a/app/src/components/MeetingViews/Filmstrip.js +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -25,6 +25,7 @@ const styles = () => height : '100%', width : '100%', display : 'grid', + overflow : 'hidden', gridTemplateColumns : '1fr', gridTemplateRows : '1fr 0.25fr' }, @@ -334,6 +335,7 @@ Filmstrip.propTypes = { spotlights : PropTypes.array.isRequired, boxes : PropTypes.number, toolbarsVisible : PropTypes.bool.isRequired, + toolAreaOpen : PropTypes.bool.isRequired, permanentTopBar : PropTypes.bool, classes : PropTypes.object.isRequired }; @@ -349,6 +351,7 @@ const mapStateToProps = (state) => spotlights : state.room.spotlights, boxes : videoBoxesSelector(state), toolbarsVisible : state.room.toolbarsVisible, + toolAreaOpen : state.toolarea.toolAreaOpen, permanentTopBar : state.settings.permanentTopBar }; }; @@ -364,6 +367,7 @@ export default withRoomContext(connect( prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.selectedPeerId === next.room.selectedPeerId && prev.room.toolbarsVisible === next.room.toolbarsVisible && + prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen && prev.settings.permanentTopBar === next.settings.permanentTopBar && prev.peers === next.peers && prev.consumers === next.consumers && diff --git a/app/src/components/Room.js b/app/src/components/Room.js index d021132..4dee360 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -12,6 +12,7 @@ import { FormattedMessage } from 'react-intl'; import CookieConsent from 'react-cookie-consent'; import CssBaseline from '@material-ui/core/CssBaseline'; import SwipeableDrawer from '@material-ui/core/SwipeableDrawer'; +import Drawer from '@material-ui/core/Drawer'; import Hidden from '@material-ui/core/Hidden'; import Notifications from './Notifications/Notifications'; import MeetingDrawer from './MeetingDrawer/MeetingDrawer'; @@ -45,6 +46,27 @@ const styles = (theme) => backgroundSize : 'cover', backgroundRepeat : 'no-repeat' }, + drawer : + { + width : '30vw', + flexShrink : 0, + [theme.breakpoints.down('lg')] : + { + width : '40vw' + }, + [theme.breakpoints.down('md')] : + { + width : '50vw' + }, + [theme.breakpoints.down('sm')] : + { + width : '70vw' + }, + [theme.breakpoints.down('xs')] : + { + width : '90vw' + } + }, drawerPaper : { width : '30vw', @@ -147,6 +169,7 @@ class Room extends React.PureComponent advancedMode, showNotifications, buttonControlBar, + drawerOverlayed, toolAreaOpen, toggleToolArea, classes, @@ -159,6 +182,8 @@ class Room extends React.PureComponent democratic : Democratic }[room.mode]; + const container = window !== undefined ? window.document.body : undefined; + return (

{ !isElectron() && @@ -195,22 +220,45 @@ class Room extends React.PureComponent onFullscreen={this.handleToggleFullscreen} /> - + { (browser.platform === 'mobile' || drawerOverlayed) ? + + : + + } { browser.platform === 'mobile' && browser.os !== 'ios' && @@ -252,6 +300,7 @@ Room.propTypes = advancedMode : PropTypes.bool.isRequired, showNotifications : PropTypes.bool.isRequired, buttonControlBar : PropTypes.bool.isRequired, + drawerOverlayed : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, @@ -266,6 +315,7 @@ const mapStateToProps = (state) => advancedMode : state.settings.advancedMode, showNotifications : state.settings.showNotifications, buttonControlBar : state.settings.buttonControlBar, + drawerOverlayed : state.settings.drawerOverlayed, toolAreaOpen : state.toolarea.toolAreaOpen }); @@ -294,6 +344,7 @@ export default connect( prev.settings.advancedMode === next.settings.advancedMode && prev.settings.showNotifications === next.settings.showNotifications && prev.settings.buttonControlBar === next.settings.buttonControlBar && + prev.settings.drawerOverlayed === next.settings.drawerOverlayed && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js index a859ab4..46cc898 100644 --- a/app/src/components/Settings/AppearenceSettings.js +++ b/app/src/components/Settings/AppearenceSettings.js @@ -26,12 +26,14 @@ const styles = (theme) => }); const AppearenceSettings = ({ + isMobile, room, settings, onTogglePermanentTopBar, onToggleHiddenControls, onToggleButtonControlBar, onToggleShowNotifications, + onToggleDrawerOverlayed, handleChangeMode, classes }) => @@ -111,6 +113,16 @@ const AppearenceSettings = ({ defaultMessage : 'Separate media controls' })} /> + { !isMobile && + } + label={intl.formatMessage({ + id : 'settings.drawerOverlayed', + defaultMessage : 'Side drawer over content' + })} + /> + } } @@ -125,18 +137,21 @@ const AppearenceSettings = ({ AppearenceSettings.propTypes = { + isMobile : PropTypes.bool.isRequired, room : appPropTypes.Room.isRequired, settings : PropTypes.object.isRequired, onTogglePermanentTopBar : PropTypes.func.isRequired, onToggleHiddenControls : PropTypes.func.isRequired, onToggleButtonControlBar : PropTypes.func.isRequired, onToggleShowNotifications : PropTypes.func.isRequired, + onToggleDrawerOverlayed : PropTypes.func.isRequired, handleChangeMode : PropTypes.func.isRequired, classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => ({ + isMobile : state.me.browser.platform === 'mobile', room : state.room, settings : state.settings }); @@ -146,6 +161,7 @@ const mapDispatchToProps = { onToggleHiddenControls : settingsActions.toggleHiddenControls, onToggleShowNotifications : settingsActions.toggleShowNotifications, onToggleButtonControlBar : settingsActions.toggleButtonControlBar, + onToggleDrawerOverlayed : settingsActions.toggleDrawerOverlayed, handleChangeMode : roomActions.setDisplayMode }; @@ -157,6 +173,7 @@ export default connect( areStatesEqual : (next, prev) => { return ( + prev.me.browser === next.me.browser && prev.room === next.room && prev.settings === next.settings ); diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index f306726..1c375bf 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -19,6 +19,7 @@ const initialState = showNotifications : true, notificationSounds : true, buttonControlBar : window.config.buttonControlBar || false, + drawerOverlayed : window.config.drawerOverlayed || true, ...window.config.defaultAudio }; @@ -153,6 +154,13 @@ const settings = (state = initialState, action) => return { ...state, buttonControlBar }; } + case 'TOGGLE_DRAWER_OVERLAYED': + { + const drawerOverlayed = !state.drawerOverlayed; + + return { ...state, drawerOverlayed }; + } + case 'TOGGLE_HIDDEN_CONTROLS': { const hiddenControls = !state.hiddenControls; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index df97372..0ce5f5b 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "无法保存文件", "filesharing.startingFileShare": "正在尝试共享文件", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index f73144e..3eac16c 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -137,6 +137,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Není možné uložit soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index d141ae9..402c4b3 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index a0bc95f..c5c3a2d 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.startingFileShare": "Forsøger at dele filen", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index c21ef1d..d71764f 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 4b33dbd..344d660 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -138,6 +138,7 @@ "settings.echoCancellation": "Echo cancellation", "settings.autoGainControl": "Auto gain control", "settings.noiseSuppression": "Noise suppression", + "settings.drawerOverlayed": "Side drawer over content", "filesharing.saveFileError": "Unable to save file", "filesharing.startingFileShare": "Attempting to share file", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 9bf9744..758b0c8 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 2b74da9..2eb7edf 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.startingFileShare": "Début du transfert de fichier", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 9d55c44..c0f8879 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index be0342d..cf65f1d 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -138,6 +138,7 @@ "settings.echoCancellation": "Visszhangelnyomás", "settings.autoGainControl": "Automatikus hangerő", "settings.noiseSuppression": "Zajelnyomás", + "settings.drawerOverlayed": null, "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 8ce1764..5ce26b6 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -137,6 +137,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Impossibile salvare file", "filesharing.startingFileShare": "Tentativo di condivisione file", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index 5a39315..7bf0b24 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -132,6 +132,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Nav iespējams saglabāt failu", "filesharing.startingFileShare": "Tiek mēģināts kopīgot failu", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 139341a..251858b 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -138,6 +138,7 @@ "settings.echoCancellation": "Echokansellering", "settings.autoGainControl": "Auto gain kontroll", "settings.noiseSuppression": "Støy reduksjon", + "settings.drawerOverlayed": "Sidemeny over innhold", "filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.startingFileShare": "Starter fildeling", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 4c8f28a..705a6d7 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index df2bf73..8250231 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index fe7fb50..1ba455c 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.startingFileShare": "Partajarea fișierului", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 4a105d4..524c557 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -135,6 +135,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index 71ead8e..a8cc077 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -138,6 +138,7 @@ "settings.echoCancellation": null, "settings.autoGainControl": null, "settings.noiseSuppression": null, + "settings.drawerOverlayed": null, "filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.startingFileShare": "Спроба поділитися файлом", From 13e611e17747481fb0ca9b207052255d32db34b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 9 May 2020 00:07:12 +0200 Subject: [PATCH 23/43] Missing tooltip, fixes #324 --- app/src/components/Controls/TopBar.js | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 52b06cd..bbf408d 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -312,18 +312,25 @@ const TopBar = (props) =>
- handleMenuOpen(event, 'moreActions')} - color='inherit' + - - + handleMenuOpen(event, 'moreActions')} + color='inherit' + > + + + { fullscreenEnabled && Date: Sat, 9 May 2020 00:09:52 +0200 Subject: [PATCH 24/43] Add h shortcut for help dialog --- app/src/RoomClient.js | 7 +++++++ app/src/components/Controls/Help.js | 1 + 2 files changed, 8 insertions(+) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index bce2f03..2217d65 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -408,6 +408,13 @@ export default class RoomClient break; } + case 'H': // Open help dialog + { + store.dispatch(roomActions.setHelpOpen(true)); + + break; + } + default: { break; diff --git a/app/src/components/Controls/Help.js b/app/src/components/Controls/Help.js index 3091ec4..98415b5 100644 --- a/app/src/components/Controls/Help.js +++ b/app/src/components/Controls/Help.js @@ -17,6 +17,7 @@ import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; const shortcuts=[ + { key: 'h', label: 'room.help', defaultMessage: 'Help' }, { key: 'm', label: 'device.muteAudio', defaultMessage: 'Mute Audio' }, { key: 'v', label: 'device.stopVideo', defaultMessage: 'Mute Video' }, { key: '1', label: 'label.democratic', defaultMessage: 'Democratic View' }, From 1bccc9a98568303e159fc66f9e833bdc16e4e8aa Mon Sep 17 00:00:00 2001 From: Roman Drozd Date: Sat, 9 May 2020 00:29:20 +0200 Subject: [PATCH 25/43] Update pl translation --- app/src/translations/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 705a6d7..8d3ecf5 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -60,7 +60,7 @@ "room.loweredHand": null, "room.extraVideo": null, "room.overRoomLimit": null, - "room.help": null, + "room.help": "Pomoc", "room.about": null, "room.shortcutKeys": null, From aa5f75b79478662ccb11ecb5ce5f45e21cfe2251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 9 May 2020 00:37:47 +0200 Subject: [PATCH 26/43] Do things in correct order. --- server/lib/Lobby.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js index 43fc30d..45cdc06 100644 --- a/server/lib/Lobby.js +++ b/server/lib/Lobby.js @@ -154,8 +154,6 @@ class Lobby extends EventEmitter this.emit('lobbyEmpty'); }; - this._notification(peer.socket, 'enteredLobby'); - this._peers[peer.id] = peer; peer.on('gotRole', peer.gotRoleHandler); @@ -165,6 +163,8 @@ class Lobby extends EventEmitter peer.socket.on('request', peer.socketRequestHandler); peer.on('close', peer.closeHandler); + + this._notification(peer.socket, 'enteredLobby'); } async _handleSocketRequest(peer, request, cb) @@ -189,7 +189,8 @@ class Lobby extends EventEmitter cb(); break; - } + } + case 'changePicture': { const { picture } = request.data; From f468f95cdc58a2ab8ca2b1a408ffb3f02e8afbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 9 May 2020 00:43:05 +0200 Subject: [PATCH 27/43] Translated media buttons, fixes #309 --- .../components/Controls/ButtonControlBar.js | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/app/src/components/Controls/ButtonControlBar.js b/app/src/components/Controls/ButtonControlBar.js index 2ee5b7e..f6e4fd6 100644 --- a/app/src/components/Controls/ButtonControlBar.js +++ b/app/src/components/Controls/ButtonControlBar.js @@ -7,6 +7,7 @@ import useMediaQuery from '@material-ui/core/useMediaQuery'; import classnames from 'classnames'; import * as appPropTypes from '../appPropTypes'; import { withRoomContext } from '../../RoomContext'; +import { useIntl } from 'react-intl'; import Fab from '@material-ui/core/Fab'; import Tooltip from '@material-ui/core/Tooltip'; import MicIcon from '@material-ui/icons/Mic'; @@ -57,6 +58,8 @@ const styles = (theme) => const ButtonControlBar = (props) => { + const intl = useIntl(); + const { roomClient, toolbarsVisible, @@ -73,20 +76,37 @@ const ButtonControlBar = (props) => let micTip; - if (!me.canSendMic || !micProducer) + if (!me.canSendMic) { micState = 'unsupported'; - micTip = 'Audio unsupported'; + micTip = intl.formatMessage({ + id : 'device.audioUnsupported', + defaultMessage : 'Audio unsupported' + }); + } + else if (!micProducer) + { + micState = 'off'; + micTip = intl.formatMessage({ + id : 'device.activateAudio', + defaultMessage : 'Activate audio' + }); } else if (!micProducer.locallyPaused && !micProducer.remotelyPaused) { micState = 'on'; - micTip = 'Mute audio'; + micTip = intl.formatMessage({ + id : 'device.muteAudio', + defaultMessage : 'Mute audio' + }); } else { - micState = 'off'; - micTip = 'Unmute audio'; + micState = 'muted'; + micTip = intl.formatMessage({ + id : 'device.unMuteAudio', + defaultMessage : 'Unmute audio' + }); } let webcamState; @@ -96,17 +116,26 @@ const ButtonControlBar = (props) => if (!me.canSendWebcam) { webcamState = 'unsupported'; - webcamTip = 'Video unsupported'; + webcamTip = intl.formatMessage({ + id : 'device.videoUnsupported', + defaultMessage : 'Video unsupported' + }); } else if (webcamProducer) { webcamState = 'on'; - webcamTip = 'Stop video'; + webcamTip = intl.formatMessage({ + id : 'device.stopVideo', + defaultMessage : 'Stop video' + }); } else { webcamState = 'off'; - webcamTip = 'Start video'; + webcamTip = intl.formatMessage({ + id : 'device.startVideo', + defaultMessage : 'Start video' + }); } let screenState; @@ -116,17 +145,26 @@ const ButtonControlBar = (props) => if (!me.canShareScreen) { screenState = 'unsupported'; - screenTip = 'Screen sharing not supported'; + screenTip = intl.formatMessage({ + id : 'device.screenSharingUnsupported', + defaultMessage : 'Screen sharing not supported' + }); } else if (screenProducer) { screenState = 'on'; - screenTip = 'Stop screen sharing'; + screenTip = intl.formatMessage({ + id : 'device.stopScreenSharing', + defaultMessage : 'Stop screen sharing' + }); } else { screenState = 'off'; - screenTip = 'Start screen sharing'; + screenTip = intl.formatMessage({ + id : 'device.startScreenSharing', + defaultMessage : 'Start screen sharing' + }); } const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); @@ -143,7 +181,10 @@ const ButtonControlBar = (props) => > Date: Sat, 9 May 2020 00:43:20 +0200 Subject: [PATCH 28/43] Remove unused function --- app/src/components/Room.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 4dee360..e862be6 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -249,7 +249,6 @@ class Room extends React.PureComponent anchor={theme.direction === 'rtl' ? 'right' : 'left'} open={toolAreaOpen} onClose={() => toggleToolArea()} - onOpen={() => toggleToolArea()} classes={{ paper : classes.drawerPaper }} From 2dcecae8889472fe74b18aec0783fe8064afade6 Mon Sep 17 00:00:00 2001 From: Roman Drozd Date: Sat, 9 May 2020 01:26:50 +0200 Subject: [PATCH 29/43] Update pl translation --- app/src/translations/pl.json | 80 ++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 8d3ecf5..174684c 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -49,25 +49,25 @@ "room.spotlights": "Aktywni uczestnicy", "room.passive": "Pasywni uczestnicy", "room.videoPaused": "To wideo jest wstrzymane.", - "room.muteAll": null, - "room.stopAllVideo": null, - "room.closeMeeting": null, - "room.clearChat": null, - "room.clearFileSharing": null, - "room.speechUnsupported": null, - "room.moderatoractions": null, - "room.raisedHand": null, - "room.loweredHand": null, - "room.extraVideo": null, - "room.overRoomLimit": null, + "room.muteAll": "Wycisz wszystkich", + "room.stopAllVideo": "Zatrzymaj wszystkie Video", + "room.closeMeeting": "Zamknij spotkanie", + "room.clearChat": "Wyczyść Chat", + "room.clearFileSharing": "Wyczyść pliki", + "room.speechUnsupported": "Twoja przeglądarka nie rozpoznaje mowy", + "room.moderatoractions": "Akcje moderatora", + "room.raisedHand": "{displayName} podniósł rękę", + "room.loweredHand": "{displayName} opuścił rękę", + "room.extraVideo": "Dodatkowe Video", + "room.overRoomLimit": "Pokój jest pełny, spróbuj za jakiś czas.", "room.help": "Pomoc", - "room.about": null, - "room.shortcutKeys": null, + "room.about": "O pogramie", + "room.shortcutKeys": "Skróty klawiaturowe", - "me.mutedPTT": null, + "me.mutedPTT": "Masz wyciszony mikrofon, przytrzymaj spację aby mówić", - "roles.gotRole": null, - "roles.lostRole": null, + "roles.gotRole": "Masz rolę {role}", + "roles.lostRole": "Nie masz już roli {role}", "tooltip.login": "Zaloguj", "tooltip.logout": "Wyloguj", @@ -79,11 +79,11 @@ "tooltip.lobby": "Pokaż poczekalnię", "tooltip.settings": "Pokaż ustawienia", "tooltip.participants": "Pokaż uczestników", - "tooltip.kickParticipant": null, - "tooltip.muteParticipant": null, - "tooltip.muteParticipantVideo": null, - "tooltip.raisedHand": null, - "tooltip.muteScreenSharing": null, + "tooltip.kickParticipant": "Wyrzuć użytkownika", + "tooltip.muteParticipant": "Wycisz użytkownika", + "tooltip.muteParticipantVideo": "Wyłącz wideo użytkownika", + "tooltip.raisedHand": "Podnieś rękę", + "tooltip.muteScreenSharing": "Anuluj udostępniania pulpitu przez użytkownika", "label.roomName": "Nazwa konferencji", "label.chooseRoomButton": "Kontynuuj", @@ -97,7 +97,7 @@ "label.filesharing": "Udostępnianie plików", "label.participants": "Uczestnicy", "label.shareFile": "Udostępnij plik", - "label.shareGalleryFile": null, + "label.shareGalleryFile": "Udostępnij obraz", "label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane", "label.unknown": "Nieznane", "label.democratic": "Układ demokratyczny", @@ -108,12 +108,12 @@ "label.veryHigh": "Bardzo wysoka (FHD)", "label.ultra": "Ultra (UHD)", "label.close": "Zamknij", - "label.media": null, - "label.appearence": null, - "label.advanced": null, - "label.addVideo": null, - "label.promoteAllPeers": null, - "label.moreActions": null, + "label.media": "Media", + "label.appearence": "Wygląd", + "label.advanced": "Zaawansowane", + "label.addVideo": "Dodaj wideo", + "label.promoteAllPeers": "Wpuść wszystkich", + "label.moreActions": "Więcej akcji", "settings.settings": "Ustawienia", "settings.camera": "Kamera", @@ -131,14 +131,14 @@ "settings.advancedMode": "Tryb zaawansowany", "settings.permanentTopBar": "Stały górny pasek", "settings.lastn": "Liczba widocznych uczestników (zdalnych)", - "settings.hiddenControls": null, - "settings.notificationSounds": null, - "settings.showNotifications": null, - "settings.buttonControlBar": null, - "settings.echoCancellation": null, - "settings.autoGainControl": null, - "settings.noiseSuppression": null, - "settings.drawerOverlayed": null, + "settings.hiddenControls": "Ukryte kontrolki mediów", + "settings.notificationSounds": "Powiadomienia dźwiękiem", + "settings.showNotifications": "Pokaż powiadomienia", + "settings.buttonControlBar": "Rozdziel kontrolki mediów", + "settings.echoCancellation": "Usuwanie echa", + "settings.autoGainControl": "Auto korekta wzmocnienia", + "settings.noiseSuppression": "Wyciszenie szumów", + "settings.drawerOverlayed": "Szuflada nad zawartością", "filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku", @@ -180,8 +180,8 @@ "devices.cameraDisconnected": "Kamera odłączona", "devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery", - "moderator.clearChat": null, - "moderator.clearFiles": null, - "moderator.muteAudio": null, - "moderator.muteVideo": null + "moderator.clearChat": "Moderator wyczyścił chat", + "moderator.clearFiles": "Moderator wyczyścił pliki", + "moderator.muteAudio": "Moderator wyciszył audio", + "moderator.muteVideo": "Moderator wyciszył twoje video" } \ No newline at end of file From 36b40fd795e346372784f5bd631e2703498698b1 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Sat, 9 May 2020 12:15:05 +0200 Subject: [PATCH 30/43] Added german translations --- app/src/translations/de.json | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 402c4b3..c4b67fe 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -52,22 +52,22 @@ "room.muteAll": "Alle stummschalten", "room.stopAllVideo": "Alle Videos stoppen", "room.closeMeeting": "Meeting schließen", - "room.clearChat": null, - "room.clearFileSharing": null, + "room.clearChat": "Liste löschen", + "room.clearFileSharing": "Liste löschen", "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", - "room.moderatoractions": null, - "room.raisedHand": null, - "room.loweredHand": null, - "room.extraVideo": null, - "room.overRoomLimit": null, - "room.help": null, - "room.about": null, - "room.shortcutKeys": null, + "room.moderatoractions": "Moderator Aktionen", + "room.raisedHand": "{displayName} hebt die Hand", + "room.loweredHand": "{displayName} senkt die Hand", + "room.extraVideo": "Video hinzufügen", + "room.overRoomLimit": "Der Raum ist voll, probiere es später nochmal", + "room.help": "Hilfe", + "room.about": "Impressum", + "room.shortcutKeys": "Tastaturkürzel", "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", - "roles.gotRole": null, - "roles.lostRole": null, + "roles.gotRole": "Rolle erhalten: {role}", + "roles.lostRole": "Rolle entzogen: {role}", "tooltip.login": "Anmelden", "tooltip.logout": "Abmelden", @@ -79,11 +79,11 @@ "tooltip.lobby": "Warteraum", "tooltip.settings": "Einstellungen", "tooltip.participants": "Teilnehmer", - "tooltip.kickParticipant": "Teilnehmer rauswerfen", - "tooltip.muteParticipant": null, - "tooltip.muteParticipantVideo": null, - "tooltip.raisedHand": null, - "tooltip.muteScreenSharing": null, + "tooltip.kickParticipant": "Rauswerfen", + "tooltip.muteParticipant": "Stummschalten", + "tooltip.muteParticipantVideo": "Video stoppen", + "tooltip.raisedHand": "Hand heben", + "tooltip.muteScreenSharing": "Stoppe Bildschirmfreigabe", "label.roomName": "Name des Raums", "label.chooseRoomButton": "Weiter", @@ -97,7 +97,7 @@ "label.filesharing": "Dateien", "label.participants": "Teilnehmer", "label.shareFile": "Datei hochladen", - "label.shareGalleryFile": null, + "label.shareGalleryFile": "Bild teilen", "label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt", "label.unknown": "Unbekannt", "label.democratic": "Demokratisch", @@ -108,12 +108,12 @@ "label.veryHigh": "Sehr hoch (FHD)", "label.ultra": "Ultra (UHD)", "label.close": "Schließen", - "label.media": null, - "label.appearence": null, - "label.advanced": null, - "label.addVideo": null, - "label.promoteAllPeers": null, - "label.moreActions": null, + "label.media": "Audio / Video", + "label.appearence": "Erscheinung", + "label.advanced": "Erweiter", + "label.addVideo": "Video hinzufügen", + "label.promoteAllPeers": "Alle Teinehmer einlassen", + "label.moreActions": "Weitere Aktionen", "settings.settings": "Einstellungen", "settings.camera": "Kamera", @@ -131,14 +131,14 @@ "settings.advancedMode": "Erweiterter Modus", "settings.permanentTopBar": "Permanente obere Leiste", "settings.lastn": "Anzahl der sichtbaren Videos", - "settings.hiddenControls": null, - "settings.notificationSounds": null, - "settings.showNotifications": null, - "settings.buttonControlBar": null, - "settings.echoCancellation": null, - "settings.autoGainControl": null, - "settings.noiseSuppression": null, - "settings.drawerOverlayed": null, + "settings.hiddenControls": "Medienwerkzeugleiste automatisch ausblenden", + "settings.notificationSounds": "Audiosignal bei Benachrichtigungen", + "settings.showNotifications": "Zeige Benachrichtigungen", + "settings.buttonControlBar": "Seperate seitliche Medienwerkzeugleiste", + "settings.echoCancellation": "Echounterdrückung", + "settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)", + "settings.noiseSuppression": "Rauschunterdrückung", + "settings.drawerOverlayed": "Seitenpanel verdeckt Hauptinhalt", "filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei", @@ -180,8 +180,8 @@ "devices.cameraDisconnected": "Kamera getrennt", "devices.cameraError": "Fehler mit deiner Kamera", - "moderator.clearChat": null, - "moderator.clearFiles": null, - "moderator.muteAudio": null, - "moderator.muteVideo": null + "moderator.clearChat": "Moderator hat Chat gelöscht", + "moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht", + "moderator.muteAudio": "Moderator hat dich stummgeschaltet", + "moderator.muteVideo": "Moderator hat dein Video gestoppt" } From 7ab388a7142066b808232b21a3a165b67d8d78b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 10 May 2020 20:43:51 +0200 Subject: [PATCH 31/43] Propagate producer score into state. --- app/src/reducers/producers.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/reducers/producers.js b/app/src/reducers/producers.js index 64aee90..a27c06e 100644 --- a/app/src/reducers/producers.js +++ b/app/src/reducers/producers.js @@ -60,6 +60,17 @@ const producers = (state = initialState, action) => return { ...state, [producerId]: newProducer }; } + case 'SET_PRODUCER_SCORE': + { + const { producerId, score } = action.payload; + + const producer = state[producerId]; + + const newProducer = { ...producer, score }; + + return { ...state, [producerId]: newProducer }; + } + default: return state; } From a7557e3e151adea6ddabfbca3afba7ab45991705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 10 May 2020 23:08:43 +0200 Subject: [PATCH 32/43] Show quality indicator on me view. Fix "Me" text on me view on hover on extra videos and screen sharing. --- app/src/components/Containers/Me.js | 86 ++++++++------- app/src/components/Containers/Peer.js | 3 + .../components/VideoContainers/VideoView.js | 103 ++++++++++-------- 3 files changed, 105 insertions(+), 87 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 743c022..ea6e9e0 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -290,6 +290,28 @@ const Me = (props) => 'margin' : spacing }; + let audioScore = null; + + if (micProducer && micProducer.score) + { + audioScore = + micProducer.score.reduce( + (prev, curr) => + (prev.score < curr.score ? prev : curr) + ); + } + + let videoScore = null; + + if (webcamProducer && webcamProducer.score) + { + videoScore = + webcamProducer.score.reduce( + (prev, curr) => + (prev.score < curr.score ? prev : curr) + ); + } + return (
videoVisible={videoVisible} audioCodec={micProducer && micProducer.codec} videoCodec={webcamProducer && webcamProducer.codec} + audioScore={audioScore} + videoScore={videoScore} onChangeDisplayName={(displayName) => { roomClient.changeDisplayName(displayName); @@ -627,6 +652,18 @@ const Me = (props) => style={spacingStyle} >
+

+ +

}, 2000); }} > -

- -

- { smallContainer ? style={spacingStyle} >
-
setHover(true)} - onMouseOut={() => setHover(false)} - onTouchStart={() => - { - if (touchTimeout) - clearTimeout(touchTimeout); - - setHover(true); - }} - onTouchEnd={() => - { - - if (touchTimeout) - clearTimeout(touchTimeout); - - touchTimeout = setTimeout(() => - { - setHover(false); - }, 2000); - }} > -

- -

-
+ +

; + let quality = null; - if (videoScore || audioScore) + if (showQuality) { - const score = videoScore ? videoScore : audioScore; + quality = ; - switch (score.producerScore) + if (videoScore || audioScore) { - case 0: - case 1: + const score = videoScore ? videoScore : audioScore; + + switch (isMe ? score.score : score.producerScore) { - quality = ; - - break; - } - - case 2: - case 3: - { - quality = ; - - break; - } - - case 4: - case 5: - case 6: - { - quality = ; - - break; - } - - case 7: - case 8: - case 9: - { - quality = ; - - break; - } - - case 10: - { - quality = null; - - break; - } - - default: - { - break; + case 0: + case 1: + { + quality = ; + + break; + } + + case 2: + case 3: + { + quality = ; + + break; + } + + case 4: + case 5: + case 6: + { + quality = ; + + break; + } + + case 7: + case 8: + case 9: + { + quality = ; + + break; + } + + case 10: + { + quality = null; + + break; + } + + default: + { + break; + } } } } @@ -258,7 +264,7 @@ class VideoView extends React.PureComponent

{videoWidth}x{videoHeight}

}
- { !isMe && + { showQuality &&
{ quality @@ -438,6 +444,7 @@ class VideoView extends React.PureComponent VideoView.propTypes = { isMe : PropTypes.bool, + showQuality : PropTypes.bool, isScreen : PropTypes.bool, displayName : PropTypes.string, showPeerInfo : PropTypes.bool, From 67ea56b02ae048317872dcefaf1482766aa146d6 Mon Sep 17 00:00:00 2001 From: Luca Date: Mon, 11 May 2020 08:50:34 +0200 Subject: [PATCH 33/43] Italian translation update --- app/src/translations/it.json | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 5ce26b6..312a2ff 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -59,10 +59,10 @@ "room.raisedHand": "{displayName} ha alzato la mano", "room.loweredHand": "{displayName} ha abbassato la mano", "room.extraVideo": "Video extra", - "room.overRoomLimit": null, - "room.help": null, - "room.about": null, - "room.shortcutKeys": null, + "room.overRoomLimit": "La stanza è piena, riprova più tardi.", + "room.help": "Aiuto", + "room.about": "Informazioni su", + "room.shortcutKeys": "Scorciatoie da tastiera", "me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare", @@ -71,7 +71,7 @@ "tooltip.login": "Log in", "tooltip.logout": "Log out", - "tooltip.admitFromLobby": "Ammetti dalla lobby", + "tooltip.admitFromLobby": "Accetta partecipante dalla lobby", "tooltip.lockRoom": "Blocca stanza", "tooltip.unLockRoom": "Sblocca stanza", "tooltip.enterFullscreen": "Modalità schermo intero", @@ -79,10 +79,11 @@ "tooltip.lobby": "Mostra lobby", "tooltip.settings": "Mostra impostazioni", "tooltip.participants": "Mostra partecipanti", + "tooltip.kickParticipant": "Espelli partecipante", "tooltip.muteParticipant": "Muta partecipante", "tooltip.muteParticipantVideo": "Ferma video partecipante", "tooltip.raisedHand": "Mano alzata", - "tooltip.muteScreenSharing": null, + "tooltip.muteScreenSharing": "Ferma condivisione schermo partecipante", "label.roomName": "Nome della stanza", "label.chooseRoomButton": "Continua", @@ -96,7 +97,7 @@ "label.filesharing": "Condivisione file", "label.participants": "Partecipanti", "label.shareFile": "Condividi file", - "label.shareGalleryFile": null, + "label.shareGalleryFile": "Condividi immagine", "label.fileSharingUnsupported": "Condivisione file non supportata", "label.unknown": "Sconosciuto", "label.democratic": "Vista Democratica", @@ -112,7 +113,7 @@ "label.advanced": "Avanzate", "label.addVideo": "Aggiungi video", "label.promoteAllPeers": "Promuovi tutti", - "label.moreActions": null, + "label.moreActions": "Altre azioni", "settings.settings": "Impostazioni", "settings.camera": "Videocamera", @@ -132,12 +133,12 @@ "settings.lastn": "Numero di video visibili", "settings.hiddenControls": "Controlli media nascosti", "settings.notificationSounds": "Suoni di notifica", - "settings.showNotifications": null, - "settings.buttonControlBar": null, - "settings.echoCancellation": null, - "settings.autoGainControl": null, - "settings.noiseSuppression": null, - "settings.drawerOverlayed": null, + "settings.showNotifications": "Mostra notifiche", + "settings.buttonControlBar": "Controlli media separati", + "settings.echoCancellation": "Cancellazione echo", + "settings.autoGainControl": "Controllo guadagno automatico", + "settings.noiseSuppression": "Riduzione del rumore", + "settings.drawerOverlayed": "Barra laterale sovrapposta", "filesharing.saveFileError": "Impossibile salvare file", "filesharing.startingFileShare": "Tentativo di condivisione file", @@ -153,7 +154,7 @@ "devices.devicesChanged": "Il tuo dispositivo è cambiato, configura i dispositivi nel menù di impostazioni", "device.audioUnsupported": "Dispositivo audio non supportato", - "device.activateAudio": "Attiva audio", + "device.activateAudio": "Attiva audio", "device.muteAudio": "Silenzia audio", "device.unMuteAudio": "Riattiva audio", @@ -183,4 +184,4 @@ "moderator.clearFiles": "Il moderatore ha pulito i file", "moderator.muteAudio": "Il moderatore ha mutato il tuo audio", "moderator.muteVideo": "Il moderatore ha fermato il tuo video" -} +} \ No newline at end of file From 2623ef2eaa3164c2af79ad36063b7ca3de54dddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 12 May 2020 11:09:32 +0200 Subject: [PATCH 34/43] Fix lint --- .../MeetingDrawer/ParticipantList/ListPeer.js | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 76f9b10..7aa5181 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -254,57 +254,57 @@ const ListPeer = (props) => } { isModerator && micConsumer && - - { - e.stopPropagation(); - - roomClient.mutePeer(peer.id); - }} + title={intl.formatMessage({ + id : 'tooltip.muteParticipant', + defaultMessage : 'Mute globally participant mic' + })} + placement='bottom' > - { !micConsumer.remotelyPaused ? - - : - - } - - + + { + e.stopPropagation(); + + roomClient.mutePeer(peer.id); + }} + > + { !micConsumer.remotelyPaused ? + + : + + } + + } { isModerator && webcamConsumer && - - { - e.stopPropagation(); - - roomClient.stopPeerVideo(peer.id); - }} + title={intl.formatMessage({ + id : 'tooltip.muteParticipantVideo', + defaultMessage : 'Mute globally participant video' + })} + placement='bottom' > - { !webcamConsumer.remotelyPaused ? - - : - - } - - + + { + e.stopPropagation(); + + roomClient.stopPeerVideo(peer.id); + }} + > + { !webcamConsumer.remotelyPaused ? + + : + + } + + } {children}
From 1e5b6680c4f66695506b4cf3db0afc5e93a054c3 Mon Sep 17 00:00:00 2001 From: Luca Date: Tue, 12 May 2020 18:09:08 +0200 Subject: [PATCH 35/43] Translate moderator mute actions on peer --- .../components/MeetingDrawer/ParticipantList/ListPeer.js | 8 ++++---- app/src/translations/cn.json | 3 +++ app/src/translations/cs.json | 3 +++ app/src/translations/de.json | 3 +++ app/src/translations/dk.json | 3 +++ app/src/translations/el.json | 3 +++ app/src/translations/en.json | 3 +++ app/src/translations/es.json | 3 +++ app/src/translations/fr.json | 3 +++ app/src/translations/hr.json | 3 +++ app/src/translations/hu.json | 3 +++ app/src/translations/it.json | 3 +++ app/src/translations/lv.json | 3 +++ app/src/translations/nb.json | 3 +++ app/src/translations/pl.json | 3 +++ app/src/translations/pt.json | 3 +++ app/src/translations/ro.json | 3 +++ app/src/translations/tr.json | 3 +++ app/src/translations/uk.json | 3 +++ 19 files changed, 58 insertions(+), 4 deletions(-) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 7aa5181..0ca5d89 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -255,8 +255,8 @@ const ListPeer = (props) => { isModerator && micConsumer && @@ -282,8 +282,8 @@ const ListPeer = (props) => { isModerator && webcamConsumer && diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 0ce5f5b..cd58a88 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "房间名称", "label.chooseRoomButton": "继续", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 3eac16c..826dcef 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -83,6 +83,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Jméno místnosti", "label.chooseRoomButton": "Pokračovat", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index c4b67fe..fc1de49 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Video stoppen", "tooltip.raisedHand": "Hand heben", "tooltip.muteScreenSharing": "Stoppe Bildschirmfreigabe", + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Name des Raums", "label.chooseRoomButton": "Weiter", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index c5c3a2d..778e10d 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Værelsesnavn", "label.chooseRoomButton": "Fortsæt", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index d71764f..22e991e 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Όνομα δωματίου", "label.chooseRoomButton": "Συνέχεια", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 344d660..776ae29 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Mute participant video", "tooltip.raisedHand": "Raise hand", "tooltip.muteScreenSharing": "Mute participant share", + "tooltip.muteParticipantAudioModerator": "Mute participant audio globally", + "tooltip.muteParticipantVideoModerator": "Mute participant video globally", + "tooltip.muteScreenSharingModerator": "Mute participant screen share globally", "label.roomName": "Room name", "label.chooseRoomButton": "Continue", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 758b0c8..abb06f4 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Nombre de la sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 2eb7edf..7d17970 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Nom de la salle", "label.chooseRoomButton": "Continuer", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index c0f8879..61d6d69 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Ne primaj video sudionika", "tooltip.raisedHand": "Podigni ruku", "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Naziv sobe", "label.chooseRoomButton": "Nastavi", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index cf65f1d..9e748d6 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása", "tooltip.raisedHand": "Jelentkezés", "tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése", + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 312a2ff..a4345cc 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Ferma video partecipante", "tooltip.raisedHand": "Mano alzata", "tooltip.muteScreenSharing": "Ferma condivisione schermo partecipante", + "tooltip.muteParticipantAudioModerator": "Muta partecipante", + "tooltip.muteParticipantVideoModerator": "Ferma video partecipante", + "tooltip.muteScreenSharingModerator": "Ferma condivisione schermo partecipante", "label.roomName": "Nome della stanza", "label.chooseRoomButton": "Continua", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index 7bf0b24..20eb8f8 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -83,6 +83,9 @@ "tooltip.muteParticipantVideo": "Atslēgt dalībnieka video", "tooltip.raisedHand": "Pacelt roku", "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Sapulces telpas nosaukums (ID)", "label.chooseRoomButton": "Turpināt", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 251858b..3714f76 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Demp deltakervideo", "tooltip.raisedHand": "Rekk opp hånden", "tooltip.muteScreenSharing": "Demp deltaker skjermdeling", + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Møtenavn", "label.chooseRoomButton": "Fortsett", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 174684c..147a87e 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": "Wyłącz wideo użytkownika", "tooltip.raisedHand": "Podnieś rękę", "tooltip.muteScreenSharing": "Anuluj udostępniania pulpitu przez użytkownika", + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Nazwa konferencji", "label.chooseRoomButton": "Kontynuuj", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 8250231..c386bcf 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Nome da sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 1ba455c..81b778b 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Numele camerei", "label.chooseRoomButton": "Continuare", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 524c557..41d8322 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Oda adı", "label.chooseRoomButton": "Devam", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index a8cc077..81c5c81 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -84,6 +84,9 @@ "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, "tooltip.muteScreenSharing": null, + "tooltip.muteParticipantAudioModerator": null, + "tooltip.muteParticipantVideoModerator": null, + "tooltip.muteScreenSharingModerator": null, "label.roomName": "Назва кімнати", "label.chooseRoomButton": "Продовжити", From 0e40ed76961d17303097fc44f548c20267ac87bf Mon Sep 17 00:00:00 2001 From: Luca Date: Tue, 12 May 2020 18:17:51 +0200 Subject: [PATCH 36/43] Update it translation --- app/src/translations/it.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/translations/it.json b/app/src/translations/it.json index a4345cc..69add90 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -84,9 +84,9 @@ "tooltip.muteParticipantVideo": "Ferma video partecipante", "tooltip.raisedHand": "Mano alzata", "tooltip.muteScreenSharing": "Ferma condivisione schermo partecipante", - "tooltip.muteParticipantAudioModerator": "Muta partecipante", - "tooltip.muteParticipantVideoModerator": "Ferma video partecipante", - "tooltip.muteScreenSharingModerator": "Ferma condivisione schermo partecipante", + "tooltip.muteParticipantAudioModerator": "Sospendi audio globale", + "tooltip.muteParticipantVideoModerator": "Sospendi video globale", + "tooltip.muteScreenSharingModerator": "Sospendi condivisione schermo globale", "label.roomName": "Nome della stanza", "label.chooseRoomButton": "Continua", From b3764a1d64be877655a3296b358f875966253913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 12 May 2020 20:29:50 +0200 Subject: [PATCH 37/43] fixes #343 --- app/src/components/Containers/Me.js | 410 +++++++++++++++------------- 1 file changed, 213 insertions(+), 197 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index ea6e9e0..8d45473 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -400,195 +400,207 @@ const Me = (props) => { smallContainer ? - - { - if (micState === 'off') - roomClient.enableMic(); - else if (micState === 'on') - roomClient.muteMic(); - else - roomClient.unmuteMic(); - }} - > - { micState === 'on' ? - - : - - } - +
+ + { + if (micState === 'off') + roomClient.enableMic(); + else if (micState === 'on') + roomClient.muteMic(); + else + roomClient.unmuteMic(); + }} + > + { micState === 'on' ? + + : + + } + +
: - - { - if (micState === 'off') - roomClient.enableMic(); - else if (micState === 'on') - roomClient.muteMic(); - else - roomClient.unmuteMic(); - }} - > - { micState === 'on' ? - - : - - } - +
+ + { + if (micState === 'off') + roomClient.enableMic(); + else if (micState === 'on') + roomClient.muteMic(); + else + roomClient.unmuteMic(); + }} + > + { micState === 'on' ? + + : + + } + +
}
{ smallContainer ? - - { - webcamState === 'on' ? - roomClient.disableWebcam() : - roomClient.enableWebcam(); - }} - > - { webcamState === 'on' ? - - : - - } - +
+ + { + webcamState === 'on' ? + roomClient.disableWebcam() : + roomClient.enableWebcam(); + }} + > + { webcamState === 'on' ? + + : + + } + +
: - - { - webcamState === 'on' ? - roomClient.disableWebcam() : - roomClient.enableWebcam(); - }} - > - { webcamState === 'on' ? - - : - - } - +
+ + { + webcamState === 'on' ? + roomClient.disableWebcam() : + roomClient.enableWebcam(); + }} + > + { webcamState === 'on' ? + + : + + } + +
}
{ me.browser.platform !== 'mobile' && { smallContainer ? - + - { - switch (screenState) - { - case 'on': - { - roomClient.disableScreenSharing(); - break; - } - case 'off': - { - roomClient.enableScreenSharing(); - break; - } - default: - { - break; - } } - }} - > - { (screenState === 'on' || screenState === 'unsupported') && + color='primary' + size='small' + onClick={() => + { + switch (screenState) + { + case 'on': + { + roomClient.disableScreenSharing(); + break; + } + case 'off': + { + roomClient.enableScreenSharing(); + break; + } + default: + { + break; + } + } + }} + > + { (screenState === 'on' || screenState === 'unsupported') && - } - { screenState === 'off' && + } + { screenState === 'off' && - } + } - + +
: - + - { - switch (screenState) - { - case 'on': - { - roomClient.disableScreenSharing(); - break; - } - case 'off': - { - roomClient.enableScreenSharing(); - break; - } - default: - { - break; - } } - }} - > - { (screenState === 'on' || screenState === 'unsupported') && + color={screenState === 'on' ? 'primary' : 'default'} + size='large' + onClick={() => + { + switch (screenState) + { + case 'on': + { + roomClient.disableScreenSharing(); + break; + } + case 'off': + { + roomClient.enableScreenSharing(); + break; + } + default: + { + break; + } + } + }} + > + { (screenState === 'on' || screenState === 'unsupported') && - } - { screenState === 'off' && + } + { screenState === 'off' && - } - + } + +
} } @@ -692,39 +704,43 @@ const Me = (props) => > { smallContainer ? - - { - roomClient.disableExtraVideo(producer.id); - }} - > - +
+ + { + roomClient.disableExtraVideo(producer.id); + }} + > + - + +
: - - { - roomClient.disableExtraVideo(producer.id); - }} - > - - +
+ + { + roomClient.disableExtraVideo(producer.id); + }} + > + + +
}
From d84d30e40215c4ffb11a96fa70b92235914b1e24 Mon Sep 17 00:00:00 2001 From: mi4aux <63875263+mi4aux@users.noreply.github.com> Date: Tue, 12 May 2020 22:52:30 +0200 Subject: [PATCH 38/43] Typofix german translation --- app/src/translations/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/translations/de.json b/app/src/translations/de.json index c4b67fe..b5e5f5d 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -64,7 +64,7 @@ "room.about": "Impressum", "room.shortcutKeys": "Tastaturkürzel", - "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", + "me.mutedPTT": "Du bist stummgeschaltet. Halte die SPACE-Taste um zu sprechen", "roles.gotRole": "Rolle erhalten: {role}", "roles.lostRole": "Rolle entzogen: {role}", @@ -110,9 +110,9 @@ "label.close": "Schließen", "label.media": "Audio / Video", "label.appearence": "Erscheinung", - "label.advanced": "Erweiter", + "label.advanced": "Erweitert", "label.addVideo": "Video hinzufügen", - "label.promoteAllPeers": "Alle Teinehmer einlassen", + "label.promoteAllPeers": "Alle Teilnehmer reinlassen", "label.moreActions": "Weitere Aktionen", "settings.settings": "Einstellungen", @@ -134,7 +134,7 @@ "settings.hiddenControls": "Medienwerkzeugleiste automatisch ausblenden", "settings.notificationSounds": "Audiosignal bei Benachrichtigungen", "settings.showNotifications": "Zeige Benachrichtigungen", - "settings.buttonControlBar": "Seperate seitliche Medienwerkzeugleiste", + "settings.buttonControlBar": "Separate seitliche Medienwerkzeugleiste", "settings.echoCancellation": "Echounterdrückung", "settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)", "settings.noiseSuppression": "Rauschunterdrückung", From af1dcd6bab9cf06a3c3aaf075bd09f4fa84647e4 Mon Sep 17 00:00:00 2001 From: mi4aux <63875263+mi4aux@users.noreply.github.com> Date: Tue, 12 May 2020 22:55:42 +0200 Subject: [PATCH 39/43] Suggestions for better german translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit room.closeMeeting: "Meeting beenden" or alternatively "Meeting abschließen" room.about: "Über" on software often also like "Über multiparty-meeting" or "Info" / "Informationen" in general "Impressum" is more like "imprint" / "legal info" on a website, .. but depends on what it's ment to be label.medium: "Mittel" better (true) german, "Medium" obtained on quality is german-english mix (or has other meaning) label.appearence: "Ansicht" shorter, more often used on software .. alternatively "Aussehen" or "Anzeige" --- app/src/translations/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/translations/de.json b/app/src/translations/de.json index b5e5f5d..e2ecc02 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -51,7 +51,7 @@ "room.videoPaused": "Video gestoppt", "room.muteAll": "Alle stummschalten", "room.stopAllVideo": "Alle Videos stoppen", - "room.closeMeeting": "Meeting schließen", + "room.closeMeeting": "Meeting beenden", "room.clearChat": "Liste löschen", "room.clearFileSharing": "Liste löschen", "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", @@ -61,7 +61,7 @@ "room.extraVideo": "Video hinzufügen", "room.overRoomLimit": "Der Raum ist voll, probiere es später nochmal", "room.help": "Hilfe", - "room.about": "Impressum", + "room.about": "Über", "room.shortcutKeys": "Tastaturkürzel", "me.mutedPTT": "Du bist stummgeschaltet. Halte die SPACE-Taste um zu sprechen", @@ -103,13 +103,13 @@ "label.democratic": "Demokratisch", "label.filmstrip": "Filmstreifen", "label.low": "Niedrig", - "label.medium": "Medium", + "label.medium": "Mittel", "label.high": "Hoch (HD)", "label.veryHigh": "Sehr hoch (FHD)", "label.ultra": "Ultra (UHD)", "label.close": "Schließen", "label.media": "Audio / Video", - "label.appearence": "Erscheinung", + "label.appearence": "Ansicht", "label.advanced": "Erweitert", "label.addVideo": "Video hinzufügen", "label.promoteAllPeers": "Alle Teilnehmer reinlassen", From bd06d68742bf6720b942724aa7a7915572994b94 Mon Sep 17 00:00:00 2001 From: mi4aux <63875263+mi4aux@users.noreply.github.com> Date: Tue, 12 May 2020 22:56:46 +0200 Subject: [PATCH 40/43] Typofix variablename --- app/src/components/Settings/Settings.js | 2 +- app/src/translations/cn.json | 2 +- app/src/translations/cs.json | 2 +- app/src/translations/de.json | 2 +- app/src/translations/dk.json | 2 +- app/src/translations/el.json | 2 +- app/src/translations/en.json | 2 +- app/src/translations/es.json | 2 +- app/src/translations/fr.json | 2 +- app/src/translations/hr.json | 2 +- app/src/translations/hu.json | 2 +- app/src/translations/it.json | 2 +- app/src/translations/lv.json | 2 +- app/src/translations/nb.json | 2 +- app/src/translations/pl.json | 2 +- app/src/translations/pt.json | 2 +- app/src/translations/ro.json | 2 +- app/src/translations/tr.json | 2 +- app/src/translations/uk.json | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 6633829..cbfb8b1 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -95,7 +95,7 @@ const Settings = ({ /> diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 0ce5f5b..bf709ef 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -109,7 +109,7 @@ "label.ultra": "超高 (UHD)", "label.close": "关闭", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 3eac16c..b5c48f0 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -108,7 +108,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Zavřít", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/de.json b/app/src/translations/de.json index e2ecc02..6acb321 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Schließen", "label.media": "Audio / Video", - "label.appearence": "Ansicht", + "label.appearance": "Ansicht", "label.advanced": "Erweitert", "label.addVideo": "Video hinzufügen", "label.promoteAllPeers": "Alle Teilnehmer reinlassen", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index c5c3a2d..ada9f33 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Luk", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/el.json b/app/src/translations/el.json index d71764f..4384c0a 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Κλείσιμο", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 344d660..a8ec093 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Close", "label.media": "Media", - "label.appearence": "Appearence", + "label.appearance": "Appearence", "label.advanced": "Advanced", "label.addVideo": "Add video", "label.promoteAllPeers": "Promote all", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 758b0c8..21e711d 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Cerrar", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 2eb7edf..aad845d 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra Haute Définition", "label.close": "Fermer", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index c0f8879..ac554d8 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra visoka (UHD)", "label.close": "Zatvori", "label.media": "Medij", - "label.appearence": "Prikaz", + "label.appearance": "Prikaz", "label.advanced": "Napredno", "label.addVideo": "Dodaj video", "label.promoteAllPeers": "Promoviraj sve", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index cf65f1d..88fccce 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra magas (UHD)", "label.close": "Bezár", "label.media": "Média", - "label.appearence": "Megjelenés", + "label.appearance": "Megjelenés", "label.advanced": "Részletek", "label.addVideo": "Videó hozzáadása", "label.promoteAllPeers": "Mindenkit beengedek", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 312a2ff..ae210c5 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Chiudi", "label.media": "Media", - "label.appearence": "Aspetto", + "label.appearance": "Aspetto", "label.advanced": "Avanzate", "label.addVideo": "Aggiungi video", "label.promoteAllPeers": "Promuovi tutti", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index 7bf0b24..7a38196 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -107,7 +107,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Aizvērt", "label.media": "Mediji", - "label.appearence": "Izskats", + "label.appearance": "Izskats", "label.advanced": "Advancēts", "label.addVideo": "Pievienot video", "label.moreActions": null, diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 251858b..b755d6e 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Lukk", "label.media": "Media", - "label.appearence": "Utseende", + "label.appearance": "Utseende", "label.advanced": "Avansert", "label.addVideo": "Legg til video", "label.promoteAllPeers": "Slipp inn alle", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 174684c..142bf34 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Zamknij", "label.media": "Media", - "label.appearence": "Wygląd", + "label.appearance": "Wygląd", "label.advanced": "Zaawansowane", "label.addVideo": "Dodaj wideo", "label.promoteAllPeers": "Wpuść wszystkich", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 8250231..3b79148 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Fechar", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 1ba455c..ab70c94 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -109,7 +109,7 @@ "label.ultra": "Rezoluție ultra înaltă (UHD)", "label.close": "Închide", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 524c557..2716d13 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -109,7 +109,7 @@ "label.ultra": "Ultra (UHD)", "label.close": "Kapat", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index a8cc077..b6b6526 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -109,7 +109,7 @@ "label.ultra": "Ультра (UHD)", "label.close": "Закрити", "label.media": null, - "label.appearence": null, + "label.appearance": null, "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, From 41d62cf9b808d71425858ffea9be11aafcdafc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 13 May 2020 07:57:38 +0200 Subject: [PATCH 41/43] Update hungarian translation --- app/src/translations/hu.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 4a2cc50..dee4034 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -84,9 +84,9 @@ "tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása", "tooltip.raisedHand": "Jelentkezés", "tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése", - "tooltip.muteParticipantAudioModerator": null, - "tooltip.muteParticipantVideoModerator": null, - "tooltip.muteScreenSharingModerator": null, + "tooltip.muteParticipantAudioModerator": "Résztvevő hangjának általános némítása", + "tooltip.muteParticipantVideoModerator": "Résztvevő videójának általános némítása", + "tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásának általános némítása", "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", @@ -141,7 +141,7 @@ "settings.echoCancellation": "Visszhangelnyomás", "settings.autoGainControl": "Automatikus hangerő", "settings.noiseSuppression": "Zajelnyomás", - "settings.drawerOverlayed": null, + "settings.drawerOverlayed": "Oldalsáv a tartalom felett", "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", From e4f6bde1647d9e9d6b8fd48e38d6c367e8aee455 Mon Sep 17 00:00:00 2001 From: Astagor Date: Wed, 13 May 2020 08:27:21 +0200 Subject: [PATCH 42/43] Added description of arrow keys for browsing passive peers into spotlight --- app/src/components/Controls/Help.js | 3 ++- app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/lv.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + 19 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/components/Controls/Help.js b/app/src/components/Controls/Help.js index 98415b5..534f3e2 100644 --- a/app/src/components/Controls/Help.js +++ b/app/src/components/Controls/Help.js @@ -23,7 +23,8 @@ const shortcuts=[ { key: '1', label: 'label.democratic', defaultMessage: 'Democratic View' }, { key: '2', label: 'label.filmstrip', defaultMessage: 'Filmstrip View' }, { key: 'space', label: 'me.mutedPTT', defaultMessage: 'Push SPACE to talk' }, - { key: 'a', label: 'label.advanced', defaultMessage: 'Show advanced information' } + { key: 'a', label: 'label.advanced', defaultMessage: 'Show advanced information' }, + { key: String.fromCharCode(8592)+' '+String.fromCharCode(8594), label: 'room.browsePeersSpotlight', defaultMessage: 'Browse participants into Spotlight' } ]; const styles = (theme) => ({ diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 0ae3fe0..2e4338c 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 8edfc1c..b76dc3b 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -62,6 +62,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 0a2368a..e0cf1da 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -63,6 +63,7 @@ "room.help": "Hilfe", "room.about": "Über", "room.shortcutKeys": "Tastaturkürzel", + "room.browsePeersSpotlight": null, "me.mutedPTT": "Du bist stummgeschaltet. Halte die SPACE-Taste um zu sprechen", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index fb9e6fc..0236280 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 2d9cb94..25df1a7 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 5014089..6237516 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -63,6 +63,7 @@ "room.help": "Help", "room.about": "About", "room.shortcutKeys": "Shortcut Keys", + "room.browsePeersSpotlight": null, "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 629bb2a..8075ff5 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 9776ac0..ea87e99 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 4788483..ef72efa 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index dee4034..58d485a 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -63,6 +63,7 @@ "room.help": "Segítség", "room.about": "Névjegy", "room.shortcutKeys": "Billentyűparancsok", + "room.browsePeersSpotlight": null, "me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a szóköz billentyűt", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 7a8bf62..243eaca 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -63,6 +63,7 @@ "room.help": "Aiuto", "room.about": "Informazioni su", "room.shortcutKeys": "Scorciatoie da tastiera", + "room.browsePeersSpotlight": null, "me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index 27adce1..824e26b 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -62,6 +62,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": "Jūs esat noklusināts. Turiet taustiņu SPACE-BAR, lai runātu", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 172fcf9..10f387a 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index dd12971..948a62d 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -63,6 +63,7 @@ "room.help": "Pomoc", "room.about": "O pogramie", "room.shortcutKeys": "Skróty klawiaturowe", + "room.browsePeersSpotlight": null, "me.mutedPTT": "Masz wyciszony mikrofon, przytrzymaj spację aby mówić", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 13fd0f6..b354e65 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 8e05b0b..d718998 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index dc05c2f..8647707 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index b06aef4..dce9425 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -63,6 +63,7 @@ "room.help": null, "room.about": null, "room.shortcutKeys": null, + "room.browsePeersSpotlight": null, "me.mutedPTT": null, From 774532e33ee979fa6f14a7815e4119f7b280b9b2 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Wed, 13 May 2020 15:12:21 +0800 Subject: [PATCH 43/43] Create zh-Hant translation --- app/src/index.js | 16 ++- app/src/translations/tw.json | 190 +++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 app/src/translations/tw.json diff --git a/app/src/index.js b/app/src/index.js index e89d06d..0a9228c 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -31,7 +31,8 @@ import messagesFrench from './translations/fr'; import messagesGreek from './translations/el'; import messagesRomanian from './translations/ro'; import messagesPortuguese from './translations/pt'; -import messagesChinese from './translations/cn'; +import messagesChineseSimplified from './translations/cn'; +import messagesChineseTraditional from './translations/tw'; import messagesSpanish from './translations/es'; import messagesCroatian from './translations/hr'; import messagesCzech from './translations/cs'; @@ -58,7 +59,8 @@ const messages = 'el' : messagesGreek, 'ro' : messagesRomanian, 'pt' : messagesPortuguese, - 'zh' : messagesChinese, + 'zh-hans' : messagesChineseSimplified, + 'zh-hant' : messagesChineseTraditional, 'es' : messagesSpanish, 'hr' : messagesCroatian, 'cs' : messagesCzech, @@ -68,7 +70,15 @@ const messages = 'lv' : messagesLatvian }; -const locale = navigator.language.split(/[-_]/)[0]; // language without region code +let browserLanguage = (navigator.language || navigator.browserLanguage).toLowerCase() +let locale = browserLanguage.split(/[-_]/)[0]; // language without region code +if (locale === 'zh') +{ + if (browserLanguage === 'zh-cn') + locale = 'zh-hans' + else + locale = 'zh-hant' +} const intl = createIntl({ locale, diff --git a/app/src/translations/tw.json b/app/src/translations/tw.json new file mode 100644 index 0000000..83f0c02 --- /dev/null +++ b/app/src/translations/tw.json @@ -0,0 +1,190 @@ +{ + "socket.disconnected": "您已斷開連接", + "socket.reconnecting": "嘗試重新連接", + "socket.reconnected": "您已重新連接", + "socket.requestError": "服務器請求錯誤", + + "room.chooseRoom": "選擇您要加入的房間的名稱", + "room.cookieConsent": "這個網站使用Cookies來提升您的使用者體驗", + "room.consentUnderstand": "了解", + "room.joined": "您已加入房間", + "room.cantJoin": "無法加入房間", + "room.youLocked": "您已鎖定房間", + "room.cantLock": "無法鎖定房間", + "room.youUnLocked": "您解鎖了房間", + "room.cantUnLock": "無法解鎖房間", + "room.locked": "房間已鎖定", + "room.unlocked": "房間現已解鎖", + "room.newLobbyPeer": "新參與者進入大廳", + "room.lobbyPeerLeft": "參與者離開大廳", + "room.lobbyPeerChangedDisplayName": "大廳的參與者將名稱變更為 {displayName}", + "room.lobbyPeerChangedPicture": "大廳的參與者變更了圖片", + "room.setAccessCode": "設置房間的進入密碼", + "room.accessCodeOn": "房間的進入密碼現已啟用", + "room.accessCodeOff": "房間的進入密碼已停用", + "room.peerChangedDisplayName": "{oldDisplayName} 已變更名稱為 {displayName}", + "room.newPeer": "{displayName} 加入了會議室", + "room.newFile": "有新文件", + "room.toggleAdvancedMode": "切換進階模式", + "room.setDemocraticView": "已更改為使用者佈局", + "room.setFilmStripView": "已更改為投影片佈局", + "room.loggedIn": "您已登入", + "room.loggedOut": "您已登出", + "room.changedDisplayName": "您的顯示名稱已變更為 {displayName}", + "room.changeDisplayNameError": "更改顯示名稱時發生錯誤", + "room.chatError": "無法發送聊天消息", + "room.aboutToJoin": "您即將參加會議", + "room.roomId": "房間ID: {roomName}", + "room.setYourName": "設置您的顯示名稱,並選擇您想加入的方式:", + "room.audioOnly": "僅通話", + "room.audioVideo": "通話和視訊", + "room.youAreReady": "準備完畢!", + "room.emptyRequireLogin": "房間是空的! 您可以登錄以開始會議或等待主持人加入", + "room.locketWait": "房間已鎖定! 請等待其他人允許您進入...", + "room.lobbyAdministration": "大廳管理", + "room.peersInLobby": "大廳的參與者", + "room.lobbyEmpty": "大廳目前沒有人", + "room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}", + "room.me": "我", + "room.spotlights": "Spotlight中的參與者", + "room.passive": "被動參與者", + "room.videoPaused": "視訊已關閉", + "room.muteAll": "全部靜音", + "room.stopAllVideo": "關閉全部視訊", + "room.closeMeeting": "關閉會議", + "room.clearChat": "清除聊天", + "room.clearFileSharing": "清除檔案", + "room.speechUnsupported": "您的瀏覽器不支援語音辨識", + "room.moderatoractions": "管理員動作", + "room.raisedHand": "{displayName} 舉手了", + "room.loweredHand": "{displayName} 放下了他的手", + "room.extraVideo": "其他視訊", + "room.overRoomLimit": "房間已滿,請稍後重試", + "room.help": "幫助", + "room.about": "關於", + "room.shortcutKeys": "鍵盤快速鍵", + + "me.mutedPTT": "您已靜音,請按下 空白鍵 來說話", + + "roles.gotRole": "您已取得身份: {role}", + "roles.lostRole": "您的 {role} 身份已被撤銷", + + "tooltip.login": "登入", + "tooltip.logout": "登出", + "tooltip.admitFromLobby": "從大廳允許", + "tooltip.lockRoom": "鎖定房間", + "tooltip.unLockRoom": "解鎖房間", + "tooltip.enterFullscreen": "進入全螢幕", + "tooltip.leaveFullscreen": "退出全螢幕", + "tooltip.lobby": "顯示大廳", + "tooltip.settings": "顯示設置", + "tooltip.participants": "顯示參加者", + "tooltip.kickParticipant": "踢出", + "tooltip.muteParticipant": "靜音", + "tooltip.muteParticipantVideo": "隱藏視訊", + "tooltip.raisedHand": "舉手", + "tooltip.muteScreenSharing": "隱藏螢幕分享", + "tooltip.muteParticipantAudioModerator": "關閉聲音", + "tooltip.muteParticipantVideoModerator": "關閉視訊", + "tooltip.muteScreenSharingModerator": "關閉螢幕分享", + + "label.roomName": "房間名稱", + "label.chooseRoomButton": "繼續", + "label.yourName": "您的名字", + "label.newWindow": "新視窗", + "label.fullscreen": "全螢幕", + "label.openDrawer": "打開側邊欄", + "label.leave": "離開", + "label.chatInput": "輸入聊天訊息", + "label.chat": "聊天", + "label.filesharing": "文件分享", + "label.participants": "參與者", + "label.shareFile": "分享文件", + "label.shareGalleryFile": "分享圖片", + "label.fileSharingUnsupported": "不支援文件分享", + "label.unknown": "未知", + "label.democratic": "使用者佈局", + "label.filmstrip": "投影片佈局", + "label.low": "低", + "label.medium": "中", + "label.high": "高 (HD)", + "label.veryHigh": "非常高 (FHD)", + "label.ultra": "超高 (UHD)", + "label.close": "關閉", + "label.media": "媒體", + "label.appearance": "外觀", + "label.advanced": "進階", + "label.addVideo": "新增視訊", + "label.promoteAllPeers": "提升全部", + "label.moreActions": "更多", + + "settings.settings": "設置", + "settings.camera": "視訊來源", + "settings.selectCamera": "選擇視訊來源", + "settings.cantSelectCamera": "無法選擇此視訊來源", + "settings.audio": "音訊來源", + "settings.selectAudio": "選擇音訊來源", + "settings.cantSelectAudio": "無法選擇音訊來源", + "settings.audioOutput": "音訊輸出", + "settings.selectAudioOutput": "選擇音訊輸出設備", + "settings.cantSelectAudioOutput": "無法選擇音訊輸出設備", + "settings.resolution": "選擇視訊解析度", + "settings.layout": "房間佈局", + "settings.selectRoomLayout": "選擇房間佈局", + "settings.advancedMode": "進階模式", + "settings.permanentTopBar": "固定頂端列", + "settings.lastn": "視訊數量上限", + "settings.hiddenControls": "隱藏控制按鈕", + "settings.notificationSounds": "通知音效", + "settings.showNotifications": "顯示通知", + "settings.buttonControlBar": "獨立控制按鈕", + "settings.echoCancellation": "回音消除", + "settings.autoGainControl": "自動增益控制", + "settings.noiseSuppression": "噪音消除", + "settings.drawerOverlayed": "側邊欄覆蓋畫面", + + "filesharing.saveFileError": "無法保存文件", + "filesharing.startingFileShare": "開始分享文件", + "filesharing.successfulFileShare": "文件已成功分享", + "filesharing.unableToShare": "無法分享文件", + "filesharing.error": "文件分享發生錯誤", + "filesharing.finished": "文件分享成功", + "filesharing.save": "保存文件", + "filesharing.sharedFile": "{displayName} 分享了一個文件", + "filesharing.download": "下載文件", + "filesharing.missingSeeds": "如果過了很久還是無法下載,則可能沒有人播種了。請讓上傳者重新上傳您想要的文件。", + + "devices.devicesChanged": "您的設備已更改,請在設置中設定您的設備", + + "device.audioUnsupported": "不支援您的音訊格式", + "device.activateAudio": "開啟音訊", + "device.muteAudio": "靜音", + "device.unMuteAudio": "取消靜音", + + "device.videoUnsupported": "不支援您的視訊格式", + "device.startVideo": "開啟視訊", + "device.stopVideo": "關閉視訊", + + "device.screenSharingUnsupported": "不支援您的螢幕分享格式", + "device.startScreenSharing": "開始螢幕分享", + "device.stopScreenSharing": "停止螢幕分享", + + "devices.microphoneDisconnected": "麥克風已斷開", + "devices.microphoneError": "麥克風發生錯誤", + "devices.microphoneMute": "麥克風靜音", + "devices.microphoneUnMute": "取消麥克風靜音", + "devices.microphoneEnable": "麥克風已啟用", + "devices.microphoneMuteError": "無法使麥克風靜音", + "devices.microphoneUnMuteError": "無法取消麥克風靜音", + + "devices.screenSharingDisconnected" : "螢幕分享已斷開", + "devices.screenSharingError": "螢幕分享時發生錯誤", + + "devices.cameraDisconnected": "相機已斷開連接", + "devices.cameraError": "存取相機時發生錯誤", + + "moderator.clearChat": "管理員清除了聊天", + "moderator.clearFiles": "管理員清除了所有檔案", + "moderator.muteAudio": "您已被管理員靜音", + "moderator.muteVideo": "您的視訊已被管理員關閉" +} \ No newline at end of file