Compare commits

..

3 Commits

Author SHA1 Message Date
Hristo Terezov
303f81f427 fix(participants-pane): replace :first-child with :first-of-type to silence emotion SSR warning
emotion/stylis emits a console error for :first-child selectors as they can
cause hydration mismatches in SSR. All three occurrences that trigger on
conference load are in ParticipantsPane. The semantics are equivalent since
all sibling elements are the same type.
2026-02-18 08:30:23 -06:00
Hristo Terezov
23a65fbf2a fix(ui): migrate ReactDOM.render to createRoot for React 18 compatibility
ReactDOM.render is deprecated in React 18 and causes console errors on every
render. Migrate to createRoot API across the main entry point and the legacy
video layout modules, storing root references to avoid recreating them on
repeated calls.
2026-02-18 08:25:25 -06:00
Hristo Terezov
5fee521938 fix(ui): replace kebab-case CSS properties with camelCase in style objects
Vendor-prefixed CSS properties like -webkit-appearance and -moz-appearance
used as object keys in emotion/tss-react style objects cause console errors
because JavaScript CSS-in-JS libraries require camelCase property names.

Replace all occurrences across Input, Chat, Filmstrip, and Slider components.
2026-02-18 08:25:08 -06:00
14 changed files with 116 additions and 315 deletions

View File

