feat(raised-hand) Change `raisedHand` to a timestamp instead of boole… (#10167)

- this was needed for sorting the raised hand participants in participants pane in
the order they raised their hand also for participants joining late
This commit is contained in:
Horatiu Muresan 2021-10-21 12:40:57 +03:00 committed by GitHub
parent f435fc4ade
commit 4b7a6741fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 97 additions and 78 deletions

View File

@ -37,7 +37,8 @@ import {
kickParticipant, kickParticipant,
raiseHand, raiseHand,
isParticipantModerator, isParticipantModerator,
isLocalParticipantModerator isLocalParticipantModerator,
hasRaisedHand
} from '../../react/features/base/participants'; } from '../../react/features/base/participants';
import { updateSettings } from '../../react/features/base/settings'; import { updateSettings } from '../../react/features/base/settings';
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks'; import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
@ -281,7 +282,7 @@ function initCommands() {
if (!localParticipant) { if (!localParticipant) {
return; return;
} }
const { raisedHand } = localParticipant; const raisedHand = hasRaisedHand(localParticipant);
sendAnalytics(createApiEvent('raise-hand.toggled')); sendAnalytics(createApiEvent('raise-hand.toggled'));
APP.store.dispatch(raiseHand(!raisedHand)); APP.store.dispatch(raiseHand(!raisedHand));

View File

@ -8,6 +8,7 @@ import { MEDIA_TYPE } from '../base/media';
import { import {
getLocalParticipant, getLocalParticipant,
getRemoteParticipants, getRemoteParticipants,
hasRaisedHand,
isLocalParticipantModerator, isLocalParticipantModerator,
isParticipantModerator, isParticipantModerator,
PARTICIPANT_UPDATED, PARTICIPANT_UPDATED,
@ -134,7 +135,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
if (isLocalParticipantModerator(state)) { if (isLocalParticipantModerator(state)) {
// this is handled only by moderators // this is handled only by moderators
if (participant.raisedHand) { if (hasRaisedHand(participant)) {
// if participant raises hand show notification // if participant raises hand show notification
!isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state) !isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state)
&& dispatch(participantPendingAudio(participant)); && dispatch(participantPendingAudio(participant));
@ -148,7 +149,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
// this is the granted moderator case // this is the granted moderator case
getRemoteParticipants(state).forEach(p => { getRemoteParticipants(state).forEach(p => {
p.raisedHand && !isParticipantApproved(p.id, MEDIA_TYPE.AUDIO)(state) hasRaisedHand(p) && !isParticipantApproved(p.id, MEDIA_TYPE.AUDIO)(state)
&& dispatch(participantPendingAudio(p)); && dispatch(participantPendingAudio(p));
}); });
} }

View File

@ -564,13 +564,13 @@ export function setLoadableAvatarUrl(participantId, url) {
* @param {boolean} enabled - Raise or lower hand. * @param {boolean} enabled - Raise or lower hand.
* @returns {{ * @returns {{
* type: LOCAL_PARTICIPANT_RAISE_HAND, * type: LOCAL_PARTICIPANT_RAISE_HAND,
* enabled: boolean * raisedHandTimestamp: number
* }} * }}
*/ */
export function raiseHand(enabled) { export function raiseHand(enabled) {
return { return {
type: LOCAL_PARTICIPANT_RAISE_HAND, type: LOCAL_PARTICIPANT_RAISE_HAND,
enabled raisedHandTimestamp: enabled ? Date.now() : 0
}; };
} }

View File

@ -461,10 +461,20 @@ async function _getFirstLoadableAvatarUrl(participant, store) {
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state * {@code getState} function to be used to retrieve the state
* features/base/participants. * features/base/participants.
* @returns {Array<string>} * @returns {Array<Object>}
*/ */
export function getRaiseHandsQueue(stateful: Object | Function): Array<string> { export function getRaiseHandsQueue(stateful: Object | Function): Array<Object> {
const { raisedHandsQueue } = toState(stateful)['features/base/participants']; const { raisedHandsQueue } = toState(stateful)['features/base/participants'];
return raisedHandsQueue; return raisedHandsQueue;
} }
/**
* Returns whether the given participant has his hand raised or not.
*
* @param {Object} participant - The participant.
* @returns {boolean} - Whether participant has raise hand or not.
*/
export function hasRaisedHand(participant: Object): boolean {
return Boolean(participant && participant.raisedHandTimestamp);
}

View File

@ -94,7 +94,7 @@ MiddlewareRegistry.register(store => next => action => {
const participant = getLocalParticipant(state); const participant = getLocalParticipant(state);
const isLocal = participant && participant.id === id; const isLocal = participant && participant.id === id;
if (isLocal && participant.raisedHand === undefined) { if (isLocal && participant.raisedHandTimestamp === undefined) {
// if local was undefined, let's leave it like that // if local was undefined, let's leave it like that
// avoids sending unnecessary presence updates // avoids sending unnecessary presence updates
break; break;
@ -105,7 +105,7 @@ MiddlewareRegistry.register(store => next => action => {
conference, conference,
id, id,
local: isLocal, local: isLocal,
raisedHand: false raisedHandTimestamp: 0
})); }));
} }
@ -127,14 +127,9 @@ MiddlewareRegistry.register(store => next => action => {
} }
case LOCAL_PARTICIPANT_RAISE_HAND: { case LOCAL_PARTICIPANT_RAISE_HAND: {
const { enabled } = action; const { raisedHandTimestamp } = action;
const localId = getLocalParticipant(store.getState())?.id; const localId = getLocalParticipant(store.getState())?.id;
store.dispatch(raiseHandUpdateQueue({
id: localId,
raisedHand: enabled
}));
store.dispatch(participantUpdated({ store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without // XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property // stating the JitsiConference instance (i.e. participant property
@ -144,11 +139,16 @@ MiddlewareRegistry.register(store => next => action => {
id: localId, id: localId,
local: true, local: true,
raisedHand: enabled raisedHandTimestamp
}));
store.dispatch(raiseHandUpdateQueue({
id: localId,
raisedHandTimestamp
})); }));
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {
APP.API.notifyRaiseHandUpdated(localId, enabled); APP.API.notifyRaiseHandUpdated(localId, raisedHandTimestamp);
} }
break; break;
@ -177,16 +177,22 @@ MiddlewareRegistry.register(store => next => action => {
case RAISE_HAND_UPDATED: { case RAISE_HAND_UPDATED: {
const { participant } = action; const { participant } = action;
const queue = getRaiseHandsQueue(store.getState()); let queue = getRaiseHandsQueue(store.getState());
if (participant.raisedHand) { if (participant.raisedHandTimestamp) {
queue.push(participant.id); queue.push({
action.queue = queue; id: participant.id,
raisedHandTimestamp: participant.raisedHandTimestamp
});
// sort the queue before adding to store.
queue = queue.sort(({ raisedHandTimestamp: a }, { raisedHandTimestamp: b }) => a - b);
} else { } else {
const filteredQueue = queue.filter(id => id !== participant.id); // no need to sort on remove value.
queue = queue.filter(({ id }) => id !== participant.id);
action.queue = filteredQueue;
} }
action.queue = queue;
break; break;
} }
@ -287,7 +293,8 @@ StateListenerRegistry.register(
id: participant.getId(), id: participant.getId(),
features: { 'screen-sharing': true } features: { 'screen-sharing': true }
})), })),
'raisedHand': (participant, value) => _raiseHandUpdated(store, conference, participant.getId(), value), 'raisedHand': (participant, value) =>
_raiseHandUpdated(store, conference, participant.getId(), value),
'remoteControlSessionStatus': (participant, value) => 'remoteControlSessionStatus': (participant, value) =>
store.dispatch(participantUpdated({ store.dispatch(participantUpdated({
conference, conference,
@ -320,7 +327,7 @@ StateListenerRegistry.register(
// We left the conference, the local participant must be updated. // We left the conference, the local participant must be updated.
_e2eeUpdated(store, conference, localParticipantId, false); _e2eeUpdated(store, conference, localParticipantId, false);
_raiseHandUpdated(store, conference, localParticipantId, false); _raiseHandUpdated(store, conference, localParticipantId, 0);
} }
} }
); );
@ -451,18 +458,19 @@ function _maybePlaySounds({ getState, dispatch }, action) {
*/ */
function _participantJoinedOrUpdated(store, next, action) { function _participantJoinedOrUpdated(store, next, action) {
const { dispatch, getState } = store; const { dispatch, getState } = store;
const { participant: { avatarURL, email, id, local, name, raisedHand } } = action; const { participant: { avatarURL, email, id, local, name, raisedHandTimestamp } } = action;
// Send an external update of the local participant's raised hand state // Send an external update of the local participant's raised hand state
// if a new raised hand state is defined in the action. // if a new raised hand state is defined in the action.
if (typeof raisedHand !== 'undefined') { if (typeof raisedHandTimestamp !== 'undefined') {
if (local) { if (local) {
const { conference } = getState()['features/base/conference']; const { conference } = getState()['features/base/conference'];
const rHand = parseInt(raisedHandTimestamp, 10);
// Send raisedHand signalling only if there is a change // Send raisedHand signalling only if there is a change
if (conference && raisedHand !== getLocalParticipant(getState()).raisedHand) { if (conference && rHand !== getLocalParticipant(getState()).raisedHandTimestamp) {
conference.setLocalParticipantProperty('raisedHand', raisedHand); conference.setLocalParticipantProperty('raisedHand', rHand);
} }
} }
} }
@ -508,22 +516,34 @@ function _participantJoinedOrUpdated(store, next, action) {
* @returns {void} * @returns {void}
*/ */
function _raiseHandUpdated({ dispatch, getState }, conference, participantId, newValue) { function _raiseHandUpdated({ dispatch, getState }, conference, participantId, newValue) {
const raisedHand = newValue === 'true'; let raisedHandTimestamp;
switch (newValue) {
case undefined:
case 'false':
raisedHandTimestamp = 0;
break;
case 'true':
raisedHandTimestamp = Date.now();
break;
default:
raisedHandTimestamp = parseInt(newValue, 10);
}
const state = getState(); const state = getState();
dispatch(participantUpdated({ dispatch(participantUpdated({
conference, conference,
id: participantId, id: participantId,
raisedHand raisedHandTimestamp
})); }));
dispatch(raiseHandUpdateQueue({ dispatch(raiseHandUpdateQueue({
id: participantId, id: participantId,
raisedHand raisedHandTimestamp
})); }));
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {
APP.API.notifyRaiseHandUpdated(participantId, raisedHand); APP.API.notifyRaiseHandUpdated(participantId, raisedHandTimestamp);
} }
const isModerator = isLocalParticipantModerator(state); const isModerator = isLocalParticipantModerator(state);
@ -540,7 +560,7 @@ function _raiseHandUpdated({ dispatch, getState }, conference, participantId, ne
customActionHandler: () => dispatch(approveParticipant(participantId)) customActionHandler: () => dispatch(approveParticipant(participantId))
} : {}; } : {};
if (raisedHand) { if (raisedHandTimestamp) {
dispatch(showNotification({ dispatch(showNotification({
titleKey: 'notify.somebody', titleKey: 'notify.somebody',
title: getParticipantDisplayName(state, participantId), title: getParticipantDisplayName(state, participantId),

View File

@ -2,7 +2,7 @@
import { Component } from 'react'; import { Component } from 'react';
import { getParticipantById } from '../../base/participants'; import { getParticipantById, hasRaisedHand } from '../../base/participants';
export type Props = { export type Props = {
@ -57,6 +57,6 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
const participant = getParticipantById(state, ownProps.participantId); const participant = getParticipantById(state, ownProps.participantId);
return { return {
_raisedHand: participant && participant.raisedHand _raisedHand: hasRaisedHand(participant)
}; };
} }

View File

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper'; import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { hasRaisedHand } from '../../../base/participants';
import { approveKnockingParticipant } from '../../../lobby/actions.native'; import { approveKnockingParticipant } from '../../../lobby/actions.native';
import { showContextMenuReject } from '../../actions.native'; import { showContextMenuReject } from '../../actions.native';
import { MEDIA_STATE } from '../../constants'; import { MEDIA_STATE } from '../../constants';
@ -35,7 +36,7 @@ export const LobbyParticipantItem = ({ participant: p }: Props) => {
onPress = { openContextMenuReject } onPress = { openContextMenuReject }
participant = { p } participant = { p }
participantID = { p.id } participantID = { p.id }
raisedHand = { p.raisedHand } raisedHand = { hasRaisedHand(p) }
videoMediaState = { MEDIA_STATE.NONE }> videoMediaState = { MEDIA_STATE.NONE }>
<Button <Button
children = { t('lobby.admit') } children = { t('lobby.admit') }

View File

@ -7,6 +7,7 @@ import {
getLocalParticipant, getLocalParticipant,
getParticipantByIdOrUndefined, getParticipantByIdOrUndefined,
getParticipantDisplayName, getParticipantDisplayName,
hasRaisedHand,
isParticipantModerator isParticipantModerator
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -190,7 +191,7 @@ function mapStateToProps(state, ownProps): Object {
_local: Boolean(participant?.local), _local: Boolean(participant?.local),
_localVideoOwner: Boolean(ownerId === localParticipantId), _localVideoOwner: Boolean(ownerId === localParticipantId),
_participantID: participant?.id, _participantID: participant?.id,
_raisedHand: Boolean(participant?.raisedHand), _raisedHand: hasRaisedHand(participant),
_videoMediaState: videoMediaState _videoMediaState: videoMediaState
}; };
} }

View File

@ -3,6 +3,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { hasRaisedHand } from '../../../base/participants';
import { ACTION_TRIGGER, MEDIA_STATE } from '../../constants'; import { ACTION_TRIGGER, MEDIA_STATE } from '../../constants';
import { useLobbyActions } from '../../hooks'; import { useLobbyActions } from '../../hooks';
@ -45,7 +46,7 @@ export const LobbyParticipantItem = ({
openDrawerForParticipant = { openDrawerForParticipant } openDrawerForParticipant = { openDrawerForParticipant }
overflowDrawer = { overflowDrawer } overflowDrawer = { overflowDrawer }
participantID = { id } participantID = { id }
raisedHand = { p.raisedHand } raisedHand = { hasRaisedHand(p) }
videoMediaState = { MEDIA_STATE.NONE } videoMediaState = { MEDIA_STATE.NONE }
youText = { t('chat.you') }> youText = { t('chat.you') }>
<ParticipantActionButton <ParticipantActionButton

View File

@ -9,6 +9,7 @@ import {
getLocalParticipant, getLocalParticipant,
getParticipantByIdOrUndefined, getParticipantByIdOrUndefined,
getParticipantDisplayName, getParticipantDisplayName,
hasRaisedHand,
isParticipantModerator isParticipantModerator
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -307,7 +308,7 @@ function _mapStateToProps(state, ownProps): Object {
_participant: participant, _participant: participant,
_participantID: participant?.id, _participantID: participant?.id,
_quickActionButtonType, _quickActionButtonType,
_raisedHand: Boolean(participant?.raisedHand), _raisedHand: hasRaisedHand(participant),
_videoMediaState _videoMediaState
}; };
} }

View File

@ -211,7 +211,7 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
const { id } = getLocalParticipant(stateful); const { id } = getLocalParticipant(stateful);
const remoteParticipants = getRemoteParticipantsSorted(stateful); const remoteParticipants = getRemoteParticipantsSorted(stateful);
const reorderedParticipants = new Set(remoteParticipants); const reorderedParticipants = new Set(remoteParticipants);
const raisedHandParticipants = getRaiseHandsQueue(stateful); const raisedHandParticipants = getRaiseHandsQueue(stateful).map(({ id: particId }) => particId);
const remoteRaisedHandParticipants = new Set(raisedHandParticipants || []); const remoteRaisedHandParticipants = new Set(raisedHandParticipants || []);
const dominantSpeaker = getDominantSpeakerParticipant(stateful); const dominantSpeaker = getDominantSpeakerParticipant(stateful);
@ -219,15 +219,11 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
// Avoid duplicates. // Avoid duplicates.
if (reorderedParticipants.has(participant)) { if (reorderedParticipants.has(participant)) {
reorderedParticipants.delete(participant); reorderedParticipants.delete(participant);
} else {
remoteRaisedHandParticipants.delete(participant);
} }
} }
// Remove self.
remoteRaisedHandParticipants.delete(id);
const dominant = []; const dominant = [];
const local = remoteRaisedHandParticipants.has(id) ? [] : [ id ];
// Remove dominant speaker. // Remove dominant speaker.
if (dominantSpeaker && dominantSpeaker.id !== id) { if (dominantSpeaker && dominantSpeaker.id !== id) {
@ -239,7 +235,7 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
// Move self and participants with raised hand to the top of the list. // Move self and participants with raised hand to the top of the list.
return [ return [
...dominant, ...dominant,
id, ...local,
...Array.from(remoteRaisedHandParticipants.keys()), ...Array.from(remoteRaisedHandParticipants.keys()),
...Array.from(reorderedParticipants.keys()) ...Array.from(reorderedParticipants.keys())
]; ];

View File

@ -12,6 +12,7 @@ import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { import {
getLocalParticipant, getLocalParticipant,
hasRaisedHand,
raiseHand raiseHand
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -157,7 +158,7 @@ function _mapStateToProps(state): Object {
return { return {
_localParticipant, _localParticipant,
_raisedHand: _localParticipant.raisedHand, _raisedHand: hasRaisedHand(_localParticipant),
_styles: ColorSchemeRegistry.get(state, 'Toolbox').raiseHandButton _styles: ColorSchemeRegistry.get(state, 'Toolbox').raiseHandButton
}; };
} }

View File

@ -7,7 +7,7 @@ import { RAISE_HAND_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons'; import { IconRaisedHand } from '../../../base/icons';
import { import {
getLocalParticipant getLocalParticipant, hasRaisedHand
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
@ -81,7 +81,7 @@ function _mapStateToProps(state, ownProps): Object {
const { visible = enabled } = ownProps; const { visible = enabled } = ownProps;
return { return {
_raisedHand: _localParticipant.raisedHand, _raisedHand: hasRaisedHand(_localParticipant),
_reactionsOpen: isDialogOpen(state, ReactionMenuDialog), _reactionsOpen: isDialogOpen(state, ReactionMenuDialog),
visible visible
}; };

View File

@ -10,7 +10,7 @@ import {
} from '../../../analytics'; } from '../../../analytics';
import { isMobileBrowser } from '../../../base/environment/utils'; import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { getLocalParticipant, participantUpdated } from '../../../base/participants'; import { getLocalParticipant, hasRaisedHand, raiseHand } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { dockToolbox } from '../../../toolbox/actions.web'; import { dockToolbox } from '../../../toolbox/actions.web';
import { addReactionToBuffer } from '../../actions.any'; import { addReactionToBuffer } from '../../actions.any';
@ -123,22 +123,9 @@ class ReactionsMenu extends Component<Props> {
* @returns {void} * @returns {void}
*/ */
_doToggleRaiseHand() { _doToggleRaiseHand() {
const { _localParticipantID, _raisedHand } = this.props; const { _raisedHand } = this.props;
const newRaisedStatus = !_raisedHand;
this.props.dispatch(participantUpdated({ this.props.dispatch(raiseHand(!_raisedHand));
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id: _localParticipantID,
local: true,
raisedHand: newRaisedStatus
}));
APP.API.notifyRaiseHandUpdated(_localParticipantID, newRaisedStatus);
} }
/** /**
@ -221,7 +208,7 @@ function mapStateToProps(state) {
return { return {
_localParticipantID: localParticipant.id, _localParticipantID: localParticipant.id,
_isMobile: isMobileBrowser(), _isMobile: isMobileBrowser(),
_raisedHand: localParticipant.raisedHand _raisedHand: hasRaisedHand(localParticipant)
}; };
} }

View File

@ -5,7 +5,7 @@ import React from 'react';
import { isMobileBrowser } from '../../../base/environment/utils'; import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { IconArrowUp, IconRaisedHand } from '../../../base/icons'; import { IconArrowUp, IconRaisedHand } from '../../../base/icons';
import { getLocalParticipant } from '../../../base/participants'; import { getLocalParticipant, hasRaisedHand } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { ToolboxButtonWithIcon } from '../../../base/toolbox/components'; import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
import ToolbarButton from '../../../toolbox/components/web/ToolbarButton'; import ToolbarButton from '../../../toolbox/components/web/ToolbarButton';
@ -138,7 +138,7 @@ function mapStateToProps(state) {
isOpen: getReactionsMenuVisibility(state), isOpen: getReactionsMenuVisibility(state),
isMobile: isMobileBrowser(), isMobile: isMobileBrowser(),
reactionsQueue: getReactionsQueue(state), reactionsQueue: getReactionsQueue(state),
raisedHand: localParticipant?.raisedHand raisedHand: hasRaisedHand(localParticipant)
}; };
} }

View File

@ -11,6 +11,7 @@ import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons'; import { IconRaisedHand } from '../../../base/icons';
import { import {
getLocalParticipant, getLocalParticipant,
hasRaisedHand,
raiseHand raiseHand
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -97,7 +98,7 @@ function _mapStateToProps(state, ownProps): Object {
return { return {
_localParticipant, _localParticipant,
_raisedHand: _localParticipant.raisedHand, _raisedHand: hasRaisedHand(_localParticipant),
visible visible
}; };
} }

View File

@ -17,6 +17,7 @@ import { translate } from '../../../base/i18n';
import JitsiMeetJS from '../../../base/lib-jitsi-meet'; import JitsiMeetJS from '../../../base/lib-jitsi-meet';
import { import {
getLocalParticipant, getLocalParticipant,
hasRaisedHand,
haveParticipantWithScreenSharingFeature, haveParticipantWithScreenSharingFeature,
raiseHand raiseHand
} from '../../../base/participants'; } from '../../../base/participants';
@ -488,12 +489,9 @@ class Toolbox extends Component<Props> {
* @returns {void} * @returns {void}
*/ */
_doToggleRaiseHand() { _doToggleRaiseHand() {
const { _localParticipantID, _raisedHand } = this.props; const { _raisedHand } = this.props;
const newRaisedStatus = !_raisedHand;
this.props.dispatch(raiseHand(newRaisedStatus)); this.props.dispatch(raiseHand(!_raisedHand));
APP.API.notifyRaiseHandUpdated(_localParticipantID, newRaisedStatus);
} }
/** /**
@ -1338,7 +1336,7 @@ function _mapStateToProps(state, ownProps) {
_localVideo: localVideo, _localVideo: localVideo,
_overflowMenuVisible: overflowMenuVisible, _overflowMenuVisible: overflowMenuVisible,
_participantsPaneOpen: getParticipantsPaneOpen(state), _participantsPaneOpen: getParticipantsPaneOpen(state),
_raisedHand: localParticipant?.raisedHand, _raisedHand: hasRaisedHand(localParticipant),
_reactionsEnabled: isReactionsEnabled(state), _reactionsEnabled: isReactionsEnabled(state),
_screenSharing: isScreenVideoShared(state), _screenSharing: isScreenVideoShared(state),
_tileViewEnabled: shouldDisplayTileView(state), _tileViewEnabled: shouldDisplayTileView(state),