Merge branch 'feat-user-roles' into develop

This commit is contained in:
Håvar Aambø Fosstveit
2020-03-26 21:31:57 +01:00
31 changed files with 1244 additions and 669 deletions
+5 -98
View File
@@ -1,21 +1,15 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext';
import isElectron from 'is-electron';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import randomString from 'random-string';
import Dialog from '@material-ui/core/Dialog';
import DialogContentText from '@material-ui/core/DialogContentText';
import IconButton from '@material-ui/core/IconButton';
import AccountCircle from '@material-ui/icons/AccountCircle';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Tooltip from '@material-ui/core/Tooltip';
import CookieConsent from 'react-cookie-consent';
import MuiDialogTitle from '@material-ui/core/DialogTitle';
import MuiDialogContent from '@material-ui/core/DialogContent';
@@ -88,63 +82,12 @@ const styles = (theme) =>
const DialogTitle = withStyles(styles)((props) =>
{
const [ open, setOpen ] = useState(false);
const intl = useIntl();
useEffect(() =>
{
const openTimer = setTimeout(() => setOpen(true), 1000);
const closeTimer = setTimeout(() => setOpen(false), 4000);
return () =>
{
clearTimeout(openTimer);
clearTimeout(closeTimer);
};
}, []);
const { children, classes, myPicture, onLogin, ...other } = props;
const handleTooltipClose = () =>
{
setOpen(false);
};
const handleTooltipOpen = () =>
{
setOpen(true);
};
const { children, classes, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography variant='h5'>{children}</Typography>
{ window.config && window.config.loginEnabled &&
<Tooltip
onClose={handleTooltipClose}
onOpen={handleTooltipOpen}
open={open}
title={intl.formatMessage({
id : 'tooltip.login',
defaultMessage : 'Click to log in'
})}
placement='left'
>
<IconButton
aria-label='Account'
className={classes.loginButton}
color='inherit'
onClick={onLogin}
>
{ myPicture ?
<Avatar src={myPicture} className={classes.largeAvatar} />
:
<AccountCircle className={classes.largeIcon} />
}
</IconButton>
</Tooltip>
}
</MuiDialogTitle>
);
});
@@ -165,9 +108,6 @@ const DialogActions = withStyles((theme) => ({
}))(MuiDialogActions);
const ChooseRoom = ({
roomClient,
loggedIn,
myPicture,
classes
}) =>
{
@@ -184,13 +124,7 @@ const ChooseRoom = ({
paper : classes.dialogPaper
}}
>
<DialogTitle
myPicture={myPicture}
onLogin={() =>
{
loggedIn ? roomClient.logout() : roomClient.login();
}}
>
<DialogTitle>
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
<hr />
</DialogTitle>
@@ -258,34 +192,7 @@ const ChooseRoom = ({
ChooseRoom.propTypes =
{
roomClient : PropTypes.any.isRequired,
loginEnabled : PropTypes.bool.isRequired,
loggedIn : PropTypes.bool.isRequired,
myPicture : PropTypes.string,
classes : PropTypes.object.isRequired
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
return {
loginEnabled : state.me.loginEnabled,
loggedIn : state.me.loggedIn,
myPicture : state.me.picture
};
};
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.me.loginEnabled === next.me.loginEnabled &&
prev.me.loggedIn === next.me.loggedIn &&
prev.me.picture === next.me.picture
);
}
}
)(withStyles(styles)(ChooseRoom)));
export default withStyles(styles)(ChooseRoom);
@@ -0,0 +1,118 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import { useIntl, FormattedMessage } from 'react-intl';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
padding : theme.spacing(1),
width : '100%',
overflow : 'hidden',
cursor : 'auto',
display : 'flex'
},
actionButtons :
{
display : 'flex'
},
divider :
{
marginLeft : theme.spacing(2)
}
});
const ListModerator = (props) =>
{
const intl = useIntl();
const {
roomClient,
room,
classes
} = props;
return (
<div className={classes.root}>
<Button
aria-label={intl.formatMessage({
id : 'room.muteAll',
defaultMessage : 'Mute all'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.muteAllInProgress}
onClick={() => roomClient.muteAllPeers()}
>
<FormattedMessage
id='room.muteAll'
defaultMessage='Mute all'
/>
</Button>
<div className={classes.divider} />
<Button
aria-label={intl.formatMessage({
id : 'room.stopAllVideo',
defaultMessage : 'Stop all video'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.stopAllVideoInProgress}
onClick={() => roomClient.stopAllPeerVideo()}
>
<FormattedMessage
id='room.stopAllVideo'
defaultMessage='Stop all video'
/>
</Button>
<div className={classes.divider} />
<Button
aria-label={intl.formatMessage({
id : 'room.closeMeeting',
defaultMessage : 'Close meeting'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.closeMeetingInProgress}
onClick={() => roomClient.closeMeeting()}
>
<FormattedMessage
id='room.closeMeeting'
defaultMessage='Close meeting'
/>
</Button>
</div>
);
};
ListModerator.propTypes =
{
roomClient : PropTypes.any.isRequired,
room : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
room : state.room
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room
);
}
}
)(withStyles(styles)(ListModerator)));
@@ -12,6 +12,7 @@ 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 ExitIcon from '@material-ui/icons/ExitToApp';
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import HandIcon from '../../../images/icon-hand-white.svg';
@@ -91,40 +92,6 @@ const styles = (theme) =>
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)'
}
}
});
@@ -134,6 +101,7 @@ const ListPeer = (props) =>
const {
roomClient,
isModerator,
peer,
micConsumer,
screenConsumer,
@@ -185,9 +153,8 @@ const ListPeer = (props) =>
})}
color={ screenVisible ? 'primary' : 'secondary'}
disabled={ peer.peerScreenInProgress }
onClick={(e) =>
onClick={() =>
{
e.stopPropagation();
screenVisible ?
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
@@ -207,9 +174,8 @@ const ListPeer = (props) =>
})}
color={ micEnabled ? 'primary' : 'secondary'}
disabled={ peer.peerAudioInProgress }
onClick={(e) =>
onClick={() =>
{
e.stopPropagation();
micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
@@ -221,6 +187,21 @@ const ListPeer = (props) =>
<MicOffIcon />
}
</IconButton>
{ isModerator &&
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.kickParticipant',
defaultMessage : 'Kick out participant'
})}
disabled={ peer.peerKickInProgress }
onClick={() =>
{
roomClient.kickPeer(peer.id);
}}
>
<ExitIcon />
</IconButton>
}
</div>
</div>
);
@@ -230,6 +211,7 @@ ListPeer.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
isModerator : PropTypes.bool,
peer : appPropTypes.Peer.isRequired,
micConsumer : appPropTypes.Consumer,
webcamConsumer : appPropTypes.Consumer,
@@ -11,7 +11,9 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import ListPeer from './ListPeer';
import ListMe from './ListMe';
import ListModerator from './ListModerator';
import Volume from '../../Containers/Volume';
import * as userRoles from '../../../reducers/userRoles';
const styles = (theme) =>
({
@@ -76,6 +78,7 @@ class ParticipantList extends React.PureComponent
const {
roomClient,
advancedMode,
isModerator,
passivePeers,
selectedPeerId,
spotlightPeers,
@@ -84,6 +87,17 @@ class ParticipantList extends React.PureComponent
return (
<div className={classes.root} ref={(node) => { this.node = node; }}>
{ isModerator &&
<ul className={classes.list}>
<li className={classes.listheader}>
<FormattedMessage
id='room.moderatoractions'
defaultMessage='Moderator actions'
/>
</li>
<ListModerator />
</ul>
}
<ul className={classes.list}>
<li className={classes.listheader}>
<FormattedMessage
@@ -108,7 +122,7 @@ class ParticipantList extends React.PureComponent
})}
onClick={() => roomClient.setSelectedPeer(peerId)}
>
<ListPeer id={peerId} advancedMode={advancedMode}>
<ListPeer id={peerId} advancedMode={advancedMode} isModerator={isModerator}>
<Volume small id={peerId} />
</ListPeer>
</li>
@@ -129,7 +143,11 @@ class ParticipantList extends React.PureComponent
})}
onClick={() => roomClient.setSelectedPeer(peerId)}
>
<ListPeer id={peerId} advancedMode={advancedMode} />
<ListPeer
id={peerId}
advancedMode={advancedMode}
isModerator={isModerator}
/>
</li>
))}
</ul>
@@ -142,6 +160,7 @@ ParticipantList.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
isModerator : PropTypes.bool,
passivePeers : PropTypes.array,
selectedPeerId : PropTypes.string,
spotlightPeers : PropTypes.array,
@@ -151,6 +170,8 @@ ParticipantList.propTypes =
const mapStateToProps = (state) =>
{
return {
isModerator : state.me.roles.includes(userRoles.MODERATOR) ||
state.me.roles.includes(userRoles.ADMIN),
passivePeers : passivePeersSelector(state),
selectedPeerId : state.room.selectedPeerId,
spotlightPeers : spotlightPeersSelector(state)
@@ -165,6 +186,7 @@ const ParticipantListContainer = withRoomContext(connect(
areStatesEqual : (next, prev) =>
{
return (
prev.me.roles === next.me.roles &&
prev.peers === next.peers &&
prev.room.spotlights === next.room.spotlights &&
prev.room.selectedPeerId === next.room.selectedPeerId