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.

Filmstrip.web.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* @flow */
  2. import _ from 'lodash';
  3. import PropTypes from 'prop-types';
  4. import React, { Component } from 'react';
  5. import { connect } from 'react-redux';
  6. import { Toolbox } from '../../toolbox';
  7. import { setFilmstripHovered } from '../actions';
  8. import { shouldRemoteVideosBeVisible } from '../functions';
  9. /**
  10. * Implements a React {@link Component} which represents the filmstrip on
  11. * Web/React.
  12. *
  13. * @extends Component
  14. */
  15. class Filmstrip extends Component {
  16. _isHovered: boolean;
  17. _notifyOfHoveredStateUpdate: Function;
  18. _onMouseOut: Function;
  19. _onMouseOver: Function;
  20. /**
  21. * {@code Filmstrip} component's property types.
  22. *
  23. * @static
  24. */
  25. static propTypes = {
  26. /**
  27. * Whether or not remote videos are currently being hovered over.
  28. */
  29. _hovered: PropTypes.bool,
  30. /**
  31. * Whether or not the remote videos should be visible. Will toggle
  32. * a class for hiding the videos.
  33. */
  34. _remoteVideosVisible: PropTypes.bool,
  35. /**
  36. * Updates the redux store with filmstrip hover changes.
  37. */
  38. dispatch: PropTypes.func,
  39. /**
  40. * Whether or not the toolbox should be displayed within the filmstrip.
  41. */
  42. displayToolbox: PropTypes.bool
  43. };
  44. /**
  45. * Initializes a new {@code Filmstrip} instance.
  46. *
  47. * @param {Object} props - The read-only properties with which the new
  48. * instance is to be initialized.
  49. */
  50. constructor(props) {
  51. super(props);
  52. // Debounce the method for dispatching the new filmstrip handled state
  53. // so that it does not get called with each mouse movement event. This
  54. // also works around an issue where mouseout and then a mouseover event
  55. // is fired when hovering over remote thumbnails, which are not yet in
  56. // react.
  57. this._notifyOfHoveredStateUpdate
  58. = _.debounce(this._notifyOfHoveredStateUpdate, 100);
  59. // Cache the current hovered state for _updateHoveredState to always
  60. // send the last known hovered state.
  61. this._isHovered = false;
  62. // Bind event handlers so they are only bound once for every instance.
  63. this._onMouseOver = this._onMouseOver.bind(this);
  64. this._onMouseOut = this._onMouseOut.bind(this);
  65. }
  66. /**
  67. * Implements React's {@link Component#render()}.
  68. *
  69. * @inheritdoc
  70. * @returns {ReactElement}
  71. */
  72. render() {
  73. /**
  74. * Note: Appending of {@code RemoteVideo} views is handled through
  75. * VideoLayout. The views do not get blown away on render() because
  76. * ReactDOMComponent is only aware of the given JSX and not new appended
  77. * DOM. As such, when updateDOMProperties gets called, only attributes
  78. * will get updated without replacing the DOM. If the known DOM gets
  79. * modified, then the views will get blown away.
  80. */
  81. const filmstripClassNames = `filmstrip ${this.props._remoteVideosVisible
  82. ? '' : 'hide-videos'}`;
  83. return (
  84. <div className = { filmstripClassNames }>
  85. { this.props.displayToolbox ? <Toolbox /> : null }
  86. <div
  87. className = 'filmstrip__videos'
  88. id = 'remoteVideos'>
  89. <div
  90. className = 'filmstrip__videos'
  91. id = 'filmstripLocalVideo'
  92. onMouseOut = { this._onMouseOut }
  93. onMouseOver = { this._onMouseOver } />
  94. <div
  95. className = 'filmstrip__videos'
  96. id = 'filmstripRemoteVideos'>
  97. {/**
  98. * This extra video container is needed for scrolling
  99. * thumbnails in Firefox; otherwise, the flex
  100. * thumbnails resize instead of causing overflow.
  101. */}
  102. <div
  103. className = 'remote-videos-container'
  104. id = 'filmstripRemoteVideosContainer'
  105. onMouseOut = { this._onMouseOut }
  106. onMouseOver = { this._onMouseOver } />
  107. </div>
  108. <audio
  109. id = 'userJoined'
  110. preload = 'auto'
  111. src = 'sounds/joined.wav' />
  112. <audio
  113. id = 'userLeft'
  114. preload = 'auto'
  115. src = 'sounds/left.wav' />
  116. </div>
  117. </div>
  118. );
  119. }
  120. /**
  121. * If the current hover state does not match the known hover state in redux,
  122. * dispatch an action to update the known hover state in redux.
  123. *
  124. * @private
  125. * @returns {void}
  126. */
  127. _notifyOfHoveredStateUpdate() {
  128. if (this.props._hovered !== this._isHovered) {
  129. this.props.dispatch(setFilmstripHovered(this._isHovered));
  130. }
  131. }
  132. /**
  133. * Updates the currently known mouseover state and attempt to dispatch an
  134. * update of the known hover state in redux.
  135. *
  136. * @private
  137. * @returns {void}
  138. */
  139. _onMouseOut() {
  140. this._isHovered = false;
  141. this._notifyOfHoveredStateUpdate();
  142. }
  143. /**
  144. * Updates the currently known mouseover state and attempt to dispatch an
  145. * update of the known hover state in redux.
  146. *
  147. * @private
  148. * @returns {void}
  149. */
  150. _onMouseOver() {
  151. this._isHovered = true;
  152. this._notifyOfHoveredStateUpdate();
  153. }
  154. }
  155. /**
  156. * Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
  157. *
  158. * @param {Object} state - The Redux state.
  159. * @private
  160. * @returns {{
  161. * _hovered: boolean,
  162. * _remoteVideosVisible: boolean
  163. * }}
  164. */
  165. function _mapStateToProps(state) {
  166. return {
  167. _hovered: state['features/filmstrip'].hovered,
  168. _remoteVideosVisible: shouldRemoteVideosBeVisible(state)
  169. };
  170. }
  171. export default connect(_mapStateToProps)(Filmstrip);