Archive Conversation

This commit is contained in:
Scott Nonnenberg 2019-03-11 17:20:16 -07:00
parent d72f89d776
commit 6ffbc0ac06
20 changed files with 568 additions and 109 deletions

View file

@ -129,8 +129,14 @@ window.searchResults.messages = [
<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
<LeftPane
searchResults={window.searchResults}
openConversation={result => console.log('openConversation', result)}
openMessage={result => console.log('onClickMessage', result)}
startNewConversation={(query, options) =>
console.log('startNewConversation', query, options)
}
openConversationInternal={(id, messageId) =>
console.log('openConversation', id, messageId)
}
showArchivedConversations={() => console.log('showArchivedConversations')}
showInbox={() => console.log('showInbox')}
renderMainHeader={() => (
<MainHeader
searchTerm="Hi there!"
@ -151,8 +157,74 @@ window.searchResults.messages = [
<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
<LeftPane
conversations={window.searchResults.conversations}
openConversation={result => console.log('openConversation', result)}
openMessage={result => console.log('onClickMessage', result)}
archivedConversations={[]}
startNewConversation={(query, options) =>
console.log('startNewConversation', query, options)
}
openConversationInternal={(id, messageId) =>
console.log('openConversation', id, messageId)
}
showArchivedConversations={() => console.log('showArchivedConversations')}
showInbox={() => console.log('showInbox')}
renderMainHeader={() => (
<MainHeader
searchTerm="Hi there!"
search={result => console.log('search', result)}
updateSearch={result => console.log('updateSearch', result)}
clearSearch={result => console.log('clearSearch', result)}
i18n={util.i18n}
/>
)}
i18n={util.i18n}
/>
</util.LeftPaneContext>
```
#### Showing inbox, with some archived
```jsx
<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
<LeftPane
conversations={window.searchResults.conversations.slice(0, 2)}
archivedConversations={window.searchResults.conversations.slice(2)}
startNewConversation={(query, options) =>
console.log('startNewConversation', query, options)
}
openConversationInternal={(id, messageId) =>
console.log('openConversation', id, messageId)
}
showArchivedConversations={() => console.log('showArchivedConversations')}
showInbox={() => console.log('showInbox')}
renderMainHeader={() => (
<MainHeader
searchTerm="Hi there!"
search={result => console.log('search', result)}
updateSearch={result => console.log('updateSearch', result)}
clearSearch={result => console.log('clearSearch', result)}
i18n={util.i18n}
/>
)}
i18n={util.i18n}
/>
</util.LeftPaneContext>
```
#### Showing archived conversations
```jsx
<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
<LeftPane
conversations={window.searchResults.conversations.slice(0, 2)}
archivedConversations={window.searchResults.conversations.slice(2)}
showArchived={true}
startNewConversation={(query, options) =>
console.log('startNewConversation', query, options)
}
openConversationInternal={(id, messageId) =>
console.log('openConversation', id, messageId)
}
showArchivedConversations={() => console.log('showArchivedConversations')}
showInbox={() => console.log('showInbox')}
renderMainHeader={() => (
<MainHeader
searchTerm="Hi there!"

View file

@ -13,19 +13,27 @@ import { LocalizerType } from '../types/Util';
export interface Props {
conversations?: Array<ConversationListItemPropsType>;
archivedConversations?: Array<ConversationListItemPropsType>;
searchResults?: SearchResultsProps;
showArchived?: boolean;
i18n: LocalizerType;
// Action Creators
startNewConversation: () => void;
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 RowRendererParams = {
type RowRendererParamsType = {
index: number;
isScrolling: boolean;
isVisible: boolean;
@ -35,12 +43,51 @@ type RowRendererParams = {
};
export class LeftPane extends React.Component<Props> {
public renderRow = ({ index, key, style }: RowRendererParams) => {
const { conversations, i18n, openConversationInternal } = this.props;
if (!conversations) {
return null;
public listRef: React.RefObject<any> = React.createRef();
public scrollToTop() {
if (this.listRef && this.listRef.current) {
const { current } = this.listRef;
current.scrollToRow(0);
}
const conversation = conversations[index];
}
public componentDidUpdate(prevProps: Props) {
const { showArchived, searchResults } = this.props;
const isNotShowingSearchResults = !searchResults;
const hasArchiveViewChanged = showArchived !== prevProps.showArchived;
if (isNotShowingSearchResults && hasArchiveViewChanged) {
this.scrollToTop();
}
}
public renderRow = ({
index,
key,
style,
}: RowRendererParamsType): JSX.Element => {
const {
archivedConversations,
conversations,
i18n,
openConversationInternal,
showArchived,
} = this.props;
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
@ -53,13 +100,50 @@ export class LeftPane extends React.Component<Props> {
);
};
public renderList() {
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 {
const {
archivedConversations,
i18n,
conversations,
openConversationInternal,
startNewConversation,
searchResults,
showArchived,
} = this.props;
if (searchResults) {
@ -73,22 +157,35 @@ export class LeftPane extends React.Component<Props> {
);
}
if (!conversations || !conversations.length) {
return null;
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);
// 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.
return (
<div className="module-left-pane__list">
{showArchived ? (
<div className="module-left-pane__archive-helper-text">
{i18n('archiveHelperText')}
</div>
) : null}
<AutoSizer>
{({ height, width }) => (
<List
className="module-left-pane__virtual-list"
ref={this.listRef}
conversations={conversations}
height={height}
rowCount={conversations.length}
rowCount={length}
rowHeight={64}
rowRenderer={this.renderRow}
width={width}
@ -99,12 +196,31 @@ export class LeftPane extends React.Component<Props> {
);
}
public render() {
const { renderMainHeader } = this.props;
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">{renderMainHeader()}</div>
<div className="module-left-pane__header">
{showArchived ? this.renderArchivedHeader() : renderMainHeader()}
</div>
{this.renderList()}
</div>
);

View file

@ -113,7 +113,9 @@ window.searchResults.messages = [
i18n={util.i18n}
onClickMessage={id => console.log('onClickMessage', id)}
onClickConversation={id => console.log('onClickConversation', id)}
onStartNewConversation={() => console.log('onStartNewConversation')}
onStartNewConversation={(query, options) =>
console.log('onStartNewConversation', query, options)
}
/>
</util.LeftPaneContext>;
```
@ -131,7 +133,9 @@ window.searchResults.messages = [
i18n={util.i18n}
onClickMessage={id => console.log('onClickMessage', id)}
onClickConversation={id => console.log('onClickConversation', id)}
onStartNewConversation={() => console.log('onStartNewConversation')}
onStartNewConversation={(query, options) =>
console.log('onStartNewConversation', query, options)
}
/>
</util.LeftPaneContext>
```
@ -147,7 +151,9 @@ window.searchResults.messages = [
i18n={util.i18n}
onClickMessage={id => console.log('onClickMessage', id)}
onClickConversation={id => console.log('onClickConversation', id)}
onStartNewConversation={() => console.log('onStartNewConversation')}
onStartNewConversation={(query, options) =>
console.log('onStartNewConversation', query, options)
}
/>
</util.LeftPaneContext>
```
@ -163,7 +169,9 @@ window.searchResults.messages = [
i18n={util.i18n}
onClickMessage={id => console.log('onClickMessage', id)}
onClickConversation={id => console.log('onClickConversation', id)}
onStartNewConversation={() => console.log('onStartNewConversation')}
onStartNewConversation={(query, options) =>
console.log('onStartNewConversation', query, options)
}
/>
</util.LeftPaneContext>
```

View file

@ -16,6 +16,7 @@ export type PropsData = {
conversations: Array<ConversationListItemPropsType>;
hideMessagesHeader: boolean;
messages: Array<MessageSearchResultPropsType>;
regionCode: string;
searchTerm: string;
showStartNewConversation: boolean;
};
@ -23,12 +24,21 @@ export type PropsData = {
type PropsHousekeeping = {
i18n: LocalizerType;
openConversation: (id: string, messageId?: string) => void;
startNewConversation: (id: string) => void;
startNewConversation: (
query: string,
options: { regionCode: string }
) => void;
};
type Props = PropsData & PropsHousekeeping;
export class SearchResults extends React.Component<Props> {
public handleStartNewConversation = () => {
const { regionCode, searchTerm, startNewConversation } = this.props;
startNewConversation(searchTerm, { regionCode });
};
public render() {
const {
conversations,
@ -37,7 +47,6 @@ export class SearchResults extends React.Component<Props> {
i18n,
messages,
openConversation,
startNewConversation,
searchTerm,
showStartNewConversation,
} = this.props;
@ -62,7 +71,7 @@ export class SearchResults extends React.Component<Props> {
<StartNewConversation
phoneNumber={searchTerm}
i18n={i18n}
onClick={startNewConversation}
onClick={this.handleStartNewConversation}
/>
) : null}
{haveConversations ? (

View file

@ -7,7 +7,7 @@ import { LocalizerType } from '../types/Util';
export interface Props {
phoneNumber: string;
i18n: LocalizerType;
onClick: (id: string) => void;
onClick: () => void;
}
export class StartNewConversation extends React.PureComponent<Props> {
@ -18,9 +18,7 @@ export class StartNewConversation extends React.PureComponent<Props> {
<div
role="button"
className="module-start-new-conversation"
onClick={() => {
onClick(phoneNumber);
}}
onClick={onClick}
>
<Avatar
color="grey"

View file

@ -16,17 +16,19 @@ interface TimerOption {
}
interface Props {
i18n: LocalizerType;
isVerified: boolean;
name?: string;
id: string;
name?: string;
phoneNumber: string;
profileName?: string;
color: string;
avatarPath?: string;
isVerified: boolean;
isMe: boolean;
isGroup: boolean;
isArchived: boolean;
expirationSettingName?: string;
showBackButton: boolean;
timerOptions: Array<TimerOption>;
@ -39,6 +41,11 @@ interface Props {
onShowAllMedia: () => void;
onShowGroupMembers: () => void;
onGoBack: () => void;
onArchive: () => void;
onMoveToInbox: () => void;
i18n: LocalizerType;
}
export class ConversationHeader extends React.Component<Props> {
@ -184,12 +191,15 @@ export class ConversationHeader extends React.Component<Props> {
i18n,
isMe,
isGroup,
isArchived,
onDeleteMessages,
onResetSession,
onSetDisappearingMessages,
onShowAllMedia,
onShowGroupMembers,
onShowSafetyNumber,
onArchive,
onMoveToInbox,
timerOptions,
} = this.props;
@ -223,6 +233,13 @@ export class ConversationHeader extends React.Component<Props> {
{!isGroup ? (
<MenuItem onClick={onResetSession}>{i18n('resetSession')}</MenuItem>
) : null}
{isArchived ? (
<MenuItem onClick={onMoveToInbox}>
{i18n('moveConversationToInbox')}
</MenuItem>
) : (
<MenuItem onClick={onArchive}>{i18n('archiveConversation')}</MenuItem>
)}
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
</ContextMenu>
);