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.

AlwaysOnTop.js 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // @flow
  2. import React, { Component } from 'react';
  3. import Toolbar from './Toolbar';
  4. const { api } = window.alwaysOnTop;
  5. /**
  6. * The timeout in ms for hiding the toolbar.
  7. */
  8. const TOOLBAR_TIMEOUT = 4000;
  9. /**
  10. * The type of the React {@code Component} state of {@link AlwaysOnTop}.
  11. */
  12. type State = {
  13. avatarURL: string,
  14. displayName: string,
  15. isVideoDisplayed: boolean,
  16. visible: boolean
  17. };
  18. /**
  19. * Represents the always on top page.
  20. *
  21. * @class AlwaysOnTop
  22. * @extends Component
  23. */
  24. export default class AlwaysOnTop extends Component<*, State> {
  25. _hovered: boolean;
  26. /**
  27. * Initializes a new {@code AlwaysOnTop} instance.
  28. *
  29. * @param {*} props - The read-only properties with which the new instance
  30. * is to be initialized.
  31. */
  32. constructor(props: *) {
  33. super(props);
  34. this.state = {
  35. avatarURL: '',
  36. displayName: '',
  37. isVideoDisplayed: true,
  38. visible: true
  39. };
  40. // Bind event handlers so they are only bound once per instance.
  41. this._avatarChangedListener = this._avatarChangedListener.bind(this);
  42. this._displayNameChangedListener
  43. = this._displayNameChangedListener.bind(this);
  44. this._largeVideoChangedListener
  45. = this._largeVideoChangedListener.bind(this);
  46. this._mouseMove = this._mouseMove.bind(this);
  47. this._onMouseOut = this._onMouseOut.bind(this);
  48. this._onMouseOver = this._onMouseOver.bind(this);
  49. }
  50. _avatarChangedListener: () => void;
  51. /**
  52. * Handles avatar changed api events.
  53. *
  54. * @returns {void}
  55. */
  56. _avatarChangedListener({ avatarURL, id }) {
  57. if (api._getOnStageParticipant() === id
  58. && avatarURL !== this.state.avatarURL) {
  59. this.setState({ avatarURL });
  60. }
  61. }
  62. _displayNameChangedListener: () => void;
  63. /**
  64. * Handles display name changed api events.
  65. *
  66. * @returns {void}
  67. */
  68. _displayNameChangedListener({ formattedDisplayName, id }) {
  69. if (api._getOnStageParticipant() === id
  70. && formattedDisplayName !== this.state.displayName) {
  71. this.setState({ displayName: formattedDisplayName });
  72. }
  73. }
  74. /**
  75. * Hides the toolbar after a timeout.
  76. *
  77. * @returns {void}
  78. */
  79. _hideToolbarAfterTimeout() {
  80. setTimeout(
  81. () => {
  82. if (this._hovered) {
  83. this._hideToolbarAfterTimeout();
  84. } else {
  85. this.setState({ visible: false });
  86. }
  87. },
  88. TOOLBAR_TIMEOUT);
  89. }
  90. _largeVideoChangedListener: () => void;
  91. /**
  92. * Handles large video changed api events.
  93. *
  94. * @returns {void}
  95. */
  96. _largeVideoChangedListener() {
  97. const userID = api._getOnStageParticipant();
  98. const avatarURL = api.getAvatarURL(userID);
  99. const displayName = api._getFormattedDisplayName(userID);
  100. const isVideoDisplayed = Boolean(api._getLargeVideo());
  101. this.setState({
  102. avatarURL,
  103. displayName,
  104. isVideoDisplayed
  105. });
  106. }
  107. _mouseMove: () => void;
  108. /**
  109. * Handles mouse move events.
  110. *
  111. * @returns {void}
  112. */
  113. _mouseMove() {
  114. this.state.visible || this.setState({ visible: true });
  115. }
  116. _onMouseOut: () => void;
  117. /**
  118. * Toolbar mouse out handler.
  119. *
  120. * @returns {void}
  121. */
  122. _onMouseOut() {
  123. this._hovered = false;
  124. }
  125. _onMouseOver: () => void;
  126. /**
  127. * Toolbar mouse over handler.
  128. *
  129. * @returns {void}
  130. */
  131. _onMouseOver() {
  132. this._hovered = true;
  133. }
  134. /**
  135. * Renders display name and avatar for the on stage participant.
  136. *
  137. * @returns {ReactElement}
  138. */
  139. _renderVideoNotAvailableScreen() {
  140. const { avatarURL, displayName, isVideoDisplayed } = this.state;
  141. if (isVideoDisplayed) {
  142. return null;
  143. }
  144. return (
  145. <div id = 'videoNotAvailableScreen'>
  146. {
  147. avatarURL
  148. ? <div id = 'avatarContainer'>
  149. <img
  150. id = 'avatar'
  151. src = { avatarURL } />
  152. </div>
  153. : null
  154. }
  155. <div
  156. className = 'displayname'
  157. id = 'displayname'>
  158. { displayName }
  159. </div>
  160. </div>
  161. );
  162. }
  163. /**
  164. * Sets mouse move listener and initial toolbar timeout.
  165. *
  166. * @inheritdoc
  167. * @returns {void}
  168. */
  169. componentDidMount() {
  170. api.on('avatarChanged', this._avatarChangedListener);
  171. api.on('displayNameChange', this._displayNameChangedListener);
  172. api.on('largeVideoChanged', this._largeVideoChangedListener);
  173. this._largeVideoChangedListener();
  174. window.addEventListener('mousemove', this._mouseMove);
  175. this._hideToolbarAfterTimeout();
  176. }
  177. /**
  178. * Sets a timeout to hide the toolbar when the toolbar is shown.
  179. *
  180. * @inheritdoc
  181. * @returns {void}
  182. */
  183. componentDidUpdate(prevProps: *, prevState: State) {
  184. if (!prevState.visible && this.state.visible) {
  185. this._hideToolbarAfterTimeout();
  186. }
  187. }
  188. /**
  189. * Removes all listeners.
  190. *
  191. * @inheritdoc
  192. * @returns {void}
  193. */
  194. componentWillUnmount() {
  195. api.removeListener('avatarChanged', this._avatarChangedListener);
  196. api.removeListener(
  197. 'displayNameChange',
  198. this._displayNameChangedListener);
  199. api.removeListener(
  200. 'largeVideoChanged',
  201. this._largeVideoChangedListener);
  202. window.removeEventListener('mousemove', this._mouseMove);
  203. }
  204. /**
  205. * Implements React's {@link Component#render()}.
  206. *
  207. * @inheritdoc
  208. * @returns {ReactElement}
  209. */
  210. render() {
  211. return (
  212. <div id = 'alwaysOnTop'>
  213. <Toolbar
  214. className = { this.state.visible ? 'fadeIn' : 'fadeOut' }
  215. onMouseOut = { this._onMouseOut }
  216. onMouseOver = { this._onMouseOver } />
  217. { this._renderVideoNotAvailableScreen() }
  218. </div>
  219. );
  220. }
  221. }