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.

AlwaysOnTop.js 7.0KB

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