signal-desktop/ts/state/selectors/search.ts

277 lines
7.3 KiB
TypeScript
Raw Normal View History

import memoizee from 'memoizee';
2019-01-14 21:49:58 +00:00
import { createSelector } from 'reselect';
import { getSearchResultsProps } from '../../shims/Whisper';
2019-01-14 21:49:58 +00:00
import { StateType } from '../reducer';
import {
MessageSearchResultLookupType,
MessageSearchResultType,
SearchStateType,
} from '../ducks/search';
import {
ConversationLookupType,
ConversationType,
} from '../ducks/conversations';
import {
PropsDataType as SearchResultsPropsType,
SearchResultRowType,
} from '../../components/SearchResults';
import { PropsDataType as MessageSearchResultPropsDataType } from '../../components/MessageSearchResult';
import { getRegionCode, getUserNumber } from './user';
import {
GetConversationByIdType,
2019-01-14 21:49:58 +00:00
getConversationLookup,
getConversationSelector,
2019-01-14 21:49:58 +00:00
getSelectedConversation,
} from './conversations';
2019-03-12 00:20:16 +00:00
2019-01-14 21:49:58 +00:00
export const getSearch = (state: StateType): SearchStateType => state.search;
export const getQuery = createSelector(
getSearch,
(state: SearchStateType): string => state.query
);
export const getSelectedMessage = createSelector(
getSearch,
(state: SearchStateType): string | undefined => state.selectedMessage
);
2019-08-09 23:12:29 +00:00
export const getSearchConversationId = createSelector(
getSearch,
(state: SearchStateType): string | undefined => state.searchConversationId
);
export const getSearchConversationName = createSelector(
getSearch,
(state: SearchStateType): string | undefined => state.searchConversationName
);
2019-01-14 21:49:58 +00:00
export const isSearching = createSelector(
getSearch,
(state: SearchStateType) => {
2019-08-20 19:34:52 +00:00
const { query } = state;
2019-01-14 21:49:58 +00:00
2019-08-20 19:34:52 +00:00
return query && query.trim().length > 1;
2019-01-14 21:49:58 +00:00
}
);
export const getMessageSearchResultLookup = createSelector(
getSearch,
(state: SearchStateType) => state.messageLookup
);
2019-01-14 21:49:58 +00:00
export const getSearchResults = createSelector(
[getSearch, getRegionCode, getConversationLookup, getSelectedConversation],
2019-01-14 21:49:58 +00:00
(
state: SearchStateType,
2019-03-12 00:20:16 +00:00
regionCode: string,
2019-01-14 21:49:58 +00:00
lookup: ConversationLookupType,
selectedConversation?: string
// tslint:disable-next-line max-func-body-length
2019-08-20 19:34:52 +00:00
): SearchResultsPropsType | undefined => {
2019-08-09 23:12:29 +00:00
const {
contacts,
conversations,
discussionsLoading,
2019-08-09 23:12:29 +00:00
messageIds,
messagesLoading,
2019-08-09 23:12:29 +00:00
searchConversationName,
} = state;
const showStartNewConversation = Boolean(
state.normalizedPhoneNumber && !lookup[state.normalizedPhoneNumber]
);
const haveConversations = conversations && conversations.length;
const haveContacts = contacts && contacts.length;
const haveMessages = messageIds && messageIds.length;
const noResults =
!discussionsLoading &&
!messagesLoading &&
!showStartNewConversation &&
!haveConversations &&
!haveContacts &&
!haveMessages;
const items: Array<SearchResultRowType> = [];
if (showStartNewConversation) {
items.push({
type: 'start-new-conversation',
data: undefined,
});
}
if (haveConversations) {
items.push({
type: 'conversations-header',
data: undefined,
});
conversations.forEach(id => {
const data = lookup[id];
items.push({
type: 'conversation',
data: {
...data,
isSelected: Boolean(data && id === selectedConversation),
},
});
});
} else if (discussionsLoading) {
items.push({
type: 'conversations-header',
data: undefined,
});
items.push({
type: 'spinner',
data: undefined,
});
}
if (haveContacts) {
items.push({
type: 'contacts-header',
data: undefined,
});
contacts.forEach(id => {
const data = lookup[id];
items.push({
type: 'contact',
data: {
...data,
isSelected: Boolean(data && id === selectedConversation),
},
});
});
}
if (haveMessages) {
items.push({
type: 'messages-header',
data: undefined,
});
messageIds.forEach(messageId => {
items.push({
type: 'message',
data: messageId,
});
});
} else if (messagesLoading) {
items.push({
type: 'messages-header',
data: undefined,
});
items.push({
type: 'spinner',
data: undefined,
});
}
2019-01-14 21:49:58 +00:00
return {
discussionsLoading,
items,
messagesLoading,
noResults,
2019-03-12 00:20:16 +00:00
regionCode: regionCode,
2019-08-09 23:12:29 +00:00
searchConversationName,
2019-01-14 21:49:58 +00:00
searchTerm: state.query,
};
}
);
export function _messageSearchResultSelector(
message: MessageSearchResultType,
// @ts-ignore
ourNumber: string,
// @ts-ignore
regionCode: string,
// @ts-ignore
sender?: ConversationType,
// @ts-ignore
recipient?: ConversationType,
2019-08-09 23:12:29 +00:00
searchConversationId?: string,
selectedMessageId?: string
): MessageSearchResultPropsDataType {
// Note: We don't use all of those parameters here, but the shim we call does.
// We want to call this function again if any of those parameters change.
return {
...getSearchResultsProps(message),
isSelected: message.id === selectedMessageId,
2019-08-09 23:12:29 +00:00
isSearchingInConversation: Boolean(searchConversationId),
};
}
// A little optimization to reset our selector cache whenever high-level application data
// changes: regionCode and userNumber.
type CachedMessageSearchResultSelectorType = (
message: MessageSearchResultType,
ourNumber: string,
regionCode: string,
sender?: ConversationType,
recipient?: ConversationType,
2019-08-09 23:12:29 +00:00
searchConversationId?: string,
selectedMessageId?: string
) => MessageSearchResultPropsDataType;
export const getCachedSelectorForMessageSearchResult = createSelector(
getRegionCode,
getUserNumber,
(): CachedMessageSearchResultSelectorType => {
// Note: memoizee will check all parameters provided, and only run our selector
// if any of them have changed.
return memoizee(_messageSearchResultSelector, { max: 500 });
}
);
type GetMessageSearchResultByIdType = (
id: string
) => MessageSearchResultPropsDataType | undefined;
export const getMessageSearchResultSelector = createSelector(
getCachedSelectorForMessageSearchResult,
getMessageSearchResultLookup,
getSelectedMessage,
getConversationSelector,
2019-08-09 23:12:29 +00:00
getSearchConversationId,
getRegionCode,
getUserNumber,
(
messageSearchResultSelector: CachedMessageSearchResultSelectorType,
messageSearchResultLookup: MessageSearchResultLookupType,
selectedMessage: string | undefined,
conversationSelector: GetConversationByIdType,
2019-08-09 23:12:29 +00:00
searchConversationId: string | undefined,
regionCode: string,
ourNumber: string
): GetMessageSearchResultByIdType => {
return (id: string) => {
const message = messageSearchResultLookup[id];
if (!message) {
return;
}
const { conversationId, source, type } = message;
let sender: ConversationType | undefined;
let recipient: ConversationType | undefined;
if (type === 'incoming') {
sender = conversationSelector(source);
recipient = conversationSelector(ourNumber);
} else if (type === 'outgoing') {
sender = conversationSelector(ourNumber);
recipient = conversationSelector(conversationId);
}
return messageSearchResultSelector(
message,
ourNumber,
regionCode,
sender,
recipient,
2019-08-09 23:12:29 +00:00
searchConversationId,
selectedMessage
);
2019-01-14 21:49:58 +00:00
};
}
);