mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-01 20:32:27 +00:00
Compare commits
13 Commits
rm-dead-co
...
6991
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cd62bc132 | ||
|
|
123a74b38b | ||
|
|
dbeca806bb | ||
|
|
f790d3e3ed | ||
|
|
a12f7fc4d2 | ||
|
|
456ce38a10 | ||
|
|
72ef1668f2 | ||
|
|
fce8f52574 | ||
|
|
8fcfd7a308 | ||
|
|
04a41395c8 | ||
|
|
18e8201167 | ||
|
|
27b8794d8c | ||
|
|
3cb0df579c |
@@ -141,7 +141,7 @@ import {
|
||||
showNotification,
|
||||
showWarningNotification
|
||||
} from './react/features/notifications';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import { initPrejoin, makePrecallTest, setJoiningInProgress } from './react/features/prejoin/actions';
|
||||
import { isPrejoinPageVisible } from './react/features/prejoin/functions';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
&-content {
|
||||
position: relative;
|
||||
right: auto;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 4px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
|
||||
@@ -3,49 +3,38 @@
|
||||
display: inline-block;
|
||||
|
||||
&-container {
|
||||
max-height: 344px;
|
||||
background: $menuBG;
|
||||
border-radius: 3px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
&-entry {
|
||||
cursor: pointer;
|
||||
height: 168px;
|
||||
margin-bottom: 8px;
|
||||
height: 138px;
|
||||
width: 244px;
|
||||
position: relative;
|
||||
width: 284px;
|
||||
margin: 0 7px 4px;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
border: 3px solid #31B76A;
|
||||
border-radius: 3px;
|
||||
cursor: default;
|
||||
height: 162px;
|
||||
width: 278px;
|
||||
border: 2px solid #4687ED;
|
||||
}
|
||||
}
|
||||
|
||||
&-video {
|
||||
border-radius: 3px;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-overlay {
|
||||
background: rgba(42, 58, 75, 0.6);
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&-error {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -56,23 +45,22 @@
|
||||
}
|
||||
|
||||
&-label {
|
||||
bottom: 8px;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: 100%;
|
||||
padding: 8px;
|
||||
z-index: 2;
|
||||
|
||||
&-container {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
background-color: #131519;
|
||||
border-radius: 3px;
|
||||
padding: 2px 8px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin: 0 auto;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 600;
|
||||
max-width: calc(100% - 16px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -80,8 +68,8 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
padding: 0;
|
||||
|
||||
&-checkbox-container {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,6 +916,7 @@
|
||||
"localRecordingVideoWarning": "Um Ihr eigenes Kamerabild aufzuzeichnen, müssen Sie Ihre Kamera beim Start der Aufnahme einschalten",
|
||||
"localRecordingWarning": "Bitte prüfen Sie, dass das aktuelle Tab auswählen, um Bild und Ton aufzuzeichnen. Die Länge der Aufzeichnung ist aktuell auf 1GB beschränkt, was ungefähr 100 Minuten entspricht.",
|
||||
"loggedIn": "Als {{userName}} angemeldet",
|
||||
"noMicPermission": "Zugriff auf Mikrofon fehlgeschlagen. Bitte erlauben Sie den Zugriff auf das Mikrofon.",
|
||||
"noStreams": "Kein Ton oder Video erkannt.",
|
||||
"off": "Aufnahme gestoppt",
|
||||
"offBy": "{{name}} stoppte die Aufnahme",
|
||||
@@ -1285,6 +1286,7 @@
|
||||
"grantModerator": "Moderationsrechte vergeben",
|
||||
"hideSelfView": "Eigene Ansicht ausblenden",
|
||||
"kick": "Hinauswerfen",
|
||||
"mirrorVideo": "Mein Video spiegeln",
|
||||
"moderator": "Moderation",
|
||||
"mute": "Person ist stumm geschaltet",
|
||||
"muted": "Stummgeschaltet",
|
||||
|
||||
@@ -1286,6 +1286,7 @@
|
||||
"grantModerator": "Grant Moderator Rights",
|
||||
"hideSelfView": "Hide self view",
|
||||
"kick": "Kick out",
|
||||
"mirrorVideo": "Mirror my video",
|
||||
"moderator": "Moderator",
|
||||
"mute": "Participant is muted",
|
||||
"muted": "Muted",
|
||||
|
||||
@@ -71,8 +71,13 @@ import {
|
||||
import { appendSuffix } from '../../react/features/display-name';
|
||||
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
|
||||
import { setMediaEncryptionKey, toggleE2EE } from '../../react/features/e2ee/actions';
|
||||
import { addStageParticipant, resizeFilmStrip, setVolume } from '../../react/features/filmstrip/actions.web';
|
||||
import { isStageFilmstripAvailable } from '../../react/features/filmstrip/functions.web';
|
||||
import {
|
||||
addStageParticipant,
|
||||
resizeFilmStrip,
|
||||
setVolume,
|
||||
togglePinStageParticipant
|
||||
} from '../../react/features/filmstrip/actions.web';
|
||||
import { getPinnedActiveParticipants, isStageFilmstripAvailable } from '../../react/features/filmstrip/functions.web';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import {
|
||||
selectParticipantInLargeVideo
|
||||
@@ -241,6 +246,22 @@ function initCommands() {
|
||||
logger.debug('Pin participant command received');
|
||||
|
||||
const state = APP.store.getState();
|
||||
|
||||
// if id not provided, unpin everybody.
|
||||
if (!id) {
|
||||
if (isStageFilmstripAvailable(state)) {
|
||||
const pinnedParticipants = getPinnedActiveParticipants(state);
|
||||
|
||||
pinnedParticipants?.forEach(p => {
|
||||
APP.store.dispatch(togglePinStageParticipant(p.participantId));
|
||||
});
|
||||
} else {
|
||||
APP.store.dispatch(pinParticipant());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const participant = videoType === VIDEO_TYPE.DESKTOP
|
||||
? getVirtualScreenshareParticipantByOwnerId(state, id) : getParticipantById(state, id);
|
||||
|
||||
@@ -254,7 +275,7 @@ function initCommands() {
|
||||
|
||||
const participantId = participant.id;
|
||||
|
||||
if (isStageFilmstripAvailable(APP.store.getState())) {
|
||||
if (isStageFilmstripAvailable(state)) {
|
||||
APP.store.dispatch(addStageParticipant(participantId, true));
|
||||
} else {
|
||||
APP.store.dispatch(pinParticipant(participantId));
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -73,7 +73,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -13553,8 +13553,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-AAEClrQNOVHNO1lKr/F1SOyiduZfI6bql3eiIxC3LZ5cBcyoRVmwI6uiAbLail0VkuKnTecEWcfYZ9lvokxrMw==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-lzgRkJtdlZ7bfq2seBuONRM6UND8NJVjMOZPlVoq7uP4UuxffBztsoHGc0g5Y5zEqi1AnfYLwVZZvXkpd82iew==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -30611,8 +30611,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-AAEClrQNOVHNO1lKr/F1SOyiduZfI6bql3eiIxC3LZ5cBcyoRVmwI6uiAbLail0VkuKnTecEWcfYZ9lvokxrMw==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-lzgRkJtdlZ7bfq2seBuONRM6UND8NJVjMOZPlVoq7uP4UuxffBztsoHGc0g5Y5zEqi1AnfYLwVZZvXkpd82iew==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
||||
@@ -30,8 +30,6 @@ import {
|
||||
// @ts-ignore
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
import { clearNotifications } from '../notifications/actions';
|
||||
// @ts-ignore
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
import { addTrackStateToURL, getDefaultURL } from './functions.native';
|
||||
import logger from './logger';
|
||||
@@ -177,7 +175,6 @@ export function maybeRedirectToWelcomePage(options: any) { // eslint-disable-lin
|
||||
*/
|
||||
export function reloadNow() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
dispatch(setFatalError(undefined));
|
||||
|
||||
const state = getState();
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { clearNotifications, showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { setFatalError } from '../overlay/actions';
|
||||
import { isWelcomePageEnabled } from '../welcome/functions';
|
||||
|
||||
import {
|
||||
@@ -222,7 +221,6 @@ export function maybeRedirectToWelcomePage(options: { feedbackSubmitted?: boolea
|
||||
*/
|
||||
export function reloadNow() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
dispatch(setFatalError(undefined));
|
||||
|
||||
const state = getState();
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { BaseApp } from '../../base/app';
|
||||
import { toURLString } from '../../base/util';
|
||||
import { OverlayContainer } from '../../overlay';
|
||||
import { appNavigate } from '../actions';
|
||||
import { getDefaultURL } from '../functions';
|
||||
|
||||
@@ -73,23 +70,7 @@ export class AbstractApp extends BaseApp<Props, *> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an extra {@link ReactElement}s to be added (unconditionally)
|
||||
* alongside the main element.
|
||||
*
|
||||
* @abstract
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_createExtraElement() {
|
||||
return (
|
||||
<Fragment>
|
||||
<OverlayContainer />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
_createMainElement: (React$Element<*>, Object) => ?React$Element<*>;
|
||||
_createMainElement: (React.ReactElement, Object) => ?React.ReactElement;
|
||||
|
||||
/**
|
||||
* Gets the default URL to be opened when this {@code App} mounts.
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import GlobalStyles from '../../base/ui/components/GlobalStyles.web';
|
||||
import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider.web';
|
||||
import DialogContainer from '../../base/ui/components/web/DialogContainer';
|
||||
import { ChromeExtensionBanner } from '../../chrome-extension-banner';
|
||||
import OverlayContainer from '../../overlay/components/web/OverlayContainer';
|
||||
|
||||
import { AbstractApp } from './AbstractApp';
|
||||
|
||||
@@ -14,12 +13,30 @@ import { AbstractApp } from './AbstractApp';
|
||||
import '../middlewares';
|
||||
import '../reducers';
|
||||
|
||||
|
||||
/**
|
||||
* Root app {@code Component} on Web/React.
|
||||
*
|
||||
* @augments AbstractApp
|
||||
*/
|
||||
export class App extends AbstractApp {
|
||||
|
||||
/**
|
||||
* Creates an extra {@link ReactElement}s to be added (unconditionally)
|
||||
* alongside the main element.
|
||||
*
|
||||
* @abstract
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_createExtraElement() {
|
||||
return (
|
||||
<Fragment>
|
||||
<OverlayContainer />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
||||
* the top most component.
|
||||
|
||||
@@ -52,9 +52,7 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
|
||||
const settings = state['features/base/settings'];
|
||||
const config: IConfig = {};
|
||||
|
||||
// FIXME: P2P is currently temporality disabled on mobile.
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false && typeof settings.disableP2P !== 'undefined') {
|
||||
if (typeof settings.disableP2P !== 'undefined') {
|
||||
config.p2p = { enabled: !settings.disableP2P };
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,8 @@ const INITIAL_RN_STATE: IConfig = {
|
||||
// than requiring this override here...
|
||||
|
||||
p2p: {
|
||||
// Temporarily disable P2P on mobile while we sort out some (codec?) issues.
|
||||
enabled: false,
|
||||
disabledCodec: 'vp9',
|
||||
preferredCodec: 'h264'
|
||||
preferredCodec: 'vp8'
|
||||
},
|
||||
|
||||
videoQuality: {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Container, Text } from '../../react';
|
||||
import { type StyleType } from '../../styles';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Children of the component.
|
||||
*/
|
||||
children: string | React$Node,
|
||||
|
||||
style: ?StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic dialog content container to provide the same styling for all custom
|
||||
* dialogs.
|
||||
*/
|
||||
export default class DialogContent extends Component<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { children, style } = this.props;
|
||||
|
||||
const childrenComponent = typeof children === 'string'
|
||||
? <Text style = { style }>{ children }</Text>
|
||||
: children;
|
||||
|
||||
return (
|
||||
<Container style = { styles.dialogContainer }>
|
||||
{ childrenComponent }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './_';
|
||||
|
||||
export { default as DialogContent } from './DialogContent';
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
// @ts-ignore
|
||||
import { randomInt } from '@jitsi/js-utils/random';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { appNavigate, reloadNow } from '../../../../app/actions.native';
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { translate } from '../../../i18n/functions';
|
||||
import { isFatalJitsiConnectionError } from '../../../lib-jitsi-meet/functions.native';
|
||||
import { connect } from '../../../redux/functions';
|
||||
// @ts-ignore
|
||||
import logger from '../../logger';
|
||||
|
||||
// @ts-ignore
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link PageReloadDialog}.
|
||||
*/
|
||||
interface IPageReloadDialogProps extends WithTranslation {
|
||||
dispatch: Dispatch<any>;
|
||||
isNetworkFailure: boolean;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of
|
||||
* {@link PageReloadDialog}.
|
||||
*/
|
||||
interface IPageReloadDialogState {
|
||||
message: string;
|
||||
timeLeft: number;
|
||||
timeoutSeconds: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React Component that is shown before the
|
||||
* conference is reloaded.
|
||||
* Shows a warning message and counts down towards the re-load.
|
||||
*/
|
||||
class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDialogState> {
|
||||
|
||||
// @ts-ignore
|
||||
_interval: IntervalID;
|
||||
|
||||
/**
|
||||
* Initializes a new PageReloadOverlay instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
* @public
|
||||
*/
|
||||
constructor(props: IPageReloadDialogProps) {
|
||||
super(props);
|
||||
|
||||
const timeoutSeconds = 10 + randomInt(0, 20);
|
||||
|
||||
let message, title;
|
||||
|
||||
if (this.props.isNetworkFailure) {
|
||||
title = 'dialog.conferenceDisconnectTitle';
|
||||
message = 'dialog.conferenceDisconnectMsg';
|
||||
} else {
|
||||
title = 'dialog.conferenceReloadTitle';
|
||||
message = 'dialog.conferenceReloadMsg';
|
||||
}
|
||||
|
||||
this.state = {
|
||||
message,
|
||||
timeLeft: timeoutSeconds,
|
||||
timeoutSeconds,
|
||||
title
|
||||
};
|
||||
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onReloadNow = this._onReloadNow.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* React Component method that executes once component is mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
const { timeLeft } = this.state;
|
||||
|
||||
logger.info(
|
||||
`The conference will be reloaded after ${
|
||||
this.state.timeoutSeconds} seconds.`);
|
||||
|
||||
this._interval
|
||||
= setInterval(
|
||||
() => {
|
||||
if (timeLeft === 0) {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
|
||||
dispatch(reloadNow());
|
||||
} else {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
timeLeft: prevState.timeLeft - 1
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the timer interval.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicking of the "Cancel" button. It will navigate back to the
|
||||
* welcome page.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onCancel() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicking on the "Reload Now" button. It will navigate to the same
|
||||
* conference URL as before immediately, without waiting for the timer to
|
||||
* kick in.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onReloadNow() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(reloadNow());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { message, timeLeft, title } = this.state;
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
cancelLabel = 'dialog.Cancel'
|
||||
confirmLabel = 'dialog.rejoinNow'
|
||||
descriptionKey = { `${t(message, { seconds: timeLeft })}` }
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onReloadNow }
|
||||
title = { title } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated component's props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* message: string,
|
||||
* reason: string,
|
||||
* title: string
|
||||
* }}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { error: conferenceError } = state['features/base/conference'];
|
||||
const { error: configError } = state['features/base/config'];
|
||||
const { error: connectionError } = state['features/base/connection'];
|
||||
const { fatalError } = state['features/overlay'];
|
||||
|
||||
const fatalConnectionError
|
||||
// @ts-ignore
|
||||
= connectionError && isFatalJitsiConnectionError(connectionError);
|
||||
const fatalConfigError = fatalError === configError;
|
||||
|
||||
const isNetworkFailure = fatalConfigError || fatalConnectionError;
|
||||
|
||||
let reason;
|
||||
|
||||
if (conferenceError) {
|
||||
reason = `error.conference.${conferenceError.name}`;
|
||||
} else if (connectionError) {
|
||||
reason = `error.conference.${connectionError.name}`;
|
||||
} else if (configError) {
|
||||
reason = `error.config.${configError.name}`;
|
||||
} else {
|
||||
logger.error('No reload reason defined!');
|
||||
}
|
||||
|
||||
return {
|
||||
isNetworkFailure,
|
||||
reason
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(PageReloadDialog));
|
||||
@@ -5,6 +5,7 @@ export { default as ConfirmDialog } from './ConfirmDialog';
|
||||
export { default as DialogContainer } from './DialogContainer';
|
||||
export { default as AlertDialog } from './AlertDialog';
|
||||
export { default as InputDialog } from './InputDialog';
|
||||
export { default as PageReloadDialog } from './PageReloadDialog';
|
||||
|
||||
// NOTE: Some dialogs reuse the style of these base classes for consistency
|
||||
// and as we're in a /native namespace, it's safe to export the styles.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { BoxModel, createStyleSheet } from '../../styles';
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of {@code Dialog}.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* Unified container for a consistent Dialog style.
|
||||
*/
|
||||
dialogContainer: {
|
||||
paddingHorizontal: BoxModel.padding,
|
||||
paddingVertical: 1.5 * BoxModel.padding
|
||||
}
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Placeholder styles for web to be able to use cross platform components
|
||||
* unmodified such as {@code DialogContent}.
|
||||
*/
|
||||
export default {};
|
||||
@@ -1,9 +1,12 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { IStateful } from '../app/types';
|
||||
import { toState } from '../redux/functions';
|
||||
|
||||
// @ts-ignore
|
||||
import JitsiMeetJS from './_';
|
||||
|
||||
|
||||
const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
|
||||
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
|
||||
// @ts-ignore
|
||||
import Video from './web/Video';
|
||||
|
||||
export default Video;
|
||||
@@ -7,6 +7,7 @@ import JitsiPortal from '../../../../toolbox/components/web/JitsiPortal';
|
||||
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
|
||||
import participantsPaneTheme from '../../../components/themes/participantsPaneTheme.json';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { spacing } from '../../Tokens';
|
||||
|
||||
/**
|
||||
* Get a style property from a style declaration as a float.
|
||||
@@ -34,6 +35,9 @@ const getComputedOuterHeight = (element: HTMLElement) => {
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* ARIA attributes.
|
||||
*/
|
||||
[key: `aria-${string}`]: string;
|
||||
|
||||
/**
|
||||
@@ -106,6 +110,11 @@ interface IProps {
|
||||
*/
|
||||
onMouseLeave?: (e?: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* Container role.
|
||||
*/
|
||||
role?: string;
|
||||
|
||||
/**
|
||||
* Tab index for the menu.
|
||||
*/
|
||||
@@ -167,7 +176,9 @@ const ContextMenu = ({
|
||||
onDrawerClose,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
tabIndex
|
||||
role,
|
||||
tabIndex,
|
||||
...aria
|
||||
}: IProps) => {
|
||||
const [ isHidden, setIsHidden ] = useState(true);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -184,9 +195,22 @@ const ContextMenu = ({
|
||||
&& offsetTarget.offsetParent instanceof HTMLElement
|
||||
) {
|
||||
const { current: container } = containerRef;
|
||||
|
||||
// make sure the max height is not set
|
||||
// @ts-ignore
|
||||
container.style.maxHeight = null;
|
||||
const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget;
|
||||
const outerHeight = getComputedOuterHeight(container);
|
||||
const height = Math.min(MAX_HEIGHT, outerHeight);
|
||||
let outerHeight = getComputedOuterHeight(container);
|
||||
let height = Math.min(MAX_HEIGHT, outerHeight);
|
||||
|
||||
if (offsetTop + height > offsetHeight + scrollTop && height > offsetTop) {
|
||||
// top offset and + padding + border
|
||||
container.style.maxHeight = `${offsetTop - ((spacing[2] * 2) + 2)}px`;
|
||||
}
|
||||
|
||||
// get the height after style changes
|
||||
outerHeight = getComputedOuterHeight(container);
|
||||
height = Math.min(MAX_HEIGHT, outerHeight);
|
||||
|
||||
container.style.top = offsetTop + height > offsetHeight + scrollTop
|
||||
? `${offsetTop - outerHeight}`
|
||||
@@ -225,6 +249,7 @@ const ContextMenu = ({
|
||||
</Drawer>
|
||||
</JitsiPortal>
|
||||
: <div
|
||||
{ ...aria }
|
||||
aria-label = { accessibilityLabel }
|
||||
className = { cx(participantsPaneTheme.ignoredChildClassName,
|
||||
styles.contextMenu,
|
||||
@@ -237,7 +262,7 @@ const ContextMenu = ({
|
||||
onMouseEnter = { onMouseEnter }
|
||||
onMouseLeave = { onMouseLeave }
|
||||
ref = { containerRef }
|
||||
role = 'menu'
|
||||
role = { role ?? 'menu' }
|
||||
tabIndex = { tabIndex }>
|
||||
{children}
|
||||
</div>;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { LobbyScreen } from '../../../lobby';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { getOverlayToRender } from '../../../overlay/functions.web';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components/web';
|
||||
import Prejoin from '../../../prejoin/components/web/Prejoin';
|
||||
import { isPrejoinPageVisible } from '../../../prejoin/functions';
|
||||
@@ -33,6 +34,7 @@ import type { AbstractProps } from '../AbstractConference';
|
||||
import ConferenceInfo from './ConferenceInfo';
|
||||
import { default as Notice } from './Notice';
|
||||
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
@@ -59,6 +61,11 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_backgroundAlpha: number,
|
||||
|
||||
/**
|
||||
* Are any overlays visible?
|
||||
*/
|
||||
_isAnyOverlayVisible: boolean,
|
||||
|
||||
/**
|
||||
* The CSS class to apply to the root of {@link Conference} to modify the
|
||||
* application layout.
|
||||
@@ -198,6 +205,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_isAnyOverlayVisible,
|
||||
_layoutClassName,
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
@@ -234,7 +242,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
|
||||
{ _showPrejoin || _showLobby || <Toolbox /> }
|
||||
|
||||
{_notificationsVisible && (_overflowDrawer
|
||||
{_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer
|
||||
? <JitsiPortal className = 'notification-portal'>
|
||||
{this.renderNotificationsContainer({ portal: true })}
|
||||
</JitsiPortal>
|
||||
@@ -383,6 +391,7 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_backgroundAlpha: backgroundAlpha,
|
||||
_isAnyOverlayVisible: Boolean(getOverlayToRender(state)),
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
|
||||
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
|
||||
_overflowDrawer: overflowDrawer,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { areThereNotifications } from '../notifications/functions';
|
||||
import { getOverlayToRender } from '../overlay/functions';
|
||||
|
||||
|
||||
/**
|
||||
* Tells whether or not the notifications should be displayed within
|
||||
@@ -12,10 +12,7 @@ import { getOverlayToRender } from '../overlay/functions';
|
||||
*/
|
||||
export function shouldDisplayNotifications(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
||||
const { calleeInfoVisible } = state['features/invite'];
|
||||
|
||||
return areThereNotifications(state)
|
||||
&& !isAnyOverlayVisible
|
||||
&& !calleeInfoVisible;
|
||||
return areThereNotifications(state) && !calleeInfoVisible;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { addStageParticipant, setFilmstripVisible } from '../filmstrip/actions';
|
||||
import { addStageParticipant, removeStageParticipant, setFilmstripVisible } from '../filmstrip/actions';
|
||||
import { setTileView } from '../video-layout/actions.any';
|
||||
|
||||
import {
|
||||
@@ -180,9 +180,19 @@ function _onFollowMeCommand(attributes: any = {}, id: string, store: IStore) {
|
||||
|
||||
if (attributes.pinnedStageParticipants !== undefined) {
|
||||
const stageParticipants = JSON.parse(attributes.pinnedStageParticipants);
|
||||
let oldStageParticipants = [];
|
||||
|
||||
if (!_.isEqual(stageParticipants, oldState.pinnedStageParticipants)) {
|
||||
stageParticipants.forEach((p: { participantId: string; }) =>
|
||||
if (oldState.pinnedStageParticipants !== undefined) {
|
||||
oldStageParticipants = JSON.parse(oldState.pinnedStageParticipants);
|
||||
}
|
||||
|
||||
if (!_.isEqual(stageParticipants, oldStageParticipants)) {
|
||||
const toRemove = _.differenceWith(oldStageParticipants, stageParticipants, _.isEqual);
|
||||
const toAdd = _.differenceWith(stageParticipants, oldStageParticipants, _.isEqual);
|
||||
|
||||
toRemove.forEach((p: { participantId: string; }) =>
|
||||
store.dispatch(removeStageParticipant(p.participantId)));
|
||||
toAdd.forEach((p: { participantId: string; }) =>
|
||||
store.dispatch(addStageParticipant(p.participantId, true)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import { getLocalTracks, isLocalTrackMuted, toggleScreensharing } from '../../ba
|
||||
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat';
|
||||
import { openChat } from '../../chat/actions';
|
||||
import { closeChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.any';
|
||||
import { SET_PAGE_RELOAD_OVERLAY_CANCELED } from '../../overlay/actionTypes';
|
||||
import { setRequestingSubtitles } from '../../subtitles';
|
||||
import { muteLocal } from '../../video-menu/actions';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
|
||||
@@ -214,16 +213,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
|
||||
case SET_PAGE_RELOAD_OVERLAY_CANCELED:
|
||||
sendEvent(
|
||||
store,
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
error: _toErrorString(action.error),
|
||||
url: _normalizeUrl(store.getState()['features/base/connection'].locationURL)
|
||||
});
|
||||
|
||||
break;
|
||||
case SET_VIDEO_MUTED:
|
||||
sendEvent(
|
||||
store,
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// @flow
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SafeAreaView, Text, View } from 'react-native';
|
||||
|
||||
// @ts-ignore
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
// @ts-ignore
|
||||
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
|
||||
|
||||
// @ts-ignore
|
||||
import { TEXT_COLOR, navigationStyles } from './styles';
|
||||
|
||||
|
||||
const ConnectingPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React, { useCallback } from 'react';
|
||||
import { StatusBar } from 'react-native';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { DialInSummary } from '../../../invite';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
// @ts-ignore
|
||||
import DialInSummary from '../../../invite/components/dial-in-summary/native/DialInSummary';
|
||||
import Prejoin from '../../../prejoin/components/native/Prejoin';
|
||||
// @ts-ignore
|
||||
import WelcomePage from '../../../welcome/components/WelcomePage';
|
||||
import { isWelcomePageEnabled } from '../../../welcome/functions';
|
||||
// @ts-ignore
|
||||
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
|
||||
// @ts-ignore
|
||||
import { rootNavigationRef } from '../rootNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../routes';
|
||||
// @ts-ignore
|
||||
import {
|
||||
conferenceNavigationContainerScreenOptions,
|
||||
connectingScreenOptions,
|
||||
@@ -18,6 +27,7 @@ import {
|
||||
navigationContainerTheme,
|
||||
preJoinScreenOptions,
|
||||
welcomeScreenOptions
|
||||
// @ts-ignore
|
||||
} from '../screenOptions';
|
||||
|
||||
import ConnectingPage from './ConnectingPage';
|
||||
@@ -32,13 +42,13 @@ type Props = {
|
||||
/**
|
||||
* Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* Is welcome page available?
|
||||
*/
|
||||
isWelcomePageAvailable: boolean
|
||||
}
|
||||
isWelcomePageAvailable: boolean;
|
||||
};
|
||||
|
||||
|
||||
const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) => {
|
||||
@@ -100,7 +110,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state: Object) {
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
isWelcomePageAvailable: isWelcomePageEnabled(state)
|
||||
};
|
||||
@@ -13,26 +13,3 @@
|
||||
*/
|
||||
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||
= 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
|
||||
|
||||
/**
|
||||
* Adjust the state of the fatal error which shows/hides the reload screen. See
|
||||
* action methods's description for more info about each of the fields.
|
||||
*
|
||||
* {
|
||||
* type: SET_FATAL_ERROR,
|
||||
* fatalError: ?Object
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_FATAL_ERROR = 'SET_FATAL_ERROR';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that the overlay was canceled.
|
||||
*
|
||||
* {
|
||||
* type: export const SET_PAGE_RELOAD_OVERLAY_CANCELED
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_PAGE_RELOAD_OVERLAY_CANCELED
|
||||
= 'SET_PAGE_RELOAD_OVERLAY_CANCELED';
|
||||
|
||||
31
react/features/overlay/actions.native.ts
Normal file
31
react/features/overlay/actions.native.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/* eslint-disable max-len */
|
||||
|
||||
// @ts-ignore
|
||||
import { PageReloadDialog, openDialog } from '../base/dialog';
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} _isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} _browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(_isVisible: boolean, _browser: string) {
|
||||
// Dummy.
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link PageReloadDialog}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openPageReloadDialog() {
|
||||
return openDialog(PageReloadDialog);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR,
|
||||
SET_PAGE_RELOAD_OVERLAY_CANCELED
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
|
||||
return {
|
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
browser,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The action indicates that an unrecoverable error has occurred and the reload
|
||||
* screen will be displayed or hidden.
|
||||
*
|
||||
* @param {Object} fatalError - A critical error which was not claimed by any
|
||||
* feature for error recovery (the recoverable flag was not set). If
|
||||
* {@code undefined} then any fatal error currently stored will be discarded.
|
||||
* @returns {{
|
||||
* type: SET_FATAL_ERROR,
|
||||
* fatalError: ?Error
|
||||
* }}
|
||||
*/
|
||||
export function setFatalError(fatalError?: Object) {
|
||||
return {
|
||||
type: SET_FATAL_ERROR,
|
||||
fatalError
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The action indicates that the overlay was canceled.
|
||||
*
|
||||
* @param {Object} error - The error that caused the display of the overlay.
|
||||
*
|
||||
* @returns {{
|
||||
* type: SET_PAGE_RELOAD_OVERLAY_CANCELED,
|
||||
* error: ?Error
|
||||
* }}
|
||||
*/
|
||||
export function setPageReloadOverlayCanceled(error: Object) {
|
||||
return {
|
||||
type: SET_PAGE_RELOAD_OVERLAY_CANCELED,
|
||||
error
|
||||
};
|
||||
}
|
||||
32
react/features/overlay/actions.web.ts
Normal file
32
react/features/overlay/actions.web.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
|
||||
return {
|
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
browser,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link PageReloadDialog}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openPageReloadDialog() {
|
||||
// Dummy
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
@@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
@@ -1,4 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as OverlayContainer } from './OverlayContainer';
|
||||
export * from './_';
|
||||
@@ -1,38 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component, type Node } from 'react';
|
||||
import { SafeAreaView, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code OverlayFrame}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The children components to be displayed into the overlay frame.
|
||||
*/
|
||||
children: Node,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React component to act as the frame for overlays.
|
||||
*/
|
||||
export default class OverlayFrame extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View style = { styles.container }>
|
||||
<SafeAreaView style = { styles.safeContainer } >
|
||||
{ this.props.children }
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { appNavigate, reloadNow } from '../../../app/actions';
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { setFatalError, setPageReloadOverlayCanceled } from '../../actions';
|
||||
import AbstractPageReloadOverlay, {
|
||||
type Props,
|
||||
abstractMapStateToProps
|
||||
} from '../AbstractPageReloadOverlay';
|
||||
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
|
||||
/**
|
||||
* Implements a React Component for page reload overlay. Shown before the
|
||||
* conference is reloaded. Shows a warning message and counts down towards the
|
||||
* reload.
|
||||
*/
|
||||
class PageReloadOverlay extends AbstractPageReloadOverlay<Props> {
|
||||
_interval: IntervalID;
|
||||
|
||||
/**
|
||||
* Initializes a new PageReloadOverlay instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
* @public
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onReloadNow = this._onReloadNow.bind(this);
|
||||
}
|
||||
|
||||
_onCancel: () => void;
|
||||
|
||||
/**
|
||||
* Handle clicking of the "Cancel" button. It will navigate back to the
|
||||
* welcome page.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(setPageReloadOverlayCanceled(this.props.error));
|
||||
this.props.dispatch(setFatalError(undefined));
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
_onReloadNow: () => void;
|
||||
|
||||
/**
|
||||
* Handle clicking on the "Reload Now" button. It will navigate to the same
|
||||
* conference URL as before immediately, without waiting for the timer to
|
||||
* kick in.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onReloadNow() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(reloadNow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { message, timeLeft, title } = this.state;
|
||||
|
||||
return (
|
||||
<OverlayFrame>
|
||||
<ConfirmDialog
|
||||
cancelLabel = 'dialog.Cancel'
|
||||
confirmLabel = 'dialog.rejoinNow'
|
||||
descriptionKey = { `${t(message, { seconds: timeLeft })}` }
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onReloadNow }
|
||||
title = { title } />
|
||||
</OverlayFrame>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(PageReloadOverlay));
|
||||
@@ -1,4 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as OverlayFrame } from './OverlayFrame';
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
@@ -1,24 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of the overlay feature.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Style for a backdrop overlay covering the screen the the overlay is
|
||||
* rendered.
|
||||
*/
|
||||
container: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: BaseTheme.palette.ui00
|
||||
},
|
||||
|
||||
safeContainer: {
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
@@ -7,15 +7,15 @@ import type { Dispatch } from 'redux';
|
||||
import {
|
||||
createPageReloadScheduledEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { reloadNow } from '../../app/actions';
|
||||
} from '../../../analytics';
|
||||
import { reloadNow } from '../../../app/actions';
|
||||
import {
|
||||
isFatalJitsiConferenceError,
|
||||
isFatalJitsiConnectionError
|
||||
} from '../../base/lib-jitsi-meet/functions';
|
||||
import logger from '../logger';
|
||||
} from '../../../base/lib-jitsi-meet/functions';
|
||||
import logger from '../../logger';
|
||||
|
||||
import ReloadButton from './web/ReloadButton';
|
||||
import ReloadButton from './ReloadButton';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
@@ -91,6 +91,7 @@ type State = {
|
||||
*/
|
||||
export default class AbstractPageReloadOverlay<P: Props>
|
||||
extends Component<P, State> {
|
||||
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
@@ -100,34 +101,18 @@ export default class AbstractPageReloadOverlay<P: Props>
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: Object) {
|
||||
// FIXME web does not rely on the 'recoverable' flag set on an error
|
||||
// action, but on a predefined list of fatal errors. Because of that
|
||||
// the value of 'fatalError' which relies on the flag should not be used
|
||||
// on web yet (until conference/connection and their errors handling is
|
||||
// not unified).
|
||||
return typeof APP === 'undefined'
|
||||
? Boolean(state['features/overlay'].fatalError)
|
||||
: this.needsRenderWeb(state);
|
||||
}
|
||||
const { error: conferenceError } = state['features/base/conference'];
|
||||
const { error: configError } = state['features/base/config'];
|
||||
const { error: connectionError } = state['features/base/connection'];
|
||||
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - If this overlay needs to be rendered, {@code true};
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRenderWeb(state: Object) {
|
||||
const conferenceError = state['features/base/conference'].error;
|
||||
const configError = state['features/base/config'].error;
|
||||
const connectionError = state['features/base/connection'].error;
|
||||
const jitsiConnectionError
|
||||
|
||||
return (
|
||||
(connectionError && isFatalJitsiConnectionError(connectionError))
|
||||
|| (conferenceError
|
||||
&& isFatalJitsiConferenceError(conferenceError))
|
||||
|| configError);
|
||||
// @ts-ignore
|
||||
= connectionError && isFatalJitsiConnectionError(connectionError);
|
||||
const jitsiConferenceError
|
||||
= conferenceError && isFatalJitsiConferenceError(conferenceError);
|
||||
|
||||
return jitsiConnectionError || jitsiConferenceError || configError;
|
||||
}
|
||||
|
||||
_interval: ?IntervalID;
|
||||
@@ -28,6 +28,6 @@ export default class AbstractSuspendedOverlay extends Component<Props> {
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: Object) {
|
||||
return state['features/power-monitor'].suspendDetected;
|
||||
return state['features/power-monitor']?.suspendDetected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { connect } from '../../base/redux';
|
||||
import { getOverlayToRender } from '../functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import { getOverlayToRender } from '../../functions.web';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@code OverlayContainer}.
|
||||
@@ -16,8 +14,8 @@ type Props = {
|
||||
* The React {@link Component} type of overlay to be rendered by the
|
||||
* associated {@code OverlayContainer}.
|
||||
*/
|
||||
overlay: ?React$ComponentType<*>
|
||||
}
|
||||
overlay: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} that will display the correct overlay
|
||||
@@ -48,7 +46,7 @@ class OverlayContainer extends Component<Props> {
|
||||
* overlay: ?Object
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
/**
|
||||
* The React {@link Component} type of overlay to be rendered by the
|
||||
@@ -1,8 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
import React, { Component, ReactChildren } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link OverlayFrame}.
|
||||
@@ -12,18 +8,18 @@ type Props = {
|
||||
/**
|
||||
* The children components to be displayed into the overlay frame.
|
||||
*/
|
||||
children: React$Node,
|
||||
children: ReactChildren;
|
||||
|
||||
/**
|
||||
* Indicates the css style of the overlay. If true, then lighter; darker,
|
||||
* otherwise.
|
||||
*/
|
||||
isLightOverlay?: boolean,
|
||||
isLightOverlay?: boolean;
|
||||
|
||||
/**
|
||||
* The style property.
|
||||
*/
|
||||
style: Object
|
||||
style: Object;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
|
||||
import AbstractPageReloadOverlay, {
|
||||
type Props,
|
||||
abstractMapStateToProps
|
||||
} from '../AbstractPageReloadOverlay';
|
||||
|
||||
} from './AbstractPageReloadOverlay';
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as OverlayFrame } from './OverlayFrame';
|
||||
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
export { default as SuspendedOverlay } from './SuspendedOverlay';
|
||||
export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';
|
||||
@@ -1,7 +1,13 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
|
||||
import { getOverlays } from './overlays';
|
||||
|
||||
// @ts-ignore
|
||||
import PageReloadOverlay from './components/web/PageReloadOverlay';
|
||||
// @ts-ignore
|
||||
import SuspendedOverlay from './components/web/SuspendedOverlay';
|
||||
// @ts-ignore
|
||||
import UserMediaPermissionsOverlay from './components/web/UserMediaPermissionsOverlay';
|
||||
/**
|
||||
* Returns the overlay to be currently rendered.
|
||||
*
|
||||
@@ -9,7 +15,13 @@ import { getOverlays } from './overlays';
|
||||
* @returns {?React$ComponentType<*>}
|
||||
*/
|
||||
export function getOverlayToRender(state: IReduxState) {
|
||||
for (const overlay of getOverlays()) {
|
||||
const overlays = [
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
];
|
||||
|
||||
for (const overlay of overlays) {
|
||||
// react-i18n / react-redux wrap components and thus we cannot access
|
||||
// the wrapped component's static methods directly.
|
||||
// @ts-ignore
|
||||
@@ -22,13 +34,3 @@ export function getOverlayToRender(state: IReduxState) {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility of the media permissions prompt.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getMediaPermissionPromptVisibility(state: IReduxState) {
|
||||
return state['features/overlay'].isMediaPermissionPromptVisible;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export * from './actions';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
@@ -1,12 +1,15 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
isFatalJitsiConferenceError,
|
||||
isFatalJitsiConnectionError
|
||||
} from '../base/lib-jitsi-meet/functions';
|
||||
} from '../base/lib-jitsi-meet/functions.any';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
|
||||
import { setFatalError } from './actions';
|
||||
import { openPageReloadDialog } from './actions';
|
||||
|
||||
|
||||
/**
|
||||
* Error type. Basically like Error, but augmented with a recoverable property.
|
||||
@@ -47,12 +50,11 @@ const ERROR_TYPES = {
|
||||
/**
|
||||
* Gets the error type and whether it's fatal or not.
|
||||
*
|
||||
* @param {Function} getState - The redux function for fetching the current state.
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object|string} error - The error to process.
|
||||
* @returns {void}
|
||||
*/
|
||||
const getErrorExtraInfo = (getState: IStore['getState'], error: ErrorType) => {
|
||||
const state = getState();
|
||||
const getErrorExtraInfo = (state: any, error: ErrorType) => {
|
||||
const { error: conferenceError } = state['features/base/conference'];
|
||||
const { error: configError } = state['features/base/config'];
|
||||
const { error: connectionError } = state['features/base/connection'];
|
||||
@@ -92,20 +94,26 @@ StateListenerRegistry.register(
|
||||
|
||||
return configError || connectionError || conferenceError;
|
||||
},
|
||||
/* listener */ (error: ErrorType, { dispatch, getState }) => {
|
||||
/* listener */ (error: ErrorType, store: IStore) => {
|
||||
const state = store.getState();
|
||||
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyError({
|
||||
...error,
|
||||
...getErrorExtraInfo(getState, error)
|
||||
...getErrorExtraInfo(state, error)
|
||||
});
|
||||
}
|
||||
|
||||
if (NON_OVERLAY_ERRORS.indexOf(error.name) === -1 && typeof error.recoverable === 'undefined') {
|
||||
dispatch(setFatalError(error));
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
store.dispatch(openPageReloadDialog());
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import { PageReloadOverlay } from './components/native';
|
||||
|
||||
/**
|
||||
* Returns the list of available platform specific overlays.
|
||||
*
|
||||
* @returns {Array<ReactElement>}
|
||||
*/
|
||||
export function getOverlays(): Array<ReactElement> {
|
||||
return [
|
||||
PageReloadOverlay
|
||||
];
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import {
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
|
||||
// @ts-ignore
|
||||
} from './components/web';
|
||||
|
||||
/**
|
||||
* Returns the list of available platform specific overlays.
|
||||
*
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
export function getOverlays(): Array<Object> {
|
||||
return [
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
];
|
||||
}
|
||||
@@ -1,17 +1,13 @@
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from '../base/config/actionTypes';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
import { assign, set } from '../base/redux/functions';
|
||||
import { assign } from '../base/redux/functions';
|
||||
|
||||
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
|
||||
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR
|
||||
} from './actionTypes';
|
||||
|
||||
export interface IOverlayState {
|
||||
browser?: string;
|
||||
fatalError?: Error;
|
||||
isMediaPermissionPromptVisible?: boolean;
|
||||
loadConfigOverlayVisible?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,18 +17,8 @@ export interface IOverlayState {
|
||||
*/
|
||||
ReducerRegistry.register<IOverlayState>('features/overlay', (state = {}, action): IOverlayState => {
|
||||
switch (action.type) {
|
||||
case CONFIG_WILL_LOAD:
|
||||
return _setShowLoadConfigOverlay(state, Boolean(action.room));
|
||||
|
||||
case LOAD_CONFIG_ERROR:
|
||||
case SET_CONFIG:
|
||||
return _setShowLoadConfigOverlay(state, false);
|
||||
|
||||
case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED:
|
||||
return _mediaPermissionPromptVisibilityChanged(state, action);
|
||||
|
||||
case SET_FATAL_ERROR:
|
||||
return _setFatalError(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -56,29 +42,3 @@ function _mediaPermissionPromptVisibilityChanged(
|
||||
isMediaPermissionPromptVisible: isVisible
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code LoadConfigOverlay} overlay visible or not.
|
||||
*
|
||||
* @param {Object} state - The redux state of the feature overlay.
|
||||
* @param {boolean} show - Whether to show or not the overlay.
|
||||
* @returns {Object} The new state of the feature overlay after the reduction of
|
||||
* the specified action.
|
||||
*/
|
||||
function _setShowLoadConfigOverlay(state: IOverlayState, show?: boolean) {
|
||||
return set(state, 'loadConfigOverlayVisible', show);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific redux action {@code SET_FATAL_ERROR} of the feature
|
||||
* overlay.
|
||||
*
|
||||
* @param {Object} state - The redux state of the feature overlay.
|
||||
* @param {Error} fatalError - If the value is set it indicates that a fatal
|
||||
* error has occurred and that the reload screen is to be displayed.
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function _setFatalError(state: IOverlayState, { fatalError }: { fatalError?: Error; }) {
|
||||
return set(state, 'fatalError', fatalError);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ const useStyles = makeStyles()(theme => {
|
||||
right: '-4px',
|
||||
top: '-3px',
|
||||
textAlign: 'center',
|
||||
boxSizing: 'border-box',
|
||||
paddingTop: '2px'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,45 +1,63 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import Video from '../../../../base/media/components/Video';
|
||||
import { equals } from '../../../../base/redux';
|
||||
import { createLocalVideoTracks } from '../../../functions';
|
||||
|
||||
import { IReduxState, IStore } from '../../../../app/types';
|
||||
import { openDialog } from '../../../../base/dialog/actions';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import { IconImage } from '../../../../base/icons/svg';
|
||||
import Video from '../../../../base/media/components/Video.web';
|
||||
import { equals } from '../../../../base/redux/functions';
|
||||
import { updateSettings } from '../../../../base/settings/actions';
|
||||
import Checkbox from '../../../../base/ui/components/web/Checkbox';
|
||||
import ContextMenu from '../../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
||||
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import VirtualBackgroundDialog from '../../../../virtual-background/components/VirtualBackgroundDialog';
|
||||
import { createLocalVideoTracks } from '../../../functions.web';
|
||||
|
||||
const videoClassName = 'video-preview-video flipVideoX';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link VideoSettingsContent}.
|
||||
*/
|
||||
export type Props = {
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Callback to change the flip state.
|
||||
*/
|
||||
changeFlip: (flip: boolean) => void;
|
||||
|
||||
/**
|
||||
* The deviceId of the camera device currently being used.
|
||||
*/
|
||||
currentCameraDeviceId: string,
|
||||
currentCameraDeviceId: string;
|
||||
|
||||
/**
|
||||
* Whether or not the local video is flipped.
|
||||
*/
|
||||
localFlipX: boolean;
|
||||
|
||||
/**
|
||||
* Open virtual background dialog.
|
||||
*/
|
||||
selectBackground: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to change current camera.
|
||||
*/
|
||||
setVideoInputDevice: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
setVideoInputDevice: Function;
|
||||
|
||||
/**
|
||||
* Callback invoked to toggle the settings popup visibility.
|
||||
*/
|
||||
toggleVideoSettings: Function,
|
||||
toggleVideoSettings: Function;
|
||||
|
||||
/**
|
||||
* All the camera device ids currently connected.
|
||||
*/
|
||||
videoDeviceIds: string[],
|
||||
};
|
||||
videoDeviceIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link VideoSettingsContent}.
|
||||
@@ -49,7 +67,7 @@ type State = {
|
||||
/**
|
||||
* An array of all the jitsiTracks and eventual errors.
|
||||
*/
|
||||
trackData: Object[],
|
||||
trackData: { deviceId: string; error?: string; jitsiTrack: any | null; }[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -58,9 +76,8 @@ type State = {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class VideoSettingsContent extends Component<Props, State> {
|
||||
class VideoSettingsContent extends Component<IProps, State> {
|
||||
_componentWasUnmounted: boolean;
|
||||
_videoContentRef: Object;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code VideoSettingsContent} instance.
|
||||
@@ -68,10 +85,9 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
this._videoContentRef = React.createRef();
|
||||
this._onToggleFlip = this._onToggleFlip.bind(this);
|
||||
|
||||
this.state = {
|
||||
trackData: new Array(props.videoDeviceIds.length).fill({
|
||||
@@ -79,20 +95,16 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
})
|
||||
};
|
||||
}
|
||||
_onEscClick: (KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Click handler for the video entries.
|
||||
* Toggles local video flip state.
|
||||
*
|
||||
* @param {KeyboardEvent} event - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscClick(event) {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._videoContentRef.current.style.display = 'none';
|
||||
}
|
||||
_onToggleFlip() {
|
||||
const { localFlipX, changeFlip } = this.props;
|
||||
|
||||
changeFlip(!localFlipX);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,9 +134,9 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
* @param {Object[]} trackData - An array of tracks that are to be disposed.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_disposeTracks(trackData) {
|
||||
_disposeTracks(trackData: { jitsiTrack: any; }[]) {
|
||||
trackData.forEach(({ jitsiTrack }) => {
|
||||
jitsiTrack && jitsiTrack.dispose();
|
||||
jitsiTrack?.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,7 +146,7 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
* @param {string} deviceId - The id of the camera device.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onEntryClick(deviceId) {
|
||||
_onEntryClick(deviceId: string) {
|
||||
return () => {
|
||||
this.props.setVideoInputDevice(deviceId);
|
||||
this.props.toggleVideoSettings();
|
||||
@@ -148,7 +160,7 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
* @param {number} index - The index of the entry.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPreviewEntry(data, index) {
|
||||
_renderPreviewEntry(data: { deviceId: string; error?: string; jitsiTrack: any | null; }, index: number) {
|
||||
const { error, jitsiTrack, deviceId } = data;
|
||||
const { currentCameraDeviceId, t } = this.props;
|
||||
const isSelected = deviceId === currentCameraDeviceId;
|
||||
@@ -167,19 +179,19 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
const props: Object = {
|
||||
const props: any = {
|
||||
className,
|
||||
key,
|
||||
tabIndex
|
||||
};
|
||||
const label = jitsiTrack && jitsiTrack.getTrackLabel();
|
||||
const label = jitsiTrack?.getTrackLabel();
|
||||
|
||||
if (isSelected) {
|
||||
props['aria-checked'] = true;
|
||||
props.className = `${className} video-preview-entry--selected`;
|
||||
} else {
|
||||
props.onClick = this._onEntryClick(deviceId);
|
||||
props.onKeyPress = e => {
|
||||
props.onKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
props.onClick();
|
||||
@@ -192,12 +204,10 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
{ ...props }
|
||||
role = 'radio'>
|
||||
<div className = 'video-preview-label'>
|
||||
{label && <div className = 'video-preview-label-container'>
|
||||
<div className = 'video-preview-label-text'>
|
||||
<span>{label}</span></div>
|
||||
{label && <div className = 'video-preview-label-text'>
|
||||
<span>{label}</span>
|
||||
</div>}
|
||||
</div>
|
||||
<div className = 'video-preview-overlay' />
|
||||
<Video
|
||||
className = { videoClassName }
|
||||
playsinline = { true }
|
||||
@@ -230,7 +240,7 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
if (!equals(this.props.videoDeviceIds, prevProps.videoDeviceIds)) {
|
||||
this._setTracks();
|
||||
}
|
||||
@@ -243,21 +253,57 @@ class VideoSettingsContent extends Component<Props, State> {
|
||||
*/
|
||||
render() {
|
||||
const { trackData } = this.state;
|
||||
const { selectBackground, t, localFlipX } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
<ContextMenu
|
||||
aria-labelledby = 'video-settings-button'
|
||||
className = 'video-preview-container'
|
||||
hidden = { false }
|
||||
id = 'video-settings-dialog'
|
||||
onKeyDown = { this._onEscClick }
|
||||
ref = { this._videoContentRef }
|
||||
role = 'radiogroup'
|
||||
tabIndex = '-1'>
|
||||
{trackData.map((data, i) => this._renderPreviewEntry(data, i))}
|
||||
</div>
|
||||
tabIndex = { -1 }>
|
||||
<ContextMenuItemGroup>
|
||||
{trackData.map((data, i) => this._renderPreviewEntry(data, i))}
|
||||
</ContextMenuItemGroup>
|
||||
<ContextMenuItemGroup>
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = 'virtualBackground.title'
|
||||
icon = { IconImage }
|
||||
onClick = { selectBackground }
|
||||
text = { t('virtualBackground.title') } />
|
||||
<div
|
||||
className = 'video-preview-checkbox-container'
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { e => e.stopPropagation() }>
|
||||
<Checkbox
|
||||
checked = { localFlipX }
|
||||
label = { t('videothumbnail.mirrorVideo') }
|
||||
onChange = { this._onToggleFlip } />
|
||||
</div>
|
||||
</ContextMenuItemGroup>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
|
||||
export default translate(VideoSettingsContent);
|
||||
return {
|
||||
localFlipX: Boolean(localFlipX)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: IStore['dispatch']) => {
|
||||
return {
|
||||
selectBackground: () => dispatch(openDialog(VirtualBackgroundDialog)),
|
||||
changeFlip: (flip: boolean) => {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: flip
|
||||
}));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default translate(connect(mapStateToProps, mapDispatchToProps)(VideoSettingsContent));
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import {
|
||||
setVideoInputDeviceAndUpdateSettings
|
||||
} from '../../../../base/devices/actions.web';
|
||||
@@ -9,36 +9,35 @@ import {
|
||||
getVideoDeviceIds
|
||||
} from '../../../../base/devices/functions.web';
|
||||
import Popover from '../../../../base/popover/components/Popover.web';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { SMALL_MOBILE_WIDTH } from '../../../../base/responsive-ui/constants';
|
||||
import { getCurrentCameraDeviceId } from '../../../../base/settings';
|
||||
import { getCurrentCameraDeviceId } from '../../../../base/settings/functions.web';
|
||||
import { toggleVideoSettings } from '../../../actions';
|
||||
import { getVideoSettingsVisibility } from '../../../functions';
|
||||
import { getVideoSettingsVisibility } from '../../../functions.web';
|
||||
|
||||
import VideoSettingsContent, { type Props as VideoSettingsProps } from './VideoSettingsContent';
|
||||
import VideoSettingsContent, { type IProps as VideoSettingsProps } from './VideoSettingsContent';
|
||||
|
||||
|
||||
type Props = VideoSettingsProps & {
|
||||
interface IProps extends VideoSettingsProps {
|
||||
|
||||
/**
|
||||
* Component children (the Video button).
|
||||
*/
|
||||
children: React$Node,
|
||||
children: ReactNode;
|
||||
|
||||
/**
|
||||
* Flag controlling the visibility of the popup.
|
||||
*/
|
||||
isOpen: boolean,
|
||||
isOpen: boolean;
|
||||
|
||||
/**
|
||||
* Callback executed when the popup closes.
|
||||
*/
|
||||
onClose: Function,
|
||||
onClose: Function;
|
||||
|
||||
/**
|
||||
* The popup placement enum value.
|
||||
*/
|
||||
popupPlacement: string
|
||||
popupPlacement: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +53,7 @@ function VideoSettingsPopup({
|
||||
popupPlacement,
|
||||
setVideoInputDevice,
|
||||
videoDeviceIds
|
||||
}: Props) {
|
||||
}: IProps) {
|
||||
return (
|
||||
<div className = 'video-preview'>
|
||||
<Popover
|
||||
@@ -80,14 +79,14 @@ function VideoSettingsPopup({
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { clientWidth } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
currentCameraDeviceId: getCurrentCameraDeviceId(state),
|
||||
isOpen: getVideoSettingsVisibility(state),
|
||||
popupPlacement: clientWidth <= SMALL_MOBILE_WIDTH ? 'auto' : 'top-end',
|
||||
videoDeviceIds: getVideoDeviceIds(state)
|
||||
isOpen: Boolean(getVideoSettingsVisibility(state)),
|
||||
popupPlacement: clientWidth <= Number(SMALL_MOBILE_WIDTH) ? 'auto' : 'top-end',
|
||||
videoDeviceIds: getVideoDeviceIds(state) ?? []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ const styles = () => {
|
||||
rowGap: '8px',
|
||||
margin: 0,
|
||||
padding: '16px',
|
||||
marginBottom: '8px'
|
||||
marginBottom: '4px'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -463,7 +463,17 @@ function _updateReceiverVideoConstraints({ getState }: IStore) {
|
||||
}
|
||||
|
||||
if (getCurrentLayout(state) === LAYOUTS.STAGE_FILMSTRIP_VIEW && activeParticipantsSources.length > 0) {
|
||||
const onStageSources = [ ...activeParticipantsSources ];
|
||||
const selectedSources: string[] = [];
|
||||
const onStageSources: string[] = [];
|
||||
|
||||
// If more than one video source is pinned to the stage filmstrip, they need to be added to the
|
||||
// 'selectedSources' so that the bridge can allocate bandwidth for all the sources as opposed to doing
|
||||
// greedy allocation for the sources (which happens when they are added to 'onStageSources').
|
||||
if (activeParticipantsSources.length > 1) {
|
||||
selectedSources.push(...activeParticipantsSources);
|
||||
} else {
|
||||
onStageSources.push(activeParticipantsSources[0]);
|
||||
}
|
||||
|
||||
activeParticipantsSources.forEach(sourceName => {
|
||||
const isScreenSharing = remoteScreenShares.includes(sourceName);
|
||||
@@ -485,6 +495,7 @@ function _updateReceiverVideoConstraints({ getState }: IStore) {
|
||||
}
|
||||
|
||||
receiverConstraints.onStageSources = onStageSources;
|
||||
receiverConstraints.selectedSources = selectedSources;
|
||||
} else if (largeVideoSourceName) {
|
||||
let quality = VIDEO_QUALITY_UNLIMITED;
|
||||
|
||||
|
||||
@@ -41,7 +41,10 @@ interface IProps extends WithTranslation {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
addBackground: {
|
||||
marginRight: theme.spacing(2)
|
||||
marginRight: theme.spacing(2),
|
||||
'& svg': {
|
||||
fill: '#669aec !important'
|
||||
}
|
||||
},
|
||||
button: {
|
||||
display: 'none'
|
||||
|
||||
@@ -256,7 +256,7 @@ const useStyles = makeStyles()(theme => {
|
||||
}
|
||||
},
|
||||
dialogMarginTop: {
|
||||
marginTop: '44px'
|
||||
marginTop: '8px'
|
||||
},
|
||||
virtualBackgroundLoading: {
|
||||
overflow: 'hidden',
|
||||
|
||||
Reference in New Issue
Block a user