snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
205
react/features/base/responsive-ui/actions.ts
Normal file
205
react/features/base/responsive-ui/actions.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { CHAT_SIZE } from '../../chat/constants';
|
||||
import { getCustomPanelWidth } from '../../custom-panel/functions';
|
||||
import { getParticipantsPaneWidth } from '../../participants-pane/functions';
|
||||
|
||||
import {
|
||||
CLIENT_RESIZED,
|
||||
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';
|
||||
|
||||
/**
|
||||
* Size threshold for determining if we are in reduced UI mode or not.
|
||||
*
|
||||
* FIXME The logic to base {@code reducedUI} on a hardcoded width or height is
|
||||
* very brittle because it's completely disconnected from the UI which wants to
|
||||
* be rendered and, naturally, it broke on iPad where even the secondary Toolbar
|
||||
* didn't fit in the height. We do need to measure the actual UI at runtime and
|
||||
* determine whether and how to render it.
|
||||
*/
|
||||
const REDUCED_UI_THRESHOLD = 300;
|
||||
const WEB_REDUCED_UI_THRESHOLD = 320;
|
||||
|
||||
/**
|
||||
* Aspect ratio hysteresis deadband margin.
|
||||
*
|
||||
* Prevents layout thrashing / infinite oscillation on square/foldable displays
|
||||
* (e.g. Galaxy Z Fold, Pixel Fold, split-screen mode, iPad multitasking) where
|
||||
* applying a layout (such as rendering a filmstrip or dialog insets) subtly alters
|
||||
* available dimensions across the 1:1 boundary.
|
||||
*/
|
||||
const ASPECT_RATIO_HYSTERESIS = 0.05;
|
||||
|
||||
/**
|
||||
* Indicates a resize of the window.
|
||||
*
|
||||
* @param {number} clientWidth - The width of the window.
|
||||
* @param {number} clientHeight - The height of the window.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function clientResized(clientWidth: number, clientHeight: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!clientWidth && !clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const prevResponsiveUI = state['features/base/responsive-ui'];
|
||||
let availableWidth = clientWidth;
|
||||
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
const { reducedUIEnabled = true } = state['features/base/config'];
|
||||
const { isOpen: isChatOpen, width } = state['features/chat'];
|
||||
|
||||
if (isChatOpen) {
|
||||
availableWidth -= width?.current ?? CHAT_SIZE;
|
||||
}
|
||||
|
||||
availableWidth -= getParticipantsPaneWidth(state);
|
||||
availableWidth -= getCustomPanelWidth(state);
|
||||
|
||||
reducedUIEnabled && dispatch(setReducedUI(availableWidth, clientHeight));
|
||||
}
|
||||
|
||||
if (prevResponsiveUI.clientHeight === clientHeight
|
||||
&& prevResponsiveUI.clientWidth === clientWidth
|
||||
&& prevResponsiveUI.videoSpaceWidth === availableWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
batch(() => {
|
||||
dispatch({
|
||||
type: CLIENT_RESIZED,
|
||||
clientHeight,
|
||||
clientWidth,
|
||||
videoSpaceWidth: availableWidth
|
||||
});
|
||||
dispatch(setAspectRatio(availableWidth, clientHeight));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aspect ratio of the app's user interface based on specific width and
|
||||
* height.
|
||||
*
|
||||
* @param {number} width - The width of the app's user interface.
|
||||
* @param {number} height - The height of the app's user interface.
|
||||
* @returns {{
|
||||
* type: SET_ASPECT_RATIO,
|
||||
* aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
export function setAspectRatio(width: number, height: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!width || !height) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentAspectRatio = getState()['features/base/responsive-ui'].aspectRatio;
|
||||
const ratio = width / height;
|
||||
let aspectRatio = currentAspectRatio;
|
||||
|
||||
// Apply hysteresis:
|
||||
// - To switch from NARROW to WIDE: width must exceed height by the hysteresis margin (ratio > 1 + margin).
|
||||
// - To switch from WIDE to NARROW: width must be less than height by the hysteresis margin (ratio < 1 - margin).
|
||||
// - In the deadband around 1:1 (~[0.95, 1.05]), retain the current stable aspect ratio.
|
||||
if (currentAspectRatio === ASPECT_RATIO_NARROW) {
|
||||
if (ratio > 1 + ASPECT_RATIO_HYSTERESIS) {
|
||||
aspectRatio = ASPECT_RATIO_WIDE;
|
||||
}
|
||||
} else if (currentAspectRatio === ASPECT_RATIO_WIDE) {
|
||||
if (ratio < 1 - ASPECT_RATIO_HYSTERESIS) {
|
||||
aspectRatio = ASPECT_RATIO_NARROW;
|
||||
}
|
||||
} else {
|
||||
aspectRatio = width < height ? ASPECT_RATIO_NARROW : ASPECT_RATIO_WIDE;
|
||||
}
|
||||
|
||||
if (aspectRatio !== currentAspectRatio) {
|
||||
return dispatch({
|
||||
type: SET_ASPECT_RATIO,
|
||||
aspectRatio
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the "reduced UI" property. In reduced UI mode some components will
|
||||
* be hidden if there is no space to render them.
|
||||
*
|
||||
* @param {number} width - Current usable width.
|
||||
* @param {number} height - Current usable height.
|
||||
* @returns {{
|
||||
* type: SET_REDUCED_UI,
|
||||
* reducedUI: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setReducedUI(width: number, height: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const threshold = navigator.product === 'ReactNative'
|
||||
? REDUCED_UI_THRESHOLD
|
||||
: WEB_REDUCED_UI_THRESHOLD;
|
||||
const reducedUI = Math.max(width, height) < threshold;
|
||||
|
||||
if (reducedUI !== getState()['features/base/responsive-ui'].reducedUI) {
|
||||
return dispatch({
|
||||
type: SET_REDUCED_UI,
|
||||
reducedUI
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the local or remote participant context menu is open.
|
||||
*
|
||||
* @param {boolean} isOpen - Whether local or remote context menu is open.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setParticipantContextMenuOpen(isOpen: boolean) {
|
||||
return {
|
||||
type: SET_CONTEXT_MENU_OPEN,
|
||||
isOpen
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insets from the SafeAreaProvider.
|
||||
*
|
||||
* @param {Object} insets - The new insets to be set.
|
||||
* @returns {{
|
||||
* type: SAFE_AREA_INSETS_CHANGED,
|
||||
* insets: Object
|
||||
* }}
|
||||
*/
|
||||
export function setSafeAreaInsets(insets: Object) {
|
||||
return {
|
||||
type: SAFE_AREA_INSETS_CHANGED,
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user