feat(narrow-layout) Use drawer menus on desktop narrow mode (#12799)

This commit is contained in:
Horatiu Muresan 2023-01-25 17:02:26 +02:00 committed by GitHub
parent cf7e692186
commit 3e58cd8af3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 90 additions and 25 deletions

View File

@ -52,3 +52,15 @@ export const SET_REDUCED_UI = 'SET_REDUCED_UI';
*/
export const SET_CONTEXT_MENU_OPEN = 'SET_CONTEXT_MENU_OPEN';
/**
* The type of redux action which signals whether we are in narrow layout.
*
* {
* type: SET_NARROW_LAYOUT,
* isNarrow: boolean
* }
*
* @public
*/
export const SET_NARROW_LAYOUT = 'SET_NARROW_LAYOUT';

View File

@ -10,6 +10,7 @@ import {
SAFE_AREA_INSETS_CHANGED,
SET_ASPECT_RATIO,
SET_CONTEXT_MENU_OPEN,
SET_NARROW_LAYOUT,
SET_REDUCED_UI
} from './actionTypes';
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
@ -143,3 +144,19 @@ export function setSafeAreaInsets(insets: Object) {
insets
};
}
/**
* Sets narrow layout.
*
* @param {boolean} isNarrow - Whether is narrow layout.
* @returns {{
* type: SET_NARROW_LAYOUT,
* isNarrow: boolean
* }}
*/
export function setNarrowLayout(isNarrow: boolean) {
return {
type: SET_NARROW_LAYOUT,
isNarrow
};
}

View File

