You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			271 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			271 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
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;
 | 
						|
 | 
						|
    return currentTab === 'conversations' ? conversations : friends;
 | 
						|
  }
 | 
						|
 | 
						|
  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}
 | 
						|
            />
 | 
						|
          )}
 | 
						|
        </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>
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |