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 13KB

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