273 lines
5.4 KiB
TypeScript
273 lines
5.4 KiB
TypeScript
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;
|
|
}
|