Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

SettingsDialog.tsx 15KB

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