@@ -3,7 +3,7 @@
import Logger from '@jitsi/logger';
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
@@ -166,7 +166,8 @@ export default class LargeVideoManager {
this.removePresenceLabel();
ReactDOM.unmountComponentAtNode(this._dominantSpeakerAvatarContainer);
this._avatarRoot?.unmount();
this._avatarRoot = null;
this.container.style.display = 'none';
}
@@ -518,14 +519,16 @@ export default class LargeVideoManager {
* Updates the src of the dominant speaker avatar
*/
updateAvatar() {
ReactDOM.render(
if (!this._avatarRoot) {
this._avatarRoot = createRoot(this._dominantSpeakerAvatarContainer);
}
this._avatarRoot.render(
<Provider store = { APP.store }>
<Avatar
id = "dominantSpeakerAvatar"
participantId = { this.id }
size = { 200 } />
</Provider>,
this._dominantSpeakerAvatarContainer
</Provider>
);
}
@@ -559,15 +562,18 @@ export default class LargeVideoManager {
const presenceLabelContainer = document.getElementById('remotePresenceMessage');
if (presenceLabelContainer) {
ReactDOM.render(
if (!this._presenceLabelRoot) {
this._presenceLabelRoot = createRoot(presenceLabelContainer);
}
this._presenceLabelRoot.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<PresenceLabel
participantID = { id }
className = 'presence-label' />
</I18nextProvider>
</Provider>,
presenceLabelContainer);
</Provider>
);
}
}
@@ -577,11 +583,8 @@ export default class LargeVideoManager {
* @returns {void}
*/
removePresenceLabel() {
const presenceLabelContainer = document.getElementById('remotePresenceMessage');
if (presenceLabelContainer) {
ReactDOM.unmountComponentAtNode(presenceLabelContainer);
}
this._presenceLabelRoot?.unmount();
this._presenceLabelRoot = null;
}
/**

View File

@@ -4,7 +4,7 @@
import Logger from '@jitsi/logger';
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip/constants';
@@ -659,7 +659,12 @@ export class VideoContainer extends LargeContainer {
return;
}
ReactDOM.render(
const container = document.getElementById('largeVideoBackgroundContainer');
if (!this._backgroundRoot) {
this._backgroundRoot = createRoot(container);
}
this._backgroundRoot.render(
<LargeVideoBackground
hidden = { this._hideBackground || this._isHidden }
mirror = {
@@ -669,8 +674,7 @@ export class VideoContainer extends LargeContainer {
}
orientationFit = { this._backgroundOrientation }
videoElement = { this.video }
videoTrack = { this.stream } />,
document.getElementById('largeVideoBackgroundContainer')
videoTrack = { this.stream } />
);
}
}

View File

@@ -1 +0,0 @@
export * from './utils.any';

View File

@@ -29,3 +29,4 @@ export function isIpadMobileBrowser() {
// @ts-ignore
return isIosMobileBrowser() && Platform.isPad;
}

View File

@@ -1,37 +0,0 @@
import { MIN_FILMSTRIP_RESIZE_WIDTH } from '../../filmstrip/constants';
/**
* Detects if the current device has touch capability.
* This includes smartphones, tablets, and laptops with touch screens.
*
* @returns {boolean} True if the device supports touch events.
*/
export function isTouchDevice(): boolean {
// Check maxTouchPoints (most reliable for modern browsers)
if ('maxTouchPoints' in navigator) {
return navigator.maxTouchPoints > 0;
}
return false;
}
/**
* Determines if resize functionality should be enabled based on device capabilities
* and screen size. On touch devices, resize is only enabled for larger screens.
* On non-touch devices (desktop), resize is always enabled.
*
* @returns {boolean} True if resize functionality should be available to the user.
*/
export function shouldEnableResize(): boolean {
const hasTouch = isTouchDevice();
// On non-touch devices (desktop), always enable resize
if (!hasTouch) {
return true;
}
// On touch devices, only enable if screen is large enough.
return window?.innerWidth >= MIN_FILMSTRIP_RESIZE_WIDTH;
}
export * from './utils.any';

View File

@@ -107,12 +107,12 @@ const useStyles = makeStyles()(theme => {
},
'input::-webkit-outer-spin-button, input::-webkit-inner-spin-button': {
'-webkit-appearance': 'none',
WebkitAppearance: 'none',
margin: 0
},
'input[type=number]': {
'-moz-appearance': 'textfield'
MozAppearance: 'textfield'
},
icon: {

View File

@@ -4,7 +4,6 @@ import { connect, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import { isTouchDevice, shouldEnableResize } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg';
import { getLocalParticipant, getRemoteParticipants, isPrivateChatEnabledSelf } from '../../../base/participants/functions';
@@ -24,16 +23,7 @@ import {
setUserChatWidth,
toggleChat
} from '../../actions.web';
import {
CHAT_DRAG_HANDLE_HEIGHT,
CHAT_DRAG_HANDLE_OFFSET,
CHAT_DRAG_HANDLE_WIDTH,
CHAT_SIZE,
CHAT_TOUCH_HANDLE_SIZE,
ChatTabs,
OPTION_GROUPCHAT,
SMALL_WIDTH_THRESHOLD
} from '../../constants';
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
import { IChatProps as AbstractProps } from '../../types';
@@ -114,12 +104,7 @@ interface IProps extends AbstractProps {
_width: number;
}
const useStyles = makeStyles<{
_isResizing: boolean;
isTouch: boolean;
resizeEnabled: boolean;
width: number;
}>()((theme, { _isResizing, isTouch, resizeEnabled, width }) => {
const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, { _isResizing, width }) => {
return {
container: {
backgroundColor: theme.palette.chatBackground,
@@ -130,15 +115,11 @@ const useStyles = makeStyles<{
width: `${width}px`,
zIndex: 300,
// On non-touch devices (desktop), show handle on hover
// On touch devices, handle is always visible if resize is enabled
...(!isTouch && {
'&:hover, &:focus-within': {
'& .dragHandleContainer': {
visibility: 'visible'
}
'&:hover, &:focus-within': {
'& .dragHandleContainer': {
visibility: 'visible'
}
}),
},
'@media (max-width: 580px)': {
height: '100dvh',
@@ -151,7 +132,7 @@ const useStyles = makeStyles<{
'*': {
userSelect: 'text',
'-webkit-user-select': 'text'
WebkitUserSelect: 'text'
}
},
@@ -202,23 +183,16 @@ const useStyles = makeStyles<{
dragHandleContainer: {
height: '100%',
// Touch devices need larger hit target but positioned to not take extra space
width: isTouch ? `${CHAT_TOUCH_HANDLE_SIZE}px` : `${CHAT_DRAG_HANDLE_WIDTH}px`,
width: '9px',
backgroundColor: 'transparent',
position: 'absolute',
cursor: 'col-resize',
display: resizeEnabled ? 'flex' : 'none', // Hide if resize not enabled
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
// On touch devices, always visible if resize enabled. On desktop, hidden by default
visibility: (isTouch && resizeEnabled) ? 'visible' : 'hidden',
// Position touch handle centered on offset from edge, maintaining same gap as non-touch
right: isTouch
? `${CHAT_DRAG_HANDLE_OFFSET - Math.floor((CHAT_TOUCH_HANDLE_SIZE - CHAT_DRAG_HANDLE_WIDTH) / 2)}px`
: `${CHAT_DRAG_HANDLE_OFFSET}px`,
visibility: 'hidden',
right: '4px',
top: 0,
// Prevent touch scrolling while dragging
touchAction: 'none',
'&:hover': {
'& .dragHandle': {
@@ -236,15 +210,10 @@ const useStyles = makeStyles<{
},
dragHandle: {
// Keep the same visual appearance on all devices
backgroundColor: theme.palette.icon02,
height: `${CHAT_DRAG_HANDLE_HEIGHT}px`,
width: `${CHAT_DRAG_HANDLE_WIDTH / 3}px`,
borderRadius: '1px',
// Make more visible when actively shown
...(isTouch && resizeEnabled && {
backgroundColor: theme.palette.icon01
})
height: '100px',
width: '3px',
borderRadius: '1px'
},
privateMessageRecipientsList: {
@@ -277,10 +246,7 @@ const Chat = ({
return null;
}
// Detect touch capability and screen size for resize functionality
const isTouch = isTouchDevice();
const resizeEnabled = shouldEnableResize();
const { classes, cx } = useStyles({ _isResizing, width: _width, isTouch, resizeEnabled });
const { classes, cx } = useStyles({ _isResizing, width: _width });
const [ isMouseDown, setIsMouseDown ] = useState(false);
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
const [ dragChatWidth, setDragChatWidth ] = useState<number | null>(null);
@@ -316,21 +282,16 @@ const Chat = ({
}, [ participants, defaultRemoteDisplayName, t, notifyTimestamp ]);
/**
* Handles pointer down on the drag handle.
* Supports both mouse and touch events via Pointer Events API.
* Handles mouse down on the drag handle.
*
* @param {React.PointerEvent} e - The pointer down event.
* @param {MouseEvent} e - The mouse down event.
* @returns {void}
*/
const onDragHandlePointerDown = useCallback((e: React.PointerEvent) => {
const onDragHandleMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
// Capture the pointer to ensure we receive all pointer events
// even if the pointer moves outside the element
(e.target as HTMLElement).setPointerCapture(e.pointerId);
// Store the initial pointer position and chat width
// Store the initial mouse position and chat width
setIsMouseDown(true);
setMousePosition(e.clientX);
setDragChatWidth(_width);
@@ -338,7 +299,7 @@ const Chat = ({
// Indicate that resizing is in progress
dispatch(setChatIsResizing(true));
// Add visual feedback that we're dragging (cursor for mouse, not visible on touch)
// Add visual feedback that we're dragging
document.body.style.cursor = 'col-resize';
// Disable text selection during resize
@@ -346,12 +307,11 @@ const Chat = ({
}, [ _width, dispatch ]);
/**
* Drag handle pointer up handler.
* Supports both mouse and touch events via Pointer Events API.
* Drag handle mouse up handler.
*
* @returns {void}
*/
const onDragPointerUp = useCallback(() => {
const onDragMouseUp = useCallback(() => {
if (isMouseDown) {
setIsMouseDown(false);
dispatch(setChatIsResizing(false));
@@ -363,13 +323,12 @@ const Chat = ({
}, [ isMouseDown, dispatch ]);
/**
* Handles drag handle pointer move.
* Supports both mouse and touch events via Pointer Events API.
* Handles drag handle mouse move.
*
* @param {PointerEvent} e - The pointermove event.
* @param {MouseEvent} e - The mousemove event.
* @returns {void}
*/
const onChatResize = useCallback(throttle((e: PointerEvent) => {
const onChatResize = useCallback(throttle((e: MouseEvent) => {
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
// For chat panel resizing on the left edge:
// - Dragging left (decreasing X coordinate) should make the panel wider
@@ -393,14 +352,14 @@ const Chat = ({
// Set up event listeners when component mounts
useEffect(() => {
document.addEventListener('pointerup', onDragPointerUp);
document.addEventListener('pointermove', onChatResize);
document.addEventListener('mouseup', onDragMouseUp);
document.addEventListener('mousemove', onChatResize);
return () => {
document.removeEventListener('pointerup', onDragPointerUp);
document.removeEventListener('pointermove', onChatResize);
document.removeEventListener('mouseup', onDragMouseUp);
document.removeEventListener('mousemove', onChatResize);
};
}, [ onDragPointerUp, onChatResize ]);
}, [ onDragMouseUp, onChatResize ]);
/**
* Sends a text message.
@@ -641,7 +600,7 @@ const Chat = ({
(isMouseDown || _isResizing) && 'visible',
'dragHandleContainer'
) }
onPointerDown = { onDragHandlePointerDown }>
onMouseDown = { onDragHandleMouseDown }>
<div className = { cx(classes.dragHandle, 'dragHandle') } />
</div>
</div> : null

View File

@@ -33,23 +33,6 @@ export const MESSAGE_TYPE_REMOTE = 'remote';
export const SMALL_WIDTH_THRESHOLD = 580;
/**
* Drag handle dimensions for resizable chat.
*/
export const CHAT_DRAG_HANDLE_WIDTH = 9;
export const CHAT_DRAG_HANDLE_HEIGHT = 100;
/**
* Touch target size for chat drag handle on touch devices.
* Provides adequate hit area (44px) for comfortable tapping.
*/
export const CHAT_TOUCH_HANDLE_SIZE = 44;
/**
* Offset from edge for positioning the chat drag handle.
*/
export const CHAT_DRAG_HANDLE_OFFSET = 4;
/**
* Lobby message type.

View File

@@ -10,7 +10,7 @@ import { withStyles } from 'tss-react/mui';
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { isMobileBrowser, isTouchDevice, shouldEnableResize } from '../../../base/environment/utils';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
@@ -32,17 +32,12 @@ import {
import {
ASPECT_RATIO_BREAKPOINT,
DEFAULT_FILMSTRIP_WIDTH,
DRAG_HANDLE_HEIGHT,
DRAG_HANDLE_TOP_PANEL_HEIGHT,
DRAG_HANDLE_TOP_PANEL_WIDTH,
DRAG_HANDLE_WIDTH,
FILMSTRIP_TYPE,
MIN_STAGE_VIEW_HEIGHT,
MIN_STAGE_VIEW_WIDTH,
TILE_HORIZONTAL_MARGIN,
TILE_VERTICAL_MARGIN,
TOP_FILMSTRIP_HEIGHT,
TOUCH_DRAG_HANDLE_PADDING
TOP_FILMSTRIP_HEIGHT
} from '../../constants';
import {
getVerticalViewMaxWidth,
@@ -57,21 +52,6 @@ import ThumbnailWrapper from './ThumbnailWrapper';
const BACKGROUND_COLOR = 'rgba(51, 51, 51, .5)';
const TOUCH_DEVICE_PADDING = {
paddingLeft: `${TOUCH_DRAG_HANDLE_PADDING}px`,
paddingRight: `${TOUCH_DRAG_HANDLE_PADDING}px`,
paddingTop: 0,
paddingBottom: 0
};
const TOUCH_DEVICE_TOP_PANEL_PADDING = {
paddingLeft: 0,
paddingRight: 0,
paddingTop: `${TOUCH_DRAG_HANDLE_PADDING}px`,
paddingBottom: `${TOUCH_DRAG_HANDLE_PADDING}px`
};
const NON_TOUCH_DEVICE_PANEL = {
pading: 0
};
/**
* Creates the styles for the component.
@@ -81,14 +61,6 @@ const NON_TOUCH_DEVICE_PANEL = {
* @returns {Object}
*/
function styles(theme: Theme, props: IProps) {
const { _topPanelFilmstrip: isTopPanel } = props;
const _isTouchDevice = isTouchDevice();
const resizeEnabled = shouldEnableResize();
const handlePaddding = _isTouchDevice
? (isTopPanel ? TOUCH_DEVICE_TOP_PANEL_PADDING : TOUCH_DEVICE_PADDING)
: NON_TOUCH_DEVICE_PANEL;
const result = {
toggleFilmstripContainer: {
display: 'flex',
@@ -122,7 +94,7 @@ function styles(theme: Theme, props: IProps) {
margin: 0,
border: 'none',
'-webkit-appearance': 'none',
WebkitAppearance: 'none' as const,
'& svg': {
fill: theme.palette.icon01
@@ -150,27 +122,23 @@ function styles(theme: Theme, props: IProps) {
right: 0,
bottom: 0,
// On touch devices, handle is always visible via base styles, so no hover needed.
// On desktop, show handle on hover/focus.
...(!_isTouchDevice && {
'&:hover, &:focus-within': {
'& .resizable-filmstrip': {
backgroundColor: BACKGROUND_COLOR
},
'&:hover, &:focus-within': {
'& .resizable-filmstrip': {
backgroundColor: BACKGROUND_COLOR
},
'& .filmstrip-hover': {
backgroundColor: BACKGROUND_COLOR
},
'& .filmstrip-hover': {
backgroundColor: BACKGROUND_COLOR
},
'& .toggleFilmstripContainer': {
opacity: 1
},
'& .toggleFilmstripContainer': {
opacity: 1
},
'& .dragHandleContainer': {
visibility: 'visible' as const
}
'& .dragHandleContainer': {
visibility: 'visible' as const
}
}),
},
'.horizontal-filmstrip &.hidden': {
bottom: '-50px',
@@ -219,22 +187,14 @@ function styles(theme: Theme, props: IProps) {
dragHandleContainer: {
height: '100%',
width: `${DRAG_HANDLE_WIDTH}px`,
width: '9px',
backgroundColor: 'transparent',
position: 'relative' as const,
cursor: 'col-resize',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
// On touch devices, always visible if resize enabled. On desktop, hidden by default
visibility: (_isTouchDevice && resizeEnabled) ? 'visible' as const : 'hidden' as const,
marginLeft: 0,
marginTop: 0,
// Touch devices get padding for easier tapping
// Vertical filmstrip: left/right padding. Top panel: top/bottom padding.
...handlePaddding,
// Prevent touch scrolling while dragging
touchAction: 'none',
visibility: 'hidden' as const,
'&:hover': {
'& .dragHandle': {
@@ -253,21 +213,20 @@ function styles(theme: Theme, props: IProps) {
'&.top-panel': {
order: 2,
width: '100%',
height: `${DRAG_HANDLE_WIDTH}px`,
height: '9px',
cursor: 'row-resize',
'& .dragHandle': {
height: `${DRAG_HANDLE_TOP_PANEL_HEIGHT}px`,
width: `${DRAG_HANDLE_TOP_PANEL_WIDTH}px`
height: '3px',
width: '100px'
}
}
},
dragHandle: {
// Keep the same visual appearance on all devices
backgroundColor: theme.palette.filmstripDragHandle,
height: `${DRAG_HANDLE_HEIGHT}px`,
width: `${DRAG_HANDLE_WIDTH / 3}px`,
height: '100px',
width: '3px',
borderRadius: '1px'
}
};
@@ -354,11 +313,6 @@ export interface IProps extends WithTranslation {
*/
_isToolboxVisible: Boolean;
/**
* Whether the device has touch capability.
*/
_isTouchDevice?: boolean;
/**
* Whether or not the current layout is vertical filmstrip.
*/
@@ -404,11 +358,6 @@ export interface IProps extends WithTranslation {
*/
_resizableFilmstrip: boolean;
/**
* Whether resize functionality should be enabled based on device and screen size.
*/
_resizeEnabled?: boolean;
/**
* The number of rows in tile view.
*/
@@ -542,10 +491,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
this._onGridItemsRendered = this._onGridItemsRendered.bind(this);
this._onListItemsRendered = this._onListItemsRendered.bind(this);
this._onToggleButtonTouch = this._onToggleButtonTouch.bind(this);
this._onDragHandlePointerDown = this._onDragHandlePointerDown.bind(this);
this._onDragHandleClick = this._onDragHandleClick.bind(this);
this._onDragHandleTouchStart = this._onDragHandleTouchStart.bind(this);
this._onDragPointerUp = this._onDragPointerUp.bind(this);
this._onDragHandleMouseDown = this._onDragHandleMouseDown.bind(this);
this._onDragMouseUp = this._onDragMouseUp.bind(this);
this._onFilmstripResize = this._onFilmstripResize.bind(this);
this._throttledResize = throttle(
@@ -569,10 +516,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
handler: this._onShortcutToggleFilmstrip
}));
document.addEventListener('pointerup', this._onDragPointerUp);
document.addEventListener('mouseup', this._onDragMouseUp);
// @ts-ignore
document.addEventListener('pointermove', this._throttledResize);
document.addEventListener('mousemove', this._throttledResize);
}
/**
@@ -583,10 +530,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
override componentWillUnmount() {
this.props.dispatch(unregisterShortcut('F'));
document.removeEventListener('pointerup', this._onDragPointerUp);
document.removeEventListener('mouseup', this._onDragMouseUp);
// @ts-ignore
document.removeEventListener('pointermove', this._throttledResize);
document.removeEventListener('mousemove', this._throttledResize);
}
/**
@@ -731,9 +678,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
(isMouseDown || _alwaysShowResizeBar) && 'visible',
_topPanelFilmstrip && 'top-panel')
}
onClick = { this._onDragHandleClick }
onPointerDown = { this._onDragHandlePointerDown }
onTouchStart = { this._onDragHandleTouchStart }>
onMouseDown = { this._onDragHandleMouseDown }>
<div className = { clsx(classes.dragHandle, 'dragHandle') } />
</div>
{filmstrip}
@@ -746,23 +691,14 @@ class Filmstrip extends PureComponent <IProps, IState> {
}
/**
* Handles pointer down on the drag handle.
* Supports both mouse and touch events via Pointer Events API.
* Handles mouse down on the drag handle.
*
* @param {React.PointerEvent} e - The pointer down event.
* @param {MouseEvent} e - The mouse down event.
* @returns {void}
*/
_onDragHandlePointerDown(e: React.PointerEvent) {
_onDragHandleMouseDown(e: React.MouseEvent) {
const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props;
// Prevent toolbar from appearing and stop event propagation
e.preventDefault();
e.stopPropagation();
// Capture the pointer to ensure we receive all pointer events
// even if the pointer moves outside the element
(e.target as HTMLElement).setPointerCapture(e.pointerId);
this.setState({
isMouseDown: true,
mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX,
@@ -773,33 +709,11 @@ class Filmstrip extends PureComponent <IProps, IState> {
}
/**
* Prevents click events on drag handle from triggering toolbar.
*
* @param {React.MouseEvent} e - The click event.
* @returns {void}
*/
_onDragHandleClick(e: React.MouseEvent) {
e.preventDefault();
e.stopPropagation();
}
/**
* Prevents touch start events on drag handle from triggering toolbar.
*
* @param {React.TouchEvent} e - The touch start event.
* @returns {void}
*/
_onDragHandleTouchStart(e: React.TouchEvent) {
e.stopPropagation();
}
/**
* Drag handle pointer up handler.
* Supports both mouse and touch events via Pointer Events API.
* Drag handle mouse up handler.
*
* @returns {void}
*/
_onDragPointerUp() {
_onDragMouseUp() {
if (this.state.isMouseDown) {
this.setState({
isMouseDown: false
@@ -809,13 +723,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
}
/**
* Handles drag handle pointer move.
* Supports both mouse and touch events via Pointer Events API.
* Handles drag handle mouse move.
*
* @param {PointerEvent} e - The pointermove event.
* @param {MouseEvent} e - The mousemove event.
* @returns {void}
*/
_onFilmstripResize(e: PointerEvent) {
_onFilmstripResize(e: React.MouseEvent) {
if (this.state.isMouseDown) {
const {
dispatch,
@@ -1250,4 +1163,4 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
};
}
export default translate(connect(_mapStateToProps)(withStyles(Filmstrip, styles)));
export default withStyles(translate(connect(_mapStateToProps)(Filmstrip)), styles);

View File

@@ -269,19 +269,6 @@ export const MIN_STAGE_VIEW_HEIGHT = 700;
*/
export const MIN_STAGE_VIEW_WIDTH = 800;
/**
* Drag handle dimensions for resizable filmstrip.
*/
export const DRAG_HANDLE_WIDTH = 9;
export const DRAG_HANDLE_HEIGHT = 100;
export const DRAG_HANDLE_TOP_PANEL_HEIGHT = 3;
export const DRAG_HANDLE_TOP_PANEL_WIDTH = 100;
/**
* Touch padding added to each side of drag handle for easier tapping on touch devices.
*/
export const TOUCH_DRAG_HANDLE_PADDING = 6;
/**
* Horizontal margin used for the vertical filmstrip.
*/
@@ -311,9 +298,3 @@ export const MAX_ACTIVE_PARTICIPANTS = 6;
* Top filmstrip default height.
*/
export const TOP_FILMSTRIP_HEIGHT = 180;
/**
* Minimum screen width needed for functional filmstrip resizing.
* Calculated as stage minimum + filmstrip minimum (800px + 120px = 920px).
*/
export const MIN_FILMSTRIP_RESIZE_WIDTH = MIN_STAGE_VIEW_WIDTH + DEFAULT_FILMSTRIP_WIDTH;

View File

@@ -2,7 +2,7 @@ import { Theme } from '@mui/material/styles';
import { IReduxState } from '../app/types';
import { IStateful } from '../base/app/types';
import { isTouchDevice, shouldEnableResize } from '../base/environment/utils';
import { isMobileBrowser } from '../base/environment/utils';
import { MEDIA_TYPE } from '../base/media/constants';
import {
getLocalParticipant,
@@ -30,7 +30,6 @@ import {
DEFAULT_LOCAL_TILE_ASPECT_RATIO,
DISPLAY_AVATAR,
DISPLAY_VIDEO,
DRAG_HANDLE_WIDTH,
FILMSTRIP_GRID_BREAKPOINT,
FILMSTRIP_TYPE,
INDICATORS_TOOLTIP_POSITION,
@@ -46,7 +45,6 @@ import {
TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
TILE_VIEW_GRID_HORIZONTAL_MARGIN,
TILE_VIEW_GRID_VERTICAL_MARGIN,
TOUCH_DRAG_HANDLE_PADDING,
VERTICAL_VIEW_HORIZONTAL_MARGIN
} from './constants';
@@ -623,7 +621,6 @@ export function getIndicatorsTooltipPosition(thumbnailType?: string) {
/**
* Returns whether or not the filmstrip is resizable.
* On touch devices, resize is only enabled for larger screens (tablets, not phones).
*
* @param {Object} state - Redux state.
* @returns {boolean}
@@ -632,7 +629,7 @@ export function isFilmstripResizable(state: IReduxState) {
const { filmstrip } = state['features/base/config'];
const _currentLayout = getCurrentLayout(state);
return !filmstrip?.disableResizable && shouldEnableResize()
return !filmstrip?.disableResizable && !isMobileBrowser()
&& (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
}
@@ -665,13 +662,8 @@ export function getVerticalViewMaxWidth(state: IReduxState) {
// Adding 4px for the border-right and margin-right.
// On non-resizable filmstrip add 4px for the left margin and border.
// Also adding 7px for the scrollbar.
// Drag handle: DRAG_HANDLE_WIDTH + padding (TOUCH_DRAG_HANDLE_PADDING on each side for touch)
const dragHandleWidth = isTouchDevice()
? DRAG_HANDLE_WIDTH + (TOUCH_DRAG_HANDLE_PADDING * 2)
: DRAG_HANDLE_WIDTH;
maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? dragHandleWidth : 4);
// Also adding 7px for the scrollbar. Also adding 9px for the drag handle.
maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? 9 : 4);
return maxWidth;
}

View File

@@ -54,7 +54,7 @@ const useStyles = makeStyles<IStylesProps>()((theme, { isChatOpen }) => {
fontWeight: 600,
height: '100%',
[[ '& > *:first-child', '& > *:last-child' ] as any]: {
[[ '& > *:first-of-type', '& > *:last-of-type' ] as any]: {
flexShrink: 0
},
@@ -116,11 +116,11 @@ const useStyles = makeStyles<IStylesProps>()((theme, { isChatOpen }) => {
antiCollapse: {
fontSize: 0,
'&:first-child': {
'&:first-of-type': {
display: 'none'
},
'&:first-child + *': {
'&:first-of-type + *': {
marginTop: 0
}
},

View File

@@ -86,7 +86,7 @@ const useStyles = makeStyles()(theme => {
slider: {
// Use an additional class here to override global CSS specificity
'&.custom-slider': {
'-webkit-appearance': 'none',
WebkitAppearance: 'none',
background: 'transparent',
height,
left: 0,
@@ -104,11 +104,11 @@ const useStyles = makeStyles()(theme => {
},
'&::-webkit-slider-runnable-track': {
'-webkit-appearance': 'none',
WebkitAppearance: 'none',
...inputTrack
},
'&::-webkit-slider-thumb': {
'-webkit-appearance': 'none',
WebkitAppearance: 'none',
position: 'relative',
top: -6,
...inputThumb

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { App } from './features/app/components/App.web';
import { getLogger } from './features/base/logging/functions';
@@ -72,14 +72,17 @@ globalNS.entryPoints = {
WHITEBOARD: WhiteboardApp
};
const _roots = new Map();
globalNS.renderEntryPoint = ({
Component,
props = {},
elementId = 'react'
}) => {
/* eslint-disable-next-line react/no-deprecated */
ReactDOM.render(
<Component { ...props } />,
document.getElementById(elementId)
);
const element = document.getElementById(elementId);
if (!_roots.has(elementId)) {
_roots.set(elementId, createRoot(element));
}
_roots.get(elementId).render(<Component { ...props } />);
};