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 { getJitsiMeetGlobalNS, loadScript } from '../base/util';
@ -13,7 +15,7 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* is being dispatched.
* @returns {void}
*/
export function initAnalytics({ getState }) {
export function initAnalytics({ getState }: { getState: Function }) {
getJitsiMeetGlobalNS().analyticsHandlers = [];
window.analyticsHandlers = []; // Legacy support.
@ -36,12 +38,12 @@ export function initAnalytics({ getState }) {
_loadHandlers(analyticsScriptUrls, handlerConstructorOptions)
.then(handlers => {
const permanentProperties = {
roomName: getState()['features/base/conference'].room,
const state = getState();
const permanentProperties: Object = {
roomName: state['features/base/conference'].room,
userAgent: navigator.userAgent
};
const { group, server } = getState()['features/jwt'];
const { group, server } = state['features/jwt'];
if (server) {
permanentProperties.server = server;
@ -50,13 +52,14 @@ export function initAnalytics({ getState }) {
permanentProperties.group = group;
}
// optionally include local deployment information based on
// the contents of window.config.deploymentInfo
if (config.deploymentInfo) {
for (const key in config.deploymentInfo) {
if (config.deploymentInfo.hasOwnProperty(key)) {
permanentProperties[key]
= config.deploymentInfo[key];
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
const { deploymentInfo } = config;
if (deploymentInfo) {
for (const key in deploymentInfo) {
if (deploymentInfo.hasOwnProperty(key)) {
permanentProperties[key] = deploymentInfo[key];
}
}
}
@ -113,23 +116,22 @@ function _loadHandlers(scriptURLs, handlerConstructorOptions) {
if (analyticsHandlers.length === 0) {
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 React, { Component } from 'react';
import { Text } from 'react-native';
@ -79,6 +81,8 @@ class WaitForOwnerDialog extends Component {
);
}
_onCancel: () => void;
/**
* Called when the cancel button is clicked.
*
@ -89,6 +93,8 @@ class WaitForOwnerDialog extends Component {
this.props.dispatch(cancelWaitForOwner());
}
_onLogin: () => void;
/**
* Called when the OK button is clicked.
*
@ -102,11 +108,11 @@ class WaitForOwnerDialog extends Component {
/**
* Renders a specific {@code string} which may contain HTML.
*
* @param {string} html - The {@code string} which may contain HTML to
* render.
* @param {string|undefined} html - The {@code string} which may
* contain HTML to render.
* @returns {ReactElement[]|string}
*/
_renderHTML(html) {
_renderHTML(html: ?string) {
if (typeof html === 'string') {
// At the time of this writing, the specified HTML contains a couple
// of spaces one after the other. They do not cause a visible
@ -164,9 +170,7 @@ class WaitForOwnerDialog extends Component {
* }}
*/
function _mapStateToProps(state) {
const {
authRequired
} = state['features/base/conference'];
const { authRequired } = state['features/base/conference'];
return {
_room: authRequired && authRequired.getName()

View File

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

View File

@ -1,4 +1,5 @@
/* global APP */
// @flow
import UIEvents from '../../../../service/UI/UIEvents';
import { CONNECTION_ESTABLISHED } from '../connection';
@ -34,6 +35,8 @@ import {
_removeLocalTracksFromConference
} from './functions';
declare var APP: Object;
/**
* 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
* specified action.
*/
function _setReceiveVideoQuality(store, next, action) {
const { audioOnly, conference }
= store.getState()['features/base/conference'];
function _setReceiveVideoQuality({ dispatch, getState }, next, action) {
const { audioOnly, conference } = getState()['features/base/conference'];
conference.setReceiverVideoConstraint(action.receiveVideoQuality);
if (audioOnly) {
store.dispatch(toggleAudioOnly());
dispatch(toggleAudioOnly());
}
return next(action);
@ -321,9 +322,9 @@ function _setReceiveVideoQuality(store, next, action) {
* @private
* @returns {Promise}
*/
function _syncConferenceLocalTracksWithState(store, action) {
const state = store.getState()['features/base/conference'];
const conference = state.conference;
function _syncConferenceLocalTracksWithState({ getState }, action) {
const state = getState()['features/base/conference'];
const { conference } = state;
let promise;
// 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
* specified action.
*/
function _syncReceiveVideoQuality(store, next, action) {
const state = store.getState()['features/base/conference'];
function _syncReceiveVideoQuality({ getState }, next, action) {
const state = getState()['features/base/conference'];
state.conference.setReceiverVideoConstraint(state.receiveVideoQuality);

View File

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

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types';
import { Component } from 'react';
@ -28,18 +30,21 @@ export default class AbstractDialog extends Component {
dispatch: PropTypes.func
};
_mounted: boolean;
state = {
};
/**
* Initializes a new {@code AbstractDialog} instance.
*
* @param {Object} props - The read-only React {@code Component} props with
* which the new instance is to be initialized.
*/
constructor(props) {
constructor(props: Object) {
super(props);
this.state = {
};
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this);
this._onSubmitFulfilled = this._onSubmitFulfilled.bind(this);
@ -66,6 +71,8 @@ export default class AbstractDialog extends Component {
this._mounted = false;
}
_onCancel: () => void;
/**
* 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
* is invoked. If the function returns a {@code thenable}, then the
* resolution of the {@code thenable} is awaited. If the submission
* completes successfully, a redux action will be dispatched to hide this
* dialog.
*
* @private
* @param {string} value - The submitted value if any.
* @protected
* @param {string} [value] - The submitted value if any.
* @returns {void}
*/
_onSubmit(value) {
_onSubmit(value: ?string) {
const { okDisabled, onSubmit } = this.props;
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
* 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());
}
_onSubmitRejected: () => void;
/**
* 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
// because we've removed Prompt and we're preserving whatever
// it's rendered only.
return (
React.cloneElement(
element,
/* props */ {
onRequestClose: this._onCancel
},
...React.Children.toArray(element.props.children))
);
return this._cloneElement(element, /* props */ {
onRequestClose: this._onCancel
});
}
if (type === TextInput) {
@ -146,14 +141,9 @@ class Dialog extends AbstractDialog {
break;
}
return (
React.cloneElement(
element,
/* props */ {
style: set(style, _TAG_KEY, undefined)
},
...React.Children.toArray(element.props.children))
);
return this._cloneElement(element, /* props */ {
style: set(style, _TAG_KEY, undefined)
});
}
}
@ -163,6 +153,23 @@ class Dialog extends AbstractDialog {
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
* of calling a specific function on every node of a specific

View File

@ -1,3 +1,5 @@
// @flow
import { _handleParticipantError } from '../base/conference';
import { MEDIA_TYPE, VIDEO_TYPE } from '../base/media';
import {
@ -16,9 +18,9 @@ import {
* @returns {Function}
*/
export function selectParticipant() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const conference = state['features/base/conference'].conference;
const { conference } = state['features/base/conference'];
if (conference) {
const largeVideo = state['features/large-video'];
@ -51,7 +53,7 @@ export function selectParticipant() {
* @returns {Function}
*/
export function selectParticipantInLargeVideo() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const participantId = _electParticipantInLargeVideo(state);
const largeVideo = state['features/large-video'];
@ -76,7 +78,7 @@ export function selectParticipantInLargeVideo() {
* resolution: number
* }}
*/
export function updateKnownLargeVideoResolution(resolution) {
export function updateKnownLargeVideoResolution(resolution: number) {
return {
type: UPDATE_KNOWN_LARGE_VIDEO_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)
* mobile Jitsi Meet SDK.
*
* @param {Object} store - The redux store associated with the need to send the
* specified event.
* @param {Object} store - The redux store.
* @param {string} name - The name of the event to send.
* @param {Object} data - The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
* @private
* @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
// to the native ExternalAPI module so that the latter may match the former
// to the native JitsiMeetView which hosts it.
const state = store.getState();
const { app } = state['features/app'];
const { app } = getState()['features/app'];
if (app) {
const { externalAPIScope } = app.props;

View File

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

View File

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

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@ -6,35 +8,44 @@ import { setPassword } from '../../base/conference';
import { Dialog } from '../../base/dialog';
/**
* Implements a React Component which prompts the user when a password is
* required to join a conference.
* {@code PasswordRequiredPrompt}'s React {@code Component} prop types.
*/
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 {
/**
* PasswordRequiredPrompt component's property types.
* {@code PasswordRequiredPrompt}'s React {@code Component} prop types.
*
* @static
*/
static propTypes = {
/**
* The JitsiConference which requires a password.
*
* @type {JitsiConference}
*/
conference: PropTypes.object,
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
* instance is to be initialized.
* @param {Props} props - The read-only React {@code Component} props with
* which the new instance is to be initialized.
*/
constructor(props) {
constructor(props: 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._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.
*
* @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() {
// 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);
}
_onSubmit: (?string) => boolean;
/**
* Notifies this prompt that it has been dismissed by submitting a specific
* value.
*
* @param {string} value - The submitted value.
* @param {string|undefined} value - The submitted value.
* @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) {
const conference = this.props.conference;
_onSubmit(value: ?string) {
const { conference }: { conference: { join: Function } } = this.props;
this.props.dispatch(setPassword(conference, conference.join, value));

View File

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

View File

@ -1,3 +1,5 @@
// @flow
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@ -29,13 +31,13 @@ class RoomLockPrompt extends Component {
/**
* 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.
*/
constructor(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._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.
*
@ -68,17 +72,19 @@ class RoomLockPrompt extends Component {
return this._onSubmit(undefined);
}
_onSubmit: (?string) => boolean;
/**
* Notifies this prompt that it has been dismissed by submitting a specific
* value.
*
* @param {string} value - The submitted value.
* @param {string|undefined} value - The submitted value.
* @private
* @returns {boolean} False because we do not want to hide this
* dialog/prompt as the hiding will be handled inside endRoomLockRequest
* after setting the password is resolved.
*/
_onSubmit(value) {
_onSubmit(value: ?string) {
this.props.dispatch(endRoomLockRequest(this.props.conference, value));
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 RoomLockPrompt } from './RoomLockPrompt';

View File

@ -1,6 +1,4 @@
/* global APP */
import UIEvents from '../../../service/UI/UIEvents';
// @flow
import {
CONFERENCE_FAILED,
@ -9,8 +7,11 @@ import {
} from '../base/conference';
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
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);
@ -27,7 +28,7 @@ MiddlewareRegistry.register(store => next => action => {
const { conference, error } = action;
if (conference && error === JitsiConferenceErrors.PASSWORD_REQUIRED) {
store.dispatch(_showPasswordDialog(conference));
store.dispatch(_openPasswordRequiredPrompt(conference));
}
break;
}

View File

@ -1,3 +1,5 @@
// @flow
import { appNavigate } from '../app';
import { SET_WEBRTC_READY } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
@ -28,26 +30,25 @@ MiddlewareRegistry.register(store => next => action => {
* specified action to the specified store.
* @param {Action} action - The Redux action SET_WEBRTC_READY which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified action.
* @private
*/
function _setWebRTCReady(store, next, action) {
const nextState = next(action);
function _setWebRTCReady({ dispatch, getState }, next, action) {
const result = next(action);
// 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
// execution enviroment has changed. The current location is not necessarily
// available through window.location (e.g. on mobile) but the following
// works at the time of this writing.
const windowLocation
= store.getState()['features/app'].app.getWindowLocation();
const windowLocation = getState()['features/app'].app.getWindowLocation();
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 { Component } from 'react';
@ -6,6 +8,14 @@ import { isRoomValid } from '../../base/conference';
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.
*
@ -22,38 +32,39 @@ export class AbstractWelcomePage extends Component {
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.
*
* @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.
*/
constructor(props) {
constructor(props: 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.
this._animateRoomnameChanging
= this._animateRoomnameChanging.bind(this);
@ -77,9 +88,9 @@ export class AbstractWelcomePage extends Component {
* before this mounted component receives new props.
*
* @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 });
}
@ -94,6 +105,8 @@ export class AbstractWelcomePage extends Component {
this._mounted = false;
}
_animateRoomnameChanging: (string) => void;
/**
* Animates the changing of the room name.
*
@ -102,8 +115,8 @@ export class AbstractWelcomePage extends Component {
* @private
* @returns {void}
*/
_animateRoomnameChanging(word) {
let animateTimeoutId = null;
_animateRoomnameChanging(word: string) {
let animateTimeoutId;
const roomPlaceholder = this.state.roomPlaceholder + word.substr(0, 1);
if (word.length > 1) {
@ -115,7 +128,6 @@ export class AbstractWelcomePage extends Component {
},
70);
}
this.setState({
animateTimeoutId,
roomPlaceholder
@ -145,6 +157,8 @@ export class AbstractWelcomePage extends Component {
return this.state.joining || !isRoomValid(this.state.room);
}
_onJoin: () => void;
/**
* Handles joining. Either by clicking on 'Join' button
* 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
// may have already been unmounted.
const onAppNavigateSettled = () => {
this._mounted && this.setState({ joining: false });
};
const onAppNavigateSettled
= () => this._mounted && this.setState({ joining: false });
this.props.dispatch(appNavigate(room))
.then(onAppNavigateSettled, onAppNavigateSettled);
}
}
_onRoomChange: (string) => void;
/**
* Handles 'change' event for the room name text input field.
*
@ -177,10 +192,12 @@ export class AbstractWelcomePage extends Component {
* @protected
* @returns {void}
*/
_onRoomChange(value) {
_onRoomChange(value: string) {
this.setState({ room: value });
}
_updateRoomname: () => void;
/**
* Triggers the generation of a new room name and initiates an animation of
* its changing.
@ -214,7 +231,7 @@ export class AbstractWelcomePage extends Component {
* _room: string
* }}
*/
export function _mapStateToProps(state) {
export function _mapStateToProps(state: Object) {
return {
_room: state['features/base/conference'].room
};