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

Chat.js 5.8KB

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