Info dialog: bold labels, no url truncation, only auto show on lonely call (#2619)

* fix(info): bold info labels

* fix(info): do not truncate url

* feat(info): show only during a lonely call
This commit is contained in:
virtuacoplenny 2018-04-04 08:58:54 -07:00 committed by bbaldino
parent 2412239206
commit 2f23f8e400
10 changed files with 119 additions and 175 deletions

View File

@ -38,7 +38,7 @@
.info-dialog-copy-element { .info-dialog-copy-element {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
position: fixed; position: absolute;
-webkit-user-select: text; -webkit-user-select: text;
user-select: text; user-select: text;
} }
@ -56,11 +56,11 @@
} }
.info-dialog-conference-url { .info-dialog-conference-url {
max-width: 250px; width: max-content;
overflow: hidden; width: -moz-max-content;
text-overflow: ellipsis; width: -webkit-max-content;
user-select: text; word-break: break-all;
white-space: nowrap; max-width: 400px;
} }
.info-dialog-dial-in { .info-dialog-dial-in {
@ -82,14 +82,18 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.info-password,
.info-dialog-password, .info-dialog-password,
.info-password,
.info-password-form { .info-password-form {
align-items: baseline;
display: flex; display: flex;
} }
.info-label {
font-weight: bold;
}
.info-password-field { .info-password-field {
margin-left: 2px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@ -110,10 +114,6 @@
.info-password-local { .info-password-local {
user-select: text; user-select: text;
} }
.conference-id {
margin-left: 5px;
}
} }
.dial-in-page { .dial-in-page {
@ -125,10 +125,6 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
* {
user-select: text;
}
.dial-in-numbers-list { .dial-in-numbers-list {
font-size: 24px; font-size: 24px;
margin-top: 20px; margin-top: 20px;
@ -139,3 +135,12 @@
width: 30%; width: 30%;
} }
} }
.info-dialog,
.dial-in-page {
* {
user-select: text;
-moz-user-select: text;
-webkit-user-select: text;
}
}

View File

@ -516,11 +516,11 @@
"info": { "info": {
"addPassword": "Add password", "addPassword": "Add password",
"cancelPassword": "Cancel password", "cancelPassword": "Cancel password",
"conferenceURL": "Link: __url__", "conferenceURL": "Link:",
"country": "Country", "country": "Country",
"dialANumber": "To join your meeting, dial one of these numbers and then enter this PIN: __conferenceID__#", "dialANumber": "To join your meeting, dial one of these numbers and then enter this PIN: __conferenceID__#",
"dialInNumber": "Dial-in: __phoneNumber__", "dialInNumber": "Dial-in:",
"dialInConferenceID": "PIN: __conferenceID__#", "dialInConferenceID": "PIN:",
"dialInNotSupported": "Sorry, dialing in is currently not suppported.", "dialInNotSupported": "Sorry, dialing in is currently not suppported.",
"genericError": "Whoops, something went wrong.", "genericError": "Whoops, something went wrong.",
"invitePhone": "To join by phone, dial __number__ and enter this PIN: __conferenceID__#", "invitePhone": "To join by phone, dial __number__ and enter this PIN: __conferenceID__#",
@ -532,7 +532,7 @@
"noRoom": "No room was specified to dial-in into.", "noRoom": "No room was specified to dial-in into.",
"numbers": "Dial-in Numbers", "numbers": "Dial-in Numbers",
"password": "Password:", "password": "Password:",
"title": "Call info", "title": "Share",
"tooltip": "Get access info about the meeting" "tooltip": "Get access info about the meeting"
}, },
"settingsView": { "settingsView": {

View File

@ -1,14 +1,3 @@
/**
* The type of the action which signals a request to display the inline
* conference info dialog.
*
* {
* type: SET_INFO_DIALOG_VISIBILITY,
* visible: boolean
* }
*/
export const SET_INFO_DIALOG_VISIBILITY = Symbol('SET_INFO_DIALOG_VISIBILITY');
/** /**
* 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

@ -1,34 +1,11 @@
// @flow // @flow
import { import {
SET_INFO_DIALOG_VISIBILITY,
UPDATE_DIAL_IN_NUMBERS_FAILED, UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS UPDATE_DIAL_IN_NUMBERS_SUCCESS
} from './actionTypes'; } from './actionTypes';
import { getDialInConferenceID, getDialInNumbers } from './functions'; import { getDialInConferenceID, getDialInNumbers } from './functions';
/**
* Opens the inline conference info dialog.
*
* @param {boolean} visible - Whether or not the dialog should be displayed.
* @param {boolean} autoClose - Whether or not the dialog should automatically
* close after a set period of time.
* @returns {{
* type: SET_INFO_DIALOG_VISIBILITY,
* autoClose: boolean,
* visible: boolean
* }}
*/
export function setInfoDialogVisibility(
visible: boolean,
autoClose: boolean = false) {
return {
type: SET_INFO_DIALOG_VISIBILITY,
autoClose,
visible
};
}
/** /**
* Sends AJAX requests for dial-in numbers and conference ID. * Sends AJAX requests for dial-in numbers and conference ID.
* *

View File

@ -7,16 +7,16 @@ import { connect } from 'react-redux';
import { createToolbarEvent, sendAnalytics } from '../../analytics'; import { createToolbarEvent, sendAnalytics } from '../../analytics';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { getParticipantCount } from '../../base/participants';
import { import {
ToolbarButton, ToolbarButton,
ToolbarButtonV2, ToolbarButtonV2,
TOOLTIP_TO_POPUP_POSITION TOOLTIP_TO_POPUP_POSITION
} from '../../toolbox'; } from '../../toolbox';
import { setInfoDialogVisibility, updateDialInNumbers } from '../actions'; import { updateDialInNumbers } from '../actions';
import { InfoDialog } from './info-dialog';
const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig; import { InfoDialog } from './info-dialog';
/** /**
* A configuration object to describe how {@code ToolbarButton} should render * A configuration object to describe how {@code ToolbarButton} should render
@ -32,6 +32,14 @@ const DEFAULT_BUTTON_CONFIGURATION = {
tooltipKey: 'info.tooltip' tooltipKey: 'info.tooltip'
}; };
/**
* The amount of time, in milliseconds, to wait until automatically showing
* the {@code InfoDialog}. This is essentially a hack as automatic showing
* should happen in a lonely call and some time is needed to populate
* participants already in the call.
*/
const INFO_DIALOG_AUTO_SHOW_TIMEOUT = 1500;
/** /**
* A React Component for displaying a button which opens a dialog with * A React Component for displaying a button which opens a dialog with
* information about the conference and with ways to invite people. * information about the conference and with ways to invite people.
@ -55,15 +63,16 @@ class InfoDialogButton extends Component {
]), ]),
/** /**
* Whether or not the {@code InfoDialog} should close by itself after a * Whether or not the {@code InfoDialog} should display automatically
* a timeout. * after {@link INFO_DIALOG_AUTO_SHOW_TIMEOUT}.
*/ */
_shouldAutoClose: PropTypes.bool, _disableAutoShow: PropTypes.bool,
/** /**
* Whether or not {@code InfoDialog} should be displayed. * The number of real participants in the call. If in a lonely call,
* the {@code InfoDialog} will be automatically shown.
*/ */
_showDialog: PropTypes.bool, _participantCount: PropTypes.number,
/** /**
* Whether or not the toolbox, in which this component exists, are * Whether or not the toolbox, in which this component exists, are
@ -98,16 +107,23 @@ class InfoDialogButton extends Component {
super(props); super(props);
/** /**
* The timeout to automatically hide the {@code InfoDialog} if it has * The timeout to automatically show the {@code InfoDialog} if it has
* not been interacted with. * not been shown yet in a lonely call.
* *
* @type {timeoutID} * @type {timeoutID}
*/ */
this._autoHideDialogTimeout = null; this._autoShowTimeout = null;
this.state = {
/**
* Whether or not {@code InfoDialog} should be visible.
*/
showDialog: false
};
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once for every instance.
this._onDialogClose = this._onDialogClose.bind(this); this._onDialogClose = this._onDialogClose.bind(this);
this._onDialogMouseOver = this._onDialogMouseOver.bind(this);
this._onDialogToggle = this._onDialogToggle.bind(this); this._onDialogToggle = this._onDialogToggle.bind(this);
} }
@ -117,30 +133,15 @@ class InfoDialogButton extends Component {
* @inheritdoc * @inheritdoc
*/ */
componentDidMount() { componentDidMount() {
if (this.props._shouldAutoClose) { this._autoShowTimeout = setTimeout(() => {
this._setAutoCloseTimeout(); this._maybeAutoShowDialog();
} }, INFO_DIALOG_AUTO_SHOW_TIMEOUT);
if (!this.props._dialInNumbers) { if (!this.props._dialInNumbers) {
this.props.dispatch(updateDialInNumbers()); this.props.dispatch(updateDialInNumbers());
} }
} }
/**
* Set or clear the timeout to automatically hide the {@code InfoDialog}.
*
* @inheritdoc
*/
componentDidUpdate(prevProps) {
// If the _shouldAutoClose flag has been updated to be true then make
// sure to set _autoHideDialogTimeout.
if (this.props._shouldAutoClose && !prevProps._shouldAutoClose) {
this._setAutoCloseTimeout();
} else {
this._clearAutoCloseTimeout();
}
}
/** /**
* Update the visibility of the {@code InfoDialog}. * Update the visibility of the {@code InfoDialog}.
* *
@ -148,7 +149,7 @@ class InfoDialogButton extends Component {
*/ */
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
// Ensure the dialog is closed when the toolbox becomes hidden. // Ensure the dialog is closed when the toolbox becomes hidden.
if (nextProps._showDialog && !nextProps._toolboxVisible) { if (this.state.showDialog && !nextProps._toolboxVisible) {
this._onDialogClose(); this._onDialogClose();
} }
} }
@ -159,7 +160,7 @@ class InfoDialogButton extends Component {
* @inheritdoc * @inheritdoc
*/ */
componentWillUnmount() { componentWillUnmount() {
this._clearAutoCloseTimeout(); clearTimeout(this._autoShowTimeout);
} }
/** /**
@ -175,14 +176,16 @@ class InfoDialogButton extends Component {
} }
/** /**
* Cancels the timeout to automatically hide the {@code InfoDialog}. * Callback invoked after a timeout to trigger display of the
* {@code InfoDialog} if certain conditions are met.
* *
* @private * @private
* @returns {void} * @returns {void}
*/ */
_clearAutoCloseTimeout() { _maybeAutoShowDialog() {
clearTimeout(this._autoHideDialogTimeout); if (this.props._participantCount < 2 && !this.props._disableAutoShow) {
this._autoHideDialogTimeout = null; this.setState({ showDialog: true });
}
} }
/** /**
@ -192,17 +195,7 @@ class InfoDialogButton extends Component {
* @returns {void} * @returns {void}
*/ */
_onDialogClose() { _onDialogClose() {
this.props.dispatch(setInfoDialogVisibility(false)); this.setState({ showDialog: false });
}
/**
* Cancels the timeout to automatically hide the {@code InfoDialog}.
*
* @private
* @returns {void}
*/
_onDialogMouseOver() {
this._clearAutoCloseTimeout();
} }
/** /**
@ -214,7 +207,7 @@ class InfoDialogButton extends Component {
_onDialogToggle() { _onDialogToggle() {
sendAnalytics(createToolbarEvent('info')); sendAnalytics(createToolbarEvent('info'));
this.props.dispatch(setInfoDialogVisibility(!this.props._showDialog)); this.setState({ showDialog: !this.state.showDialog });
} }
/** /**
@ -225,22 +218,21 @@ class InfoDialogButton extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderOldToolbarButton() { _renderOldToolbarButton() {
const { _showDialog, _toolboxVisible, tooltipPosition } = this.props; const { tooltipPosition } = this.props;
const { showDialog } = this.state;
const buttonConfiguration = { const buttonConfiguration = {
...DEFAULT_BUTTON_CONFIGURATION, ...DEFAULT_BUTTON_CONFIGURATION,
classNames: [ classNames: [
...DEFAULT_BUTTON_CONFIGURATION.classNames, ...DEFAULT_BUTTON_CONFIGURATION.classNames,
_showDialog ? 'toggled button-active' : '' showDialog ? 'toggled button-active' : ''
] ]
}; };
return ( return (
<InlineDialog <InlineDialog
content = { <InfoDialog content = { <InfoDialog onClose = { this._onDialogClose } /> }
autoUpdateNumbers = { false } isOpen = { showDialog }
onClose = { this._onDialogClose }
onMouseOver = { this._onDialogMouseOver } /> }
isOpen = { _toolboxVisible && _showDialog }
onClose = { this._onDialogClose } onClose = { this._onDialogClose }
position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }> position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
<ToolbarButton <ToolbarButton
@ -259,17 +251,16 @@ class InfoDialogButton extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderNewToolbarButton() { _renderNewToolbarButton() {
const { _showDialog, _toolboxVisible, t } = this.props; const { t } = this.props;
const iconClass = `icon-info ${_showDialog ? 'toggled' : ''}`; const { showDialog } = this.state;
const iconClass = `icon-info ${showDialog ? 'toggled' : ''}`;
return ( return (
<div className = 'toolbox-button-wth-dialog'> <div className = 'toolbox-button-wth-dialog'>
<InlineDialog <InlineDialog
content = { <InfoDialog content = {
autoUpdateNumbers = { false } <InfoDialog onClose = { this._onDialogClose } /> }
onClose = { this._onDialogClose } isOpen = { showDialog }
onMouseOver = { this._onDialogMouseOver } /> }
isOpen = { _toolboxVisible && _showDialog }
onClose = { this._onDialogClose } onClose = { this._onDialogClose }
position = { 'top right' }> position = { 'top right' }>
<ToolbarButtonV2 <ToolbarButtonV2
@ -281,22 +272,6 @@ class InfoDialogButton extends Component {
</div> </div>
); );
} }
/**
* Set a timeout to automatically hide the {@code InfoDialog}.
*
* @private
* @returns {void}
*/
_setAutoCloseTimeout() {
this._clearAutoCloseTimeout();
this._autoHideDialogTimeout = setTimeout(() => {
if (this.props._showDialog) {
this._onDialogClose();
}
}, INITIAL_TOOLBAR_TIMEOUT);
}
} }
/** /**
@ -307,22 +282,17 @@ class InfoDialogButton extends Component {
* @private * @private
* @returns {{ * @returns {{
* _dialInNumbers: Array, * _dialInNumbers: Array,
* _shouldAutoClose: boolean, * _disableAutoShow: bolean,
* _showDialog: boolean, * _participantCount: number,
* _toolboxVisible: boolean * _toolboxVisible: boolean
* }} * }}
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
const {
infoDialogVisible,
infoDialogWillAutoClose,
numbers
} = state['features/invite'];
return { return {
_dialInNumbers: numbers, _dialInNumbers: state['features/invite'].numbers,
_shouldAutoClose: infoDialogWillAutoClose, _disableAutoShow: state['features/base/config'].iAmRecorder,
_showDialog: infoDialogVisible, _participantCount:
getParticipantCount(state['features/base/participants']),
_toolboxVisible: state['features/toolbox'].visible _toolboxVisible: state['features/toolbox'].visible
}; };
} }

View File

@ -41,16 +41,28 @@ class DialInNumber extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { conferenceID, phoneNumber } = this.props; const { conferenceID, phoneNumber, t } = this.props;
return ( return (
<div className = 'dial-in-number'> <div className = 'dial-in-number'>
<span className = 'phone-number'> <span className = 'phone-number'>
{ this.props.t('info.dialInNumber', { phoneNumber }) } <span className = 'info-label'>
{ t('info.dialInNumber') }
</span>
<span className = 'spacer'>&nbsp;</span>
<span className = 'info-value'>
{ phoneNumber }
</span>
</span> </span>
<span className = 'spacer'>&nbsp;</span>
<span className = 'conference-id'> <span className = 'conference-id'>
{ this.props.t( <span className = 'info-label'>
'info.dialInConferenceID', { conferenceID }) } { t('info.dialInConferenceID') }
</span>
<span className = 'spacer'>&nbsp;</span>
<span className = 'info-value'>
{ `${conferenceID}#` }
</span>
</span> </span>
</div> </div>
); );

View File

@ -214,14 +214,13 @@ class InfoDialog extends Component {
{ t('info.title') } { t('info.title') }
</div> </div>
<div className = 'info-dialog-conference-url'> <div className = 'info-dialog-conference-url'>
{ t('info.conferenceURL', <span className = 'info-label'>
{ url: this._getURLToDisplay() }) } { t('info.conferenceURL') }
<textarea </span>
className = 'info-dialog-copy-element' <span className = 'spacer'>&nbsp;</span>
readOnly = { true } <span className = 'info-value'>
ref = { this._setCopyElement } { this._getURLToDisplay() }
tabIndex = '-1' </span>
value = { this._getTextToCopy() } />
</div> </div>
<div className = 'info-dialog-dial-in'> <div className = 'info-dialog-dial-in'>
{ this._renderDialInDisplay() } { this._renderDialInDisplay() }
@ -244,6 +243,12 @@ class InfoDialog extends Component {
{ this._renderPasswordAction() } { this._renderPasswordAction() }
</div> </div>
</div> </div>
<textarea
className = 'info-dialog-copy-element'
readOnly = { true }
ref = { this._setCopyElement }
tabIndex = '-1'
value = { this._getTextToCopy() } />
</div> </div>
); );
} }

View File

@ -94,10 +94,13 @@ class PasswordForm extends Component {
return ( return (
<div className = 'info-password'> <div className = 'info-password'>
<div>{ t('info.password') }</div> <span className = 'info-label'>
<div className = 'info-password-field'> { t('info.password') }
</span>
<span className = 'spacer'>&nbsp;</span>
<span className = 'info-password-field info-value'>
{ this._renderPasswordField() } { this._renderPasswordField() }
</div> </span>
</div> </div>
); );
} }

View File

@ -1,7 +1,5 @@
import { CONFERENCE_JOINED } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { setInfoDialogVisibility } from './actions';
import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes'; import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
const logger = require('jitsi-meet-logger').getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
@ -17,13 +15,6 @@ MiddlewareRegistry.register(store => next => action => {
const result = next(action); const result = next(action);
switch (action.type) { switch (action.type) {
case CONFERENCE_JOINED:
// we do not want to show call info in iAmRecorder mode
if (store.getState()['features/base/config'].iAmRecorder) {
return result;
}
store.dispatch(setInfoDialogVisibility(true, true));
break;
case UPDATE_DIAL_IN_NUMBERS_FAILED: case UPDATE_DIAL_IN_NUMBERS_FAILED:
logger.error( logger.error(

View File

@ -1,7 +1,6 @@
import { ReducerRegistry } from '../base/redux'; import { ReducerRegistry } from '../base/redux';
import { import {
SET_INFO_DIALOG_VISIBILITY,
UPDATE_DIAL_IN_NUMBERS_FAILED, UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS UPDATE_DIAL_IN_NUMBERS_SUCCESS
} from './actionTypes'; } from './actionTypes';
@ -12,13 +11,6 @@ const DEFAULT_STATE = {
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => { ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
switch (action.type) { switch (action.type) {
case SET_INFO_DIALOG_VISIBILITY:
return {
...state,
infoDialogVisible: action.visible,
infoDialogWillAutoClose: action.autoClose
};
case UPDATE_DIAL_IN_NUMBERS_FAILED: case UPDATE_DIAL_IN_NUMBERS_FAILED:
return { return {
...state, ...state,