store messages currently loaded in convo on redux

pull/1381/head
Audric Ackermann
parent 221f264de6
commit e45ce43e01
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -79,9 +79,8 @@
this.propsForSearchResult = this.getPropsForSearchResult();
this.propsForMessage = this.getPropsForMessage();
}
Whisper.events.trigger('messageChanged', this);
};
const triggerChange = () => this.trigger('change');
this.on('change', generateProps);
const applicableConversationChanges =
@ -93,7 +92,7 @@
// trigger a change event on this component.
// this will call generateProps and refresh the Message.tsx component with new props
this.listenTo(conversation, 'disable:input', triggerChange);
this.listenTo(conversation, 'disable:input', () => this.trigger('change'));
if (fromContact) {
this.listenTo(
fromContact,
@ -661,8 +660,8 @@
contact.number && contact.number[0] && contact.number[0].value;
const onSendMessage = firstNumber
? () => {
this.trigger('open-conversation', firstNumber);
}
this.trigger('open-conversation', firstNumber);
}
: null;
const onClick = async () => {
// First let's be sure that the signal account check is complete.
@ -693,8 +692,8 @@
!path && !objectUrl
? null
: Object.assign({}, attachment.thumbnail || {}, {
objectUrl: path || objectUrl,
});
objectUrl: path || objectUrl,
});
return Object.assign({}, attachment, {
isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment),
@ -747,13 +746,13 @@
const onClick = noClick
? null
: event => {
event.stopPropagation();
this.trigger('scroll-to-message', {
author,
id,
referencedMessageNotFound,
});
};
event.stopPropagation();
this.trigger('scroll-to-message', {
author,
id,
referencedMessageNotFound,
});
};
const firstAttachment = quote.attachments && quote.attachments[0];
@ -788,15 +787,15 @@
url: path ? getAbsoluteAttachmentPath(path) : null,
screenshot: screenshot
? {
...screenshot,
url: getAbsoluteAttachmentPath(screenshot.path),
}
...screenshot,
url: getAbsoluteAttachmentPath(screenshot.path),
}
: null,
thumbnail: thumbnail
? {
...thumbnail,
url: getAbsoluteAttachmentPath(thumbnail.path),
}
...thumbnail,
url: getAbsoluteAttachmentPath(thumbnail.path),
}
: null,
};
},
@ -825,9 +824,9 @@
const phoneNumbers = this.isIncoming()
? [this.get('source')]
: _.union(
this.get('sent_to') || [],
this.get('recipients') || this.getConversation().getRecipients()
);
this.get('sent_to') || [],
this.get('recipients') || this.getConversation().getRecipients()
);
// This will make the error message for outgoing key errors a bit nicer
const allErrors = (this.get('errors') || []).map(error => {
@ -944,7 +943,6 @@
} else {
convo.removeMessageSelection(this);
}
this.trigger('change');
},
@ -1278,8 +1276,8 @@
*/
const hasBodyOrAttachments = Boolean(
dataMessage &&
(dataMessage.body ||
(dataMessage.attachments && dataMessage.attachments.length))
(dataMessage.body ||
(dataMessage.attachments && dataMessage.attachments.length))
);
const shouldNotifyPushServer =
hasBodyOrAttachments && isSessionOrClosedMessage;
@ -1351,7 +1349,6 @@
// unidentifiedDeliveries: result.unidentifiedDeliveries,
});
await this.commit();
this.trigger('change', this);
this.getConversation().updateLastMessage();
@ -1442,7 +1439,7 @@
return null;
},
async setCalculatingPoW() {
if (this.calculatingPoW) {
if (this.get('calculatingPoW')) {
return;
}
@ -1616,6 +1613,8 @@
forceSave,
Message: Whisper.Message,
});
console.warn('case commit')
this.trigger('change');
return id;
},

