signal-desktop/ts/state/selectors/stories.ts

252 lines
6.2 KiB
TypeScript
Raw Normal View History

2022-03-04 21:14:52 +00:00
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { createSelector } from 'reselect';
import { pick } from 'lodash';
2022-04-15 00:08:46 +00:00
import type { GetConversationByIdType } from './conversations';
2022-03-04 21:14:52 +00:00
import type {
ConversationStoryType,
StoryViewType,
} from '../../components/StoryListItem';
2022-04-15 00:08:46 +00:00
import type { ReplyStateType } from '../../types/Stories';
2022-03-04 21:14:52 +00:00
import type { StateType } from '../reducer';
2022-04-15 00:08:46 +00:00
import type { StoryDataType, StoriesStateType } from '../ducks/stories';
2022-03-04 21:14:52 +00:00
import { ReadStatus } from '../../messages/MessageReadStatus';
2022-04-15 00:08:46 +00:00
import { canReply } from './message';
import {
getContactNameColorSelector,
getConversationSelector,
getMe,
} from './conversations';
import { getUserConversationId } from './user';
2022-03-04 21:14:52 +00:00
export const getStoriesState = (state: StateType): StoriesStateType =>
state.stories;
export const shouldShowStoriesView = createSelector(
getStoriesState,
({ isShowingStoriesView }): boolean => isShowingStoriesView
);
2022-04-08 15:40:15 +00:00
function getNewestStory(x: ConversationStoryType): StoryViewType {
return x.stories[x.stories.length - 1];
}
function sortByRecencyAndUnread(
a: ConversationStoryType,
b: ConversationStoryType
): number {
const storyA = getNewestStory(a);
const storyB = getNewestStory(b);
if (storyA.isUnread && storyB.isUnread) {
return storyA.timestamp > storyB.timestamp ? -1 : 1;
}
if (storyB.isUnread) {
return 1;
}
if (storyA.isUnread) {
return -1;
}
return storyA.timestamp > storyB.timestamp ? -1 : 1;
}
2022-04-15 00:08:46 +00:00
function getConversationStory(
conversationSelector: GetConversationByIdType,
story: StoryDataType,
ourConversationId?: string
): ConversationStoryType {
const sender = pick(conversationSelector(story.sourceUuid || story.source), [
'acceptedMessageRequest',
'avatarPath',
'color',
'firstName',
'hideStory',
'id',
'isMe',
'name',
'profileName',
'sharedGroupNames',
'title',
]);
const conversation = pick(conversationSelector(story.conversationId), [
'acceptedMessageRequest',
'avatarPath',
'color',
'id',
'name',
'profileName',
'sharedGroupNames',
'title',
]);
const { attachment, timestamp } = pick(story, ['attachment', 'timestamp']);
const storyView: StoryViewType = {
attachment,
canReply: canReply(story, ourConversationId, conversationSelector),
isUnread: story.readStatus === ReadStatus.Unread,
messageId: story.messageId,
selectedReaction: story.selectedReaction,
sender,
timestamp,
};
return {
conversationId: conversation.id,
group: conversation.id !== sender.id ? conversation : undefined,
isHidden: Boolean(sender.hideStory),
stories: [storyView],
};
}
export type GetStoriesByConversationIdType = (
conversationId: string
) => ConversationStoryType;
export const getStoriesSelector = createSelector(
getConversationSelector,
getUserConversationId,
getStoriesState,
(
conversationSelector,
ourConversationId,
{ stories }: Readonly<StoriesStateType>
): GetStoriesByConversationIdType => {
return conversationId => {
const conversationStoryAcc: ConversationStoryType = {
conversationId,
stories: [],
};
return stories.reduce((acc, story) => {
if (story.conversationId !== conversationId) {
return acc;
}
const conversationStory = getConversationStory(
conversationSelector,
story,
ourConversationId
);
return {
...acc,
...conversationStory,
stories: [...acc.stories, ...conversationStory.stories],
};
}, conversationStoryAcc);
};
}
);
export const getStoryReplies = createSelector(
getConversationSelector,
getContactNameColorSelector,
getMe,
getStoriesState,
(
conversationSelector,
contactNameColorSelector,
me,
{ replyState }: Readonly<StoriesStateType>
): ReplyStateType | undefined => {
if (!replyState) {
return;
}
return {
messageId: replyState.messageId,
replies: replyState.replies.map(reply => {
const conversation =
reply.type === 'outgoing'
? me
: conversationSelector(reply.sourceUuid || reply.source);
return {
...pick(conversation, [
'acceptedMessageRequest',
'avatarPath',
'color',
'isMe',
'name',
'profileName',
'sharedGroupNames',
'title',
]),
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
contactNameColor: contactNameColorSelector(
reply.conversationId,
conversation.id
),
};
}),
};
}
);
2022-03-04 21:14:52 +00:00
export const getStories = createSelector(
getConversationSelector,
2022-04-15 00:08:46 +00:00
getUserConversationId,
2022-03-04 21:14:52 +00:00
getStoriesState,
2022-03-29 01:10:08 +00:00
shouldShowStoriesView,
2022-03-04 21:14:52 +00:00
(
conversationSelector,
2022-04-15 00:08:46 +00:00
ourConversationId,
2022-03-29 01:10:08 +00:00
{ stories }: Readonly<StoriesStateType>,
isShowingStoriesView
2022-03-04 21:14:52 +00:00
): {
hiddenStories: Array<ConversationStoryType>;
stories: Array<ConversationStoryType>;
} => {
2022-03-29 01:10:08 +00:00
if (!isShowingStoriesView) {
return {
hiddenStories: [],
stories: [],
};
}
2022-03-04 21:14:52 +00:00
const storiesById = new Map<string, ConversationStoryType>();
const hiddenStoriesById = new Map<string, ConversationStoryType>();
stories.forEach(story => {
2022-04-15 00:08:46 +00:00
const conversationStory = getConversationStory(
conversationSelector,
story,
ourConversationId
2022-03-04 21:14:52 +00:00
);
let storiesMap: Map<string, ConversationStoryType>;
2022-04-15 00:08:46 +00:00
if (conversationStory.isHidden) {
2022-03-04 21:14:52 +00:00
storiesMap = hiddenStoriesById;
} else {
storiesMap = storiesById;
}
2022-04-15 00:08:46 +00:00
const existingConversationStory = storiesMap.get(
conversationStory.conversationId
) || { stories: [] };
2022-03-04 21:14:52 +00:00
2022-04-15 00:08:46 +00:00
storiesMap.set(conversationStory.conversationId, {
...existingConversationStory,
2022-03-04 21:14:52 +00:00
...conversationStory,
2022-04-15 00:08:46 +00:00
stories: [
...existingConversationStory.stories,
...conversationStory.stories,
],
2022-03-04 21:14:52 +00:00
});
});
return {
2022-04-08 15:40:15 +00:00
hiddenStories: Array.from(hiddenStoriesById.values()).sort(
sortByRecencyAndUnread
),
stories: Array.from(storiesById.values()).sort(sortByRecencyAndUnread),
2022-03-04 21:14:52 +00:00
};
}
);