fix(reactions) Moved reactions behind feature flag

pull/9602/head jitsi-meet_6108
robertpin 3 years ago committed by GitHub
parent 2209394d09
commit 2d04f3852c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      config.js
  2. 10
      css/_reactions-menu.scss
  3. 6
      react/features/base/flags/constants.js
  4. 19
      react/features/toolbox/components/native/OverflowMenu.js
  5. 105
      react/features/toolbox/components/native/RaiseHandButton.js
  6. 21
      react/features/toolbox/components/native/Toolbox.js
  7. 83
      react/features/toolbox/components/web/RaiseHandButton.js
  8. 103
      react/features/toolbox/components/web/Toolbox.js

@ -70,6 +70,9 @@ var config = {
// callStatsThreshold: 5 // enable callstats for 5% of the users. // callStatsThreshold: 5 // enable callstats for 5% of the users.
}, },
// Enables reactions feature.
enableReactions: false,
// Disables ICE/UDP by filtering out local and remote UDP candidates in // Disables ICE/UDP by filtering out local and remote UDP candidates in
// signalling. // signalling.
// webrtcIceUdpDisable: false, // webrtcIceUdpDisable: false,

@ -90,7 +90,7 @@
width: 20%; width: 20%;
bottom: 0; bottom: 0;
left: 40%; left: 40%;
height: 48px; height: 0;
} }
.reactions-menu-popup-container, .reactions-menu-popup-container,
@ -111,8 +111,8 @@ $reactionCount: 20;
line-height: 32px; line-height: 32px;
width: 32px; width: 32px;
height: 32px; height: 32px;
top: 32px; top: 0;
left: 10px; left: 20px;
opacity: 0; opacity: 0;
z-index: 1; z-index: 1;
@ -123,8 +123,8 @@ $reactionCount: 20;
@for $i from 1 through $reactionCount { @for $i from 1 through $reactionCount {
&.reaction-#{$i} { &.reaction-#{$i} {
animation: animation-#{$i} 5s forwards ease-in-out; animation: animation-#{$i} 5s forwards ease-in-out;
top: #{random(50, 0)}px; top: #{random(-40, 10)}px;
left: #{random(-10, 10)}px; left: #{random(0, 30)}px;
} }
} }
} }

@ -214,3 +214,9 @@ export const VIDEO_SHARE_BUTTON_ENABLED = 'video-share.enabled';
* Default: disabled (false). * Default: disabled (false).
*/ */
export const WELCOME_PAGE_ENABLED = 'welcomepage.enabled'; export const WELCOME_PAGE_ENABLED = 'welcomepage.enabled';
/**
* Flag indicating if the reactions feature should be enabled.
* Default: disabled (false).
*/
export const REACTIONS_ENABLED = 'reactions.enabled';

