From fd1e512a80ef095d48408796e84fd57c7ba344ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 3 Apr 2019 00:09:27 +0200 Subject: [PATCH] Added redux selectors to improve performance. Fixed drawer. Cleaned up code and removed some unused code. --- app/package.json | 3 +- app/src/components/Containers/Me.js | 19 +-- app/src/components/Containers/Peer.js | 32 ++--- .../components/MeetingDrawer/MeetingDrawer.js | 113 ++++++++-------- .../MeetingDrawer/ParticipantList/ListPeer.js | 28 ++-- app/src/components/MeetingViews/Democratic.js | 36 +++--- app/src/components/PeerAudio/AudioPeer.js | 39 ------ app/src/components/PeerAudio/AudioPeers.js | 21 +-- app/src/components/Room.js | 64 ++++----- app/src/components/Selectors.js | 104 +++++++++++++++ .../VideoContainers/FullScreenView.js | 26 ++-- .../components/VideoContainers/FullView.js | 122 ------------------ .../components/VideoContainers/VideoView.js | 3 +- app/src/components/VideoWindow/VideoWindow.js | 5 +- 14 files changed, 271 insertions(+), 344 deletions(-) delete mode 100644 app/src/components/PeerAudio/AudioPeer.js create mode 100644 app/src/components/Selectors.js delete mode 100644 app/src/components/VideoContainers/FullView.js diff --git a/app/package.json b/app/package.json index 3efacd1..3f0afd6 100644 --- a/app/package.json +++ b/app/package.json @@ -29,6 +29,7 @@ "redux-logger": "^3.0.6", "redux-persist": "^5.10.0", "redux-thunk": "^2.3.0", + "reselect": "^4.0.0", "resize-observer-polyfill": "^1.5.1", "riek": "^1.1.0", "socket.io-client": "^2.2.0", @@ -168,7 +169,7 @@ "no-case-declarations": 2, "no-catch-shadow": 2, "no-class-assign": 2, - "no-confusing-arrow": 2, + "no-confusing-arrow": ["error", {"allowParens": true}], "no-console": 2, "no-const-assign": 2, "no-debugger": 2, diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 07022ce..d63448d 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import { meProducersSelector } from '../Selectors'; import { withRoomContext } from '../../RoomContext'; import { withStyles } from '@material-ui/core/styles'; import PropTypes from 'prop-types'; @@ -133,21 +134,11 @@ Me.propTypes = const mapStateToProps = (state) => { - const producersArray = Object.values(state.producers); - const micProducer = - producersArray.find((producer) => producer.source === 'mic'); - const webcamProducer = - producersArray.find((producer) => producer.source === 'webcam'); - const screenProducer = - producersArray.find((producer) => producer.source === 'screen'); - return { - connected : state.room.state === 'connected', - me : state.me, - micProducer : micProducer, - webcamProducer : webcamProducer, - screenProducer : screenProducer, - activeSpeaker : state.me.name === state.room.activeSpeakerName + connected : state.room.state === 'connected', + me : state.me, + ...meProducersSelector(state), + activeSpeaker : state.me.name === state.room.activeSpeakerName }; }; diff --git a/app/src/components/Containers/Peer.js b/app/src/components/Containers/Peer.js index de77246..e89bbaa 100644 --- a/app/src/components/Containers/Peer.js +++ b/app/src/components/Containers/Peer.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { connect } from 'react-redux'; +import { makePeerConsumerSelector } from '../Selectors'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import * as appPropTypes from '../appPropTypes'; @@ -423,26 +424,21 @@ Peer.propTypes = theme : PropTypes.object.isRequired }; -const mapStateToProps = (state, { name }) => +const makeMapStateToProps = () => { - const peer = state.peers[name]; - const consumersArray = peer.consumers - .map((consumerId) => state.consumers[consumerId]); - const micConsumer = - consumersArray.find((consumer) => consumer.source === 'mic'); - const webcamConsumer = - consumersArray.find((consumer) => consumer.source === 'webcam'); - const screenConsumer = - consumersArray.find((consumer) => consumer.source === 'screen'); + const getPeerConsumers = makePeerConsumerSelector(); - return { - peer, - micConsumer, - webcamConsumer, - screenConsumer, - windowConsumer : state.room.windowConsumer, - activeSpeaker : name === state.room.activeSpeakerName + const mapStateToProps = (state, props) => + { + return { + peer : state.peers[props.name], + ...getPeerConsumers(state, props), + windowConsumer : state.room.windowConsumer, + activeSpeaker : props.name === state.room.activeSpeakerName + }; }; + + return mapStateToProps; }; const mapDispatchToProps = (dispatch) => @@ -462,6 +458,6 @@ const mapDispatchToProps = (dispatch) => }; export default withRoomContext(connect( - mapStateToProps, + makeMapStateToProps, mapDispatchToProps )(withStyles(styles, { withTheme: true })(Peer))); diff --git a/app/src/components/MeetingDrawer/MeetingDrawer.js b/app/src/components/MeetingDrawer/MeetingDrawer.js index 29aac62..7e31570 100644 --- a/app/src/components/MeetingDrawer/MeetingDrawer.js +++ b/app/src/components/MeetingDrawer/MeetingDrawer.js @@ -30,64 +30,71 @@ const styles = (theme) => width : '100%', height : '100%', backgroundColor : theme.palette.background.paper + }, + appBar : + { + display : 'flex', + flexDirection : 'row' + }, + tabsHeader : + { + flexGrow : 1 } }); -class MeetingDrawer extends React.PureComponent +const MeetingDrawer = (props) => { - handleChange = (event, value) => - { - this.props.setToolTab(tabs[value]); - }; + const { + currentToolTab, + unreadMessages, + unreadFiles, + closeDrawer, + setToolTab, + classes, + theme + } = props; - render() - { - const { - currentToolTab, - unreadMessages, - unreadFiles, - closeDrawer, - classes, - theme - } = this.props; - - return ( -
- - - - Chat - - } - /> - - File sharing - - } - /> - - - {theme.direction === 'ltr' ? : } - - - - {currentToolTab === 'chat' && } - {currentToolTab === 'files' && } - {currentToolTab === 'users' && } -
- ); - } -} + return ( +
+ + setToolTab(tabs[value])} + indicatorColor='primary' + textColor='primary' + variant='fullWidth' + > + + Chat + + } + /> + + File sharing + + } + /> + + + + {theme.direction === 'ltr' ? : } + + + {currentToolTab === 'chat' && } + {currentToolTab === 'files' && } + {currentToolTab === 'users' && } +
+ ); +}; MeetingDrawer.propTypes = { diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index b19554f..e6574b8 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import { makePeerConsumerSelector } from '../../Selectors'; import { withStyles } from '@material-ui/core/styles'; import PropTypes from 'prop-types'; import classnames from 'classnames'; @@ -269,26 +270,21 @@ ListPeer.propTypes = classes : PropTypes.object.isRequired }; -const mapStateToProps = (state, { name }) => +const makeMapStateToProps = () => { - const peer = state.peers[name]; - const consumersArray = peer.consumers - .map((consumerId) => state.consumers[consumerId]); - const micConsumer = - consumersArray.find((consumer) => consumer.source === 'mic'); - const webcamConsumer = - consumersArray.find((consumer) => consumer.source === 'webcam'); - const screenConsumer = - consumersArray.find((consumer) => consumer.source === 'screen'); + const getPeerConsumers = makePeerConsumerSelector(); - return { - peer, - micConsumer, - webcamConsumer, - screenConsumer + const mapStateToProps = (state, props) => + { + return { + peer : state.peers[props.name], + ...getPeerConsumers(state, props) + }; }; + + return mapStateToProps; }; export default withRoomContext(connect( - mapStateToProps + makeMapStateToProps )(withStyles(styles)(ListPeer))); \ No newline at end of file diff --git a/app/src/components/MeetingViews/Democratic.js b/app/src/components/MeetingViews/Democratic.js index 81a0158..a5e6772 100644 --- a/app/src/components/MeetingViews/Democratic.js +++ b/app/src/components/MeetingViews/Democratic.js @@ -1,5 +1,11 @@ import React from 'react'; import { connect } from 'react-redux'; +import { + peersSelector, + videoBoxesSelector, + spotlightsSelector, + spotlightsLengthSelector +} from '../Selectors'; import PropTypes from 'prop-types'; import debounce from 'lodash/debounce'; import { withStyles } from '@material-ui/core/styles'; @@ -159,28 +165,22 @@ class Democratic extends React.PureComponent } Democratic.propTypes = - { - advancedMode : PropTypes.bool, - peers : PropTypes.object.isRequired, - boxes : PropTypes.number, - spotlightsLength : PropTypes.number, - spotlights : PropTypes.array.isRequired, - classes : PropTypes.object.isRequired - }; +{ + advancedMode : PropTypes.bool, + peers : PropTypes.object.isRequired, + boxes : PropTypes.number, + spotlightsLength : PropTypes.number, + spotlights : PropTypes.array.isRequired, + classes : PropTypes.object.isRequired +}; const mapStateToProps = (state) => { - const spotlights = state.room.spotlights; - const spotlightsLength = spotlights ? state.room.spotlights.length : 0; - const boxes = spotlightsLength + Object.values(state.consumers) - .filter((consumer) => consumer.source === 'screen').length + Object.values(state.producers) - .filter((producer) => producer.source === 'screen').length + 1; - return { - peers : state.peers, - boxes, - spotlights, - spotlightsLength + peers : peersSelector(state), + boxes : videoBoxesSelector(state), + spotlights : spotlightsSelector(state), + spotlightsLength : spotlightsLengthSelector(state) }; }; diff --git a/app/src/components/PeerAudio/AudioPeer.js b/app/src/components/PeerAudio/AudioPeer.js deleted file mode 100644 index ad1a3b6..0000000 --- a/app/src/components/PeerAudio/AudioPeer.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import * as appPropTypes from '../appPropTypes'; -import PeerAudio from './PeerAudio'; - -const AudioPeer = ({ micConsumer }) => -{ - return ( - - ); -}; - -AudioPeer.propTypes = -{ - micConsumer : appPropTypes.Consumer, - name : PropTypes.string -}; - -const mapStateToProps = (state, { name }) => -{ - const peer = state.peers[name]; - const consumersArray = peer.consumers - .map((consumerId) => state.consumers[consumerId]); - const micConsumer = - consumersArray.find((consumer) => consumer.source === 'mic'); - - return { - micConsumer - }; -}; - -const AudioPeerContainer = connect( - mapStateToProps -)(AudioPeer); - -export default AudioPeerContainer; diff --git a/app/src/components/PeerAudio/AudioPeers.js b/app/src/components/PeerAudio/AudioPeers.js index c272b98..b9ffdcd 100644 --- a/app/src/components/PeerAudio/AudioPeers.js +++ b/app/src/components/PeerAudio/AudioPeers.js @@ -1,19 +1,24 @@ import React from 'react'; import { connect } from 'react-redux'; +import { micConsumerSelector } from '../Selectors'; import PropTypes from 'prop-types'; -import AudioPeer from './AudioPeer'; +import PeerAudio from './PeerAudio'; -const AudioPeers = ({ peers }) => +const AudioPeers = (props) => { + const { + micConsumers + } = props; + return (
{ - Object.values(peers).map((peer) => + micConsumers.map((micConsumer) => { return ( - ); }) @@ -24,12 +29,12 @@ const AudioPeers = ({ peers }) => AudioPeers.propTypes = { - peers : PropTypes.object + micConsumers : PropTypes.array }; const mapStateToProps = (state) => ({ - peers : state.peers + micConsumers : micConsumerSelector(state) }); const AudioPeersContainer = connect( diff --git a/app/src/components/Room.js b/app/src/components/Room.js index b6bd629..1f64460 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -18,6 +18,7 @@ import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; +import Avatar from '@material-ui/core/Avatar'; import Badge from '@material-ui/core/Badge'; import AccountCircle from '@material-ui/icons/AccountCircle'; import Notifications from './Notifications/Notifications'; @@ -152,7 +153,6 @@ class Room extends React.PureComponent this.state = { - drawerOpen : false, fullscreen : false }; } @@ -220,7 +220,9 @@ class Room extends React.PureComponent const { roomClient, room, - me, + myPicture, + loggedIn, + loginEnabled, setSettingsOpen, toolAreaOpen, toggleToolArea, @@ -338,16 +340,20 @@ class Room extends React.PureComponent > - { me.loginEnabled ? + { loginEnabled ? { - me.loggedIn ? roomClient.logout() : roomClient.login(); + loggedIn ? roomClient.logout() : roomClient.login(); }} > - + { myPicture ? + + : + + } :null } @@ -373,20 +379,6 @@ class Room extends React.PureComponent - { /* - -
- -
-
- */ } - @@ -400,10 +392,10 @@ Room.propTypes = { roomClient : PropTypes.object.isRequired, room : appPropTypes.Room.isRequired, - me : appPropTypes.Me.isRequired, - // amActiveSpeaker : PropTypes.bool.isRequired, + myPicture : PropTypes.string, + loggedIn : PropTypes.bool.isRequired, + loginEnabled : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, - screenProducer : appPropTypes.Producer, setToolbarsVisible : PropTypes.func.isRequired, setSettingsOpen : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, @@ -413,25 +405,18 @@ Room.propTypes = }; const mapStateToProps = (state) => -{ - const producersArray = Object.values(state.producers); - const screenProducer = - producersArray.find((producer) => producer.source === 'screen'); - - return { - room : state.room, - me : state.me, - screenProducer : screenProducer, - toolAreaOpen : state.toolarea.toolAreaOpen, - unread : state.toolarea.unreadMessages + + ({ + room : state.room, + loggedIn : state.me.loggedIn, + loginEnabled : state.me.loginEnabled, + myPicture : state.me.picture, + toolAreaOpen : state.toolarea.toolAreaOpen, + unread : state.toolarea.unreadMessages + state.toolarea.unreadFiles - // amActiveSpeaker : state.me.name === state.room.activeSpeakerName, - }; -}; + }); const mapDispatchToProps = (dispatch) => -{ - return { + ({ setToolbarsVisible : (visible) => { dispatch(stateActions.setToolbarsVisible(visible)); @@ -444,8 +429,7 @@ const mapDispatchToProps = (dispatch) => { dispatch(stateActions.toggleToolArea()); } - }; -}; + }); export default withRoomContext(connect( mapStateToProps, diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js new file mode 100644 index 0000000..836d158 --- /dev/null +++ b/app/src/components/Selectors.js @@ -0,0 +1,104 @@ +import { createSelector } from 'reselect'; + +const producersSelect = (state) => state.producers; +const consumersSelect = (state) => state.consumers; + +export const spotlightsSelector = (state) => state.room.spotlights; + +export const peersSelector = (state) => state.peers; + +export const micProducersSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).filter((producer) => producer.source === 'mic') +); + +export const webcamProducersSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).filter((producer) => producer.source === 'webcam') +); + +export const screenProducersSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).filter((producer) => producer.source === 'screen') +); + +export const micProducerSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).find((producer) => producer.source === 'mic') +); + +export const webcamProducerSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).find((producer) => producer.source === 'webcam') +); + +export const screenProducerSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).find((producer) => producer.source === 'screen') +); + +export const micConsumerSelector = createSelector( + consumersSelect, + (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'mic') +); + +export const webcamConsumerSelector = createSelector( + consumersSelect, + (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'webcam') +); + +export const screenConsumerSelector = createSelector( + consumersSelect, + (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen') +); + +export const spotlightsLengthSelector = createSelector( + spotlightsSelector, + (spotlights) => (spotlights ? spotlights.length : 0) +); + +export const videoBoxesSelector = createSelector( + spotlightsLengthSelector, + screenProducersSelector, + screenConsumerSelector, + (spotlightsLength, screenProducers, screenConsumers) => + spotlightsLength + 1 + screenProducers.length + screenConsumers.length +); + +export const meProducersSelector = createSelector( + micProducerSelector, + webcamProducerSelector, + screenProducerSelector, + (micProducer, webcamProducer, screenProducer) => + { + return { + micProducer, + webcamProducer, + screenProducer + }; + } +); + +const getPeerConsumers = (state, props) => state.peers[props.name].consumers; +const getAllConsumers = (state) => state.consumers; + +export const makePeerConsumerSelector = () => +{ + return createSelector( + getPeerConsumers, + getAllConsumers, + (consumers, allConsumers) => + { + const consumersArray = consumers + .map((consumerId) => allConsumers[consumerId]); + const micConsumer = + consumersArray.find((consumer) => consumer.source === 'mic'); + const webcamConsumer = + consumersArray.find((consumer) => consumer.source === 'webcam'); + const screenConsumer = + consumersArray.find((consumer) => consumer.source === 'screen'); + + return { micConsumer, webcamConsumer, screenConsumer }; + } + ); +}; \ No newline at end of file diff --git a/app/src/components/VideoContainers/FullScreenView.js b/app/src/components/VideoContainers/FullScreenView.js index 6fcfea2..0e53a4a 100644 --- a/app/src/components/VideoContainers/FullScreenView.js +++ b/app/src/components/VideoContainers/FullScreenView.js @@ -5,8 +5,8 @@ import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import * as appPropTypes from '../appPropTypes'; import * as stateActions from '../../actions/stateActions'; -import FullView from './FullView'; import FullScreenExitIcon from '@material-ui/icons/FullscreenExit'; +import VideoView from './VideoView'; const styles = () => ({ @@ -41,7 +41,12 @@ const styles = () => transitionProperty : 'opacity, background-color', transitionDuration : '0.15s', width : '5vmin', - height : '5vmin' + height : '5vmin', + opacity : 0, + '&.visible' : + { + opacity : 1 + } }, icon : { @@ -106,7 +111,7 @@ const FullScreenView = (props) =>
@@ -119,8 +124,9 @@ const FullScreenView = (props) =>
- -{ - return { + ({ consumer : state.consumers[state.room.fullScreenConsumer], toolbarsVisible : state.room.toolbarsVisible - }; -}; + }); const mapDispatchToProps = (dispatch) => -{ - return { + ({ toggleConsumerFullscreen : (consumer) => { if (consumer) dispatch(stateActions.toggleConsumerFullscreen(consumer.id)); } - }; -}; + }); export default connect( mapStateToProps, diff --git a/app/src/components/VideoContainers/FullView.js b/app/src/components/VideoContainers/FullView.js deleted file mode 100644 index 4e53a89..0000000 --- a/app/src/components/VideoContainers/FullView.js +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import { withStyles } from '@material-ui/core/styles'; - -const styles = () => - ({ - root : - { - position : 'relative', - flex : '100 100 auto', - height : '100%', - width : '100%', - display : 'flex', - flexDirection : 'column', - overflow : 'hidden' - }, - video : - { - flex : '100 100 auto', - height : '100%', - width : '100%', - objectFit : 'contain', - userSelect : 'none', - transitionProperty : 'opacity', - transitionDuration : '.15s', - backgroundColor : 'rgba(0, 0, 0, 1)', - '&.hidden' : - { - opacity : 0, - transitionDuration : '0s' - }, - '&.loading' : - { - filter : 'blur(5px)' - } - } - }); - -class FullView extends React.PureComponent -{ - constructor(props) - { - super(props); - - // Latest received video track. - // @type {MediaStreamTrack} - this._videoTrack = null; - - this.video = React.createRef(); - } - - render() - { - const { - videoVisible, - videoProfile, - classes - } = this.props; - - return ( -
-
- ); - } - - componentDidMount() - { - const { videoTrack } = this.props; - - this._setTracks(videoTrack); - } - - componentDidUpdate() - { - const { videoTrack } = this.props; - - this._setTracks(videoTrack); - } - - _setTracks(videoTrack) - { - if (this._videoTrack === videoTrack) - return; - - this._videoTrack = videoTrack; - - const video = this.video.current; - - if (videoTrack) - { - const stream = new MediaStream(); - - stream.addTrack(videoTrack); - video.srcObject = stream; - } - else - { - video.srcObject = null; - } - } -} - -FullView.propTypes = -{ - videoTrack : PropTypes.any, - videoVisible : PropTypes.bool, - videoProfile : PropTypes.string, - classes : PropTypes.object.isRequired -}; - -export default withStyles(styles)(FullView); diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 6756e93..a8608c3 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -42,7 +42,8 @@ const styles = () => }, '&.contain' : { - objectFit : 'contain' + objectFit : 'contain', + backgroundColor : 'rgba(0, 0, 0, 1)' } }, info : diff --git a/app/src/components/VideoWindow/VideoWindow.js b/app/src/components/VideoWindow/VideoWindow.js index f7bd796..61fb90c 100644 --- a/app/src/components/VideoWindow/VideoWindow.js +++ b/app/src/components/VideoWindow/VideoWindow.js @@ -4,7 +4,7 @@ import NewWindow from './NewWindow'; import PropTypes from 'prop-types'; import * as appPropTypes from '../appPropTypes'; import * as stateActions from '../../actions/stateActions'; -import FullView from '../VideoContainers/FullView'; +import VideoView from '../VideoContainers/VideoView'; const VideoWindow = (props) => { @@ -30,8 +30,9 @@ const VideoWindow = (props) => return ( -