Add badges to all conversation lists
This commit is contained in:
parent
2c4dfc74c4
commit
2cbcd59609
22 changed files with 131 additions and 83 deletions
|
@ -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]}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -398,6 +398,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
|||
>
|
||||
<ConversationList
|
||||
dimensions={contentRect.bounds}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getRow={getRow}
|
||||
i18n={i18n}
|
||||
onClickArchiveButton={shouldNeverBeCalled}
|
||||
|
|
|
@ -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]}
|
||||
/>
|
||||
),
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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(),
|
||||
})),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue