mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-18 17:57:47 +00:00
feat(reactions): New button for web.
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user