fix(AddPeopleDialog): Improve contact invite form

- Disables the invite buttons while invites are ongoing
- Adds a keyboard shortcut (Enter) to send out invites
- Closes AddPeopleDialog upon successful invites sent
- Fixes the SecurityDialog closing when trying to set E2EE key via Enter shortcut
- Removes superfluous separator from SecurityDialog
This commit is contained in:
Mihai Uscat 2020-06-24 15:12:23 +03:00 committed by Zoltan Bettenbuk
parent 0494200383
commit 093254d948
9 changed files with 104 additions and 21 deletions

View File

@ -208,6 +208,12 @@
padding: 8px 16px; padding: 8px 16px;
background: #0376DA; background: #0376DA;
} }
&.disabled {
& > a {
pointer-events: none;
}
}
} }
&.stream { &.stream {

View File

@ -21,15 +21,11 @@
font-size: 14px; font-size: 14px;
color: #6FB1EA; color: #6FB1EA;
} }
&>a+a {
margin-left: 24px;
}
} }
} }
} }
&> :first-child:not(:last-child) { & > :first-child:not(:last-child) {
margin-right: 24px; margin-right: 24px;
} }

View File

@ -109,6 +109,7 @@ class E2EESection extends Component<Props, State> {
disabled = { !editing } disabled = { !editing }
name = 'e2eeKey' name = 'e2eeKey'
onChange = { this._onKeyChange } onChange = { this._onKeyChange }
onKeyDown = { this._onKeyDown }
placeholder = { t('dialog.e2eeNoKey') } placeholder = { t('dialog.e2eeNoKey') }
ref = { this.fieldRef } ref = { this.fieldRef }
type = 'password' type = 'password'
@ -137,6 +138,20 @@ class E2EESection extends Component<Props, State> {
this.setState({ key: event.target.value.trim() }); this.setState({ key: event.target.value.trim() });
} }
_onKeyDown: (Object) => void;
/**
* Handler for the keydown event on the form, preventing the closing of the dialog.
*
* @param {Object} event - The DOM event triggered by keydown events.
* @returns {void}
*/
_onKeyDown(event) {
if (event.key === 'Enter') {
event.preventDefault();
}
}
_onSet: () => void; _onSet: () => void;
/** /**

View File

@ -41,6 +41,11 @@ export const REMOVE_PENDING_INVITE_REQUESTS
*/ */
export const SET_CALLEE_INFO_VISIBLE = 'SET_CALLEE_INFO_VISIBLE'; export const SET_CALLEE_INFO_VISIBLE = 'SET_CALLEE_INFO_VISIBLE';
/**
* The type of redux action to signal that the {@code AddPeopleDialog} should close.
*/
export const HIDE_ADD_PEOPLE_DIALOG = 'HIDE_ADD_PEOPLE_DIALOG';
/** /**
* The type of the action which signals an error occurred while requesting dial- * The type of the action which signals an error occurred while requesting dial-
* in numbers. * in numbers.

View File

@ -9,6 +9,7 @@ import { inviteVideoRooms } from '../videosipgw';
import { import {
ADD_PENDING_INVITE_REQUEST, ADD_PENDING_INVITE_REQUEST,
BEGIN_ADD_PEOPLE, BEGIN_ADD_PEOPLE,
HIDE_ADD_PEOPLE_DIALOG,
REMOVE_PENDING_INVITE_REQUESTS, REMOVE_PENDING_INVITE_REQUESTS,
SET_CALLEE_INFO_VISIBLE, SET_CALLEE_INFO_VISIBLE,
UPDATE_DIAL_IN_NUMBERS_FAILED, UPDATE_DIAL_IN_NUMBERS_FAILED,
@ -36,6 +37,20 @@ export function beginAddPeople() {
}; };
} }
/**
* Creates a (redux) action to signal that the {@code AddPeopleDialog}
* should close.
*
* @returns {{
* type: HIDE_ADD_PEOPLE_DIALOG
* }}
*/
export function hideAddPeopleDialog() {
return {
type: HIDE_ADD_PEOPLE_DIALOG
};
}
/** /**
* Invites (i.e. Sends invites to) an array of invitees (which may be a * Invites (i.e. Sends invites to) an array of invitees (which may be a

View File

@ -10,6 +10,7 @@ import { Icon, IconPhone } from '../../../../base/icons';
import { getLocalParticipant } from '../../../../base/participants'; import { getLocalParticipant } from '../../../../base/participants';
import { MultiSelectAutocomplete } from '../../../../base/react'; import { MultiSelectAutocomplete } from '../../../../base/react';
import { connect } from '../../../../base/redux'; import { connect } from '../../../../base/redux';
import { hideAddPeopleDialog } from '../../../actions';
import AbstractAddPeopleDialog, { import AbstractAddPeopleDialog, {
type Props as AbstractProps, type Props as AbstractProps,
type State, type State,
@ -72,6 +73,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
this._parseQueryResults = this._parseQueryResults.bind(this); this._parseQueryResults = this._parseQueryResults.bind(this);
this._setMultiSelectElement = this._setMultiSelectElement.bind(this); this._setMultiSelectElement = this._setMultiSelectElement.bind(this);
this._renderFooterText = this._renderFooterText.bind(this); this._renderFooterText = this._renderFooterText.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
this._resourceClient = { this._resourceClient = {
makeQuery: this._query, makeQuery: this._query,
@ -135,7 +137,9 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
} }
return ( return (
<div className = 'add-people-form-wrap'> <div
className = 'add-people-form-wrap'
onKeyDown = { this._onKeyDown }>
{ this._renderErrorMessage() } { this._renderErrorMessage() }
<MultiSelectAutocomplete <MultiSelectAutocomplete
footer = { footerText } footer = { footerText }
@ -217,11 +221,30 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
this._multiselect.setSelectedItems(itemsToSelect); this._multiselect.setSelectedItems(itemsToSelect);
} }
} else { } else {
// Do nothing. this.props.dispatch(hideAddPeopleDialog());
} }
}); });
} }
_onKeyDown: (Object) => void;
/**
* Handles 'Enter' key in the form to trigger the invite.
*
* @param {Object} event - The key event.
* @returns {void}
*/
_onKeyDown(event) {
const { inviteItems } = this.state;
if (event.key === 'Enter') {
event.preventDefault();
if (!this._isAddDisabled() && inviteItems.length) {
this._onSubmit();
}
}
}
_parseQueryResults: (?Array<Object>) => Array<Object>; _parseQueryResults: (?Array<Object>) => Array<Object>;
/** /**
@ -380,7 +403,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
} }
return ( return (
<div className = 'invite-more-dialog invite-buttons'> <div className = { `invite-more-dialog invite-buttons${this._isAddDisabled() ? ' disabled' : ''}` }>
<a <a
className = 'invite-more-dialog invite-buttons-cancel' className = 'invite-more-dialog invite-buttons-cancel'
onClick = { this._onClearItems }> onClick = { this._onClearItems }>

View File

@ -1,9 +1,9 @@
// @flow // @flow
import { openDialog } from '../base/dialog'; import { hideDialog, openDialog } from '../base/dialog';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { BEGIN_ADD_PEOPLE } from './actionTypes'; import { BEGIN_ADD_PEOPLE, HIDE_ADD_PEOPLE_DIALOG } from './actionTypes';
import { AddPeopleDialog } from './components'; import { AddPeopleDialog } from './components';
import './middleware.any'; import './middleware.any';
@ -17,6 +17,8 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) { switch (action.type) {
case BEGIN_ADD_PEOPLE: case BEGIN_ADD_PEOPLE:
return _beginAddPeople(store, next, action); return _beginAddPeople(store, next, action);
case HIDE_ADD_PEOPLE_DIALOG:
return _hideAddPeopleDialog(store, next, action);
} }
return next(action); return next(action);
@ -42,3 +44,22 @@ function _beginAddPeople({ dispatch }, next, action) {
return result; return result;
} }
/**
* Notifies the feature invite that the action {@link HIDE_ADD_PEOPLE_DIALOG} is being
* dispatched within a specific redux {@code store}.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code HIDE_ADD_PEOPLE_DIALOG} which is
* being dispatched in the specified {@code store}.
* @private
* @returns {*} The value returned by {@code next(action)}.
*/
function _hideAddPeopleDialog({ dispatch }, next, action) {
dispatch(hideDialog(AddPeopleDialog));
return next(action);
}

View File

@ -86,17 +86,20 @@ class LobbySection extends PureComponent<Props, State> {
} }
return ( return (
<div id = 'lobby-section'> <>
{ t('lobby.enableDialogText') } <div id = 'lobby-section'>
<div className = 'control-row'> { t('lobby.enableDialogText') }
<label> <div className = 'control-row'>
{ t('lobby.toggleLabel') } <label>
</label> { t('lobby.toggleLabel') }
<Switch </label>
onValueChange = { this._onToggleLobby } <Switch
value = { this.state.lobbyEnabled } /> onValueChange = { this._onToggleLobby }
value = { this.state.lobbyEnabled } />
</div>
</div> </div>
</div> <div className = 'separator-line' />
</>
); );
} }

View File

@ -89,7 +89,6 @@ function SecurityDialog({
width = { 'small' }> width = { 'small' }>
<div className = 'security-dialog'> <div className = 'security-dialog'>
<LobbySection /> <LobbySection />
<div className = 'separator-line' />
<PasswordSection <PasswordSection
canEditPassword = { _canEditPassword } canEditPassword = { _canEditPassword }
conference = { _conference } conference = { _conference }