Merge branch 'feat-user-roles' into develop
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user