Add badges to all conversation lists

This commit is contained in:
Evan Hahn 2021-11-17 15:11:21 -06:00 committed by GitHub
parent 2c4dfc74c4
commit 2cbcd59609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 131 additions and 83 deletions

View File

@ -17,6 +17,7 @@ import { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbo
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { ThemeType } from '../types/Util';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { UUID } from '../types/UUID';
@ -60,6 +61,7 @@ const Wrapper = ({
height: 350,
}}
rowCount={rows.length}
getPreferredBadge={() => undefined}
getRow={(index: number) => rows[index]}
shouldRecomputeRowHeights={false}
i18n={i18n}
@ -72,11 +74,13 @@ const Wrapper = ({
bodyRanges={[]}
conversationId="marc-convo"
from={defaultConversations[0]}
getPreferredBadge={() => undefined}
i18n={i18n}
id={id}
openConversationInternal={action('openConversationInternal')}
sentAt={1587358800000}
snippet="Lorem <<left>>ipsum<<right>> wow"
theme={ThemeType.light}
to={defaultConversations[1]}
/>
)}

View File

@ -8,17 +8,16 @@ import { List } from 'react-virtualized';
import classNames from 'classnames';
import { get, pick } from 'lodash';
import { getOwn } from '../util/getOwn';
import { missingCaseError } from '../util/missingCaseError';
import { assert } from '../util/assert';
import type { LocalizerType, ThemeType } from '../types/Util';
import { ScrollBehavior } from '../types/Util';
import { getConversationListWidthBreakpoint } from './_util';
import type { BadgeType } from '../badges/types';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { PropsData as ConversationListItemPropsType } from './conversationList/ConversationListItem';
import { ConversationListItem } from './conversationList/ConversationListItem';
import type { PropsDataType as ContactListItemPropsType } from './conversationList/ContactListItem';
import type { ContactListItemConversationType as ContactListItemPropsType } from './conversationList/ContactListItem';
import { ContactListItem } from './conversationList/ContactListItem';
import type { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox';
import { ContactCheckbox as ContactCheckboxComponent } from './conversationList/ContactCheckbox';
@ -116,7 +115,6 @@ export type Row =
| UsernameRowType;
export type PropsType = {
badgesById?: Record<string, BadgeType>;
dimensions?: {
width: number;
height: number;
@ -131,6 +129,7 @@ export type PropsType = {
shouldRecomputeRowHeights: boolean;
scrollable?: boolean;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
theme: ThemeType;
@ -150,8 +149,8 @@ const NORMAL_ROW_HEIGHT = 76;
const HEADER_ROW_HEIGHT = 40;
export const ConversationList: React.FC<PropsType> = ({
badgesById,
dimensions,
getPreferredBadge,
getRow,
i18n,
onClickArchiveButton,
@ -231,8 +230,10 @@ export const ConversationList: React.FC<PropsType> = ({
result = (
<ContactListItem
{...row.contact}
badge={getPreferredBadge(row.contact.badges)}
onClick={isClickable ? onSelectConversation : undefined}
i18n={i18n}
theme={theme}
/>
);
break;
@ -241,10 +242,12 @@ export const ConversationList: React.FC<PropsType> = ({
result = (
<ContactCheckboxComponent
{...row.contact}
badge={getPreferredBadge(row.contact.badges)}
isChecked={row.isChecked}
disabledReason={row.disabledReason}
onClick={onClickContactCheckbox}
i18n={i18n}
theme={theme}
/>
);
break;
@ -274,12 +277,6 @@ export const ConversationList: React.FC<PropsType> = ({
'unreadCount',
]);
const { badges, title, unreadCount, lastMessage } = itemProps;
let badge: undefined | BadgeType;
if (badgesById && badges[0]) {
badge = getOwn(badgesById, badges[0].id);
}
result = (
<div
aria-label={i18n('ConversationList__aria-label', {
@ -293,7 +290,7 @@ export const ConversationList: React.FC<PropsType> = ({
<ConversationListItem
{...itemProps}
key={key}
badge={badge}
badge={getPreferredBadge(badges)}
onClick={onSelectConversation}
i18n={i18n}
theme={theme}
@ -361,7 +358,7 @@ export const ConversationList: React.FC<PropsType> = ({
);
},
[
badgesById,
getPreferredBadge,
getRow,
i18n,
onClickArchiveButton,

View File

@ -398,6 +398,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
>
<ConversationList
dimensions={contentRect.bounds}
getPreferredBadge={getPreferredBadge}
getRow={getRow}
i18n={i18n}
onClickArchiveButton={shouldNeverBeCalled}

View File

@ -14,6 +14,7 @@ import type { ConversationType } from '../state/ducks/conversations';
import { MessageSearchResult } from './conversationList/MessageSearchResult';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { ThemeType } from '../types/Util';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
@ -81,7 +82,6 @@ const defaultModeSpecificProps = {
const emptySearchResultsGroup = { isLoading: false, results: [] };
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
badgesById: {},
cantAddContactToGroup: action('cantAddContactToGroup'),
canResizeLeftPane: true,
clearGroupCreationError: action('clearGroupCreationError'),
@ -93,6 +93,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
composeReplaceAvatar: action('composeReplaceAvatar'),
composeSaveAvatarToDisk: action('composeSaveAvatarToDisk'),
createGroup: action('createGroup'),
getPreferredBadge: () => undefined,
i18n,
modeSpecificProps: defaultModeSpecificProps,
preferredWidthFromStorage: 320,
@ -112,11 +113,13 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
bodyRanges={[]}
conversationId="marc-convo"
from={defaultConversations[0]}
getPreferredBadge={() => undefined}
i18n={i18n}
id={id}
openConversationInternal={action('openConversationInternal')}
sentAt={1587358800000}
snippet="Lorem <<left>>ipsum<<right>> wow"
theme={ThemeType.light}
to={defaultConversations[1]}
/>
),

View File

@ -25,7 +25,7 @@ import { LeftPaneSetGroupMetadataHelper } from './leftPane/LeftPaneSetGroupMetad
import * as OS from '../OS';
import type { LocalizerType, ThemeType } from '../types/Util';
import { ScrollBehavior } from '../types/Util';
import type { BadgeType } from '../badges/types';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { usePrevious } from '../hooks/usePrevious';
import { missingCaseError } from '../util/missingCaseError';
import { strictAssert } from '../util/assert';
@ -83,8 +83,8 @@ export type PropsType = {
| ({
mode: LeftPaneMode.SetGroupMetadata;
} & LeftPaneSetGroupMetadataPropsType);
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
badgesById: Record<string, BadgeType>;
preferredWidthFromStorage: number;
selectedConversationId: undefined | string;
selectedMessageId: undefined | string;
@ -147,7 +147,6 @@ export type PropsType = {
};
export const LeftPane: React.FC<PropsType> = ({
badgesById,
cantAddContactToGroup,
canResizeLeftPane,
challengeStatus,
@ -160,6 +159,7 @@ export const LeftPane: React.FC<PropsType> = ({
composeReplaceAvatar,
composeSaveAvatarToDisk,
createGroup,
getPreferredBadge,
i18n,
modeSpecificProps,
openConversationInternal,
@ -573,11 +573,11 @@ export const LeftPane: React.FC<PropsType> = ({
tabIndex={-1}
>
<ConversationList
badgesById={badgesById}
dimensions={{
width,
height: contentRect.bounds?.height || 0,
}}
getPreferredBadge={getPreferredBadge}
getRow={getRow}
i18n={i18n}
onClickArchiveButton={showArchivedConversations}

View File

@ -31,6 +31,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
candidateContacts: allCandidateContacts,
clearRequestError: action('clearRequestError'),
conversationIdsAlreadyInGroup: new Set(),
getPreferredBadge: () => undefined,
groupTitle: 'Tahoe Trip',
i18n,
onClose: action('onClose'),

View File

@ -11,6 +11,7 @@ import {
AddGroupMemberErrorDialogMode,
} from '../../AddGroupMemberErrorDialog';
import type { ConversationType } from '../../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
import {
getGroupSizeRecommendedLimit,
getGroupSizeHardLimit,
@ -30,6 +31,7 @@ type PropsType = {
candidateContacts: ReadonlyArray<ConversationType>;
clearRequestError: () => void;
conversationIdsAlreadyInGroup: Set<string>;
getPreferredBadge: PreferredBadgeSelectorType;
groupTitle: string;
i18n: LocalizerType;
makeRequest: (conversationIds: ReadonlyArray<string>) => Promise<void>;
@ -147,6 +149,7 @@ export const AddGroupMembersModal: FunctionComponent<PropsType> = ({
candidateContacts,
clearRequestError,
conversationIdsAlreadyInGroup,
getPreferredBadge,
groupTitle,
i18n,
onClose,
@ -279,6 +282,7 @@ export const AddGroupMembersModal: FunctionComponent<PropsType> = ({
confirmAdds={confirmAdds}
contactLookup={contactLookup}
conversationIdsAlreadyInGroup={conversationIdsAlreadyInGroup}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
maxGroupSize={maxGroupSize}
onClose={onClose}

View File

@ -14,6 +14,7 @@ import { useRestoreFocus } from '../../../../hooks/useRestoreFocus';
import { missingCaseError } from '../../../../util/missingCaseError';
import { filterAndSortConversationsByTitle } from '../../../../util/filterAndSortConversations';
import type { ConversationType } from '../../../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../../../state/selectors/badges';
import { ModalHost } from '../../../ModalHost';
import { ContactPills } from '../../../ContactPills';
import { ContactPill } from '../../../ContactPill';
@ -28,6 +29,7 @@ type PropsType = {
confirmAdds: () => void;
contactLookup: Record<string, ConversationType>;
conversationIdsAlreadyInGroup: Set<string>;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
maxGroupSize: number;
onClose: () => void;
@ -48,6 +50,7 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
confirmAdds,
contactLookup,
conversationIdsAlreadyInGroup,
getPreferredBadge,
i18n,
maxGroupSize,
onClose,
@ -192,6 +195,7 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
>
<ConversationList
dimensions={contentRect.bounds}
getPreferredBadge={getPreferredBadge}
getRow={getRow}
i18n={i18n}
onClickArchiveButton={shouldNeverBeCalled}

View File

@ -46,6 +46,7 @@ const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
}
: conversation,
hasGroupLink,
getPreferredBadge: () => undefined,
i18n,
isAdmin: false,
isGroup: true,
@ -56,7 +57,6 @@ const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
isMe: i === 2,
}),
})),
preferredBadgeByConversation: {},
pendingApprovalMemberships: times(8, () => ({
member: getDefaultConversation(),
})),

View File

@ -6,6 +6,7 @@ import React, { useState } from 'react';
import { Button, ButtonIconType, ButtonVariant } from '../../Button';
import type { ConversationType } from '../../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
import { assert } from '../../../util/assert';
import { getMutedUntilText } from '../../../util/getMutedUntilText';
@ -59,12 +60,12 @@ export type StateProps = {
candidateContactsToAdd: Array<ConversationType>;
conversation?: ConversationType;
hasGroupLink: boolean;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
isAdmin: boolean;
isGroup: boolean;
loadRecentMediaItems: (limit: number) => void;
memberships: Array<GroupV2Membership>;
preferredBadgeByConversation: Record<string, BadgeType>;
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
setDisappearingMessages: (seconds: number) => void;
@ -114,6 +115,7 @@ export const ConversationDetails: React.ComponentType<Props> = ({
conversation,
deleteAvatarFromDisk,
hasGroupLink,
getPreferredBadge,
i18n,
isAdmin,
isGroup,
@ -126,7 +128,6 @@ export const ConversationDetails: React.ComponentType<Props> = ({
onUnblock,
pendingApprovalMemberships,
pendingMemberships,
preferredBadgeByConversation,
replaceAvatar,
saveAvatarToDisk,
searchInConversation,
@ -235,6 +236,7 @@ export const ConversationDetails: React.ComponentType<Props> = ({
conversationIdsAlreadyInGroup={
new Set(memberships.map(membership => membership.member.id))
}
getPreferredBadge={getPreferredBadge}
groupTitle={conversation.title}
i18n={i18n}
makeRequest={async conversationIds => {
@ -459,9 +461,9 @@ export const ConversationDetails: React.ComponentType<Props> = ({
<ConversationDetailsMembershipList
canAddNewMembers={canEditGroupInfo}
conversationId={conversation.id}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
memberships={memberships}
preferredBadgeByConversation={preferredBadgeByConversation}
showContactModal={showContactModal}
startAddingNewMembers={() => {
setModalState(ModalState.AddingGroupMembers);

View File

@ -11,9 +11,7 @@ import { number } from '@storybook/addon-knobs';
import { setupI18n } from '../../../util/setupI18n';
import enMessages from '../../../../_locales/en/messages.json';
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
import { getFakeBadge } from '../../../test-both/helpers/getFakeBadge';
import { ThemeType } from '../../../types/Util';
import type { BadgeType } from '../../../badges/types';
import type {
Props,
@ -48,20 +46,9 @@ const createProps = (overrideProps: Partial<Props>): Props => ({
? overrideProps.canAddNewMembers
: false,
conversationId: '123',
getPreferredBadge: () => undefined,
i18n,
memberships: overrideProps.memberships || [],
preferredBadgeByConversation:
overrideProps.preferredBadgeByConversation ||
(overrideProps.memberships || []).reduce(
(result: Record<string, BadgeType>, { member }, index) =>
(index + 1) % 3 === 0
? {
...result,
[member.id]: getFakeBadge({ alternate: index % 2 !== 0 }),
}
: result,
{}
),
showContactModal: action('showContactModal'),
startAddingNewMembers: action('startAddingNewMembers'),
theme: ThemeType.light,

View File

@ -4,14 +4,13 @@
import React from 'react';
import type { LocalizerType, ThemeType } from '../../../types/Util';
import { getOwn } from '../../../util/getOwn';
import type { BadgeType } from '../../../badges/types';
import { Avatar } from '../../Avatar';
import { Emojify } from '../Emojify';
import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon';
import type { ConversationType } from '../../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
import { PanelRow } from './PanelRow';
import { PanelSection } from './PanelSection';
@ -23,10 +22,10 @@ export type GroupV2Membership = {
export type Props = {
canAddNewMembers: boolean;
conversationId: string;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
maxShownMemberCount?: number;
memberships: Array<GroupV2Membership>;
preferredBadgeByConversation: Record<string, BadgeType>;
showContactModal: (contactId: string, conversationId: string) => void;
startAddingNewMembers?: () => void;
theme: ThemeType;
@ -74,10 +73,10 @@ function sortMemberships(
export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
canAddNewMembers,
conversationId,
getPreferredBadge,
i18n,
maxShownMemberCount = 5,
memberships,
preferredBadgeByConversation,
showContactModal,
startAddingNewMembers,
theme,
@ -114,7 +113,7 @@ export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
icon={
<Avatar
conversationType="direct"
badge={getOwn(preferredBadgeByConversation, member.id)}
badge={getPreferredBadge(member.badges)}
i18n={i18n}
size={32}
theme={theme}

View File

@ -29,7 +29,6 @@ export const MESSAGE_TEXT_CLASS_NAME = `${MESSAGE_CLASS_NAME}__text`;
const CHECKBOX_CLASS_NAME = `${BASE_CLASS_NAME}__checkbox`;
type PropsType = {
badge?: BadgeType;
checked?: boolean;
conversationType: 'group' | 'direct';
disabled?: boolean;
@ -47,7 +46,6 @@ type PropsType = {
messageTextIsAlwaysFullSize?: boolean;
onClick?: () => void;
shouldShowSpinner?: boolean;
theme?: ThemeType;
unreadCount?: number;
} & Pick<
ConversationType,
@ -62,7 +60,11 @@ type PropsType = {
| 'sharedGroupNames'
| 'title'
| 'unblurredAvatarPath'
>;
> &
(
| { badge?: undefined; theme?: ThemeType }
| { badge: BadgeType; theme: ThemeType }
);
export const BaseConversationListItem: FunctionComponent<PropsType> =
React.memo(function BaseConversationListItem({

View File

@ -9,7 +9,8 @@ import {
HEADER_CONTACT_NAME_CLASS_NAME,
} from './BaseConversationListItem';
import type { ConversationType } from '../../state/ducks/conversations';
import type { LocalizerType } from '../../types/Util';
import type { BadgeType } from '../../badges/types';
import type { LocalizerType, ThemeType } from '../../types/Util';
import { ContactName } from '../conversation/ContactName';
import { About } from '../conversation/About';
@ -21,6 +22,7 @@ export enum ContactCheckboxDisabledReason {
}
export type PropsDataType = {
badge: undefined | BadgeType;
disabledReason?: ContactCheckboxDisabledReason;
isChecked: boolean;
} & Pick<
@ -46,6 +48,7 @@ type PropsHousekeepingType = {
id: string,
disabledReason: undefined | ContactCheckboxDisabledReason
) => void;
theme: ThemeType;
};
type PropsType = PropsDataType & PropsHousekeepingType;
@ -55,6 +58,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
about,
acceptedMessageRequest,
avatarPath,
badge,
color,
disabledReason,
i18n,
@ -66,6 +70,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
phoneNumber,
profileName,
sharedGroupNames,
theme,
title,
type,
unblurredAvatarPath,
@ -97,6 +102,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
<BaseConversationListItem
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={badge}
checked={isChecked}
color={color}
conversationType={type}
@ -112,6 +118,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
phoneNumber={phoneNumber}
profileName={profileName}
sharedGroupNames={sharedGroupNames}
theme={theme}
title={title}
unblurredAvatarPath={unblurredAvatarPath}
/>

View File

@ -9,15 +9,17 @@ import {
HEADER_CONTACT_NAME_CLASS_NAME,
} from './BaseConversationListItem';
import type { ConversationType } from '../../state/ducks/conversations';
import type { LocalizerType } from '../../types/Util';
import type { BadgeType } from '../../badges/types';
import type { LocalizerType, ThemeType } from '../../types/Util';
import { ContactName } from '../conversation/ContactName';
import { About } from '../conversation/About';
export type PropsDataType = Pick<
export type ContactListItemConversationType = Pick<
ConversationType,
| 'about'
| 'acceptedMessageRequest'
| 'avatarPath'
| 'badges'
| 'color'
| 'id'
| 'isMe'
@ -30,9 +32,14 @@ export type PropsDataType = Pick<
| 'unblurredAvatarPath'
>;
type PropsDataType = ContactListItemConversationType & {
badge: undefined | BadgeType;
};
type PropsHousekeepingType = {
i18n: LocalizerType;
onClick?: (id: string) => void;
theme: ThemeType;
};
type PropsType = PropsDataType & PropsHousekeepingType;
@ -42,6 +49,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
about,
acceptedMessageRequest,
avatarPath,
badge,
color,
i18n,
id,
@ -51,6 +59,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
phoneNumber,
profileName,
sharedGroupNames,
theme,
title,
type,
unblurredAvatarPath,
@ -70,6 +79,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
<BaseConversationListItem
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={badge}
color={color}
conversationType={type}
headerName={headerName}
@ -83,6 +93,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
phoneNumber={phoneNumber}
profileName={profileName}
sharedGroupNames={sharedGroupNames}
theme={theme}
title={title}
unblurredAvatarPath={unblurredAvatarPath}
/>

View File

@ -8,6 +8,9 @@ import { boolean, text, withKnobs } from '@storybook/addon-knobs';
import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json';
import { StorybookThemeContext } from '../../../.storybook/StorybookThemeContext';
import { strictAssert } from '../../util/assert';
import { getFakeBadge } from '../../test-both/helpers/getFakeBadge';
import type { PropsType } from './MessageSearchResult';
import { MessageSearchResult } from './MessageSearchResult';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
@ -37,7 +40,7 @@ const group = getDefaultConversation({
type: 'group',
});
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
i18n,
id: '',
conversationId: '',
@ -50,16 +53,18 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
bodyRanges: overrideProps.bodyRanges || [],
from: overrideProps.from as PropsType['from'],
to: overrideProps.to as PropsType['to'],
getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined),
isSelected: boolean('isSelected', overrideProps.isSelected || false),
openConversationInternal: action('openConversationInternal'),
isSearchingInConversation: boolean(
'isSearchingInConversation',
overrideProps.isSearchingInConversation || false
),
theme: React.useContext(StorybookThemeContext),
});
story.add('Default', () => {
const props = createProps({
const props = useProps({
from: someone,
to: me,
});
@ -67,8 +72,24 @@ story.add('Default', () => {
return <MessageSearchResult {...props} />;
});
story.add('Sender has a badge', () => {
const props = useProps({
from: { ...someone, badges: [{ id: 'sender badge' }] },
to: me,
getPreferredBadge: badges => {
strictAssert(
badges[0]?.id === 'sender badge',
'Rendering the wrong badge!'
);
return getFakeBadge();
},
});
return <MessageSearchResult {...props} />;
});
story.add('Selected', () => {
const props = createProps({
const props = useProps({
from: someone,
to: me,
isSelected: true,
@ -78,7 +99,7 @@ story.add('Selected', () => {
});
story.add('From You', () => {
const props = createProps({
const props = useProps({
from: me,
to: someone,
});
@ -87,7 +108,7 @@ story.add('From You', () => {
});
story.add('Searching in Conversation', () => {
const props = createProps({
const props = useProps({
from: me,
to: someone,
isSearchingInConversation: true,
@ -97,7 +118,7 @@ story.add('Searching in Conversation', () => {
});
story.add('From You to Yourself', () => {
const props = createProps({
const props = useProps({
from: me,
to: me,
});
@ -106,7 +127,7 @@ story.add('From You to Yourself', () => {
});
story.add('From You to Group', () => {
const props = createProps({
const props = useProps({
from: me,
to: group,
});
@ -115,7 +136,7 @@ story.add('From You to Group', () => {
});
story.add('From Someone to Group', () => {
const props = createProps({
const props = useProps({
from: someone,
to: group,
});
@ -130,7 +151,7 @@ story.add('Long Search Result', () => {
];
return snippets.map(snippet => {
const props = createProps({
const props = useProps({
from: someone,
to: me,
snippet,
@ -141,13 +162,13 @@ story.add('Long Search Result', () => {
});
story.add('Empty (should be invalid)', () => {
const props = createProps();
const props = useProps();
return <MessageSearchResult {...props} />;
});
story.add('@mention', () => {
const props = createProps({
const props = useProps({
body: 'moss banana twine sound lake zoo brain count vacuum work stairs try power forget hair dry diary years no results \uFFFC elephant sorry umbrella potato igloo kangaroo home Georgia bayonet vector orange forge diary zebra turtle rise front \uFFFC',
bodyRanges: [
{
@ -173,7 +194,7 @@ story.add('@mention', () => {
});
story.add('@mention regexp', () => {
const props = createProps({
const props = useProps({
body: '\uFFFC This is a (long) /text/ ^$ that is ... specially **crafted** to (test) our regexp escaping mechanism! Making sure that the code we write works in all sorts of scenarios',
bodyRanges: [
{
@ -193,7 +214,7 @@ story.add('@mention regexp', () => {
});
story.add('@mention no-matches', () => {
const props = createProps({
const props = useProps({
body: '\uFFFC hello',
bodyRanges: [
{
@ -212,7 +233,7 @@ story.add('@mention no-matches', () => {
});
story.add('@mention no-matches', () => {
const props = createProps({
const props = useProps({
body: 'moss banana twine sound lake zoo brain count vacuum work stairs try power forget hair dry diary years no results \uFFFC elephant sorry umbrella potato igloo kangaroo home Georgia bayonet vector orange forge diary zebra turtle rise front \uFFFC',
bodyRanges: [
{
@ -238,7 +259,7 @@ story.add('@mention no-matches', () => {
});
story.add('Double @mention', () => {
const props = createProps({
const props = useProps({
body: 'Hey \uFFFC \uFFFC test',
bodyRanges: [
{

View File

@ -9,9 +9,14 @@ import { MessageBodyHighlight } from './MessageBodyHighlight';
import { ContactName } from '../conversation/ContactName';
import { assert } from '../../util/assert';
import type { BodyRangesType, LocalizerType } from '../../types/Util';
import type {
BodyRangesType,
LocalizerType,
ThemeType,
} from '../../types/Util';
import { BaseConversationListItem } from './BaseConversationListItem';
import type { ConversationType } from '../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
export type PropsDataType = {
isSelected?: boolean;
@ -29,6 +34,7 @@ export type PropsDataType = {
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'badges'
| 'color'
| 'isMe'
| 'name'
@ -50,11 +56,13 @@ export type PropsDataType = {
};
type PropsHousekeepingType = {
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
openConversationInternal: (_: {
conversationId: string;
messageId?: string;
}) => void;
theme: ThemeType;
};
export type PropsType = PropsDataType & PropsHousekeepingType;
@ -136,11 +144,13 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
bodyRanges,
conversationId,
from,
getPreferredBadge,
i18n,
id,
openConversationInternal,
sentAt,
snippet,
theme,
to,
}) {
const onClickItem = useCallback(() => {
@ -179,6 +189,7 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
<BaseConversationListItem
acceptedMessageRequest={from.acceptedMessageRequest}
avatarPath={from.avatarPath}
badge={getPreferredBadge(from.badges)}
color={from.color}
conversationType="direct"
headerDate={sentAt}
@ -194,6 +205,7 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
phoneNumber={from.phoneNumber}
profileName={from.profileName}
sharedGroupNames={from.sharedGroupNames}
theme={theme}
title={from.title}
unblurredAvatarPath={from.unblurredAvatarPath}
/>

View File

@ -8,7 +8,7 @@ import type { PhoneNumber } from 'google-libphonenumber';
import { LeftPaneHelper } from './LeftPaneHelper';
import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsDataType as ContactListItemPropsType } from '../conversationList/ContactListItem';
import type { ContactListItemConversationType } from '../conversationList/ContactListItem';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import { SearchInput } from '../SearchInput';
import type { LocalizerType } from '../../types/Util';
@ -21,7 +21,7 @@ import { missingCaseError } from '../../util/missingCaseError';
import { getUsernameFromSearch } from '../../types/Username';
export type LeftPaneComposePropsType = {
composeContacts: ReadonlyArray<ContactListItemPropsType>;
composeContacts: ReadonlyArray<ContactListItemConversationType>;
composeGroups: ReadonlyArray<ConversationListItemPropsType>;
regionCode: string;
@ -37,7 +37,7 @@ enum TopButton {
}
export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsType> {
private readonly composeContacts: ReadonlyArray<ContactListItemPropsType>;
private readonly composeContacts: ReadonlyArray<ContactListItemConversationType>;
private readonly composeGroups: ReadonlyArray<ConversationListItemPropsType>;

View File

@ -7,7 +7,7 @@ import React from 'react';
import { LeftPaneHelper } from './LeftPaneHelper';
import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsDataType as ContactListItemPropsType } from '../conversationList/ContactListItem';
import type { ContactListItemConversationType } from '../conversationList/ContactListItem';
import { DisappearingTimerSelect } from '../DisappearingTimerSelect';
import type { LocalizerType } from '../../types/Util';
import { Alert } from '../Alert';
@ -32,7 +32,7 @@ export type LeftPaneSetGroupMetadataPropsType = {
hasError: boolean;
isCreating: boolean;
isEditingAvatar: boolean;
selectedContacts: ReadonlyArray<ContactListItemPropsType>;
selectedContacts: ReadonlyArray<ContactListItemConversationType>;
userAvatarData: ReadonlyArray<AvatarDataType>;
};
@ -49,7 +49,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
private readonly isEditingAvatar: boolean;
private readonly selectedContacts: ReadonlyArray<ContactListItemPropsType>;
private readonly selectedContacts: ReadonlyArray<ContactListItemConversationType>;
private readonly userAvatarData: ReadonlyArray<AvatarDataType>;

View File

@ -19,7 +19,6 @@ import {
getBadgesSelector,
getPreferredBadgeSelector,
} from '../selectors/badges';
import type { BadgeType } from '../../badges/types';
import { assert } from '../../util/assert';
import { SignalService as Proto } from '../../protobuf';
@ -81,24 +80,15 @@ const mapStateToProps = (
const badges = getBadgesSelector(state)(conversation.badges);
const preferredBadgeByConversation: Record<string, BadgeType> = {};
const getPreferredBadge = getPreferredBadgeSelector(state);
groupMemberships.memberships.forEach(({ member }) => {
const preferredBadge = getPreferredBadge(member.badges);
if (preferredBadge) {
preferredBadgeByConversation[member.id] = preferredBadge;
}
});
return {
...props,
badges,
canEditGroupInfo,
candidateContactsToAdd,
conversation,
getPreferredBadge: getPreferredBadgeSelector(state),
i18n: getIntl(state),
isAdmin,
preferredBadgeByConversation,
...groupMemberships,
userAvatarData: conversation.avatars || [],
hasGroupLink,

View File

@ -20,7 +20,7 @@ import {
isSearching,
} from '../selectors/search';
import { getIntl, getRegionCode, getTheme } from '../selectors/user';
import { getBadgesById } from '../selectors/badges';
import { getPreferredBadgeSelector } from '../selectors/badges';
import {
getPreferredLeftPaneWidth,
getUsernamesEnabled,
@ -166,7 +166,6 @@ const getModeSpecificProps = (
const mapStateToProps = (state: StateType) => {
return {
modeSpecificProps: getModeSpecificProps(state),
badgesById: getBadgesById(state),
canResizeLeftPane: window.Signal.RemoteConfig.isEnabled(
'desktop.internalUser'
),
@ -174,6 +173,7 @@ const mapStateToProps = (state: StateType) => {
selectedConversationId: getSelectedConversationId(state),
selectedMessageId: getSelectedMessage(state)?.id,
showArchived: getShowArchived(state),
getPreferredBadge: getPreferredBadgeSelector(state),
i18n: getIntl(state),
regionCode: getRegionCode(state),
challengeStatus: state.network.challengeStatus,

View File

@ -8,7 +8,8 @@ import { mapDispatchToProps } from '../actions';
import type { StateType } from '../reducer';
import { MessageSearchResult } from '../../components/conversationList/MessageSearchResult';
import { getIntl } from '../selectors/user';
import { getPreferredBadgeSelector } from '../selectors/badges';
import { getIntl, getTheme } from '../selectors/user';
import { getMessageSearchResultSelector } from '../selectors/search';
type SmartProps = {
@ -26,8 +27,10 @@ function mapStateToProps(state: StateType, ourProps: SmartProps) {
return {
...props,
getPreferredBadge: getPreferredBadgeSelector(state),
i18n: getIntl(state),
style,
theme: getTheme(state),
};
}
const smart = connect(mapStateToProps, mapDispatchToProps);