Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

AlwaysOnTop.js 7.5KB

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