import React, { Component } from 'react'; import { appNavigate } from '../../app'; import { isRoomValid } from '../../base/conference'; import { VideoTrack } from '../../base/media'; import { getLocalVideoTrack } from '../../base/tracks'; import { generateRoomWithoutSeparator } from '../roomnameGenerator'; /** * Base (abstract) class for container component rendering the welcome page. * * @abstract */ export class AbstractWelcomePage extends Component { /** * AbstractWelcomePage component's property types. * * @static */ static propTypes = { _localVideoTrack: React.PropTypes.object, _room: React.PropTypes.string, dispatch: React.PropTypes.func } /** * Initializes a new AbstractWelcomePage instance, including the initial * state of the room name input. * * @param {Object} props - Component properties. */ constructor(props) { super(props); /** * Save room name into component's local state. * * @type {Object} * @property {number|null} animateTimeoutId - Identificator for * 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 - Identificator for * updating generated room name. */ this.state = { animateTimeoutId: null, generatedRoomname: '', room: '', roomPlaceholder: '', updateTimeoutId: null }; // Bind event handlers so they are only bound once for every instance. this._animateRoomnameChanging = this._animateRoomnameChanging.bind(this); this._onJoin = this._onJoin.bind(this); this._onRoomChange = this._onRoomChange.bind(this); this._updateRoomname = this._updateRoomname.bind(this); } /** * This method is executed when component receives new properties. * * @inheritdoc * @param {Object} nextProps - New props component will receive. */ componentWillReceiveProps(nextProps) { this.setState({ room: nextProps._room }); } /** * This method is executed when method will be unmounted from DOM. * * @inheritdoc */ componentWillUnmount() { this._clearTimeouts(); } /** * Animates the changing of the room name. * * @param {string} word - The part of room name that should be added to * placeholder. * @private * @returns {void} */ _animateRoomnameChanging(word) { let animateTimeoutId = null; const roomPlaceholder = this.state.roomPlaceholder + word.substr(0, 1); if (word.length > 1) { animateTimeoutId = setTimeout( () => { this._animateRoomnameChanging( word.substring(1, word.length)); }, 70); } this.setState({ animateTimeoutId, roomPlaceholder }); } /** * Method that clears timeouts for animations and updates of room name. * * @private * @returns {void} */ _clearTimeouts() { clearTimeout(this.state.animateTimeoutId); clearTimeout(this.state.updateTimeoutId); } /** * Determines whether the 'Join' button is (to be) disabled i.e. there's no * valid room name typed into the respective text input field. * * @protected * @returns {boolean} If the 'Join' button is (to be) disabled, true; * otherwise, false. */ _isJoinDisabled() { return !isRoomValid(this.state.room); } /** * Handles joining. Either by clicking on 'Join' button * or by pressing 'Enter' in room name input field. * * @protected * @returns {void} */ _onJoin() { const room = this.state.room || this.state.generatedRoomname; if (room) { this.props.dispatch(appNavigate(room)); } } /** * Handles 'change' event for the room name text input field. * * @param {string} value - The text typed into the respective text input * field. * @protected * @returns {void} */ _onRoomChange(value) { this.setState({ room: value }); } /** * Renders a local video if any. * * @protected * @returns {(ReactElement|null)} */ _renderLocalVideo() { return ( ); } /** * Triggers the generation of a new room name and initiates an animation of * its changing. * * @protected * @returns {void} */ _updateRoomname() { const generatedRoomname = generateRoomWithoutSeparator(); const roomPlaceholder = ''; const updateTimeoutId = setTimeout(this._updateRoomname, 10000); this._clearTimeouts(); this.setState( { generatedRoomname, roomPlaceholder, updateTimeoutId }, () => this._animateRoomnameChanging(generatedRoomname)); } } /** * Selects local video track from tracks in state, local participant and room * and maps them to component props. It seems it's not possible to 'connect' * base component and then extend from it. So we export this function in order * to be used in child classes for 'connect'. * * @param {Object} state - Redux state. * @protected * @returns {{ * _localVideoTrack: (Track|undefined), * _room: string * }} */ export function _mapStateToProps(state) { const conference = state['features/base/conference']; const tracks = state['features/base/tracks']; return { _localVideoTrack: getLocalVideoTrack(tracks), _room: conference.room }; }