import { Theme } from '@mui/material'; import { withStyles } from '@mui/styles'; import React, { Component } from 'react'; import { IReduxState } from '../../../app/types'; import { IconBell, IconCalendar, IconGear, IconModerator, IconShortcuts, IconUser, IconVideo, IconVolumeUp } from '../../../base/icons/svg'; import { connect } from '../../../base/redux/functions'; import { withPixelLineHeight } from '../../../base/styles/functions.web'; import DialogWithTabs, { IDialogTab } from '../../../base/ui/components/web/DialogWithTabs'; import { isCalendarEnabled } from '../../../calendar-sync/functions.web'; import { submitAudioDeviceSelectionTab, submitVideoDeviceSelectionTab } from '../../../device-selection/actions.web'; import AudioDevicesSelection from '../../../device-selection/components/AudioDevicesSelection'; import VideoDeviceSelection from '../../../device-selection/components/VideoDeviceSelection'; import { getAudioDeviceSelectionDialogProps, getVideoDeviceSelectionDialogProps } from '../../../device-selection/functions.web'; import { submitModeratorTab, submitMoreTab, submitNotificationsTab, submitProfileTab, submitShortcutsTab } from '../../actions'; import { SETTINGS_TABS } from '../../constants'; import { getModeratorTabProps, getMoreTabProps, getNotificationsMap, getNotificationsTabProps, getProfileTabProps, getShortcutsTabProps } from '../../functions'; // @ts-ignore import CalendarTab from './CalendarTab'; import ModeratorTab from './ModeratorTab'; import MoreTab from './MoreTab'; import NotificationsTab from './NotificationsTab'; import ProfileTab from './ProfileTab'; import ShortcutsTab from './ShortcutsTab'; /** * The type of the React {@code Component} props of * {@link ConnectedSettingsDialog}. */ interface IProps { /** * Information about the tabs to be rendered. */ _tabs: IDialogTab[]; /** * An object containing the CSS classes. */ classes: Object; /** * Which settings tab should be initially displayed. If not defined then * the first tab will be displayed. */ defaultTab: string; /** * Invoked to save changed settings. */ dispatch: Function; /** * Indicates whether the device selection dialog is displayed on the * welcome page or not. */ isDisplayedOnWelcomePage: boolean; } /** * Creates the styles for the component. * * @param {Object} theme - The current UI theme. * * @returns {Object} */ const styles = (theme: Theme) => { return { settingsDialog: { display: 'flex', width: '100%', '& .auth-name': { marginBottom: theme.spacing(1) }, '& .mock-atlaskit-label': { color: '#b8c7e0', fontSize: '12px', fontWeight: 600, lineHeight: 1.33, padding: `20px 0px ${theme.spacing(1)} 0px` }, '& .checkbox-label': { color: theme.palette.text01, ...withPixelLineHeight(theme.typography.bodyShortRegular), marginBottom: theme.spacing(2), display: 'block', marginTop: '20px' }, '& input[type="checkbox"]:checked + svg': { '--checkbox-background-color': '#6492e7', '--checkbox-border-color': '#6492e7' }, '& input[type="checkbox"] + svg + span': { color: '#9FB0CC' }, // @ts-ignore [[ '& .calendar-tab', '& .more-tab', '& .box' ]]: { display: 'flex', justifyContent: 'space-between', width: '100%' }, '& .settings-sub-pane': { flex: 1 }, '& .settings-sub-pane .right': { flex: 1 }, '& .settings-sub-pane .left': { flex: 1 }, '& .settings-sub-pane-element': { textAlign: 'left', flex: 1 }, '& .dropdown-menu': { marginTop: '20px' }, '& .settings-checkbox': { display: 'flex', marginBottom: theme.spacing(3) }, '& .calendar-tab': { alignItems: 'center', flexDirection: 'column', fontSize: '14px', minHeight: '100px', textAlign: 'center', marginTop: '20px' }, '& .calendar-tab-sign-in': { marginTop: '20px' }, '& .sign-out-cta': { marginBottom: '20px' }, '& .sign-out-cta-button': { display: 'flex', justifyContent: 'center' }, '@media only screen and (max-width: 700px)': { '& .more-tab': { flexDirection: 'column' } } } }; }; /** * A React {@code Component} for displaying a dialog to modify local settings * and conference-wide (moderator) settings. This version is connected to * redux to get the current settings. * * @augments Component */ class SettingsDialog extends Component { /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const { _tabs, defaultTab, dispatch } = this.props; const correctDefaultTab = _tabs.find(tab => tab.name === defaultTab)?.name; const tabs = _tabs.map(tab => { return { ...tab, submit: (...args: any) => tab.submit && dispatch(tab.submit(...args)) }; }); return ( ); } } /** * Maps (parts of) the Redux state to the associated props for the * {@code ConnectedSettingsDialog} component. * * @param {Object} state - The Redux state. * @param {Object} ownProps - The props passed to the component. * @private * @returns {{ * tabs: Array * }} */ function _mapStateToProps(state: IReduxState, ownProps: any) { const { classes, isDisplayedOnWelcomePage } = ownProps; const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || []; // The settings sections to display. const showDeviceSettings = configuredTabs.includes('devices'); const moreTabProps = getMoreTabProps(state); const moderatorTabProps = getModeratorTabProps(state); const { showModeratorSettings } = moderatorTabProps; const showMoreTab = configuredTabs.includes('more'); const showProfileSettings = configuredTabs.includes('profile') && !state['features/base/config'].disableProfile; const showCalendarSettings = configuredTabs.includes('calendar') && isCalendarEnabled(state); const showSoundsSettings = configuredTabs.includes('sounds'); const enabledNotifications = getNotificationsMap(state); const showNotificationsSettings = Object.keys(enabledNotifications).length > 0; const tabs: IDialogTab[] = []; if (showDeviceSettings) { tabs.push({ name: SETTINGS_TABS.AUDIO, component: AudioDevicesSelection, labelKey: 'settings.audio', props: getAudioDeviceSelectionDialogProps(state, isDisplayedOnWelcomePage), propsUpdateFunction: (tabState: any, newProps: ReturnType) => { // Ensure the device selection tab gets updated when new devices // are found by taking the new props and only preserving the // current user selected devices. If this were not done, the // tab would keep using a copy of the initial props it received, // leaving the device list to become stale. return { ...newProps, noiseSuppressionEnabled: tabState.noiseSuppressionEnabled, selectedAudioInputId: tabState.selectedAudioInputId, selectedAudioOutputId: tabState.selectedAudioOutputId }; }, className: `settings-pane ${classes.settingsDialog} devices-pane`, submit: (newState: any) => submitAudioDeviceSelectionTab(newState, isDisplayedOnWelcomePage), icon: IconVolumeUp }); tabs.push({ name: SETTINGS_TABS.VIDEO, component: VideoDeviceSelection, labelKey: 'settings.video', props: getVideoDeviceSelectionDialogProps(state, isDisplayedOnWelcomePage), propsUpdateFunction: (tabState: any, newProps: ReturnType) => { // Ensure the device selection tab gets updated when new devices // are found by taking the new props and only preserving the // current user selected devices. If this were not done, the // tab would keep using a copy of the initial props it received, // leaving the device list to become stale. return { ...newProps, currentFramerate: tabState?.currentFramerate, localFlipX: tabState.localFlipX, selectedVideoInputId: tabState.selectedVideoInputId }; }, className: `settings-pane ${classes.settingsDialog} devices-pane`, submit: (newState: any) => submitVideoDeviceSelectionTab(newState, isDisplayedOnWelcomePage), icon: IconVideo }); } if (showSoundsSettings || showNotificationsSettings) { tabs.push({ name: SETTINGS_TABS.NOTIFICATIONS, component: NotificationsTab, labelKey: 'settings.notifications', propsUpdateFunction: (tabState: any, newProps: any) => { return { ...newProps, enabledNotifications: tabState?.enabledNotifications || {} }; }, props: getNotificationsTabProps(state, showSoundsSettings), className: `settings-pane ${classes.settingsDialog}`, submit: submitNotificationsTab, icon: IconBell }); } if (showModeratorSettings) { tabs.push({ name: SETTINGS_TABS.MODERATOR, component: ModeratorTab, labelKey: 'settings.moderator', props: moderatorTabProps, propsUpdateFunction: (tabState: any, newProps: typeof moderatorTabProps) => { // Updates tab props, keeping users selection return { ...newProps, followMeEnabled: tabState?.followMeEnabled, startAudioMuted: tabState?.startAudioMuted, startVideoMuted: tabState?.startVideoMuted, startReactionsMuted: tabState?.startReactionsMuted }; }, className: `settings-pane ${classes.settingsDialog} moderator-pane`, submit: submitModeratorTab, icon: IconModerator }); } if (showProfileSettings) { tabs.push({ name: SETTINGS_TABS.PROFILE, component: ProfileTab, labelKey: 'profile.title', props: getProfileTabProps(state), className: `settings-pane ${classes.settingsDialog} profile-pane`, submit: submitProfileTab, icon: IconUser }); } if (showCalendarSettings) { tabs.push({ name: SETTINGS_TABS.CALENDAR, component: CalendarTab, labelKey: 'settings.calendar.title', className: `settings-pane ${classes.settingsDialog} calendar-pane`, icon: IconCalendar }); } tabs.push({ name: SETTINGS_TABS.SHORTCUTS, component: ShortcutsTab, labelKey: 'settings.shortcuts', props: getShortcutsTabProps(state, isDisplayedOnWelcomePage), propsUpdateFunction: (tabState: any, newProps: any) => { // Updates tab props, keeping users selection return { ...newProps, keyboardShortcutsEnabled: tabState?.keyboardShortcutsEnabled }; }, className: `settings-pane ${classes.settingsDialog}`, submit: submitShortcutsTab, icon: IconShortcuts }); if (showMoreTab) { tabs.push({ name: SETTINGS_TABS.MORE, // @ts-ignore component: MoreTab, labelKey: 'settings.more', props: moreTabProps, propsUpdateFunction: (tabState: any, newProps: typeof moreTabProps) => { // Updates tab props, keeping users selection return { ...newProps, currentLanguage: tabState?.currentLanguage, hideSelfView: tabState?.hideSelfView, showPrejoinPage: tabState?.showPrejoinPage, maxStageParticipants: tabState?.maxStageParticipants }; }, className: `settings-pane ${classes.settingsDialog} more-pane`, submit: submitMoreTab, icon: IconGear }); } return { _tabs: tabs }; } export default withStyles(styles)(connect(_mapStateToProps)(SettingsDialog));