diff --git a/Makefile b/Makefile index 84023eca60..7c5cbdfb4a 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,8 @@ deploy-appbundle: $(BUILD_DIR)/external_api.min.map \ $(BUILD_DIR)/device_selection_popup_bundle.min.js \ $(BUILD_DIR)/device_selection_popup_bundle.min.map \ + $(BUILD_DIR)/dial_in_info_bundle.min.js \ + $(BUILD_DIR)/dial_in_info_bundle.min.map \ $(BUILD_DIR)/alwaysontop.min.js \ $(BUILD_DIR)/alwaysontop.min.map \ $(OUTPUT_DIR)/analytics-ga.js \ diff --git a/css/modals/invite/_info.scss b/css/modals/invite/_info.scss index 0c81b4ee6d..c8761c7435 100644 --- a/css/modals/invite/_info.scss +++ b/css/modals/invite/_info.scss @@ -4,16 +4,20 @@ .info-dialog-action-link { display: inline-block; + line-height: 1.5em; a { cursor: pointer; + vertical-align: middle; } } .info-dialog-action-link:before { color: $linkFontColor; content: '\2022'; + font-size: 1.5em; padding: 0 10px; + vertical-align: middle; } .info-dialog-action-link:first-child:before { @@ -22,6 +26,8 @@ } .info-dialog-action-links { + font-weight: bold; + margin-top: 10px; white-space: nowrap; } @@ -39,16 +45,27 @@ .info-dialog-column { margin-right: 10px; + overflow: hidden; + + a, + a:active, + a:focus, + a:hover { + text-decoration: none; + } } .info-dialog-conference-url { - margin: 10px 0; max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .info-dialog-dial-in { + white-space: nowrap; + } + .info-dialog-icon { color: #6453C0; font-size: 16px; @@ -56,5 +73,55 @@ .info-dialog-title { font-weight: bold; + margin-bottom: 10px; + } + + .info-password, + .info-dialog-password, + .info-password-form { + display: flex; + } + + .info-password-field { + margin-left: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .info-password-none, + .info-password-remote { + opacity: 0.5; + } + + .info-password-input { + background-color: transparent; + border: none; + color: inherit; + padding-left: 0; + } + + .conference-id { + margin-left: 5px; + } +} + +.dial-in-page { + align-items: center; + display: flex; + flex-direction: column; + font-size: 24px; + height: 100%; + justify-content: center; + width: 100%; + + .dial-in-numbers-list { + font-size: 24px; + margin-top: 20px; + } + + .dial-in-conference-id { + text-align: center; + width: 30%; } } diff --git a/lang/main.json b/lang/main.json index 2e6a815e17..9d90574a80 100644 --- a/lang/main.json +++ b/lang/main.json @@ -533,9 +533,22 @@ "veryGood": "Very Good" }, "info": { - "copy": "Copy link", - "invite": "Invite in __app__", - "title": "Call access info", + "cancelPassword": "Cancel password", + "conferenceURL": "Link: __url__", + "country": "Country", + "dialANumber": "To join your meeting, dial one of these numbers and then enter this PIN: __conferenceID__#", + "dialInNumber": "Dial-in: __phoneNumber__", + "dialInConferenceID": "PIN: __conferenceID__#", + "dialInNotSupported": "Sorry, dialing in is currently not suppported.", + "genericError": "Whoops, something went wrong.", + "invitePhone": "To join by phone, dial __number__ and enter this PIN: __pin__#", + "invitePhoneAlternatives": "To view more phone numbers, click this link: __url__", + "inviteURL": "To join the video meeting, click this link: __url__", + "moreNumbers": "More numbers", + "noPassword": "None", + "numbers": "Dial-in Numbers", + "password": "Password:", + "title": "Call info", "tooltip": "Get access info about the meeting" }, "profileModal": { diff --git a/package.json b/package.json index 06aa21fe6f..88fc792f38 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "string-replace-loader": "1.3.0", "style-loader": "0.19.0", "uglifyjs-webpack-plugin": "1.1.2", + "whatwg-fetch": "2.0.3", "webpack": "3.9.1", "webpack-dev-server": "2.9.5" }, diff --git a/react/features/invite/components/InfoDialog.web.js b/react/features/invite/components/InfoDialog.web.js deleted file mode 100644 index 9be57ee0ed..0000000000 --- a/react/features/invite/components/InfoDialog.web.js +++ /dev/null @@ -1,199 +0,0 @@ -/* global interfaceConfig */ - -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; - -import { getInviteURL } from '../../base/connection'; -import { openDialog } from '../../base/dialog'; -import { translate } from '../../base/i18n'; - -import AddPeopleDialog from './AddPeopleDialog'; - -const logger = require('jitsi-meet-logger').getLogger(__filename); - -/** - * A React Component with the contents for a dialog that shows information about - * the current conference and provides ways to invite other participants. - * - * @extends Component - */ -class InfoDialog extends Component { - /** - * {@code InfoDialog} component's property types. - * - * @static - */ - static propTypes = { - /** - * The current url of the conference to be copied onto the clipboard. - */ - _inviteURL: PropTypes.string, - - /** - * Whether or not the link to open the {@code AddPeopleDialog} should be - * displayed. - */ - _showAddPeople: PropTypes.bool, - - /** - * Invoked to open a dialog for adding participants to the conference. - */ - dispatch: PropTypes.func, - - /** - * Callback invoked when the dialog should be closed. - */ - onClose: PropTypes.func, - - /** - * Callback invoked when a mouse-related event has been detected. - */ - onMouseOver: PropTypes.func, - - /** - * Invoked to obtain translated strings. - */ - t: PropTypes.func - }; - - /** - * Initializes new {@code InfoDialog} instance. - * - * @param {Object} props - The read-only properties with which the new - * instance is to be initialized. - */ - constructor(props) { - super(props); - - /** - * The internal reference to the DOM/HTML element backing the React - * {@code Component} input. It is necessary for the implementation - * of copying to the clipboard. - * - * @private - * @type {HTMLInputElement} - */ - this._copyElement = null; - - // Bind event handlers so they are only bound once for every instance. - this._onCopyInviteURL = this._onCopyInviteURL.bind(this); - this._onOpenInviteDialog = this._onOpenInviteDialog.bind(this); - this._setCopyElement = this._setCopyElement.bind(this); - } - - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - * @returns {ReactElement} - */ - render() { - return ( -
-
-

- -

-
-
-
- { this.props.t('info.title') } -
-
- { this.props._inviteURL } - -
-
-
- - { this.props.t('info.copy') } - -
- { this.props._showAddPeople - ?
- - { this.props.t('info.invite', { - app: interfaceConfig.ADD_PEOPLE_APP_NAME - }) } - -
- : null } -
-
-
- ); - } - - /** - * Callback invoked to copy the contents of {@code this._copyElement} to the - * clipboard. - * - * @private - * @returns {void} - */ - _onCopyInviteURL() { - try { - this._copyElement.select(); - document.execCommand('copy'); - this._copyElement.blur(); - } catch (err) { - logger.error('error when copying the text', err); - } - } - - /** - * Callback invoked to open the {@code AddPeople} dialog. - * - * @private - * @returns {void} - */ - _onOpenInviteDialog() { - this.props.dispatch(openDialog(AddPeopleDialog)); - - if (this.props.onClose) { - this.props.onClose(); - } - } - - /** - * Sets the internal reference to the DOM/HTML element backing the React - * {@code Component} input. - * - * @param {HTMLInputElement} element - The DOM/HTML element for this - * {@code Component}'s input. - * @private - * @returns {void} - */ - _setCopyElement(element) { - this._copyElement = element; - } -} - -/** - * Maps (parts of) the Redux state to the associated props for the - * {@code InfoDialog} component. - * - * @param {Object} state - The Redux state. - * @private - * @returns {{ - * _inviteURL: string - * }} - */ -function _mapStateToProps(state) { - return { - _inviteURL: getInviteURL(state), - _showAddPeople: !state['features/base/jwt'].isGuest - }; -} - -export default translate(connect(_mapStateToProps)(InfoDialog)); diff --git a/react/features/invite/components/InfoDialogButton.web.js b/react/features/invite/components/InfoDialogButton.web.js index 7cf0ffa0fe..47fdce6821 100644 --- a/react/features/invite/components/InfoDialogButton.web.js +++ b/react/features/invite/components/InfoDialogButton.web.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { ToolbarButton, TOOLTIP_TO_POPUP_POSITION } from '../../toolbox'; import { setInfoDialogVisibility } from '../actions'; -import InfoDialog from './InfoDialog'; +import { InfoDialog } from './info-dialog'; const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig; diff --git a/react/features/invite/components/InfoDialog.native.js b/react/features/invite/components/dial-in-info-page/ConferenceID.native.js similarity index 100% rename from react/features/invite/components/InfoDialog.native.js rename to react/features/invite/components/dial-in-info-page/ConferenceID.native.js diff --git a/react/features/invite/components/dial-in-info-page/ConferenceID.web.js b/react/features/invite/components/dial-in-info-page/ConferenceID.web.js new file mode 100644 index 0000000000..b921701443 --- /dev/null +++ b/react/features/invite/components/dial-in-info-page/ConferenceID.web.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; + +import { translate } from '../../../base/i18n'; + +/** + * Displays a conference ID used as a pin for dialing into a conferene. + * + * @extends Component + */ +class ConferenceID extends Component { + /** + * {@code ConferenceID} component's property types. + * + * @static + */ + static propTypes = { + /** + * The conference ID for dialing in. + */ + conferenceID: PropTypes.number, + + /** + * Invoked to obtain translated strings. + */ + t: PropTypes.func + }; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { conferenceID, t } = this.props; + + return ( +
+ { t('info.dialANumber', { conferenceID }) } +
+ ); + } +} + +export default translate(ConferenceID); diff --git a/react/features/invite/components/dial-in-info-page/DialInInfoApp.native.js b/react/features/invite/components/dial-in-info-page/DialInInfoApp.native.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.js b/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.js new file mode 100644 index 0000000000..8e10ac3250 --- /dev/null +++ b/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.js @@ -0,0 +1,24 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { I18nextProvider } from 'react-i18next'; + +import parseURLParams from '../../../base/config/parseURLParams'; +import { i18next } from '../../../base/i18n'; + +import DialInInfoPage from './DialInInfoPage'; + +document.addEventListener('DOMContentLoaded', () => { + const params = parseURLParams(window.location, true, 'search'); + + ReactDOM.render( + + + , + document.getElementById('react') + ); +}); + +window.addEventListener('beforeunload', () => { + ReactDOM.unmountComponentAtNode(document.getElementById('react')); +}); diff --git a/react/features/invite/components/dial-in-info-page/DialInInfoPage.native.js b/react/features/invite/components/dial-in-info-page/DialInInfoPage.native.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/react/features/invite/components/dial-in-info-page/DialInInfoPage.web.js b/react/features/invite/components/dial-in-info-page/DialInInfoPage.web.js new file mode 100644 index 0000000000..3d847c1991 --- /dev/null +++ b/react/features/invite/components/dial-in-info-page/DialInInfoPage.web.js @@ -0,0 +1,220 @@ +/* global config */ + +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; + +import { translate } from '../../../base/i18n'; + +import ConferenceID from './ConferenceID'; +import NumbersList from './NumbersList'; + +/** + * Displays a page listing numbers for dialing into a conference and pin to + * the a specific conference. + * + * @extends Component + */ +class DialInInfoPage extends Component { + /** + * {@code DialInInfoPage} component's property types. + * + * @static + */ + static propTypes = { + /** + * The name of the conference to show a conferenceID for. + */ + room: PropTypes.string, + + /** + * Invoked to obtain translated strings. + */ + t: PropTypes.func + }; + + /** + * {@code DialInInfoPage} component's local state. + * + * @type {Object} + * @property {number} conferenceID - The numeric ID of the conference, used + * as a pin when dialing in. + * @property {string} error - An error message to display. + * @property {boolean} loading - Whether or not the app is fetching data. + * @property {Array|Object} numbers - The dial-in numbers. + * entered by the local participant. + * @property {boolean} numbersEnabled - Whether or not dial-in is allowed. + */ + state = { + conferenceID: null, + error: '', + loading: true, + numbers: null, + numbersEnabled: null + }; + + /** + * Initializes a new {@code DialInInfoPage} instance. + * + * @param {Object} 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. + this._onGetNumbersSuccess = this._onGetNumbersSuccess.bind(this); + this._onGetConferenceIDSuccess + = this._onGetConferenceIDSuccess.bind(this); + this._setErrorMessage = this._setErrorMessage.bind(this); + } + + /** + * Implements {@link Component#componentDidMount()}. Invoked immediately + * after this component is mounted. + * + * @inheritdoc + * @returns {void} + */ + componentDidMount() { + const getNumbers = this._getNumbers() + .then(this._onGetNumbersSuccess) + .catch(this._setErrorMessage); + + const getID = this._getConferenceID() + .then(this._onGetConferenceIDSuccess) + .catch(this._setErrorMessage); + + Promise.all([ getNumbers, getID ]) + .then(() => { + this.setState({ loading: false }); + }); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + let contents; + + const { conferenceID, error, loading, numbersEnabled } = this.state; + + if (loading) { + contents = ''; + } else if (numbersEnabled === false) { + contents = this.props.t('invite.disabled'); + } else if (error) { + contents = error; + } else { + contents = [ + conferenceID + ? + : null, + + ]; + } + + return ( +
+ { contents } +
+ ); + } + + /** + * Creates an AJAX request for the conference ID. + * + * @private + * @returns {Promise} + */ + _getConferenceID() { + const { room } = this.props; + const { dialInConfCodeUrl, hosts } = config; + const mucURL = hosts && hosts.muc; + + if (!dialInConfCodeUrl || !mucURL || !room) { + return Promise.resolve(); + } + + const conferenceIDURL + = `${dialInConfCodeUrl}?conference=${room}@${mucURL}`; + + return fetch(conferenceIDURL) + .then(response => response.json()) + .catch(() => Promise.reject(this.props.t('info.genericError'))); + } + + /** + * Creates an AJAX request for dial-in numbers. + * + * @private + * @returns {Promise} + */ + _getNumbers() { + const { dialInNumbersUrl } = config; + + if (!dialInNumbersUrl) { + return Promise.reject(this.props.t('info.dialInNotSupported')); + } + + return fetch(dialInNumbersUrl) + .then(response => response.json()) + .catch(() => Promise.reject(this.props.t('info.genericError'))); + } + + /** + * Callback invoked when fetching the conference ID succeeds. + * + * @param {Object} response - The response from fetching the conference ID. + * @private + * @returns {void} + */ + _onGetConferenceIDSuccess(response = {}) { + const { conference, id } = response; + + if (!conference || !id) { + return; + } + + this.setState({ conferenceID: id }); + } + + /** + * Callback invoked when fetching dial-in numbers succeeds. Sets the + * internal to show the numbers. + * + * @param {Object} response - The response from fetching dial-in numbers. + * @param {Array|Object} response.numbers - The dial-in numbers. + * @param {boolean} reponse.numbersEnabled - Whether or not dial-in is + * enabled. + * @private + * @returns {void} + */ + _onGetNumbersSuccess({ numbers, numbersEnabled }) { + this.setState({ + numbersEnabled, + numbers + }); + } + + /** + * Sets an error message to display on the page instead of content. + * + * @param {string} error - The error message to display. + * @private + * @returns {void} + */ + _setErrorMessage(error) { + this.setState({ + error + }); + } +} + +export default translate(DialInInfoPage); diff --git a/react/features/invite/components/dial-in-info-page/NumbersList.native.js b/react/features/invite/components/dial-in-info-page/NumbersList.native.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/react/features/invite/components/dial-in-info-page/NumbersList.web.js b/react/features/invite/components/dial-in-info-page/NumbersList.web.js new file mode 100644 index 0000000000..554dd6c463 --- /dev/null +++ b/react/features/invite/components/dial-in-info-page/NumbersList.web.js @@ -0,0 +1,120 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; + +import { translate } from '../../../base/i18n'; + +/** + * Displays a table with phone numbers to dial in to a conference. + * + * @extends Component + */ +class NumbersList extends Component { + /** + * {@code NumbersList} component's property types. + * + * @static + */ + static propTypes = { + /** + * The phone numbers to display. Can be an array of numbers + * or an object with countries as keys and an array of numbers + * as values. + */ + numbers: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]), + + /** + * Invoked to obtain translated strings. + */ + t: PropTypes.func + }; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { numbers, t } = this.props; + const showWithoutCountries = Array.isArray(numbers); + + return ( + + + + { showWithoutCountries + ? null + : } + + + + + { showWithoutCountries + ? numbers.map(this._renderNumberRow) + : this._renderWithCountries() } + +
{ t('info.country') }{ t('info.numbers') }
); + } + + /** + * Renders rows of countries and associated phone numbers. + * + * @private + * @returns {ReactElement[]} + */ + _renderWithCountries() { + const rows = []; + + for (const [ country, numbers ] of Object.entries(this.props.numbers)) { + const formattedNumbers = numbers.map(this._renderNumberDiv); + + rows.push( + + { country } + { formattedNumbers } + + ); + } + + return rows; + } + + /** + * Renders a table row for a phone number. + * + * @param {string} number - The phone number to display. + * @private + * @returns {ReactElement[]} + */ + _renderNumberRow(number) { + return ( + + + { number } + + + ); + } + + /** + * Renders a div container for a phone number. + * + * @param {string} number - The phone number to display. + * @private + * @returns {ReactElement[]} + */ + _renderNumberDiv(number) { + return ( +
+ { number } +
+ ); + } +} + +export default translate(NumbersList); diff --git a/react/features/invite/components/dial-in-info-page/index.js b/react/features/invite/components/dial-in-info-page/index.js new file mode 100644 index 0000000000..dff1aba32f --- /dev/null +++ b/react/features/invite/components/dial-in-info-page/index.js @@ -0,0 +1 @@ +export { default as DialInInfoApp } from './DialInInfoApp'; diff --git a/react/features/invite/components/info-dialog/DialInNumber.native.js b/react/features/invite/components/info-dialog/DialInNumber.native.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/react/features/invite/components/info-dialog/DialInNumber.web.js b/react/features/invite/components/info-dialog/DialInNumber.web.js new file mode 100644 index 0000000000..38e8af28bf --- /dev/null +++ b/react/features/invite/components/info-dialog/DialInNumber.web.js @@ -0,0 +1,60 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { translate } from '../../../base/i18n'; + +/** + * React {@code Component} responsible for displaying a telephone number and + * conference ID for dialing into a conference. + * + * @extends Component + */ +class DialInNumber extends Component { + /** + * {@code DialInNumber} component's property types. + * + * @static + */ + static propTypes = { + /** + * The numberic identifier for the current conference, used after + * dialing a the number to join the conference. + */ + conferenceID: PropTypes.number, + + /** + * The phone number to dial to begin the process of dialing into a + * conference. + */ + phoneNumber: PropTypes.string, + + /** + * Invoked to obtain translated strings. + */ + t: PropTypes.func + }; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { conferenceID, phoneNumber } = this.props; + + return ( +
+ + { this.props.t('info.dialInNumber', { phoneNumber }) } + + + { this.props.t( + 'info.dialInConferenceID', { conferenceID }) } + +
+ ); + } +} + +export default translate(DialInNumber); diff --git a/react/features/invite/components/info-dialog/InfoDialog.native.js b/react/features/invite/components/info-dialog/InfoDialog.native.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/react/features/invite/components/info-dialog/InfoDialog.web.js b/react/features/invite/components/info-dialog/InfoDialog.web.js new file mode 100644 index 0000000000..b7dfbcb769 --- /dev/null +++ b/react/features/invite/components/info-dialog/InfoDialog.web.js @@ -0,0 +1,503 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +import { setPassword } from '../../../base/conference'; +import { getInviteURL } from '../../../base/connection'; +import { translate } from '../../../base/i18n'; +import { + PARTICIPANT_ROLE, + getLocalParticipant +} from '../../../base/participants'; + +import { updateDialInNumbers } from '../../actions'; + +import DialInNumber from './DialInNumber'; +import PasswordForm from './PasswordForm'; + +const logger = require('jitsi-meet-logger').getLogger(__filename); + +/** + * A React Component with the contents for a dialog that shows information about + * the current conference. + * + * @extends Component + */ +class InfoDialog extends Component { + /** + * {@code InfoDialog} component's property types. + * + * @static + */ + static propTypes = { + /** + * Whether or not the current user can modify the current password. + */ + _canEditPassword: PropTypes.bool, + + /** + * The JitsiConference for which to display a lock state and change the + * password. + * + * @type {JitsiConference} + */ + _conference: PropTypes.object, + + /** + * The name of the current conference. Used as part of inviting users. + */ + _conferenceName: PropTypes.string, + + /** + * The redux state representing the dial-in numbers feature. + */ + _dialIn: PropTypes.object, + + /** + * The current url of the conference to be copied onto the clipboard. + */ + _inviteURL: PropTypes.string, + + /** + * The value for how the conference is locked (or undefined if not + * locked) as defined by room-lock constants. + */ + _locked: PropTypes.string, + + /** + * The current known password for the JitsiConference. + */ + _password: PropTypes.string, + + /** + * Invoked to open a dialog for adding participants to the conference. + */ + dispatch: PropTypes.func, + + /** + * Callback invoked when the dialog should be closed. + */ + onClose: PropTypes.func, + + /** + * Callback invoked when a mouse-related event has been detected. + */ + onMouseOver: PropTypes.func, + + /** + * Invoked to obtain translated strings. + */ + t: PropTypes.func + }; + + /** + * {@code InfoDialog} component's local state. + * + * @type {Object} + * @property {boolean} passwordEditEnabled - Whether or not to show the + * {@code PasswordForm} in its editing state. + * @property {string} phoneNumber - The number to display for dialing into + * the conference. + */ + state = { + passwordEditEnabled: false, + phoneNumber: '' + }; + + /** + * Initializes new {@code InfoDialog} instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + const { defaultCountry, numbers } = props._dialIn; + + if (numbers) { + this.state.phoneNumber + = this._getDefaultPhoneNumber(numbers, defaultCountry); + } + + /** + * The internal reference to the DOM/HTML element backing the React + * {@code Component} text area. It is necessary for the implementation + * of copying to the clipboard. + * + * @private + * @type {HTMLTextAreaElement} + */ + this._copyElement = null; + + // Bind event handlers so they are only bound once for every instance. + this._onCopyInviteURL = this._onCopyInviteURL.bind(this); + this._onPasswordRemove = this._onPasswordRemove.bind(this); + this._onPasswordSubmit = this._onPasswordSubmit.bind(this); + this._onTogglePasswordEditState + = this._onTogglePasswordEditState.bind(this); + this._setCopyElement = this._setCopyElement.bind(this); + } + + /** + * Implements {@link Component#componentDidMount()}. Invoked immediately + * after this component is mounted. Requests dial-in numbers if not + * already known. + * + * @inheritdoc + * @returns {void} + */ + componentDidMount() { + if (!this.state.phoneNumber) { + this.props.dispatch(updateDialInNumbers()); + } + } + + /** + * Implements React's {@link Component#componentWillReceiveProps()}. Invoked + * before this mounted component receives new props. + * + * @inheritdoc + * @param {Props} nextProps - New props component will receive. + */ + componentWillReceiveProps(nextProps) { + if (!this.props._password && nextProps._password) { + this.setState({ passwordEditEnabled: false }); + } + + if (!this.state.phoneNumber && nextProps._dialIn.numbers) { + const { defaultCountry, numbers } = nextProps._dialIn; + + this.setState({ + phoneNumber: + this._getDefaultPhoneNumber(numbers, defaultCountry) + }); + } + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { onMouseOver, t } = this.props; + + return ( +
+
+

+ +

+
+
+
+ { t('info.title') } +
+
+ { t('info.conferenceURL', + { url: this._getURLToDisplay() }) } +