@ -6,6 +6,7 @@ import {
SAFE_AREA_INSETS_CHANGED,
SET_ASPECT_RATIO,
SET_CONTEXT_MENU_OPEN,
SET_NARROW_LAYOUT,
SET_REDUCED_UI
} from './actionTypes';
import { ASPECT_RATIO_NARROW } from './constants';
@ -22,6 +23,7 @@ const DEFAULT_STATE = {
aspectRatio: ASPECT_RATIO_NARROW,
clientHeight: innerHeight,
clientWidth: innerWidth,
isNarrowLayout: false,
reducedUI: false,
contextMenuOpened: false
};
@ -31,6 +33,7 @@ export interface IResponsiveUIState {
clientHeight: number;
clientWidth: number;
contextMenuOpened: boolean;
isNarrowLayout: boolean;
reducedUI: boolean;
safeAreaInsets?: {
bottom: number;
@ -65,6 +68,9 @@ ReducerRegistry.register<IResponsiveUIState>('features/base/responsive-ui',
case SET_CONTEXT_MENU_OPEN:
return set(state, 'contextMenuOpened', action.isOpen);
case SET_NARROW_LAYOUT:
return set(state, 'isNarrowLayout', action.isNarrow);
}
return state;

View File

@ -81,6 +81,11 @@ type Props = AbstractProps & {
*/
_enableSaveLogs: boolean,
/**
* Whether is narrow layout or not.
*/
_isNarrowLayout: boolean,
/**
* Whether or not the displays stats are for local video.
*/
@ -193,6 +198,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
enableSaveLogs = { this.props._enableSaveLogs }
framerate = { framerate }
isLocalVideo = { this.props._isLocalVideo }
isNarrowLayout = { this.props._isNarrowLayout }
isVirtualScreenshareParticipant = { this.props._isVirtualScreenshareParticipant }
maxEnabledResolution = { maxEnabledResolution }
onSaveLogs = { this.props._onSaveLogs }
@ -312,6 +318,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
const conference = state['features/base/conference'].conference;
const participant
= participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
const { isNarrowLayout } = state['features/base/responsive-ui'];
const tracks = state['features/base/tracks'];
const audioTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantId);
let videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
@ -330,6 +337,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
_isConnectionStatusInactive,
_isConnectionStatusInterrupted,
_isE2EEVerified: participant?.e2eeVerified,
_isNarrowLayout: isNarrowLayout,
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant),
_isLocalVideo: participant?.local,
_region: participant?.region,

View File

@ -98,6 +98,11 @@ interface IProps extends WithTranslation {
*/
isLocalVideo: boolean;
/**
* Whether we are in narrow layout mode or not.
*/
isNarrowLayout: boolean;
/**
* Whether or not the statistics are for screen share.
*/
@ -261,9 +266,10 @@ class ConnectionStatsTable extends Component<IProps> {
disableShowMoreStats,
enableSaveLogs,
isVirtualScreenshareParticipant,
isLocalVideo
isLocalVideo,
isNarrowLayout
} = this.props;
const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() || isNarrowLayout });
if (isVirtualScreenshareParticipant) {
return this._renderScreenShareStatus();

View File

@ -1,8 +1,7 @@
import { isMobileBrowser } from '../base/environment/utils';
import { pinParticipant } from '../base/participants/actions';
import { getParticipantCountWithFake } from '../base/participants/functions';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { clientResized } from '../base/responsive-ui/actions';
import { clientResized, setNarrowLayout } from '../base/responsive-ui/actions';
import { shouldHideSelfView } from '../base/settings/functions.web';
import { selectParticipantInLargeVideo } from '../large-video/actions.any';
import { getParticipantsPaneOpen } from '../participants-pane/functions';
@ -136,9 +135,8 @@ StateListenerRegistry.register(
StateListenerRegistry.register(
/* selector */ state => state['features/base/responsive-ui'].clientWidth < DISPLAY_DRAWER_THRESHOLD,
/* listener */ (widthBelowThreshold, store) => {
if (isMobileBrowser()) {
store.dispatch(setOverflowDrawer(widthBelowThreshold));
}
store.dispatch(setOverflowDrawer(widthBelowThreshold));
store.dispatch(setNarrowLayout(widthBelowThreshold));
});
/**

View File

@ -3,6 +3,7 @@
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { isMobileBrowser } from '../../../../../base/environment/utils';
import { isLocalParticipantModerator } from '../../../../../base/participants';
import { equals } from '../../../../../base/redux';
import useContextMenu from '../../../../../base/ui/hooks/useContextMenu.web';
@ -40,10 +41,11 @@ export const RoomList = ({ searchString }: Props) => {
const isLocalModerator = useSelector(isLocalParticipantModerator);
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
const { hideJoinRoomButton } = useSelector(getBreakoutRoomsConfig);
const _overflowDrawer = useSelector(showOverflowDrawer);
const overflowDrawer = useSelector(showOverflowDrawer);
const [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();
const [ lowerParticipantMenu, raiseParticipantMenu, toggleParticipantMenu,
participantMenuEnter, participantMenuLeave, raiseParticipantContext ] = useContextMenu();
const hideMenu = useCallback(() => !overflowDrawer && lowerMenu(), [ overflowDrawer, lowerMenu ]);
const onRaiseMenu = useCallback(room => target => raiseMenu(room, target), [ raiseMenu ]);
return (
@ -55,14 +57,14 @@ export const RoomList = ({ searchString }: Props) => {
<React.Fragment key = { room.id }>
<CollapsibleRoom
isHighlighted = { raiseContext.entity === room }
onLeave = { lowerMenu }
onLeave = { hideMenu }
onRaiseMenu = { onRaiseMenu(room) }
participantContextEntity = { raiseParticipantContext.entity }
raiseParticipantContextMenu = { raiseParticipantMenu }
room = { room }
searchString = { searchString }
toggleParticipantMenu = { toggleParticipantMenu }>
{!_overflowDrawer && <>
{!isMobileBrowser() && <>
{!hideJoinRoomButton && <JoinActionButton room = { room } />}
{isLocalModerator && !room.isMainRoom
&& <RoomActionEllipsis onClick = { toggleMenu(room) } />}

View File

@ -236,10 +236,11 @@ 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(),
_isMobile: isMobileBrowser() || isNarrowLayout,
_isGifEnabled: isGifEnabled(state),
_isGifMenuVisible: isGifsMenuOpen(state),
_raisedHand: hasRaisedHand(localParticipant)

View File

@ -43,9 +43,9 @@ interface IProps extends WithTranslation {
handleClick: Function;
/**
* Whether or not it's a mobile browser.
* Whether or not it's narrow mode or mobile browser.
*/
isMobile: boolean;
isNarrow: boolean;
/**
* Whether or not the reactions menu is open.
@ -75,7 +75,7 @@ function ReactionsMenuButton({
dispatch,
handleClick,
isOpen,
isMobile,
isNarrow,
notifyMode,
reactionsQueue,
t
@ -95,7 +95,7 @@ function ReactionsMenuButton({
return (
<div className = 'reactions-menu-popup-container'>
{!_reactionsEnabled || isMobile ? (
{!_reactionsEnabled || isNarrow ? (
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
@ -135,10 +135,12 @@ function ReactionsMenuButton({
* @returns {Object}
*/
function mapStateToProps(state: IReduxState) {
const { isNarrowLayout } = state['features/base/responsive-ui'];
return {
_reactionsEnabled: isReactionsEnabled(state),
isOpen: getReactionsMenuVisibility(state),
isMobile: isMobileBrowser(),
isNarrow: isMobileBrowser() || isNarrowLayout,
reactionsQueue: getReactionsQueue(state)
};
}

View File

@ -155,12 +155,13 @@ class AudioSettingsButton extends Component<Props> {
*/
function mapStateToProps(state) {
const { permissions = {} } = state['features/base/devices'];
const { isNarrowLayout } = state['features/base/responsive-ui'];
return {
hasPermissions: permissions.audio,
isDisabled: isAudioSettingsButtonDisabled(state),
isOpen: getAudioSettingsVisibility(state),
visible: !isMobileBrowser()
visible: !isMobileBrowser() && !isNarrowLayout
};
}

View File

@ -5,13 +5,13 @@ import { makeStyles } from 'tss-react/mui';
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState } from '../../../app/types';
import Popover from '../../../base/popover/components/Popover.web';
// @ts-ignore
import { ReactionEmoji, ReactionsMenu } from '../../../reactions/components';
import { REACTIONS_MENU_HEIGHT } from '../../../reactions/constants';
import { getReactionsQueue } from '../../../reactions/functions.any';
import { DRAWER_MAX_HEIGHT } from '../../constants';
import { showOverflowDrawer } from '../../functions.web';
// @ts-ignore
import Drawer from './Drawer';
@ -67,7 +67,7 @@ const OverflowMenuButton = ({
showMobileReactions
}: IProps) => {
const { classes } = useStyles();
const overflowDrawer = useSelector((state: IReduxState) => state['features/toolbox'].overflowDrawer);
const overflowDrawer = useSelector(showOverflowDrawer);
const reactionsQueue = useSelector(getReactionsQueue);
const onCloseDialog = useCallback(() => {

View File

@ -231,6 +231,11 @@ interface IProps extends WithTranslation {
*/
_isMobile: boolean;
/**
* Whether we are in narrow layout mode.
*/
_isNarrowLayout: boolean;
/**
* Whether or not the profile is disabled.
*/
@ -710,8 +715,10 @@ class Toolbox extends Component<IProps> {
_hasSalesforce,
_isIosMobile,
_isMobile,
_isNarrowLayout,
_isSpeakerStatsDisabled,
_multiStreamModeEnabled,
_reactionsEnabled,
_screenSharing,
_whiteboardEnabled
} = this.props;
@ -748,7 +755,7 @@ class Toolbox extends Component<IProps> {
group: 2
};
const raisehand = {
const raisehand = (!_reactionsEnabled || (!_isNarrowLayout && !_isMobile)) && {
key: 'raisehand',
Content: ReactionsMenuButton,
handleClick: this._onToolbarToggleRaiseHand,
@ -1387,6 +1394,7 @@ class Toolbox extends Component<IProps> {
_endConferenceSupported,
_hangupMenuVisible,
_isMobile,
_isNarrowLayout,
_overflowDrawer,
_overflowMenuVisible,
_reactionsEnabled,
@ -1396,7 +1404,7 @@ class Toolbox extends Component<IProps> {
} = this.props;
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
const containerClassName = `toolbox-content${_isMobile ? ' toolbox-content-mobile' : ''}`;
const containerClassName = `toolbox-content${_isMobile || _isNarrowLayout ? ' toolbox-content-mobile' : ''}`;
const { mainMenuButtons, overflowMenuButtons } = this._getVisibleButtons();
@ -1424,7 +1432,7 @@ class Toolbox extends Component<IProps> {
key = 'overflow-menu'
onVisibilityChange = { this._onSetOverflowVisible }
showMobileReactions = {
_reactionsEnabled && overflowMenuButtons.find(({ key }) => key === 'raisehand')
_reactionsEnabled && (_isMobile || _isNarrowLayout)
}>
<ContextMenu
accessibilityLabel = { t(toolbarAccLabel) }
@ -1509,6 +1517,7 @@ class Toolbox extends Component<IProps> {
*/
function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const { conference } = state['features/base/conference'];
const { isNarrowLayout } = state['features/base/responsive-ui'];
const endConferenceSupported = conference?.isEndConferenceSupported();
const {
@ -1551,6 +1560,7 @@ function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
_jwtDisabledButons: getJwtDisabledButtons(state),
_hasSalesforce: isSalesforceEnabled(state),
_hangupMenuVisible: hangupMenuVisible,
_isNarrowLayout: isNarrowLayout,
_localParticipantID: localParticipant?.id,
_localVideo: localVideo,
_multiStreamModeEnabled: getMultipleVideoSendingSupportFeatureFlag(state),

View File

@ -170,13 +170,14 @@ class VideoSettingsButton extends Component<Props> {
*/
function mapStateToProps(state) {
const { permissions = {} } = state['features/base/devices'];
const { isNarrowLayout } = state['features/base/responsive-ui'];
return {
hasPermissions: permissions.video,
hasVideoTrack: Boolean(getLocalJitsiVideoTrack(state)),
isDisabled: isVideoSettingsButtonDisabled(state),
isOpen: getVideoSettingsVisibility(state),
visible: !isMobileBrowser()
visible: !isMobileBrowser() && !isNarrowLayout
};
}

View File

@ -221,7 +221,7 @@ class LocalVideoMenuTriggerButton extends Component<IProps> {
overflowDrawer = { _overflowDrawer }
position = { _menuPosition }
visible = { popoverVisible }>
{!_overflowDrawer && buttonVisible && !isMobileBrowser() && (
{buttonVisible && !isMobileBrowser() && (
<Button
accessibilityLabel = { t('dialog.localUserControls') }
className = { classes.triggerButton }

View File

@ -192,9 +192,10 @@ class RemoteVideoMenuTriggerButton extends Component<IProps> {
id = 'remote-video-menu-trigger'
onPopoverClose = { this._onPopoverClose }
onPopoverOpen = { this._onPopoverOpen }
overflowDrawer = { _overflowDrawer }
position = { this.props._menuPosition }
visible = { popoverVisible }>
{!_overflowDrawer && buttonVisible && !_disabled && (
{ buttonVisible && !_disabled && (
!isMobileBrowser() && <Button
accessibilityLabel = { this.props.t('dialog.remoteUserControls', { username }) }
className = { classes.triggerButton }