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