Moves web password required dialog to react.

This commit is contained in:
damencho 2017-03-06 21:43:41 -06:00
parent 51f0c8a388
commit 61470c0d24
13 changed files with 186 additions and 223 deletions

View File

@ -387,10 +387,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:
{

View File

@ -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);
});
});
}
/**

View File

@ -1,162 +0,0 @@
/* global APP */
import UIUtil from '../util/UIUtil';
/**
* Show dialog which asks for required conference password.
* @returns {Promise<string>} 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 (
`<div class="form-control">
<label class="input-control__label"
data-i18n="${this.labelKey}"></label>
<input class="input-control__input input-control"
name="lockKey" type="text"
data-i18n="[placeholder]dialog.password"
autofocus id="${this.inputId}">
<p class="form-control__hint form-control__hint_error hide"
id="${this.errorId}"
data-i18n="${this.errorKey}"></p>
</div>`
);
}
/**
* 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;
}
}

View File

@ -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();
}
}
};
}

View File

@ -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",
"async": "0.9.0",
"autosize": "1.18.13",

View File

@ -1,6 +1,8 @@
import { appInit } from '../actions';
import { AbstractApp } from './AbstractApp';
import '../../room-lock';
/**
* Root application component.
*

View File

@ -1,4 +1,6 @@
import { setPassword } from '../base/conference';
import { openDialog } from '../base/dialog';
import { PasswordRequiredPrompt } from './components';
import { BEGIN_ROOM_LOCK_REQUEST, END_ROOM_LOCK_REQUEST } from './actionTypes';
@ -53,3 +55,19 @@ export function endRoomLockRequest(conference, password) {
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: BEGIN_DIALOG_REQUEST,
* component: Component,
* props: React.PropTypes
* }}
*/
export function _showPasswordDialog(conference) {
return openDialog(PasswordRequiredPrompt, { conference });
}

View File

@ -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 (
<Dialog
isModal = { true }
onSubmit = { this._onSubmit }
titleKey = 'dialog.passwordRequired'
width = 'small'>
{ this._renderBody() }
</Dialog>);
}
/**
* Display component in dialog body.
*
* @returns {ReactElement}
* @protected
*/
_renderBody() {
const { t } = this.props;
return (
<div>
<AKFieldText
compact = { true }
label = { t('dialog.passwordLabel') }
name = 'lockKey'
onChange = { this._onPasswordChanged }
shouldFitContainer = { true }
type = 'text'
value = { this.state.password } />
</div>);
}
/**
* 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));

View File

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

View File

@ -1,4 +1,5 @@
export * from './actions';
export * from './components';
import './middleware';
import './reducer';

View File

@ -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);
});

View File

@ -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.
*/