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

Chat.js 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // @flow
  2. import React from 'react';
  3. import { translate } from '../../../base/i18n';
  4. import { connect } from '../../../base/redux';
  5. import AbstractChat, {
  6. _mapStateToProps,
  7. type Props
  8. } from '../AbstractChat';
  9. import ChatDialog from './ChatDialog';
  10. import Header from './ChatDialogHeader';
  11. import ChatInput from './ChatInput';
  12. import DisplayNameForm from './DisplayNameForm';
  13. import KeyboardAvoider from './KeyboardAvoider';
  14. import MessageContainer from './MessageContainer';
  15. import MessageRecipient from './MessageRecipient';
  16. import TouchmoveHack from './TouchmoveHack';
  17. /**
  18. * React Component for holding the chat feature in a side panel that slides in
  19. * and out of view.
  20. */
  21. class Chat extends AbstractChat<Props> {
  22. /**
  23. * Whether or not the {@code Chat} component is off-screen, having finished
  24. * its hiding animation.
  25. */
  26. _isExited: boolean;
  27. /**
  28. * Reference to the React Component for displaying chat messages. Used for
  29. * scrolling to the end of the chat messages.
  30. */
  31. _messageContainerRef: Object;
  32. /**
  33. * Initializes a new {@code Chat} instance.
  34. *
  35. * @param {Object} props - The read-only properties with which the new
  36. * instance is to be initialized.
  37. */
  38. constructor(props: Props) {
  39. super(props);
  40. this._isExited = true;
  41. this._messageContainerRef = React.createRef();
  42. // Bind event handlers so they are only bound once for every instance.
  43. this._renderPanelContent = this._renderPanelContent.bind(this);
  44. this._onChatInputResize = this._onChatInputResize.bind(this);
  45. }
  46. /**
  47. * Implements {@code Component#componentDidMount}.
  48. *
  49. * @inheritdoc
  50. */
  51. componentDidMount() {
  52. this._scrollMessageContainerToBottom(true);
  53. }
  54. /**
  55. * Implements {@code Component#componentDidUpdate}.
  56. *
  57. * @inheritdoc
  58. */
  59. componentDidUpdate(prevProps) {
  60. if (this.props._messages !== prevProps._messages) {
  61. this._scrollMessageContainerToBottom(true);
  62. } else if (this.props._isOpen && !prevProps._isOpen) {
  63. this._scrollMessageContainerToBottom(false);
  64. }
  65. }
  66. /**
  67. * Implements React's {@link Component#render()}.
  68. *
  69. * @inheritdoc
  70. * @returns {ReactElement}
  71. */
  72. render() {
  73. return (
  74. <>
  75. { this._renderPanelContent() }
  76. </>
  77. );
  78. }
  79. _onChatInputResize: () => void;
  80. /**
  81. * Callback invoked when {@code ChatInput} changes height. Preserves
  82. * displaying the latest message if it is scrolled to.
  83. *
  84. * @private
  85. * @returns {void}
  86. */
  87. _onChatInputResize() {
  88. this._messageContainerRef.current.maybeUpdateBottomScroll();
  89. }
  90. /**
  91. * Returns a React Element for showing chat messages and a form to send new
  92. * chat messages.
  93. *
  94. * @private
  95. * @returns {ReactElement}
  96. */
  97. _renderChat() {
  98. return (
  99. <>
  100. <TouchmoveHack isModal = { this.props._isModal }>
  101. <MessageContainer
  102. messages = { this.props._messages }
  103. ref = { this._messageContainerRef } />
  104. </TouchmoveHack>
  105. <MessageRecipient />
  106. <ChatInput
  107. onResize = { this._onChatInputResize }
  108. onSend = { this._onSendMessage } />
  109. <KeyboardAvoider />
  110. </>
  111. );
  112. }
  113. /**
  114. * Instantiates a React Element to display at the top of {@code Chat} to
  115. * close {@code Chat}.
  116. *
  117. * @private
  118. * @returns {ReactElement}
  119. */
  120. _renderChatHeader() {
  121. return (
  122. <Header className = 'chat-header' />
  123. );
  124. }
  125. _renderPanelContent: () => React$Node | null;
  126. /**
  127. * Renders the contents of the chat panel.
  128. *
  129. * @private
  130. * @returns {ReactElement | null}
  131. */
  132. _renderPanelContent() {
  133. const { _isModal, _isOpen, _showNamePrompt } = this.props;
  134. let ComponentToRender = null;
  135. if (_isOpen) {
  136. if (_isModal) {
  137. ComponentToRender = (
  138. <ChatDialog>
  139. { _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
  140. </ChatDialog>
  141. );
  142. } else {
  143. ComponentToRender = (
  144. <>
  145. { this._renderChatHeader() }
  146. { _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
  147. </>
  148. );
  149. }
  150. }
  151. let className = '';
  152. if (_isOpen) {
  153. className = 'slideInExt';
  154. } else if (this._isExited) {
  155. className = 'invisible';
  156. }
  157. return (
  158. <div
  159. className = { `sideToolbarContainer ${className}` }
  160. id = 'sideToolbarContainer'>
  161. { ComponentToRender }
  162. </div>
  163. );
  164. }
  165. /**
  166. * Scrolls the chat messages so the latest message is visible.
  167. *
  168. * @param {boolean} withAnimation - Whether or not to show a scrolling
  169. * animation.
  170. * @private
  171. * @returns {void}
  172. */
  173. _scrollMessageContainerToBottom(withAnimation) {
  174. if (this._messageContainerRef.current) {
  175. this._messageContainerRef.current.scrollToBottom(withAnimation);
  176. }
  177. }
  178. _onSendMessage: (string) => void;
  179. }
  180. export default translate(connect(_mapStateToProps)(Chat));