Files
jitsi-meet/react/features/reactions/components/web/ReactionsMenu.js
Robert Pintilii 190041fc5a feat(gif) Added GIF support (GIPHY integration) (#11021)
Show GIF menu in reactions menu
Search GIFs using the GIPHY API
Show GIFs as images in chat
Show GIFs on the thumbnail of the participant that sent it
Move GIF focus using up/ down arrows and send with Enter
Added analytics
2022-03-11 15:00:49 +02:00

277 lines
7.3 KiB
JavaScript

// @flow
/* eslint-disable react/jsx-no-bind */
import { withStyles } from '@material-ui/styles';
import clsx from 'clsx';
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import {
createReactionMenuEvent,
createToolbarEvent,
sendAnalytics
} from '../../../analytics';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n';
import { getLocalParticipant, hasRaisedHand, raiseHand } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { GifsMenu, GifsMenuButton } from '../../../gifs/components';
import { isGifEnabled, isGifsMenuOpen } from '../../../gifs/functions';
import { dockToolbox } from '../../../toolbox/actions.web';
import { addReactionToBuffer } from '../../actions.any';
import { toggleReactionsMenuVisibility } from '../../actions.web';
import { REACTIONS, REACTIONS_MENU_HEIGHT } from '../../constants';
import ReactionButton from './ReactionButton';
type Props = {
/**
* Docks the toolbox.
*/
_dockToolbox: Function,
/**
* Whether or not the GIF feature is enabled.
*/
_isGifEnabled: boolean,
/**
* Whether or not the GIF menu is visible.
*/
_isGifMenuVisible: boolean,
/**
* Whether or not it's a mobile browser.
*/
_isMobile: boolean,
/**
* The ID of the local participant.
*/
_localParticipantID: String,
/**
* Whether or not the local participant's hand is raised.
*/
_raisedHand: boolean,
/**
* An object containing the CSS classes.
*/
classes: Object,
/**
* The Redux Dispatch function.
*/
dispatch: Function,
/**
* Whether or not it's displayed in the overflow menu.
*/
overflowMenu: boolean,
/**
* Used for translation.
*/
t: Function
};
declare var APP: Object;
const styles = theme => {
return {
overflow: {
width: 'auto',
paddingBottom: 'max(env(safe-area-inset-bottom, 0), 16px)',
backgroundColor: theme.palette.ui01,
boxShadow: 'none',
borderRadius: 0,
position: 'relative',
boxSizing: 'border-box',
height: `${REACTIONS_MENU_HEIGHT}px`
}
};
};
/**
* Implements the reactions menu.
*
* @returns {ReactElement}
*/
class ReactionsMenu extends Component<Props> {
/**
* Initializes a new {@code ReactionsMenu} instance.
*
* @param {Props} props - The read-only React {@code Component} props with
* which the new instance is to be initialized.
*/
constructor(props: Props) {
super(props);
this._onToolbarToggleRaiseHand = this._onToolbarToggleRaiseHand.bind(this);
this._getReactionButtons = this._getReactionButtons.bind(this);
}
_onToolbarToggleRaiseHand: () => void;
_getReactionButtons: () => Array<React$Element<*>>;
/**
* Implements React Component's componentDidMount.
*
* @inheritdoc
*/
componentDidMount() {
this.props._dockToolbox(true);
}
/**
* Implements React Component's componentWillUnmount.
*
* @inheritdoc
*/
componentWillUnmount() {
this.props._dockToolbox(false);
}
/**
* Creates an analytics toolbar event and dispatches an action for toggling
* raise hand.
*
* @returns {void}
*/
_onToolbarToggleRaiseHand() {
const { dispatch, _raisedHand } = this.props;
sendAnalytics(createToolbarEvent(
'raise.hand',
{ enable: !_raisedHand }));
this._doToggleRaiseHand();
dispatch(toggleReactionsMenuVisibility());
}
/**
* Dispatches an action to toggle the local participant's raised hand state.
*
* @private
* @returns {void}
*/
_doToggleRaiseHand() {
const { _raisedHand } = this.props;
this.props.dispatch(raiseHand(!_raisedHand));
}
/**
* Returns the emoji reaction buttons.
*
* @returns {Array}
*/
_getReactionButtons() {
const { t, dispatch } = this.props;
let modifierKey = 'Alt';
if (window.navigator?.platform) {
if (window.navigator.platform.indexOf('Mac') !== -1) {
modifierKey = '⌥';
}
}
return Object.keys(REACTIONS).map(key => {
/**
* Sends reaction message.
*
* @returns {void}
*/
function doSendReaction() {
dispatch(addReactionToBuffer(key));
sendAnalytics(createReactionMenuEvent(key));
}
return (<ReactionButton
accessibilityLabel = { t(`toolbar.accessibilityLabel.${key}`) }
icon = { REACTIONS[key].emoji }
key = { key }
onClick = { doSendReaction }
toggled = { false }
tooltip = { `${t(`toolbar.${key}`)} (${modifierKey} + ${REACTIONS[key].shortcutChar})` } />);
});
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
render() {
const { _raisedHand, t, overflowMenu, _isMobile, classes, _isGifMenuVisible, _isGifEnabled } = this.props;
return (
<div
className = { clsx('reactions-menu', _isGifEnabled && 'with-gif',
overflowMenu && `overflow ${classes.overflow}`) }>
{_isGifEnabled && _isGifMenuVisible && <GifsMenu />}
<div className = 'reactions-row'>
{ this._getReactionButtons() }
{_isGifEnabled && <GifsMenuButton />}
</div>
{_isMobile && (
<div className = 'raise-hand-row'>
<ReactionButton
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
icon = '✋'
key = 'raisehand'
label = {
`${t(`toolbar.${_raisedHand ? 'lowerYourHand' : 'raiseYourHand'}`)}
${overflowMenu ? '' : ' (R)'}`
}
onClick = { this._onToolbarToggleRaiseHand }
toggled = { true } />
</div>
)}
</div>
);
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @returns {Object}
*/
function mapStateToProps(state) {
const localParticipant = getLocalParticipant(state);
return {
_localParticipantID: localParticipant.id,
_isMobile: isMobileBrowser(),
_isGifEnabled: isGifEnabled(state),
_isGifMenuVisible: isGifsMenuOpen(state),
_raisedHand: hasRaisedHand(localParticipant)
};
}
/**
* Function that maps parts of Redux actions into component props.
*
* @param {Object} dispatch - Redux dispatch.
* @returns {Object}
*/
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators(
{
_dockToolbox: dockToolbox
}, dispatch)
};
}
export default translate(connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(ReactionsMenu)));