@ -4,6 +4,7 @@ import React, { PureComponent } from 'react';
import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog'; import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
import { getFeatureFlag, REACTIONS_ENABLED } from '../../../base/flags';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles'; import { StyleType } from '../../../base/styles';
import { SharedDocumentButton } from '../../../etherpad'; import { SharedDocumentButton } from '../../../etherpad';
@ -22,6 +23,7 @@ import MuteEveryoneButton from '../MuteEveryoneButton';
import MuteEveryonesVideoButton from '../MuteEveryonesVideoButton'; import MuteEveryonesVideoButton from '../MuteEveryonesVideoButton';
import AudioOnlyButton from './AudioOnlyButton'; import AudioOnlyButton from './AudioOnlyButton';
import RaiseHandButton from './RaiseHandButton';
import ScreenSharingButton from './ScreenSharingButton.js'; import ScreenSharingButton from './ScreenSharingButton.js';
import ToggleCameraButton from './ToggleCameraButton'; import ToggleCameraButton from './ToggleCameraButton';
@ -50,6 +52,11 @@ type Props = {
*/ */
_width: number, _width: number,
/**
* Whether or not the reactions feature is enabled.
*/
_reactionsEnabled: boolean,
/** /**
* Used for hiding the dialog when the selection was completed. * Used for hiding the dialog when the selection was completed.
*/ */
@ -102,7 +109,7 @@ class OverflowMenu extends PureComponent<Props, State> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { _bottomSheetStyles, _width } = this.props; const { _bottomSheetStyles, _width, _reactionsEnabled } = this.props;
const toolbarButtons = getMovableButtons(_width); const toolbarButtons = getMovableButtons(_width);
const buttonProps = { const buttonProps = {
@ -128,13 +135,14 @@ class OverflowMenu extends PureComponent<Props, State> {
return ( return (
<BottomSheet <BottomSheet
onCancel = { this._onCancel } onCancel = { this._onCancel }
renderFooter = { toolbarButtons.has('raisehand') renderFooter = { _reactionsEnabled && !toolbarButtons.has('raisehand')
? null ? this._renderReactionMenu
: this._renderReactionMenu }> : null }>
<AudioRouteButton { ...topButtonProps } /> <AudioRouteButton { ...topButtonProps } />
<ParticipantsPaneButton { ...buttonProps } /> <ParticipantsPaneButton { ...buttonProps } />
{!toolbarButtons.has('invite') && <InviteButton { ...buttonProps } />} {!toolbarButtons.has('invite') && <InviteButton { ...buttonProps } />}
<AudioOnlyButton { ...buttonProps } /> <AudioOnlyButton { ...buttonProps } />
{!_reactionsEnabled && !toolbarButtons.has('raisehand') && <RaiseHandButton { ...buttonProps } />}
<SecurityDialogButton { ...buttonProps } /> <SecurityDialogButton { ...buttonProps } />
<ScreenSharingButton { ...buttonProps } /> <ScreenSharingButton { ...buttonProps } />
{!toolbarButtons.has('togglecamera') && <ToggleCameraButton { ...buttonProps } />} {!toolbarButtons.has('togglecamera') && <ToggleCameraButton { ...buttonProps } />}
@ -194,7 +202,8 @@ function _mapStateToProps(state) {
return { return {
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'), _bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
_isOpen: isDialogOpen(state, OverflowMenu_), _isOpen: isDialogOpen(state, OverflowMenu_),
_width: state['features/base/responsive-ui'].clientWidth _width: state['features/base/responsive-ui'].clientWidth,
_reactionsEnabled: getFeatureFlag(state, REACTIONS_ENABLED, false)
}; };
} }

@ -0,0 +1,105 @@
// @flow
import { type Dispatch } from 'redux';
import {
createToolbarEvent,
sendAnalytics
} from '../../../analytics';
import { RAISE_HAND_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons';
import {
getLocalParticipant,
raiseHand
} from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
/**
* The type of the React {@code Component} props of {@link RaiseHandButton}.
*/
type Props = AbstractButtonProps & {
/**
* The local participant.
*/
_localParticipant: Object,
/**
* Whether the participant raused their hand or not.
*/
_raisedHand: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Dispatch<any>
};
/**
* An implementation of a button to raise or lower hand.
*/
class RaiseHandButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
icon = IconRaisedHand;
label = 'toolbar.raiseYourHand';
toggledLabel = 'toolbar.lowerYourHand';
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_handleClick() {
this._toggleRaisedHand();
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._raisedHand;
}
/**
* Toggles the rased hand status of the local participant.
*
* @returns {void}
*/
_toggleRaisedHand() {
const enable = !this.props._raisedHand;
sendAnalytics(createToolbarEvent('raise.hand', { enable }));
this.props.dispatch(raiseHand(enable));
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @private
* @returns {Props}
*/
function _mapStateToProps(state, ownProps): Object {
const _localParticipant = getLocalParticipant(state);
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);
const { visible = enabled } = ownProps;
return {
_localParticipant,
_raisedHand: _localParticipant.raisedHand,
visible
};
}
export default translate(connect(_mapStateToProps)(RaiseHandButton));

@ -4,6 +4,7 @@ import React from 'react';
import { SafeAreaView, View } from 'react-native'; import { SafeAreaView, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { getFeatureFlag, REACTIONS_ENABLED } from '../../../base/flags';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles'; import { StyleType } from '../../../base/styles';
import { ChatButton } from '../../../chat'; import { ChatButton } from '../../../chat';
@ -16,6 +17,7 @@ import HangupButton from '../HangupButton';
import VideoMuteButton from '../VideoMuteButton'; import VideoMuteButton from '../VideoMuteButton';
import OverflowMenuButton from './OverflowMenuButton'; import OverflowMenuButton from './OverflowMenuButton';
import RaiseHandButton from './RaiseHandButton';
import ToggleCameraButton from './ToggleCameraButton'; import ToggleCameraButton from './ToggleCameraButton';
import styles from './styles'; import styles from './styles';
@ -39,6 +41,11 @@ type Props = {
*/ */
_width: number, _width: number,
/**
* Whether or not the reactions feature is enabled.
*/
_reactionsEnabled: boolean,
/** /**
* The redux {@code dispatch} function. * The redux {@code dispatch} function.
*/ */
@ -56,7 +63,7 @@ function Toolbox(props: Props) {
return null; return null;
} }
const { _styles, _width } = props; const { _styles, _width, _reactionsEnabled } = props;
const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles; const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles;
const additionalButtons = getMovableButtons(_width); const additionalButtons = getMovableButtons(_width);
const backgroundToggledStyle = { const backgroundToggledStyle = {
@ -86,10 +93,13 @@ function Toolbox(props: Props) {
styles = { buttonStylesBorderless } styles = { buttonStylesBorderless }
toggledStyles = { backgroundToggledStyle } />} toggledStyles = { backgroundToggledStyle } />}
{ additionalButtons.has('raisehand') { additionalButtons.has('raisehand') && (_reactionsEnabled
&& <ReactionsMenuButton ? <ReactionsMenuButton
styles = { buttonStylesBorderless }
toggledStyles = { backgroundToggledStyle } />
: <RaiseHandButton
styles = { buttonStylesBorderless } styles = { buttonStylesBorderless }
toggledStyles = { backgroundToggledStyle } />} toggledStyles = { backgroundToggledStyle } />)}
{additionalButtons.has('tileview') && <TileViewButton styles = { buttonStylesBorderless } />} {additionalButtons.has('tileview') && <TileViewButton styles = { buttonStylesBorderless } />}
{additionalButtons.has('invite') && <InviteButton styles = { buttonStylesBorderless } />} {additionalButtons.has('invite') && <InviteButton styles = { buttonStylesBorderless } />}
{additionalButtons.has('togglecamera') {additionalButtons.has('togglecamera')
@ -119,7 +129,8 @@ function _mapStateToProps(state: Object): Object {
return { return {
_styles: ColorSchemeRegistry.get(state, 'Toolbox'), _styles: ColorSchemeRegistry.get(state, 'Toolbox'),
_visible: isToolboxVisible(state), _visible: isToolboxVisible(state),
_width: state['features/base/responsive-ui'].clientWidth _width: state['features/base/responsive-ui'].clientWidth,
_reactionsEnabled: getFeatureFlag(state, REACTIONS_ENABLED, false)
}; };
} }

@ -0,0 +1,83 @@
// @flow
import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons';
import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
type Props = AbstractButtonProps & {
/**
* Whether or not the local participant's hand is raised.
*/
_raisedHand: boolean,
/**
* External handler for click action.
*/
handleClick: Function
};
/**
* Implementation of a button for toggling raise hand functionality.
*/
class RaiseHandButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
icon = IconRaisedHand
label = 'toolbar.raiseYourHand';
toggledLabel = 'toolbar.lowerYourHand'
/**
* Retrieves tooltip dynamically.
*/
get tooltip() {
return this.props._raisedHand ? 'toolbar.lowerYourHand' : 'toolbar.raiseYourHand';
}
/**
* Required by linter due to AbstractButton overwritten prop being writable.
*
* @param {string} value - The value.
*/
set tooltip(value) {
return value;
}
/**
* Handles clicking / pressing the button, and opens the appropriate dialog.
*
* @protected
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._raisedHand;
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @returns {Object}
*/
const mapStateToProps = state => {
const localParticipant = getLocalParticipant(state);
return {
_raisedHand: localParticipant.raisedHand
};
};
export default translate(connect(mapStateToProps)(RaiseHandButton));

@ -85,6 +85,7 @@ import AudioSettingsButton from './AudioSettingsButton';
import FullscreenButton from './FullscreenButton'; import FullscreenButton from './FullscreenButton';
import OverflowMenuButton from './OverflowMenuButton'; import OverflowMenuButton from './OverflowMenuButton';
import ProfileButton from './ProfileButton'; import ProfileButton from './ProfileButton';
import RaiseHandButton from './RaiseHandButton';
import Separator from './Separator'; import Separator from './Separator';
import ShareDesktopButton from './ShareDesktopButton'; import ShareDesktopButton from './ShareDesktopButton';
import VideoSettingsButton from './VideoSettingsButton'; import VideoSettingsButton from './VideoSettingsButton';
@ -213,7 +214,12 @@ type Props = {
/** /**
* Returns the selected virtual source object. * Returns the selected virtual source object.
*/ */
_virtualSource: Object, _virtualSource: Object,
/**
* Whether or not reactions feature is enabled.
*/
_reactionsEnabled: boolean,
/** /**
* Invoked to active other features of the app. * Invoked to active other features of the app.
@ -259,6 +265,7 @@ class Toolbox extends Component<Props> {
this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this); this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this);
this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this); this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this); this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this);
this._onToolbarToggleRaiseHand = this._onToolbarToggleRaiseHand.bind(this);
this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this); this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this); this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this);
this._onEscKey = this._onEscKey.bind(this); this._onEscKey = this._onEscKey.bind(this);
@ -271,7 +278,7 @@ class Toolbox extends Component<Props> {
* @returns {void} * @returns {void}
*/ */
componentDidMount() { componentDidMount() {
const { _toolbarButtons, t, dispatch } = this.props; const { _toolbarButtons, t, dispatch, _reactionsEnabled } = this.props;
const KEYBOARD_SHORTCUTS = [ const KEYBOARD_SHORTCUTS = [
isToolbarButtonEnabled('videoquality', _toolbarButtons) && { isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
character: 'A', character: 'A',
@ -320,30 +327,32 @@ class Toolbox extends Component<Props> {
} }
}); });
const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => { if (_reactionsEnabled) {
const onShortcutSendReaction = () => { const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
dispatch(addReactionToBuffer(key)); const onShortcutSendReaction = () => {
sendAnalytics(createShortcutEvent( dispatch(addReactionToBuffer(key));
`reaction.${key}` sendAnalytics(createShortcutEvent(
)); `reaction.${key}`
}; ));
};
return {
character: REACTIONS[key].shortcutChar, return {
exec: onShortcutSendReaction, character: REACTIONS[key].shortcutChar,
helpDescription: t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`), exec: onShortcutSendReaction,
altKey: true helpDescription: t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`),
}; altKey: true
}); };
});
REACTION_SHORTCUTS.forEach(shortcut => {
APP.keyboardshortcut.registerShortcut( REACTION_SHORTCUTS.forEach(shortcut => {
shortcut.character, APP.keyboardshortcut.registerShortcut(
null, shortcut.character,
shortcut.exec, null,
shortcut.helpDescription, shortcut.exec,
shortcut.altKey); shortcut.helpDescription,
}); shortcut.altKey);
});
}
} }
/** /**
@ -375,9 +384,11 @@ class Toolbox extends Component<Props> {
[ 'A', 'C', 'D', 'R', 'S' ].forEach(letter => [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter)); APP.keyboardshortcut.unregisterShortcut(letter));
Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar) if (this.props._reactionsEnabled) {
.forEach(letter => Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
APP.keyboardshortcut.unregisterShortcut(letter, true)); .forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter, true));
}
} }
/** /**
@ -541,7 +552,8 @@ class Toolbox extends Component<Props> {
const { const {
_feedbackConfigured, _feedbackConfigured,
_isMobile, _isMobile,
_screenSharing _screenSharing,
_reactionsEnabled
} = this.props; } = this.props;
const microphone = { const microphone = {
@ -578,7 +590,8 @@ class Toolbox extends Component<Props> {
const raisehand = { const raisehand = {
key: 'raisehand', key: 'raisehand',
Content: ReactionsMenuButton, Content: _reactionsEnabled ? ReactionsMenuButton : RaiseHandButton,
handleClick: _reactionsEnabled ? null : this._onToolbarToggleRaiseHand,
group: 2 group: 2
}; };
@ -1054,6 +1067,23 @@ class Toolbox extends Component<Props> {
this._doToggleFullScreen(); this._doToggleFullScreen();
} }
_onToolbarToggleRaiseHand: () => void;
/**
* Creates an analytics toolbar event and dispatches an action for toggling
* raise hand.
*
* @private
* @returns {void}
*/
_onToolbarToggleRaiseHand() {
sendAnalytics(createToolbarEvent(
'raise.hand',
{ enable: !this.props._raisedHand }));
this._doToggleRaiseHand();
}
_onToolbarToggleScreenshare: () => void; _onToolbarToggleScreenshare: () => void;
/** /**
@ -1131,7 +1161,8 @@ class Toolbox extends Component<Props> {
_isMobile, _isMobile,
_overflowMenuVisible, _overflowMenuVisible,
_toolbarButtons, _toolbarButtons,
t t,
_reactionsEnabled
} = this.props; } = this.props;
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu'; const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
@ -1160,7 +1191,7 @@ class Toolbox extends Component<Props> {
key = 'overflow-menu' key = 'overflow-menu'
onVisibilityChange = { this._onSetOverflowVisible } onVisibilityChange = { this._onSetOverflowVisible }
showMobileReactions = { showMobileReactions = {
overflowMenuButtons.find(({ key }) => key === 'raisehand') _reactionsEnabled && overflowMenuButtons.find(({ key }) => key === 'raisehand')
}> }>
<ul <ul
aria-label = { t(toolbarAccLabel) } aria-label = { t(toolbarAccLabel) }
@ -1171,7 +1202,7 @@ class Toolbox extends Component<Props> {
{overflowMenuButtons.map(({ group, key, Content, ...rest }, index, arr) => { {overflowMenuButtons.map(({ group, key, Content, ...rest }, index, arr) => {
const showSeparator = index > 0 && arr[index - 1].group !== group; const showSeparator = index > 0 && arr[index - 1].group !== group;
return key !== 'raisehand' return (key !== 'raisehand' || !_reactionsEnabled)
&& <> && <>
{showSeparator && <Separator key = { `hr${group}` } />} {showSeparator && <Separator key = { `hr${group}` } />}
<Content <Content
@ -1218,6 +1249,7 @@ function _mapStateToProps(state) {
const localParticipant = getLocalParticipant(state); const localParticipant = getLocalParticipant(state);
const localVideo = getLocalVideoTrack(state['features/base/tracks']); const localVideo = getLocalVideoTrack(state['features/base/tracks']);
const { clientWidth } = state['features/base/responsive-ui']; const { clientWidth } = state['features/base/responsive-ui'];
const { enableReactions } = state['features/base/config'];
let desktopSharingDisabledTooltipKey; let desktopSharingDisabledTooltipKey;
@ -1253,7 +1285,8 @@ function _mapStateToProps(state) {
_screenSharing: isScreenVideoShared(state), _screenSharing: isScreenVideoShared(state),
_toolbarButtons: getToolbarButtons(state), _toolbarButtons: getToolbarButtons(state),
_visible: isToolboxVisible(state), _visible: isToolboxVisible(state),
_visibleButtons: getToolbarButtons(state) _visibleButtons: getToolbarButtons(state),
_reactionsEnabled: enableReactions
}; };
} }

Loading…
Cancel
Save