Massive change. Changed to react-scripts (webpack) for building. Changed to material-ui where applicable. Changed to CSS-in-JS.

This commit is contained in:
Håvar Aambø Fosstveit
2019-03-29 14:12:13 +01:00
parent 36944a9ea7
commit 0449d6ff20
185 changed files with 4564 additions and 6442 deletions
@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import MessageList from './MessageList';
import ChatInput from './ChatInput';
const styles = () =>
({
root :
{
display : 'flex',
flexDirection : 'column',
width : '100%',
height : '100%'
}
});
const Chat = (props) =>
{
const {
classes
} = props;
return (
<Paper className={classes.root}>
<MessageList />
<ChatInput />
</Paper>
);
};
Chat.propTypes =
{
classes : PropTypes.object.isRequired
};
export default withStyles(styles)(Chat);
@@ -0,0 +1,131 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext';
import Paper from '@material-ui/core/Paper';
import InputBase from '@material-ui/core/InputBase';
import IconButton from '@material-ui/core/IconButton';
import SendIcon from '@material-ui/icons/Send';
const styles = (theme) =>
({
root :
{
padding : theme.spacing.unit,
display : 'flex',
alignItems : 'center',
borderRadius : 0
},
input :
{
marginLeft : 8,
flex : 1
},
iconButton :
{
padding : 10
}
});
class ChatInput extends Component
{
constructor(props)
{
super(props);
this.state =
{
message : ''
};
}
createNewMessage = (text, sender, name, picture) =>
({
type : 'message',
text,
time : Date.now(),
name,
sender,
picture
});
handleChange = (e) =>
{
this.setState({ message: e.target.value });
}
render()
{
const {
roomClient,
displayName,
picture,
classes
} = this.props;
return (
<Paper className={classes.root}>
<InputBase
className={classes.input}
placeholder='Enter chat message...'
value={this.state.message || ''}
onChange={this.handleChange}
onKeyPress={(ev) =>
{
if (ev.key === 'Enter')
{
ev.preventDefault();
if (this.state.message && this.state.message !== '')
{
const message = this.createNewMessage(this.state.message, 'response', displayName, picture);
roomClient.sendChatMessage(message);
this.setState({ message: '' });
}
}
}}
autoFocus
/>
<IconButton
color='primary'
className={classes.iconButton}
aria-label='Send'
onClick={() =>
{
if (this.state.message && this.state.message !== '')
{
const message = this.createNewMessage(this.state.message, 'response', displayName, picture);
roomClient.sendChatMessage(message);
this.setState({ message: '' });
}
}}
>
<SendIcon />
</IconButton>
</Paper>
);
}
}
ChatInput.propTypes =
{
roomClient : PropTypes.object.isRequired,
displayName : PropTypes.string,
picture : PropTypes.string,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
displayName : state.me.displayName,
picture : state.me.picture
});
export default withRoomContext(
connect(mapStateToProps)(withStyles(styles)(ChatInput))
);
@@ -0,0 +1,99 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles';
import marked from 'marked';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
const linkRenderer = new marked.Renderer();
linkRenderer.link = (href, title, text) =>
{
title = title ? title : href;
text = text ? text : href;
return (`<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`);
};
const styles = (theme) =>
({
root :
{
display : 'flex',
marginBottom : theme.spacing.unit,
padding : theme.spacing.unit
},
selfMessage :
{
marginLeft : 'auto',
},
remoteMessage :
{
marginRight : 'auto'
},
text :
{
'& p' :
{
margin : 0
}
},
content :
{
marginLeft : theme.spacing.unit
},
avatar :
{
borderRadius : '50%',
height : '2rem',
alignSelf : 'center'
},
});
const Message = (props) =>
{
const {
self,
picture,
text,
time,
name,
classes
} = props;
return (
<Paper
className={classnames(
classes.root,
self ? classes.selfMessage : classes.remoteMessage
)}
>
<img alt='Avatar' className={classes.avatar} src={picture} />
<div className={classes.content}>
<Typography
className={classes.text}
variant='subtitle1'
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html : marked.parse(
text,
{ sanitize: true, renderer: linkRenderer }
) }}
/>
<Typography variant='caption'>{self ? 'Me' : name} - {time}</Typography>
</div>
</Paper>
);
};
Message.propTypes =
{
self : PropTypes.bool,
picture : PropTypes.string,
text : PropTypes.string,
time : PropTypes.string,
name : PropTypes.string,
classes : PropTypes.object.isRequired
};
export default withStyles(styles)(Message);
@@ -0,0 +1,92 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Message from './Message';
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
const styles = (theme) =>
({
root :
{
height : '100%',
display : 'flex',
flexDirection : 'column',
alignItems : 'center',
overflowY : 'auto',
padding : theme.spacing.unit
}
});
class MessageList extends React.Component
{
componentDidMount()
{
this.node.scrollTop = this.node.scrollHeight;
}
getSnapshotBeforeUpdate()
{
return this.node.scrollTop
+ this.node.offsetHeight === this.node.scrollHeight;
}
componentDidUpdate(prevProps, prevState, shouldScroll)
{
if (shouldScroll)
{
this.node.scrollTop = this.node.scrollHeight;
}
}
getTimeString(time)
{
return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`;
}
render()
{
const {
chatmessages,
classes
} = this.props;
return (
<div className={classes.root} ref={(node) => { this.node = node; }}>
{
chatmessages.map((message, index) =>
{
const messageTime = new Date(message.time);
const picture = (message.sender === 'response' ?
message.picture : this.props.myPicture) || EmptyAvatar;
return (
<Message
key={index}
self={message.sender === 'client'}
picture={picture}
text={message.text}
time={this.getTimeString(messageTime)}
name={message.name}
/>
);
})
}
</div>
);
}
}
MessageList.propTypes =
{
chatmessages : PropTypes.array,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
chatmessages : state.chatmessages
});
export default connect(mapStateToProps, null)(withStyles(styles)(MessageList));
@@ -0,0 +1,164 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles';
import magnet from 'magnet-uri';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
const styles = (theme) =>
({
root :
{
display : 'flex',
alignItems : 'center',
width : '100%',
padding : theme.spacing.unit,
boxShadow : '0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
'&:not(:last-child)' :
{
marginBottom : theme.spacing.unit
}
},
avatar :
{
borderRadius : '50%',
height : '2rem'
},
text :
{
margin : 0,
padding : theme.spacing.unit
},
fileContent :
{
display : 'flex',
alignItems : 'center'
},
fileInfo :
{
display : 'flex',
alignItems : 'center',
padding : theme.spacing.unit
},
button :
{
marginRight : 'auto'
}
});
class File extends Component
{
render()
{
const {
roomClient,
torrentSupport,
file,
classes
} = this.props;
return (
<div className={classes.root}>
<img alt='Peer avatar' className={classes.avatar} src={file.picture || EmptyAvatar} />
<div className={classes.fileContent}>
{ file.files ?
<Fragment>
<Typography className={classes.text}>
File finished downloading
</Typography>
{ file.files.map((sharedFile, i) => (
<div className={classes.fileInfo} key={i}>
<Typography className={classes.text}>
{sharedFile.name}
</Typography>
<Button
variant='contained'
component='span'
className={classes.button}
onClick={() =>
{
roomClient.saveFile(sharedFile);
}}
>
Save
</Button>
</div>
))}
</Fragment>
:null
}
<Typography className={classes.text}>
{ file.me ?
'You shared a file'
:
`${file.displayName} shared a file`
}
</Typography>
{ !file.active && !file.files ?
<div className={classes.fileInfo}>
<Typography className={classes.text}>
{magnet.decode(file.magnetUri).dn}
</Typography>
{ torrentSupport ?
<Button
variant='contained'
component='span'
className={classes.button}
onClick={() =>
{
roomClient.handleDownload(file.magnetUri);
}}
>
Download
</Button>
:
<Typography className={classes.text}>
Your browser does not support downloading files using WebTorrent.
</Typography>
}
</div>
:null
}
{ file.timeout ?
<Typography className={classes.text}>
If this process takes a long time, there might not be anyone seeding
this torrent. Try asking someone to reupload the file that you want.
</Typography>
:null
}
{ file.active ?
<progress value={file.progress} />
:null
}
</div>
</div>
);
}
}
File.propTypes = {
roomClient : PropTypes.object.isRequired,
torrentSupport : PropTypes.bool.isRequired,
file : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state, { magnetUri }) =>
{
return {
file : state.files[magnetUri],
torrentSupport : state.room.torrentSupport
};
};
export default withRoomContext(connect(
mapStateToProps
)(withStyles(styles)(File)));
@@ -0,0 +1,71 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import File from './File';
const styles = (theme) =>
({
root :
{
height : '100%',
display : 'flex',
flexDirection : 'column',
alignItems : 'center',
overflowY : 'auto',
padding : theme.spacing.unit
}
});
class FileList extends Component
{
componentDidMount()
{
this.node.scrollTop = this.node.scrollHeight;
}
getSnapshotBeforeUpdate()
{
return this.node.scrollTop
+ this.node.offsetHeight === this.node.scrollHeight;
}
componentDidUpdate(prevProps, prevState, shouldScroll)
{
if (shouldScroll)
{
this.node.scrollTop = this.node.scrollHeight;
}
}
render()
{
const {
files,
classes
} = this.props;
return (
<div className={classes.root} ref={(node) => { this.node = node; }}>
{ Object.keys(files).map((magnetUri) =>
<File key={magnetUri} magnetUri={magnetUri} />
)}
</div>
);
}
}
FileList.propTypes =
{
files : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
return {
files : state.files
};
};
export default connect(mapStateToProps)(withStyles(styles)(FileList));
@@ -0,0 +1,99 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext';
import FileList from './FileList';
import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
display : 'flex',
flexDirection : 'column',
width : '100%',
height : '100%'
},
input :
{
display : 'none'
},
button :
{
margin : theme.spacing.unit
}
});
class FileSharing extends Component
{
constructor(props)
{
super(props);
this._fileInput = React.createRef();
}
handleFileChange = async (event) =>
{
if (event.target.files.length > 0)
{
this.props.roomClient.shareFiles(event.target.files);
}
};
render()
{
const {
torrentSupport,
classes
} = this.props;
const buttonDescription = torrentSupport ?
'Share file' : 'File sharing not supported';
return (
<Paper className={classes.root}>
<input
ref={this._fileInput}
className={classes.input}
type='file'
onChange={this.handleFileChange}
id='share-files-button'
/>
<label htmlFor='share-files-button'>
<Button
variant='contained'
component='span'
className={classes.button}
disabled={!torrentSupport}
>
{buttonDescription}
</Button>
</label>
<FileList />
</Paper>
);
}
}
FileSharing.propTypes = {
roomClient : PropTypes.any.isRequired,
torrentSupport : PropTypes.bool.isRequired,
tabOpen : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
return {
torrentSupport : state.room.torrentSupport,
tabOpen : state.toolarea.currentToolTab === 'files'
};
};
export default withRoomContext(connect(
mapStateToProps
)(withStyles(styles)(FileSharing)));
@@ -0,0 +1,108 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import * as stateActions from '../../actions/stateActions';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Badge from '@material-ui/core/Badge';
import Chat from './Chat/Chat';
import FileSharing from './FileSharing/FileSharing';
import ParticipantList from './ParticipantList/ParticipantList';
const tabs =
[
'chat',
'files',
'users'
];
const styles = (theme) =>
({
root :
{
display : 'flex',
flexDirection : 'column',
width : '100%',
height : '100%',
backgroundColor : theme.palette.background.paper
}
});
class MeetingDrawer extends React.Component
{
handleChange = (event, value) =>
{
this.props.setToolTab(tabs[value]);
};
render()
{
const {
currentToolTab,
unreadMessages,
unreadFiles,
classes
} = this.props;
return (
<div className={classes.root}>
<AppBar position='static' color='default'>
<Tabs
value={tabs.indexOf(currentToolTab)}
onChange={this.handleChange}
indicatorColor='primary'
textColor='primary'
variant='fullWidth'
>
<Tab
label=
{
<Badge color='secondary' badgeContent={unreadMessages}>
Chat
</Badge>
}
/>
<Tab
label=
{
<Badge color='secondary' badgeContent={unreadFiles}>
File sharing
</Badge>
}
/>
<Tab label='Participants' />
</Tabs>
</AppBar>
{currentToolTab === 'chat' && <Chat />}
{currentToolTab === 'files' && <FileSharing />}
{currentToolTab === 'users' && <ParticipantList />}
</div>
);
}
}
MeetingDrawer.propTypes =
{
currentToolTab : PropTypes.string.isRequired,
setToolTab : PropTypes.func.isRequired,
unreadMessages : PropTypes.number.isRequired,
unreadFiles : PropTypes.number.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
currentToolTab : state.toolarea.currentToolTab,
unreadMessages : state.toolarea.unreadMessages,
unreadFiles : state.toolarea.unreadFiles
});
const mapDispatchToProps = {
setToolTab : stateActions.setToolTab
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(MeetingDrawer));
@@ -0,0 +1,115 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import * as appPropTypes from '../../appPropTypes';
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import HandIcon from '../../../images/icon-hand-white.svg';
const styles = () =>
({
root :
{
padding : '0.5rem',
width : '100%',
overflow : 'hidden',
cursor : 'auto',
display : 'flex'
},
listPeer :
{
display : 'flex'
},
avatar :
{
borderRadius : '50%',
height : '2rem'
},
peerInfo :
{
fontSize : '1rem',
border : 'none',
display : 'flex',
paddingLeft : '0.5rem',
flexGrow : 1,
alignItems : 'center'
},
indicators :
{
left : 0,
top : 0,
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'center',
transition : 'opacity 0.3s'
},
icon :
{
flex : '0 0 auto',
margin : '0.3rem',
borderRadius : 2,
backgroundPosition : 'center',
backgroundSize : '75%',
backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 0.5)',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
},
'&.raise-hand' :
{
backgroundImage : `url(${HandIcon})`,
opacity : 1
}
},
});
const ListMe = (props) =>
{
const {
me,
classes
} = props;
const picture = me.picture || EmptyAvatar;
return (
<li className={classes.root}>
<div className={classes.listPeer}>
<img alt='My avatar' className={classes.avatar} src={picture} />
<div className={classes.peerInfo}>
{me.displayName}
</div>
<div className={classes.indicators}>
{ me.raisedHand ?
<div className={classnames(classes.icon, 'raise-hand')} />
:null
}
</div>
</div>
</li>
);
};
ListMe.propTypes =
{
me : appPropTypes.Me.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
me : state.me
});
export default connect(
mapStateToProps
)(withStyles(styles)(ListMe));
@@ -0,0 +1,294 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext';
import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff';
import ScreenIcon from '@material-ui/icons/ScreenShare';
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import HandIcon from '../../../images/icon-hand-white.svg';
const styles = () =>
({
root :
{
padding : '0.5rem',
width : '100%',
overflow : 'hidden',
cursor : 'auto',
display : 'flex'
},
listPeer :
{
display : 'flex'
},
avatar :
{
borderRadius : '50%',
height : '2rem'
},
peerInfo :
{
fontSize : '1rem',
border : 'none',
display : 'flex',
paddingLeft : '0.5rem',
flexGrow : 1,
alignItems : 'center'
},
indicators :
{
left : 0,
top : 0,
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'center',
transition : 'opacity 0.3s'
},
icon :
{
flex : '0 0 auto',
margin : '0.3rem',
borderRadius : 2,
backgroundPosition : 'center',
backgroundSize : '75%',
backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 0.5)',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
},
'&.on' :
{
opacity : 1
},
'&.off' :
{
opacity : 0.2
},
'&.raise-hand' :
{
backgroundImage : `url(${HandIcon})`
}
},
volumeContainer :
{
float : 'right',
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
width : '1vmin',
position : 'relative',
backgroundSize : '75%'
},
bar :
{
flex : '0 0 auto',
margin : '0.3rem',
backgroundSize : '75%',
backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 1)',
cursor : 'pointer',
transitionProperty : 'opacity, background-color',
width : 3,
borderRadius : 6,
transitionDuration : '0.25s',
position : 'absolute',
bottom : 0,
'&.level0' : { height: 0 },
'&.level1' : { height: '0.2vh' },
'&.level2' : { height: '0.4vh' },
'&.level3' : { height: '0.6vh' },
'&.level4' : { height: '0.8vh' },
'&.level5' : { height: '1.0vh' },
'&.level6' : { height: '1.2vh' },
'&.level7' : { height: '1.4vh' },
'&.level8' : { height: '1.6vh' },
'&.level9' : { height: '1.8vh' },
'&.level10' : { height: '2.0vh' }
},
controls :
{
float : 'right',
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'center'
},
button :
{
flex : '0 0 auto',
margin : '0.3rem',
borderRadius : 2,
backgroundColor : 'rgba(0, 0, 0, 0.5)',
cursor : 'pointer',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
},
'&.unsupported' :
{
pointerEvents : 'none'
},
'&.disabled' :
{
pointerEvents : 'none',
backgroundColor : 'var(--media-control-botton-disabled)'
},
'&.on' :
{
backgroundColor : 'var(--media-control-botton-on)'
},
'&.off' :
{
backgroundColor : 'var(--media-control-botton-off)'
}
}
});
const ListPeer = (props) =>
{
const {
roomClient,
peer,
micConsumer,
screenConsumer,
classes
} = props;
const micEnabled = (
Boolean(micConsumer) &&
!micConsumer.locallyPaused &&
!micConsumer.remotelyPaused
);
const screenVisible = (
Boolean(screenConsumer) &&
!screenConsumer.locallyPaused &&
!screenConsumer.remotelyPaused
);
const picture = peer.picture || EmptyAvatar;
return (
<div className={classes.root}>
<img alt='Peer avatar' className={classes.avatar} src={picture} />
<div className={classes.peerInfo}>
{peer.displayName}
</div>
<div className={classes.indicators}>
{ peer.raiseHandState ?
<div className={
classnames(
classes.icon, 'raise-hand', {
on : peer.raiseHandState,
off : !peer.raiseHandState
}
)
}
/>
:null
}
</div>
<div className={classes.volumeContainer}>
<div className={classnames(classes.bar, `level${micEnabled && micConsumer ? micConsumer.volume:0}`)} />
</div>
<div className={classes.controls}>
{ screenConsumer ?
<div
className={classnames(classes.button, 'screen', {
on : screenVisible,
off : !screenVisible,
disabled : peer.peerScreenInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
screenVisible ?
roomClient.modifyPeerConsumer(peer.name, 'screen', true) :
roomClient.modifyPeerConsumer(peer.name, 'screen', false);
}}
>
{ screenVisible ?
<ScreenIcon />
:
<ScreenOffIcon />
}
</div>
:null
}
<div
className={classnames(classes.button, 'mic', {
on : micEnabled,
off : !micEnabled,
disabled : peer.peerAudioInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
micEnabled ?
roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
roomClient.modifyPeerConsumer(peer.name, 'mic', false);
}}
>
{ micEnabled ?
<MicIcon />
:
<MicOffIcon />
}
</div>
</div>
</div>
);
};
ListPeer.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
peer : appPropTypes.Peer.isRequired,
micConsumer : appPropTypes.Consumer,
webcamConsumer : appPropTypes.Consumer,
screenConsumer : appPropTypes.Consumer,
classes : PropTypes.object.isRequired
};
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');
const webcamConsumer =
consumersArray.find((consumer) => consumer.source === 'webcam');
const screenConsumer =
consumersArray.find((consumer) => consumer.source === 'screen');
return {
peer,
micConsumer,
webcamConsumer,
screenConsumer
};
};
export default withRoomContext(connect(
mapStateToProps
)(withStyles(styles)(ListPeer)));
@@ -0,0 +1,154 @@
import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { withStyles } from '@material-ui/core/styles';
import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext';
import PropTypes from 'prop-types';
import ListPeer from './ListPeer';
import ListMe from './ListMe';
const styles = (theme) =>
({
root :
{
width : '100%',
overflowY : 'auto',
padding : 6
},
list :
{
listStyleType : 'none',
padding : theme.spacing.unit,
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
backgroundColor : 'rgba(255, 255, 255, 1)'
},
listheader :
{
padding : '0.5rem',
fontWeight : 'bolder'
},
listItem :
{
padding : '0.5rem',
width : '100%',
overflow : 'hidden',
cursor : 'pointer',
'&.selected' :
{
backgroundColor: 'rgba(55, 126, 255, 1)'
},
'&:not(:last-child)' :
{
borderBottom : '1px solid #CBCBCB'
}
}
});
class ParticipantList extends React.Component
{
componentDidMount()
{
this.node.scrollTop = this.node.scrollHeight;
}
getSnapshotBeforeUpdate()
{
return this.node.scrollTop
+ this.node.offsetHeight === this.node.scrollHeight;
}
componentDidUpdate(prevProps, prevState, shouldScroll)
{
if (shouldScroll)
{
this.node.scrollTop = this.node.scrollHeight;
}
}
render()
{
const {
roomClient,
advancedMode,
peers,
selectedPeerName,
spotlights,
classes
} = this.props;
return (
<div className={classes.root} ref={(node) => { this.node = node; }}>
<ul className={classes.list}>
<li className={classes.listheader}>Me:</li>
<ListMe />
</ul>
<br />
<ul className={classes.list}>
<li className={classes.listheader}>Participants in Spotlight:</li>
{peers.filter((peer) =>
{
return (spotlights.find((spotlight) =>
{ return (spotlight === peer.name); }));
}).map((peer) => (
<li
key={peer.name}
className={classNames(classes.listItem, {
selected : peer.name === selectedPeerName
})}
onClick={() => roomClient.setSelectedPeer(peer.name)}
>
<ListPeer name={peer.name} advancedMode={advancedMode} />
</li>
))}
</ul>
<br />
<ul className={classes.list}>
<li className={classes.listheader}>Passive Participants:</li>
{peers.filter((peer) =>
{
return !(spotlights.find((spotlight) =>
{ return (spotlight === peer.name); }));
}).map((peer) => (
<li
key={peer.name}
className={classNames(classes.listItem, {
selected : peer.name === selectedPeerName
})}
onClick={() => roomClient.setSelectedPeer(peer.name)}
>
<ListPeer name={peer.name} advancedMode={advancedMode} />
</li>
))}
</ul>
</div>
);
}
}
ParticipantList.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired,
selectedPeerName : PropTypes.string,
spotlights : PropTypes.array.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
const peersArray = Object.values(state.peers);
return {
peers : peersArray,
selectedPeerName : state.room.selectedPeerName,
spotlights : state.room.spotlights
};
};
const ParticipantListContainer = withRoomContext(connect(
mapStateToProps
)(withStyles(styles)(ParticipantList)));
export default ParticipantListContainer;