|  |  |  | import React from 'react'; | 
					
						
							|  |  |  | import classNames from 'classnames'; | 
					
						
							|  |  |  | import { AutoSizer, List } from 'react-virtualized'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   ConversationListItem, | 
					
						
							|  |  |  |   PropsData as ConversationListItemPropsType, | 
					
						
							|  |  |  | } from './ConversationListItem'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   PropsData as SearchResultsProps, | 
					
						
							|  |  |  |   SearchResults, | 
					
						
							|  |  |  | } from './SearchResults'; | 
					
						
							|  |  |  | import { LocalizerType } from '../types/Util'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface Props { | 
					
						
							|  |  |  |   conversations?: Array<ConversationListItemPropsType>; | 
					
						
							|  |  |  |   friends?: Array<ConversationListItemPropsType>; | 
					
						
							|  |  |  |   archivedConversations?: Array<ConversationListItemPropsType>; | 
					
						
							|  |  |  |   searchResults?: SearchResultsProps; | 
					
						
							|  |  |  |   showArchived?: boolean; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   i18n: LocalizerType; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Action Creators
 | 
					
						
							|  |  |  |   startNewConversation: ( | 
					
						
							|  |  |  |     query: string, | 
					
						
							|  |  |  |     options: { regionCode: string } | 
					
						
							|  |  |  |   ) => void; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   openConversationInternal: (id: string, messageId?: string) => void; | 
					
						
							|  |  |  |   showArchivedConversations: () => void; | 
					
						
							|  |  |  |   showInbox: () => void; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Render Props
 | 
					
						
							|  |  |  |   renderMainHeader: () => JSX.Element; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
 | 
					
						
							|  |  |  | type RowRendererParamsType = { | 
					
						
							|  |  |  |   index: number; | 
					
						
							|  |  |  |   isScrolling: boolean; | 
					
						
							|  |  |  |   isVisible: boolean; | 
					
						
							|  |  |  |   key: string; | 
					
						
							|  |  |  |   parent: Object; | 
					
						
							|  |  |  |   style: Object; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class LeftPane extends React.Component<Props, any> { | 
					
						
							|  |  |  |   public state = { | 
					
						
							|  |  |  |     currentTab: 'conversations', | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public getCurrentConversations(): | 
					
						
							|  |  |  |     | Array<ConversationListItemPropsType> | 
					
						
							|  |  |  |     | undefined { | 
					
						
							|  |  |  |     const { conversations, friends } = this.props; | 
					
						
							|  |  |  |     const { currentTab } = this.state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let conversationList = | 
					
						
							|  |  |  |       currentTab === 'conversations' ? conversations : friends; | 
					
						
							|  |  |  |     if (conversationList !== undefined) { | 
					
						
							|  |  |  |       conversationList = conversationList.filter( | 
					
						
							|  |  |  |         conversation => !conversation.isSecondary | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return conversationList; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderTabs(): JSX.Element { | 
					
						
							|  |  |  |     const { i18n } = this.props; | 
					
						
							|  |  |  |     const { currentTab } = this.state; | 
					
						
							|  |  |  |     const tabs = [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         id: 'conversations', | 
					
						
							|  |  |  |         name: i18n('conversationsTab'), | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         id: 'friends', | 
					
						
							|  |  |  |         name: i18n('friendsTab'), | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <div className="module-left-pane__tabs" key="tabs"> | 
					
						
							|  |  |  |         {tabs.map(tab => ( | 
					
						
							|  |  |  |           <div | 
					
						
							|  |  |  |             role="button" | 
					
						
							|  |  |  |             className={classNames('tab', tab.id === currentTab && 'selected')} | 
					
						
							|  |  |  |             key={tab.id} | 
					
						
							|  |  |  |             onClick={() => { | 
					
						
							|  |  |  |               this.setState({ currentTab: tab.id }); | 
					
						
							|  |  |  |             }} | 
					
						
							|  |  |  |           > | 
					
						
							|  |  |  |             {tab.name} | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         ))} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderRow = ({ | 
					
						
							|  |  |  |     index, | 
					
						
							|  |  |  |     key, | 
					
						
							|  |  |  |     style, | 
					
						
							|  |  |  |   }: RowRendererParamsType): JSX.Element => { | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |       archivedConversations, | 
					
						
							|  |  |  |       i18n, | 
					
						
							|  |  |  |       openConversationInternal, | 
					
						
							|  |  |  |       showArchived, | 
					
						
							|  |  |  |     } = this.props; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { currentTab } = this.state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const conversations = this.getCurrentConversations(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!conversations || !archivedConversations) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'renderRow: Tried to render without conversations or archivedConversations' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!showArchived && index === conversations.length) { | 
					
						
							|  |  |  |       return this.renderArchivedButton({ key, style }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const conversation = showArchived | 
					
						
							|  |  |  |       ? archivedConversations[index] | 
					
						
							|  |  |  |       : conversations[index]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <ConversationListItem | 
					
						
							|  |  |  |         key={key} | 
					
						
							|  |  |  |         style={style} | 
					
						
							|  |  |  |         {...conversation} | 
					
						
							|  |  |  |         onClick={openConversationInternal} | 
					
						
							|  |  |  |         i18n={i18n} | 
					
						
							|  |  |  |         isFriendItem={currentTab !== 'conversations'} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderArchivedButton({ | 
					
						
							|  |  |  |     key, | 
					
						
							|  |  |  |     style, | 
					
						
							|  |  |  |   }: { | 
					
						
							|  |  |  |     key: string; | 
					
						
							|  |  |  |     style: Object; | 
					
						
							|  |  |  |   }): JSX.Element { | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |       archivedConversations, | 
					
						
							|  |  |  |       i18n, | 
					
						
							|  |  |  |       showArchivedConversations, | 
					
						
							|  |  |  |     } = this.props; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!archivedConversations || !archivedConversations.length) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'renderArchivedButton: Tried to render without archivedConversations' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <div | 
					
						
							|  |  |  |         key={key} | 
					
						
							|  |  |  |         className="module-left-pane__archived-button" | 
					
						
							|  |  |  |         style={style} | 
					
						
							|  |  |  |         role="button" | 
					
						
							|  |  |  |         onClick={showArchivedConversations} | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         {i18n('archivedConversations')}{' '} | 
					
						
							|  |  |  |         <span className="module-left-pane__archived-button__archived-count"> | 
					
						
							|  |  |  |           {archivedConversations.length} | 
					
						
							|  |  |  |         </span> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderList(): JSX.Element | Array<JSX.Element | null> { | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |       archivedConversations, | 
					
						
							|  |  |  |       i18n, | 
					
						
							|  |  |  |       openConversationInternal, | 
					
						
							|  |  |  |       startNewConversation, | 
					
						
							|  |  |  |       searchResults, | 
					
						
							|  |  |  |       showArchived, | 
					
						
							|  |  |  |     } = this.props; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (searchResults) { | 
					
						
							|  |  |  |       return ( | 
					
						
							|  |  |  |         <SearchResults | 
					
						
							|  |  |  |           {...searchResults} | 
					
						
							|  |  |  |           openConversation={openConversationInternal} | 
					
						
							|  |  |  |           startNewConversation={startNewConversation} | 
					
						
							|  |  |  |           i18n={i18n} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const conversations = this.getCurrentConversations(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!conversations || !archivedConversations) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'render: must provided conversations and archivedConverstions if no search results are provided' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // That extra 1 element added to the list is the 'archived converastions' button
 | 
					
						
							|  |  |  |     const length = showArchived | 
					
						
							|  |  |  |       ? archivedConversations.length | 
					
						
							|  |  |  |       : conversations.length + (archivedConversations.length ? 1 : 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const archived = showArchived ? ( | 
					
						
							|  |  |  |       <div className="module-left-pane__archive-helper-text" key={0}> | 
					
						
							|  |  |  |         {i18n('archiveHelperText')} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ) : null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // We ensure that the listKey differs between inbox and archive views, which ensures
 | 
					
						
							|  |  |  |     //   that AutoSizer properly detects the new size of its slot in the flexbox. The
 | 
					
						
							|  |  |  |     //   archive explainer text at the top of the archive view causes problems otherwise.
 | 
					
						
							|  |  |  |     //   It also ensures that we scroll to the top when switching views.
 | 
					
						
							|  |  |  |     const listKey = showArchived ? 1 : 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Note: conversations is not a known prop for List, but it is required to ensure that
 | 
					
						
							|  |  |  |     //   it re-renders when our conversation data changes. Otherwise it would just render
 | 
					
						
							|  |  |  |     //   on startup and scroll.
 | 
					
						
							|  |  |  |     const list = ( | 
					
						
							|  |  |  |       <div className="module-left-pane__list" key={listKey}> | 
					
						
							|  |  |  |         <AutoSizer> | 
					
						
							|  |  |  |           {({ height, width }) => ( | 
					
						
							|  |  |  |             <List | 
					
						
							|  |  |  |               className="module-left-pane__virtual-list" | 
					
						
							|  |  |  |               conversations={conversations} | 
					
						
							|  |  |  |               height={height} | 
					
						
							|  |  |  |               rowCount={length} | 
					
						
							|  |  |  |               rowHeight={64} | 
					
						
							|  |  |  |               rowRenderer={this.renderRow} | 
					
						
							|  |  |  |               width={width} | 
					
						
							|  |  |  |               autoHeight={true} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           )} | 
					
						
							|  |  |  |         </AutoSizer> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return [this.renderTabs(), archived, list]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public renderArchivedHeader(): JSX.Element { | 
					
						
							|  |  |  |     const { i18n, showInbox } = this.props; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <div className="module-left-pane__archive-header"> | 
					
						
							|  |  |  |         <div | 
					
						
							|  |  |  |           role="button" | 
					
						
							|  |  |  |           onClick={showInbox} | 
					
						
							|  |  |  |           className="module-left-pane__to-inbox-button" | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |         <div className="module-left-pane__archive-header-text"> | 
					
						
							|  |  |  |           {i18n('archivedConversations')} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public render(): JSX.Element { | 
					
						
							|  |  |  |     const { renderMainHeader, showArchived } = this.props; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <div className="module-left-pane"> | 
					
						
							|  |  |  |         <div className="module-left-pane__header"> | 
					
						
							|  |  |  |           {showArchived ? this.renderArchivedHeader() : renderMainHeader()} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         {this.renderList()} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |