Don't mutate state in TimelineItem
This commit is contained in:
parent
1cc7c5dc2d
commit
80c1ad6ee3
5 changed files with 127 additions and 61 deletions
|
@ -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,
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue