feat(profile-tab) Update Profile tab in Settings Dialog (#12992)

Implement redesign
Move some options from More to this tab
This commit is contained in:
Robert Pintilii 2023-03-03 10:42:59 +02:00 committed by GitHub
parent 17ed45799c
commit b1a71d55d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 193 additions and 199 deletions

View File

@ -886,9 +886,9 @@
}, },
"profile": { "profile": {
"avatar": "avatar", "avatar": "avatar",
"setDisplayNameLabel": "Set your display name", "setDisplayNameLabel": "Name",
"setEmailInput": "Enter email", "setEmailInput": "Enter email",
"setEmailLabel": "Set your gravatar email", "setEmailLabel": "Gravatar email",
"title": "Profile" "title": "Profile"
}, },
"raisedHand": "Would like to speak", "raisedHand": "Would like to speak",

View File

@ -107,20 +107,12 @@ export function submitMoreTab(newState: any) {
})); }));
} }
if (newState.currentLanguage !== currentState.currentLanguage) {
i18next.changeLanguage(newState.currentLanguage);
}
if (newState.currentFramerate !== currentState.currentFramerate) { if (newState.currentFramerate !== currentState.currentFramerate) {
const frameRate = parseInt(newState.currentFramerate, 10); const frameRate = parseInt(newState.currentFramerate, 10);
dispatch(setScreenshareFramerate(frameRate)); dispatch(setScreenshareFramerate(frameRate));
} }
if (newState.hideSelfView !== currentState.hideSelfView) {
dispatch(updateSettings({ disableSelfView: newState.hideSelfView }));
}
if (newState.maxStageParticipants !== currentState.maxStageParticipants) { if (newState.maxStageParticipants !== currentState.maxStageParticipants) {
dispatch(updateSettings({ maxStageParticipants: Number(newState.maxStageParticipants) })); dispatch(updateSettings({ maxStageParticipants: Number(newState.maxStageParticipants) }));
} }
@ -174,6 +166,14 @@ export function submitProfileTab(newState: any) {
if (newState.email !== currentState.email) { if (newState.email !== currentState.email) {
APP.conference.changeLocalEmail(newState.email); APP.conference.changeLocalEmail(newState.email);
} }
if (newState.hideSelfView !== currentState.hideSelfView) {
dispatch(updateSettings({ disableSelfView: newState.hideSelfView }));
}
if (newState.currentLanguage !== currentState.currentLanguage) {
i18next.changeLanguage(newState.currentLanguage);
}
}; };
} }

View File

