session-desktop/ts/state/ducks/conversations.ts

340 lines
7 KiB
TypeScript
Raw Normal View History

2019-01-14 22:49:58 +01:00
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;
2019-03-12 01:20:16 +01:00
isArchived: boolean;
2019-01-14 22:49:58 +01:00
activeAt?: number;
timestamp: number;
lastMessage?: {
status: 'error' | 'sending' | 'sent' | 'delivered' | 'read';
text: string;
2019-07-23 08:15:39 +02:00
isRss: boolean;
2019-01-14 22:49:58 +01:00
};
phoneNumber: string;
type: 'direct' | 'group';
isMe: boolean;
isClosable?: boolean;
2019-01-14 22:49:58 +01:00
lastUpdated: number;
unreadCount: number;
isSelected: boolean;
isTyping: boolean;
2019-04-18 02:37:23 +02:00
isFriend?: boolean;
2019-01-14 22:49:58 +01:00
};
export type ConversationLookupType = {
[key: string]: ConversationType;
};
export type ConversationsStateType = {
conversationLookup: ConversationLookupType;
selectedConversation?: string;
2019-03-12 01:20:16 +01:00
showArchived: boolean;
2019-01-14 22:49:58 +01:00
};
// 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;
};
};
2019-03-12 01:20:16 +01:00
type ShowInboxActionType = {
type: 'SHOW_INBOX';
payload: null;
};
type ShowArchivedConversationsActionType = {
type: 'SHOW_ARCHIVED_CONVERSATIONS';
payload: null;
};
2019-01-14 22:49:58 +01:00
export type ConversationActionType =
| ConversationAddedActionType
| ConversationChangedActionType
| ConversationRemovedActionType
| RemoveAllConversationsActionType
| MessageExpiredActionType
2019-03-12 01:20:16 +01:00
| SelectedConversationChangedActionType
| MessageExpiredActionType
| SelectedConversationChangedActionType
| ShowInboxActionType
| ShowArchivedConversationsActionType;
2019-01-14 22:49:58 +01:00
// Action Creators
export const actions = {
conversationAdded,
conversationChanged,
conversationRemoved,
removeAllConversations,
messageExpired,
openConversationInternal,
openConversationExternal,
2019-03-12 01:20:16 +01:00
showInbox,
showArchivedConversations,
2019-01-14 22:49:58 +01:00
};
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,
};
}
2019-03-12 01:20:16 +01:00
2019-01-14 22:49:58 +01:00
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,
},
};
}
2019-03-12 01:20:16 +01:00
function showInbox() {
return {
type: 'SHOW_INBOX',
payload: null,
};
}
function showArchivedConversations() {
return {
type: 'SHOW_ARCHIVED_CONVERSATIONS',
payload: null,
};
}
2019-01-14 22:49:58 +01:00
// Reducer
function getEmptyState(): ConversationsStateType {
return {
conversationLookup: {},
2019-03-12 01:20:16 +01:00
showArchived: false,
2019-01-14 22:49:58 +01:00
};
}
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 === 'CONVERSATION_CHANGED') {
const { payload } = action;
const { id, data } = payload;
const { conversationLookup } = state;
2019-03-12 01:20:16 +01:00
let showArchived = state.showArchived;
let selectedConversation = state.selectedConversation;
const existing = conversationLookup[id];
2019-01-14 22:49:58 +01:00
// In the change case we only modify the lookup if we already had that conversation
2019-03-12 01:20:16 +01:00
if (!existing) {
2019-01-14 22:49:58 +01:00
return state;
}
2019-03-12 01:20:16 +01:00
if (selectedConversation === id) {
// Archived -> Inbox: we go back to the normal inbox view
if (existing.isArchived && !data.isArchived) {
showArchived = false;
}
// Inbox -> Archived: no conversation is selected
// Note: With today's stacked converastions architecture, this can result in weird
// behavior - no selected conversation in the left pane, but a conversation show
// in the right pane.
if (!existing.isArchived && data.isArchived) {
selectedConversation = undefined;
}
}
2019-01-14 22:49:58 +01:00
return {
...state,
2019-03-12 01:20:16 +01:00
selectedConversation,
showArchived,
2019-01-14 22:49:58 +01:00
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
}
2019-03-12 01:20:16 +01:00
if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
const { payload } = action;
const { id } = payload;
return {
...state,
selectedConversation: id,
};
}
if (action.type === 'SHOW_INBOX') {
return {
...state,
showArchived: false,
};
}
if (action.type === 'SHOW_ARCHIVED_CONVERSATIONS') {
return {
...state,
showArchived: true,
};
}
2019-01-14 22:49:58 +01:00
return state;
}