import { omit } from 'lodash'; import { trigger } from '../../shims/events'; import { NoopActionType } from './noop'; // State export type MessageType = { id: string; conversationId: string; receivedAt: number; snippet: string; from: { phoneNumber: string; isMe?: boolean; name?: string; color?: string; profileName?: string; avatarPath?: string; }; to: { groupName?: string; phoneNumber: string; isMe?: boolean; name?: string; profileName?: string; }; isSelected?: boolean; }; export type ConversationType = { id: string; name?: string; activeAt?: number; timestamp: number; lastMessage?: { status: 'error' | 'sending' | 'sent' | 'delivered' | 'read'; text: string; }; phoneNumber: string; type: 'direct' | 'group'; isMe: boolean; lastUpdated: number; unreadCount: number; isSelected: boolean; isTyping: boolean; }; export type ConversationLookupType = { [key: string]: ConversationType; }; export type ConversationsStateType = { conversationLookup: ConversationLookupType; selectedConversation?: string; }; // Actions type ConversationAddedActionType = { type: 'CONVERSATION_ADDED'; payload: { id: string; data: ConversationType; }; }; type ConversationChangedActionType = { type: 'CONVERSATION_CHANGED'; payload: { id: string; data: ConversationType; }; }; type ConversationRemovedActionType = { type: 'CONVERSATION_REMOVED'; payload: { id: string; }; }; export type RemoveAllConversationsActionType = { type: 'CONVERSATIONS_REMOVE_ALL'; payload: null; }; export type MessageExpiredActionType = { type: 'MESSAGE_EXPIRED'; payload: { id: string; conversationId: string; }; }; export type SelectedConversationChangedActionType = { type: 'SELECTED_CONVERSATION_CHANGED'; payload: { id: string; messageId?: string; }; }; export type ConversationActionType = | ConversationAddedActionType | ConversationChangedActionType | ConversationRemovedActionType | RemoveAllConversationsActionType | MessageExpiredActionType | SelectedConversationChangedActionType; // Action Creators export const actions = { conversationAdded, conversationChanged, conversationRemoved, removeAllConversations, messageExpired, openConversationInternal, openConversationExternal, }; function conversationAdded( id: string, data: ConversationType ): ConversationAddedActionType { return { type: 'CONVERSATION_ADDED', payload: { id, data, }, }; } function conversationChanged( id: string, data: ConversationType ): ConversationChangedActionType { return { type: 'CONVERSATION_CHANGED', payload: { id, data, }, }; } function conversationRemoved(id: string): ConversationRemovedActionType { return { type: 'CONVERSATION_REMOVED', payload: { id, }, }; } function removeAllConversations(): RemoveAllConversationsActionType { return { type: 'CONVERSATIONS_REMOVE_ALL', payload: null, }; } function messageExpired( id: string, conversationId: string ): MessageExpiredActionType { return { type: 'MESSAGE_EXPIRED', payload: { id, conversationId, }, }; } // Note: we need two actions here to simplify. Operations outside of the left pane can // trigger an 'openConversation' so we go through Whisper.events for all conversation // selection. function openConversationInternal( id: string, messageId?: string ): NoopActionType { trigger('showConversation', id, messageId); return { type: 'NOOP', payload: null, }; } function openConversationExternal( id: string, messageId?: string ): SelectedConversationChangedActionType { return { type: 'SELECTED_CONVERSATION_CHANGED', payload: { id, messageId, }, }; } // Reducer function getEmptyState(): ConversationsStateType { return { conversationLookup: {}, }; } export function reducer( state: ConversationsStateType, action: ConversationActionType ): ConversationsStateType { if (!state) { return getEmptyState(); } if (action.type === 'CONVERSATION_ADDED') { const { payload } = action; const { id, data } = payload; const { conversationLookup } = state; return { ...state, conversationLookup: { ...conversationLookup, [id]: data, }, }; } if (action.type === 'SELECTED_CONVERSATION_CHANGED') { const { payload } = action; const { id } = payload; return { ...state, selectedConversation: id, }; } if (action.type === 'CONVERSATION_CHANGED') { const { payload } = action; const { id, data } = payload; const { conversationLookup } = state; // In the change case we only modify the lookup if we already had that conversation if (!conversationLookup[id]) { return state; } return { ...state, conversationLookup: { ...conversationLookup, [id]: data, }, }; } if (action.type === 'CONVERSATION_REMOVED') { const { payload } = action; const { id } = payload; const { conversationLookup } = state; return { ...state, conversationLookup: omit(conversationLookup, [id]), }; } if (action.type === 'CONVERSATIONS_REMOVE_ALL') { return getEmptyState(); } if (action.type === 'MESSAGE_EXPIRED') { // noop - for now this is only important for search } return state; }