Split search actions between discussions and messages

This commit is contained in:
Ken Powers 2019-09-04 10:46:28 -04:00 committed by GitHub
parent 1ab844674a
commit 1d3fe4bbf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 213 additions and 93 deletions

View file

@ -3219,20 +3219,6 @@
padding-right: 1em;
width: 100%;
text-align: center;
animation: delayed-fade-in 2s;
}
@keyframes delayed-fade-in {
0% {
opacity: 0;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.module-search-results__contacts-header {
@ -3257,6 +3243,13 @@
letter-spacing: 0;
}
.module-search-results__spinner-container {
width: 100%;
padding: 10px;
text-align: center;
}
// Module: Message Search Result
.module-message-search-result {

View file

@ -1396,7 +1396,7 @@ body.dark-theme {
}
&:focus {
border: solid 1px blue;
border: solid 1px $color-signal-blue;
outline: none;
}
}

View file

@ -25,11 +25,16 @@ export interface PropsType {
i18n: LocalizerType;
updateSearchTerm: (searchTerm: string) => void;
search: (
searchMessages: (
query: string,
options: {
searchConversationId?: string;
regionCode: string;
}
) => void;
searchDiscussions: (
query: string,
options: {
ourNumber: string;
noteToSelf: string;
}
@ -66,15 +71,21 @@ export class MainHeader extends React.Component<PropsType> {
i18n,
ourNumber,
regionCode,
search,
searchDiscussions,
searchMessages,
searchConversationId,
} = this.props;
if (search) {
search(searchTerm, {
searchConversationId,
if (searchDiscussions) {
searchDiscussions(searchTerm, {
noteToSelf: i18n('noteToSelf').toLowerCase(),
ourNumber,
});
}
if (searchMessages) {
searchMessages(searchTerm, {
searchConversationId,
regionCode,
});
}

View file

@ -8,6 +8,7 @@ import {
import { Intl } from './Intl';
import { Emojify } from './conversation/Emojify';
import { Spinner } from './Spinner';
import {
ConversationListItem,
PropsData as ConversationListItemPropsType,
@ -17,11 +18,13 @@ import { StartNewConversation } from './StartNewConversation';
import { LocalizerType } from '../types/Util';
export type PropsDataType = {
discussionsLoading: boolean;
items: Array<SearchResultRowType>;
messagesLoading: boolean;
noResults: boolean;
regionCode: string;
searchTerm: string;
searchConversationName?: string;
searchTerm: string;
};
type StartNewConversationType = {
@ -52,6 +55,10 @@ type MessageType = {
type: 'message';
data: string;
};
type SpinnerType = {
type: 'spinner';
data: undefined;
};
export type SearchResultRowType =
| StartNewConversationType
@ -60,7 +67,8 @@ export type SearchResultRowType =
| MessagesHeaderType
| ConversationType
| ContactsType
| MessageType;
| MessageType
| SpinnerType;
type PropsHousekeepingType = {
i18n: LocalizerType;
@ -89,7 +97,7 @@ export class SearchResults extends React.Component<PropsType> {
public mostRecentWidth = 0;
public mostRecentHeight = 0;
public cellSizeCache = new CellMeasurerCache({
defaultHeight: 36,
defaultHeight: 80,
fixedWidth: true,
});
public listRef = React.createRef<any>();
@ -160,6 +168,12 @@ export class SearchResults extends React.Component<PropsType> {
const { data } = row;
return renderMessageSearchResult(data);
} else if (row.type === 'spinner') {
return (
<div className="module-search-results__spinner-container">
<Spinner size="24px" svgSize="small" />
</div>
);
} else {
throw new Error(
'SearchResults.renderRowContents: Encountered unknown row type'
@ -194,14 +208,24 @@ export class SearchResults extends React.Component<PropsType> {
};
public componentDidUpdate(prevProps: PropsType) {
const { items } = this.props;
const {
items,
searchTerm,
discussionsLoading,
messagesLoading,
} = this.props;
if (
if (searchTerm !== prevProps.searchTerm) {
this.resizeAll();
} else if (
discussionsLoading !== prevProps.discussionsLoading ||
messagesLoading !== prevProps.messagesLoading
) {
this.resizeAll();
} else if (
items &&
items.length > 0 &&
prevProps.items &&
prevProps.items.length > 0 &&
items !== prevProps.items
prevProps.items.length !== items.length
) {
this.resizeAll();
}
@ -270,7 +294,7 @@ export class SearchResults extends React.Component<PropsType> {
}
return (
<div className="module-search-results" key={searchTerm}>
<div className="module-search-results">
<AutoSizer>
{({ height, width }) => {
this.mostRecentWidth = width;

View file

@ -4,8 +4,8 @@ import { normalize } from '../../types/PhoneNumber';
import { trigger } from '../../shims/events';
import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import {
searchConversations,
searchMessages,
searchConversations as dataSearchConversations,
searchMessages as dataSearchMessages,
searchMessagesInConversation,
} from '../../../js/modules/data';
import { makeLookup } from '../../util/makeLookup';
@ -40,25 +40,48 @@ export type SearchStateType = {
// We do store message data to pass through the selector
messageLookup: MessageSearchResultLookupType;
selectedMessage?: string;
// Loading state
discussionsLoading: boolean;
messagesLoading: boolean;
};
// Actions
type SearchResultsPayloadType = {
type SearchResultsBaseType = {
query: string;
normalizedPhoneNumber?: string;
};
type SearchMessagesResultsPayloadType = SearchResultsBaseType & {
messages: Array<MessageSearchResultType>;
};
type SearchDiscussionsResultsPayloadType = SearchResultsBaseType & {
conversations: Array<string>;
contacts: Array<string>;
};
type SearchResultsKickoffActionType = {
type: 'SEARCH_RESULTS';
payload: Promise<SearchResultsPayloadType>;
type SearchMessagesResultsKickoffActionType = {
type: 'SEARCH_MESSAGES_RESULTS';
payload: Promise<SearchMessagesResultsPayloadType>;
};
type SearchResultsFulfilledActionType = {
type: 'SEARCH_RESULTS_FULFILLED';
payload: SearchResultsPayloadType;
type SearchDiscussionsResultsKickoffActionType = {
type: 'SEARCH_DISCUSSIONS_RESULTS';
payload: Promise<SearchDiscussionsResultsPayloadType>;
};
type SearchMessagesResultsPendingActionType = {
type: 'SEARCH_MESSAGES_RESULTS_PENDING';
payload: SearchMessagesResultsPayloadType;
};
type SearchDiscussionsResultsPendingActionType = {
type: 'SEARCH_DISCUSSIONS_RESULTS_PENDING';
payload: SearchDiscussionsResultsPayloadType;
};
type SearchMessagesResultsFulfilledActionType = {
type: 'SEARCH_MESSAGES_RESULTS_FULFILLED';
payload: SearchMessagesResultsPayloadType;
};
type SearchDiscussionsResultsFulfilledActionType = {
type: 'SEARCH_DISCUSSIONS_RESULTS_FULFILLED';
payload: SearchDiscussionsResultsPayloadType;
};
type UpdateSearchTermActionType = {
type: 'SEARCH_UPDATE';
@ -83,7 +106,12 @@ type SearchInConversationActionType = {
};
export type SEARCH_TYPES =
| SearchResultsFulfilledActionType
| SearchMessagesResultsKickoffActionType
| SearchDiscussionsResultsKickoffActionType
| SearchMessagesResultsPendingActionType
| SearchDiscussionsResultsPendingActionType
| SearchMessagesResultsFulfilledActionType
| SearchDiscussionsResultsFulfilledActionType
| UpdateSearchTermActionType
| ClearSearchActionType
| ClearConversationSearchActionType
@ -95,7 +123,8 @@ export type SEARCH_TYPES =
// Action Creators
export const actions = {
search,
searchMessages,
searchDiscussions,
clearSearch,
clearConversationSearch,
searchInConversation,
@ -103,59 +132,73 @@ export const actions = {
startNewConversation,
};
function search(
function searchMessages(
query: string,
options: {
searchConversationId?: string;
regionCode: string;
ourNumber: string;
noteToSelf: string;
}
): SearchResultsKickoffActionType {
): SearchMessagesResultsKickoffActionType {
return {
type: 'SEARCH_RESULTS',
payload: doSearch(query, options),
type: 'SEARCH_MESSAGES_RESULTS',
payload: doSearchMessages(query, options),
};
}
async function doSearch(
function searchDiscussions(
query: string,
options: {
ourNumber: string;
noteToSelf: string;
}
): SearchDiscussionsResultsKickoffActionType {
return {
type: 'SEARCH_DISCUSSIONS_RESULTS',
payload: doSearchDiscussions(query, options),
};
}
async function doSearchMessages(
query: string,
options: {
searchConversationId?: string;
regionCode: string;
}
): Promise<SearchMessagesResultsPayloadType> {
const { regionCode, searchConversationId } = options;
const normalizedPhoneNumber = normalize(query, { regionCode });
const messages = await queryMessages(query, searchConversationId);
return {
messages,
normalizedPhoneNumber,
query,
};
}
async function doSearchDiscussions(
query: string,
options: {
ourNumber: string;
noteToSelf: string;
}
): Promise<SearchResultsPayloadType> {
const { regionCode, ourNumber, noteToSelf, searchConversationId } = options;
const normalizedPhoneNumber = normalize(query, { regionCode });
): Promise<SearchDiscussionsResultsPayloadType> {
const { ourNumber, noteToSelf } = options;
const { conversations, contacts } = await queryConversationsAndContacts(
query,
{
ourNumber,
noteToSelf,
}
);
if (searchConversationId) {
const messages = await queryMessages(query, searchConversationId);
return {
contacts: [],
conversations: [],
messages,
normalizedPhoneNumber,
query,
};
} else {
const [discussions, messages] = await Promise.all([
queryConversationsAndContacts(query, { ourNumber, noteToSelf }),
queryMessages(query),
]);
const { conversations, contacts } = discussions;
return {
contacts,
conversations,
messages,
normalizedPhoneNumber,
query,
};
}
return {
conversations,
contacts,
query,
};
}
function clearSearch(): ClearSearchActionType {
return {
type: 'SEARCH_CLEAR',
@ -214,7 +257,7 @@ async function queryMessages(query: string, searchConversationId?: string) {
return searchMessagesInConversation(normalized, searchConversationId);
}
return searchMessages(normalized);
return dataSearchMessages(normalized);
} catch (e) {
return [];
}
@ -227,7 +270,7 @@ async function queryConversationsAndContacts(
const { ourNumber, noteToSelf } = options;
const query = providedQuery.replace(/[+-.()]*/g, '');
const searchResults: Array<ConversationType> = await searchConversations(
const searchResults: Array<ConversationType> = await dataSearchConversations(
query
);
@ -266,6 +309,8 @@ function getEmptyState(): SearchStateType {
messageLookup: {},
conversations: [],
contacts: [],
discussionsLoading: false,
messagesLoading: false,
};
}
@ -312,15 +357,27 @@ export function reducer(
};
}
if (action.type === 'SEARCH_RESULTS_FULFILLED') {
if (action.type === 'SEARCH_MESSAGES_RESULTS_PENDING') {
return {
...state,
messageIds: [],
messageLookup: {},
messagesLoading: true,
};
}
if (action.type === 'SEARCH_DISCUSSIONS_RESULTS_PENDING') {
return {
...state,
contacts: [],
conversations: [],
discussionsLoading: true,
};
}
if (action.type === 'SEARCH_MESSAGES_RESULTS_FULFILLED') {
const { payload } = action;
const {
contacts,
conversations,
messages,
normalizedPhoneNumber,
query,
} = payload;
const { messages, normalizedPhoneNumber, query } = payload;
// Reject if the associated query is not the most recent user-provided query
if (state.query !== query) {
@ -331,12 +388,23 @@ export function reducer(
return {
...state,
contacts,
conversations,
normalizedPhoneNumber,
query,
messageIds,
messageLookup: makeLookup(messages, 'id'),
messagesLoading: false,
};
}
if (action.type === 'SEARCH_DISCUSSIONS_RESULTS_FULFILLED') {
const { payload } = action;
const { contacts, conversations } = payload;
return {
...state,
contacts,
conversations,
discussionsLoading: false,
};
}

View file

@ -63,7 +63,6 @@ export const getMessageSearchResultLookup = createSelector(
getSearch,
(state: SearchStateType) => state.messageLookup
);
export const getSearchResults = createSelector(
[getSearch, getRegionCode, getConversationLookup, getSelectedConversation],
(
@ -71,11 +70,14 @@ export const getSearchResults = createSelector(
regionCode: string,
lookup: ConversationLookupType,
selectedConversation?: string
// tslint:disable-next-line max-func-body-length
): SearchResultsPropsType | undefined => {
const {
contacts,
conversations,
discussionsLoading,
messageIds,
messagesLoading,
searchConversationName,
} = state;
@ -86,6 +88,8 @@ export const getSearchResults = createSelector(
const haveContacts = contacts && contacts.length;
const haveMessages = messageIds && messageIds.length;
const noResults =
!discussionsLoading &&
!messagesLoading &&
!showStartNewConversation &&
!haveConversations &&
!haveContacts &&
@ -115,6 +119,15 @@ export const getSearchResults = createSelector(
},
});
});
} else if (discussionsLoading) {
items.push({
type: 'conversations-header',
data: undefined,
});
items.push({
type: 'spinner',
data: undefined,
});
}
if (haveContacts) {
@ -145,10 +158,21 @@ export const getSearchResults = createSelector(
data: messageId,
});
});
} else if (messagesLoading) {
items.push({
type: 'messages-header',
data: undefined,
});
items.push({
type: 'spinner',
data: undefined,
});
}
return {
discussionsLoading,
items,
messagesLoading,
noResults,
regionCode: regionCode,
searchConversationName,

View file

@ -7502,7 +7502,7 @@
"rule": "React-createRef",
"path": "ts/components/MainHeader.js",
"line": " this.inputRef = react_1.default.createRef();",
"lineNumber": 83,
"lineNumber": 87,
"reasonCategory": "usageTrusted",
"updated": "2019-08-09T21:17:57.798Z",
"reasonDetail": "Used only to set focus"
@ -7511,7 +7511,7 @@
"rule": "React-createRef",
"path": "ts/components/MainHeader.tsx",
"line": " this.inputRef = React.createRef();",
"lineNumber": 48,
"lineNumber": 53,
"reasonCategory": "usageTrusted",
"updated": "2019-08-09T21:17:57.798Z",
"reasonDetail": "Used only to set focus"
@ -7520,7 +7520,7 @@
"rule": "React-createRef",
"path": "ts/components/SearchResults.js",
"line": " this.listRef = react_1.default.createRef();",
"lineNumber": 21,
"lineNumber": 22,
"reasonCategory": "usageTrusted",
"updated": "2019-08-09T00:44:31.008Z",
"reasonDetail": "SearchResults needs to interact with its child List directly"