Compare commits

...

1 Commits

Author SHA1 Message Date
Hristo Terezov
4605a72031 fix(reactions): new toolbar button 2023-04-06 11:54:30 -05:00
21 changed files with 501 additions and 161 deletions

View File

@@ -1,5 +1,24 @@
@use 'sass:math';
#overflow-context-menu {
.reactions-menu {
&.with-gif {
width: inherit;
}
.toolbox-icon {
width: 24px;
height: 24px;
span.emoji {
width: 24px;
height: 24px;
line-height: 24px;
font-size: 16px;
}
}
}
}
.reactions-menu {
width: 280px;
background: $menuBG;

View File

@@ -36,6 +36,7 @@ export const TOOLBAR_BUTTONS = [
'participants-pane',
'profile',
'raisehand',
'reactions',
'recording',
'security',
'select-background',

View File

@@ -299,7 +299,8 @@ class Popover extends Component<IProps, IState> {
&& !this.props.overflowDrawer
&& this._contextMenuRef
&& this._contextMenuRef.contains // @ts-ignore
&& !this._contextMenuRef.contains(event.target)) {
&& !this._contextMenuRef.contains(event.target)
&& !this._containerRef?.current?.contains(event.target)) {
this._onHideDialog();
}
}

View File

@@ -35,17 +35,17 @@ type Props = {
/**
* Icon of the button.
*/
icon: Function,
icon?: Function,
/**
* Flag used for disabling the small icon.
*/
iconDisabled: boolean,
iconDisabled?: boolean,
/**
* The ID of the icon button.
*/
iconId: string,
iconId?: string,
/**
* Popover close callback.
@@ -88,6 +88,7 @@ export default function ToolboxButtonWithIconPopup(props: Props) {
ariaHasPopup,
ariaLabel,
children,
disableTouchEvents,
icon,
iconDisabled,
iconId,
@@ -95,9 +96,30 @@ export default function ToolboxButtonWithIconPopup(props: Props) {
onPopoverOpen,
popoverContent,
styles,
trigger,
visible
} = props;
if (!icon) {
return (
<div
className = 'settings-button-container'
styles = { styles }>
<Popover
content = { popoverContent }
disableTouchEvents = { disableTouchEvents }
headingLabel = { ariaLabel }
onPopoverClose = { onPopoverClose }
onPopoverOpen = { onPopoverOpen }
position = 'top'
trigger = { trigger }
visible = { visible }>
{children}
</Popover>
</div>
);
}
const iconProps = {};
if (iconDisabled) {

View File

@@ -157,6 +157,14 @@ const useStyles = makeStyles()(theme => {
fill: theme.palette.icon01
}
}
},
content: {
position: 'relative'
},
footer: {
position: 'relative',
bottom: 0
}
};
});
@@ -166,6 +174,7 @@ const ContextMenu = ({
children,
className,
entity,
// footer,
hidden,
id,
inDrawer,
@@ -232,7 +241,12 @@ const ContextMenu = ({
return (<div
className = { styles.drawer }
onClick = { onDrawerClose }>
{children}
{/* <div className = { styles.content }> */}
{children}
{/* </div>
<div className = { styles.footer }>
{footer}
</div> */}
</div>);
}
@@ -244,7 +258,12 @@ const ContextMenu = ({
<div
className = { styles.drawer }
onClick = { onDrawerClose }>
{children}
{/* <div className = { styles.content }> */}
{children}
{/* </div> */}
{/* <div className = { styles.footer }>
{footer}
</div> */}
</div>
</Drawer>
</JitsiPortal>
@@ -264,7 +283,12 @@ const ContextMenu = ({
ref = { containerRef }
role = { role }
tabIndex = { tabIndex }>
{children}
{/* <div className = { styles.content }> */}
{children}
{/* </div> */}
{/* <div className = { styles.footer }>
{footer}
</div> */}
</div>;
};

View File

@@ -37,6 +37,16 @@ export const commonStyles = (theme: Theme) => {
}
},
// '.not-drawer': {
// '.toolbox-button': {
// 'span': {
// lineHeight: '24px',
// width: '24px',
// height: '24px'
// }
// }
// },
'.overflow-menu-item': {
alignItems: 'center',
color: theme.palette.text01,

View File

@@ -27,6 +27,15 @@ export const HIDE_GIF_FOR_PARTICIPANT = 'HIDE_GIF_FOR_PARTICIPANT';
*/
export const REMOVE_GIF_FOR_PARTICIPANT = 'REMOVE_GIF_FOR_PARTICIPANT';
/**
* Set gif overflow menu visibility.
* {{
* type: SET_GIF_DRAWER_VISIBILITY,
* visible: boolean
* }}
*/
export const SET_GIF_OVERFLOW_MENU_VISIBILITY = 'SET_GIF_OVERFLOW_MENU_VISIBILITY';
/**
* Set gif menu drawer visibility.
* {{

View File

@@ -4,6 +4,7 @@ import {
REMOVE_GIF_FOR_PARTICIPANT,
SET_GIF_DRAWER_VISIBILITY,
SET_GIF_MENU_VISIBILITY,
SET_GIF_OVERFLOW_MENU_VISIBILITY,
SHOW_GIF_FOR_PARTICIPANT
} from './actionTypes';
@@ -61,6 +62,19 @@ export function hideGif(participantId: string) {
};
}
/**
* Set visibility of the GIF drawer.
*
* @param {boolean} visible - Whether or not it should be visible.
* @returns {Object}
*/
export function setGifOverflowMenuVisibility(visible: boolean) {
return {
type: SET_GIF_OVERFLOW_MENU_VISIBILITY,
visible
};
}
/**
* Set visibility of the GIF drawer.
*

View File

@@ -15,7 +15,7 @@ import { toggleReactionsMenuVisibility } from '../../../reactions/actions.web';
import { setOverflowMenuVisible } from '../../../toolbox/actions.web';
import Drawer from '../../../toolbox/components/web/Drawer';
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { showOverflowDrawer, showOverflowMenu } from '../../../toolbox/functions.web';
import { setGifDrawerVisibility } from '../../actions';
import {
formatGifUrlMessage,
@@ -60,16 +60,23 @@ const useStyles = makeStyles()(theme => {
marginTop: theme.spacing(1)
},
overflowMenu: {
overflowDrawerMenu: {
padding: theme.spacing(3),
width: '100%',
boxSizing: 'border-box'
boxSizing: 'border-box',
height: '100%'
},
gifContainerOverflow: {
flexGrow: 1
},
overflowMenu: {
height: '200px',
width: '201px',
marginBottom: '0px'
},
drawer: {
display: 'flex',
height: '100%'
@@ -77,12 +84,17 @@ const useStyles = makeStyles()(theme => {
};
});
interface IProps {
columns?: number;
}
/**
* Gifs menu.
*
* @param {Props} props - The props of the component.
* @returns {ReactElement}
*/
function GifsMenu() {
function GifsMenu({ columns = 2 }: IProps) {
const API_KEY = useSelector(getGifAPIKey);
const giphyFetch = new GiphyFetch(API_KEY);
const [ searchKey, setSearchKey ] = useState<string>();
@@ -90,6 +102,7 @@ function GifsMenu() {
const dispatch = useDispatch();
const { t } = useTranslation();
const overflowDrawer: boolean = useSelector(showOverflowDrawer);
const overflowMenu: boolean = useSelector(showOverflowMenu);
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const rating = useSelector(getGifRating);
const proxyUrl = useSelector(getGiphyProxyUrl);
@@ -191,7 +204,8 @@ function GifsMenu() {
const gifMenu = (
<div
className = { cx(styles.gifsMenu,
overflowDrawer && styles.overflowMenu
overflowDrawer && styles.overflowDrawerMenu,
overflowMenu && !overflowDrawer && styles.overflowMenu
) }>
<Input
autoFocus = { true }
@@ -210,7 +224,7 @@ function GifsMenu() {
className = { cx(styles.gifContainer,
overflowDrawer && styles.gifContainerOverflow) }>
<Grid
columns = { 2 }
columns = { columns }
fetchGifs = { fetchGifs }
gutter = { 6 }
hideAttribution = { true }
@@ -221,7 +235,7 @@ function GifsMenu() {
onGifKeyPress = { handleGifKeyPress }
width = { overflowDrawer
? clientWidth - (2 * OVERFLOW_DRAWER_PADDING) - SCROLL_SIZE
: 320
: overflowMenu ? 201 : 320
} />
</div>
<div className = { styles.logoContainer }>

View File

@@ -4,10 +4,18 @@ import { useDispatch, useSelector } from 'react-redux';
import ReactionButton from '../../../reactions/components/web/ReactionButton';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { setGifDrawerVisibility, setGifMenuVisibility } from '../../actions';
import { setGifDrawerVisibility, setGifMenuVisibility, setGifOverflowMenuVisibility } from '../../actions';
import { isGifsMenuOpen } from '../../functions';
const GifsMenuButton = () => {
interface Props {
overflowMenu?: boolean;
}
/**
*
* @returns
*/
function GifsMenuButton({ overflowMenu = false }: Props) {
const menuOpen = useSelector(isGifsMenuOpen);
const overflowDrawer = useSelector(showOverflowDrawer);
const { t } = useTranslation();
@@ -16,17 +24,18 @@ const GifsMenuButton = () => {
const icon = (
<img
alt = 'GIPHY Logo'
height = { 24 }
height = { overflowMenu && !overflowDrawer ? 16 : 24 }
src = 'images/GIPHY_icon.png' />
);
const handleClick = useCallback(() =>
const handleClick = useCallback(() => {
dispatch(
overflowDrawer
? setGifDrawerVisibility(!menuOpen)
: setGifMenuVisibility(!menuOpen)
)
, [ menuOpen, overflowDrawer ]);
overflowMenu && !overflowDrawer ? setGifOverflowMenuVisibility(!menuOpen)
: overflowDrawer
? setGifDrawerVisibility(!menuOpen)
: setGifMenuVisibility(!menuOpen)
);
}, [ menuOpen, overflowDrawer ]);
return (
<ReactionButton
@@ -37,6 +46,6 @@ const GifsMenuButton = () => {
toggled = { true }
tooltip = { t('toolbar.accessibilityLabel.giphy') } />
);
};
}
export default GifsMenuButton;

View File

@@ -11,7 +11,12 @@ export * from './function.any';
*/
export function isGifsMenuOpen(state: IReduxState) {
const overflowDrawer = showOverflowDrawer(state);
const { drawerVisible, menuOpen } = state['features/gifs'];
const { overflowMenuVisible } = state['features/toolbox'];
const { overflowVisible, drawerVisible, menuOpen } = state['features/gifs'];
if (overflowMenuVisible && !overflowDrawer) {
return overflowVisible;
}
return overflowDrawer ? drawerVisible : menuOpen;
}

View File

@@ -5,13 +5,15 @@ import {
HIDE_GIF_FOR_PARTICIPANT,
REMOVE_GIF_FOR_PARTICIPANT,
SET_GIF_DRAWER_VISIBILITY,
SET_GIF_MENU_VISIBILITY
SET_GIF_MENU_VISIBILITY,
SET_GIF_OVERFLOW_MENU_VISIBILITY
} from './actionTypes';
const initialState = {
drawerVisible: false,
gifList: new Map(),
menuOpen: false
menuOpen: false,
overflowVisible: false
};
export interface IGif {
@@ -23,6 +25,7 @@ export interface IGifsState {
drawerVisible: boolean;
gifList: Map<string, IGif>;
menuOpen: boolean;
overflowVisible: boolean;
}
ReducerRegistry.register<IGifsState>(
@@ -66,6 +69,11 @@ ReducerRegistry.register<IGifsState>(
gifList: newList
};
}
case SET_GIF_OVERFLOW_MENU_VISIBILITY:
return {
...state,
overflowVisible: action.visible
};
case SET_GIF_DRAWER_VISIBILITY:
return {
...state,

View File

@@ -107,7 +107,9 @@ class ReactionButton extends AbstractToolbarButton<Props, State> {
*
* @returns {void}
*/
_onClickHandler() {
_onClickHandler(event) {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
clearTimeout(this.state.increaseTimeout);
const timeout = setTimeout(() => {

View File

@@ -8,7 +8,6 @@ import { connect } from 'react-redux';
import { createReactionMenuEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import { raiseHand } from '../../../base/participants/actions';
import { getLocalParticipant, hasRaisedHand } from '../../../base/participants/functions';
@@ -20,7 +19,7 @@ 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 { RAISE_HAND_ROW_HEIGHT, REACTIONS, REACTIONS_MENU_HEIGHT } from '../../constants';
// @ts-ignore
import ReactionButton from './ReactionButton';
@@ -42,11 +41,6 @@ interface IProps extends WithTranslation {
*/
_isGifMenuVisible: boolean;
/**
* Whether or not it's a mobile browser.
*/
_isMobile: boolean;
/**
* The ID of the local participant.
*/
@@ -67,10 +61,19 @@ interface IProps extends WithTranslation {
*/
dispatch: Function;
gifMenuColumns?: number;
overflowDrawer?: boolean;
/**
* Whether or not it's displayed in the overflow menu.
*/
overflowMenu?: boolean;
/**
* Whether to show the raised hand button.
*/
showRaisedHand?: boolean;
}
const styles = (theme: Theme) => {
@@ -83,7 +86,25 @@ const styles = (theme: Theme) => {
borderRadius: 0,
position: 'relative' as const,
boxSizing: 'border-box' as const,
height: `${REACTIONS_MENU_HEIGHT}px`
height:
(props: IProps) => {
const { overflowMenu, overflowDrawer, showRaisedHand, _isGifMenuVisible } = props;
let reactionsMenuHeight = REACTIONS_MENU_HEIGHT;
if (overflowMenu) {
if (!showRaisedHand) {
reactionsMenuHeight -= RAISE_HAND_ROW_HEIGHT;
}
if (!overflowDrawer) {
reactionsMenuHeight -= 38;
}
if (!overflowDrawer && _isGifMenuVisible) {
reactionsMenuHeight += 200;
}
}
return `${reactionsMenuHeight}px`;
}
}
};
};
@@ -197,18 +218,32 @@ class ReactionsMenu extends Component<IProps> {
* @inheritdoc
*/
render() {
const { _raisedHand, t, overflowMenu, _isMobile, classes, _isGifMenuVisible, _isGifEnabled } = this.props;
const {
_raisedHand,
t,
overflowMenu,
classes,
_isGifMenuVisible,
_isGifEnabled,
gifMenuColumns,
showRaisedHand = false
} = this.props;
const buttons = this._getReactionButtons();
if (_isGifEnabled) {
buttons.push(<GifsMenuButton overflowMenu = { overflowMenu } />);
}
return (
<div
className = { clsx('reactions-menu', _isGifEnabled && 'with-gif',
overflowMenu && `overflow ${classes.overflow}`) }>
{_isGifEnabled && _isGifMenuVisible && <GifsMenu />}
{_isGifEnabled && _isGifMenuVisible && <GifsMenu columns = { gifMenuColumns } />}
<div className = 'reactions-row'>
{ this._getReactionButtons() }
{_isGifEnabled && <GifsMenuButton />}
{buttons}
</div>
{_isMobile && (
{showRaisedHand && (
<div className = 'raise-hand-row'>
{/* @ts-ignore */}
<ReactionButton
@@ -236,11 +271,9 @@ class ReactionsMenu extends Component<IProps> {
*/
function mapStateToProps(state: IReduxState) {
const localParticipant = getLocalParticipant(state);
const { isNarrowLayout } = state['features/base/responsive-ui'];
return {
_localParticipantID: localParticipant?.id,
_isMobile: isMobileBrowser() || isNarrowLayout,
_isGifEnabled: isGifEnabled(state),
_isGifMenuVisible: isGifsMenuOpen(state),
_raisedHand: hasRaisedHand(localParticipant)

View File

@@ -5,8 +5,9 @@ import { useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import { IconArrowUp } from '../../../base/icons/svg';
import { IconFaceSmile } from '../../../base/icons/svg';
import { connect } from '../../../base/redux/functions';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import ToolboxButtonWithIconPopup from '../../../base/toolbox/components/web/ToolboxButtonWithIconPopup';
@@ -16,10 +17,36 @@ import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
import { getReactionsMenuVisibility } from '../../functions.web';
// @ts-ignore
import RaiseHandButton from './RaiseHandButton';
import ReactionEmoji from './ReactionEmoji';
import ReactionsMenu from './ReactionsMenu';
/**
* Implementation of a button for raising hand.
*/
class ReactionsButton extends AbstractButton<AbstractButtonProps, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
icon = IconFaceSmile;
label = 'toolbar.raiseHand';
toggledLabel = 'toolbar.raiseHand';
/**
* Retrieves tooltip dynamically.
*/
get tooltip() {
return 'toolbar.raiseHand';
}
/**
* Required by linter due to AbstractButton overwritten prop being writable.
*
* @param {string} _value - The value.
*/
set tooltip(_value) {
// Unused.
}
}
interface IProps extends WithTranslation {
/**
@@ -71,54 +98,74 @@ interface IProps extends WithTranslation {
*/
function ReactionsMenuButton({
_reactionsEnabled,
_isMobile,
buttonKey,
dispatch,
handleClick,
isOpen,
isNarrow,
notifyMode,
reactionsQueue,
t
}: IProps) {
if (!_reactionsEnabled || isNarrow) {
return null;
}
const visible = useSelector(getReactionsMenuVisibility);
const toggleReactionsMenu = useCallback(() => {
console.error('Toggle');
dispatch(toggleReactionsMenuVisibility());
}, [ dispatch ]);
const openReactionsMenu = useCallback(() => {
console.error('Open', visible);
!visible && toggleReactionsMenu();
}, [ visible, toggleReactionsMenu ]);
const closeReactionsMenu = useCallback(() => {
console.error('Close', visible);
visible && toggleReactionsMenu();
}, [ visible, toggleReactionsMenu ]);
const reactionsMenu = (<div className = 'reactions-menu-container'>
<ReactionsMenu />
</div>);
const reactionsButton = _isMobile ? (
<ToolboxButtonWithIconPopup
ariaControls = 'reactions-menu-dialog'
ariaExpanded = { isOpen }
ariaHasPopup = { true }
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
disableTouchEvents = { true }
onPopoverClose = { closeReactionsMenu }
onPopoverOpen = { openReactionsMenu }
trigger = { 'click' }
popoverContent = { reactionsMenu }
visible = { visible }>
<ReactionsButton
buttonKey = { buttonKey }
// handleClick = { visible ? closeReactionsMenu : openReactionsMenu }
notifyMode = { notifyMode } />
</ToolboxButtonWithIconPopup>
) : (
<ToolboxButtonWithIconPopup
ariaControls = 'reactions-menu-dialog'
ariaExpanded = { isOpen }
ariaHasPopup = { true }
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
onPopoverClose = { toggleReactionsMenu }
onPopoverOpen = { openReactionsMenu }
popoverContent = { reactionsMenu }
visible = { visible }>
<ReactionsButton
buttonKey = { buttonKey }
notifyMode = { notifyMode } />
</ToolboxButtonWithIconPopup>
);
return (
<div className = 'reactions-menu-popup-container'>
{!_reactionsEnabled || isNarrow ? (
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />)
: (
<ToolboxButtonWithIconPopup
ariaControls = 'reactions-menu-dialog'
ariaExpanded = { isOpen }
ariaHasPopup = { true }
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
icon = { IconArrowUp }
iconDisabled = { false }
iconId = 'reactions-menu-button'
onPopoverClose = { toggleReactionsMenu }
onPopoverOpen = { openReactionsMenu }
popoverContent = { reactionsMenu }
visible = { visible }>
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />
</ToolboxButtonWithIconPopup>
)}
{reactionsButton}
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index }
key = { uid }
@@ -139,8 +186,9 @@ function mapStateToProps(state: IReduxState) {
return {
_reactionsEnabled: isReactionsEnabled(state),
_isMobile: isMobileBrowser(),
isOpen: getReactionsMenuVisibility(state),
isNarrow: isMobileBrowser() || isNarrowLayout,
isNarrow: isNarrowLayout,
reactionsQueue: getReactionsQueue(state)
};
}

View File

@@ -7,6 +7,8 @@ import {
SURPRISE_SOUND_FILES
} from './sounds';
export const RAISE_HAND_ROW_HEIGHT = 54;
/**
* Reactions menu height on mobile web (px).
*/

View File

@@ -106,8 +106,8 @@ export default class AbstractHighlightButton<P: Props> extends Component<P> {
* }}
*/
export function _abstractMapStateToProps(state: Object) {
const isRecordingRunning = getActiveSession(state, JitsiRecordingConstants.mode.FILE);
const isButtonDisabled = isHighlightMeetingMomentDisabled(state);
const isRecordingRunning = getActiveSession(state, JitsiRecordingConstants.mode.FILE); //false
const isButtonDisabled = isHighlightMeetingMomentDisabled(state); // false
const { webhookProxyUrl } = state['features/base/config'];
const {

View File

@@ -1,14 +1,19 @@
/* eslint-disable lines-around-comment */
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 { setGifOverflowMenuVisibility } from '../../../gifs/actions';
import { isGifsMenuOpen } from '../../../gifs/functions.web';
// @ts-ignore
import { ReactionEmoji, ReactionsMenu } from '../../../reactions/components';
import { REACTIONS_MENU_HEIGHT } from '../../../reactions/constants';
import { RAISE_HAND_ROW_HEIGHT, REACTIONS_MENU_HEIGHT } from '../../../reactions/constants';
import { getReactionsQueue } from '../../../reactions/functions.any';
import { DRAWER_MAX_HEIGHT } from '../../constants';
import { showOverflowDrawer } from '../../functions.web';
@@ -32,29 +37,53 @@ interface IProps {
/**
* A child React Element to display within {@code Popover}.
*/
children: ReactNode;
children: Object[];
/**
* Whether or not the OverflowMenu popover should display.
*/
isOpen: boolean;
onToolboxEscKey: (e?: React.KeyboardEvent) => void;
/**
* Callback to change the visibility of the overflow menu.
*/
onVisibilityChange: Function;
showRaiseHandInReactionsMenu: boolean;
/**
* Whether or not to display the reactions in the mobile menu.
*/
showMobileReactions: boolean;
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)',
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
}
};
});
@@ -62,16 +91,22 @@ const useStyles = makeStyles()(() => {
const OverflowMenuButton = ({
children,
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(setGifOverflowMenuVisibility(false));
}
}, [ onVisibilityChange, setGifOverflowMenuVisibility, isGiphyVisible, overflowDrawer, dispatch ]);
const onOpenDialog = useCallback(() => {
onVisibilityChange(true);
@@ -91,50 +126,121 @@ const OverflowMenuButton = ({
onVisibilityChange(!isOpen);
}, [ isOpen, onVisibilityChange ]);
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
const { t } = useTranslation();
let reactionsMenuHeight = 0;
if (showReactionsMenu) {
reactionsMenuHeight = REACTIONS_MENU_HEIGHT;
if (!showRaiseHandInReactionsMenu) {
reactionsMenuHeight -= RAISE_HAND_ROW_HEIGHT;
}
if (!overflowDrawer) {
reactionsMenuHeight -= 38;
}
if (!overflowDrawer && isGiphyVisible) {
reactionsMenuHeight += 200;
}
}
const { classes } = useStyles({
reactionsMenuHeight,
overflowDrawer
});
const groupsJSX = children.map((buttonGroup: any) => (
<ContextMenuItemGroup key = { `group-${buttonGroup[0].group}` }>
{buttonGroup.map(({ key, Content, ...rest }) => {
const props = { ...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} ${overflowDrawer ? 'is-drawer' : 'not-drawer'}` }
hidden = { false }
id = 'overflow-context-menu'
inDrawer = { overflowDrawer }
onKeyDown = { onToolboxEscKey }>
<div className = { classes.content }>
{ groupsJSX }
</div>
{
showReactionsMenu && (<div className = { classes.footer }>
<ReactionsMenu
gifMenuColumns = { overflowDrawer ? undefined : 1 }
overflowDrawer = { overflowDrawer }
overflowMenu = { true }
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'>
<Popover
content = { overflowMenu }
headingId = 'overflow-context-menu'
onPopoverClose = { onCloseDialog }
onPopoverOpen = { onOpenDialog }
position = 'top'
trigger = 'click'
visible = { isOpen }>
<OverflowToggleButton
isOpen = { isOpen }
onKeyDown = { onEscClick } />
</Popover>
{
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>
)
}
showReactionsMenu && <div className = 'reactions-animations-container'>
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index }
key = { uid }
reaction = { reaction }
uid = { uid } />))}
</div> }
</div>
);
};

View File

@@ -60,6 +60,7 @@ import { ParticipantsPaneButton } from '../../../participants-pane/components/we
import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
import { addReactionToBuffer } from '../../../reactions/actions.any';
import { 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.any';
@@ -729,7 +730,6 @@ class Toolbox extends Component<IProps> {
_hasSalesforce,
_isIosMobile,
_isMobile,
_isNarrowLayout,
_isSpeakerStatsDisabled,
_multiStreamModeEnabled,
_reactionsEnabled,
@@ -769,13 +769,20 @@ class Toolbox extends Component<IProps> {
group: 2
};
const raisehand = (!_reactionsEnabled || (!_isNarrowLayout && !_isMobile)) && {
const raisehand = {
key: 'raisehand',
Content: ReactionsMenuButton,
Content: RaiseHandButton,
handleClick: this._onToolbarToggleRaiseHand,
group: 2
};
const reactions = _reactionsEnabled && {
key: 'reactions',
Content: ReactionsMenuButton,
group: 2
};
const participants = {
key: 'participants-pane',
Content: ParticipantsPaneButton,
@@ -945,6 +952,7 @@ class Toolbox extends Component<IProps> {
desktop,
chat,
raisehand,
reactions,
participants,
invite,
tileview,
@@ -1425,7 +1433,6 @@ class Toolbox extends Component<IProps> {
_isNarrowLayout,
_overflowDrawer,
_overflowMenuVisible,
_reactionsEnabled,
_toolbarButtons,
classes,
t
@@ -1435,6 +1442,9 @@ class Toolbox extends Component<IProps> {
const containerClassName = `toolbox-content${_isMobile || _isNarrowLayout ? ' toolbox-content-mobile' : ''}`;
const { mainMenuButtons, overflowMenuButtons } = this._getVisibleButtons();
const showReactionsInOverflowMenu = overflowMenuButtons.some(({ key }) => key === 'reactions');
const showRaiseHandInReactionsMenu = showReactionsInOverflowMenu
&& overflowMenuButtons.some(({ key }) => key === 'raisehand');
return (
<div className = { containerClassName }>
@@ -1458,44 +1468,34 @@ class Toolbox extends Component<IProps> {
ariaControls = 'overflow-menu'
isOpen = { _overflowMenuVisible }
key = 'overflow-menu'
onToolboxEscKey = { this._onEscKey }
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;
showRaiseHandInReactionsMenu = { showRaiseHandInReactionsMenu }
showReactionsMenu = { showReactionsInOverflowMenu }>
{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>
return acc;
}, [])}
</OverflowMenuButton>
)}

View File

@@ -2,6 +2,10 @@
* Thresholds for displaying toolbox buttons.
*/
export const THRESHOLDS = [
{
width: 565,
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants', 'tileview' ]
},
{
width: 520,
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants', 'tileview' ]

View File

@@ -124,6 +124,15 @@ export function showOverflowDrawer(state: IReduxState) {
return state['features/toolbox'].overflowDrawer;
}
/**
*
* @param state
* @returns
*/
export function showOverflowMenu(state: IReduxState) {
return state['features/toolbox'].overflowMenuVisible;
}
/**
* Indicates whether the toolbox is enabled or not.
*