Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Prejoin.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. // @flow
  2. import InlineDialog from '@atlaskit/inline-dialog';
  3. import React, { Component } from 'react';
  4. import { getRoomName } from '../../base/conference';
  5. import { getToolbarButtons } from '../../base/config';
  6. import { translate } from '../../base/i18n';
  7. import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
  8. import { isVideoMutedByUser } from '../../base/media';
  9. import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting';
  10. import { connect } from '../../base/redux';
  11. import { getDisplayName, updateSettings } from '../../base/settings';
  12. import { getLocalJitsiVideoTrack } from '../../base/tracks';
  13. import { isButtonEnabled } from '../../toolbox/functions.web';
  14. import {
  15. joinConference as joinConferenceAction,
  16. joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
  17. setSkipPrejoin as setSkipPrejoinAction,
  18. setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
  19. } from '../actions';
  20. import {
  21. isDeviceStatusVisible,
  22. isDisplayNameRequired,
  23. isJoinByPhoneButtonVisible,
  24. isJoinByPhoneDialogVisible,
  25. isPrejoinSkipped
  26. } from '../functions';
  27. import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
  28. import DeviceStatus from './preview/DeviceStatus';
  29. declare var interfaceConfig: Object;
  30. type Props = {
  31. /**
  32. * Flag signaling if the 'skip prejoin' button is toggled or not.
  33. */
  34. buttonIsToggled: boolean,
  35. /**
  36. * Flag signaling if the device status is visible or not.
  37. */
  38. deviceStatusVisible: boolean,
  39. /**
  40. * If join by phone button should be visible.
  41. */
  42. hasJoinByPhoneButton: boolean,
  43. /**
  44. * Joins the current meeting.
  45. */
  46. joinConference: Function,
  47. /**
  48. * Joins the current meeting without audio.
  49. */
  50. joinConferenceWithoutAudio: Function,
  51. /**
  52. * The name of the user that is about to join.
  53. */
  54. name: string,
  55. /**
  56. * Updates settings.
  57. */
  58. updateSettings: Function,
  59. /**
  60. * The name of the meeting that is about to be joined.
  61. */
  62. roomName: string,
  63. /**
  64. * Sets visibility of the prejoin page for the next sessions.
  65. */
  66. setSkipPrejoin: Function,
  67. /**
  68. * Sets visibility of the 'JoinByPhoneDialog'.
  69. */
  70. setJoinByPhoneDialogVisiblity: Function,
  71. /**
  72. * Indicates whether the avatar should be shown when video is off
  73. */
  74. showAvatar: boolean,
  75. /**
  76. * Flag signaling the visibility of camera preview.
  77. */
  78. showCameraPreview: boolean,
  79. /**
  80. * If should show an error when joining without a name.
  81. */
  82. showErrorOnJoin: boolean,
  83. /**
  84. * Flag signaling the visibility of join label, input and buttons
  85. */
  86. showJoinActions: boolean,
  87. /**
  88. * Flag signaling the visibility of the conference URL section.
  89. */
  90. showConferenceInfo: boolean,
  91. /**
  92. * If 'JoinByPhoneDialog' is visible or not.
  93. */
  94. showDialog: boolean,
  95. /**
  96. * Flag signaling the visibility of the skip prejoin toggle
  97. */
  98. showSkipPrejoin: boolean,
  99. /**
  100. * Used for translation.
  101. */
  102. t: Function,
  103. /**
  104. * The JitsiLocalTrack to display.
  105. */
  106. videoTrack: ?Object,
  107. /**
  108. * Array with the buttons which this Toolbox should display.
  109. */
  110. visibleButtons: Array<string>
  111. };
  112. type State = {
  113. /**
  114. * Flag controlling the visibility of the error label.
  115. */
  116. showError: boolean,
  117. /**
  118. * Flag controlling the visibility of the 'join by phone' buttons.
  119. */
  120. showJoinByPhoneButtons: boolean
  121. }
  122. /**
  123. * This component is displayed before joining a meeting.
  124. */
  125. class Prejoin extends Component<Props, State> {
  126. /**
  127. * Default values for {@code Prejoin} component's properties.
  128. *
  129. * @static
  130. */
  131. static defaultProps = {
  132. showConferenceInfo: true,
  133. showJoinActions: true,
  134. showSkipPrejoin: true
  135. };
  136. /**
  137. * Initializes a new {@code Prejoin} instance.
  138. *
  139. * @inheritdoc
  140. */
  141. constructor(props) {
  142. super(props);
  143. this.state = {
  144. showError: false,
  145. showJoinByPhoneButtons: false
  146. };
  147. this._closeDialog = this._closeDialog.bind(this);
  148. this._showDialog = this._showDialog.bind(this);
  149. this._onJoinButtonClick = this._onJoinButtonClick.bind(this);
  150. this._onToggleButtonClick = this._onToggleButtonClick.bind(this);
  151. this._onDropdownClose = this._onDropdownClose.bind(this);
  152. this._onOptionsClick = this._onOptionsClick.bind(this);
  153. this._setName = this._setName.bind(this);
  154. this._onJoinConferenceWithoutAudioKeyPress = this._onJoinConferenceWithoutAudioKeyPress.bind(this);
  155. this._showDialogKeyPress = this._showDialogKeyPress.bind(this);
  156. this._onJoinKeyPress = this._onJoinKeyPress.bind(this);
  157. }
  158. _onJoinButtonClick: () => void;
  159. /**
  160. * Handler for the join button.
  161. *
  162. * @param {Object} e - The synthetic event.
  163. * @returns {void}
  164. */
  165. _onJoinButtonClick() {
  166. if (this.props.showErrorOnJoin) {
  167. this.setState({
  168. showError: true
  169. });
  170. return;
  171. }
  172. this.setState({ showError: false });
  173. this.props.joinConference();
  174. }
  175. _onJoinKeyPress: (Object) => void;
  176. /**
  177. * KeyPress handler for accessibility.
  178. *
  179. * @param {Object} e - The key event to handle.
  180. *
  181. * @returns {void}
  182. */
  183. _onJoinKeyPress(e) {
  184. if (e.key === ' ' || e.key === 'Enter') {
  185. e.preventDefault();
  186. this._onJoinButtonClick();
  187. }
  188. }
  189. _onToggleButtonClick: () => void;
  190. /**
  191. * Handler for the toggle button.
  192. *
  193. * @param {Object} e - The synthetic event.
  194. * @returns {void}
  195. */
  196. _onToggleButtonClick() {
  197. this.props.setSkipPrejoin(!this.props.buttonIsToggled);
  198. }
  199. _onDropdownClose: () => void;
  200. /**
  201. * Closes the dropdown.
  202. *
  203. * @returns {void}
  204. */
  205. _onDropdownClose() {
  206. this.setState({
  207. showJoinByPhoneButtons: false
  208. });
  209. }
  210. _onOptionsClick: () => void;
  211. /**
  212. * Displays the join by phone buttons dropdown.
  213. *
  214. * @param {Object} e - The synthetic event.
  215. * @returns {void}
  216. */
  217. _onOptionsClick(e) {
  218. e.stopPropagation();
  219. this.setState({
  220. showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons
  221. });
  222. }
  223. _setName: () => void;
  224. /**
  225. * Sets the guest participant name.
  226. *
  227. * @param {string} displayName - Participant name.
  228. * @returns {void}
  229. */
  230. _setName(displayName) {
  231. this.props.updateSettings({
  232. displayName
  233. });
  234. }
  235. _closeDialog: () => void;
  236. /**
  237. * Closes the join by phone dialog.
  238. *
  239. * @returns {undefined}
  240. */
  241. _closeDialog() {
  242. this.props.setJoinByPhoneDialogVisiblity(false);
  243. }
  244. _showDialog: () => void;
  245. /**
  246. * Displays the dialog for joining a meeting by phone.
  247. *
  248. * @returns {undefined}
  249. */
  250. _showDialog() {
  251. this.props.setJoinByPhoneDialogVisiblity(true);
  252. this._onDropdownClose();
  253. }
  254. _showDialogKeyPress: (Object) => void;
  255. /**
  256. * KeyPress handler for accessibility.
  257. *
  258. * @param {Object} e - The key event to handle.
  259. *
  260. * @returns {void}
  261. */
  262. _showDialogKeyPress(e) {
  263. if (e.key === ' ' || e.key === 'Enter') {
  264. e.preventDefault();
  265. this._showDialog();
  266. }
  267. }
  268. _onJoinConferenceWithoutAudioKeyPress: (Object) => void;
  269. /**
  270. * KeyPress handler for accessibility.
  271. *
  272. * @param {Object} e - The key event to handle.
  273. *
  274. * @returns {void}
  275. */
  276. _onJoinConferenceWithoutAudioKeyPress(e) {
  277. if (this.props.joinConferenceWithoutAudio
  278. && (e.key === ' '
  279. || e.key === 'Enter')) {
  280. e.preventDefault();
  281. this.props.joinConferenceWithoutAudio();
  282. }
  283. }
  284. /**
  285. * Implements React's {@link Component#render()}.
  286. *
  287. * @inheritdoc
  288. * @returns {ReactElement}
  289. */
  290. render() {
  291. const {
  292. hasJoinByPhoneButton,
  293. joinConference,
  294. joinConferenceWithoutAudio,
  295. name,
  296. showAvatar,
  297. showCameraPreview,
  298. showDialog,
  299. showConferenceInfo,
  300. showJoinActions,
  301. t,
  302. videoTrack,
  303. visibleButtons
  304. } = this.props;
  305. const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, _showDialogKeyPress,
  306. _onJoinConferenceWithoutAudioKeyPress, _onOptionsClick, _setName, _showDialog } = this;
  307. const { showJoinByPhoneButtons, showError } = this.state;
  308. return (
  309. <PreMeetingScreen
  310. footer = { this._renderFooter() }
  311. name = { name }
  312. showAvatar = { showAvatar }
  313. showConferenceInfo = { showConferenceInfo }
  314. skipPrejoinButton = { this._renderSkipPrejoinButton() }
  315. title = { t('prejoin.joinMeeting') }
  316. videoMuted = { !showCameraPreview }
  317. videoTrack = { videoTrack }
  318. visibleButtons = { visibleButtons }>
  319. {showJoinActions && (
  320. <div className = 'prejoin-input-area-container'>
  321. <div className = 'prejoin-input-area'>
  322. <label
  323. className = 'prejoin-input-area-label'
  324. htmlFor = { 'Prejoin-input-field-id' } >
  325. { t('dialog.enterDisplayNameToJoin') }</label>
  326. <InputField
  327. autoComplete = { 'name' }
  328. autoFocus = { true }
  329. className = { showError ? 'error' : '' }
  330. hasError = { showError }
  331. id = { 'Prejoin-input-field-id' }
  332. onChange = { _setName }
  333. onSubmit = { joinConference }
  334. placeHolder = { t('dialog.enterDisplayName') }
  335. value = { name } />
  336. {showError && <div
  337. className = 'prejoin-error'
  338. data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
  339. <div className = 'prejoin-preview-dropdown-container'>
  340. <InlineDialog
  341. content = { <div className = 'prejoin-preview-dropdown-btns'>
  342. <div
  343. className = 'prejoin-preview-dropdown-btn'
  344. data-testid = 'prejoin.joinWithoutAudio'
  345. onClick = { joinConferenceWithoutAudio }
  346. onKeyPress = { _onJoinConferenceWithoutAudioKeyPress }
  347. role = 'button'
  348. tabIndex = { 0 }>
  349. <Icon
  350. className = 'prejoin-preview-dropdown-icon'
  351. size = { 24 }
  352. src = { IconVolumeOff } />
  353. { t('prejoin.joinWithoutAudio') }
  354. </div>
  355. {hasJoinByPhoneButton && <div
  356. className = 'prejoin-preview-dropdown-btn'
  357. onClick = { _showDialog }
  358. onKeyPress = { _showDialogKeyPress }
  359. role = 'button'
  360. tabIndex = { 0 }>
  361. <Icon
  362. className = 'prejoin-preview-dropdown-icon'
  363. data-testid = 'prejoin.joinByPhone'
  364. size = { 24 }
  365. src = { IconPhone } />
  366. { t('prejoin.joinAudioByPhone') }
  367. </div>}
  368. </div> }
  369. isOpen = { showJoinByPhoneButtons }
  370. onClose = { _onDropdownClose }>
  371. <ActionButton
  372. OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
  373. ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
  374. ariaLabel = { t('prejoin.joinMeeting') }
  375. ariaPressed = { showJoinByPhoneButtons }
  376. hasOptions = { true }
  377. onClick = { _onJoinButtonClick }
  378. onKeyPress = { _onJoinKeyPress }
  379. onOptionsClick = { _onOptionsClick }
  380. role = 'button'
  381. tabIndex = { 0 }
  382. testId = 'prejoin.joinMeeting'
  383. type = 'primary'>
  384. { t('prejoin.joinMeeting') }
  385. </ActionButton>
  386. </InlineDialog>
  387. </div>
  388. </div>
  389. </div>
  390. )}
  391. { showDialog && (
  392. <JoinByPhoneDialog
  393. joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
  394. onClose = { _closeDialog } />
  395. )}
  396. </PreMeetingScreen>
  397. );
  398. }
  399. /**
  400. * Renders the screen footer if any.
  401. *
  402. * @returns {React$Element}
  403. */
  404. _renderFooter() {
  405. return this.props.deviceStatusVisible && <DeviceStatus />;
  406. }
  407. /**
  408. * Renders the 'skip prejoin' button.
  409. *
  410. * @returns {React$Element}
  411. */
  412. _renderSkipPrejoinButton() {
  413. const { buttonIsToggled, t, showSkipPrejoin } = this.props;
  414. if (!showSkipPrejoin) {
  415. return null;
  416. }
  417. return (
  418. <div className = 'prejoin-checkbox-container'>
  419. <ToggleButton
  420. isToggled = { buttonIsToggled }
  421. onClick = { this._onToggleButtonClick }>
  422. {t('prejoin.doNotShow')}
  423. </ToggleButton>
  424. </div>
  425. );
  426. }
  427. }
  428. /**
  429. * Maps (parts of) the redux state to the React {@code Component} props.
  430. *
  431. * @param {Object} state - The redux state.
  432. * @param {Object} ownProps - The props passed to the component.
  433. * @returns {Object}
  434. */
  435. function mapStateToProps(state, ownProps): Object {
  436. const name = getDisplayName(state);
  437. const showErrorOnJoin = isDisplayNameRequired(state) && !name;
  438. const { showJoinActions } = ownProps;
  439. const isInviteButtonEnabled = isButtonEnabled('invite', state);
  440. // Hide conference info when interfaceConfig is available and the invite button is disabled.
  441. // In all other cases we want to preserve the behaviour and control the the conference info
  442. // visibility through showJoinActions.
  443. const showConferenceInfo
  444. = typeof isInviteButtonEnabled === 'undefined' || isInviteButtonEnabled === true
  445. ? showJoinActions
  446. : false;
  447. return {
  448. buttonIsToggled: isPrejoinSkipped(state),
  449. name,
  450. deviceStatusVisible: isDeviceStatusVisible(state),
  451. roomName: getRoomName(state),
  452. showDialog: isJoinByPhoneDialogVisible(state),
  453. showErrorOnJoin,
  454. hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
  455. showCameraPreview: !isVideoMutedByUser(state),
  456. showConferenceInfo,
  457. videoTrack: getLocalJitsiVideoTrack(state),
  458. visibleButtons: getToolbarButtons(state)
  459. };
  460. }
  461. const mapDispatchToProps = {
  462. joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
  463. joinConference: joinConferenceAction,
  464. setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
  465. setSkipPrejoin: setSkipPrejoinAction,
  466. updateSettings
  467. };
  468. export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));