feat(reactions): New button for web.

This commit is contained in:
Hristo Terezov
2023-04-27 19:19:53 -05:00
parent be55ccd6f4
commit f8bd8b616e
20 changed files with 559 additions and 263 deletions

View File

@@ -1,14 +1,25 @@
import React, { ReactNode, useCallback } from 'react';
import { useSelector } from 'react-redux';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import Popover from '../../../base/popover/components/Popover.web';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
import { setGifMenuVisibility } from '../../../gifs/actions';
import { isGifsMenuOpen } from '../../../gifs/functions.web';
import ReactionEmoji from '../../../reactions/components/web/ReactionEmoji';
import ReactionsMenu from '../../../reactions/components/web/ReactionsMenu';
import { REACTIONS_MENU_HEIGHT } from '../../../reactions/constants';
import {
GIFS_MENU_HEIGHT_IN_OVERFLOW_MENU,
RAISE_HAND_ROW_HEIGHT,
REACTIONS_MENU_HEIGHT_DRAWER,
REACTIONS_MENU_HEIGHT_IN_OVERFLOW_MENU
} from '../../../reactions/constants';
import { getReactionsQueue } from '../../../reactions/functions.any';
import { IReactionsMenuParent } from '../../../reactions/types';
import { DRAWER_MAX_HEIGHT } from '../../constants';
import { showOverflowDrawer } from '../../functions.web';
@@ -27,48 +38,85 @@ interface IProps {
ariaControls: string;
/**
* A child React Element to display within {@code Popover}.
* Information about the buttons that need to be rendered in the overflow menu.
*/
children: ReactNode;
buttons: Object[];
/**
* Whether or not the OverflowMenu popover should display.
*/
isOpen: boolean;
/**
* Esc key handler.
*/
onToolboxEscKey: (e?: React.KeyboardEvent) => void;
/**
* Callback to change the visibility of the overflow menu.
*/
onVisibilityChange: Function;
/**
* Whether or not to display the reactions in the mobile menu.
* Whether to show the raise hand in the reactions menu or not.
*/
showMobileReactions: boolean;
showRaiseHandInReactionsMenu: boolean;
/**
* Whether or not to display the reactions menu.
*/
showReactionsMenu: boolean;
}
const useStyles = makeStyles()(() => {
const useStyles = makeStyles<{ overflowDrawer: boolean; reactionsMenuHeight: number; }>()(
(_theme, { reactionsMenuHeight, overflowDrawer }) => {
return {
overflowMenuDrawer: {
overflowY: 'auto' as const,
height: `calc(${DRAWER_MAX_HEIGHT} - ${REACTIONS_MENU_HEIGHT}px - 16px)`
overflow: 'hidden',
height: `calc(${DRAWER_MAX_HEIGHT} - ${reactionsMenuHeight}px - 16px)`
},
contextMenu: {
position: 'relative' as const,
right: 'auto',
margin: 0,
marginBottom: '8px',
maxHeight: overflowDrawer ? undefined : 'calc(100vh - 100px)',
paddingBottom: overflowDrawer ? undefined : 0,
minWidth: '240px',
overflow: 'hidden'
},
content: {
position: 'relative',
maxHeight: overflowDrawer
? `calc(100% - ${reactionsMenuHeight}px - 16px)` : `calc(100vh - 100px - ${reactionsMenuHeight}px)`,
overflowY: 'auto'
},
footer: {
position: 'relative',
bottom: 0
}
};
});
const OverflowMenuButton = ({
children,
buttons,
isOpen,
onToolboxEscKey,
onVisibilityChange,
showMobileReactions
showRaiseHandInReactionsMenu,
showReactionsMenu
}: IProps) => {
const { classes } = useStyles();
const overflowDrawer = useSelector(showOverflowDrawer);
const reactionsQueue = useSelector(getReactionsQueue);
const isGiphyVisible = useSelector(isGifsMenuOpen);
const dispatch = useDispatch();
const onCloseDialog = useCallback(() => {
onVisibilityChange(false);
}, [ onVisibilityChange ]);
if (isGiphyVisible && !overflowDrawer) {
dispatch(setGifMenuVisibility(false));
}
}, [ onVisibilityChange, setGifMenuVisibility, isGiphyVisible, overflowDrawer, dispatch ]);
const onOpenDialog = useCallback(() => {
onVisibilityChange(true);
@@ -88,50 +136,118 @@ const OverflowMenuButton = ({
onVisibilityChange(!isOpen);
}, [ isOpen, onVisibilityChange ]);
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
const { t } = useTranslation();
let reactionsMenuHeight = 0;
if (showReactionsMenu) {
reactionsMenuHeight = REACTIONS_MENU_HEIGHT_DRAWER;
if (!overflowDrawer) {
reactionsMenuHeight = REACTIONS_MENU_HEIGHT_IN_OVERFLOW_MENU;
}
if (!showRaiseHandInReactionsMenu) {
reactionsMenuHeight -= RAISE_HAND_ROW_HEIGHT;
}
if (!overflowDrawer && isGiphyVisible) {
reactionsMenuHeight += GIFS_MENU_HEIGHT_IN_OVERFLOW_MENU;
}
}
const { classes } = useStyles({
reactionsMenuHeight,
overflowDrawer
});
const groupsJSX = buttons.map((buttonGroup: any) => (
<ContextMenuItemGroup key = { `group-${buttonGroup[0].group}` }>
{buttonGroup.map(({ key, Content, ...rest }: { Content: React.ElementType; key: string; }) => {
const props: { buttonKey?: string; contextMenu?: boolean; showLabel?: boolean; } = { ...rest };
if (key !== 'reactions') {
props.buttonKey = key;
props.contextMenu = true;
props.showLabel = true;
}
return (
<Content
{ ...props }
key = { key } />);
})}
</ContextMenuItemGroup>));
const overflowMenu = groupsJSX && (
<ContextMenu
accessibilityLabel = { t(toolbarAccLabel) }
className = { classes.contextMenu }
hidden = { false }
id = 'overflow-context-menu'
inDrawer = { overflowDrawer }
onKeyDown = { onToolboxEscKey }>
<div className = { classes.content }>
{ groupsJSX }
</div>
{
showReactionsMenu && (<div className = { classes.footer }>
<ReactionsMenu
parent = {
overflowDrawer ? IReactionsMenuParent.OverflowDrawer : IReactionsMenuParent.OverflowMenu }
showRaisedHand = { showRaiseHandInReactionsMenu } />
</div>)
}
</ContextMenu>);
if (overflowDrawer) {
return (
<div className = 'toolbox-button-wth-dialog context-menu'>
<>
<OverflowToggleButton
handleClick = { toggleDialogVisibility }
isOpen = { isOpen }
onKeyDown = { onEscClick } />
<JitsiPortal>
<Drawer
isOpen = { isOpen }
onClose = { onCloseDialog }>
<>
<div className = { classes.overflowMenuDrawer }>
{ overflowMenu }
</div>
</>
</Drawer>
{showReactionsMenu && <div className = 'reactions-animations-container'>
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index }
key = { uid }
reaction = { reaction }
uid = { uid } />))}
</div>}
</JitsiPortal>
</>
</div>
);
}
return (
<div className = 'toolbox-button-wth-dialog context-menu'>
{
overflowDrawer ? (
<>
<OverflowToggleButton
handleClick = { toggleDialogVisibility }
isOpen = { isOpen }
onKeyDown = { onEscClick } />
<JitsiPortal>
<Drawer
isOpen = { isOpen }
onClose = { onCloseDialog }>
<>
<div className = { classes.overflowMenuDrawer }>
{children}
</div>
{showMobileReactions && <ReactionsMenu overflowMenu = { true } />}
</>
</Drawer>
{showMobileReactions && <div className = 'reactions-animations-container'>
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index }
key = { uid }
reaction = { reaction }
uid = { uid } />))}
</div>}
</JitsiPortal>
</>
) : (
<Popover
content = { children }
headingId = 'overflow-context-menu'
onPopoverClose = { onCloseDialog }
onPopoverOpen = { onOpenDialog }
position = 'top'
trigger = 'click'
visible = { isOpen }>
<OverflowToggleButton
isOpen = { isOpen }
onKeyDown = { onEscClick } />
</Popover>
)
}
<Popover
content = { overflowMenu }
headingId = 'overflow-context-menu'
onPopoverClose = { onCloseDialog }
onPopoverOpen = { onOpenDialog }
position = 'top'
trigger = 'click'
visible = { isOpen }>
<OverflowToggleButton
isOpen = { isOpen }
onKeyDown = { onEscClick } />
</Popover>
{showReactionsMenu && <div className = 'reactions-animations-container'>
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index }
key = { uid }
reaction = { reaction }
uid = { uid } />))}
</div>}
</div>
);
};

