You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SettingsDialog.tsx 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import { withStyles } from '@mui/styles';
  2. import React, { Component } from 'react';
  3. import { IReduxState } from '../../../app/types';
  4. import {
  5. IconBell,
  6. IconCalendar,
  7. IconGear,
  8. IconImage,
  9. IconModerator,
  10. IconShortcuts,
  11. IconUser,
  12. IconVideo,
  13. IconVolumeUp
  14. } from '../../../base/icons/svg';
  15. import { connect } from '../../../base/redux/functions';
  16. import DialogWithTabs, { IDialogTab } from '../../../base/ui/components/web/DialogWithTabs';
  17. import { isCalendarEnabled } from '../../../calendar-sync/functions.web';
  18. import { submitAudioDeviceSelectionTab, submitVideoDeviceSelectionTab } from '../../../device-selection/actions.web';
  19. import AudioDevicesSelection from '../../../device-selection/components/AudioDevicesSelection';
  20. import VideoDeviceSelection from '../../../device-selection/components/VideoDeviceSelection';
  21. import {
  22. getAudioDeviceSelectionDialogProps,
  23. getVideoDeviceSelectionDialogProps
  24. } from '../../../device-selection/functions.web';
  25. import { checkBlurSupport } from '../../../virtual-background/functions';
  26. import {
  27. submitModeratorTab,
  28. submitMoreTab,
  29. submitNotificationsTab,
  30. submitProfileTab,
  31. submitShortcutsTab,
  32. submitVirtualBackgroundTab
  33. } from '../../actions';
  34. import { SETTINGS_TABS } from '../../constants';
  35. import {
  36. getModeratorTabProps,
  37. getMoreTabProps,
  38. getNotificationsMap,
  39. getNotificationsTabProps,
  40. getProfileTabProps,
  41. getShortcutsTabProps,
  42. getVirtualBackgroundTabProps
  43. } from '../../functions';
  44. import CalendarTab from './CalendarTab';
  45. import ModeratorTab from './ModeratorTab';
  46. import MoreTab from './MoreTab';
  47. import NotificationsTab from './NotificationsTab';
  48. import ProfileTab from './ProfileTab';
  49. import ShortcutsTab from './ShortcutsTab';
  50. import VirtualBackgroundTab from './VirtualBackgroundTab';
  51. /**
  52. * The type of the React {@code Component} props of
  53. * {@link ConnectedSettingsDialog}.
  54. */
  55. interface IProps {
  56. /**
  57. * Information about the tabs to be rendered.
  58. */
  59. _tabs: IDialogTab<any>[];
  60. /**
  61. * An object containing the CSS classes.
  62. */
  63. classes: Object;
  64. /**
  65. * Which settings tab should be initially displayed. If not defined then
  66. * the first tab will be displayed.
  67. */
  68. defaultTab: string;
  69. /**
  70. * Invoked to save changed settings.
  71. */
  72. dispatch: Function;
  73. /**
  74. * Indicates whether the device selection dialog is displayed on the
  75. * welcome page or not.
  76. */
  77. isDisplayedOnWelcomePage: boolean;
  78. }
  79. /**
  80. * Creates the styles for the component.
  81. *
  82. * @param {Object} theme - The current UI theme.
  83. *
  84. * @returns {Object}
  85. */
  86. const styles = () => {
  87. return {
  88. settingsDialog: {
  89. display: 'flex',
  90. width: '100%'
  91. }
  92. };
  93. };
  94. /**
  95. * A React {@code Component} for displaying a dialog to modify local settings
  96. * and conference-wide (moderator) settings. This version is connected to
  97. * redux to get the current settings.
  98. *
  99. * @augments Component
  100. */
  101. class SettingsDialog extends Component<IProps> {
  102. /**
  103. * Implements React's {@link Component#render()}.
  104. *
  105. * @inheritdoc
  106. * @returns {ReactElement}
  107. */
  108. render() {
  109. const { _tabs, defaultTab, dispatch } = this.props;
  110. const correctDefaultTab = _tabs.find(tab => tab.name === defaultTab)?.name;
  111. const tabs = _tabs.map(tab => {
  112. return {
  113. ...tab,
  114. submit: (...args: any) => tab.submit
  115. && dispatch(tab.submit(...args))
  116. };
  117. });
  118. return (
  119. <DialogWithTabs
  120. className = 'settings-dialog'
  121. defaultTab = { correctDefaultTab }
  122. tabs = { tabs }
  123. titleKey = 'settings.title' />
  124. );
  125. }
  126. }
  127. /**
  128. * Maps (parts of) the Redux state to the associated props for the
  129. * {@code ConnectedSettingsDialog} component.
  130. *
  131. * @param {Object} state - The Redux state.
  132. * @param {Object} ownProps - The props passed to the component.
  133. * @private
  134. * @returns {{
  135. * tabs: Array<Object>
  136. * }}
  137. */
  138. function _mapStateToProps(state: IReduxState, ownProps: any) {
  139. const { classes, isDisplayedOnWelcomePage } = ownProps;
  140. const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
  141. // The settings sections to display.
  142. const showDeviceSettings = configuredTabs.includes('devices');
  143. const moreTabProps = getMoreTabProps(state);
  144. const moderatorTabProps = getModeratorTabProps(state);
  145. const { showModeratorSettings } = moderatorTabProps;
  146. const showMoreTab = configuredTabs.includes('more');
  147. const showProfileSettings
  148. = configuredTabs.includes('profile') && !state['features/base/config'].disableProfile;
  149. const showCalendarSettings
  150. = configuredTabs.includes('calendar') && isCalendarEnabled(state);
  151. const showSoundsSettings = configuredTabs.includes('sounds');
  152. const enabledNotifications = getNotificationsMap(state);
  153. const showNotificationsSettings = Object.keys(enabledNotifications).length > 0;
  154. const virtualBackgroundSupported = checkBlurSupport();
  155. const tabs: IDialogTab<any>[] = [];
  156. if (showDeviceSettings) {
  157. tabs.push({
  158. name: SETTINGS_TABS.AUDIO,
  159. component: AudioDevicesSelection,
  160. labelKey: 'settings.audio',
  161. props: getAudioDeviceSelectionDialogProps(state, isDisplayedOnWelcomePage),
  162. propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getAudioDeviceSelectionDialogProps>) => {
  163. // Ensure the device selection tab gets updated when new devices
  164. // are found by taking the new props and only preserving the
  165. // current user selected devices. If this were not done, the
  166. // tab would keep using a copy of the initial props it received,
  167. // leaving the device list to become stale.
  168. return {
  169. ...newProps,
  170. noiseSuppressionEnabled: tabState.noiseSuppressionEnabled,
  171. selectedAudioInputId: tabState.selectedAudioInputId,
  172. selectedAudioOutputId: tabState.selectedAudioOutputId
  173. };
  174. },
  175. className: `settings-pane ${classes.settingsDialog}`,
  176. submit: (newState: any) => submitAudioDeviceSelectionTab(newState, isDisplayedOnWelcomePage),
  177. icon: IconVolumeUp
  178. });
  179. tabs.push({
  180. name: SETTINGS_TABS.VIDEO,
  181. component: VideoDeviceSelection,
  182. labelKey: 'settings.video',
  183. props: getVideoDeviceSelectionDialogProps(state, isDisplayedOnWelcomePage),
  184. propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getVideoDeviceSelectionDialogProps>) => {
  185. // Ensure the device selection tab gets updated when new devices
  186. // are found by taking the new props and only preserving the
  187. // current user selected devices. If this were not done, the
  188. // tab would keep using a copy of the initial props it received,
  189. // leaving the device list to become stale.
  190. return {
  191. ...newProps,
  192. currentFramerate: tabState?.currentFramerate,
  193. localFlipX: tabState.localFlipX,
  194. selectedVideoInputId: tabState.selectedVideoInputId
  195. };
  196. },
  197. className: `settings-pane ${classes.settingsDialog}`,
  198. submit: (newState: any) => submitVideoDeviceSelectionTab(newState, isDisplayedOnWelcomePage),
  199. icon: IconVideo
  200. });
  201. }
  202. if (virtualBackgroundSupported) {
  203. tabs.push({
  204. name: SETTINGS_TABS.VIRTUAL_BACKGROUND,
  205. component: VirtualBackgroundTab,
  206. labelKey: 'virtualBackground.title',
  207. props: getVirtualBackgroundTabProps(state),
  208. className: `settings-pane ${classes.settingsDialog}`,
  209. submit: (newState: any) => submitVirtualBackgroundTab(newState),
  210. cancel: () => {
  211. const { _virtualBackground } = getVirtualBackgroundTabProps(state);
  212. return submitVirtualBackgroundTab({
  213. options: {
  214. backgroundType: _virtualBackground.backgroundType,
  215. enabled: _virtualBackground.backgroundEffectEnabled,
  216. url: _virtualBackground.virtualSource,
  217. selectedThumbnail: _virtualBackground.selectedThumbnail,
  218. blurValue: _virtualBackground.blurValue
  219. }
  220. }, true);
  221. },
  222. icon: IconImage
  223. });
  224. }
  225. if (showSoundsSettings || showNotificationsSettings) {
  226. tabs.push({
  227. name: SETTINGS_TABS.NOTIFICATIONS,
  228. component: NotificationsTab,
  229. labelKey: 'settings.notifications',
  230. propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getNotificationsTabProps>) => {
  231. return {
  232. ...newProps,
  233. enabledNotifications: tabState?.enabledNotifications || {}
  234. };
  235. },
  236. props: getNotificationsTabProps(state, showSoundsSettings),
  237. className: `settings-pane ${classes.settingsDialog}`,
  238. submit: submitNotificationsTab,
  239. icon: IconBell
  240. });
  241. }
  242. if (showModeratorSettings) {
  243. tabs.push({
  244. name: SETTINGS_TABS.MODERATOR,
  245. component: ModeratorTab,
  246. labelKey: 'settings.moderator',
  247. props: moderatorTabProps,
  248. propsUpdateFunction: (tabState: any, newProps: typeof moderatorTabProps) => {
  249. // Updates tab props, keeping users selection
  250. return {
  251. ...newProps,
  252. followMeEnabled: tabState?.followMeEnabled,
  253. startAudioMuted: tabState?.startAudioMuted,
  254. startVideoMuted: tabState?.startVideoMuted,
  255. startReactionsMuted: tabState?.startReactionsMuted
  256. };
  257. },
  258. className: `settings-pane ${classes.settingsDialog}`,
  259. submit: submitModeratorTab,
  260. icon: IconModerator
  261. });
  262. }
  263. if (showProfileSettings) {
  264. tabs.push({
  265. name: SETTINGS_TABS.PROFILE,
  266. component: ProfileTab,
  267. labelKey: 'profile.title',
  268. props: getProfileTabProps(state),
  269. className: `settings-pane ${classes.settingsDialog}`,
  270. submit: submitProfileTab,
  271. icon: IconUser
  272. });
  273. }
  274. if (showCalendarSettings) {
  275. tabs.push({
  276. name: SETTINGS_TABS.CALENDAR,
  277. component: CalendarTab,
  278. labelKey: 'settings.calendar.title',
  279. className: `settings-pane ${classes.settingsDialog}`,
  280. icon: IconCalendar
  281. });
  282. }
  283. tabs.push({
  284. name: SETTINGS_TABS.SHORTCUTS,
  285. component: ShortcutsTab,
  286. labelKey: 'settings.shortcuts',
  287. props: getShortcutsTabProps(state, isDisplayedOnWelcomePage),
  288. propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getShortcutsTabProps>) => {
  289. // Updates tab props, keeping users selection
  290. return {
  291. ...newProps,
  292. keyboardShortcutsEnabled: tabState?.keyboardShortcutsEnabled
  293. };
  294. },
  295. className: `settings-pane ${classes.settingsDialog}`,
  296. submit: submitShortcutsTab,
  297. icon: IconShortcuts
  298. });
  299. if (showMoreTab) {
  300. tabs.push({
  301. name: SETTINGS_TABS.MORE,
  302. component: MoreTab,
  303. labelKey: 'settings.more',
  304. props: moreTabProps,
  305. propsUpdateFunction: (tabState: any, newProps: typeof moreTabProps) => {
  306. // Updates tab props, keeping users selection
  307. return {
  308. ...newProps,
  309. currentLanguage: tabState?.currentLanguage,
  310. hideSelfView: tabState?.hideSelfView,
  311. showPrejoinPage: tabState?.showPrejoinPage,
  312. maxStageParticipants: tabState?.maxStageParticipants
  313. };
  314. },
  315. className: `settings-pane ${classes.settingsDialog}`,
  316. submit: submitMoreTab,
  317. icon: IconGear
  318. });
  319. }
  320. return { _tabs: tabs };
  321. }
  322. export default withStyles(styles)(connect(_mapStateToProps)(SettingsDialog));