From adbb5f8ead4148826a43f1cdf1e35351854e49ee Mon Sep 17 00:00:00 2001 From: Avram Tudor Date: Fri, 17 Sep 2021 13:12:34 +0300 Subject: [PATCH] fix(prejoin) implement ux improvements for mobile (#9939) --- css/premeeting/_connection-status.scss | 2 +- css/premeeting/_device-status.scss | 23 ++-- css/premeeting/_prejoin.scss | 13 +-- css/premeeting/_premeeting-screens.scss | 109 +++++------------- lang/main.json | 1 + .../base/icons/svg/exclamation-triangle.svg | 3 + react/features/base/icons/svg/index.js | 1 + .../premeeting/components/web/ToggleButton.js | 64 ---------- .../base/premeeting/components/web/index.js | 1 - react/features/prejoin/components/Prejoin.js | 51 +------- .../components/preview/DeviceStatus.js | 29 ++--- 11 files changed, 65 insertions(+), 232 deletions(-) create mode 100644 react/features/base/icons/svg/exclamation-triangle.svg delete mode 100644 react/features/base/premeeting/components/web/ToggleButton.js diff --git a/css/premeeting/_connection-status.scss b/css/premeeting/_connection-status.scss index 52ff1d232b..445e7cea84 100644 --- a/css/premeeting/_connection-status.scss +++ b/css/premeeting/_connection-status.scss @@ -11,7 +11,7 @@ background-color: rgba(0, 0, 0, 0.7); align-items: center; display: flex; - padding: 8px 12px; + padding: 14px 16px; } &-circle { diff --git a/css/premeeting/_device-status.scss b/css/premeeting/_device-status.scss index aff964244e..5db7a76913 100644 --- a/css/premeeting/_device-status.scss +++ b/css/premeeting/_device-status.scss @@ -1,16 +1,25 @@ .device { &-status { align-items: center; - align-self: stretch; color: #fff; display: flex; font-size: 14px; - font-weight: 400; - justify-content: center; line-height: 20px; - margin-top: 8px; padding: 6px; text-align: center; + + &-error { + align-items: flex-start; + background-color: #F8AE1A; + border-radius: 6px; + color: #040404; + padding: 12px 16px; + text-align: left; + } + + span { + margin-left: 16px; + } } &-icon { @@ -18,14 +27,8 @@ background-repeat: no-repeat; display: inline-block; height: 16px; - margin-right: 10px; width: 16px; - &--warning { - svg path { - fill: rgba(241, 173, 51, 1); - } - } &--ok { svg path { fill: #189b55; diff --git a/css/premeeting/_prejoin.scss b/css/premeeting/_prejoin.scss index ba1fd02444..640a4e88a9 100644 --- a/css/premeeting/_prejoin.scss +++ b/css/premeeting/_prejoin.scss @@ -3,21 +3,16 @@ width: 100%; } - &-checkbox-container { - margin-bottom: 16px; - width: 100%; - text-align: center; - } - &-error { - color: white; background-color: #E04757; border-radius: 6px; - padding: 4px; box-sizing: border-box; + color: white; + font-size: 12px; + line-height: 16px; margin-bottom: 16px; margin-top: -8px; - font-size: 12px; + padding: 4px; text-align: center; width: 100%; } diff --git a/css/premeeting/_premeeting-screens.scss b/css/premeeting/_premeeting-screens.scss index ee540ba73e..62cb18c1a0 100644 --- a/css/premeeting/_premeeting-screens.scss +++ b/css/premeeting/_premeeting-screens.scss @@ -16,6 +16,7 @@ cursor: pointer; display: inline-block; font-size: 14px; + font-weight: 600; line-height: 24px; margin-bottom: 16px; padding: 7px 16px; @@ -128,10 +129,22 @@ #new-toolbox { bottom: 0; - margin-bottom: 16px; position: relative; transition: none; + .toolbox-content { + margin-bottom: 4px; + } + + .toolbox-content-items { + background: transparent; + border-radius: 0; + box-shadow: none; + display: flex; + justify-content: space-evenly; + padding: 8px 0; + } + .toolbox-content, .toolbox-content-wrapper, .toolbox-content-items { @@ -163,17 +176,26 @@ padding: 16px; width: 100%; + &-controls { + input.field { + font-size: 16px; + padding: 14px 16px; + } + } + .title { - font-size: 20px; - line-height: 28px; - letter-spacing: -0.012; - margin-bottom: 24px; + display: none; } } .con-status { - margin: 16px; - width: calc(100% - 32px); + margin: 0; + width: 100%; + } + + .device-status-error { + border-radius: 0; + margin: 0 -16px; } input.field { @@ -183,15 +205,9 @@ .action-btn { font-size: 16px; + margin-bottom: 8px; padding: 11px 16px; } - - .toolbox-content-items { - border-radius: 0; - display: flex; - justify-content: space-evenly; - padding: 8px 0; - } } input::placeholder { @@ -227,68 +243,3 @@ display: flex; justify-content: center; } - -@mixin icon-container($bg, $fill) { - .toggle-button-icon-container { - background: $bg; - - svg { - fill: $fill - } - } -} - -.toggle-button { - border-radius: 6px; - cursor: pointer; - color: #fff; - font-size: 13px; - height: 40px; - margin: 0 auto; - transition: background 0.16s ease-out; - - @include flex-centered(); - - svg { - fill: transparent; - } - - label { - cursor: pointer; - } - - &:hover { - background: rgba(255, 255, 255, 0.1); - - .toggle-button-icon-container { - display: none; - } - } - - &-container { - position: relative; - - @include flex-centered(); - } - - &-icon-container { - border-radius: 50%; - left: -22px; - padding: 2px; - position: absolute; - } - - &--toggled { - @include icon-container(white, #1C2025); - - &:hover { - .toggle-button-icon-container { - display: block; - } - } - - .toggle-button-icon-container { - display: block; - } - } -} diff --git a/lang/main.json b/lang/main.json index 354e1cb06e..56bfdf7de4 100644 --- a/lang/main.json +++ b/lang/main.json @@ -696,6 +696,7 @@ "errorDialOutFailed": "Could not dial out. Call failed", "errorDialOutStatus": "Error getting dial out status", "errorMissingName": "Please enter your name to join the meeting", + "errorNoPermissions": "You need to enable microphone and camera access", "errorStatusCode": "Error dialing out, status code: {{status}}", "errorValidation": "Number validation failed", "iWantToDialIn": "I want to dial in", diff --git a/react/features/base/icons/svg/exclamation-triangle.svg b/react/features/base/icons/svg/exclamation-triangle.svg new file mode 100644 index 0000000000..bf9ce46ee3 --- /dev/null +++ b/react/features/base/icons/svg/exclamation-triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/react/features/base/icons/svg/index.js b/react/features/base/icons/svg/index.js index bb03001d09..9f2a660a06 100644 --- a/react/features/base/icons/svg/index.js +++ b/react/features/base/icons/svg/index.js @@ -51,6 +51,7 @@ export { default as IconEmail } from './envelope.svg'; export { default as IconEventNote } from './event_note.svg'; export { default as IconExclamation } from './exclamation.svg'; export { default as IconExclamationSolid } from './exclamation-solid.svg'; +export { default as IconExclamationTriangle } from './exclamation-triangle.svg'; export { default as IconExitFullScreen } from './exit-full-screen.svg'; export { default as IconFeedback } from './feedback.svg'; export { default as IconFullScreen } from './full-screen.svg'; diff --git a/react/features/base/premeeting/components/web/ToggleButton.js b/react/features/base/premeeting/components/web/ToggleButton.js deleted file mode 100644 index ef0f8356a5..0000000000 --- a/react/features/base/premeeting/components/web/ToggleButton.js +++ /dev/null @@ -1,64 +0,0 @@ -// @flow - -import React, { useCallback } from 'react'; - -import { Icon, IconCheck } from '../../../icons'; - -const mainClass = 'toggle-button'; - -type Props = { - - /** - * Text of the button. - */ - children: React$Node, - - /** - * If the button is toggled or not. - */ - isToggled?: boolean, - - /** - * OnClick button handler. - */ - onClick: Function -} - -/** - * Button used as a toggle. - * - * @returns {ReactElement} - */ -function ToggleButton({ children, isToggled, onClick }: Props) { - const className = isToggled ? `${mainClass} ${mainClass}--toggled` : mainClass; - - const onKeyPressHandler = useCallback(e => { - if (onClick && (e.key === ' ')) { - e.preventDefault(); - onClick(); - } - }, [ onClick ]); - - return ( -
-
-
- -
- -
-
- ); -} - -export default ToggleButton; diff --git a/react/features/base/premeeting/components/web/index.js b/react/features/base/premeeting/components/web/index.js index 18b58934f6..669a795daf 100644 --- a/react/features/base/premeeting/components/web/index.js +++ b/react/features/base/premeeting/components/web/index.js @@ -3,4 +3,3 @@ export { default as ActionButton } from './ActionButton'; export { default as InputField } from './InputField'; export { default as PreMeetingScreen } from './PreMeetingScreen'; -export { default as ToggleButton } from './ToggleButton'; diff --git a/react/features/prejoin/components/Prejoin.js b/react/features/prejoin/components/Prejoin.js index 124642a9b3..908b98379d 100644 --- a/react/features/prejoin/components/Prejoin.js +++ b/react/features/prejoin/components/Prejoin.js @@ -7,33 +7,26 @@ import { getRoomName } from '../../base/conference'; import { translate } from '../../base/i18n'; import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons'; import { isVideoMutedByUser } from '../../base/media'; -import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting'; +import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting'; import { connect } from '../../base/redux'; import { getDisplayName, updateSettings } from '../../base/settings'; import { getLocalJitsiVideoTrack } from '../../base/tracks'; import { joinConference as joinConferenceAction, joinConferenceWithoutAudio as joinConferenceWithoutAudioAction, - setSkipPrejoin as setSkipPrejoinAction, setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction } from '../actions'; import { isDeviceStatusVisible, isDisplayNameRequired, isJoinByPhoneButtonVisible, - isJoinByPhoneDialogVisible, - isPrejoinSkipped + isJoinByPhoneDialogVisible } from '../functions'; import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog'; type Props = { - /** - * Flag signaling if the 'skip prejoin' button is toggled or not. - */ - buttonIsToggled: boolean, - /** * Flag signaling if the device status is visible or not. */ @@ -69,11 +62,6 @@ type Props = { */ roomName: string, - /** - * Sets visibility of the prejoin page for the next sessions. - */ - setSkipPrejoin: Function, - /** * Sets visibility of the 'JoinByPhoneDialog'. */ @@ -138,7 +126,6 @@ class Prejoin extends Component { this._closeDialog = this._closeDialog.bind(this); this._showDialog = this._showDialog.bind(this); this._onJoinButtonClick = this._onJoinButtonClick.bind(this); - this._onToggleButtonClick = this._onToggleButtonClick.bind(this); this._onDropdownClose = this._onDropdownClose.bind(this); this._onOptionsClick = this._onOptionsClick.bind(this); this._setName = this._setName.bind(this); @@ -183,18 +170,6 @@ class Prejoin extends Component { } } - _onToggleButtonClick: () => void; - - /** - * Handler for the toggle button. - * - * @param {Object} e - The synthetic event. - * @returns {void} - */ - _onToggleButtonClick() { - this.props.setSkipPrejoin(!this.props.buttonIsToggled); - } - _onDropdownClose: () => void; /** @@ -321,7 +296,6 @@ class Prejoin extends Component { return ( @@ -400,25 +374,6 @@ class Prejoin extends Component { ); } - - /** - * Renders the 'skip prejoin' button. - * - * @returns {React$Element} - */ - _renderSkipPrejoinButton() { - const { buttonIsToggled, t } = this.props; - - return ( -
- - {t('prejoin.doNotShow')} - -
- ); - } } /** @@ -432,7 +387,6 @@ function mapStateToProps(state): Object { const showErrorOnJoin = isDisplayNameRequired(state) && !name; return { - buttonIsToggled: isPrejoinSkipped(state), name, deviceStatusVisible: isDeviceStatusVisible(state), roomName: getRoomName(state), @@ -448,7 +402,6 @@ const mapDispatchToProps = { joinConferenceWithoutAudio: joinConferenceWithoutAudioAction, joinConference: joinConferenceAction, setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction, - setSkipPrejoin: setSkipPrejoinAction, updateSettings }; diff --git a/react/features/prejoin/components/preview/DeviceStatus.js b/react/features/prejoin/components/preview/DeviceStatus.js index 522db73b49..5eb653513a 100644 --- a/react/features/prejoin/components/preview/DeviceStatus.js +++ b/react/features/prejoin/components/preview/DeviceStatus.js @@ -3,12 +3,11 @@ import React from 'react'; import { translate } from '../../../base/i18n'; -import { Icon, IconCheckSolid, IconExclamation } from '../../../base/icons'; +import { Icon, IconCheckSolid, IconExclamationTriangle } from '../../../base/icons'; import { connect } from '../../../base/redux'; import { getDeviceStatusType, - getDeviceStatusText, - getRawError + getDeviceStatusText } from '../../functions'; export type Props = { @@ -24,11 +23,6 @@ export type Props = { */ deviceStatusType: string, - /** - * The error coming from device configuration. - */ - rawError: string, - /** * Used for translation. */ @@ -37,7 +31,7 @@ export type Props = { const iconMap = { warning: { - src: IconExclamation, + src: IconExclamationTriangle, className: 'device-icon--warning' }, ok: { @@ -52,25 +46,23 @@ const iconMap = { * * @returns {ReactElement} */ -function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props) { +function DeviceStatus({ deviceStatusType, deviceStatusText, t }: Props) { const { src, className } = iconMap[deviceStatusType]; + const hasError = deviceStatusType === 'warning'; + const containerClassName = `device-status ${hasError ? 'device-status-error' : ''}`; return (
- - {t(deviceStatusText)} + + {hasError ? t('prejoin.errorNoPermissions') : t(deviceStatusText)} - { rawError && - { rawError } - }
); } @@ -84,8 +76,7 @@ function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props function mapStateToProps(state) { return { deviceStatusText: getDeviceStatusText(state), - deviceStatusType: getDeviceStatusType(state), - rawError: getRawError(state) + deviceStatusType: getDeviceStatusType(state) }; }