View File

@@ -30,7 +30,6 @@ import {
import { getLocalVideoTrack } from '../../../base/tracks/functions.web';
import { ITrack } from '../../../base/tracks/types';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
import { toggleChat } from '../../../chat/actions.web';
import ChatButton from '../../../chat/components/web/ChatButton';
import EmbedMeetingButton from '../../../embed-meeting/components/EmbedMeetingButton';
@@ -54,9 +53,10 @@ import {
addReactionToBuffer,
toggleReactionsMenuVisibility
} from '../../../reactions/actions.web';
import RaiseHandButton from '../../../reactions/components/web/RaiseHandButton';
import ReactionsMenuButton from '../../../reactions/components/web/ReactionsMenuButton';
import { REACTIONS } from '../../../reactions/constants';
import { isReactionsEnabled } from '../../../reactions/functions.web';
import { isReactionsButtonEnabled, isReactionsEnabled } from '../../../reactions/functions.web';
import LiveStreamButton from '../../../recording/components/LiveStream/web/LiveStreamButton';
import RecordButton from '../../../recording/components/Recording/web/RecordButton';
import { isSalesforceEnabled } from '../../../salesforce/functions';
@@ -273,6 +273,11 @@ interface IProps extends WithTranslation {
*/
_raisedHand: boolean;
/**
* Whether or not to display reactions in separate button.
*/
_reactionsButtonEnabled: boolean;
/**
* Whether or not reactions feature is enabled.
*/
@@ -717,6 +722,7 @@ class Toolbox extends Component<IProps> {
_isNarrowLayout,
_isSpeakerStatsDisabled,
_multiStreamModeEnabled,
_reactionsButtonEnabled,
_reactionsEnabled,
_screenSharing,
_shortcutsEnabled,
@@ -755,8 +761,21 @@ class Toolbox extends Component<IProps> {
group: 2
};
const raisehand = (!_reactionsEnabled || (!_isNarrowLayout && !_isMobile)) && {
// In Narrow layout and mobile web we are using drawer for popups and that is why it is better to include
// all forms of reactions in the overflow menu. Otherwise the toolbox will be hidden and the reactions popup
// misaligned.
const showReactionsAsPartOfRaiseHand
= !_reactionsButtonEnabled && _reactionsEnabled && !_isNarrowLayout && !_isMobile;
const raisehand = {
key: 'raisehand',
Content: showReactionsAsPartOfRaiseHand ? ReactionsMenuButton : RaiseHandButton,
handleClick: this._onToolbarToggleRaiseHand,
group: 2
};
const reactions = _reactionsButtonEnabled && _reactionsEnabled && {
key: 'reactions',
Content: ReactionsMenuButton,
handleClick: this._onToolbarToggleRaiseHand,
group: 2
@@ -931,6 +950,7 @@ class Toolbox extends Component<IProps> {
desktop,
chat,
raisehand,
reactions,
participants,
invite,
tileview,
@@ -1416,6 +1436,7 @@ class Toolbox extends Component<IProps> {
_overflowDrawer,
_overflowMenuVisible,
_reactionsEnabled,
_reactionsButtonEnabled,
_toolbarButtons,
classes,
t
@@ -1425,6 +1446,12 @@ class Toolbox extends Component<IProps> {
const containerClassName = `toolbox-content${_isMobile || _isNarrowLayout ? ' toolbox-content-mobile' : ''}`;
const { mainMenuButtons, overflowMenuButtons } = this._getVisibleButtons();
const raiseHandInOverflowMenu = overflowMenuButtons.some(({ key }) => key === 'raisehand');
const showReactionsInOverflowMenu
= (_reactionsEnabled && !_reactionsButtonEnabled
&& (raiseHandInOverflowMenu || _isNarrowLayout || _isMobile))
|| overflowMenuButtons.some(({ key }) => key === 'reactions');
const showRaiseHandInReactionsMenu = showReactionsInOverflowMenu && raiseHandInOverflowMenu;
return (
<div className = { containerClassName }>
@@ -1448,47 +1475,36 @@ class Toolbox extends Component<IProps> {
{Boolean(overflowMenuButtons.length) && (
<OverflowMenuButton
ariaControls = 'overflow-menu'
isOpen = { _overflowMenuVisible }
key = 'overflow-menu'
onVisibilityChange = { this._onSetOverflowVisible }
showMobileReactions = {
_reactionsEnabled && (_isMobile || _isNarrowLayout)
}>
<ContextMenu
accessibilityLabel = { t(toolbarAccLabel) }
className = { classes.contextMenu }
hidden = { false }
id = 'overflow-context-menu'
inDrawer = { _overflowDrawer }
onKeyDown = { this._onEscKey }>
{overflowMenuButtons.reduce((acc, val) => {
if (acc.length) {
const prev = acc[acc.length - 1];
const group = prev[prev.length - 1].group;
buttons = { overflowMenuButtons.reduce((acc, val) => {
if (val.key === 'reactions' && showReactionsInOverflowMenu) {
return acc;
}
if (group === val.group) {
prev.push(val);
} else {
acc.push([ val ]);
}
if (val.key === 'raisehand' && showRaiseHandInReactionsMenu) {
return acc;
}
if (acc.length) {
const prev = acc[acc.length - 1];
const group = prev[prev.length - 1].group;
if (group === val.group) {
prev.push(val);
} else {
acc.push([ val ]);
}
} else {
acc.push([ val ]);
}
return acc;
}, []).map((buttonGroup: any) => (
<ContextMenuItemGroup key = { `group-${buttonGroup[0].group}` }>
{buttonGroup.map(({ key, Content, ...rest }: any) => (
key !== 'raisehand' || !_reactionsEnabled)
&& <Content
{ ...rest }
buttonKey = { key }
contextMenu = { true }
key = { key }
showLabel = { true } />)}
</ContextMenuItemGroup>))}
</ContextMenu>
</OverflowMenuButton>
return acc;
}, []) }
isOpen = { _overflowMenuVisible }
key = 'overflow-menu'
onToolboxEscKey = { this._onEscKey }
onVisibilityChange = { this._onSetOverflowVisible }
showRaiseHandInReactionsMenu = { showRaiseHandInReactionsMenu }
showReactionsMenu = { showReactionsInOverflowMenu } />
)}
{ isToolbarButtonEnabled('hangup', _toolbarButtons) && (
@@ -1558,6 +1574,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
const { clientWidth } = state['features/base/responsive-ui'];
let toolbarButtons = ownProps.toolbarButtons || getToolbarButtons(state);
const _reactionsEnabled = isReactionsEnabled(state);
if (iAmVisitor(state)) {
toolbarButtons = VISITORS_MODE_BUTTONS.filter(e => toolbarButtons.indexOf(e) > -1);
@@ -1594,7 +1611,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_overflowDrawer: overflowDrawer,
_participantsPaneOpen: getParticipantsPaneOpen(state),
_raisedHand: hasRaisedHand(localParticipant),
_reactionsEnabled: isReactionsEnabled(state),
_reactionsButtonEnabled: isReactionsButtonEnabled(state),
_reactionsEnabled,
_screenSharing: isScreenVideoShared(state),
_shortcutsEnabled: areKeyboardShortcutsEnabled(state),
_tileViewEnabled: shouldDisplayTileView(state),