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.

SpeakerEntry.tsx 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import React, { useRef } from 'react';
  2. import { makeStyles } from 'tss-react/mui';
  3. import { IconCheck } from '../../../../base/icons/svg';
  4. import Button from '../../../../base/ui/components/web/Button';
  5. import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
  6. import { BUTTON_TYPES, TEXT_OVERFLOW_TYPES } from '../../../../base/ui/constants.any';
  7. import logger from '../../../logger';
  8. const TEST_SOUND_PATH = 'sounds/ring.mp3';
  9. /**
  10. * The type of the React {@code Component} props of {@link SpeakerEntry}.
  11. */
  12. interface IProps {
  13. /**
  14. * The text label for the entry.
  15. */
  16. children: string;
  17. /**
  18. * The deviceId of the speaker.
  19. */
  20. deviceId: string;
  21. /**
  22. * Flag controlling the selection state of the entry.
  23. */
  24. index: number;
  25. /**
  26. * Flag controlling the selection state of the entry.
  27. */
  28. isSelected: boolean;
  29. /**
  30. * Flag controlling the selection state of the entry.
  31. */
  32. length: number;
  33. /**
  34. * Click handler for the component.
  35. */
  36. onClick: Function;
  37. }
  38. const useStyles = makeStyles()(() => {
  39. return {
  40. container: {
  41. position: 'relative',
  42. [[ '&:hover', '&:focus', '&:focus-within' ] as any]: {
  43. '& .entryText': {
  44. maxWidth: '178px',
  45. marginRight: 0
  46. },
  47. '& .testButton': {
  48. display: 'inline-block'
  49. }
  50. }
  51. },
  52. entryText: {
  53. maxWidth: '238px',
  54. '&.left-margin': {
  55. marginLeft: '36px'
  56. }
  57. },
  58. testButton: {
  59. display: 'none',
  60. padding: '4px 10px',
  61. position: 'absolute',
  62. right: '16px',
  63. top: '6px'
  64. }
  65. };
  66. });
  67. /**
  68. * Implements a React {@link Component} which displays an audio
  69. * output settings entry. The user can click and play a test sound.
  70. *
  71. * @param {IProps} props - Component props.
  72. * @returns {JSX.Element}
  73. */
  74. const SpeakerEntry = (props: IProps) => {
  75. const audioRef = useRef<HTMLAudioElement | null>(null);
  76. const { classes, cx } = useStyles();
  77. /**
  78. * Click handler for the entry.
  79. *
  80. * @returns {void}
  81. */
  82. function _onClick() {
  83. props.onClick(props.deviceId);
  84. }
  85. /**
  86. * Key pressed handler for the entry.
  87. *
  88. * @param {Object} e - The event.
  89. * @private
  90. *
  91. * @returns {void}
  92. */
  93. function _onKeyPress(e: React.KeyboardEvent) {
  94. if (e.key === 'Enter' || e.key === ' ') {
  95. e.preventDefault();
  96. props.onClick(props.deviceId);
  97. }
  98. }
  99. /**
  100. * Click handler for Test button.
  101. * Sets the current audio output id and plays a sound.
  102. *
  103. * @param {Object} e - The synthetic event.
  104. * @returns {void}
  105. */
  106. async function _onTestButtonClick(e: React.KeyboardEvent | React.MouseEvent) {
  107. e.stopPropagation();
  108. try {
  109. await audioRef.current?.setSinkId(props.deviceId);
  110. audioRef.current?.play();
  111. } catch (err) {
  112. logger.log('Could not set sink id', err);
  113. }
  114. }
  115. const { children, isSelected, index, length } = props;
  116. /* eslint-disable react/jsx-no-bind */
  117. return (
  118. <li
  119. aria-checked = { isSelected }
  120. aria-posinset = { index }
  121. aria-setsize = { length }
  122. className = { classes.container }
  123. onClick = { _onClick }
  124. onKeyPress = { _onKeyPress }
  125. role = 'radio'
  126. tabIndex = { 0 }>
  127. <ContextMenuItem
  128. accessibilityLabel = { children }
  129. icon = { isSelected ? IconCheck : undefined }
  130. overflowType = { TEXT_OVERFLOW_TYPES.SCROLL_ON_HOVER }
  131. selected = { isSelected }
  132. text = { children }
  133. textClassName = { cx(classes.entryText, 'entryText', !isSelected && 'left-margin') }>
  134. <Button
  135. className = { cx(classes.testButton, 'testButton') }
  136. label = 'Test'
  137. onClick = { _onTestButtonClick }
  138. onKeyPress = { _onTestButtonClick }
  139. type = { BUTTON_TYPES.SECONDARY } />
  140. </ContextMenuItem>
  141. <audio
  142. preload = 'auto'
  143. ref = { audioRef }
  144. src = { TEST_SOUND_PATH } />
  145. </li>
  146. );
  147. };
  148. export default SpeakerEntry;