diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index 06654b0..464c2c2 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -587,6 +587,43 @@ export default class RoomClient }); } + sendRaiseHandState(state) + { + logger.debug('sendRaiseHandState: ', state); + + this._dispatch( + stateActions.setMyRaiseHandStateInProgress(true)); + + return this._protoo.send('raisehand-message', { raiseHandState: state }) + .then(() => + { + this._dispatch( + stateActions.setMyRaiseHandState(state)); + + this._dispatch(requestActions.notify( + { + text : 'raiseHand state changed' + })); + this._dispatch( + stateActions.setMyRaiseHandStateInProgress(false)); + }) + .catch((error) => + { + logger.error('sendRaiseHandState() | failed: %o', error); + + this._dispatch(requestActions.notify( + { + type : 'error', + text : `Could not change raise hand state: ${error}` + })); + + // We need to refresh the component for it to render changed state + this._dispatch(stateActions.setMyRaiseHandState(!state)); + this._dispatch( + stateActions.setMyRaiseHandStateInProgress(false)); + }); + } + restartIce() { logger.debug('restartIce()'); @@ -713,6 +750,17 @@ export default class RoomClient break; } + case 'raisehand-message': + { + accept(); + const { peerName, raiseHandState } = request.data; + + logger.debug('Got raiseHandState from "%s"', peerName); + + this._dispatch( + stateActions.setPeerRaiseHandState(peerName, raiseHandState)); + break; + } case 'chat-message-receive': { diff --git a/app/lib/components/Peer.jsx b/app/lib/components/Peer.jsx index 66536eb..38b300f 100644 --- a/app/lib/components/Peer.jsx +++ b/app/lib/components/Peer.jsx @@ -59,6 +59,10 @@ const Peer = (props) => return (
+ {peer.raiseHandState ? +
+ :null + } {!micEnabled ?
:null diff --git a/app/lib/components/Room.jsx b/app/lib/components/Room.jsx index eb19f99..80f030b 100644 --- a/app/lib/components/Room.jsx +++ b/app/lib/components/Room.jsx @@ -24,10 +24,11 @@ class Room extends React.Component onRoomLinkCopy, onSetAudioMode, onRestartIce, - onLeaveMeeting, onShareScreen, onUnShareScreen, - onNeedExtension + onNeedExtension, + onToggleHand, + onLeaveMeeting } = this.props; let screenState; @@ -157,6 +158,16 @@ class Room extends React.Component onClick={() => onRestartIce()} /> +
onToggleHand(!me.raiseHand)} + /> +
@@ -226,6 +238,13 @@ const mapDispatchToProps = (dispatch) => { dispatch(requestActions.restartIce()); }, + onToggleHand : (enable) => + { + if (enable) + dispatch(requestActions.raiseHand()); + else + dispatch(requestActions.lowerHand()); + }, onLeaveMeeting : () => { dispatch(requestActions.leaveRoom()); diff --git a/app/lib/redux/STATE.md b/app/lib/redux/STATE.md index c05ffc2..cd3de86 100644 --- a/app/lib/redux/STATE.md +++ b/app/lib/redux/STATE.md @@ -51,10 +51,11 @@ { 'alice' : { - name : 'alice', - displayName : 'Alice Thomsom', - device : { flag: 'chrome', name: 'Chrome', version: '58' }, - consumers : [ 5551, 5552 ] + name : 'alice', + displayName : 'Alice Thomsom', + raiseHandState : false, + device : { flag: 'chrome', name: 'Chrome', version: '58' }, + consumers : [ 5551, 5552 ] } }, consumers : diff --git a/app/lib/redux/reducers/me.js b/app/lib/redux/reducers/me.js index dd6b41c..8f15038 100644 --- a/app/lib/redux/reducers/me.js +++ b/app/lib/redux/reducers/me.js @@ -13,6 +13,8 @@ const initialState = screenShareInProgress : false, audioOnly : false, audioOnlyInProgress : false, + raiseHand : false, + raiseHandInProgress : false, restartIceInProgress : false }; @@ -33,11 +35,11 @@ const me = (state = initialState, action) => return { ...state, canSendMic, canSendWebcam }; } - + case 'SET_SCREEN_CAPABILITIES': { const { canShareScreen, needExtension } = action.payload; - + return { ...state, canShareScreen, needExtension }; } @@ -87,6 +89,20 @@ const me = (state = initialState, action) => return { ...state, audioOnlyInProgress: flag }; } + case 'SET_MY_RAISE_HAND_STATE': + { + const { flag } = action.payload; + + return { ...state, raiseHand: flag }; + } + + case 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS': + { + const { flag } = action.payload; + + return { ...state, raiseHandInProgress: flag }; + } + case 'SET_RESTART_ICE_IN_PROGRESS': { const { flag } = action.payload; diff --git a/app/lib/redux/reducers/peers.js b/app/lib/redux/reducers/peers.js index 59761e2..8ff36bd 100644 --- a/app/lib/redux/reducers/peers.js +++ b/app/lib/redux/reducers/peers.js @@ -34,6 +34,19 @@ const peers = (state = initialState, action) => return { ...state, [newPeer.name]: newPeer }; } + case 'SET_PEER_RAISE_HAND_STATE': + { + const { peerName, raiseHandState } = action.payload; + const peer = state[peerName]; + + if (!peer) + throw new Error('no Peer found'); + + const newPeer = { ...peer, raiseHandState }; + + return { ...state, [newPeer.name]: newPeer }; + } + case 'ADD_CONSUMER': { const { consumer, peerName } = action.payload; diff --git a/app/lib/redux/requestActions.js b/app/lib/redux/requestActions.js index 1c3b304..e13203b 100644 --- a/app/lib/redux/requestActions.js +++ b/app/lib/redux/requestActions.js @@ -78,6 +78,20 @@ export const disableAudioOnly = () => }; }; +export const raiseHand = () => +{ + return { + type : 'RAISE_HAND' + }; +}; + +export const lowerHand = () => +{ + return { + type : 'LOWER_HAND' + }; +}; + export const restartIce = () => { return { diff --git a/app/lib/redux/roomClientMiddleware.js b/app/lib/redux/roomClientMiddleware.js index fed0951..971985a 100644 --- a/app/lib/redux/roomClientMiddleware.js +++ b/app/lib/redux/roomClientMiddleware.js @@ -102,6 +102,20 @@ export default ({ dispatch, getState }) => (next) => break; } + case 'RAISE_HAND': + { + client.sendRaiseHandState(true); + + break; + } + + case 'LOWER_HAND': + { + client.sendRaiseHandState(false); + + break; + } + case 'RESTART_ICE': { client.restartIce(); diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index bab6033..21bb7c3 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -86,6 +86,30 @@ export const setAudioOnlyInProgress = (flag) => }; }; +export const setMyRaiseHandState = (flag) => +{ + return { + type : 'SET_MY_RAISE_HAND_STATE', + payload : { flag } + }; +}; + +export const setMyRaiseHandStateInProgress = (flag) => +{ + return { + type : 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS', + payload : { flag } + }; +}; + +export const setPeerRaiseHandState = (peerName, raiseHandState) => +{ + return { + type : 'SET_PEER_RAISE_HAND_STATE', + payload : { peerName, raiseHandState } + }; +}; + export const setRestartIceInProgress = (flag) => { return { diff --git a/app/resources/images/icon-hand-black.svg b/app/resources/images/icon-hand-black.svg new file mode 100644 index 0000000..8f0f065 --- /dev/null +++ b/app/resources/images/icon-hand-black.svg @@ -0,0 +1,26 @@ + + + + image/svg+xml + + + diff --git a/app/resources/images/icon-hand-white.svg b/app/resources/images/icon-hand-white.svg new file mode 100644 index 0000000..0e2f05f --- /dev/null +++ b/app/resources/images/icon-hand-white.svg @@ -0,0 +1,26 @@ + + + + image/svg+xml + + + diff --git a/app/resources/images/icon_remote_raise_hand.svg b/app/resources/images/icon_remote_raise_hand.svg new file mode 100644 index 0000000..0e2f05f --- /dev/null +++ b/app/resources/images/icon_remote_raise_hand.svg @@ -0,0 +1,26 @@ + + + + image/svg+xml + + + diff --git a/app/stylus/components/Peer.styl b/app/stylus/components/Peer.styl index 8426b0f..3c32d99 100644 --- a/app/stylus/components/Peer.styl +++ b/app/stylus/components/Peer.styl @@ -38,6 +38,10 @@ opacity: 0.85; } + &.raise-hand { + background-image: url('/resources/images/icon_remote_raise_hand.svg'); + } + &.mic-off { background-image: url('/resources/images/icon_remote_mic_white_off.svg'); } diff --git a/app/stylus/components/Room.styl b/app/stylus/components/Room.styl index fcc1471..6685afd 100644 --- a/app/stylus/components/Room.styl +++ b/app/stylus/components/Room.styl @@ -250,6 +250,13 @@ background-image: url('/resources/images/share-screen-extension.svg'); } } + &.raise-hand { + background-image: url('/resources/images/icon-hand-white.svg'); + + &.on { + background-image: url('/resources/images/icon-hand-black.svg'); + } + } &.leave-meeting { background-image: url('/resources/images/leave-meeting.svg'); diff --git a/server/lib/Room.js b/server/lib/Room.js index a2bad3f..4a72188 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -260,6 +260,27 @@ class Room extends EventEmitter break; } + case 'raisehand-message': + { + accept(); + + const { raiseHandState } = request.data; + const { mediaPeer } = protooPeer.data; + + mediaPeer.appData.raiseHand = request.data.raiseHandState; + + // Spread to others via protoo. + this._protooRoom.spread( + 'raisehand-message', + { + peerName : protooPeer.id, + raiseHandState : raiseHandState + }, + [ protooPeer ]); + + break; + } + default: { logger.error('unknown request.method "%s"', request.method);