Simplify participantlist and order participants based on status. Raise hand queue, and moderator can remove raised hand. Fixes #146, #278
parent
fa5f4f02a6
commit
136037d83f
|
|
@ -1533,6 +1533,26 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async lowerPeerHand(peerId)
|
||||
{
|
||||
logger.debug('lowerPeerHand() [peerId:"%s"]', peerId);
|
||||
|
||||
store.dispatch(
|
||||
peerActions.setPeerRaisedHandInProgress(peerId, true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('moderator:lowerHand', { peerId });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('lowerPeerHand() | [error:"%o"]', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
peerActions.setPeerRaisedHandInProgress(peerId, false));
|
||||
}
|
||||
|
||||
async setRaisedHand(raisedHand)
|
||||
{
|
||||
logger.debug('setRaisedHand: ', raisedHand);
|
||||
|
|
@ -2534,6 +2554,13 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:lowerHand':
|
||||
{
|
||||
this.setRaisedHand(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'gotRole':
|
||||
{
|
||||
const { peerId, role } = notification.data;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ export const setPeerRaisedHand = (peerId, raisedHand, raisedHandTimestamp) =>
|
|||
payload : { peerId, raisedHand, raisedHandTimestamp }
|
||||
});
|
||||
|
||||
export const setPeerRaisedHandInProgress = (peerId, flag) =>
|
||||
({
|
||||
type : 'SET_PEER_RAISED_HAND_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
});
|
||||
|
||||
export const setPeerPicture = (peerId, picture) =>
|
||||
({
|
||||
type : 'SET_PEER_PICTURE',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
|
@ -23,7 +24,7 @@ const styles = (theme) =>
|
|||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem',
|
||||
marginTop : theme.spacing(1)
|
||||
marginTop : theme.spacing(0.5)
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
|
|
@ -33,6 +34,10 @@ const styles = (theme) =>
|
|||
flexGrow : 1,
|
||||
alignItems : 'center'
|
||||
},
|
||||
buttons :
|
||||
{
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
|
|
@ -71,7 +76,9 @@ const ListMe = (props) =>
|
|||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
className={me.raisedHand ? classes.green : null}
|
||||
className={
|
||||
classnames(me.raisedHand ? classes.green : null, classes.buttons)
|
||||
}
|
||||
disabled={me.raisedHandInProgress}
|
||||
color='primary'
|
||||
onClick={(e) =>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { green } from '@material-ui/core/colors';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import VideocamIcon from '@material-ui/icons/Videocam';
|
||||
|
|
@ -17,6 +18,7 @@ import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
|
|||
import ExitIcon from '@material-ui/icons/ExitToApp';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import PanIcon from '@material-ui/icons/PanTool';
|
||||
import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -31,7 +33,7 @@ const styles = (theme) =>
|
|||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem',
|
||||
marginTop : theme.spacing(1)
|
||||
marginTop : theme.spacing(0.5)
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
|
|
@ -44,11 +46,16 @@ const styles = (theme) =>
|
|||
indicators :
|
||||
{
|
||||
display : 'flex',
|
||||
padding : theme.spacing(1.5)
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
buttons :
|
||||
{
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
color : 'rgba(0, 153, 0, 1)',
|
||||
marginLeft : theme.spacing(2)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -59,6 +66,7 @@ const ListPeer = (props) =>
|
|||
const {
|
||||
roomClient,
|
||||
isModerator,
|
||||
spotlight,
|
||||
peer,
|
||||
micConsumer,
|
||||
webcamConsumer,
|
||||
|
|
@ -94,11 +102,30 @@ const ListPeer = (props) =>
|
|||
<div className={classes.peerInfo}>
|
||||
{peer.displayName}
|
||||
</div>
|
||||
<div className={classes.indicators}>
|
||||
{ peer.raisedHand &&
|
||||
<PanIcon className={classes.green} />
|
||||
}
|
||||
</div>
|
||||
{ peer.raisedHand &&
|
||||
<IconButton
|
||||
className={classes.buttons}
|
||||
style={{ color: green[500] }}
|
||||
disabled={!isModerator || peer.raisedHandInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
roomClient.lowerPeerHand(peer.id);
|
||||
}}
|
||||
>
|
||||
<PanIcon />
|
||||
</IconButton>
|
||||
}
|
||||
{ spotlight &&
|
||||
<IconButton
|
||||
className={classes.buttons}
|
||||
style={{ color: green[500] }}
|
||||
disabled
|
||||
>
|
||||
<RecordVoiceOverIcon />
|
||||
</IconButton>
|
||||
}
|
||||
{ screenConsumer &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
|
|
@ -114,6 +141,7 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={screenVisible ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerScreenInProgress}
|
||||
className={classes.buttons}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -145,6 +173,7 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={webcamEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerVideoInProgress}
|
||||
className={classes.buttons}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -175,6 +204,7 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={micEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerAudioInProgress}
|
||||
className={classes.buttons}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -205,6 +235,7 @@ const ListPeer = (props) =>
|
|||
defaultMessage : 'Kick out participant'
|
||||
})}
|
||||
disabled={peer.peerKickInProgress}
|
||||
className={classes.buttons}
|
||||
color='secondary'
|
||||
onClick={(e) =>
|
||||
{
|
||||
|
|
@ -227,6 +258,7 @@ ListPeer.propTypes =
|
|||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
isModerator : PropTypes.bool,
|
||||
spotlight : PropTypes.bool,
|
||||
peer : appPropTypes.Peer.isRequired,
|
||||
micConsumer : appPropTypes.Consumer,
|
||||
webcamConsumer : appPropTypes.Consumer,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
passivePeersSelector,
|
||||
spotlightSortedPeersSelector
|
||||
participantListSelector
|
||||
} from '../../Selectors';
|
||||
import classNames from 'classnames';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import PropTypes from 'prop-types';
|
||||
|
|
@ -76,9 +75,9 @@ class ParticipantList extends React.PureComponent
|
|||
roomClient,
|
||||
advancedMode,
|
||||
isModerator,
|
||||
passivePeers,
|
||||
participants,
|
||||
spotlights,
|
||||
selectedPeerId,
|
||||
spotlightPeers,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -107,48 +106,34 @@ class ParticipantList extends React.PureComponent
|
|||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.spotlights'
|
||||
defaultMessage='Participants in Spotlight'
|
||||
id='label.participants'
|
||||
defaultMessage='Participants'
|
||||
/>
|
||||
</li>
|
||||
{ spotlightPeers.map((peer) => (
|
||||
{ participants.map((peer) => (
|
||||
<li
|
||||
key={peer.id}
|
||||
className={classNames(classes.listItem, {
|
||||
className={classnames(classes.listItem, {
|
||||
selected : peer.id === selectedPeerId
|
||||
})}
|
||||
onClick={() => roomClient.setSelectedPeer(peer.id)}
|
||||
>
|
||||
<ListPeer
|
||||
id={peer.id}
|
||||
advancedMode={advancedMode}
|
||||
isModerator={isModerator}
|
||||
>
|
||||
<Volume small id={peer.id} />
|
||||
</ListPeer>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.passive'
|
||||
defaultMessage='Passive Participants'
|
||||
/>
|
||||
</li>
|
||||
{ passivePeers.map((peer) => (
|
||||
<li
|
||||
key={peer.id}
|
||||
className={classNames(classes.listItem, {
|
||||
selected : peer.id === selectedPeerId
|
||||
})}
|
||||
onClick={() => roomClient.setSelectedPeer(peer.id)}
|
||||
>
|
||||
<ListPeer
|
||||
id={peer.id}
|
||||
advancedMode={advancedMode}
|
||||
isModerator={isModerator}
|
||||
/>
|
||||
{ spotlights.includes(peer.id) ?
|
||||
<ListPeer
|
||||
id={peer.id}
|
||||
advancedMode={advancedMode}
|
||||
isModerator={isModerator}
|
||||
spotlight
|
||||
>
|
||||
<Volume small id={peer.id} />
|
||||
</ListPeer>
|
||||
:
|
||||
<ListPeer
|
||||
id={peer.id}
|
||||
advancedMode={advancedMode}
|
||||
isModerator={isModerator}
|
||||
/>
|
||||
}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
@ -162,9 +147,9 @@ ParticipantList.propTypes =
|
|||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
isModerator : PropTypes.bool,
|
||||
passivePeers : PropTypes.array,
|
||||
participants : PropTypes.array,
|
||||
spotlights : PropTypes.array,
|
||||
selectedPeerId : PropTypes.string,
|
||||
spotlightPeers : PropTypes.array,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -174,9 +159,9 @@ const mapStateToProps = (state) =>
|
|||
isModerator :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)),
|
||||
passivePeers : passivePeersSelector(state),
|
||||
selectedPeerId : state.room.selectedPeerId,
|
||||
spotlightPeers : spotlightSortedPeersSelector(state)
|
||||
participants : participantListSelector(state),
|
||||
spotlights : state.room.spotlights,
|
||||
selectedPeerId : state.room.selectedPeerId
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ const peersKeySelector = createSelector(
|
|||
peersSelector,
|
||||
(peers) => Object.keys(peers)
|
||||
);
|
||||
const peersValueSelector = createSelector(
|
||||
|
||||
export const peersValueSelector = createSelector(
|
||||
peersSelector,
|
||||
(peers) => Object.values(peers)
|
||||
);
|
||||
|
|
@ -113,8 +114,31 @@ export const spotlightPeersSelector = createSelector(
|
|||
export const spotlightSortedPeersSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
peersValueSelector,
|
||||
(spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id))
|
||||
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
|
||||
(spotlights, peers) =>
|
||||
peers.filter((peer) => spotlights.includes(peer.id) && !peer.raisedHand)
|
||||
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
|
||||
);
|
||||
|
||||
const raisedHandSortedPeers = createSelector(
|
||||
peersValueSelector,
|
||||
(peers) => peers.filter((peer) => peer.raisedHand)
|
||||
.sort((a, b) => a.raisedHandTimestamp - b.raisedHandTimestamp)
|
||||
);
|
||||
|
||||
const peersSortedSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
peersValueSelector,
|
||||
(spotlights, peers) =>
|
||||
peers.filter((peer) => !spotlights.includes(peer.id) && !peer.raisedHand)
|
||||
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
|
||||
);
|
||||
|
||||
export const participantListSelector = createSelector(
|
||||
raisedHandSortedPeers,
|
||||
spotlightSortedPeersSelector,
|
||||
peersSortedSelector,
|
||||
(raisedHands, spotlights, peers) =>
|
||||
[ ...raisedHands, ...spotlights, ...peers ]
|
||||
);
|
||||
|
||||
export const peersLengthSelector = createSelector(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ const peer = (state = {}, action) =>
|
|||
raisedHandTimestamp : action.payload.raisedHandTimestamp
|
||||
};
|
||||
|
||||
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
|
||||
return {
|
||||
...state,
|
||||
raisedHandInProgress : action.payload.flag
|
||||
};
|
||||
|
||||
case 'ADD_CONSUMER':
|
||||
{
|
||||
const consumers = [ ...state.consumers, action.payload.consumer.id ];
|
||||
|
|
@ -91,6 +97,7 @@ const peers = (state = {}, action) =>
|
|||
case 'SET_PEER_AUDIO_IN_PROGRESS':
|
||||
case 'SET_PEER_SCREEN_IN_PROGRESS':
|
||||
case 'SET_PEER_RAISED_HAND':
|
||||
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
|
||||
case 'SET_PEER_PICTURE':
|
||||
case 'ADD_CONSUMER':
|
||||
case 'ADD_PEER_ROLE':
|
||||
|
|
|
|||
|
|
@ -1430,6 +1430,29 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:lowerHand':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
|
||||
)
|
||||
)
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
||||
const lowerPeer = this._peers[peerId];
|
||||
|
||||
if (!lowerPeer)
|
||||
throw new Error(`peer with id "${peerId}" not found`);
|
||||
|
||||
this._notification(lowerPeer.socket, 'moderator:lowerHand');
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
logger.error('unknown request.method "%s"', request.method);
|
||||
|
|
|
|||
Loading…
Reference in New Issue