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

ConnectionIndicator.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. // @flow
  2. import { withStyles } from '@material-ui/styles';
  3. import clsx from 'clsx';
  4. import React from 'react';
  5. import type { Dispatch } from 'redux';
  6. import { translate } from '../../../base/i18n';
  7. import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
  8. import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
  9. import { getLocalParticipant, getParticipantById } from '../../../base/participants';
  10. import { Popover } from '../../../base/popover';
  11. import { connect } from '../../../base/redux';
  12. import AbstractConnectionIndicator, {
  13. INDICATOR_DISPLAY_THRESHOLD,
  14. type Props as AbstractProps,
  15. type State as AbstractState
  16. } from '../AbstractConnectionIndicator';
  17. import ConnectionIndicatorContent from './ConnectionIndicatorContent';
  18. /**
  19. * An array of display configurations for the connection indicator and its bars.
  20. * The ordering is done specifically for faster iteration to find a matching
  21. * configuration to the current connection strength percentage.
  22. *
  23. * @type {Object[]}
  24. */
  25. const QUALITY_TO_WIDTH: Array<Object> = [
  26. // Full (3 bars)
  27. {
  28. colorClass: 'status-high',
  29. percent: INDICATOR_DISPLAY_THRESHOLD,
  30. tip: 'connectionindicator.quality.good'
  31. },
  32. // 2 bars
  33. {
  34. colorClass: 'status-med',
  35. percent: 10,
  36. tip: 'connectionindicator.quality.nonoptimal'
  37. },
  38. // 1 bar
  39. {
  40. colorClass: 'status-low',
  41. percent: 0,
  42. tip: 'connectionindicator.quality.poor'
  43. }
  44. // Note: we never show 0 bars as long as there is a connection.
  45. ];
  46. /**
  47. * The type of the React {@code Component} props of {@link ConnectionIndicator}.
  48. */
  49. type Props = AbstractProps & {
  50. /**
  51. * The current condition of the user's connection, matching one of the
  52. * enumerated values in the library.
  53. */
  54. _connectionStatus: string,
  55. /**
  56. * Disable/enable inactive indicator.
  57. */
  58. _connectionIndicatorInactiveDisabled: boolean,
  59. /**
  60. * Wether the indicator popover is disabled.
  61. */
  62. _popoverDisabled: boolean,
  63. /**
  64. * Whether or not the component should ignore setting a visibility class for
  65. * hiding the component when the connection quality is not strong.
  66. */
  67. alwaysVisible: boolean,
  68. /**
  69. * The audio SSRC of this client.
  70. */
  71. audioSsrc: number,
  72. /**
  73. * An object containing the CSS classes.
  74. */
  75. classes: Object,
  76. /**
  77. * The Redux dispatch function.
  78. */
  79. dispatch: Dispatch<any>,
  80. /**
  81. * Whether or not clicking the indicator should display a popover for more
  82. * details.
  83. */
  84. enableStatsDisplay: boolean,
  85. /**
  86. * The font-size for the icon.
  87. */
  88. iconSize: number,
  89. /**
  90. * Relative to the icon from where the popover for more connection details
  91. * should display.
  92. */
  93. statsPopoverPosition: string,
  94. /**
  95. * Invoked to obtain translated strings.
  96. */
  97. t: Function,
  98. };
  99. type State = AbstractState & {
  100. /**
  101. * Whether popover is ivisible or not.
  102. */
  103. popoverVisible: boolean
  104. }
  105. const styles = theme => {
  106. return {
  107. container: {
  108. display: 'inline-block'
  109. },
  110. hidden: {
  111. display: 'none'
  112. },
  113. icon: {
  114. padding: '6px',
  115. borderRadius: '4px',
  116. '&.status-high': {
  117. backgroundColor: theme.palette.success01
  118. },
  119. '&.status-med': {
  120. backgroundColor: theme.palette.warning01
  121. },
  122. '&.status-low': {
  123. backgroundColor: theme.palette.iconError
  124. },
  125. '&.status-disabled': {
  126. background: 'transparent'
  127. },
  128. '&.status-lost': {
  129. backgroundColor: theme.palette.ui05
  130. },
  131. '&.status-other': {
  132. backgroundColor: theme.palette.action01
  133. }
  134. },
  135. inactiveIcon: {
  136. padding: 0,
  137. borderRadius: '50%'
  138. }
  139. };
  140. };
  141. /**
  142. * Implements a React {@link Component} which displays the current connection
  143. * quality percentage and has a popover to show more detailed connection stats.
  144. *
  145. * @augments {Component}
  146. */
  147. class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
  148. /**
  149. * Initializes a new {@code ConnectionIndicator} instance.
  150. *
  151. * @param {Object} props - The read-only properties with which the new
  152. * instance is to be initialized.
  153. */
  154. constructor(props: Props) {
  155. super(props);
  156. this.state = {
  157. showIndicator: false,
  158. stats: {},
  159. popoverVisible: false
  160. };
  161. this._onShowPopover = this._onShowPopover.bind(this);
  162. this._onHidePopover = this._onHidePopover.bind(this);
  163. }
  164. /**
  165. * Implements React's {@link Component#render()}.
  166. *
  167. * @inheritdoc
  168. * @returns {ReactElement}
  169. */
  170. render() {
  171. const { enableStatsDisplay, participantId, statsPopoverPosition, classes } = this.props;
  172. const visibilityClass = this._getVisibilityClass();
  173. if (this.props._popoverDisabled) {
  174. return this._renderIndicator();
  175. }
  176. return (
  177. <Popover
  178. className = { clsx(classes.container, visibilityClass) }
  179. content = { <ConnectionIndicatorContent
  180. inheritedStats = { this.state.stats }
  181. participantId = { participantId } /> }
  182. disablePopover = { !enableStatsDisplay }
  183. id = 'participant-connection-indicator'
  184. noPaddingContent = { true }
  185. onPopoverClose = { this._onHidePopover }
  186. onPopoverOpen = { this._onShowPopover }
  187. position = { statsPopoverPosition }
  188. visible = { this.state.popoverVisible }>
  189. { this._renderIndicator() }
  190. </Popover>
  191. );
  192. }
  193. /**
  194. * Returns a CSS class that interprets the current connection status as a
  195. * color.
  196. *
  197. * @private
  198. * @returns {string}
  199. */
  200. _getConnectionColorClass() {
  201. const { _connectionStatus } = this.props;
  202. const { percent } = this.state.stats;
  203. const { INACTIVE, INTERRUPTED } = JitsiParticipantConnectionStatus;
  204. if (_connectionStatus === INACTIVE) {
  205. if (this.props._connectionIndicatorInactiveDisabled) {
  206. return 'status-disabled';
  207. }
  208. return 'status-other';
  209. } else if (_connectionStatus === INTERRUPTED) {
  210. return 'status-lost';
  211. } else if (typeof percent === 'undefined') {
  212. return 'status-high';
  213. }
  214. return this._getDisplayConfiguration(percent).colorClass;
  215. }
  216. /**
  217. * Get the icon configuration from QUALITY_TO_WIDTH which has a percentage
  218. * that matches or exceeds the passed in percentage. The implementation
  219. * assumes QUALITY_TO_WIDTH is already sorted by highest to lowest
  220. * percentage.
  221. *
  222. * @param {number} percent - The connection percentage, out of 100, to find
  223. * the closest matching configuration for.
  224. * @private
  225. * @returns {Object}
  226. */
  227. _getDisplayConfiguration(percent: number): Object {
  228. return QUALITY_TO_WIDTH.find(x => percent >= x.percent) || {};
  229. }
  230. /**
  231. * Returns additional class names to add to the root of the component. The
  232. * class names are intended to be used for hiding or showing the indicator.
  233. *
  234. * @private
  235. * @returns {string}
  236. */
  237. _getVisibilityClass() {
  238. const { _connectionStatus, classes } = this.props;
  239. return this.state.showIndicator
  240. || this.props.alwaysVisible
  241. || _connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED
  242. || _connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
  243. ? '' : classes.hidden;
  244. }
  245. _onHidePopover: () => void;
  246. /**
  247. * Hides popover.
  248. *
  249. * @private
  250. * @returns {void}
  251. */
  252. _onHidePopover() {
  253. this.setState({ popoverVisible: false });
  254. }
  255. /**
  256. * Creates a ReactElement for displaying an icon that represents the current
  257. * connection quality.
  258. *
  259. * @returns {ReactElement}
  260. */
  261. _renderIcon() {
  262. const colorClass = this._getConnectionColorClass();
  263. if (this.props._connectionStatus === JitsiParticipantConnectionStatus.INACTIVE) {
  264. if (this.props._connectionIndicatorInactiveDisabled) {
  265. return null;
  266. }
  267. return (
  268. <span className = 'connection_ninja'>
  269. <Icon
  270. className = { clsx(this.props.classes.icon, this.props.classes.inactiveIcon, colorClass) }
  271. size = { 24 }
  272. src = { IconConnectionInactive } />
  273. </span>
  274. );
  275. }
  276. let emptyIconWrapperClassName = 'connection_empty';
  277. if (this.props._connectionStatus
  278. === JitsiParticipantConnectionStatus.INTERRUPTED) {
  279. // emptyIconWrapperClassName is used by the torture tests to
  280. // identify lost connection status handling.
  281. emptyIconWrapperClassName = 'connection_lost';
  282. }
  283. return (
  284. <span className = { emptyIconWrapperClassName }>
  285. <Icon
  286. className = { clsx(this.props.classes.icon, colorClass) }
  287. size = { 12 }
  288. src = { IconConnectionActive } />
  289. </span>
  290. );
  291. }
  292. _onShowPopover: () => void;
  293. /**
  294. * Shows popover.
  295. *
  296. * @private
  297. * @returns {void}
  298. */
  299. _onShowPopover() {
  300. this.setState({ popoverVisible: true });
  301. }
  302. /**
  303. * Creates a ReactElement for displaying the indicator (GSM bar).
  304. *
  305. * @returns {ReactElement}
  306. */
  307. _renderIndicator() {
  308. return (
  309. <div
  310. style = {{ fontSize: this.props.iconSize }}>
  311. {this._renderIcon()}
  312. </div>
  313. );
  314. }
  315. }
  316. /**
  317. * Maps part of the Redux state to the props of this component.
  318. *
  319. * @param {Object} state - The Redux state.
  320. * @param {Props} ownProps - The own props of the component.
  321. * @returns {Props}
  322. */
  323. export function _mapStateToProps(state: Object, ownProps: Props) {
  324. const { participantId } = ownProps;
  325. const participant
  326. = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
  327. return {
  328. _connectionIndicatorInactiveDisabled:
  329. Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
  330. _popoverDisabled: state['features/base/config'].connectionIndicators?.disableDetails,
  331. _connectionStatus: participant?.connectionStatus
  332. };
  333. }
  334. export default translate(connect(_mapStateToProps)(
  335. withStyles(styles)(ConnectionIndicator)));