您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

AlwaysOnTop.tsx 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import React, { Component } from 'react';
  2. // We need to reference these files directly to avoid loading things that are not available
  3. // in this environment (e.g. JitsiMeetJS or interfaceConfig)
  4. import StatelessAvatar from '../base/avatar/components/web/StatelessAvatar';
  5. import { getAvatarColor, getInitials } from '../base/avatar/functions';
  6. import Toolbar from './Toolbar';
  7. const { api } = window.alwaysOnTop;
  8. /**
  9. * The timeout in ms for hiding the toolbar.
  10. */
  11. const TOOLBAR_TIMEOUT = 4000;
  12. /**
  13. * The type of the React {@code Component} state of {@link AlwaysOnTop}.
  14. */
  15. interface IState {
  16. avatarURL: string;
  17. customAvatarBackgrounds: Array<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. * @augments Component
  29. */
  30. export default class AlwaysOnTop extends Component<any, IState> {
  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: any) {
  39. super(props);
  40. this.state = {
  41. avatarURL: '',
  42. customAvatarBackgrounds: [],
  43. displayName: '',
  44. formattedDisplayName: '',
  45. isVideoDisplayed: true,
  46. userID: '',
  47. visible: true
  48. };
  49. // Bind event handlers so they are only bound once per instance.
  50. this._avatarChangedListener = this._avatarChangedListener.bind(this);
  51. this._displayNameChangedListener
  52. = this._displayNameChangedListener.bind(this);
  53. this._videoChangedListener
  54. = this._videoChangedListener.bind(this);
  55. this._mouseMove = this._mouseMove.bind(this);
  56. this._onMouseOut = this._onMouseOut.bind(this);
  57. this._onMouseOver = this._onMouseOver.bind(this);
  58. }
  59. /**
  60. * Handles avatar changed api events.
  61. *
  62. * @returns {void}
  63. */
  64. _avatarChangedListener({ avatarURL, id }: { avatarURL: string; id: string; }) {
  65. if (api._getOnStageParticipant() === id
  66. && avatarURL !== this.state.avatarURL) {
  67. this.setState({ avatarURL });
  68. }
  69. }
  70. /**
  71. * Handles display name changed api events.
  72. *
  73. * @returns {void}
  74. */
  75. _displayNameChangedListener({ displayname, formattedDisplayName, id }: { displayname: string;
  76. formattedDisplayName: string; id: string; }) {
  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. /**
  104. * Handles large video changed api events.
  105. *
  106. * @returns {void}
  107. */
  108. _videoChangedListener() {
  109. const userID = api._getOnStageParticipant();
  110. const avatarURL = api.getAvatarURL(userID);
  111. const displayName = api.getDisplayName(userID);
  112. const formattedDisplayName = api._getFormattedDisplayName(userID);
  113. const isVideoDisplayed = Boolean(api._getPrejoinVideo?.() || api._getLargeVideo());
  114. this.setState({
  115. avatarURL,
  116. displayName,
  117. formattedDisplayName,
  118. isVideoDisplayed,
  119. userID
  120. });
  121. }
  122. /**
  123. * Handles mouse move events.
  124. *
  125. * @returns {void}
  126. */
  127. _mouseMove() {
  128. this.state.visible || this.setState({ visible: true });
  129. }
  130. /**
  131. * Toolbar mouse out handler.
  132. *
  133. * @returns {void}
  134. */
  135. _onMouseOut() {
  136. this._hovered = false;
  137. }
  138. /**
  139. * Toolbar mouse over handler.
  140. *
  141. * @returns {void}
  142. */
  143. _onMouseOver() {
  144. this._hovered = true;
  145. }
  146. /**
  147. * Renders display name and avatar for the on stage participant.
  148. *
  149. * @returns {ReactElement}
  150. */
  151. _renderVideoNotAvailableScreen() {
  152. const {
  153. avatarURL,
  154. customAvatarBackgrounds,
  155. displayName,
  156. formattedDisplayName,
  157. isVideoDisplayed
  158. } = this.state;
  159. if (isVideoDisplayed) {
  160. return null;
  161. }
  162. return (
  163. <div id = 'videoNotAvailableScreen'>
  164. <div id = 'avatarContainer'>
  165. <StatelessAvatar
  166. color = { getAvatarColor(displayName, customAvatarBackgrounds) }
  167. id = 'avatar'
  168. initials = { getInitials(displayName) }
  169. url = { avatarURL } />)
  170. </div>
  171. <div
  172. className = 'displayname'
  173. id = 'displayname'>
  174. { formattedDisplayName }
  175. </div>
  176. </div>
  177. );
  178. }
  179. /**
  180. * Sets mouse move listener and initial toolbar timeout.
  181. *
  182. * @inheritdoc
  183. * @returns {void}
  184. */
  185. componentDidMount() {
  186. api.on('avatarChanged', this._avatarChangedListener);
  187. api.on('displayNameChange', this._displayNameChangedListener);
  188. api.on('largeVideoChanged', this._videoChangedListener);
  189. api.on('prejoinVideoChanged', this._videoChangedListener);
  190. api.on('videoConferenceJoined', this._videoChangedListener);
  191. this._videoChangedListener();
  192. window.addEventListener('mousemove', this._mouseMove);
  193. this._hideToolbarAfterTimeout();
  194. api.getCustomAvatarBackgrounds()
  195. .then((res: { avatarBackgrounds?: string[]; }) =>
  196. this.setState({
  197. customAvatarBackgrounds: res.avatarBackgrounds || []
  198. }))
  199. .catch(console.error);
  200. }
  201. /**
  202. * Sets a timeout to hide the toolbar when the toolbar is shown.
  203. *
  204. * @inheritdoc
  205. * @returns {void}
  206. */
  207. componentDidUpdate(_prevProps: any, prevState: IState) {
  208. if (!prevState.visible && this.state.visible) {
  209. this._hideToolbarAfterTimeout();
  210. }
  211. }
  212. /**
  213. * Removes all listeners.
  214. *
  215. * @inheritdoc
  216. * @returns {void}
  217. */
  218. componentWillUnmount() {
  219. api.removeListener('avatarChanged', this._avatarChangedListener);
  220. api.removeListener(
  221. 'displayNameChange',
  222. this._displayNameChangedListener);
  223. api.removeListener(
  224. 'largeVideoChanged',
  225. this._videoChangedListener);
  226. api.removeListener(
  227. 'prejoinVideoChanged',
  228. this._videoChangedListener);
  229. api.removeListener(
  230. 'videoConferenceJoined',
  231. this._videoChangedListener);
  232. window.removeEventListener('mousemove', this._mouseMove);
  233. }
  234. /**
  235. * Implements React's {@link Component#render()}.
  236. *
  237. * @inheritdoc
  238. * @returns {ReactElement}
  239. */
  240. render() {
  241. return (
  242. <div id = 'alwaysOnTop'>
  243. <Toolbar
  244. className = { this.state.visible ? 'fadeIn' : 'fadeOut' }
  245. onMouseOut = { this._onMouseOut }
  246. onMouseOver = { this._onMouseOver } />
  247. { this._renderVideoNotAvailableScreen() }
  248. </div>
  249. );
  250. }
  251. }