@ -22,22 +22,11 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
*/ */
currentFramerate: string; currentFramerate: string;
/**
* The currently selected language to display in the language select
* dropdown.
*/
currentLanguage: string;
/** /**
* All available desktop capture frame rates. * All available desktop capture frame rates.
*/ */
desktopShareFramerates: Array<number>; desktopShareFramerates: Array<number>;
/**
* Whether to show hide self view setting.
*/
disableHideSelfView: boolean;
/** /**
* The types of enabled notifications that can be configured and their specific visibility. * The types of enabled notifications that can be configured and their specific visibility.
*/ */
@ -48,26 +37,11 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
*/ */
followMeActive: boolean; followMeActive: boolean;
/**
* Whether or not to hide self-view screen.
*/
hideSelfView: boolean;
/**
* All available languages to display in the language select dropdown.
*/
languages: Array<string>;
/** /**
* The number of max participants to display on stage. * The number of max participants to display on stage.
*/ */
maxStageParticipants: number; maxStageParticipants: number;
/**
* Whether or not to display the language select dropdown.
*/
showLanguageSettings: boolean;
/** /**
* Whether or not to display moderator-only settings. * Whether or not to display moderator-only settings.
*/ */
@ -116,11 +90,9 @@ class MoreTab extends AbstractDialogTab<Props, any> {
// Bind event handler so it is only bound once for every instance. // Bind event handler so it is only bound once for every instance.
this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this); this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this);
this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this);
this._onEnabledNotificationsChanged = this._onEnabledNotificationsChanged.bind(this); this._onEnabledNotificationsChanged = this._onEnabledNotificationsChanged.bind(this);
this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this); this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this);
this._onKeyboardShortcutEnableChanged = this._onKeyboardShortcutEnableChanged.bind(this); this._onKeyboardShortcutEnableChanged = this._onKeyboardShortcutEnableChanged.bind(this);
this._onHideSelfViewChanged = this._onHideSelfViewChanged.bind(this);
this._renderMaxStageParticipantsSelect = this._renderMaxStageParticipantsSelect.bind(this); this._renderMaxStageParticipantsSelect = this._renderMaxStageParticipantsSelect.bind(this);
this._onMaxStageParticipantsSelect = this._onMaxStageParticipantsSelect.bind(this); this._onMaxStageParticipantsSelect = this._onMaxStageParticipantsSelect.bind(this);
} }
@ -159,19 +131,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
super._onChange({ currentFramerate: frameRate }); super._onChange({ currentFramerate: frameRate });
} }
/**
* Callback invoked to select a language from select dropdown.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onLanguageItemSelect(e: React.ChangeEvent<HTMLSelectElement>) {
const language = e.target.value;
super._onChange({ currentLanguage: language });
}
/** /**
* Callback invoked to select if the lobby * Callback invoked to select if the lobby
* should be shown. * should be shown.
@ -215,17 +174,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
super._onChange({ keyboardShortcutEnable: checked }); super._onChange({ keyboardShortcutEnable: checked });
} }
/**
* Callback invoked to select if hide self view should be enabled.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onHideSelfViewChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) {
super._onChange({ hideSelfView: checked });
}
/** /**
* Callback invoked to select a max number of stage participants from the select dropdown. * Callback invoked to select a max number of stage participants from the select dropdown.
* *
@ -296,67 +244,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
); );
} }
/**
* Returns the React Element for self view setting.
*
* @private
* @returns {ReactElement}
*/
_renderSelfViewCheckbox() {
const { hideSelfView, t } = this.props;
return (
<div
className = 'settings-sub-pane-element'
key = 'selfview'>
<span className = 'checkbox-label'>
{ t('settings.selfView') }
</span>
<Checkbox
checked = { hideSelfView }
label = { t('videothumbnail.hideSelfView') }
name = 'hide-self-view'
onChange = { this._onHideSelfViewChanged } />
</div>
);
}
/**
* Returns the menu item for changing displayed language.
*
* @private
* @returns {ReactElement}
*/
_renderLanguageSelect() {
const {
currentLanguage,
languages,
t
} = this.props;
const languageItems
= languages.map((language: string) => {
return {
value: language,
label: t(`languages:${language}`)
};
});
return (
<div
className = 'settings-sub-pane-element'
key = 'language'>
<div className = 'dropdown-menu'>
<Select
label = { t('settings.language') }
onChange = { this._onLanguageItemSelect }
options = { languageItems }
value = { currentLanguage } />
</div>
</div>
);
}
/** /**
* Returns the React Element for modifying prejoin screen settings. * Returns the React Element for modifying prejoin screen settings.
* *
@ -454,13 +341,10 @@ class MoreTab extends AbstractDialogTab<Props, any> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderSettingsRight() { _renderSettingsRight() {
const { showLanguageSettings } = this.props;
return ( return (
<div <div
className = 'settings-sub-pane right' className = 'settings-sub-pane right'
key = 'settings-sub-pane-right'> key = 'settings-sub-pane-right'>
{ showLanguageSettings && this._renderLanguageSelect() }
{ this._renderFramerateSelect() } { this._renderFramerateSelect() }
{ this._renderMaxStageParticipantsSelect() } { this._renderMaxStageParticipantsSelect() }
</div> </div>
@ -473,7 +357,7 @@ class MoreTab extends AbstractDialogTab<Props, any> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderSettingsLeft() { _renderSettingsLeft() {
const { disableHideSelfView, showNotificationsSettings, showPrejoinSettings } = this.props; const { showNotificationsSettings, showPrejoinSettings } = this.props;
return ( return (
<div <div
@ -482,7 +366,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
{ showPrejoinSettings && this._renderPrejoinScreenSettings() } { showPrejoinSettings && this._renderPrejoinScreenSettings() }
{ showNotificationsSettings && this._renderNotificationsSettings() } { showNotificationsSettings && this._renderNotificationsSettings() }
{ this._renderKeyboardShortcutCheckbox() } { this._renderKeyboardShortcutCheckbox() }
{ !disableHideSelfView && this._renderSelfViewCheckbox() }
</div> </div>
); );
} }

View File

@ -1,4 +1,5 @@
/* eslint-disable lines-around-comment */ import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import React from 'react'; import React from 'react';
import { WithTranslation } from 'react-i18next'; import { WithTranslation } from 'react-i18next';
@ -6,20 +7,22 @@ import { WithTranslation } from 'react-i18next';
import UIEvents from '../../../../../service/UI/UIEvents'; import UIEvents from '../../../../../service/UI/UIEvents';
import { createProfilePanelButtonEvent } from '../../../analytics/AnalyticsEvents'; import { createProfilePanelButtonEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions'; import { sendAnalytics } from '../../../analytics/functions';
// eslint-disable-next-line lines-around-comment
// @ts-ignore // @ts-ignore
import { AbstractDialogTab } from '../../../base/dialog'; import Avatar from '../../../base/avatar/components/Avatar';
// @ts-ignore import AbstractDialogTab, {
import type { Props as AbstractDialogTabProps } from '../../../base/dialog'; IProps as AbstractDialogTabProps } from '../../../base/dialog/components/web/AbstractDialogTab';
import { translate } from '../../../base/i18n/functions'; import { translate } from '../../../base/i18n/functions';
import Button from '../../../base/ui/components/web/Button'; import Button from '../../../base/ui/components/web/Button';
import Checkbox from '../../../base/ui/components/web/Checkbox';
import Input from '../../../base/ui/components/web/Input'; import Input from '../../../base/ui/components/web/Input';
import Select from '../../../base/ui/components/web/Select';
import { openLogoutDialog } from '../../actions'; import { openLogoutDialog } from '../../actions';
/* eslint-enable lines-around-comment */
/** /**
* The type of the React {@code Component} props of {@link ProfileTab}. * The type of the React {@code Component} props of {@link ProfileTab}.
*/ */
export type Props = AbstractDialogTabProps & WithTranslation & { export interface IProps extends AbstractDialogTabProps, WithTranslation {
/** /**
* Whether or not server-side authentication is available. * Whether or not server-side authentication is available.
@ -31,6 +34,22 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
*/ */
authLogin: string; authLogin: string;
/**
* CSS classes object.
*/
classes: any;
/**
* The currently selected language to display in the language select
* dropdown.
*/
currentLanguage: string;
/**
* Whether to show hide self view setting.
*/
disableHideSelfView: boolean;
/** /**
* The display name to display for the local participant. * The display name to display for the local participant.
*/ */
@ -46,15 +65,52 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
*/ */
hideEmailInSettings?: boolean; hideEmailInSettings?: boolean;
/**
* Whether or not to hide self-view screen.
*/
hideSelfView: boolean;
/**
* The id of the local participant.
*/
id: string;
/**
* All available languages to display in the language select dropdown.
*/
languages: Array<string>;
/** /**
* If the display name is read only. * If the display name is read only.
*/ */
readOnlyName: boolean; readOnlyName: boolean;
/** /**
* Invoked to obtain translated strings. * Whether or not to display the language select dropdown.
*/ */
t: Function; showLanguageSettings: boolean;
}
const styles = (theme: Theme) => {
return {
container: {
display: 'flex',
flexDirection: 'column' as const,
width: '100%',
padding: '0 2px'
},
avatarContainer: {
display: 'flex',
width: '100%',
justifyContent: 'center',
marginBottom: theme.spacing(4)
},
bottomMargin: {
marginBottom: theme.spacing(4)
}
};
}; };
/** /**
@ -62,7 +118,7 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
* *
* @augments Component * @augments Component
*/ */
class ProfileTab extends AbstractDialogTab<Props> { class ProfileTab extends AbstractDialogTab<IProps, any> {
static defaultProps = { static defaultProps = {
displayName: '', displayName: '',
email: '' email: ''
@ -71,16 +127,18 @@ class ProfileTab extends AbstractDialogTab<Props> {
/** /**
* Initializes a new {@code ConnectedSettingsDialog} instance. * Initializes a new {@code ConnectedSettingsDialog} instance.
* *
* @param {Props} props - The React {@code Component} props to initialize * @param {IProps} props - The React {@code Component} props to initialize
* the new {@code ConnectedSettingsDialog} instance with. * the new {@code ConnectedSettingsDialog} instance with.
*/ */
constructor(props: Props) { constructor(props: IProps) {
super(props); super(props);
// 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._onAuthToggle = this._onAuthToggle.bind(this); this._onAuthToggle = this._onAuthToggle.bind(this);
this._onDisplayNameChange = this._onDisplayNameChange.bind(this); this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
this._onEmailChange = this._onEmailChange.bind(this); this._onEmailChange = this._onEmailChange.bind(this);
this._onHideSelfViewChanged = this._onHideSelfViewChanged.bind(this);
this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this);
} }
/** /**
@ -105,6 +163,62 @@ class ProfileTab extends AbstractDialogTab<Props> {
super._onChange({ email: value }); super._onChange({ email: value });
} }
/**
* Callback invoked to select if hide self view should be enabled.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onHideSelfViewChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) {
super._onChange({ hideSelfView: checked });
}
/**
* Callback invoked to select a language from select dropdown.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onLanguageItemSelect(e: React.ChangeEvent<HTMLSelectElement>) {
const language = e.target.value;
super._onChange({ currentLanguage: language });
}
/**
* Returns the menu item for changing displayed language.
*
* @private
* @returns {ReactElement}
*/
_renderLanguageSelect() {
const {
classes,
currentLanguage,
languages,
t
} = this.props;
const languageItems
= languages.map((language: string) => {
return {
value: language,
label: t(`languages:${language}`)
};
});
return (
<Select
className = { classes.bottomMargin }
label = { t('settings.language') }
onChange = { this._onLanguageItemSelect }
options = { languageItems }
value = { currentLanguage } />
);
}
/** /**
* Implements React's {@link Component#render()}. * Implements React's {@link Component#render()}.
* *
@ -114,38 +228,55 @@ class ProfileTab extends AbstractDialogTab<Props> {
render() { render() {
const { const {
authEnabled, authEnabled,
classes,
disableHideSelfView,
displayName, displayName,
email, email,
hideEmailInSettings, hideEmailInSettings,
hideSelfView,
id,
readOnlyName, readOnlyName,
t // @ts-ignore showLanguageSettings,
t
} = this.props; } = this.props;
return ( return (
<div> <div className = { classes.container } >
<div className = 'profile-edit'> <div className = { classes.avatarContainer }>
<div className = 'profile-edit-field'> <Avatar
<Input participantId = { id }
disabled = { readOnlyName } size = { 60 } />
id = 'setDisplayName'
label = { t('profile.setDisplayNameLabel') }
name = 'name'
onChange = { this._onDisplayNameChange }
placeholder = { t('settings.name') }
type = 'text'
value = { displayName } />
</div>
{!hideEmailInSettings && <div className = 'profile-edit-field'>
<Input
id = 'setEmail'
label = { t('profile.setEmailLabel') }
name = 'email'
onChange = { this._onEmailChange }
placeholder = { t('profile.setEmailInput') }
type = 'text'
value = { email } />
</div>}
</div> </div>
<Input
className = { classes.bottomMargin }
disabled = { readOnlyName }
id = 'setDisplayName'
label = { t('profile.setDisplayNameLabel') }
name = 'name'
onChange = { this._onDisplayNameChange }
placeholder = { t('settings.name') }
type = 'text'
value = { displayName } />
{!hideEmailInSettings && <div className = 'profile-edit-field'>
<Input
className = { classes.bottomMargin }
id = 'setEmail'
label = { t('profile.setEmailLabel') }
name = 'email'
onChange = { this._onEmailChange }
placeholder = { t('profile.setEmailInput') }
type = 'text'
value = { email } />
</div>}
{!disableHideSelfView && (
<Checkbox
checked = { hideSelfView }
className = { classes.bottomMargin }
label = { t('videothumbnail.hideSelfView') }
name = 'hide-self-view'
onChange = { this._onHideSelfViewChanged } />
)}
{showLanguageSettings && this._renderLanguageSelect()}
{ authEnabled && this._renderAuth() } { authEnabled && this._renderAuth() }
</div> </div>
); );
@ -159,7 +290,6 @@ class ProfileTab extends AbstractDialogTab<Props> {
* @returns {void} * @returns {void}
*/ */
_onAuthToggle() { _onAuthToggle() {
// @ts-ignore
if (this.props.authLogin) { if (this.props.authLogin) {
sendAnalytics(createProfilePanelButtonEvent('logout.button')); sendAnalytics(createProfilePanelButtonEvent('logout.button'));
@ -183,8 +313,6 @@ class ProfileTab extends AbstractDialogTab<Props> {
const { const {
authLogin, authLogin,
t t
// @ts-ignore
} = this.props; } = this.props;
return ( return (
@ -206,5 +334,4 @@ class ProfileTab extends AbstractDialogTab<Props> {
} }
} }
// @ts-ignore export default withStyles(styles)(translate(ProfileTab));
export default translate(ProfileTab);

View File

@ -84,10 +84,6 @@ const styles = (theme: Theme) => {
display: 'flex', display: 'flex',
width: '100%', width: '100%',
'&.profile-pane': {
flexDirection: 'column'
},
'& .auth-name': { '& .auth-name': {
marginBottom: theme.spacing(1) marginBottom: theme.spacing(1)
}, },
@ -130,19 +126,6 @@ const styles = (theme: Theme) => {
width: '100%' width: '100%'
}, },
'& .profile-edit': {
display: 'flex',
width: '100%',
padding: '0 2px',
boxSizing: 'border-box'
},
'& .profile-edit-field': {
flex: 0.5,
marginRight: '20px',
marginTop: theme.spacing(3)
},
'& .settings-sub-pane': { '& .settings-sub-pane': {
flex: 1 flex: 1
}, },
@ -303,7 +286,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
component: ProfileTab, component: ProfileTab,
labelKey: 'profile.title', labelKey: 'profile.title',
props: getProfileTabProps(state), props: getProfileTabProps(state),
className: `settings-pane ${classes.settingsDialog} profile-pane`, className: `settings-pane ${classes.settingsDialog}`,
submit: submitProfileTab, submit: submitProfileTab,
icon: IconUser icon: IconUser
}); });

View File

@ -116,22 +116,12 @@ export function getNotificationsMap(stateful: IStateful) {
export function getMoreTabProps(stateful: IStateful) { export function getMoreTabProps(stateful: IStateful) {
const state = toState(stateful); const state = toState(stateful);
const framerate = state['features/screen-share'].captureFrameRate ?? SS_DEFAULT_FRAME_RATE; const framerate = state['features/screen-share'].captureFrameRate ?? SS_DEFAULT_FRAME_RATE;
const language = i18next.language || DEFAULT_LANGUAGE;
const configuredTabs: string[] = interfaceConfig.SETTINGS_SECTIONS || [];
const enabledNotifications = getNotificationsMap(stateful); const enabledNotifications = getNotificationsMap(stateful);
const stageFilmstripEnabled = isStageFilmstripEnabled(state); const stageFilmstripEnabled = isStageFilmstripEnabled(state);
// when self view is controlled by the config we hide the settings
const { disableSelfView, disableSelfViewSettings } = state['features/base/config'];
return { return {
currentFramerate: framerate, currentFramerate: framerate,
currentLanguage: language,
desktopShareFramerates: SS_SUPPORTED_FRAMERATES, desktopShareFramerates: SS_SUPPORTED_FRAMERATES,
disableHideSelfView: disableSelfViewSettings || disableSelfView,
hideSelfView: getHideSelfView(state),
languages: LANGUAGES,
showLanguageSettings: configuredTabs.includes('language'),
enabledNotifications, enabledNotifications,
showNotificationsSettings: Object.keys(enabledNotifications).length > 0, showNotificationsSettings: Object.keys(enabledNotifications).length > 0,
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin, showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
@ -207,14 +197,25 @@ export function getProfileTabProps(stateful: IStateful) {
} = state['features/base/conference']; } = state['features/base/conference'];
const { hideEmailInSettings } = state['features/base/config']; const { hideEmailInSettings } = state['features/base/config'];
const localParticipant = getLocalParticipant(state); const localParticipant = getLocalParticipant(state);
const language = i18next.language || DEFAULT_LANGUAGE;
const configuredTabs: string[] = interfaceConfig.SETTINGS_SECTIONS || [];
// when self view is controlled by the config we hide the settings
const { disableSelfView, disableSelfViewSettings } = state['features/base/config'];
return { return {
authEnabled: Boolean(conference && authEnabled), authEnabled: Boolean(conference && authEnabled),
authLogin, authLogin,
disableHideSelfView: disableSelfViewSettings || disableSelfView,
currentLanguage: language,
displayName: localParticipant?.name, displayName: localParticipant?.name,
email: localParticipant?.email, email: localParticipant?.email,
hideEmailInSettings,
hideSelfView: getHideSelfView(state),
id: localParticipant?.id,
languages: LANGUAGES,
readOnlyName: isNameReadOnly(state), readOnlyName: isNameReadOnly(state),
hideEmailInSettings showLanguageSettings: configuredTabs.includes('language')
}; };
} }

View File

@ -22,7 +22,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
titleKey: 'notify.selfViewTitle', titleKey: 'notify.selfViewTitle',
customActionNameKey: [ 'settings.title' ], customActionNameKey: [ 'settings.title' ],
customActionHandler: [ () => customActionHandler: [ () =>
dispatch(openSettingsDialog(SETTINGS_TABS.MORE)) dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE))
] ]
}, NOTIFICATION_TIMEOUT_TYPE.STICKY)); }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
} }

View File

@ -92,7 +92,7 @@ const LanguageSelectorDialog = ({
}, [ _language ]); }, [ _language ]);
const onSourceLanguageClick = useCallback(() => { const onSourceLanguageClick = useCallback(() => {
dispatch(openSettingsDialog(SETTINGS_TABS.MORE, false)); dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE, false));
}, []); }, []);
return ( return (