moved conversations reducer to createSlice

pull/1783/head
Audric Ackermann 4 years ago
parent 63b81b4c8e
commit 16d34a7137
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -243,7 +243,7 @@ const ConversationListItem = (props: Props) => {
<div
role="button"
onClick={() => {
dispatch(openConversationExternal(conversationId));
dispatch(openConversationExternal({ id: conversationId }));
}}
onContextMenu={(e: any) => {
contextMenu.show({

@ -82,7 +82,7 @@ const AvatarItem = (props: { from: FindAndFormatContactType }) => {
);
};
export const MessageSearchResult = (props: Props) => {
const { from, id, isSelected, conversationId, receivedAt, snippet, to } = props;
const { from, id: messageId, isSelected, conversationId, receivedAt, snippet, to } = props;
const dispatch = useDispatch();
@ -94,7 +94,7 @@ export const MessageSearchResult = (props: Props) => {
<div
role="button"
onClick={() => {
dispatch(openConversationExternal(conversationId, id));
dispatch(openConversationExternal({ id: conversationId, messageId }));
}}
className={classNames(
'module-message-search-result',

@ -14,20 +14,19 @@ import {
getConversationHeaderProps,
getConversationHeaderTitleProps,
getSelectedConversation,
getSelectedMessageIds,
isMessageDetailView,
isMessageSelectionMode,
} from '../../state/selectors/conversations';
import { useDispatch, useSelector } from 'react-redux';
import { useMembersAvatars } from '../../hooks/useMembersAvatar';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
import {
closeMessageDetailsView,
openRightPanel,
resetSelectedMessageIds,
} from '../../state/ducks/conversationScreen';
import {
getSelectedMessageIds,
isMessageDetailView,
isMessageSelectionMode,
} from '../../state/selectors/conversationScreen';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
} from '../../state/ducks/conversations';
export interface TimerOption {
name: string;

@ -42,12 +42,12 @@ import autoBind from 'auto-bind';
import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer';
import { ClickToTrustSender } from './message/ClickToTrustSender';
import { getMessageById } from '../../data/data';
import { showMessageDetailsView } from '../../state/ducks/conversationScreen';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
import { getSelectedMessage } from '../../state/selectors/search';
import { connect } from 'react-redux';
import { StateType } from '../../state/reducer';
import { getSelectedMessageIds, isMessageSelected } from '../../state/selectors/conversationScreen';
import { getSelectedMessageIds } from '../../state/selectors/conversations';
import { showMessageDetailsView } from '../../state/ducks/conversations';
// Same as MIN_WIDTH in ImageGrid.tsx
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;

@ -8,8 +8,8 @@ import { Message } from './Message';
import { MessageRegularProps } from '../../models/messageType';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
import { useSelector } from 'react-redux';
import { getMessageDetailsViewProps } from '../../state/selectors/conversationScreen';
import { ContactPropsMessageDetail } from '../../state/ducks/conversations';
import { getMessageDetailsViewProps } from '../../state/selectors/conversations';
const AvatarItem = (props: { contact: ContactPropsMessageDetail }) => {
const { avatarPath, phoneNumber, name, profileName } = props.contact;

@ -37,7 +37,11 @@ import { getOpenGroupManager } from '../../opengroup/opengroupV2/OpenGroupManage
import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool';
import { getSwarmPollingInstance } from '../../session/snode_api';
import { DURATION } from '../../session/constants';
import { actions as conversationActions } from '../../state/ducks/conversations';
import {
actions as conversationActions,
conversationChanged,
conversationRemoved,
} from '../../state/ducks/conversations';
import { editProfileModal, onionPathModal } from '../../state/ducks/modalDialog';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { ModalContainer } from './ModalContainer';
@ -173,9 +177,9 @@ const removeAllV1OpenGroups = async () => {
window.log.info(`deleting v1convo : ${v1Convo.id}`);
getConversationController().unsafeDelete(v1Convo);
if (window.inboxStore) {
window.inboxStore?.dispatch(conversationActions.conversationRemoved(v1Convo.id));
window.inboxStore?.dispatch(conversationRemoved(v1Convo.id));
window.inboxStore?.dispatch(
conversationActions.conversationChanged(v1Convo.id, v1Convo.getProps())
conversationChanged({ id: v1Convo.id, data: v1Convo.getProps() })
);
}
} catch (e) {

@ -319,7 +319,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
pubkeyorOns,
ConversationTypeEnum.PRIVATE
);
window.inboxStore?.dispatch(openConversationExternal(pubkeyorOns));
window.inboxStore?.dispatch(openConversationExternal({ id: pubkeyorOns }));
this.handleToggleOverlay(undefined);
} else {
// this might be an ONS, validate the regex first
@ -339,7 +339,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
resolvedSessionID,
ConversationTypeEnum.PRIVATE
);
window.inboxStore?.dispatch(openConversationExternal(resolvedSessionID));
window.inboxStore?.dispatch(openConversationExternal({ id: resolvedSessionID }));
this.handleToggleOverlay(undefined);
} catch (e) {
window?.log?.warn('failed to resolve ons name', pubkeyorOns, e);

@ -23,7 +23,6 @@ import { SessionMainPanel } from '../SessionMainPanel';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import { TimerOptionsArray, TimerOptionsState } from '../../state/ducks/timerOptions';
import { initialConversationScreen } from '../../state/ducks/conversationScreen';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
@ -103,6 +102,10 @@ export class SessionInboxView extends React.Component<any, State> {
conversations: {
conversationLookup: makeLookup(fullFilledConversations, 'id'),
messages: [],
showRightPanel: false,
messageDetailProps: undefined,
selectedMessageIds: [],
selectedConversation: undefined,
},
user: {
ourNumber: UserUtils.getOurPubKeyStrFromCache(),
@ -118,7 +121,6 @@ export class SessionInboxView extends React.Component<any, State> {
timerOptions: {
timerOptions,
},
conversationScreen: initialConversationScreen,
};
this.store = createStore(initialState);

@ -22,6 +22,7 @@ import {
fetchMessagesForConversation,
PropsForMessage,
ReduxConversationType,
resetSelectedMessageIds,
SortedMessageModelProps,
} from '../../../state/ducks/conversations';
import { MessageView } from '../../MainViewController';
@ -34,7 +35,6 @@ import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmen
import { updateMentionsMembers } from '../../../state/ducks/mentionsInput';
import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
import { resetSelectedMessageIds } from '../../../state/ducks/conversationScreen';
interface State {
unreadCount: number;

@ -26,11 +26,11 @@ import autoBind from 'auto-bind';
import { ConversationTypeEnum } from '../../../models/conversation';
import { DataExtractionNotification } from '../../conversation/DataExtractionNotification';
import { StateType } from '../../../state/reducer';
import { getSelectedMessageIds } from '../../../state/selectors/conversationScreen';
import { connect } from 'react-redux';
import {
getSelectedConversation,
getSelectedConversationKey,
getSelectedMessageIds,
} from '../../../state/selectors/conversations';
interface State {

@ -38,10 +38,9 @@ import { MediaItemType } from '../../LightboxGallery';
import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux';
import { getTimerOptions } from '../../../state/selectors/timerOptions';
import { closeRightPanel } from '../../../state/ducks/conversationScreen';
import { isRightPanelShowing } from '../../../state/selectors/conversationScreen';
import { getSelectedConversation } from '../../../state/selectors/conversations';
import { getSelectedConversation, isRightPanelShowing } from '../../../state/selectors/conversations';
import { useMembersAvatars } from '../../../hooks/useMembersAvatar';
import { closeRightPanel } from '../../../state/ducks/conversations';
type Props = {
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails

@ -32,14 +32,17 @@ import {
lastAvatarUploadTimestamp,
removeAllMessagesInConversation,
} from '../data/data';
import { conversationReset, SortedMessageModelProps } from '../state/ducks/conversations';
import {
conversationReset,
resetSelectedMessageIds,
SortedMessageModelProps,
} from '../state/ducks/conversations';
import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
import { IMAGE_JPEG } from '../types/MIME';
import { FSv2 } from '../fileserver';
import { fromBase64ToArray, toHex } from '../session/utils/String';
import { SessionButtonColor } from '../components/session/SessionButton';
import { perfEnd, perfStart } from '../session/utils/Performance';
import { resetSelectedMessageIds } from '../state/ducks/conversationScreen';
export const getCompleteUrlForV2ConvoId = async (convoId: string) => {
if (convoId.match(openGroupV2ConversationIdRegex)) {
@ -286,11 +289,7 @@ export function showChangeNickNameByConvoId(conversationId: string) {
export async function deleteMessagesByConvoIdNoConfirmation(conversationId: string) {
const conversation = getConversationController().get(conversationId);
await removeAllMessagesInConversation(conversationId);
window.inboxStore?.dispatch(
conversationReset({
conversationKey: conversationId,
})
);
window.inboxStore?.dispatch(conversationReset(conversationId));
// destroy message keeps the active timestamp set so the
// conversation still appears on the conversation list but is empty

@ -23,6 +23,7 @@ import {
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
import {
actions as conversationActions,
conversationChanged,
LastMessageStatusType,
MessageModelProps,
ReduxConversationType,
@ -202,7 +203,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.typingRefreshTimer = null;
this.typingPauseTimer = null;
window.inboxStore?.dispatch(conversationActions.conversationChanged(this.id, this.getProps()));
window.inboxStore?.dispatch(conversationChanged({ id: this.id, data: this.getProps() }));
}
public idForLogging() {
@ -885,9 +886,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public triggerUIRefresh() {
window.inboxStore?.dispatch(
conversationActions.conversationChanged(this.id, {
...this.getProps(),
isSelected: false,
conversationChanged({
id: this.id,
data: {
...this.getProps(),
isSelected: false,
},
})
);
}

@ -31,7 +31,10 @@ import { forceSyncConfigurationNowIfNeeded } from '../session/utils/syncUtils';
import { getMessageController } from '../session/messages';
import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
import { queueAllCachedFromSource } from './receiver';
import { actions as conversationActions } from '../state/ducks/conversations';
import {
actions as conversationActions,
openConversationExternal,
} from '../state/ducks/conversations';
import { getSwarmPollingInstance } from '../session/snode_api';
import { MessageModel } from '../models/message';
@ -952,7 +955,7 @@ export async function createClosedGroup(groupName: string, members: Array<string
await forceSyncConfigurationNowIfNeeded();
window.inboxStore?.dispatch(conversationActions.openConversationExternal(groupPublicKey));
window.inboxStore?.dispatch(openConversationExternal({ id: groupPublicKey }));
}
/**

@ -118,7 +118,10 @@ export class ConversationController {
conversation.initialPromise.then(async () => {
if (window?.inboxStore) {
window.inboxStore?.dispatch(
conversationActions.conversationAdded(conversation.id, conversation.getProps())
conversationActions.conversationAdded({
id: conversation.id,
data: conversation.getProps(),
})
);
}
if (!conversation.isPublic()) {
@ -245,7 +248,10 @@ export class ConversationController {
if (window?.inboxStore) {
window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
window.inboxStore?.dispatch(
conversationActions.conversationChanged(conversation.id, conversation.getProps())
conversationActions.conversationChanged({
id: conversation.id,
data: conversation.getProps(),
})
);
}
window.log.info(`deleteContact !isPrivate, convo removed from store: ${id}`);

@ -1,7 +1,7 @@
import promise from 'redux-promise-middleware';
import { createLogger } from 'redux-logger';
import { configureStore } from '@reduxjs/toolkit';
import { reducer as allReducers } from './reducer';
import { rootReducer } from './reducer';
import { persistReducer } from 'redux-persist';
// tslint:disable-next-line: no-submodule-imports match-default-export-name
@ -32,7 +32,7 @@ export const persistConfig = {
whitelist: ['userConfig'],
};
const persistedReducer = persistReducer(persistConfig, allReducers);
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Exclude logger if we're in production mode
const disableLogging = env === 'production' || true; // ALWAYS TURNED OFF

@ -1,79 +0,0 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { MessagePropsDetails } from './conversations';
export type ConversationScreenState = {
messageDetailProps: MessagePropsDetails | undefined;
showRightPanel: boolean;
selectedMessageIds: Array<string>;
};
export const initialConversationScreen: ConversationScreenState = {
messageDetailProps: undefined,
showRightPanel: false,
selectedMessageIds: [],
};
/**
* This slice is the one holding the layout of the Conversation Screen of the app
*/
const conversationScreenSlice = createSlice({
name: 'conversationScreen',
initialState: initialConversationScreen,
reducers: {
showMessageDetailsView(
state: ConversationScreenState,
action: PayloadAction<MessagePropsDetails>
) {
// force the right panel to be hidden when showing message detail view
return { ...state, messageDetailProps: action.payload, showRightPanel: false };
},
closeMessageDetailsView(state: ConversationScreenState) {
return { ...state, messageDetailProps: undefined };
},
openRightPanel(state: ConversationScreenState) {
return { ...state, showRightPanel: true };
},
closeRightPanel(state: ConversationScreenState) {
return { ...state, showRightPanel: false };
},
addMessageIdToSelection(state: ConversationScreenState, action: PayloadAction<string>) {
if (state.selectedMessageIds.some(id => id === action.payload)) {
return state;
}
return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
},
removeMessageIdFromSelection(state: ConversationScreenState, action: PayloadAction<string>) {
const index = state.selectedMessageIds.findIndex(id => id === action.payload);
if (index === -1) {
return state;
}
return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
},
toggleSelectedMessageId(state: ConversationScreenState, action: PayloadAction<string>) {
const index = state.selectedMessageIds.findIndex(id => id === action.payload);
if (index === -1) {
return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
}
return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
},
resetSelectedMessageIds(state: ConversationScreenState) {
return { ...state, selectedMessageIds: [] };
},
},
});
// destructures
const { actions, reducer } = conversationScreenSlice;
export const {
showMessageDetailsView,
closeMessageDetailsView,
openRightPanel,
closeRightPanel,
addMessageIdToSelection,
resetSelectedMessageIds,
} = actions;
export const defaultConversationScreenReducer = reducer;

@ -1,9 +1,8 @@
import _, { omit } from 'lodash';
import { Constants } from '../../session';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { getConversationController } from '../../session/conversations';
import { MessageModel } from '../../models/message';
import { getMessagesByConversation } from '../../data/data';
import {
ConversationNotificationSettingType,
@ -231,6 +230,9 @@ export type ConversationsStateType = {
conversationLookup: ConversationLookupType;
selectedConversation?: string;
messages: Array<SortedMessageModelProps>;
messageDetailProps: MessagePropsDetails | undefined;
showRightPanel: boolean;
selectedMessageIds: Array<string>;
};
async function getMessages(
@ -338,246 +340,15 @@ export const fetchMessagesForConversation = createAsyncThunk(
}
);
// Actions
type ConversationAddedActionType = {
type: 'CONVERSATION_ADDED';
payload: {
id: string;
data: ReduxConversationType;
};
};
type ConversationChangedActionType = {
type: 'CONVERSATION_CHANGED';
payload: {
id: string;
data: ReduxConversationType;
};
};
type ConversationRemovedActionType = {
type: 'CONVERSATION_REMOVED';
payload: {
id: string;
};
};
export type RemoveAllConversationsActionType = {
type: 'CONVERSATIONS_REMOVE_ALL';
payload: null;
};
export type MessageExpiredActionType = {
type: 'MESSAGE_EXPIRED';
payload: {
messageId: string;
conversationKey: string;
};
};
export type MessageChangedActionType = {
type: 'MESSAGE_CHANGED';
payload: MessageModelProps;
};
export type MessagesChangedActionType = {
type: 'MESSAGES_CHANGED';
payload: Array<MessageModelProps>;
};
export type MessageAddedActionType = {
type: 'MESSAGE_ADDED';
payload: {
conversationKey: string;
messageModelProps: MessageModelProps;
};
};
export type MessageDeletedActionType = {
type: 'MESSAGE_DELETED';
payload: {
conversationKey: string;
messageId: string;
};
};
export type ConversationResetActionType = {
type: 'CONVERSATION_RESET';
payload: {
conversationKey: string;
};
};
export type SelectedConversationChangedActionType = {
type: 'SELECTED_CONVERSATION_CHANGED';
payload: {
id: string;
messageId?: string;
};
};
export type FetchMessagesForConversationType = {
type: 'messages/fetchByConversationKey/fulfilled';
payload: {
conversationKey: string;
messages: Array<MessageModelProps>;
};
};
export type ConversationActionType =
| ConversationAddedActionType
| ConversationChangedActionType
| ConversationRemovedActionType
| ConversationResetActionType
| RemoveAllConversationsActionType
| MessageExpiredActionType
| MessageAddedActionType
| MessageDeletedActionType
| MessageChangedActionType
| MessagesChangedActionType
| SelectedConversationChangedActionType
| SelectedConversationChangedActionType
| FetchMessagesForConversationType;
// Action Creators
export const actions = {
conversationAdded,
conversationChanged,
conversationRemoved,
removeAllConversations,
messageExpired,
messageAdded,
messageDeleted,
conversationReset,
messageChanged,
messagesChanged,
fetchMessagesForConversation,
openConversationExternal,
};
function conversationAdded(id: string, data: ReduxConversationType): ConversationAddedActionType {
return {
type: 'CONVERSATION_ADDED',
payload: {
id,
data,
},
};
}
function conversationChanged(
id: string,
data: ReduxConversationType
): 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({
conversationKey,
messageId,
}: {
conversationKey: string;
messageId: string;
}): MessageExpiredActionType {
return {
type: 'MESSAGE_EXPIRED',
payload: {
conversationKey,
messageId,
},
};
}
function messageChanged(messageModelProps: MessageModelProps): MessageChangedActionType {
return {
type: 'MESSAGE_CHANGED',
payload: messageModelProps,
};
}
function messagesChanged(messageModelsProps: Array<MessageModelProps>): MessagesChangedActionType {
return {
type: 'MESSAGES_CHANGED',
payload: messageModelsProps,
};
}
function messageAdded({
conversationKey,
messageModelProps,
}: {
conversationKey: string;
messageModelProps: MessageModelProps;
}): MessageAddedActionType {
return {
type: 'MESSAGE_ADDED',
payload: {
conversationKey,
messageModelProps,
},
};
}
function messageDeleted({
conversationKey,
messageId,
}: {
conversationKey: string;
messageId: string;
}): MessageDeletedActionType {
return {
type: 'MESSAGE_DELETED',
payload: {
conversationKey,
messageId,
},
};
}
export function conversationReset({
conversationKey,
}: {
conversationKey: string;
}): ConversationResetActionType {
return {
type: 'CONVERSATION_RESET',
payload: {
conversationKey,
},
};
}
export function openConversationExternal(
id: string,
messageId?: string
): SelectedConversationChangedActionType {
window?.log?.info(`openConversationExternal with convoId: ${id}; messageId: ${messageId}`);
return {
type: 'SELECTED_CONVERSATION_CHANGED',
payload: {
id,
messageId,
},
};
}
// Reducer
function getEmptyState(): ConversationsStateType {
return {
conversationLookup: {},
messages: [],
messageDetailProps: undefined,
showRightPanel: false,
selectedMessageIds: [],
};
}
@ -607,7 +378,13 @@ function sortMessages(
return messagesSorted;
}
function handleMessageAdded(state: ConversationsStateType, action: MessageAddedActionType) {
function handleMessageAdded(
state: ConversationsStateType,
action: PayloadAction<{
conversationKey: string;
messageModelProps: MessageModelProps;
}>
) {
const { messages } = state;
const { conversationKey, messageModelProps: addedMessageProps } = action.payload;
if (conversationKey === state.selectedConversation) {
@ -631,9 +408,7 @@ function handleMessageAdded(state: ConversationsStateType, action: MessageAddedA
return state;
}
function handleMessageChanged(state: ConversationsStateType, action: MessageChangedActionType) {
const { payload } = action;
function handleMessageChanged(state: ConversationsStateType, payload: MessageModelProps) {
const messageInStoreIndex = state?.messages?.findIndex(
m => m.propsForMessage.id === payload.propsForMessage.id
);
@ -661,15 +436,10 @@ function handleMessageChanged(state: ConversationsStateType, action: MessageChan
return state;
}
function handleMessagesChanged(state: ConversationsStateType, action: MessagesChangedActionType) {
const { payload } = action;
function handleMessagesChanged(state: ConversationsStateType, payload: Array<MessageModelProps>) {
payload.forEach(element => {
// tslint:disable-next-line: no-parameter-reassignment
state = handleMessageChanged(state, {
payload: element,
type: 'MESSAGE_CHANGED',
});
state = handleMessageChanged(state, element);
});
return state;
@ -677,7 +447,10 @@ function handleMessagesChanged(state: ConversationsStateType, action: MessagesCh
function handleMessageExpiredOrDeleted(
state: ConversationsStateType,
action: MessageDeletedActionType | MessageExpiredActionType
action: PayloadAction<{
messageId: string;
conversationKey: string;
}>
) {
const { conversationKey, messageId } = action.payload;
if (conversationKey === state.selectedConversation) {
@ -709,11 +482,8 @@ function handleMessageExpiredOrDeleted(
return state;
}
function handleConversationReset(
state: ConversationsStateType,
action: ConversationResetActionType
) {
const { conversationKey } = action.payload;
function handleConversationReset(state: ConversationsStateType, action: PayloadAction<string>) {
const conversationKey = action.payload;
if (conversationKey === state.selectedConversation) {
// just empty the list of messages
return {
@ -724,110 +494,220 @@ function handleConversationReset(
return state;
}
// tslint:disable: cyclomatic-complexity
// tslint:disable: max-func-body-length
export function reducer(
state: ConversationsStateType = getEmptyState(),
action: ConversationActionType
): ConversationsStateType {
if (action.type === 'CONVERSATION_ADDED') {
const { payload } = action;
const { id, data } = payload;
const { conversationLookup } = state;
const conversationsSlice = createSlice({
name: 'conversations',
initialState: getEmptyState(),
reducers: {
showMessageDetailsView(
state: ConversationsStateType,
action: PayloadAction<MessagePropsDetails>
) {
// force the right panel to be hidden when showing message detail view
return { ...state, messageDetailProps: action.payload, showRightPanel: false };
},
return {
...state,
conversationLookup: {
...conversationLookup,
[id]: data,
},
};
}
if (action.type === 'CONVERSATION_CHANGED') {
const { payload } = action;
const { id, data } = payload;
const { conversationLookup, selectedConversation } = state;
const existing = conversationLookup[id];
// In the change case we only modify the lookup if we already had that conversation
if (!existing) {
return state;
}
closeMessageDetailsView(state: ConversationsStateType) {
return { ...state, messageDetailProps: undefined };
},
return {
...state,
selectedConversation,
conversationLookup: {
...conversationLookup,
[id]: data,
},
};
}
if (action.type === 'CONVERSATION_REMOVED') {
const { payload } = action;
const { id } = payload;
const { conversationLookup, selectedConversation } = state;
return {
...state,
conversationLookup: omit(conversationLookup, [id]),
selectedConversation: selectedConversation === id ? undefined : selectedConversation,
};
}
if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
return getEmptyState();
}
openRightPanel(state: ConversationsStateType) {
return { ...state, showRightPanel: true };
},
closeRightPanel(state: ConversationsStateType) {
return { ...state, showRightPanel: false };
},
addMessageIdToSelection(state: ConversationsStateType, action: PayloadAction<string>) {
if (state.selectedMessageIds.some(id => id === action.payload)) {
return state;
}
return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
},
removeMessageIdFromSelection(state: ConversationsStateType, action: PayloadAction<string>) {
const index = state.selectedMessageIds.findIndex(id => id === action.payload);
if (index === -1) {
return state;
}
return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
},
toggleSelectedMessageId(state: ConversationsStateType, action: PayloadAction<string>) {
const index = state.selectedMessageIds.findIndex(id => id === action.payload);
if (index === -1) {
return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
}
return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
},
resetSelectedMessageIds(state: ConversationsStateType) {
return { ...state, selectedMessageIds: [] };
},
if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
const { payload } = action;
const { id } = payload;
const oldSelectedConversation = state.selectedConversation;
const newSelectedConversation = id;
conversationAdded(
state: ConversationsStateType,
action: PayloadAction<{
id: string;
data: ReduxConversationType;
}>
) {
const { conversationLookup } = state;
if (newSelectedConversation !== oldSelectedConversation) {
// empty the message list
return {
...state,
messages: [],
selectedConversation: id,
conversationLookup: {
...conversationLookup,
[action.payload.id]: action.payload.data,
},
};
}
return {
...state,
selectedConversation: id,
};
}
},
conversationChanged(
state: ConversationsStateType,
action: PayloadAction<{
id: string;
data: ReduxConversationType;
}>
) {
const { payload } = action;
const { id, data } = payload;
const { conversationLookup, selectedConversation } = state;
const existing = conversationLookup[id];
// In the change case we only modify the lookup if we already had that conversation
if (!existing) {
return state;
}
// this is called once the messages are loaded from the db for the currently selected conversation
if (action.type === fetchMessagesForConversation.fulfilled.type) {
const { messagesProps, conversationKey } = action.payload as FetchedMessageResults;
// double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) {
return {
...state,
messages: messagesProps,
selectedConversation,
conversationLookup: {
...conversationLookup,
[id]: data,
},
};
}
return state;
}
},
if (action.type === 'MESSAGE_CHANGED') {
return handleMessageChanged(state, action);
}
conversationRemoved(
state: ConversationsStateType,
action: PayloadAction<{
id: string;
}>
) {
const { payload } = action;
const { id } = payload;
const { conversationLookup, selectedConversation } = state;
return {
...state,
conversationLookup: omit(conversationLookup, [id]),
selectedConversation: selectedConversation === id ? undefined : selectedConversation,
};
},
if (action.type === 'MESSAGES_CHANGED') {
return handleMessagesChanged(state, action);
}
removeAllConversations() {
return getEmptyState();
},
if (action.type === 'MESSAGE_ADDED') {
return handleMessageAdded(state, action);
}
if (action.type === 'MESSAGE_EXPIRED' || action.type === 'MESSAGE_DELETED') {
return handleMessageExpiredOrDeleted(state, action);
}
messageAdded(
state: ConversationsStateType,
action: PayloadAction<{
conversationKey: string;
messageModelProps: MessageModelProps;
}>
) {
return handleMessageAdded(state, action);
},
if (action.type === 'CONVERSATION_RESET') {
return handleConversationReset(state, action);
}
messageChanged(state: ConversationsStateType, action: PayloadAction<MessageModelProps>) {
return handleMessageChanged(state, action.payload);
},
messagesChanged(
state: ConversationsStateType,
action: PayloadAction<Array<MessageModelProps>>
) {
return handleMessagesChanged(state, action.payload);
},
return state;
}
messageExpired(
state: ConversationsStateType,
action: PayloadAction<{
messageId: string;
conversationKey: string;
}>
) {
return handleMessageExpiredOrDeleted(state, action);
},
messageDeleted(
state: ConversationsStateType,
action: PayloadAction<{
messageId: string;
conversationKey: string;
}>
) {
return handleMessageExpiredOrDeleted(state, action);
},
conversationReset(state: ConversationsStateType, action: PayloadAction<string>) {
return handleConversationReset(state, action);
},
openConversationExternal(
state: ConversationsStateType,
action: PayloadAction<{
id: string;
messageId?: string;
}>
) {
if (state.selectedConversation === action.payload.id) {
return state;
}
state.showRightPanel = false;
state.messageDetailProps = undefined;
state.selectedMessageIds = [];
state.selectedConversation = action.payload.id;
state.messages = [];
return state;
},
},
extraReducers: (builder: any) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(
fetchMessagesForConversation.fulfilled,
(state: ConversationsStateType, action: any) => {
// this is called once the messages are loaded from the db for the currently selected conversation
const { messagesProps, conversationKey } = action.payload as FetchedMessageResults;
// double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) {
return {
...state,
messages: messagesProps,
};
}
return state;
}
);
},
});
// destructures
export const { actions, reducer } = conversationsSlice;
export const {
// conversation and messages list
conversationAdded,
conversationChanged,
conversationRemoved,
removeAllConversations,
messageExpired,
messageAdded,
messageDeleted,
conversationReset,
messageChanged,
messagesChanged,
openConversationExternal,
// layout stuff
showMessageDetailsView,
closeMessageDetailsView,
openRightPanel,
closeRightPanel,
addMessageIdToSelection,
resetSelectedMessageIds,
} = actions;

@ -6,11 +6,9 @@ import { searchConversations, searchMessages } from '../../../ts/data/data';
import { makeLookup } from '../../util/makeLookup';
import {
MessageExpiredActionType,
openConversationExternal,
PropsForSearchResults,
ReduxConversationType,
RemoveAllConversationsActionType,
SelectedConversationChangedActionType,
} from './conversations';
import { PubKey } from '../../session/types';
import { MessageModel } from '../../models/message';
@ -64,10 +62,7 @@ type ClearSearchActionType = {
export type SEARCH_TYPES =
| SearchResultsFulfilledActionType
| UpdateSearchTermActionType
| ClearSearchActionType
| MessageExpiredActionType
| RemoveAllConversationsActionType
| SelectedConversationChangedActionType;
| ClearSearchActionType;
// Action Creators
@ -332,39 +327,39 @@ export function reducer(state: SearchStateType | undefined, action: SEARCH_TYPES
};
}
if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
return getEmptyState();
}
if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
const { payload } = action;
const { messageId } = payload;
if (!messageId) {
return state;
}
return {
...state,
selectedMessage: messageId,
};
}
if (action.type === 'MESSAGE_EXPIRED') {
const { messages, messageLookup } = state;
if (!messages.length) {
return state;
}
const { payload } = action;
const { messageId } = payload;
return {
...state,
messages: reject(messages, message => messageId === message.id),
messageLookup: omit(messageLookup, ['id']),
};
}
// if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
// return getEmptyState();
// }
// if (action.type === openConversationExternal.name) {
// const { payload } = action;
// const { messageId } = payload;
// if (!messageId) {
// return state;
// }
// return {
// ...state,
// selectedMessage: messageId,
// };
// }
// if (action.type === 'MESSAGE_EXPIRED') {
// const { messages, messageLookup } = state;
// if (!messages.length) {
// return state;
// }
// const { payload } = action;
// const { messageId } = payload;
// return {
// ...state,
// messages: reject(messages, message => messageId === message.id),
// messageLookup: omit(messageLookup, ['id']),
// };
// }
return state;
}

@ -10,10 +10,7 @@ import {
defaultMentionsInputReducer as mentionsInput,
MentionsInputState,
} from './ducks/mentionsInput';
import {
ConversationScreenState,
defaultConversationScreenReducer as conversationScreen,
} from './ducks/conversationScreen';
import { defaultOnionReducer as onionPaths, OnionState } from './ducks/onion';
import { modalReducer as modals, ModalState } from './ducks/modalDialog';
import { userConfigReducer as userConfig, UserConfigState } from './ducks/userConfig';
@ -31,7 +28,6 @@ export type StateType = {
modals: ModalState;
userConfig: UserConfigState;
timerOptions: TimerOptionsState;
conversationScreen: ConversationScreenState;
};
export const reducers = {
@ -46,10 +42,9 @@ export const reducers = {
modals,
userConfig,
timerOptions,
conversationScreen,
};
// Making this work would require that our reducer signature supported AnyAction, not
// our restricted actions
// @ts-ignore
export const reducer = combineReducers(reducers);
export const rootReducer = combineReducers(reducers);

@ -1,38 +0,0 @@
import { createSelector } from 'reselect';
import { StateType } from '../reducer';
import { ConversationScreenState } from '../ducks/conversationScreen';
import { MessagePropsDetails } from '../ducks/conversations';
export const getConversationScreenState = (state: StateType): ConversationScreenState =>
state.conversationScreen;
export const isMessageDetailView = createSelector(
getConversationScreenState,
(state: ConversationScreenState): boolean => state.messageDetailProps !== undefined
);
export const getMessageDetailsViewProps = createSelector(
getConversationScreenState,
(state: ConversationScreenState): MessagePropsDetails | undefined => state.messageDetailProps
);
export const isRightPanelShowing = createSelector(
getConversationScreenState,
(state: ConversationScreenState): boolean => state.showRightPanel
);
export const isMessageSelectionMode = createSelector(
getConversationScreenState,
(state: ConversationScreenState): boolean => state.selectedMessageIds.length > 0
);
export const getSelectedMessageIds = createSelector(
getConversationScreenState,
(state: ConversationScreenState): Array<string> => state.selectedMessageIds
);
export const isMessageSelected = (messageId: string) =>
createSelector(getConversationScreenState, (state: ConversationScreenState): boolean =>
state.selectedMessageIds.includes(messageId)
);

@ -4,6 +4,7 @@ import { StateType } from '../reducer';
import {
ConversationLookupType,
ConversationsStateType,
MessagePropsDetails,
ReduxConversationType,
SortedMessageModelProps,
} from '../ducks/conversations';
@ -257,3 +258,28 @@ export const getNumberOfPinnedConversations = createSelector(getConversations, (
const values = Object.values(state.conversationLookup);
return values.filter(conversation => conversation.isPinned).length;
});
export const isMessageDetailView = createSelector(
getConversations,
(state: ConversationsStateType): boolean => state.messageDetailProps !== undefined
);
export const getMessageDetailsViewProps = createSelector(
getConversations,
(state: ConversationsStateType): MessagePropsDetails | undefined => state.messageDetailProps
);
export const isRightPanelShowing = createSelector(
getConversations,
(state: ConversationsStateType): boolean => state.showRightPanel
);
export const isMessageSelectionMode = createSelector(
getConversations,
(state: ConversationsStateType): boolean => state.selectedMessageIds.length > 0
);
export const getSelectedMessageIds = createSelector(
getConversations,
(state: ConversationsStateType): Array<string> => state.selectedMessageIds
);

@ -7,13 +7,11 @@ import {
getMessagesOfSelectedConversation,
getSelectedConversation,
getSelectedConversationKey,
} from '../selectors/conversations';
import { getOurNumber } from '../selectors/user';
import {
getSelectedMessageIds,
isMessageDetailView,
isRightPanelShowing,
} from '../selectors/conversationScreen';
} from '../selectors/conversations';
import { getOurNumber } from '../selectors/user';
const mapStateToProps = (state: StateType) => {
return {

Loading…
Cancel
Save