Flow, coding style

This commit is contained in:
Lyubo Marinov 2017-10-04 17:36:09 -05:00
parent 4c00d39bf2
commit f53c79ab24
18 changed files with 266 additions and 178 deletions

View File

@ -1,3 +1,5 @@
// @flow
import JitsiMeetJS, { isAnalyticsEnabled } from '../base/lib-jitsi-meet'; import JitsiMeetJS, { isAnalyticsEnabled } from '../base/lib-jitsi-meet';
import { getJitsiMeetGlobalNS, loadScript } from '../base/util'; import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
@ -13,7 +15,7 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* is being dispatched. * is being dispatched.
* @returns {void} * @returns {void}
*/ */
export function initAnalytics({ getState }) { export function initAnalytics({ getState }: { getState: Function }) {
getJitsiMeetGlobalNS().analyticsHandlers = []; getJitsiMeetGlobalNS().analyticsHandlers = [];
window.analyticsHandlers = []; // Legacy support. window.analyticsHandlers = []; // Legacy support.
@ -36,12 +38,12 @@ export function initAnalytics({ getState }) {
_loadHandlers(analyticsScriptUrls, handlerConstructorOptions) _loadHandlers(analyticsScriptUrls, handlerConstructorOptions)
.then(handlers => { .then(handlers => {
const permanentProperties = { const state = getState();
roomName: getState()['features/base/conference'].room, const permanentProperties: Object = {
roomName: state['features/base/conference'].room,
userAgent: navigator.userAgent userAgent: navigator.userAgent
}; };
const { group, server } = state['features/jwt'];
const { group, server } = getState()['features/jwt'];
if (server) { if (server) {
permanentProperties.server = server; permanentProperties.server = server;
@ -50,13 +52,14 @@ export function initAnalytics({ getState }) {
permanentProperties.group = group; permanentProperties.group = group;
} }
// optionally include local deployment information based on // Optionally, include local deployment information based on the
// the contents of window.config.deploymentInfo // contents of window.config.deploymentInfo.
if (config.deploymentInfo) { const { deploymentInfo } = config;
for (const key in config.deploymentInfo) {
if (config.deploymentInfo.hasOwnProperty(key)) { if (deploymentInfo) {
permanentProperties[key] for (const key in deploymentInfo) {
= config.deploymentInfo[key]; if (deploymentInfo.hasOwnProperty(key)) {
permanentProperties[key] = deploymentInfo[key];
} }
} }
} }
@ -113,23 +116,22 @@ function _loadHandlers(scriptURLs, handlerConstructorOptions) {
if (analyticsHandlers.length === 0) { if (analyticsHandlers.length === 0) {
throw new Error('No analytics handlers available'); throw new Error('No analytics handlers available');
} else {
const handlers = [];
for (const Handler of analyticsHandlers) {
// catch any error while loading to avoid
// skipping analytics in case of multiple scripts
try {
handlers.push(new Handler(handlerConstructorOptions));
} catch (error) {
logger.warn(`Error creating analytics handler: ${error}`);
}
}
logger.debug(`Loaded ${handlers.length} analytics handlers`);
return handlers;
} }
const handlers = [];
for (const Handler of analyticsHandlers) {
// Catch any error while loading to avoid skipping analytics in case
// of multiple scripts.
try {
handlers.push(new Handler(handlerConstructorOptions));
} catch (error) {
logger.warn(`Error creating analytics handler: ${error}`);
}
}
logger.debug(`Loaded ${handlers.length} analytics handlers`);
return handlers;
}); });
} }

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
@ -79,6 +81,8 @@ class WaitForOwnerDialog extends Component {
); );
} }
_onCancel: () => void;
/** /**
* Called when the cancel button is clicked. * Called when the cancel button is clicked.
* *
@ -89,6 +93,8 @@ class WaitForOwnerDialog extends Component {
this.props.dispatch(cancelWaitForOwner()); this.props.dispatch(cancelWaitForOwner());
} }
_onLogin: () => void;
/** /**
* Called when the OK button is clicked. * Called when the OK button is clicked.
* *
@ -102,11 +108,11 @@ class WaitForOwnerDialog extends Component {
/** /**
* Renders a specific {@code string} which may contain HTML. * Renders a specific {@code string} which may contain HTML.
* *
* @param {string} html - The {@code string} which may contain HTML to * @param {string|undefined} html - The {@code string} which may
* render. * contain HTML to render.
* @returns {ReactElement[]|string} * @returns {ReactElement[]|string}
*/ */
_renderHTML(html) { _renderHTML(html: ?string) {
if (typeof html === 'string') { if (typeof html === 'string') {
// At the time of this writing, the specified HTML contains a couple // At the time of this writing, the specified HTML contains a couple
// of spaces one after the other. They do not cause a visible // of spaces one after the other. They do not cause a visible
@ -164,9 +170,7 @@ class WaitForOwnerDialog extends Component {
* }} * }}
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
const { const { authRequired } = state['features/base/conference'];
authRequired
} = state['features/base/conference'];
return { return {
_room: authRequired && authRequired.getName() _room: authRequired && authRequired.getName()

View File

@ -1,3 +1,5 @@
// @flow
import { appNavigate } from '../app'; import { appNavigate } from '../app';
import { import {
CONFERENCE_FAILED, CONFERENCE_FAILED,
@ -134,7 +136,8 @@ MiddlewareRegistry.register(store => next => action => {
* @param {Object} store - The redux store. * @param {Object} store - The redux store.
* @returns {void} * @returns {void}
*/ */
function _clearExistingWaitForOwnerTimeout({ getState }) { function _clearExistingWaitForOwnerTimeout(
{ getState }: { getState: Function }) {
const { waitForOwnerTimeoutID } = getState()['features/authentication']; const { waitForOwnerTimeoutID } = getState()['features/authentication'];
waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID); waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
@ -146,7 +149,7 @@ function _clearExistingWaitForOwnerTimeout({ getState }) {
* @param {Object} store - The redux store. * @param {Object} store - The redux store.
* @returns {void} * @returns {void}
*/ */
function _hideLoginDialog({ dispatch }) { function _hideLoginDialog({ dispatch }: { dispatch: Dispatch<*> }) {
dispatch(hideDialog(LoginDialog)); dispatch(hideDialog(LoginDialog));
} }
@ -156,6 +159,6 @@ function _hideLoginDialog({ dispatch }) {
* @param {Object} store - The redux store. * @param {Object} store - The redux store.
* @returns {boolean} * @returns {boolean}
*/ */
function _isWaitingForOwner({ getState }) { function _isWaitingForOwner({ getState }: { getState: Function }) {
return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID); return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
} }

View File

@ -1,4 +1,5 @@
/* global APP */ // @flow
import UIEvents from '../../../../service/UI/UIEvents'; import UIEvents from '../../../../service/UI/UIEvents';
import { CONNECTION_ESTABLISHED } from '../connection'; import { CONNECTION_ESTABLISHED } from '../connection';
@ -34,6 +35,8 @@ import {
_removeLocalTracksFromConference _removeLocalTracksFromConference
} from './functions'; } from './functions';
declare var APP: Object;
/** /**
* Implements the middleware of the feature base/conference. * Implements the middleware of the feature base/conference.
* *
@ -299,14 +302,12 @@ function _setLastN(store, next, action) {
* @returns {Object} The new state that is the result of the reduction of the * @returns {Object} The new state that is the result of the reduction of the
* specified action. * specified action.
*/ */
function _setReceiveVideoQuality(store, next, action) { function _setReceiveVideoQuality({ dispatch, getState }, next, action) {
const { audioOnly, conference } const { audioOnly, conference } = getState()['features/base/conference'];
= store.getState()['features/base/conference'];
conference.setReceiverVideoConstraint(action.receiveVideoQuality); conference.setReceiverVideoConstraint(action.receiveVideoQuality);
if (audioOnly) { if (audioOnly) {
store.dispatch(toggleAudioOnly()); dispatch(toggleAudioOnly());
} }
return next(action); return next(action);
@ -321,9 +322,9 @@ function _setReceiveVideoQuality(store, next, action) {
* @private * @private
* @returns {Promise} * @returns {Promise}
*/ */
function _syncConferenceLocalTracksWithState(store, action) { function _syncConferenceLocalTracksWithState({ getState }, action) {
const state = store.getState()['features/base/conference']; const state = getState()['features/base/conference'];
const conference = state.conference; const { conference } = state;
let promise; let promise;
// XXX The conference may already be in the process of being left, that's // XXX The conference may already be in the process of being left, that's
@ -354,8 +355,8 @@ function _syncConferenceLocalTracksWithState(store, action) {
* @returns {Object} The new state that is the result of the reduction of the * @returns {Object} The new state that is the result of the reduction of the
* specified action. * specified action.
*/ */
function _syncReceiveVideoQuality(store, next, action) { function _syncReceiveVideoQuality({ getState }, next, action) {
const state = store.getState()['features/base/conference']; const state = getState()['features/base/conference'];
state.conference.setReceiverVideoConstraint(state.receiveVideoQuality); state.conference.setReceiverVideoConstraint(state.receiveVideoQuality);

View File

@ -1,3 +1,5 @@
// @flow
import { CONNECTION_WILL_CONNECT } from '../connection'; import { CONNECTION_WILL_CONNECT } from '../connection';
import { JitsiConferenceErrors } from '../lib-jitsi-meet'; import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import { assign, ReducerRegistry, set } from '../redux'; import { assign, ReducerRegistry, set } from '../redux';
@ -351,7 +353,7 @@ function _setReceiveVideoQuality(state, action) {
* reduction of the specified action. * reduction of the specified action.
*/ */
function _setRoom(state, action) { function _setRoom(state, action) {
let room = action.room; let { room } = action;
if (!isRoomValid(room)) { if (!isRoomValid(room)) {
// Technically, there are multiple values which don't represent valid // Technically, there are multiple values which don't represent valid

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Component } from 'react'; import { Component } from 'react';
@ -28,18 +30,21 @@ export default class AbstractDialog extends Component {
dispatch: PropTypes.func dispatch: PropTypes.func
}; };
_mounted: boolean;
state = {
};
/** /**
* Initializes a new {@code AbstractDialog} instance. * Initializes a new {@code AbstractDialog} instance.
* *
* @param {Object} props - The read-only React {@code Component} props with * @param {Object} props - The read-only React {@code Component} props with
* which the new instance is to be initialized. * which the new instance is to be initialized.
*/ */
constructor(props) { constructor(props: Object) {
super(props); super(props);
this.state = { // Bind event handlers so they are only bound once per instance.
};
this._onCancel = this._onCancel.bind(this); this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this); this._onSubmit = this._onSubmit.bind(this);
this._onSubmitFulfilled = this._onSubmitFulfilled.bind(this); this._onSubmitFulfilled = this._onSubmitFulfilled.bind(this);
@ -66,6 +71,8 @@ export default class AbstractDialog extends Component {
this._mounted = false; this._mounted = false;
} }
_onCancel: () => void;
/** /**
* Dispatches a redux action to hide this dialog when it's canceled. * Dispatches a redux action to hide this dialog when it's canceled.
* *
@ -81,19 +88,21 @@ export default class AbstractDialog extends Component {
} }
} }
_onSubmit: (?string) => void;
/** /**
* Submits this dialog. If the React {@code Component} prop * Submits this {@code Dialog}. If the React {@code Component} prop
* {@code onSubmit} is defined, the function that is the value of the prop * {@code onSubmit} is defined, the function that is the value of the prop
* is invoked. If the function returns a {@code thenable}, then the * is invoked. If the function returns a {@code thenable}, then the
* resolution of the {@code thenable} is awaited. If the submission * resolution of the {@code thenable} is awaited. If the submission
* completes successfully, a redux action will be dispatched to hide this * completes successfully, a redux action will be dispatched to hide this
* dialog. * dialog.
* *
* @private * @protected
* @param {string} value - The submitted value if any. * @param {string} [value] - The submitted value if any.
* @returns {void} * @returns {void}
*/ */
_onSubmit(value) { _onSubmit(value: ?string) {
const { okDisabled, onSubmit } = this.props; const { okDisabled, onSubmit } = this.props;
if (typeof okDisabled === 'undefined' || !okDisabled) { if (typeof okDisabled === 'undefined' || !okDisabled) {
@ -125,6 +134,8 @@ export default class AbstractDialog extends Component {
} }
} }
_onSubmitFulfilled: () => void;
/** /**
* Notifies this {@code AbstractDialog} that it has been submitted * Notifies this {@code AbstractDialog} that it has been submitted
* successfully. Dispatches a redux action to hide this dialog after it has * successfully. Dispatches a redux action to hide this dialog after it has
@ -139,6 +150,8 @@ export default class AbstractDialog extends Component {
this.props.dispatch(hideDialog()); this.props.dispatch(hideDialog());
} }
_onSubmitRejected: () => void;
/** /**
* Notifies this {@code AbstractDialog} that its submission has failed. * Notifies this {@code AbstractDialog} that its submission has failed.
* *

View File

@ -108,14 +108,9 @@ class Dialog extends AbstractDialog {
// Secondly, we cannot get Prompt's default behavior anyway // Secondly, we cannot get Prompt's default behavior anyway
// because we've removed Prompt and we're preserving whatever // because we've removed Prompt and we're preserving whatever
// it's rendered only. // it's rendered only.
return ( return this._cloneElement(element, /* props */ {
React.cloneElement( onRequestClose: this._onCancel
element, });
/* props */ {
onRequestClose: this._onCancel
},
...React.Children.toArray(element.props.children))
);
} }
if (type === TextInput) { if (type === TextInput) {
@ -146,14 +141,9 @@ class Dialog extends AbstractDialog {
break; break;
} }
return ( return this._cloneElement(element, /* props */ {
React.cloneElement( style: set(style, _TAG_KEY, undefined)
element, });
/* props */ {
style: set(style, _TAG_KEY, undefined)
},
...React.Children.toArray(element.props.children))
);
} }
} }
@ -163,6 +153,23 @@ class Dialog extends AbstractDialog {
return element; return element;
} }
/**
* Clones a specific {@code ReactElement} and adds/merges specific props
* into the clone.
*
* @param {ReactElement} element - The {@code ReactElement} to clone.
* @param {Object} props - The props to add/merge into the clone.
* @returns {ReactElement} The close of the specified {@code element} with
* the specified {@code props} added/merged.
*/
_cloneElement(element, props) {
return (
React.cloneElement(
element,
props,
...React.Children.toArray(element.props.children)));
}
/** /**
* Creates a deep clone of a specific {@code ReactElement} with the results * Creates a deep clone of a specific {@code ReactElement} with the results
* of calling a specific function on every node of a specific * of calling a specific function on every node of a specific

View File

@ -1,3 +1,5 @@
// @flow
import { _handleParticipantError } from '../base/conference'; import { _handleParticipantError } from '../base/conference';
import { MEDIA_TYPE, VIDEO_TYPE } from '../base/media'; import { MEDIA_TYPE, VIDEO_TYPE } from '../base/media';
import { import {
@ -16,9 +18,9 @@ import {
* @returns {Function} * @returns {Function}
*/ */
export function selectParticipant() { export function selectParticipant() {
return (dispatch, getState) => { return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState(); const state = getState();
const conference = state['features/base/conference'].conference; const { conference } = state['features/base/conference'];
if (conference) { if (conference) {
const largeVideo = state['features/large-video']; const largeVideo = state['features/large-video'];
@ -51,7 +53,7 @@ export function selectParticipant() {
* @returns {Function} * @returns {Function}
*/ */
export function selectParticipantInLargeVideo() { export function selectParticipantInLargeVideo() {
return (dispatch, getState) => { return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState(); const state = getState();
const participantId = _electParticipantInLargeVideo(state); const participantId = _electParticipantInLargeVideo(state);
const largeVideo = state['features/large-video']; const largeVideo = state['features/large-video'];
@ -76,7 +78,7 @@ export function selectParticipantInLargeVideo() {
* resolution: number * resolution: number
* }} * }}
*/ */
export function updateKnownLargeVideoResolution(resolution) { export function updateKnownLargeVideoResolution(resolution: number) {
return { return {
type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
resolution resolution

View File

@ -85,20 +85,21 @@ function _getSymbolDescription(symbol: Symbol) {
* apps may listen to such events via the mechanisms provided by the (native) * apps may listen to such events via the mechanisms provided by the (native)
* mobile Jitsi Meet SDK. * mobile Jitsi Meet SDK.
* *
* @param {Object} store - The redux store associated with the need to send the * @param {Object} store - The redux store.
* specified event.
* @param {string} name - The name of the event to send. * @param {string} name - The name of the event to send.
* @param {Object} data - The details/specifics of the event to send determined * @param {Object} data - The details/specifics of the event to send determined
* by/associated with the specified {@code name}. * by/associated with the specified {@code name}.
* @private * @private
* @returns {void} * @returns {void}
*/ */
function _sendEvent(store: Object, name: string, data: Object) { function _sendEvent(
{ getState }: { getState: Function },
name: string,
data: Object) {
// The JavaScript App needs to provide uniquely identifying information // The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the former // to the native ExternalAPI module so that the latter may match the former
// to the native JitsiMeetView which hosts it. // to the native JitsiMeetView which hosts it.
const state = store.getState(); const { app } = getState()['features/app'];
const { app } = state['features/app'];
if (app) { if (app) {
const { externalAPIScope } = app.props; const { externalAPIScope } = app.props;

View File

@ -1,3 +1,5 @@
// @flow
import { CONFERENCE_FAILED } from '../base/conference'; import { CONFERENCE_FAILED } from '../base/conference';
import { import {
CONNECTION_ESTABLISHED, CONNECTION_ESTABLISHED,
@ -140,10 +142,12 @@ function _connectionWillConnect(
* @returns {Object} The new state of the feature overlay after the reduction of * @returns {Object} The new state of the feature overlay after the reduction of
* the specified action. * the specified action.
*/ */
function _mediaPermissionPromptVisibilityChanged(state, action) { function _mediaPermissionPromptVisibilityChanged(
state,
{ browser, isVisible }) {
return assign(state, { return assign(state, {
browser: action.browser, browser,
isMediaPermissionPromptVisible: action.isVisible isMediaPermissionPromptVisible: isVisible
}); });
} }

View File

@ -1,3 +1,5 @@
// @flow
import { setPassword } from '../base/conference'; import { setPassword } from '../base/conference';
import { hideDialog, openDialog } from '../base/dialog'; import { hideDialog, openDialog } from '../base/dialog';
import { PasswordRequiredPrompt, RoomLockPrompt } from './components'; import { PasswordRequiredPrompt, RoomLockPrompt } from './components';
@ -9,15 +11,12 @@ import { PasswordRequiredPrompt, RoomLockPrompt } from './components';
* if specified or undefined if the current JitsiConference is to be locked. * if specified or undefined if the current JitsiConference is to be locked.
* @returns {Function} * @returns {Function}
*/ */
export function beginRoomLockRequest(conference) { export function beginRoomLockRequest(conference: ?Object) {
return (dispatch, getState) => { return (dispatch: Function, getState: Function) => {
if (typeof conference === 'undefined') { if (typeof conference === 'undefined') {
const state = getState();
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
conference = state['features/base/conference'].conference; conference = getState()['features/base/conference'].conference;
} }
if (conference) { if (conference) {
dispatch(openDialog(RoomLockPrompt, { conference })); dispatch(openDialog(RoomLockPrompt, { conference }));
} }
@ -33,15 +32,15 @@ export function beginRoomLockRequest(conference) {
* the specified conference. * the specified conference.
* @returns {Function} * @returns {Function}
*/ */
export function endRoomLockRequest(conference, password) { export function endRoomLockRequest(
return dispatch => { conference: { lock: Function },
password: ?string) {
return (dispatch: Function) => {
const setPassword_ const setPassword_
= password = password
? dispatch(setPassword(conference, conference.lock, password)) ? dispatch(setPassword(conference, conference.lock, password))
: Promise.resolve(); : Promise.resolve();
const endRoomLockRequest_ = () => { const endRoomLockRequest_ = () => dispatch(hideDialog(RoomLockPrompt));
dispatch(hideDialog());
};
setPassword_.then(endRoomLockRequest_, endRoomLockRequest_); setPassword_.then(endRoomLockRequest_, endRoomLockRequest_);
}; };
@ -59,6 +58,6 @@ export function endRoomLockRequest(conference, password) {
* props: PropTypes * props: PropTypes
* }} * }}
*/ */
export function _showPasswordDialog(conference) { export function _openPasswordRequiredPrompt(conference: Object) {
return openDialog(PasswordRequiredPrompt, { conference }); return openDialog(PasswordRequiredPrompt, { conference });
} }

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -6,35 +8,44 @@ import { setPassword } from '../../base/conference';
import { Dialog } from '../../base/dialog'; import { Dialog } from '../../base/dialog';
/** /**
* Implements a React Component which prompts the user when a password is * {@code PasswordRequiredPrompt}'s React {@code Component} prop types.
* required to join a conference. */
type Props = {
/**
* The {@code JitsiConference} which requires a password.
*
* @type {JitsiConference}
*/
conference: { join: Function },
dispatch: Dispatch<*>
};
/**
* Implements a React {@code Component} which prompts the user when a password
* is required to join a conference.
*/ */
class PasswordRequiredPrompt extends Component { class PasswordRequiredPrompt extends Component {
/** /**
* PasswordRequiredPrompt component's property types. * {@code PasswordRequiredPrompt}'s React {@code Component} prop types.
* *
* @static * @static
*/ */
static propTypes = { static propTypes = {
/**
* The JitsiConference which requires a password.
*
* @type {JitsiConference}
*/
conference: PropTypes.object, conference: PropTypes.object,
dispatch: PropTypes.func dispatch: PropTypes.func
}; };
/** /**
* Initializes a new PasswordRequiredPrompt instance. * Initializes a new {@code PasswordRequiredPrompt} instance.
* *
* @param {Object} props - The read-only properties with which the new * @param {Props} props - The read-only React {@code Component} props with
* instance is to be initialized. * which the new instance is to be initialized.
*/ */
constructor(props) { constructor(props: Props) {
super(props); super(props);
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this); this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this); this._onSubmit = this._onSubmit.bind(this);
} }
@ -55,11 +66,14 @@ class PasswordRequiredPrompt extends Component {
); );
} }
_onCancel: () => boolean;
/** /**
* Notifies this prompt that it has been dismissed by cancel. * Notifies this prompt that it has been dismissed by cancel.
* *
* @private * @private
* @returns {boolean} True to hide this dialog/prompt; otherwise, false. * @returns {boolean} If this prompt is to be closed/hidden, {@code true};
* otherwise, {@code false}.
*/ */
_onCancel() { _onCancel() {
// XXX The user has canceled this prompt for a password so we are to // XXX The user has canceled this prompt for a password so we are to
@ -69,16 +83,19 @@ class PasswordRequiredPrompt extends Component {
return this._onSubmit(undefined); return this._onSubmit(undefined);
} }
_onSubmit: (?string) => boolean;
/** /**
* Notifies this prompt that it has been dismissed by submitting a specific * Notifies this prompt that it has been dismissed by submitting a specific
* value. * value.
* *
* @param {string} value - The submitted value. * @param {string|undefined} value - The submitted value.
* @private * @private
* @returns {boolean} True to hide this dialog/prompt; otherwise, false. * @returns {boolean} If this prompt is to be closed/hidden, {@code true};
* otherwise, {@code false}.
*/ */
_onSubmit(value) { _onSubmit(value: ?string) {
const conference = this.props.conference; const { conference }: { conference: { join: Function } } = this.props;
this.props.dispatch(setPassword(conference, conference.join, value)); this.props.dispatch(setPassword(conference, conference.join, value));

View File

@ -1,3 +1,5 @@
// @flow
import AKFieldText from '@atlaskit/field-text'; import AKFieldText from '@atlaskit/field-text';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
@ -28,6 +30,10 @@ class PasswordRequiredPrompt extends Component {
t: PropTypes.func t: PropTypes.func
}; };
state = {
password: ''
};
/** /**
* Initializes a new PasswordRequiredPrompt instance. * Initializes a new PasswordRequiredPrompt instance.
* *
@ -37,10 +43,7 @@ class PasswordRequiredPrompt extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { // Bind event handlers so they are only bound once per instance.
password: ''
};
this._onPasswordChanged = this._onPasswordChanged.bind(this); this._onPasswordChanged = this._onPasswordChanged.bind(this);
this._onSubmit = this._onSubmit.bind(this); this._onSubmit = this._onSubmit.bind(this);
} }
@ -59,7 +62,8 @@ class PasswordRequiredPrompt extends Component {
titleKey = 'dialog.passwordRequired' titleKey = 'dialog.passwordRequired'
width = 'small'> width = 'small'>
{ this._renderBody() } { this._renderBody() }
</Dialog>); </Dialog>
);
} }
/** /**
@ -84,6 +88,8 @@ class PasswordRequiredPrompt extends Component {
); );
} }
_onPasswordChanged: ({ target: { value: * }}) => void;
/** /**
* Notifies this dialog that password has changed. * Notifies this dialog that password has changed.
* *
@ -91,17 +97,19 @@ class PasswordRequiredPrompt extends Component {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_onPasswordChanged(event) { _onPasswordChanged({ target: { value } }) {
this.setState({ this.setState({
password: event.target.value password: value
}); });
} }
_onSubmit: () => boolean;
/** /**
* Dispatches action to submit value from thus dialog. * Dispatches action to submit value from this dialog.
* *
* @private * @private
* @returns {void} * @returns {boolean}
*/ */
_onSubmit() { _onSubmit() {
const { conference } = this.props; const { conference } = this.props;

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -29,13 +31,13 @@ class RoomLockPrompt extends Component {
/** /**
* Initializes a new RoomLockPrompt instance. * Initializes a new RoomLockPrompt instance.
* *
* @param {Object} props - The read-only properties with which the new * @param {Props} props - The read-only properties with which the new
* instance is to be initialized. * instance is to be initialized.
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this); this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this); this._onSubmit = this._onSubmit.bind(this);
} }
@ -56,6 +58,8 @@ class RoomLockPrompt extends Component {
); );
} }
_onCancel: () => boolean;
/** /**
* Notifies this prompt that it has been dismissed by cancel. * Notifies this prompt that it has been dismissed by cancel.
* *
@ -68,17 +72,19 @@ class RoomLockPrompt extends Component {
return this._onSubmit(undefined); return this._onSubmit(undefined);
} }
_onSubmit: (?string) => boolean;
/** /**
* Notifies this prompt that it has been dismissed by submitting a specific * Notifies this prompt that it has been dismissed by submitting a specific
* value. * value.
* *
* @param {string} value - The submitted value. * @param {string|undefined} value - The submitted value.
* @private * @private
* @returns {boolean} False because we do not want to hide this * @returns {boolean} False because we do not want to hide this
* dialog/prompt as the hiding will be handled inside endRoomLockRequest * dialog/prompt as the hiding will be handled inside endRoomLockRequest
* after setting the password is resolved. * after setting the password is resolved.
*/ */
_onSubmit(value) { _onSubmit(value: ?string) {
this.props.dispatch(endRoomLockRequest(this.props.conference, value)); this.props.dispatch(endRoomLockRequest(this.props.conference, value));
return false; // Do not hide. return false; // Do not hide.

View File

@ -1,2 +1,2 @@
export { default as RoomLockPrompt } from './RoomLockPrompt';
export { default as PasswordRequiredPrompt } from './PasswordRequiredPrompt'; export { default as PasswordRequiredPrompt } from './PasswordRequiredPrompt';
export { default as RoomLockPrompt } from './RoomLockPrompt';

View File

@ -1,6 +1,4 @@
/* global APP */ // @flow
import UIEvents from '../../../service/UI/UIEvents';
import { import {
CONFERENCE_FAILED, CONFERENCE_FAILED,
@ -9,8 +7,11 @@ import {
} from '../base/conference'; } from '../base/conference';
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet'; import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import UIEvents from '../../../service/UI/UIEvents';
import { _showPasswordDialog } from './actions'; import { _openPasswordRequiredPrompt } from './actions';
declare var APP: Object;
const logger = require('jitsi-meet-logger').getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
@ -27,7 +28,7 @@ MiddlewareRegistry.register(store => next => action => {
const { conference, error } = action; const { conference, error } = action;
if (conference && error === JitsiConferenceErrors.PASSWORD_REQUIRED) { if (conference && error === JitsiConferenceErrors.PASSWORD_REQUIRED) {
store.dispatch(_showPasswordDialog(conference)); store.dispatch(_openPasswordRequiredPrompt(conference));
} }
break; break;
} }

View File

@ -1,3 +1,5 @@
// @flow
import { appNavigate } from '../app'; import { appNavigate } from '../app';
import { SET_WEBRTC_READY } from '../base/lib-jitsi-meet'; import { SET_WEBRTC_READY } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
@ -28,26 +30,25 @@ MiddlewareRegistry.register(store => next => action => {
* specified action to the specified store. * specified action to the specified store.
* @param {Action} action - The Redux action SET_WEBRTC_READY which is being * @param {Action} action - The Redux action SET_WEBRTC_READY which is being
* dispatched in the specified store. * dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the * @returns {Object} The new state that is the result of the reduction of the
* specified action. * specified action.
* @private
*/ */
function _setWebRTCReady(store, next, action) { function _setWebRTCReady({ dispatch, getState }, next, action) {
const nextState = next(action); const result = next(action);
// FIXME The feature unsupported-browser needs to notify the app that it may // FIXME The feature unsupported-browser needs to notify the app that it may
// need to render a different Component at its current location because the // need to render a different Component at its current location because the
// execution enviroment has changed. The current location is not necessarily // execution enviroment has changed. The current location is not necessarily
// available through window.location (e.g. on mobile) but the following // available through window.location (e.g. on mobile) but the following
// works at the time of this writing. // works at the time of this writing.
const windowLocation const windowLocation = getState()['features/app'].app.getWindowLocation();
= store.getState()['features/app'].app.getWindowLocation();
if (windowLocation) { if (windowLocation) {
const href = windowLocation.href; const { href } = windowLocation;
href && store.dispatch(appNavigate(href)); href && dispatch(appNavigate(href));
} }
return nextState; return result;
} }

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Component } from 'react'; import { Component } from 'react';
@ -6,6 +8,14 @@ import { isRoomValid } from '../../base/conference';
import { generateRoomWithoutSeparator } from '../functions'; import { generateRoomWithoutSeparator } from '../functions';
/**
* {@code AbstractWelcomePage}'s React {@code Component} prop types.
*/
type Props = {
_room: string,
dispatch: Dispatch<*>
};
/** /**
* Base (abstract) class for container component rendering the welcome page. * Base (abstract) class for container component rendering the welcome page.
* *
@ -22,38 +32,39 @@ export class AbstractWelcomePage extends Component {
dispatch: PropTypes.func dispatch: PropTypes.func
}; };
_mounted: ?boolean;
/**
* Save room name into component's local state.
*
* @type {Object}
* @property {number|null} animateTimeoutId - Identifier of the letter
* animation timeout.
* @property {string} generatedRoomname - Automatically generated room name.
* @property {string} room - Room name.
* @property {string} roomPlaceholder - Room placeholder that's used as a
* placeholder for input.
* @property {nubmer|null} updateTimeoutId - Identifier of the timeout
* updating the generated room name.
*/
state = {
animateTimeoutId: undefined,
generatedRoomname: '',
joining: false,
room: '',
roomPlaceholder: '',
updateTimeoutId: undefined
};
/** /**
* Initializes a new {@code AbstractWelcomePage} instance. * Initializes a new {@code AbstractWelcomePage} instance.
* *
* @param {Object} props - The React {@code Component} props to initialize * @param {Props} props - The React {@code Component} props to initialize
* the new {@code AbstractWelcomePage} instance with. * the new {@code AbstractWelcomePage} instance with.
*/ */
constructor(props) { constructor(props: Props) {
super(props); super(props);
/**
* Save room name into component's local state.
*
* @type {Object}
* @property {number|null} animateTimeoutId - Identifier of the letter
* animation timeout.
* @property {string} generatedRoomname - Automatically generated
* room name.
* @property {string} room - Room name.
* @property {string} roomPlaceholder - Room placeholder
* that's used as a placeholder for input.
* @property {nubmer|null} updateTimeoutId - Identifier of the timeout
* updating the generated room name.
*/
this.state = {
animateTimeoutId: null,
generatedRoomname: '',
joining: false,
room: '',
roomPlaceholder: '',
updateTimeoutId: null
};
// Bind event handlers so they are only bound once per instance. // Bind event handlers so they are only bound once per instance.
this._animateRoomnameChanging this._animateRoomnameChanging
= this._animateRoomnameChanging.bind(this); = this._animateRoomnameChanging.bind(this);
@ -77,9 +88,9 @@ export class AbstractWelcomePage extends Component {
* before this mounted component receives new props. * before this mounted component receives new props.
* *
* @inheritdoc * @inheritdoc
* @param {Object} nextProps - New props component will receive. * @param {Props} nextProps - New props component will receive.
*/ */
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps: Props) {
this.setState({ room: nextProps._room }); this.setState({ room: nextProps._room });
} }
@ -94,6 +105,8 @@ export class AbstractWelcomePage extends Component {
this._mounted = false; this._mounted = false;
} }
_animateRoomnameChanging: (string) => void;
/** /**
* Animates the changing of the room name. * Animates the changing of the room name.
* *
@ -102,8 +115,8 @@ export class AbstractWelcomePage extends Component {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_animateRoomnameChanging(word) { _animateRoomnameChanging(word: string) {
let animateTimeoutId = null; let animateTimeoutId;
const roomPlaceholder = this.state.roomPlaceholder + word.substr(0, 1); const roomPlaceholder = this.state.roomPlaceholder + word.substr(0, 1);
if (word.length > 1) { if (word.length > 1) {
@ -115,7 +128,6 @@ export class AbstractWelcomePage extends Component {
}, },
70); 70);
} }
this.setState({ this.setState({
animateTimeoutId, animateTimeoutId,
roomPlaceholder roomPlaceholder
@ -145,6 +157,8 @@ export class AbstractWelcomePage extends Component {
return this.state.joining || !isRoomValid(this.state.room); return this.state.joining || !isRoomValid(this.state.room);
} }
_onJoin: () => void;
/** /**
* Handles joining. Either by clicking on 'Join' button * Handles joining. Either by clicking on 'Join' button
* or by pressing 'Enter' in room name input field. * or by pressing 'Enter' in room name input field.
@ -160,15 +174,16 @@ export class AbstractWelcomePage extends Component {
// By the time the Promise of appNavigate settles, this component // By the time the Promise of appNavigate settles, this component
// may have already been unmounted. // may have already been unmounted.
const onAppNavigateSettled = () => { const onAppNavigateSettled
this._mounted && this.setState({ joining: false }); = () => this._mounted && this.setState({ joining: false });
};
this.props.dispatch(appNavigate(room)) this.props.dispatch(appNavigate(room))
.then(onAppNavigateSettled, onAppNavigateSettled); .then(onAppNavigateSettled, onAppNavigateSettled);
} }
} }
_onRoomChange: (string) => void;
/** /**
* Handles 'change' event for the room name text input field. * Handles 'change' event for the room name text input field.
* *
@ -177,10 +192,12 @@ export class AbstractWelcomePage extends Component {
* @protected * @protected
* @returns {void} * @returns {void}
*/ */
_onRoomChange(value) { _onRoomChange(value: string) {
this.setState({ room: value }); this.setState({ room: value });
} }
_updateRoomname: () => void;
/** /**
* Triggers the generation of a new room name and initiates an animation of * Triggers the generation of a new room name and initiates an animation of
* its changing. * its changing.
@ -214,7 +231,7 @@ export class AbstractWelcomePage extends Component {
* _room: string * _room: string
* }} * }}
*/ */
export function _mapStateToProps(state) { export function _mapStateToProps(state: Object) {
return { return {
_room: state['features/base/conference'].room _room: state['features/base/conference'].room
}; };