From 0d5dae7ab9557b3fffbad995cfd980e80f63ac07 Mon Sep 17 00:00:00 2001 From: Robert Pintilii Date: Mon, 30 Jan 2023 13:34:56 +0200 Subject: [PATCH] feat(prejoin) Update design (#12844) --- css/premeeting/_prejoin.scss | 15 +- css/premeeting/_premeeting-screens.scss | 141 ++------------ .../components/web/ConnectionStatus.tsx | 11 +- .../components/web/PreMeetingScreen.tsx | 78 +++++++- react/features/base/ui/Tokens.ts | 2 - .../features/base/ui/components/web/Input.tsx | 4 + react/features/base/ui/constants.web.ts | 10 +- .../web/{Prejoin.js => Prejoin.tsx} | 176 ++++++++---------- .../components/web/preview/DeviceStatus.tsx | 52 ++---- 9 files changed, 206 insertions(+), 283 deletions(-) rename react/features/prejoin/components/web/{Prejoin.js => Prejoin.tsx} (80%) diff --git a/css/premeeting/_prejoin.scss b/css/premeeting/_prejoin.scss index 32992da46..b6d2a3e29 100644 --- a/css/premeeting/_prejoin.scss +++ b/css/premeeting/_prejoin.scss @@ -41,11 +41,11 @@ &-dropdown-btns { padding: 8px 0; } - + &-dropdown-container { position: relative; width: 100%; - + /** * Override default InlineDialog behaviour, since it does not play nicely with relative widths */ @@ -56,5 +56,12 @@ width: 100%; } } - } - \ No newline at end of file +} + +.prejoin-input { + margin-bottom: 16px; + + & input { + text-align: center; + } +} diff --git a/css/premeeting/_premeeting-screens.scss b/css/premeeting/_premeeting-screens.scss index 56455dd9d..d7138ec22 100644 --- a/css/premeeting/_premeeting-screens.scss +++ b/css/premeeting/_premeeting-screens.scss @@ -1,14 +1,4 @@ - .premeeting-screen { - background: #292929; - bottom: 0; - display: flex; - font-size: 1.3em; - left: 0; - position: absolute; - right: 0; - top: 0; - z-index: $toolbarZ + 2; - +.premeeting-screen { .action-btn { border-radius: 6px; box-sizing: border-box; @@ -75,129 +65,38 @@ } } - .content { - align-items: center; - box-sizing: border-box; - display: flex; - flex-direction: column; - flex-shrink: 0; - height: 100%; - margin: 0 30px; - padding: 24px 0 16px; + #new-toolbox { + bottom: 0; position: relative; - width: $prejoinDefaultContentWidth; - z-index: $toolbarZ + 2; + transition: none; - &-controls { - align-items: center; + .toolbox-content { + margin-bottom: 4px; + } + + .toolbox-content-items { + @include ltr; + background: transparent; + box-shadow: none; display: flex; - flex-direction: column; - margin: auto; + justify-content: space-between; + padding: 8px 0; + } + + .toolbox-content, + .toolbox-content-wrapper, + .toolbox-content-items { + box-sizing: border-box; width: 100%; - - .title { - color: #fff; - font-size: 28px; - font-weight: 600; - letter-spacing: -0.015; - line-height: 36px; - margin-bottom: 16px; - text-align: center; - } - - input.field { - background-color: white; - border: none; - outline: none; - border-radius: 6px; - font-size: 14px; - line-height: 20px; - margin-bottom: 16px; - color: #1C2025; - padding: 10px 16px; - text-align: center; - width: 100%; - - &.error { - border: 1px solid #E04757; - } - - &.focused { - box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white; - } - } - - #new-toolbox { - bottom: 0; - position: relative; - transition: none; - - .toolbox-content { - margin-bottom: 4px; - } - - .toolbox-content-items { - @include ltr; - background: transparent; - box-shadow: none; - display: flex; - justify-content: space-evenly; - padding: 8px 0; - } - - .toolbox-content, - .toolbox-content-wrapper, - .toolbox-content-items { - box-sizing: border-box; - width: 100%; - } - } - } - } - - @media (max-width: 720px) { - flex-direction: column-reverse; - - .content { - height: auto; - margin: 0 auto; - } - } - - // mobile phone landscape - @media (max-height: 420px) { - div.content { - padding: 16px 16px 0 16px; } } @media (max-width: 400px) { - .content { - padding: 16px; - width: 100%; - - &-controls { - input.field { - font-size: 16px; - padding: 14px 16px; - } - } - - .title { - display: none; - } - } - .device-status-error { border-radius: 0; margin: 0 -16px; } - input.field { - font-size: 16px; - padding: 14px 16px; - } - .action-btn { font-size: 16px; margin-bottom: 8px; diff --git a/react/features/base/premeeting/components/web/ConnectionStatus.tsx b/react/features/base/premeeting/components/web/ConnectionStatus.tsx index 69d5b9b68..9973327ec 100644 --- a/react/features/base/premeeting/components/web/ConnectionStatus.tsx +++ b/react/features/base/premeeting/components/web/ConnectionStatus.tsx @@ -7,6 +7,7 @@ import { translate } from '../../../i18n/functions'; import Icon from '../../../icons/components/Icon'; import { IconArrowDown, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons/svg'; import { connect } from '../../../redux/functions'; +import { withPixelLineHeight } from '../../../styles/functions.web'; import { PREJOIN_DEFAULT_CONTENT_WIDTH } from '../../../ui/components/variables'; import { CONNECTION_TYPE } from '../../constants'; import { getConnectionData } from '../../functions'; @@ -27,11 +28,8 @@ interface IProps extends WithTranslation { const useStyles = makeStyles()(theme => { return { connectionStatus: { - borderRadius: '6px', color: '#fff', - fontSize: '12px', - letterSpacing: '0.16px', - lineHeight: '16px', + ...withPixelLineHeight(theme.typography.bodyShortRegular), position: 'absolute', width: '100%', @@ -56,14 +54,15 @@ const useStyles = makeStyles()(theme => { backgroundColor: 'rgba(0, 0, 0, 0.7)', alignItems: 'center', display: 'flex', - padding: '14px 16px' + padding: '12px 16px', + borderRadius: theme.shape.borderRadius }, '& .con-status-circle': { borderRadius: '50%', display: 'inline-block', padding: theme.spacing(1), - marginRight: theme.spacing(3) + marginRight: theme.spacing(2) }, '& .con-status--good': { diff --git a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx index 2d55fbb2b..e0b2edcf9 100644 --- a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx +++ b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx @@ -1,5 +1,7 @@ /* eslint-disable lines-around-comment */ +import clsx from 'clsx'; import React, { ReactNode } from 'react'; +import { connect } from 'react-redux'; import { makeStyles } from 'tss-react/mui'; import { IReduxState } from '../../../../app/types'; @@ -9,12 +11,12 @@ import { Toolbox } from '../../../../toolbox/components/web'; import { getConferenceName } from '../../../conference/functions'; import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants'; import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/functions.web'; -import { connect } from '../../../redux/functions'; import { withPixelLineHeight } from '../../../styles/functions.web'; import ConnectionStatus from './ConnectionStatus'; // @ts-ignore import Preview from './Preview'; +/* eslint-enable lines-around-comment */ interface IProps { @@ -51,7 +53,7 @@ interface IProps { /** * Indicates whether the copy url button should be shown. */ - showCopyUrlButton: boolean; + showCopyUrlButton?: boolean; /** * Indicates whether the device status should be shown. @@ -86,7 +88,64 @@ interface IProps { const useStyles = makeStyles()(theme => { return { - subtitle: { + container: { + height: '100%', + position: 'absolute', + inset: '0 0 0 0', + display: 'flex', + backgroundColor: theme.palette.ui01, + zIndex: 252, + + '@media (max-width: 720px)': { + flexDirection: 'column-reverse' + } + }, + content: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flexShrink: 0, + boxSizing: 'border-box', + margin: '0 48px', + padding: '24px 0 16px', + position: 'relative', + width: '300px', + height: '100%', + zIndex: 252, + + '@media (max-width: 720px)': { + height: 'auto', + margin: '0 auto' + }, + + // mobile phone landscape + '@media (max-width: 420px)': { + padding: '16px 16px 0 16px', + width: '100%' + }, + + '@media (max-width: 400px)': { + padding: '16px' + } + }, + contentControls: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + margin: 'auto', + width: '100%' + }, + title: { + ...withPixelLineHeight(theme.typography.heading4), + color: `${theme.palette.text01}!important`, + marginBottom: theme.spacing(3), + textAlign: 'center', + + '@media (max-width: 400px)': { + display: 'none' + } + }, + roomName: { ...withPixelLineHeight(theme.typography.heading5), color: theme.palette.text01, marginBottom: theme.spacing(4), @@ -112,7 +171,6 @@ const PreMeetingScreen = ({ videoTrack }: IProps) => { const { classes } = useStyles(); - const containerClassName = `premeeting-screen ${className ? className : ''}`; const style = _premeetingBackground ? { background: _premeetingBackground, backgroundPosition: 'center', @@ -120,17 +178,17 @@ const PreMeetingScreen = ({ } : {}; return ( -
+
-
+
-
-

+
+

{title}

{_roomName && ( - + {_roomName} )} @@ -175,7 +233,7 @@ function mapStateToProps(state: IReduxState, ownProps: Partial) { ? premeetingButtons : premeetingButtons.filter(b => isToolbarButtonEnabled(b, toolbarButtons)), _premeetingBackground: premeetingBackground, - _roomName: hideConferenceSubject ? undefined : getConferenceName(state) + _roomName: (hideConferenceSubject ? undefined : getConferenceName(state)) ?? '' }; } diff --git a/react/features/base/ui/Tokens.ts b/react/features/base/ui/Tokens.ts index 1f0b81a25..4b62e43ef 100644 --- a/react/features/base/ui/Tokens.ts +++ b/react/features/base/ui/Tokens.ts @@ -48,7 +48,6 @@ export const colors = { // after we replace them in the components. primary10: '#17A0DB', primary11: '#1081B2', - primary12: '#B8C7E0', surface00: '#111111', surface12: '#AAAAAA', surface13: '#495258', @@ -199,7 +198,6 @@ export const colorMap = { border01: 'surface08', border02: 'surface06', border03: 'surface04', - border04: 'primary12', border05: 'surface07', borderError: 'error06', warning03: 'warning07', diff --git a/react/features/base/ui/components/web/Input.tsx b/react/features/base/ui/components/web/Input.tsx index 8d867484d..cca8abf37 100644 --- a/react/features/base/ui/components/web/Input.tsx +++ b/react/features/base/ui/components/web/Input.tsx @@ -10,6 +10,7 @@ import { IInputProps } from '../types'; interface IProps extends IInputProps { accessibilityLabel?: string; + autoComplete?: string; autoFocus?: boolean; bottomLabel?: string; className?: string; @@ -131,6 +132,7 @@ const useStyles = makeStyles()(theme => { const Input = React.forwardRef(({ accessibilityLabel, + autoComplete, autoFocus, bottomLabel, className, @@ -175,6 +177,7 @@ const Input = React.forwardRef(({ {textarea ? ( (({ ) : ( { padding: 6, textAlign: 'center' as const, pointerEvents: 'all' as const, - boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)', - - '& > div': { - marginLeft: 8, - - '&:first-child': { - marginLeft: 0 - } - } + boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)' } }; }; diff --git a/react/features/prejoin/components/web/Prejoin.js b/react/features/prejoin/components/web/Prejoin.tsx similarity index 80% rename from react/features/prejoin/components/web/Prejoin.js rename to react/features/prejoin/components/web/Prejoin.tsx index f0b457165..466c02be0 100644 --- a/react/features/prejoin/components/web/Prejoin.js +++ b/react/features/prejoin/components/web/Prejoin.tsx @@ -1,25 +1,30 @@ -// @flow - import InlineDialog from '@atlaskit/inline-dialog'; import React, { Component } from 'react'; +import { WithTranslation } from 'react-i18next'; +import { IReduxState } from '../../../app/types'; +// eslint-disable-next-line lines-around-comment +// @ts-ignore import { Avatar } from '../../../base/avatar'; -import { isNameReadOnly } from '../../../base/config'; -import { translate } from '../../../base/i18n'; -import { IconArrowDown, IconArrowUp, IconPhoneRinging, IconVolumeOff } from '../../../base/icons'; -import { isVideoMutedByUser } from '../../../base/media'; -import { getLocalParticipant } from '../../../base/participants'; -import { ActionButton, InputField, PreMeetingScreen } from '../../../base/premeeting'; -import { connect } from '../../../base/redux'; -import { getDisplayName, updateSettings } from '../../../base/settings'; -import { getLocalJitsiVideoTrack } from '../../../base/tracks'; +import { isNameReadOnly } from '../../../base/config/functions.web'; +import { translate } from '../../../base/i18n/functions'; +import { IconArrowDown, IconArrowUp, IconPhoneRinging, IconVolumeOff } from '../../../base/icons/svg'; +import { isVideoMutedByUser } from '../../../base/media/functions'; +import { getLocalParticipant } from '../../../base/participants/functions'; +import ActionButton from '../../../base/premeeting/components/web/ActionButton'; +import PreMeetingScreen from '../../../base/premeeting/components/web/PreMeetingScreen'; +import { connect } from '../../../base/redux/functions'; +import { updateSettings } from '../../../base/settings/actions'; +import { getDisplayName } from '../../../base/settings/functions.web'; +import { getLocalJitsiVideoTrack } from '../../../base/tracks/functions.web'; import Button from '../../../base/ui/components/web/Button'; +import Input from '../../../base/ui/components/web/Input'; import { BUTTON_TYPES } from '../../../base/ui/constants.any'; import { joinConference as joinConferenceAction, joinConferenceWithoutAudio as joinConferenceWithoutAudioAction, setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction -} from '../../actions'; +} from '../../actions.web'; import { isDeviceStatusVisible, isDisplayNameRequired, @@ -28,114 +33,112 @@ import { isPrejoinDisplayNameVisible } from '../../functions'; +// @ts-ignore import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog'; -type Props = { +interface IProps extends WithTranslation { /** * Indicates whether the display name is editable. */ - canEditDisplayName: boolean, + canEditDisplayName: boolean; /** * Flag signaling if the device status is visible or not. */ - deviceStatusVisible: boolean, + deviceStatusVisible: boolean; /** * If join by phone button should be visible. */ - hasJoinByPhoneButton: boolean, + hasJoinByPhoneButton: boolean; /** * Joins the current meeting. */ - joinConference: Function, + joinConference: Function; /** * Joins the current meeting without audio. */ - joinConferenceWithoutAudio: Function, + joinConferenceWithoutAudio: Function; /** * Whether conference join is in progress. */ - joiningInProgress: boolean, + joiningInProgress: boolean; /** * The name of the user that is about to join. */ - name: string, - - /** - * Updates settings. - */ - updateSettings: Function, + name: string; /** * Local participant id. */ - participantId: string, + participantId: string; /** * The prejoin config. */ - prejoinConfig?: Object, + prejoinConfig?: any; /** * Whether the name input should be read only or not. */ - readOnlyName: boolean, + readOnlyName: boolean; /** * Sets visibility of the 'JoinByPhoneDialog'. */ - setJoinByPhoneDialogVisiblity: Function, + setJoinByPhoneDialogVisiblity: Function; /** * Flag signaling the visibility of camera preview. */ - showCameraPreview: boolean, - - /** - * If should show an error when joining without a name. - */ - showErrorOnJoin: boolean, + showCameraPreview: boolean; /** * If 'JoinByPhoneDialog' is visible or not. */ - showDialog: boolean, + showDialog: boolean; /** - * Used for translation. + * If should show an error when joining without a name. */ - t: Function, + showErrorOnJoin: boolean; + + /** + * Updates settings. + */ + updateSettings: Function; /** * The JitsiLocalTrack to display. */ - videoTrack: ?Object -}; + videoTrack?: Object; +} -type State = { +interface IState { /** * Flag controlling the visibility of the 'join by phone' buttons. */ - showJoinByPhoneButtons: boolean + showJoinByPhoneButtons: boolean; } /** * This component is displayed before joining a meeting. */ -class Prejoin extends Component { +class Prejoin extends Component { + showDisplayNameField: boolean; + /** * Initializes a new {@code Prejoin} instance. * * @inheritdoc */ - constructor(props) { + constructor(props: IProps) { super(props); this.state = { @@ -150,12 +153,11 @@ class Prejoin extends Component { this._setName = this._setName.bind(this); this._onJoinConferenceWithoutAudioKeyPress = this._onJoinConferenceWithoutAudioKeyPress.bind(this); this._showDialogKeyPress = this._showDialogKeyPress.bind(this); - this._onJoinKeyPress = this._onJoinKeyPress.bind(this); this._getExtraJoinButtons = this._getExtraJoinButtons.bind(this); + this._onInputKeyPress = this._onInputKeyPress.bind(this); this.showDisplayNameField = props.canEditDisplayName || props.showErrorOnJoin; } - _onJoinButtonClick: () => void; /** * Handler for the join button. @@ -170,24 +172,6 @@ class Prejoin extends Component { this.props.joinConference(); } - _onJoinKeyPress: (Object) => void; - - /** - * KeyPress handler for accessibility. - * - * @param {Object} e - The key event to handle. - * - * @returns {void} - */ - _onJoinKeyPress(e) { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - this._onJoinButtonClick(); - } - } - - _onDropdownClose: () => void; - /** * Closes the dropdown. * @@ -199,38 +183,32 @@ class Prejoin extends Component { }); } - _onOptionsClick: () => void; - /** * Displays the join by phone buttons dropdown. * * @param {Object} e - The synthetic event. * @returns {void} */ - _onOptionsClick(e) { - e.stopPropagation(); + _onOptionsClick(e?: React.KeyboardEvent | React.MouseEvent | undefined) { + e?.stopPropagation(); this.setState({ showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons }); } - _setName: () => void; - /** * Sets the guest participant name. * * @param {string} displayName - Participant name. * @returns {void} */ - _setName(displayName) { + _setName(displayName: string) { this.props.updateSettings({ displayName }); } - _closeDialog: () => void; - /** * Closes the join by phone dialog. * @@ -240,8 +218,6 @@ class Prejoin extends Component { this.props.setJoinByPhoneDialogVisiblity(false); } - _showDialog: () => void; - /** * Displays the dialog for joining a meeting by phone. * @@ -252,8 +228,6 @@ class Prejoin extends Component { this._onDropdownClose(); } - _showDialogKeyPress: (Object) => void; - /** * KeyPress handler for accessibility. * @@ -261,15 +235,13 @@ class Prejoin extends Component { * * @returns {void} */ - _showDialogKeyPress(e) { + _showDialogKeyPress(e: React.KeyboardEvent) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._showDialog(); } } - _onJoinConferenceWithoutAudioKeyPress: (Object) => void; - /** * KeyPress handler for accessibility. * @@ -277,7 +249,7 @@ class Prejoin extends Component { * * @returns {void} */ - _onJoinConferenceWithoutAudioKeyPress(e) { + _onJoinConferenceWithoutAudioKeyPress(e: React.KeyboardEvent) { if (this.props.joinConferenceWithoutAudio && (e.key === ' ' || e.key === 'Enter')) { @@ -286,8 +258,6 @@ class Prejoin extends Component { } } - _getExtraJoinButtons: () => Object; - /** * Gets the list of extra join buttons. * @@ -320,6 +290,20 @@ class Prejoin extends Component { }; } + /** + * Handle keypress on input. + * + * @param {KeyboardEvent} e - Keyboard event. + * @returns {void} + */ + _onInputKeyPress(e: React.KeyboardEvent) { + const { joinConference } = this.props; + + if (e.key === 'Enter') { + joinConference(); + } + } + /** * Implements React's {@link Component#render()}. * @@ -330,7 +314,6 @@ class Prejoin extends Component { const { deviceStatusVisible, hasJoinByPhoneButton, - joinConference, joinConferenceWithoutAudio, joiningInProgress, name, @@ -343,16 +326,16 @@ class Prejoin extends Component { t, videoTrack } = this.props; - const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, - _onOptionsClick, _setName } = this; + const { _closeDialog, _onDropdownClose, _onJoinButtonClick, + _onOptionsClick, _setName, _onInputKeyPress } = this; const extraJoinButtons = this._getExtraJoinButtons(); - let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: Object) => + let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: any) => !(prejoinConfig?.hideExtraJoinButtons || []).includes(val.key) ); if (!hasJoinByPhoneButton) { - extraButtonsToRender = extraButtonsToRender.filter((btn: Object) => btn.key !== 'by-phone'); + extraButtonsToRender = extraButtonsToRender.filter((btn: any) => btn.key !== 'by-phone'); } const hasExtraJoinButtons = Boolean(extraButtonsToRender.length); const { showJoinByPhoneButtons } = this.state; @@ -366,14 +349,14 @@ class Prejoin extends Component {
- {this.showDisplayNameField ? ( ) : ( @@ -394,7 +377,7 @@ class Prejoin extends Component {
- {extraButtonsToRender.map(({ key, ...rest }: Object) => ( + {extraButtonsToRender.map(({ key, ...rest }) => (