snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
555
react/features/conference/components/web/Conference.tsx
Normal file
555
react/features/conference/components/web/Conference.tsx
Normal file
@@ -0,0 +1,555 @@
|
||||
import { throttle } from 'lodash-es';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect as reactReduxConnect, useDispatch, useSelector, useStore } from 'react-redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getConferenceNameForTitle } from '../../../base/conference/functions';
|
||||
import { hangup } from '../../../base/connection/actions.web';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils.web';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AudioTracksContainer from '../../../base/media/components/web/AudioTracksContainer';
|
||||
import { setColorAlpha } from '../../../base/util/helpers';
|
||||
import { openChat, setFocusedTab } from '../../../chat/actions.web';
|
||||
import Chat from '../../../chat/components/web/Chat';
|
||||
import { ChatTabs } from '../../../chat/constants';
|
||||
import CustomPanel from '../../../custom-panel/components/web/CustomPanel';
|
||||
import { isFileUploadingEnabled, processFiles } from '../../../file-sharing/functions.any';
|
||||
import MainFilmstrip from '../../../filmstrip/components/web/MainFilmstrip';
|
||||
import ScreenshareFilmstrip from '../../../filmstrip/components/web/ScreenshareFilmstrip';
|
||||
import StageFilmstrip from '../../../filmstrip/components/web/StageFilmstrip';
|
||||
import CalleeInfoContainer from '../../../invite/components/callee-info/CalleeInfoContainer';
|
||||
import LargeVideo from '../../../large-video/components/LargeVideo.web';
|
||||
import LobbyScreen from '../../../lobby/components/web/LobbyScreen';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import SecondScreenPortals from '../../../multi-screen/components/SecondScreenPortals';
|
||||
import { getOverlayToRender } from '../../../overlay/functions.web';
|
||||
import ParticipantsPane from '../../../participants-pane/components/web/ParticipantsPane';
|
||||
import Prejoin from '../../../prejoin/components/web/Prejoin';
|
||||
import { isPrejoinPageVisible } from '../../../prejoin/functions.web';
|
||||
import ReactionAnimations from '../../../reactions/components/web/ReactionsAnimations';
|
||||
import { isTimeTimerExpiredUnacknowledged } from '../../../time-timer/functions';
|
||||
import { toggleToolboxVisible } from '../../../toolbox/actions.any';
|
||||
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
|
||||
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
|
||||
import Toolbox from '../../../toolbox/components/web/Toolbox';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
|
||||
import { getCurrentLayout } from '../../../video-layout/functions.any';
|
||||
import VisitorsQueue from '../../../visitors/components/web/VisitorsQueue';
|
||||
import { showVisitorsQueue } from '../../../visitors/functions';
|
||||
import { init } from '../../actions.web';
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../../functions.web';
|
||||
import {
|
||||
AbstractConference,
|
||||
type AbstractProps,
|
||||
abstractMapStateToProps
|
||||
} from '../AbstractConference';
|
||||
|
||||
import ConferenceInfo from './ConferenceInfo';
|
||||
import { default as Notice } from './Notice';
|
||||
|
||||
/**
|
||||
* DOM events for when full screen mode has changed. Different browsers need
|
||||
* different vendor prefixes.
|
||||
*
|
||||
* @private
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
const FULL_SCREEN_EVENTS = [
|
||||
'webkitfullscreenchange',
|
||||
'mozfullscreenchange',
|
||||
'fullscreenchange'
|
||||
];
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Conference}.
|
||||
*/
|
||||
interface IProps extends AbstractProps, WithTranslation {
|
||||
|
||||
/**
|
||||
* The alpha(opacity) of the background.
|
||||
*/
|
||||
_backgroundAlpha?: number;
|
||||
|
||||
/**
|
||||
* Are any overlays visible?
|
||||
*/
|
||||
_isAnyOverlayVisible: boolean;
|
||||
|
||||
/**
|
||||
* The CSS class to apply to the root of {@link Conference} to modify the
|
||||
* application layout.
|
||||
*/
|
||||
_layoutClassName: string;
|
||||
|
||||
/**
|
||||
* The config specified interval for triggering mouseMoved iframe api events.
|
||||
*/
|
||||
_mouseMoveCallbackInterval?: number;
|
||||
|
||||
/**
|
||||
*Whether or not the notifications should be displayed in the overflow drawer.
|
||||
*/
|
||||
_overflowDrawer: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced.
|
||||
*/
|
||||
_reducedUI: boolean;
|
||||
|
||||
/**
|
||||
* Name for this conference room.
|
||||
*/
|
||||
_roomName: string;
|
||||
|
||||
/**
|
||||
* If lobby page is visible or not.
|
||||
*/
|
||||
_showLobby: boolean;
|
||||
|
||||
/**
|
||||
* If prejoin page is visible or not.
|
||||
*/
|
||||
_showPrejoin: boolean;
|
||||
|
||||
/**
|
||||
* If visitors queue page is visible or not.
|
||||
* NOTE: This should be set to true once we received an error on connect. Before the first connect this will always
|
||||
* be false.
|
||||
*/
|
||||
_showVisitorsQueue: boolean;
|
||||
|
||||
/**
|
||||
* Whether the meeting time-timer has reached / passed the scheduled end.
|
||||
*/
|
||||
_timerExpired: boolean;
|
||||
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the prejoin screen should be displayed and false otherwise.
|
||||
*
|
||||
* @param {IProps} props - The props object.
|
||||
* @returns {boolean} - True if the prejoin screen should be displayed and false otherwise.
|
||||
*/
|
||||
function shouldShowPrejoin({ _showLobby, _showPrejoin, _showVisitorsQueue }: IProps) {
|
||||
return _showPrejoin && !_showVisitorsQueue && !_showLobby;
|
||||
}
|
||||
|
||||
/**
|
||||
* The conference page of the Web application.
|
||||
*/
|
||||
class Conference extends AbstractConference<IProps, any> {
|
||||
_originalOnMouseMove: Function;
|
||||
_originalOnShowToolbar: Function;
|
||||
|
||||
/**
|
||||
* Initializes a new Conference instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const { _mouseMoveCallbackInterval } = props;
|
||||
|
||||
// Throttle and bind this component's mousemove handler to prevent it
|
||||
// from firing too often.
|
||||
this._originalOnShowToolbar = this._onShowToolbar;
|
||||
this._originalOnMouseMove = this._onMouseMove;
|
||||
|
||||
this._onShowToolbar = throttle(
|
||||
() => this._originalOnShowToolbar(),
|
||||
100,
|
||||
{
|
||||
leading: true,
|
||||
trailing: false
|
||||
});
|
||||
|
||||
this._onMouseMove = throttle(
|
||||
event => this._originalOnMouseMove(event),
|
||||
_mouseMoveCallbackInterval,
|
||||
{
|
||||
leading: true,
|
||||
trailing: false
|
||||
});
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
||||
this._onVideospaceTouchStart = this._onVideospaceTouchStart.bind(this);
|
||||
this._setBackground = this._setBackground.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the connection and get the UI ready for the conference.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override componentDidMount() {
|
||||
document.title = `${this.props._roomName} | ${interfaceConfig.APP_NAME}`;
|
||||
this._start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls into legacy UI to update the application layout, if necessary.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
override componentDidUpdate(prevProps: IProps) {
|
||||
if (this.props._shouldDisplayTileView
|
||||
=== prevProps._shouldDisplayTileView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: For now VideoLayout is being called as LargeVideo and Filmstrip
|
||||
// sizing logic is still handled outside of React. Once all components
|
||||
// are in react they should calculate size on their own as much as
|
||||
// possible and pass down sizings.
|
||||
VideoLayout.refreshLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the conference when component will be
|
||||
* unmounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override componentWillUnmount() {
|
||||
APP.UI.unbindEvents();
|
||||
|
||||
FULL_SCREEN_EVENTS.forEach(name =>
|
||||
document.removeEventListener(name, this._onFullScreenChange));
|
||||
|
||||
APP.conference.isJoined() && this.props.dispatch(hangup());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const {
|
||||
_isAnyOverlayVisible,
|
||||
_layoutClassName,
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
_reducedUI,
|
||||
_showLobby,
|
||||
_showPrejoin,
|
||||
_showVisitorsQueue,
|
||||
_timerExpired,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
const videospaceClassName = _timerExpired ? 'timer-expired' : undefined;
|
||||
|
||||
if (_reducedUI) {
|
||||
return (
|
||||
<div
|
||||
id = 'layout_wrapper'
|
||||
onMouseEnter = { this._onMouseEnter }
|
||||
onMouseLeave = { this._onMouseLeave }
|
||||
onMouseMove = { this._onMouseMove }
|
||||
ref = { this._setBackground }>
|
||||
<Chat />
|
||||
<div
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||
<ConferenceInfo />
|
||||
<Notice />
|
||||
<div
|
||||
className = { videospaceClassName }
|
||||
id = 'videospace'
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
</div>
|
||||
<AudioTracksContainer />
|
||||
<span
|
||||
aria-level = { 1 }
|
||||
className = 'sr-only'
|
||||
role = 'heading'>
|
||||
{ t('toolbar.accessibilityLabel.heading') }
|
||||
</span>
|
||||
<Toolbox />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id = 'layout_wrapper'
|
||||
onMouseEnter = { this._onMouseEnter }
|
||||
onMouseLeave = { this._onMouseLeave }
|
||||
onMouseMove = { this._onMouseMove }
|
||||
ref = { this._setBackground }>
|
||||
<Chat />
|
||||
<div
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||
{ _showPrejoin || _showLobby || <ConferenceInfo /> }
|
||||
<Notice />
|
||||
<div
|
||||
className = { videospaceClassName }
|
||||
id = 'videospace'
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
{
|
||||
_showPrejoin || _showLobby || (<>
|
||||
<StageFilmstrip />
|
||||
<ScreenshareFilmstrip />
|
||||
<MainFilmstrip />
|
||||
</>)
|
||||
}
|
||||
</div>
|
||||
<AudioTracksContainer />
|
||||
<SecondScreenPortals />
|
||||
{ _showPrejoin || _showLobby || (
|
||||
<>
|
||||
<span
|
||||
aria-level = { 1 }
|
||||
className = 'sr-only'
|
||||
role = 'heading'>
|
||||
{ t('toolbar.accessibilityLabel.heading') }
|
||||
</span>
|
||||
<Toolbox />
|
||||
</>
|
||||
)}
|
||||
|
||||
{_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer
|
||||
? <JitsiPortal className = 'notification-portal'>
|
||||
{this.renderNotificationsContainer({ portal: true })}
|
||||
</JitsiPortal>
|
||||
: this.renderNotificationsContainer())
|
||||
}
|
||||
<CalleeInfoContainer />
|
||||
{ shouldShowPrejoin(this.props) && <Prejoin />}
|
||||
{ (_showLobby && !_showVisitorsQueue) && <LobbyScreen />}
|
||||
{ _showVisitorsQueue && <VisitorsQueue />}
|
||||
</div>
|
||||
<ParticipantsPane />
|
||||
<CustomPanel />
|
||||
<ReactionAnimations />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets custom background opacity based on config. It also applies the
|
||||
* opacity on parent element, as the parent element is not accessible directly,
|
||||
* only though it's child.
|
||||
*
|
||||
* @param {Object} element - The DOM element for which to apply opacity.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setBackground(element: HTMLDivElement) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props._backgroundAlpha !== undefined) {
|
||||
const elemColor = element.style.background;
|
||||
const alphaElemColor = setColorAlpha(elemColor, this.props._backgroundAlpha);
|
||||
|
||||
element.style.background = alphaElemColor;
|
||||
if (element.parentElement) {
|
||||
const parentColor = element.parentElement.style.background;
|
||||
const alphaParentColor = setColorAlpha(parentColor, this.props._backgroundAlpha);
|
||||
|
||||
element.parentElement.style.background = alphaParentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler used for touch start on Video container.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onVideospaceTouchStart() {
|
||||
this.props.dispatch(toggleToolboxVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Redux state when full screen mode has been enabled or
|
||||
* disabled.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onFullScreenChange() {
|
||||
this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers iframe API mouseEnter event.
|
||||
*
|
||||
* @param {MouseEvent} event - The mouse event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMouseEnter(event: React.MouseEvent) {
|
||||
APP.API.notifyMouseEnter(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers iframe API mouseLeave event.
|
||||
*
|
||||
* @param {MouseEvent} event - The mouse event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMouseLeave(event: React.MouseEvent) {
|
||||
APP.API.notifyMouseLeave(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers iframe API mouseMove event.
|
||||
*
|
||||
* @param {MouseEvent} event - The mouse event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMouseMove(event: React.MouseEvent) {
|
||||
APP.API.notifyMouseMove(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the toolbar.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onShowToolbar() {
|
||||
this.props.dispatch(showToolbox());
|
||||
}
|
||||
|
||||
/**
|
||||
* Until we don't rewrite UI using react components
|
||||
* we use UI.start from old app. Also method translates
|
||||
* component right after it has been mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_start() {
|
||||
APP.UI.start();
|
||||
APP.UI.bindEvents();
|
||||
|
||||
FULL_SCREEN_EVENTS.forEach(name =>
|
||||
document.addEventListener(name, this._onFullScreenChange));
|
||||
|
||||
const { dispatch, t } = this.props;
|
||||
|
||||
// if we will be showing prejoin we don't want to call connect from init.
|
||||
// Connect will be dispatched from prejoin screen.
|
||||
dispatch(init(!shouldShowPrejoin(this.props)));
|
||||
|
||||
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code Conference} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { backgroundAlpha, mouseMoveCallbackInterval } = state['features/base/config'];
|
||||
const { overflowDrawer } = state['features/toolbox'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_backgroundAlpha: backgroundAlpha,
|
||||
_isAnyOverlayVisible: Boolean(getOverlayToRender(state)),
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state) ?? ''],
|
||||
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
|
||||
_overflowDrawer: overflowDrawer,
|
||||
_reducedUI: reducedUI,
|
||||
_roomName: getConferenceNameForTitle(state),
|
||||
_showLobby: getIsLobbyVisible(state),
|
||||
_showPrejoin: isPrejoinPageVisible(state),
|
||||
_showVisitorsQueue: showVisitorsQueue(state),
|
||||
_timerExpired: isTimeTimerExpiredUnacknowledged(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default reactReduxConnect(_mapStateToProps)(translate(props => {
|
||||
const dispatch = useDispatch();
|
||||
const store = useStore();
|
||||
|
||||
const [ isDragging, setIsDragging ] = useState(false);
|
||||
|
||||
const { isOpen: isChatOpen } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const isFileUploadEnabled = useSelector(isFileUploadingEnabled);
|
||||
const isOnPrejoin = useSelector(isPrejoinPageVisible);
|
||||
|
||||
const handleDragEnter = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isFileUploadEnabled || isOnPrejoin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
if (!isChatOpen) {
|
||||
dispatch(openChat());
|
||||
}
|
||||
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
|
||||
}
|
||||
}, [ isChatOpen, isDragging, isFileUploadEnabled, isOnPrejoin ]);
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
|
||||
if (!isFileUploadEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.dataTransfer.files?.length > 0) {
|
||||
processFiles(e.dataTransfer.files, store);
|
||||
}
|
||||
}, [ isFileUploadEnabled, processFiles ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid = 'conference-drag-zone'
|
||||
onDragEnter = { handleDragEnter }
|
||||
onDragLeave = { handleDragLeave }
|
||||
onDragOver = { handleDragOver }
|
||||
onDrop = { handleDrop }>
|
||||
{/* @ts-ignore */}
|
||||
<Conference { ...props } />
|
||||
</div>
|
||||
);
|
||||
}));
|
||||
Reference in New Issue
Block a user