Don't mutate state in TimelineItem

This commit is contained in:
Fedor Indutny 2021-08-19 13:14:41 -07:00 committed by GitHub
parent 1cc7c5dc2d
commit 80c1ad6ee3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 61 deletions

View file

@ -99,6 +99,7 @@ import {
getActiveCall,
} from '../state/selectors/calling';
import { getAccountSelector } from '../state/selectors/accounts';
import { getContactNameColorSelector } from '../state/selectors/conversations';
import {
MessageReceipts,
MessageReceiptType,
@ -378,6 +379,14 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const accountSelector = getAccountSelector(state);
return accountSelector(identifier);
},
contactNameColorSelector: (
conversationId: string,
contactId: string
) => {
const state = window.reduxStore.getState();
const contactNameColorSelector = getContactNameColorSelector(state);
return contactNameColorSelector(conversationId, contactId);
},
}),
errors,
contacts,

View file

@ -680,6 +680,71 @@ export const getCachedSelectorForMessage = createSelector(
}
);
const getCachedConversationMemberColorsSelector = createSelector(
getConversationSelector,
getUserConversationId,
(
conversationSelector: GetConversationByIdType,
ourConversationId: string
) => {
return memoizee(
(conversationId: string) => {
const contactNameColors: Map<string, ContactNameColorType> = new Map();
const {
sortedGroupMembers = [],
type,
id: theirId,
} = conversationSelector(conversationId);
if (type === 'direct') {
contactNameColors.set(ourConversationId, ContactNameColors[0]);
contactNameColors.set(theirId, ContactNameColors[0]);
return contactNameColors;
}
[...sortedGroupMembers]
.sort((left, right) =>
String(left.uuid) > String(right.uuid) ? 1 : -1
)
.forEach((member, i) => {
contactNameColors.set(
member.id,
ContactNameColors[i % ContactNameColors.length]
);
});
return contactNameColors;
},
{ max: 100 }
);
}
);
export type ContactNameColorSelectorType = (
conversationId: string,
contactId: string
) => ContactNameColorType;
export const getContactNameColorSelector = createSelector(
getCachedConversationMemberColorsSelector,
conversationMemberColorsSelector => {
return (
conversationId: string,
contactId: string
): ContactNameColorType => {
const contactNameColors = conversationMemberColorsSelector(
conversationId
);
const color = contactNameColors.get(contactId);
if (!color) {
window.log.warn(`No color generated for contact ${contactId}`);
return ContactNameColors[0];
}
return color;
};
}
);
type GetMessageByIdType = (id: string) => TimelineItemType | undefined;
export const getMessageSelector = createSelector(
getCachedSelectorForMessage,
@ -693,6 +758,7 @@ export const getMessageSelector = createSelector(
getCallSelector,
getActiveCall,
getAccountSelector,
getContactNameColorSelector,
(
messageSelector: typeof getPropsForBubble,
messageLookup: MessageLookupType,
@ -704,7 +770,8 @@ export const getMessageSelector = createSelector(
ourConversationId: string,
callSelector: CallSelectorType,
activeCall: undefined | CallStateType,
accountSelector: AccountSelectorType
accountSelector: AccountSelectorType,
contactNameColorSelector: ContactNameColorSelectorType
): GetMessageByIdType => {
return (id: string) => {
const message = messageLookup[id];
@ -720,6 +787,7 @@ export const getMessageSelector = createSelector(
regionCode,
selectedMessageId: selectedMessage?.id,
selectedMessageCounter: selectedMessage?.counter,
contactNameColorSelector,
callSelector,
activeCall,
accountSelector,
@ -838,54 +906,6 @@ export const getInvitedContactsForNewlyCreatedGroup = createSelector(
)
);
const getCachedConversationMemberColorsSelector = createSelector(
getConversationSelector,
(conversationSelector: GetConversationByIdType) => {
return memoizee(
(conversationId: string) => {
const contactNameColors: Map<string, ContactNameColorType> = new Map();
const { sortedGroupMembers = [] } = conversationSelector(
conversationId
);
[...sortedGroupMembers]
.sort((left, right) =>
String(left.uuid) > String(right.uuid) ? 1 : -1
)
.forEach((member, i) => {
contactNameColors.set(
member.id,
ContactNameColors[i % ContactNameColors.length]
);
});
return contactNameColors;
},
{ max: 100 }
);
}
);
export const getContactNameColorSelector = createSelector(
getCachedConversationMemberColorsSelector,
conversationMemberColorsSelector => {
return (
conversationId: string,
contactId: string
): ContactNameColorType => {
const contactNameColors = conversationMemberColorsSelector(
conversationId
);
const color = contactNameColors.get(contactId);
if (!color) {
window.log.warn(`No color generated for contact ${contactId}`);
return ContactNameColors[0];
}
return color;
};
}
);
export const getConversationsWithCustomColorSelector = createSelector(
getAllConversations,
conversations => {

View file

@ -55,10 +55,12 @@ import { isMoreRecentThan } from '../../util/timestamp';
import { ConversationType } from '../ducks/conversations';
import { AccountSelectorType } from './accounts';
import { CallSelectorType, CallStateType } from './calling';
import {
GetConversationByIdType,
isMissingRequiredProfileSharing,
ContactNameColorSelectorType,
} from './conversations';
import {
SendStatus,
@ -100,7 +102,8 @@ export type GetPropsForBubbleOptions = Readonly<{
regionCode: string;
callSelector: CallSelectorType;
activeCall?: CallStateType;
accountSelector: (identifier?: string) => boolean;
accountSelector: AccountSelectorType;
contactNameColorSelector: ContactNameColorSelectorType;
}>;
export function isIncoming(
@ -450,10 +453,13 @@ export type GetPropsForMessageOptions = Pick<
GetPropsForBubbleOptions,
| 'conversationSelector'
| 'ourConversationId'
| 'ourUuid'
| 'ourNumber'
| 'selectedMessageId'
| 'selectedMessageCounter'
| 'regionCode'
| 'accountSelector'
| 'contactNameColorSelector'
>;
type ShallowPropsType = Pick<
@ -462,6 +468,7 @@ type ShallowPropsType = Pick<
| 'canDownload'
| 'canReply'
| 'contact'
| 'contactNameColor'
| 'conversationColor'
| 'conversationId'
| 'conversationType'
@ -497,12 +504,15 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)(
accountSelector,
conversationSelector,
ourConversationId,
ourNumber,
ourUuid,
regionCode,
selectedMessageId,
selectedMessageCounter,
contactNameColorSelector,
}: GetPropsForMessageOptions
): ShallowPropsType => {
const { expireTimer, expirationStartTimestamp } = message;
const { expireTimer, expirationStartTimestamp, conversationId } = message;
const expirationLength = expireTimer ? expireTimer * 1000 : undefined;
const expirationTimestamp =
expirationStartTimestamp && expirationLength
@ -522,14 +532,26 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)(
{}
).emoji;
const author = getContact(message, {
conversationSelector,
ourConversationId,
ourNumber,
ourUuid,
});
const contactNameColor = contactNameColorSelector(
conversationId,
author.id
);
return {
canDeleteForEveryone: canDeleteForEveryone(message),
canDownload: canDownload(message, conversationSelector),
canReply: canReply(message, ourConversationId, conversationSelector),
contact: getPropsForEmbeddedContact(message, regionCode, accountSelector),
contactNameColor,
conversationColor:
conversation?.conversationColor ?? ConversationColors[0],
conversationId: message.conversationId,
conversationId,
conversationType: isGroup ? 'group' : 'direct',
customColor: conversation?.customColor,
deletedForEveryone: message.deletedForEveryone || false,

View file

@ -10,7 +10,6 @@ import { StateType } from '../reducer';
import { TimelineItem } from '../../components/conversation/TimelineItem';
import { getIntl, getInteractionMode, getTheme } from '../selectors/user';
import {
getContactNameColorSelector,
getConversationSelector,
getMessageSelector,
getSelectedMessage,
@ -44,14 +43,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
const messageSelector = getMessageSelector(state);
const item = messageSelector(id);
if (item?.type === 'message' && item.data.conversationType === 'group') {
const { author } = item.data;
item.data.contactNameColor = getContactNameColorSelector(state)(
conversationId,
author.id
);
}
const selectedMessage = getSelectedMessage(state);
const isSelected = Boolean(selectedMessage && id === selectedMessage.id);

View file

@ -1737,6 +1737,7 @@ describe('both/state/selectors/conversations', () => {
it('returns the right color order sorted by UUID ASC', () => {
const group = makeConversation('group');
group.type = 'group';
group.sortedGroupMembers = [
makeConversationWithUuid('zyx'),
makeConversationWithUuid('vut'),
@ -1766,5 +1767,28 @@ describe('both/state/selectors/conversations', () => {
assert.equal(contactNameColorSelector('group', 'vut'), '330');
assert.equal(contactNameColorSelector('group', 'zyx'), '230');
});
it('returns the right colors for direct conversation', () => {
const direct = makeConversation('theirId');
const emptyState = getEmptyRootState();
const state = {
...emptyState,
user: {
...emptyState.user,
ourConversationId: 'us',
},
conversations: {
...getEmptyState(),
conversationLookup: {
direct,
},
},
};
const contactNameColorSelector = getContactNameColorSelector(state);
assert.equal(contactNameColorSelector('direct', 'theirId'), '200');
assert.equal(contactNameColorSelector('direct', 'us'), '200');
});
});
});