diff --git a/conference.js b/conference.js index a1404ce77..7edfc93c1 100644 --- a/conference.js +++ b/conference.js @@ -385,10 +385,6 @@ class ConferenceConnector { logger.error('CONFERENCE FAILED:', err, ...params); APP.UI.hideRingOverLay(); switch (err) { - // room is locked by the password - case ConferenceErrors.PASSWORD_REQUIRED: - APP.UI.emitEvent(UIEvents.PASSWORD_REQUIRED); - break; case ConferenceErrors.CONNECTION_ERROR: { diff --git a/modules/UI/invite/Invite.js b/modules/UI/invite/Invite.js index baa401581..b5127d7b6 100644 --- a/modules/UI/invite/Invite.js +++ b/modules/UI/invite/Invite.js @@ -47,31 +47,8 @@ class Invite { } }); - this.conference.on(ConferenceEvents.CONFERENCE_JOINED, () => { - let roomLocker = this.getRoomLocker(); - roomLocker.hideRequirePasswordDialog(); - }); - APP.UI.addListener( UIEvents.INVITE_CLICKED, () => { this.openLinkDialog(); }); - - APP.UI.addListener( UIEvents.PASSWORD_REQUIRED, - () => { - let roomLocker = this.getRoomLocker(); - this.setLockedFromElsewhere(true); - roomLocker.requirePassword().then(() => { - let pass = roomLocker.password; - // we received that password is required, but user is trying - // anyway to login without a password, mark room as not - // locked in case he succeeds (maybe someone removed the - // password meanwhile), if it is still locked another - // password required will be received and the room again - // will be marked as locked. - if (!pass) - this.setLockedFromElsewhere(false); - this.conference.join(pass); - }); - }); } /** diff --git a/modules/UI/invite/RequirePasswordDialog.js b/modules/UI/invite/RequirePasswordDialog.js deleted file mode 100644 index 5fe12eb88..000000000 --- a/modules/UI/invite/RequirePasswordDialog.js +++ /dev/null @@ -1,162 +0,0 @@ -/* global APP */ - -import UIUtil from '../util/UIUtil'; - -/** - * Show dialog which asks for required conference password. - * @returns {Promise} password or nothing if user canceled - */ -export default class RequirePasswordDialog { - constructor() { - this.titleKey = 'dialog.passwordRequired'; - this.labelKey = 'dialog.passwordLabel'; - this.errorKey = 'dialog.incorrectPassword'; - this.errorId = 'passwordRequiredError'; - this.inputId = 'passwordRequiredInput'; - this.inputErrorClass = 'error'; - this.isOpened = false; - } - - /** - * Registering dialog listeners - * @private - */ - _registerListeners() { - let el = document.getElementById(this.inputId); - el.addEventListener('keypress', this._hideError.bind(this)); - } - - /** - * Helper method returning dialog body - * @returns {string} - * @private - */ - _getBodyMessage() { - return ( - `
- - -

-
` - ); - } - - /** - * Asking for a password - * @returns {Promise} - */ - askForPassword() { - if (!this.isOpened) { - return this.open(); - } - - return new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - this._showError(); - }); - } - - /** - * Opens the dialog - * @returns {Promise} - */ - open() { - let { titleKey } = this; - let msgString = this._getBodyMessage(); - - return new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - let submitFunction = this._submitFunction.bind(this); - let closeFunction = this._closeFunction.bind(this); - - this._dialog = APP.UI.messageHandler.openTwoButtonDialog({ - titleKey, - msgString, - leftButtonKey: "dialog.Ok", - submitFunction, - closeFunction, - focus: ':input:first' - }); - - this._registerListeners(); - this.isOpened = true; - }); - } - - /** - * Submit dialog callback - * @param e - event - * @param v - value - * @param m - message - * @param f - form - * @private - */ - _submitFunction(e, v, m, f) { - e.preventDefault(); - this._processInput(v, f); - } - - /** - * Processing input in dialog - * @param v - value - * @param f - form - * @private - */ - _processInput(v, f) { - if (v && f.lockKey) { - this.resolve(UIUtil.escapeHtml(f.lockKey)); - } else { - this.reject(APP.UI.messageHandler.CANCEL); - } - } - - /** - * Close dialog callback - * @private - */ - _closeFunction(e, v, m, f) { - this._processInput(v, f); - this._hideError(); - this.close(); - } - - /** - * Method showing error hint - * @private - */ - _showError() { - let className = this.inputErrorClass; - let input = document.getElementById(this.inputId); - document.getElementById(this.errorId).classList.remove('hide'); - input.classList.add(className); - input.select(); - } - - /** - * Method hiding error hint - * @private - */ - _hideError() { - let className = this.inputErrorClass; - document.getElementById(this.errorId).classList.add('hide'); - document.getElementById(this.inputId).classList.remove(className); - } - - /** - * Close the dialog - */ - close() { - if (this._dialog) { - this._dialog.close(); - } - this.isOpened = false; - } -} \ No newline at end of file diff --git a/modules/UI/invite/RoomLocker.js b/modules/UI/invite/RoomLocker.js index da5c0e235..59f43e331 100644 --- a/modules/UI/invite/RoomLocker.js +++ b/modules/UI/invite/RoomLocker.js @@ -1,8 +1,6 @@ /* global APP, JitsiMeetJS */ const logger = require("jitsi-meet-logger").getLogger(__filename); -import RequirePasswordDialog from './RequirePasswordDialog'; - /** * Show notification that user cannot set password for the conference * because server doesn't support that. @@ -33,7 +31,6 @@ const ConferenceErrors = JitsiMeetJS.errors.conference; */ export default function createRoomLocker (room) { let password; - let requirePasswordDialog = new RequirePasswordDialog(); /** * If the room was locked from someone other than us, we indicate it with * this property in order to have correct roomLocker state of isLocked. @@ -102,31 +99,5 @@ export default function createRoomLocker (room) { password = null; }, - /** - * Asks user for required conference password. - */ - requirePassword () { - return requirePasswordDialog.askForPassword().then( - newPass => { password = newPass; } - ).catch( - reason => { - // user canceled, no pass was entered. - // clear, as if we use the same instance several times - // pass stays between attempts - password = null; - if (reason !== APP.UI.messageHandler.CANCEL) - logger.error(reason); - } - ); - }, - - /** - * Hides require password dialog - */ - hideRequirePasswordDialog() { - if (requirePasswordDialog.isOpened) { - requirePasswordDialog.close(); - } - } }; } diff --git a/package.json b/package.json index c233e84cc..d7c2b76cb 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@atlassian/aui": "6.0.6", "@atlaskit/button": "1.0.3", "@atlaskit/button-group": "1.0.0", + "@atlaskit/field-text": "2.0.3", "@atlaskit/modal-dialog": "1.2.4", "@atlaskit/tabs": "1.2.5", "async": "0.9.0", diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index c2dc8d129..574e8156e 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,6 +1,8 @@ import { appInit } from '../actions'; import { AbstractApp } from './AbstractApp'; +import '../../room-lock'; + /** * Root application component. * diff --git a/react/features/conference/components/Conference.native.js b/react/features/conference/components/Conference.native.js index 60997f1cb..b473eda49 100644 --- a/react/features/conference/components/Conference.native.js +++ b/react/features/conference/components/Conference.native.js @@ -6,10 +6,8 @@ import { DialogContainer } from '../../base/dialog'; import { Container } from '../../base/react'; import { FilmStrip } from '../../film-strip'; import { LargeVideo } from '../../large-video'; -import { RoomLockPrompt } from '../../room-lock'; import { Toolbar } from '../../toolbar'; -import PasswordRequiredPrompt from './PasswordRequiredPrompt'; import { styles } from './styles'; /** @@ -30,23 +28,6 @@ class Conference extends Component { * @static */ static propTypes = { - /** - * The indicator which determines whether a password is required to join - * the conference and has not been provided yet. - * - * @private - * @type {JitsiConference} - */ - _passwordRequired: React.PropTypes.object, - - /** - * The indicator which determines whether the user has requested to lock - * the conference/room. - * - * @private - * @type {JitsiConference} - */ - _roomLockRequested: React.PropTypes.object, dispatch: React.PropTypes.func } @@ -128,9 +109,6 @@ class Conference extends Component { - { - this._renderPrompt() - } ); } @@ -164,56 +142,6 @@ class Conference extends Component { this._setToolbarTimeout(toolbarVisible); } - /** - * Renders a prompt if a password is required to join the conference. - * - * @private - * @returns {ReactElement} - */ - _renderPasswordRequiredPrompt() { - const required = this.props._passwordRequired; - - if (required) { - return ( - - ); - } - - return null; - } - - /** - * Renders a prompt if necessary such as when a password is required to join - * the conference or the user has requested to lock the conference/room. - * - * @private - * @returns {ReactElement} - */ - _renderPrompt() { - return ( - this._renderPasswordRequiredPrompt() - || this._renderRoomLockPrompt() - ); - } - - /** - * Renders a prompt if the user has requested to lock the conference/room. - * - * @private - * @returns {ReactElement} - */ - _renderRoomLockPrompt() { - const requested = this.props._roomLockRequested; - - if (requested) { - return ( - - ); - } - - return null; - } - /** * Triggers the default toolbar timeout. * @@ -231,35 +159,4 @@ class Conference extends Component { } } -/** - * Maps (parts of) the Redux state to the associated Conference's props. - * - * @param {Object} state - The Redux state. - * @private - * @returns {{ - * _passwordRequired: boolean - * }} - */ -function _mapStateToProps(state) { - return { - /** - * The indicator which determines whether a password is required to join - * the conference and has not been provided yet. - * - * @private - * @type {JitsiConference} - */ - _passwordRequired: state['features/base/conference'].passwordRequired, - - /** - * The indicator which determines whether the user has requested to lock - * the conference/room. - * - * @private - * @type {JitsiConference} - */ - _roomLockRequested: state['features/room-lock'].requested - }; -} - -export default reactReduxConnect(_mapStateToProps)(Conference); +export default reactReduxConnect()(Conference); diff --git a/react/features/room-lock/actionTypes.js b/react/features/room-lock/actionTypes.js deleted file mode 100644 index cecc53f51..000000000 --- a/react/features/room-lock/actionTypes.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Symbol } from '../base/react'; - -/** - * The type of Redux action which begins a (user) request to lock a specific - * JitsiConference. - * - * { - * type: BEGIN_ROOM_LOCK_REQUEST, - * conference: JitsiConference - * } - */ -export const BEGIN_ROOM_LOCK_REQUEST = Symbol('BEGIN_ROOM_LOCK_REQUEST'); - -/** - * The type of Redux action which end a (user) request to lock a specific - * JitsiConference. - * - * { - * type: END_ROOM_LOCK_REQUEST, - * conference: JitsiConference, - * password: string - * } - */ -export const END_ROOM_LOCK_REQUEST = Symbol('END_ROOM_LOCK_REQUEST'); diff --git a/react/features/room-lock/actions.js b/react/features/room-lock/actions.js index 7d6c7bba1..cd8993f8d 100644 --- a/react/features/room-lock/actions.js +++ b/react/features/room-lock/actions.js @@ -1,6 +1,6 @@ import { setPassword } from '../base/conference'; - -import { BEGIN_ROOM_LOCK_REQUEST, END_ROOM_LOCK_REQUEST } from './actionTypes'; +import { hideDialog, openDialog } from '../base/dialog'; +import { PasswordRequiredPrompt, RoomLockPrompt } from './components'; /** * Begins a (user) request to lock a specific conference/room. @@ -19,10 +19,7 @@ export function beginRoomLockRequest(conference) { } if (conference) { - dispatch({ - type: BEGIN_ROOM_LOCK_REQUEST, - conference - }); + dispatch(openDialog(RoomLockPrompt, { conference })); } }; } @@ -43,13 +40,25 @@ export function endRoomLockRequest(conference, password) { ? dispatch(setPassword(conference, conference.lock, password)) : Promise.resolve(); const endRoomLockRequest_ = () => { - dispatch({ - type: END_ROOM_LOCK_REQUEST, - conference, - password - }); + dispatch(hideDialog()); }; setPassword_.then(endRoomLockRequest_, endRoomLockRequest_); }; } + +/** + * Begins a request to enter password for a specific conference/room. + * + * @param {JitsiConference} conference - The JitsiConference + * requesting password. + * @protected + * @returns {{ + * type: OPEN_DIALOG, + * component: Component, + * props: React.PropTypes + * }} + */ +export function _showPasswordDialog(conference) { + return openDialog(PasswordRequiredPrompt, { conference }); +} diff --git a/react/features/conference/components/PasswordRequiredPrompt.native.js b/react/features/room-lock/components/PasswordRequiredPrompt.native.js similarity index 75% rename from react/features/conference/components/PasswordRequiredPrompt.native.js rename to react/features/room-lock/components/PasswordRequiredPrompt.native.js index e92498877..f5efce88f 100644 --- a/react/features/conference/components/PasswordRequiredPrompt.native.js +++ b/react/features/room-lock/components/PasswordRequiredPrompt.native.js @@ -1,9 +1,8 @@ import React, { Component } from 'react'; -import Prompt from 'react-native-prompt'; import { connect } from 'react-redux'; +import { Dialog } from '../../base/dialog'; import { setPassword } from '../../base/conference'; -import { translate } from '../../base/i18n'; /** * Implements a React Component which prompts the user when a password is @@ -22,15 +21,7 @@ class PasswordRequiredPrompt extends Component { * @type {JitsiConference} */ conference: React.PropTypes.object, - dispatch: React.PropTypes.func, - - /** - * The function to translate human-readable text. - * - * @public - * @type {Function} - */ - t: React.PropTypes.func + dispatch: React.PropTypes.func } /** @@ -54,15 +45,13 @@ class PasswordRequiredPrompt extends Component { * @returns {ReactElement} */ render() { - const { t } = this.props; return ( - + titleKey = 'dialog.passwordRequired' /> ); } @@ -70,14 +59,14 @@ class PasswordRequiredPrompt extends Component { * Notifies this prompt that it has been dismissed by cancel. * * @private - * @returns {void} + * @returns {boolean} whether to hide dialog. */ _onCancel() { // XXX The user has canceled this prompt for a password so we are to // attempt joining the conference without a password. If the conference // still requires a password to join, the user will be prompted again // later. - this._onSubmit(undefined); + return this._onSubmit(undefined); } /** @@ -86,13 +75,15 @@ class PasswordRequiredPrompt extends Component { * * @param {string} value - The submitted value. * @private - * @returns {void} + * @returns {boolean} whether to hide dialog. */ _onSubmit(value) { const conference = this.props.conference; this.props.dispatch(setPassword(conference, conference.join, value)); + + return true; } } -export default translate(connect()(PasswordRequiredPrompt)); +export default connect()(PasswordRequiredPrompt); diff --git a/react/features/room-lock/components/PasswordRequiredPrompt.web.js b/react/features/room-lock/components/PasswordRequiredPrompt.web.js new file mode 100644 index 000000000..0d42014f9 --- /dev/null +++ b/react/features/room-lock/components/PasswordRequiredPrompt.web.js @@ -0,0 +1,127 @@ +/* global APP */ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import AKFieldText from '@atlaskit/field-text'; + +import { setPassword } from '../../base/conference'; +import { Dialog } from '../../base/dialog'; +import { translate } from '../../base/i18n'; + +/** + * Implements a React Component which prompts the user when a password is + * required to join a conference. + */ +class PasswordRequiredPrompt extends Component { + /** + * PasswordRequiredPrompt component's property types. + * + * @static + */ + static propTypes = { + /** + * The JitsiConference which requires a password. + * + * @type {JitsiConference} + */ + conference: React.PropTypes.object, + dispatch: React.PropTypes.func, + t: React.PropTypes.func + } + + /** + * Initializes a new PasswordRequiredPrompt instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + this.state = { password: '' }; + + this._onPasswordChanged = this._onPasswordChanged.bind(this); + this._onSubmit = this._onSubmit.bind(this); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + + { this._renderBody() } + ); + } + + /** + * Display component in dialog body. + * + * @returns {ReactElement} + * @protected + */ + _renderBody() { + const { t } = this.props; + + return ( +
+ +
); + } + + /** + * Notifies this dialog that password has changed. + * + * @param {Object} event - The details of the notification/event. + * @private + * @returns {void} + */ + _onPasswordChanged(event) { + this.setState({ password: event.target.value }); + } + + /** + * Dispatches action to submit value from thus dialog. + * + * @private + * @returns {void} + */ + _onSubmit() { + const conference = this.props.conference; + + // we received that password is required, but user is trying + // anyway to login without a password, mark room as not + // locked in case he succeeds (maybe someone removed the + // password meanwhile), if it is still locked another + // password required will be received and the room again + // will be marked as locked. + if (!this.state.password || this.state.password === '') { + // XXX temporary solution till we move the whole invite logic + // in react + APP.conference.invite.setLockedFromElsewhere(false); + } + + this.props.dispatch(setPassword( + conference, conference.join, this.state.password)); + + // we have used the password lets clean it + this.setState({ password: undefined }); + + return true; + } +} + +export default translate(connect()(PasswordRequiredPrompt)); diff --git a/react/features/room-lock/components/RoomLockPrompt.native.js b/react/features/room-lock/components/RoomLockPrompt.native.js index 6590e3efd..38ef771df 100644 --- a/react/features/room-lock/components/RoomLockPrompt.native.js +++ b/react/features/room-lock/components/RoomLockPrompt.native.js @@ -1,8 +1,6 @@ import React, { Component } from 'react'; -import Prompt from 'react-native-prompt'; import { connect } from 'react-redux'; - -import { translate } from '../../base/i18n'; +import { Dialog } from '../../base/dialog'; import { endRoomLockRequest } from '../actions'; @@ -23,15 +21,7 @@ class RoomLockPrompt extends Component { * @type {JitsiConference} */ conference: React.PropTypes.object, - dispatch: React.PropTypes.func, - - /** - * The function to translate human-readable text. - * - * @public - * @type {Function} - */ - t: React.PropTypes.func + dispatch: React.PropTypes.func } /** @@ -55,15 +45,13 @@ class RoomLockPrompt extends Component { * @returns {ReactElement} */ render() { - const { t } = this.props; return ( - + titleKey = 'toolbar.lock' /> ); } @@ -71,12 +59,12 @@ class RoomLockPrompt extends Component { * Notifies this prompt that it has been dismissed by cancel. * * @private - * @returns {void} + * @returns {boolean} whether to hide the dialog */ _onCancel() { // An undefined password is understood to cancel the request to lock the // conference/room. - this._onSubmit(undefined); + return this._onSubmit(undefined); } /** @@ -85,11 +73,16 @@ class RoomLockPrompt extends Component { * * @param {string} value - The submitted value. * @private - * @returns {void} + * @returns {boolean} returns false, we do not want to hide dialog as this + * will be handled inside endRoomLockRequest after setting password is + * resolved. */ _onSubmit(value) { this.props.dispatch(endRoomLockRequest(this.props.conference, value)); + + // do not hide + return false; } } -export default translate(connect()(RoomLockPrompt)); +export default connect()(RoomLockPrompt); diff --git a/react/features/room-lock/components/RoomLockPrompt.web.js b/react/features/room-lock/components/RoomLockPrompt.web.js new file mode 100644 index 000000000..e69de29bb diff --git a/react/features/room-lock/components/index.js b/react/features/room-lock/components/index.js index a67f1c4db..0119e8682 100644 --- a/react/features/room-lock/components/index.js +++ b/react/features/room-lock/components/index.js @@ -1 +1,2 @@ export { default as RoomLockPrompt } from './RoomLockPrompt'; +export { default as PasswordRequiredPrompt } from './PasswordRequiredPrompt'; diff --git a/react/features/room-lock/index.js b/react/features/room-lock/index.js index 582e1f9dd..a640fd3bf 100644 --- a/react/features/room-lock/index.js +++ b/react/features/room-lock/index.js @@ -1,4 +1,4 @@ export * from './actions'; export * from './components'; -import './reducer'; +import './middleware'; diff --git a/react/features/room-lock/middleware.js b/react/features/room-lock/middleware.js new file mode 100644 index 000000000..10a8fb6e5 --- /dev/null +++ b/react/features/room-lock/middleware.js @@ -0,0 +1,36 @@ +/* global APP */ +import JitsiMeetJS from '../base/lib-jitsi-meet'; + +import { CONFERENCE_FAILED } from '../base/conference'; +import { MiddlewareRegistry } from '../base/redux'; +import { _showPasswordDialog } from './actions'; + +/** + * Middleware that captures conference failed and checks for password required + * error and requests a dialog for user to enter password. + * + * @param {Store} store - Redux store. + * @returns {Function} + */ +MiddlewareRegistry.register(store => next => action => { + + switch (action.type) { + case CONFERENCE_FAILED: { + const JitsiConferenceErrors = JitsiMeetJS.errors.conference; + + if (action.conference + && JitsiConferenceErrors.PASSWORD_REQUIRED === action.error) { + // XXX temporary solution till we move the whole invite + // logic in react + if (typeof APP !== 'undefined') { + APP.conference.invite.setLockedFromElsewhere(true); + } + + store.dispatch(_showPasswordDialog(action.conference)); + } + break; + } + } + + return next(action); +}); diff --git a/react/features/room-lock/reducer.js b/react/features/room-lock/reducer.js deleted file mode 100644 index 80025f788..000000000 --- a/react/features/room-lock/reducer.js +++ /dev/null @@ -1,33 +0,0 @@ -import { - CONFERENCE_FAILED, - CONFERENCE_JOINED, - CONFERENCE_LEFT -} from '../base/conference'; -import { ReducerRegistry, setStateProperty } from '../base/redux'; - -import { BEGIN_ROOM_LOCK_REQUEST, END_ROOM_LOCK_REQUEST } from './actionTypes'; - -ReducerRegistry.register('features/room-lock', (state = {}, action) => { - switch (action.type) { - case BEGIN_ROOM_LOCK_REQUEST: - return setStateProperty(state, 'requested', action.conference); - - case CONFERENCE_FAILED: - case CONFERENCE_LEFT: - case END_ROOM_LOCK_REQUEST: { - if (state.requested === action.conference) { - return setStateProperty(state, 'requested', undefined); - } - break; - } - - case CONFERENCE_JOINED: { - if (state.requested !== action.conference) { - return setStateProperty(state, 'requested', undefined); - } - break; - } - } - - return state; -}); diff --git a/service/UI/UIEvents.js b/service/UI/UIEvents.js index 899436374..a17f11ed0 100644 --- a/service/UI/UIEvents.js +++ b/service/UI/UIEvents.js @@ -150,11 +150,6 @@ export default { */ DISPLAY_NAME_CHANGED: "UI.display_name_changed", - /** - * Indicates that a password is required for the call. - */ - PASSWORD_REQUIRED: "UI.password_required", - /** * Show custom popup/tooltip for a specified button. */