@ -114,6 +114,7 @@ const {
const { createStore } = require('../../ts/state/createStore');
const conversationsDuck = require('../../ts/state/ducks/conversations');
const userDuck = require('../../ts/state/ducks/user');
const messagesDuck = require('../../ts/state/ducks/messages');
// Migrations
const {
@ -293,6 +294,7 @@ exports.setup = (options = {}) => {
const Ducks = {
conversations: conversationsDuck,
user: userDuck,
messages: messagesDuck,
};
const State = {
bindActionCreators,

@ -143,6 +143,10 @@
Signal.State.Ducks.user.actions,
this.store.dispatch
);
const { messageChanged } = Signal.State.bindActionCreators(
Signal.State.Ducks.messages.actions,
this.store.dispatch
);
this.openConversationAction = openConversationExternal;
@ -175,6 +179,7 @@
.events.addListener('fail', this.handleMessageSentFailure);
Whisper.events.on('messageExpired', messageExpired);
Whisper.events.on('messageChanged', messageChanged);
Whisper.events.on('userChanged', userChanged);
// Finally, add it to the DOM

@ -1,95 +0,0 @@
/* global Whisper: false */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.MessageView = Whisper.View.extend({
tagName: 'li',
id() {
return this.model.id;
},
initialize() {
this.listenTo(this.model, 'change', this.onChange);
this.listenTo(this.model, 'destroy', this.onDestroy);
this.listenTo(this.model, 'unload', this.onUnload);
this.listenTo(this.model, 'expired', this.onExpired);
},
onChange() {
this.addId();
},
addId() {
// The ID is important for other items inserting themselves into the DOM. Because
// of ReactWrapperView and this view, there are two layers of DOM elements
// between the parent and the elements returned by the React component, so this is
// necessary.
const { id } = this.model;
this.$el.attr('id', id);
},
onExpired() {
setTimeout(() => this.onUnload(), 1000);
},
onUnload() {
if (this.childView) {
this.childView.remove();
}
this.remove();
},
onDestroy() {
this.onUnload();
},
getRenderInfo() {
return null;
},
render() {
this.addId();
if (this.childView) {
this.childView.remove();
this.childView = null;
}
const { Component, props } = this.getRenderInfo();
this.childView = new Whisper.ReactWrapperView({
className: 'message-wrapper',
Component,
props,
});
const update = () => {
const info = this.getRenderInfo();
this.childView.update(info.props);
};
this.listenTo(this.model, 'change', update);
this.listenTo(this.model, 'expired', update);
const applicableConversationChanges =
'change:color change:name change:number change:profileName change:profileAvatar';
this.conversation = this.model.getConversation();
this.listenTo(this.conversation, applicableConversationChanges, update);
this.fromContact = this.model.getIncomingContact();
if (this.fromContact) {
this.listenTo(this.fromContact, applicableConversationChanges, update);
}
this.quotedContact = this.model.getQuoteContact();
if (this.quotedContact) {
this.listenTo(
this.quotedContact,
applicableConversationChanges,
update
);
}
this.$el.append(this.childView.el);
return this;
},
});
})();

@ -56,6 +56,7 @@
},
"dependencies": {
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b",
"@reduxjs/toolkit": "^1.4.0",
"@sindresorhus/is": "0.8.0",
"@types/dompurify": "^2.0.0",
"@types/emoji-mart": "^2.11.3",

@ -4,7 +4,7 @@
overflow-y: auto;
}
.message-container{
.message-container {
list-style: none;
li {

@ -41,7 +41,6 @@ interface State {
sendingProgressStatus: -1 | 0 | 1 | 2;
unreadCount: number;
initialFetchComplete: boolean;
selectedMessages: Array<string>;
isScrolledToBottom: boolean;
displayScrollToBottomButton: boolean;
@ -93,8 +92,6 @@ export class SessionConversation extends React.Component<Props, State> {
prevSendingProgress: 0,
sendingProgressStatus: 0,
unreadCount,
initialFetchComplete: false,
messages: [],
selectedMessages: [],
isScrolledToBottom: !unreadCount,
displayScrollToBottomButton: false,
@ -131,8 +128,6 @@ export class SessionConversation extends React.Component<Props, State> {
this.replyToMessage = this.replyToMessage.bind(this);
this.onClickAttachment = this.onClickAttachment.bind(this);
this.downloadAttachment = this.downloadAttachment.bind(this);
this.refreshMessages = this.refreshMessages.bind(this);
this.getMessages = this.getMessages.bind(this);
// Keyboard navigation
this.onKeyDown = this.onKeyDown.bind(this);
@ -159,15 +154,6 @@ export class SessionConversation extends React.Component<Props, State> {
}
public componentWillUnmount() {
const { conversationKey } = this.props;
try {
const conversationModel = window.ConversationController.getOrThrow(
conversationKey
);
conversationModel.off('change', this.refreshMessages);
} catch (e) {
window.log.error(e);
}
const div = this.messageContainerRef.current;
div?.removeEventListener('dragenter', this.handleDragIn);
div?.removeEventListener('dragleave', this.handleDragOut);
@ -175,34 +161,8 @@ export class SessionConversation extends React.Component<Props, State> {
div?.removeEventListener('drop', this.handleDrop);
}
public componentDidUpdate(prevProps: Props, prevState: State) {
const { conversationKey: oldKey } = prevProps;
try {
const oldConversationModel = window.ConversationController.getOrThrow(
oldKey
);
oldConversationModel.off('change', this.refreshMessages);
} catch (e) {
window.log.warn(e);
}
try {
const { conversationKey: newKey } = this.props;
const newConversationModel = window.ConversationController.getOrThrow(
newKey
);
newConversationModel.on('change', this.refreshMessages);
} catch (e) {
window.log.warn(e);
}
}
public componentDidMount() {
// reload as much messages as we had before the change.
const { conversationKey } = this.props;
const conversationModel = window.ConversationController.getOrThrow(
conversationKey
);
conversationModel.on('change', this.refreshMessages);
// Pause thread to wait for rendering to complete
setTimeout(() => {
const div = this.messageContainerRef.current;
@ -225,7 +185,6 @@ export class SessionConversation extends React.Component<Props, State> {
selectedMessages,
isDraggingFile,
stagedAttachments,
initialFetchComplete,
} = this.state;
const selectionMode = !!selectedMessages.length;
@ -285,11 +244,7 @@ export class SessionConversation extends React.Component<Props, State> {
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
<div className="conversation-messages">
{initialFetchComplete && (
<SessionConversationMessagesList
{...this.getMessagesListProps()}
/>
)}
<SessionConversationMessagesList {...this.getMessagesListProps()} />
{showRecordingView && (
<div className="conversation-messages__blocking-overlay" />
@ -342,14 +297,7 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public async loadInitialMessages() {
// Grabs the initial set of messages and adds them to our conversation model.
// After the inital fetch, all new messages are automatically added from onNewMessage
// in the conversation model.
// The only time we need to call getMessages() is to grab more messages on scroll.
if (this.state.initialFetchComplete) {
return;
}
const { conversationKey, conversation } = this.props;
const { conversationKey } = this.props;
const conversationModel = window.ConversationController.getOrThrow(
conversationKey
);
@ -358,67 +306,11 @@ export class SessionConversation extends React.Component<Props, State> {
Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT,
unreadCount
);
if (conversation) {
return this.getMessages(messagesToFetch, () => {
this.setState({ initialFetchComplete: true });
});
}
}
// tslint:disable-next-line: no-empty
public async getMessages(numMessages?: number, callback: any = () => {}) {
const { unreadCount } = this.state;
const { conversationKey, conversation } = this.props;
let msgCount =
numMessages ||
Number(Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) + unreadCount;
msgCount =
msgCount > Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
? Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
: msgCount;
if (msgCount < Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) {
msgCount = Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
}
if (!conversation) {
// no valid conversation, early return
return;
}
const messageSet = await window.Signal.Data.getMessagesByConversation(
this.props.actions.fetchMessagesForConversation({
conversationKey,
{ limit: msgCount, MessageCollection: window.Whisper.MessageCollection }
);
// Set first member of series here.
const messageModels = messageSet.models;
const messages = [];
// no need to do that `firstMessageOfSeries` on a private chat
if (conversation.type === 'direct') {
this.setState({ messages: messageSet.models }, callback);
return;
}
// messages are got from the more recent to the oldest, so we need to check if
// the next messages in the list is still the same author.
// The message is the first of the series if the next message is not from the same authori
for (let i = 0; i < messageModels.length; i++) {
// Handle firstMessageOfSeries for conditional avatar rendering
let firstMessageOfSeries = true;
const currentSender = messageModels[i].propsForMessage?.authorPhoneNumber;
const nextSender =
i < messageModels.length - 1
? messageModels[i + 1].propsForMessage?.authorPhoneNumber
: undefined;
if (i > 0 && currentSender === nextSender) {
firstMessageOfSeries = false;
}
messages.push({ ...messageModels[i], firstMessageOfSeries });
}
this.setState({ messages }, callback);
count: messagesToFetch,
});
}
public getHeaderProps() {
@ -524,8 +416,8 @@ export class SessionConversation extends React.Component<Props, State> {
}
public getMessagesListProps() {
const { conversation } = this.props;
const { messages, quotedMessageTimestamp, selectedMessages } = this.state;
const { conversation, messages, actions } = this.props;
const { quotedMessageTimestamp, selectedMessages } = this.state;
return {
selectedMessages,
@ -535,7 +427,7 @@ export class SessionConversation extends React.Component<Props, State> {
quotedMessageTimestamp,
conversation,
selectMessage: this.selectMessage,
getMessages: this.getMessages,
fetchMessagesForConversation: actions.fetchMessagesForConversation,
replyToMessage: this.replyToMessage,
onClickAttachment: this.onClickAttachment,
onDownloadAttachment: this.downloadAttachment,
@ -657,8 +549,7 @@ export class SessionConversation extends React.Component<Props, State> {
public async deleteSelectedMessages() {
// Get message objects
const { messages } = this.state;
const { conversationKey } = this.props;
const { conversationKey, messages } = this.props;
const conversationModel = window.ConversationController.getOrThrow(
conversationKey
@ -738,9 +629,7 @@ export class SessionConversation extends React.Component<Props, State> {
);
// Update view and trigger update
this.setState({ selectedMessages: [] }, () => {
conversationModel.trigger('change', conversationModel);
});
this.setState({ selectedMessages: [] });
};
// If removable from server, we "Unsend" - otherwise "Delete"
@ -801,21 +690,24 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private async replyToMessage(quotedMessageTimestamp?: number) {
if (!_.isEqual(this.state.quotedMessageTimestamp, quotedMessageTimestamp)) {
const { conversation, conversationKey } = this.props;
const { messages, conversationKey } = this.props;
const conversationModel = window.ConversationController.getOrThrow(
conversationKey
);
let quotedMessageProps = null;
if (quotedMessageTimestamp) {
const quotedMessageModel = conversationModel.getMessagesWithTimestamp(
conversation.id,
quotedMessageTimestamp
);
if (quotedMessageModel && quotedMessageModel.length === 1) {
quotedMessageProps = await conversationModel.makeQuote(
quotedMessageModel[0]
);
const quotedMessage = messages.find(m => m.attributes.sent_at === quotedMessageTimestamp);
if (quotedMessage) {
const quotedMessageModel = await getMessageById(quotedMessage.id, {
Message: window.Whisper.Message,
});
if (quotedMessageModel) {
quotedMessageProps = await conversationModel.makeQuote(
quotedMessageModel
);
}
}
}
this.setState({ quotedMessageTimestamp, quotedMessageProps }, () => {
@ -1197,11 +1089,4 @@ export class SessionConversation extends React.Component<Props, State> {
this.setState({ isDraggingFile: false });
}
}
private refreshMessages() {
void this.getMessages(
this.state.messages.length ||
Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT
);
}
}

@ -30,7 +30,13 @@ interface Props {
conversation: ConversationType;
messageContainerRef: React.RefObject<any>;
selectMessage: (messageId: string) => void;
getMessages: (numMessages: number) => Promise<void>;
fetchMessagesForConversation: ({
conversationKey,
count,
}: {
conversationKey: string;
count: number;
}) => void;
replyToMessage: (messageId: number) => Promise<void>;
onClickAttachment: (attachment: any, message: any) => void;
onDownloadAttachment: ({ attachment }: { attachment: any }) => void;
@ -298,6 +304,8 @@ export class SessionConversationMessagesList extends React.Component<
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public async handleScroll() {
const messageContainer = this.messageContainerRef?.current;
const { fetchMessagesForConversation, conversationKey } = this.props;
if (!messageContainer) {
return;
}
@ -354,7 +362,7 @@ export class SessionConversationMessagesList extends React.Component<
const oldLen = messages.length;
const previousTopMessage = messages[oldLen - 1]?.id;
await this.props.getMessages(numMessages);
fetchMessagesForConversation({ conversationKey, count: numMessages });
if (previousTopMessage && oldLen !== messages.length) {
this.scrollToMessage(previousTopMessage);
}

@ -4,14 +4,16 @@ import { actions as search } from './ducks/search';
import { actions as conversations } from './ducks/conversations';
import { actions as user } from './ducks/user';
import { actions as sections } from './ducks/section';
import { actions as messages } from './ducks/messages';
const actions = {
...search,
...conversations,
...user,
...messages,
...sections,
};
export function mapDispatchToProps(dispatch: Dispatch): Object {
return bindActionCreators(actions, dispatch);
return { ...bindActionCreators(actions, dispatch) };
}

@ -1,9 +1,7 @@
import { applyMiddleware, createStore as reduxCreateStore } from 'redux';
import promise from 'redux-promise-middleware';
import { createLogger } from 'redux-logger';
import { reducer } from './reducer';
import { configureStore } from '@reduxjs/toolkit';
import { reducer as allReducers } from './reducer';
// @ts-ignore
const env = window.getEnvironment();
@ -28,7 +26,10 @@ const logger = createLogger({
const disableLogging = env === 'production' || true; // ALWAYS TURNED OFF
const middlewareList = disableLogging ? [promise] : [promise, logger];
const enhancer = applyMiddleware.apply(null, middlewareList);
export const createStore = (initialState: any) =>
reduxCreateStore(reducer, initialState, enhancer);
configureStore({
reducer: allReducers,
preloadedState: initialState,
middleware: (getDefaultMiddleware: any) =>
getDefaultMiddleware().concat(middlewareList),
});

@ -106,13 +106,6 @@ export type SelectedConversationChangedActionType = {
messageId?: string;
};
};
export type LoadMoreMessagesActionType = {
type: 'LOAD_MORE_MESSAGES_ACTION_TYPE';
payload: {
id: string;
currentMessageCount: number;
};
};
export type ConversationActionType =
| ConversationAddedActionType
@ -122,8 +115,7 @@ export type ConversationActionType =
| MessageExpiredActionType
| SelectedConversationChangedActionType
| MessageExpiredActionType
| SelectedConversationChangedActionType
| LoadMoreMessagesActionType;
| SelectedConversationChangedActionType;
// Action Creators
@ -135,7 +127,6 @@ export const actions = {
messageExpired,
openConversationInternal,
openConversationExternal,
loadMoreMessages,
};
function conversationAdded(
@ -177,19 +168,6 @@ function removeAllConversations(): RemoveAllConversationsActionType {
};
}
function loadMoreMessages(
id: string,
currentMessageCount: number
): LoadMoreMessagesActionType {
return {
type: 'LOAD_MORE_MESSAGES_ACTION_TYPE',
payload: {
id,
currentMessageCount,
},
};
}
function messageExpired(
id: string,
conversationId: string
@ -239,13 +217,9 @@ function getEmptyState(): ConversationsStateType {
}
export function reducer(
state: ConversationsStateType,
state: ConversationsStateType = getEmptyState(),
action: ConversationActionType
): ConversationsStateType {
if (!state) {
return getEmptyState();
}
if (action.type === 'CONVERSATION_ADDED') {
const { payload } = action;
const { id, data } = payload;

@ -1,3 +1,116 @@
export const reducer = (state: any, _action: any) => {
return state;
import { Constants } from '../../session';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import _ from 'lodash';
import { MessageType } from './conversations';
export type MessagesStateType = Array<MessageType>;
export async function getMessages(
conversationKey: string,
numMessages: number
) : Promise<MessagesStateType> {
const conversation = window.ConversationController.get(conversationKey);
if (!conversation) {
// no valid conversation, early return
window.log.error('Failed to get convo on reducer.');
return [];
}
const unreadCount = await conversation.getUnreadCount();
let msgCount =
numMessages ||
Number(Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) + unreadCount;
msgCount =
msgCount > Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
? Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
: msgCount;
if (msgCount < Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) {
msgCount = Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
}
const messageSet = await window.Signal.Data.getMessagesByConversation(
conversationKey,
{ limit: msgCount, MessageCollection: window.Whisper.MessageCollection }
);
// Set first member of series here.
const messageModels = messageSet.models;
const messages = [];
// no need to do that `firstMessageOfSeries` on a private chat
if (conversation.isPrivate()) {
return messageSet.models;
}
// messages are got from the more recent to the oldest, so we need to check if
// the next messages in the list is still the same author.
// The message is the first of the series if the next message is not from the same authori
for (let i = 0; i < messageModels.length; i++) {
// Handle firstMessageOfSeries for conditional avatar rendering
let firstMessageOfSeries = true;
const currentSender = messageModels[i].propsForMessage?.authorPhoneNumber;
const nextSender =
i < messageModels.length - 1
? messageModels[i + 1].propsForMessage?.authorPhoneNumber
: undefined;
if (i > 0 && currentSender === nextSender) {
firstMessageOfSeries = false;
}
messages.push({ ...messageModels[i], firstMessageOfSeries });
}
return messages;
}
// ACTIONS
const fetchMessagesForConversation = createAsyncThunk(
'messages/fetchByConversationKey',
async ({
conversationKey,
count,
}: {
conversationKey: string;
count: number;
}) => {
return getMessages(conversationKey, count);
}
);
const toOmitFromMessageModel = [
'cid',
'collection',
// 'changing',
// 'previousAttributes',
'_events',
'_listeningTo',
];
const messageSlice = createSlice({
name: 'messages',
initialState: [] as MessagesStateType,
reducers: {
messageChanged(state, action){
console.log('message changed ', state, action)
const messageInStoreIndex = state.findIndex(m => m.id === action.payload.id);
if (messageInStoreIndex >= 0) {
state[messageInStoreIndex] = _.omit(action.payload, toOmitFromMessageModel)
;
}
return state;
},
},
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[fetchMessagesForConversation.fulfilled.type]: (state, action) => {
const lightMessages = action.payload.map((m: any) =>
_.omit(m, toOmitFromMessageModel)
);
return lightMessages;
},
},
});
export const actions = {
...messageSlice.actions,
fetchMessagesForConversation,
};
export const reducer = messageSlice.reducer;

@ -8,10 +8,11 @@ import {
import { reducer as user, UserStateType } from './ducks/user';
import { reducer as theme, ThemeStateType } from './ducks/theme';
import { reducer as section, SectionStateType } from './ducks/section';
import { MessagesStateType, reducer as messages } from './ducks/messages';
export type StateType = {
search: SearchStateType;
// messages: any;
messages: MessagesStateType;
user: UserStateType;
conversations: ConversationsStateType;
theme: ThemeStateType;
@ -21,8 +22,8 @@ export type StateType = {
export const reducers = {
search,
// Temporary until ./ducks/messages is working
// messages,
messages: search,
messages,
// messages: search,
conversations,
user,
theme,

@ -4,39 +4,6 @@ import { SessionConversation } from '../../components/session/conversation/Sessi
import { StateType } from '../reducer';
const mapStateToProps = (state: StateType) => {
// Get messages here!!!!!
// FIXME VINCE: Get messages for all conversations, not just this one
// Store as object of objects with key refs
// console.log(`[update] State from dispatch:`, state);
// const message: Array<any> = [];
// if(state.conversations) {
// const conversationKey = state.conversations.selectedConversation;
// // FIXME VINCE: msgCount should not be a magic number
// const msgCount = 30;
// const messageSet = await window.Signal.Data.getMessagesByConversation(
// conversationKey,
// { limit: msgCount, MessageCollection: window.Whisper.MessageCollection },
// );
// const messageModels = messageSet.models;
// let previousSender;
// for (let i = 0; i < messageModels.length; i++){
// // Handle firstMessageOfSeries for conditional avatar rendering
// let firstMessageOfSeries = true;
// if (i > 0 && previousSender === messageModels[i].authorPhoneNumber){
// firstMessageOfSeries = false;
// }
// messages.push({...messageModels[i], firstMessageOfSeries});
// previousSender = messageModels[i].authorPhoneNumber;
// }
// }
const conversationKey = state.conversations.selectedConversation;
const conversation =
(conversationKey &&
@ -47,8 +14,19 @@ const mapStateToProps = (state: StateType) => {
conversation,
conversationKey,
theme: state.theme,
messages: state.messages,
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
const smart = connect(
mapStateToProps,
mapDispatchToProps,
(stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
router: ownProps,
actions: dispatchProps,
};
}
);
export const SmartSessionConversation = smart(SessionConversation);

@ -282,6 +282,16 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@reduxjs/toolkit@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.4.0.tgz#ee2e2384cc3d1d76780d844b9c2da3580d32710d"
integrity sha512-hkxQwVx4BNVRsYdxjNF6cAseRmtrkpSlcgJRr3kLUcHPIAMZAmMJkXmHh/eUEGTMqPzsYpJLM7NN2w9fxQDuGw==
dependencies:
immer "^7.0.3"
redux "^4.0.0"
redux-thunk "^2.3.0"
reselect "^4.0.0"
"@sindresorhus/is@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.8.0.tgz#073aee40b0aab2d4ace33c0a2a2672a37da6fa12"
@ -5260,6 +5270,11 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
immer@^7.0.3:
version "7.0.14"
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.14.tgz#3e605f8584b15a9520d2f2f3fda9441cc9170d25"
integrity sha512-BxCs6pJwhgSEUEOZjywW7OA8DXVzfHjkBelSEl0A+nEu0+zS4cFVdNOONvt55N4WOm8Pu4xqSPYxhm1Lv2iBBA==
import-lazy@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@ -9082,6 +9097,11 @@ redux-promise-middleware@6.1.0:
resolved "https://registry.yarnpkg.com/redux-promise-middleware/-/redux-promise-middleware-6.1.0.tgz#ecdb22488cdd673c1a3f0d278d82b48d92ca5d06"
integrity sha512-C62Ku3TgMwxFh5r3h1/iD+XPdsoizyHLT74dTkqhJ8c0LCbEVl1z9fm8zKitAjI16e6w6+h3mxf6wHdonaYXfQ==
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5"
@ -9355,7 +9375,7 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
reselect@4.0.0:
reselect@4.0.0, reselect@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==

Loading…
Cancel
Save