Switched ForwardMessageModal to use ListTile
This commit is contained in:
parent
257f5e1231
commit
d64e0b65c4
25 changed files with 528 additions and 239 deletions
|
@ -122,6 +122,8 @@ const rules = {
|
|||
|
||||
'react/display-name': 'error',
|
||||
|
||||
'react/jsx-pascal-case': ['error', {allowNamespace: true}],
|
||||
|
||||
// Allow returning values from promise executors for brevity.
|
||||
'no-promise-executor-return': 'off',
|
||||
|
||||
|
|
|
@ -4405,6 +4405,13 @@ button.module-image__border-overlay:focus {
|
|||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
// list tiles in choose-group-members and compose extend to the edge
|
||||
.module-left-pane--mode-choose-group-members &,
|
||||
.module-left-pane--mode-compose & {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&--width-narrow {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
|
|
@ -64,4 +64,9 @@
|
|||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.module-conversation-list {
|
||||
// remove horizontal padding so ListTiles extend to the edges
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
max-height: 88px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding-left: 12px;
|
||||
padding: 4px 24px;
|
||||
gap: 8px 12px;
|
||||
|
||||
.module-ContactPill {
|
||||
margin: 4px 6px;
|
||||
max-width: calc(
|
||||
100% - 15px
|
||||
); // 6px for the right margin and 9px for the scrollbar
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
color: $color-gray-05;
|
||||
}
|
||||
|
||||
.module-conversation-list {
|
||||
// remove horizontal padding so ListTiles extend to the edges
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&--link-preview {
|
||||
border-bottom: 1px solid $color-gray-15;
|
||||
padding: 12px 16px;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { noop, pick } from 'lodash';
|
||||
import React from 'react';
|
||||
import { pick } from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import type { ListRowProps } from 'react-virtualized';
|
||||
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import type {
|
||||
|
@ -15,12 +16,16 @@ import type {
|
|||
import { ToastType } from '../types/Toast';
|
||||
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||
import type { Row } from './ConversationList';
|
||||
import { ConversationList, RowType } from './ConversationList';
|
||||
import { DisabledReason } from './conversationList/GroupListItem';
|
||||
import type { GroupListItemConversationType } from './conversationList/GroupListItem';
|
||||
import {
|
||||
DisabledReason,
|
||||
GroupListItem,
|
||||
} from './conversationList/GroupListItem';
|
||||
import { Modal } from './Modal';
|
||||
import { SearchInput } from './SearchInput';
|
||||
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
||||
import { ListView } from './ListView';
|
||||
import { ListTile } from './ListTile';
|
||||
|
||||
type OwnProps = {
|
||||
i18n: LocalizerType;
|
||||
|
@ -47,7 +52,6 @@ export type Props = OwnProps & DispatchProps;
|
|||
|
||||
export function AddUserToAnotherGroupModal({
|
||||
i18n,
|
||||
theme,
|
||||
contact,
|
||||
toggleAddUserToAnotherGroupModal,
|
||||
addMembersToGroup,
|
||||
|
@ -108,7 +112,7 @@ export function AddUserToAnotherGroupModal({
|
|||
);
|
||||
|
||||
const handleGetRow = React.useCallback(
|
||||
(idx: number): Row | undefined => {
|
||||
(idx: number): GroupListItemConversationType => {
|
||||
const convo = filteredConversations[idx];
|
||||
|
||||
// these are always populated in the case of a group
|
||||
|
@ -129,18 +133,36 @@ export function AddUserToAnotherGroupModal({
|
|||
}
|
||||
|
||||
return {
|
||||
type: RowType.SelectSingleGroup,
|
||||
group: {
|
||||
...pick(convo, 'id', 'avatarPath', 'title', 'unblurredAvatarPath'),
|
||||
memberships,
|
||||
membersCount,
|
||||
disabledReason,
|
||||
},
|
||||
...pick(convo, 'id', 'avatarPath', 'title', 'unblurredAvatarPath'),
|
||||
memberships,
|
||||
membersCount,
|
||||
disabledReason,
|
||||
};
|
||||
},
|
||||
[filteredConversations, contact]
|
||||
);
|
||||
|
||||
const renderGroupListItem = useCallback(
|
||||
({ key, index, style }: ListRowProps) => {
|
||||
const group = handleGetRow(index);
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
<GroupListItem
|
||||
i18n={i18n}
|
||||
group={group}
|
||||
onSelectGroup={setSelectedGroupId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[i18n, handleGetRow]
|
||||
);
|
||||
|
||||
const handleCalculateRowHeight = useCallback(
|
||||
() => ListTile.heightCompact,
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!selectedGroup && (
|
||||
|
@ -163,30 +185,30 @@ export function AddUserToAnotherGroupModal({
|
|||
/>
|
||||
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => (
|
||||
<div
|
||||
className="AddUserToAnotherGroupModal__list-wrapper"
|
||||
ref={measureRef}
|
||||
>
|
||||
<ConversationList
|
||||
dimensions={contentRect.bounds}
|
||||
getPreferredBadge={() => undefined}
|
||||
getRow={handleGetRow}
|
||||
i18n={i18n}
|
||||
lookupConversationWithoutUuid={async _ => undefined}
|
||||
onClickArchiveButton={noop}
|
||||
onClickContactCheckbox={noop}
|
||||
onSelectConversation={setSelectedGroupId}
|
||||
rowCount={filteredConversations.length}
|
||||
setIsFetchingUUID={noop}
|
||||
shouldRecomputeRowHeights={false}
|
||||
showChooseGroupMembers={noop}
|
||||
showConversation={noop}
|
||||
showUserNotFoundModal={noop}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => {
|
||||
// Though `width` and `height` are required properties, we want to be
|
||||
// careful in case the caller sends bogus data. Notably, react-measure's
|
||||
// types seem to be inaccurate.
|
||||
const { width = 100, height = 100 } = contentRect.bounds || {};
|
||||
if (!width || !height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="AddUserToAnotherGroupModal__list-wrapper"
|
||||
ref={measureRef}
|
||||
>
|
||||
<ListView
|
||||
width={width}
|
||||
height={height}
|
||||
rowCount={filteredConversations.length}
|
||||
calculateRowHeight={handleCalculateRowHeight}
|
||||
rowRenderer={renderGroupListItem}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -215,6 +215,9 @@ export function ConversationList({
|
|||
case RowType.SearchResultsLoadingFakeHeader:
|
||||
return HEADER_ROW_HEIGHT;
|
||||
case RowType.SelectSingleGroup:
|
||||
case RowType.ContactCheckbox:
|
||||
case RowType.Contact:
|
||||
case RowType.CreateNewGroup:
|
||||
return SELECT_ROW_HEIGHT;
|
||||
default:
|
||||
return NORMAL_ROW_HEIGHT;
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
shouldNeverBeCalled,
|
||||
asyncShouldNeverBeCalled,
|
||||
} from '../util/shouldNeverBeCalled';
|
||||
import { Emojify } from './conversation/Emojify';
|
||||
|
||||
export type DataPropsType = {
|
||||
attachments?: ReadonlyArray<AttachmentType>;
|
||||
|
@ -408,8 +409,13 @@ export function ForwardMessageModal({
|
|||
)}
|
||||
<div className="module-ForwardMessageModal__footer">
|
||||
<div>
|
||||
{Boolean(selectedContacts.length) &&
|
||||
selectedContacts.map(contact => contact.title).join(', ')}
|
||||
{Boolean(selectedContacts.length) && (
|
||||
<Emojify
|
||||
text={selectedContacts
|
||||
.map(contact => contact.title)
|
||||
.join(', ')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{isEditingMessage || !isMessageEditable ? (
|
||||
|
|
|
@ -23,14 +23,18 @@ import { setupI18n } from '../util/setupI18n';
|
|||
import { DurationInSeconds, DAY } from '../util/durations';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { ThemeType } from '../types/Util';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultGroupListItem,
|
||||
} from '../test-both/helpers/getDefaultConversation';
|
||||
import { DialogType } from '../types/Dialogs';
|
||||
import { SocketStatus } from '../types/SocketStatus';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
|
||||
import {
|
||||
makeFakeLookupConversationWithoutUuid,
|
||||
useUuidFetchState,
|
||||
} from '../test-both/helpers/fakeLookupConversationWithoutUuid';
|
||||
import type { GroupListItemConversationType } from './conversationList/GroupListItem';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -62,18 +66,14 @@ const defaultSearchProps = {
|
|||
startSearchCounter: 0,
|
||||
};
|
||||
|
||||
const defaultGroups: Array<ConversationType> = [
|
||||
getDefaultConversation({
|
||||
const defaultGroups: Array<GroupListItemConversationType> = [
|
||||
getDefaultGroupListItem({
|
||||
id: 'biking-group',
|
||||
title: 'Mtn Biking Arizona 🚵☀️⛰',
|
||||
type: 'group',
|
||||
sharedGroupNames: [],
|
||||
}),
|
||||
getDefaultConversation({
|
||||
getDefaultGroupListItem({
|
||||
id: 'dance-group',
|
||||
title: 'Are we dancers? 💃',
|
||||
type: 'group',
|
||||
sharedGroupNames: [],
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
|
@ -605,7 +605,11 @@ export function LeftPane({
|
|||
className={classNames(
|
||||
'module-left-pane',
|
||||
isResizing && 'module-left-pane--is-resizing',
|
||||
`module-left-pane--width-${widthBreakpoint}`
|
||||
`module-left-pane--width-${widthBreakpoint}`,
|
||||
modeSpecificProps.mode === LeftPaneMode.ChooseGroupMembers &&
|
||||
'module-left-pane--mode-choose-group-members',
|
||||
modeSpecificProps.mode === LeftPaneMode.Compose &&
|
||||
'module-left-pane--mode-compose'
|
||||
)}
|
||||
style={{ width }}
|
||||
>
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { getClassNamesFor } from '../util/getClassNamesFor';
|
||||
import { CircleCheckbox } from './CircleCheckbox';
|
||||
|
||||
export type Props = {
|
||||
title: string | JSX.Element;
|
||||
|
@ -23,6 +25,7 @@ export type Props = {
|
|||
variant?: 'item' | 'panelrow';
|
||||
// defaults to div
|
||||
rootElement?: 'div' | 'button';
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
const getClassName = getClassNamesFor('ListTile');
|
||||
|
@ -53,8 +56,17 @@ const getClassName = getClassNamesFor('ListTile');
|
|||
* - panelrow: more horizontal padding, intended for information rows (usually not in
|
||||
* modals) that tend to occupy more horizontal space
|
||||
*/
|
||||
export const ListTile = React.forwardRef<HTMLButtonElement, Props>(
|
||||
function ListTile(
|
||||
export function ListTile(
|
||||
params: Props & React.RefAttributes<HTMLButtonElement>
|
||||
): JSX.Element {
|
||||
// forwardRef makes it impossible to add extra static fields to the function type so
|
||||
// we have to create this inner implementation that can be wrapped with a non-arrow
|
||||
// function. A bit weird, but looks fine at call-site.
|
||||
return <ListTileImpl {...params} />;
|
||||
}
|
||||
|
||||
const ListTileImpl = React.forwardRef<HTMLButtonElement, Props>(
|
||||
function ListTileImpl(
|
||||
{
|
||||
title,
|
||||
subtitle,
|
||||
|
@ -67,6 +79,7 @@ export const ListTile = React.forwardRef<HTMLButtonElement, Props>(
|
|||
disabled = false,
|
||||
variant = 'item',
|
||||
rootElement = 'div',
|
||||
testId,
|
||||
}: Props,
|
||||
ref
|
||||
) {
|
||||
|
@ -81,6 +94,7 @@ export const ListTile = React.forwardRef<HTMLButtonElement, Props>(
|
|||
onClick,
|
||||
'aria-disabled': disabled ? true : undefined,
|
||||
onContextMenu,
|
||||
'data-testid': testId,
|
||||
};
|
||||
|
||||
const contents = (
|
||||
|
@ -112,3 +126,52 @@ export const ListTile = React.forwardRef<HTMLButtonElement, Props>(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
// although these heights are not required for ListTile (which sizes itself based on
|
||||
// content), they are useful as constants for ListView.calculateRowHeight
|
||||
|
||||
/** Effective ListTile height for an avatar (leading) size 36 */
|
||||
ListTile.heightFull = 64;
|
||||
|
||||
/** Effective ListTile height for an avatar (leading) size 48 */
|
||||
ListTile.heightCompact = 52;
|
||||
|
||||
/**
|
||||
* ListTile with a trailing checkbox.
|
||||
*
|
||||
* It also wraps the ListTile with a <label> to get typical click-label-to-check behavior
|
||||
*
|
||||
* Same API except for:
|
||||
* - no "trailing" param since it is populated by the checkbox
|
||||
* - isChecked
|
||||
*/
|
||||
ListTile.checkbox = (
|
||||
props: Omit<Props, 'trailing'> & { isChecked: boolean }
|
||||
) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const htmlId: string = useMemo(() => uuid(), []);
|
||||
|
||||
const { onClick, disabled, isChecked, ...otherProps } = props;
|
||||
return (
|
||||
<label
|
||||
htmlFor={htmlId}
|
||||
// `onClick` is will double-fire if we're enabled. We want it to fire when we're
|
||||
// disabled so we can show any "can't add contact" modals, etc. This won't
|
||||
// work for keyboard users, though, because labels are not tabbable.
|
||||
{...(disabled ? { onClick } : {})}
|
||||
>
|
||||
<ListTile
|
||||
{...otherProps}
|
||||
disabled={disabled}
|
||||
trailing={
|
||||
<CircleCheckbox
|
||||
id={htmlId}
|
||||
checked={isChecked}
|
||||
onChange={onClick}
|
||||
disabled={disabled}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright 2018 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
|
||||
import type { ReactNode, RefObject } from 'react';
|
||||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable local-rules/valid-i18n-keys */
|
||||
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
|
@ -8,13 +10,14 @@ import React, {
|
|||
useRef,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import { noop, omit } from 'lodash';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import type { ListRowProps } from 'react-virtualized';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../../../types/Util';
|
||||
import { getUsernameFromSearch } from '../../../../types/Username';
|
||||
import { strictAssert } from '../../../../util/assert';
|
||||
import { strictAssert, assertDev } from '../../../../util/assert';
|
||||
import { refMerger } from '../../../../util/refMerger';
|
||||
import { useRestoreFocus } from '../../../../hooks/useRestoreFocus';
|
||||
import { missingCaseError } from '../../../../util/missingCaseError';
|
||||
|
@ -36,11 +39,16 @@ import { ModalHost } from '../../../ModalHost';
|
|||
import { ContactPills } from '../../../ContactPills';
|
||||
import { ContactPill } from '../../../ContactPill';
|
||||
import type { Row } from '../../../ConversationList';
|
||||
import { ConversationList, RowType } from '../../../ConversationList';
|
||||
import { ContactCheckboxDisabledReason } from '../../../conversationList/ContactCheckbox';
|
||||
import { RowType } from '../../../ConversationList';
|
||||
import {
|
||||
ContactCheckbox,
|
||||
ContactCheckboxDisabledReason,
|
||||
} from '../../../conversationList/ContactCheckbox';
|
||||
import { Button, ButtonVariant } from '../../../Button';
|
||||
import { SearchInput } from '../../../SearchInput';
|
||||
import { shouldNeverBeCalled } from '../../../../util/shouldNeverBeCalled';
|
||||
import { ListView } from '../../../ListView';
|
||||
import { UsernameCheckbox } from '../../../conversationList/UsernameCheckbox';
|
||||
import { PhoneNumberCheckbox } from '../../../conversationList/PhoneNumberCheckbox';
|
||||
|
||||
export type StatePropsType = {
|
||||
regionCode: string | undefined;
|
||||
|
@ -77,7 +85,6 @@ export function ChooseGroupMembersModal({
|
|||
candidateContacts,
|
||||
confirmAdds,
|
||||
conversationIdsAlreadyInGroup,
|
||||
getPreferredBadge,
|
||||
i18n,
|
||||
maxGroupSize,
|
||||
onClose,
|
||||
|
@ -278,6 +285,94 @@ export function ChooseGroupMembersModal({
|
|||
return undefined;
|
||||
};
|
||||
|
||||
const handleContactClick = (
|
||||
conversationId: string,
|
||||
disabledReason: undefined | ContactCheckboxDisabledReason
|
||||
) => {
|
||||
switch (disabledReason) {
|
||||
case undefined:
|
||||
toggleSelectedContact(conversationId);
|
||||
break;
|
||||
case ContactCheckboxDisabledReason.AlreadyAdded:
|
||||
case ContactCheckboxDisabledReason.MaximumContactsSelected:
|
||||
// These are no-ops.
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(disabledReason);
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ key, index, style }: ListRowProps) => {
|
||||
const row = getRow(index);
|
||||
|
||||
let item;
|
||||
switch (row?.type) {
|
||||
case RowType.Header:
|
||||
item = (
|
||||
<div
|
||||
className="module-conversation-list__item--header"
|
||||
aria-label={i18n(row.i18nKey)}
|
||||
>
|
||||
{i18n(row.i18nKey)}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case RowType.ContactCheckbox:
|
||||
item = (
|
||||
<ContactCheckbox
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
{...row.contact}
|
||||
onClick={handleContactClick}
|
||||
isChecked={row.isChecked}
|
||||
badge={undefined}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case RowType.UsernameCheckbox:
|
||||
item = (
|
||||
<UsernameCheckbox
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
username={row.username}
|
||||
isChecked={row.isChecked}
|
||||
isFetching={row.isFetching}
|
||||
toggleConversationInChooseMembers={conversationId =>
|
||||
handleContactClick(conversationId, undefined)
|
||||
}
|
||||
showUserNotFoundModal={noop}
|
||||
setIsFetchingUUID={setIsFetchingUUID}
|
||||
lookupConversationWithoutUuid={() => Promise.resolve(undefined)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case RowType.PhoneNumberCheckbox:
|
||||
item = (
|
||||
<PhoneNumberCheckbox
|
||||
phoneNumber={row.phoneNumber}
|
||||
lookupConversationWithoutUuid={lookupConversationWithoutUuid}
|
||||
showUserNotFoundModal={showUserNotFoundModal}
|
||||
setIsFetchingUUID={setIsFetchingUUID}
|
||||
toggleConversationInChooseMembers={conversationId =>
|
||||
handleContactClick(conversationId, undefined)
|
||||
}
|
||||
isChecked={row.isChecked}
|
||||
isFetching={row.isFetching}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
{item}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalHost
|
||||
modalName="AddGroupMembersModal.ChooseGroupMembersModal"
|
||||
|
@ -335,6 +430,14 @@ export function ChooseGroupMembersModal({
|
|||
{rowCount ? (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => {
|
||||
// Though `width` and `height` are required properties, we want to be
|
||||
// careful in case the caller sends bogus data. Notably, react-measure's
|
||||
// types seem to be inaccurate.
|
||||
const { width = 100, height = 100 } = contentRect.bounds || {};
|
||||
if (!width || !height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We disable this ESLint rule because we're capturing a bubbled keydown
|
||||
// event. See [this note in the jsx-a11y docs][0].
|
||||
//
|
||||
|
@ -350,43 +453,25 @@ export function ChooseGroupMembersModal({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<ConversationList
|
||||
dimensions={contentRect.bounds}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getRow={getRow}
|
||||
i18n={i18n}
|
||||
onClickArchiveButton={shouldNeverBeCalled}
|
||||
onClickContactCheckbox={(
|
||||
conversationId: string,
|
||||
disabledReason: undefined | ContactCheckboxDisabledReason
|
||||
) => {
|
||||
switch (disabledReason) {
|
||||
case undefined:
|
||||
toggleSelectedContact(conversationId);
|
||||
break;
|
||||
case ContactCheckboxDisabledReason.AlreadyAdded:
|
||||
case ContactCheckboxDisabledReason.MaximumContactsSelected:
|
||||
// These are no-ops.
|
||||
break;
|
||||
<ListView
|
||||
width={width}
|
||||
height={height}
|
||||
rowCount={rowCount}
|
||||
calculateRowHeight={index => {
|
||||
const row = getRow(index);
|
||||
if (!row) {
|
||||
assertDev(false, `Expected a row at index ${index}`);
|
||||
return 52;
|
||||
}
|
||||
|
||||
switch (row.type) {
|
||||
case RowType.Header:
|
||||
return 40;
|
||||
default:
|
||||
throw missingCaseError(disabledReason);
|
||||
return 52;
|
||||
}
|
||||
}}
|
||||
lookupConversationWithoutUuid={
|
||||
lookupConversationWithoutUuid
|
||||
}
|
||||
showUserNotFoundModal={showUserNotFoundModal}
|
||||
setIsFetchingUUID={setIsFetchingUUID}
|
||||
showConversation={shouldNeverBeCalled}
|
||||
onSelectConversation={shouldNeverBeCalled}
|
||||
renderMessageSearchResult={() => {
|
||||
shouldNeverBeCalled();
|
||||
return <div />;
|
||||
}}
|
||||
rowCount={rowCount}
|
||||
shouldRecomputeRowHeights={false}
|
||||
showChooseGroupMembers={shouldNeverBeCalled}
|
||||
theme={theme}
|
||||
rowRenderer={renderItem}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -30,7 +30,7 @@ const MESSAGE_CLASS_NAME = `${CONTENT_CLASS_NAME}__message`;
|
|||
export const MESSAGE_TEXT_CLASS_NAME = `${MESSAGE_CLASS_NAME}__text`;
|
||||
const CHECKBOX_CONTAINER_CLASS_NAME = `${BASE_CLASS_NAME}__checkbox--container`;
|
||||
const CHECKBOX_CLASS_NAME = `${BASE_CLASS_NAME}__checkbox`;
|
||||
const SPINNER_CLASS_NAME = `${BASE_CLASS_NAME}__spinner`;
|
||||
export const SPINNER_CLASS_NAME = `${BASE_CLASS_NAME}__spinner`;
|
||||
|
||||
type PropsType = {
|
||||
checked?: boolean;
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FunctionComponent, ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import type { FunctionComponent } from 'react';
|
||||
|
||||
import {
|
||||
BaseConversationListItem,
|
||||
HEADER_CONTACT_NAME_CLASS_NAME,
|
||||
} from './BaseConversationListItem';
|
||||
import { HEADER_CONTACT_NAME_CLASS_NAME } from './BaseConversationListItem';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import type { BadgeType } from '../../badges/types';
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
import { ContactName } from '../conversation/ContactName';
|
||||
import { About } from '../conversation/About';
|
||||
import { ListTile } from '../ListTile';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
|
||||
export enum ContactCheckboxDisabledReason {
|
||||
// We start the enum at 1 because the default starting value of 0 is falsy.
|
||||
|
@ -61,7 +60,6 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
badge,
|
||||
color,
|
||||
disabledReason,
|
||||
groupId,
|
||||
i18n,
|
||||
id,
|
||||
isChecked,
|
||||
|
@ -74,7 +72,6 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
title,
|
||||
type,
|
||||
unblurredAvatarPath,
|
||||
uuid,
|
||||
}) {
|
||||
const disabled = Boolean(disabledReason);
|
||||
|
||||
|
@ -86,13 +83,11 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
<ContactName module={HEADER_CONTACT_NAME_CLASS_NAME} title={title} />
|
||||
);
|
||||
|
||||
let messageText: ReactNode;
|
||||
let messageText: undefined | string | JSX.Element;
|
||||
if (disabledReason === ContactCheckboxDisabledReason.AlreadyAdded) {
|
||||
messageText = i18n('alreadyAMember');
|
||||
} else if (about) {
|
||||
messageText = <About className="" text={about} />;
|
||||
} else {
|
||||
messageText = null;
|
||||
}
|
||||
|
||||
const onClickItem = () => {
|
||||
|
@ -100,29 +95,33 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
};
|
||||
|
||||
return (
|
||||
<BaseConversationListItem
|
||||
acceptedMessageRequest={acceptedMessageRequest}
|
||||
avatarPath={avatarPath}
|
||||
badge={badge}
|
||||
checked={isChecked}
|
||||
color={color}
|
||||
conversationType={type}
|
||||
<ListTile.checkbox
|
||||
clickable
|
||||
disabled={disabled}
|
||||
groupId={groupId}
|
||||
headerName={headerName}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
isMe={isMe}
|
||||
isSelected={false}
|
||||
messageText={messageText}
|
||||
isChecked={isChecked}
|
||||
leading={
|
||||
<Avatar
|
||||
acceptedMessageRequest={acceptedMessageRequest}
|
||||
avatarPath={avatarPath}
|
||||
color={color}
|
||||
conversationType={type}
|
||||
noteToSelf={Boolean(isMe)}
|
||||
i18n={i18n}
|
||||
isMe={isMe}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
title={title}
|
||||
sharedGroupNames={sharedGroupNames}
|
||||
size={AvatarSize.THIRTY_TWO}
|
||||
unblurredAvatarPath={unblurredAvatarPath}
|
||||
// appease the type checker.
|
||||
{...(badge ? { badge, theme } : { badge: undefined })}
|
||||
/>
|
||||
}
|
||||
title={headerName}
|
||||
subtitle={isMe ? undefined : messageText}
|
||||
subtitleMaxLines={1}
|
||||
onClick={onClickItem}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
sharedGroupNames={sharedGroupNames}
|
||||
theme={theme}
|
||||
title={title}
|
||||
unblurredAvatarPath={unblurredAvatarPath}
|
||||
uuid={uuid}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,15 +4,14 @@
|
|||
import type { FunctionComponent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
BaseConversationListItem,
|
||||
HEADER_CONTACT_NAME_CLASS_NAME,
|
||||
} from './BaseConversationListItem';
|
||||
import { HEADER_CONTACT_NAME_CLASS_NAME } from './BaseConversationListItem';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import type { BadgeType } from '../../badges/types';
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
import { ContactName } from '../conversation/ContactName';
|
||||
import { About } from '../conversation/About';
|
||||
import { ListTile } from '../ListTile';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||
|
||||
export type ContactListItemConversationType = Pick<
|
||||
|
@ -55,7 +54,6 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
|||
avatarPath,
|
||||
badge,
|
||||
color,
|
||||
groupId,
|
||||
i18n,
|
||||
id,
|
||||
isMe,
|
||||
|
@ -82,30 +80,33 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
|||
);
|
||||
|
||||
const messageText =
|
||||
about && !isMe ? <About className="" text={about} /> : null;
|
||||
about && !isMe ? <About className="" text={about} /> : undefined;
|
||||
|
||||
return (
|
||||
<BaseConversationListItem
|
||||
acceptedMessageRequest={acceptedMessageRequest}
|
||||
avatarPath={avatarPath}
|
||||
badge={badge}
|
||||
color={color}
|
||||
conversationType={type}
|
||||
groupId={groupId}
|
||||
headerName={headerName}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
isMe={isMe}
|
||||
isSelected={false}
|
||||
messageText={messageText}
|
||||
<ListTile
|
||||
leading={
|
||||
<Avatar
|
||||
acceptedMessageRequest={acceptedMessageRequest}
|
||||
avatarPath={avatarPath}
|
||||
color={color}
|
||||
conversationType={type}
|
||||
noteToSelf={Boolean(isMe)}
|
||||
i18n={i18n}
|
||||
isMe={isMe}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
title={title}
|
||||
sharedGroupNames={sharedGroupNames}
|
||||
size={AvatarSize.THIRTY_TWO}
|
||||
unblurredAvatarPath={unblurredAvatarPath}
|
||||
// This is here to appease the type checker.
|
||||
{...(badge ? { badge, theme } : { badge: undefined })}
|
||||
/>
|
||||
}
|
||||
title={headerName}
|
||||
subtitle={messageText}
|
||||
subtitleMaxLines={1}
|
||||
onClick={onClick ? () => onClick(id) : undefined}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
sharedGroupNames={sharedGroupNames}
|
||||
theme={theme}
|
||||
title={title}
|
||||
unblurredAvatarPath={unblurredAvatarPath}
|
||||
uuid={uuid}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
import type { FunctionComponent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { BaseConversationListItem } from './BaseConversationListItem';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { ListTile } from '../ListTile';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
|
||||
type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
|
@ -17,17 +18,22 @@ export const CreateNewGroupButton: FunctionComponent<PropsType> = React.memo(
|
|||
const title = i18n('createNewGroupButton');
|
||||
|
||||
return (
|
||||
<BaseConversationListItem
|
||||
acceptedMessageRequest={false}
|
||||
conversationType="group"
|
||||
headerName={title}
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
isSelected={false}
|
||||
onClick={onClick}
|
||||
sharedGroupNames={[]}
|
||||
<ListTile
|
||||
testId="CreateNewGroupButton"
|
||||
leading={
|
||||
<Avatar
|
||||
acceptedMessageRequest={false}
|
||||
conversationType="group"
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
title={title}
|
||||
sharedGroupNames={[]}
|
||||
size={AvatarSize.THIRTY_TWO}
|
||||
badge={undefined}
|
||||
/>
|
||||
}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@ import React from 'react';
|
|||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { AvatarSize } from '../Avatar';
|
||||
import { BaseConversationListItem } from './BaseConversationListItem';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
import { Emojify } from '../conversation/Emojify';
|
||||
import { ListTile } from '../ListTile';
|
||||
|
||||
export enum DisabledReason {
|
||||
AlreadyMember = 'already-member',
|
||||
|
@ -49,21 +50,25 @@ export function GroupListItem({
|
|||
count: group.membersCount,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseConversationListItem
|
||||
disabled={group.disabledReason !== undefined}
|
||||
conversationType="group"
|
||||
title={group.title}
|
||||
avatarSize={AvatarSize.THIRTY_TWO}
|
||||
avatarPath={group.avatarPath}
|
||||
acceptedMessageRequest
|
||||
isMe={false}
|
||||
sharedGroupNames={[]}
|
||||
headerName={group.title}
|
||||
i18n={i18n}
|
||||
isSelected={false}
|
||||
<ListTile
|
||||
leading={
|
||||
<Avatar
|
||||
acceptedMessageRequest
|
||||
avatarPath={group.avatarPath}
|
||||
conversationType="group"
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
title={group.title}
|
||||
sharedGroupNames={[]}
|
||||
size={AvatarSize.THIRTY_TWO}
|
||||
badge={undefined}
|
||||
/>
|
||||
}
|
||||
title={<Emojify text={group.title} />}
|
||||
subtitle={<Emojify text={messageText} />}
|
||||
onClick={() => onSelectGroup(group.id)}
|
||||
messageText={messageText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,15 @@ import React, { useState } from 'react';
|
|||
|
||||
import { ButtonVariant } from '../Button';
|
||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||
import { BaseConversationListItem } from './BaseConversationListItem';
|
||||
import { SPINNER_CLASS_NAME } from './BaseConversationListItem';
|
||||
import type { ParsedE164Type } from '../../util/libphonenumberInstance';
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
import { AvatarColors } from '../../types/Colors';
|
||||
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
|
||||
import { ListTile } from '../ListTile';
|
||||
import { Emojify } from '../conversation/Emojify';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
import { Spinner } from '../Spinner';
|
||||
|
||||
export type PropsDataType = {
|
||||
phoneNumber: ParsedE164Type;
|
||||
|
@ -31,7 +35,6 @@ export const PhoneNumberCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
phoneNumber,
|
||||
isChecked,
|
||||
isFetching,
|
||||
theme,
|
||||
i18n,
|
||||
lookupConversationWithoutUuid,
|
||||
showUserNotFoundModal,
|
||||
|
@ -88,24 +91,46 @@ export const PhoneNumberCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
);
|
||||
}
|
||||
|
||||
const avatar = (
|
||||
<Avatar
|
||||
acceptedMessageRequest={false}
|
||||
color={AvatarColors[0]}
|
||||
conversationType="direct"
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
phoneNumber={phoneNumber.userInput}
|
||||
title={phoneNumber.userInput}
|
||||
sharedGroupNames={[]}
|
||||
size={AvatarSize.THIRTY_TWO}
|
||||
badge={undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
const title = <Emojify text={phoneNumber.userInput} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseConversationListItem
|
||||
acceptedMessageRequest={false}
|
||||
checked={isChecked}
|
||||
color={AvatarColors[0]}
|
||||
conversationType="direct"
|
||||
headerName={phoneNumber.userInput}
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
isSelected={false}
|
||||
onClick={onClickItem}
|
||||
phoneNumber={phoneNumber.userInput}
|
||||
shouldShowSpinner={isFetching}
|
||||
theme={theme}
|
||||
sharedGroupNames={[]}
|
||||
title={phoneNumber.userInput}
|
||||
/>
|
||||
{isFetching ? (
|
||||
<ListTile
|
||||
leading={avatar}
|
||||
title={title}
|
||||
trailing={
|
||||
<Spinner
|
||||
size="20px"
|
||||
svgSize="small"
|
||||
moduleClassName={SPINNER_CLASS_NAME}
|
||||
direction="on-progress-dialog"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ListTile.checkbox
|
||||
isChecked={isChecked}
|
||||
onClick={onClickItem}
|
||||
leading={avatar}
|
||||
title={<Emojify text={phoneNumber.userInput} />}
|
||||
/>
|
||||
)}
|
||||
{modal}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FunctionComponent } from 'react';
|
||||
import React from 'react';
|
||||
import type { FunctionComponent } from 'react';
|
||||
|
||||
import { BaseConversationListItem } from './BaseConversationListItem';
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
import { AvatarColors } from '../../types/Colors';
|
||||
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
|
||||
import { ListTile } from '../ListTile';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { SPINNER_CLASS_NAME } from './BaseConversationListItem';
|
||||
|
||||
export type PropsDataType = {
|
||||
username: string;
|
||||
|
@ -28,7 +31,6 @@ export const UsernameCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
username,
|
||||
isChecked,
|
||||
isFetching,
|
||||
theme,
|
||||
i18n,
|
||||
lookupConversationWithoutUuid,
|
||||
showUserNotFoundModal,
|
||||
|
@ -62,22 +64,41 @@ export const UsernameCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||
|
||||
const title = i18n('at-username', { username });
|
||||
|
||||
return (
|
||||
<BaseConversationListItem
|
||||
const avatar = (
|
||||
<Avatar
|
||||
acceptedMessageRequest={false}
|
||||
checked={isChecked}
|
||||
color={AvatarColors[0]}
|
||||
conversationType="direct"
|
||||
headerName={title}
|
||||
searchResult
|
||||
i18n={i18n}
|
||||
isMe={false}
|
||||
isSelected={false}
|
||||
isUsernameSearchResult
|
||||
onClick={onClickItem}
|
||||
shouldShowSpinner={isFetching}
|
||||
theme={theme}
|
||||
sharedGroupNames={[]}
|
||||
title={title}
|
||||
sharedGroupNames={[]}
|
||||
size={AvatarSize.THIRTY_TWO}
|
||||
badge={undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
return isFetching ? (
|
||||
<ListTile
|
||||
leading={avatar}
|
||||
title={title}
|
||||
trailing={
|
||||
<Spinner
|
||||
size="20px"
|
||||
svgSize="small"
|
||||
moduleClassName={SPINNER_CLASS_NAME}
|
||||
direction="on-progress-dialog"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ListTile.checkbox
|
||||
leading={avatar}
|
||||
title={title}
|
||||
isChecked={isChecked}
|
||||
onClick={onClickItem}
|
||||
clickable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import { LeftPaneHelper } from './LeftPaneHelper';
|
|||
import type { Row } from '../ConversationList';
|
||||
import { RowType } from '../ConversationList';
|
||||
import type { ContactListItemConversationType } from '../conversationList/ContactListItem';
|
||||
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
|
||||
import { SearchInput } from '../SearchInput';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { ParsedE164Type } from '../../util/libphonenumberInstance';
|
||||
|
@ -20,10 +19,11 @@ import {
|
|||
isFetchingByUsername,
|
||||
isFetchingByE164,
|
||||
} from '../../util/uuidFetchState';
|
||||
import type { GroupListItemConversationType } from '../conversationList/GroupListItem';
|
||||
|
||||
export type LeftPaneComposePropsType = {
|
||||
composeContacts: ReadonlyArray<ContactListItemConversationType>;
|
||||
composeGroups: ReadonlyArray<ConversationListItemPropsType>;
|
||||
composeGroups: ReadonlyArray<GroupListItemConversationType>;
|
||||
|
||||
regionCode: string | undefined;
|
||||
searchTerm: string;
|
||||
|
@ -39,7 +39,7 @@ enum TopButton {
|
|||
export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsType> {
|
||||
private readonly composeContacts: ReadonlyArray<ContactListItemConversationType>;
|
||||
|
||||
private readonly composeGroups: ReadonlyArray<ConversationListItemPropsType>;
|
||||
private readonly composeGroups: ReadonlyArray<GroupListItemConversationType>;
|
||||
|
||||
private readonly uuidFetchState: UUIDFetchStateType;
|
||||
|
||||
|
@ -224,8 +224,8 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
const group = this.composeGroups[virtualRowIndex];
|
||||
if (group) {
|
||||
return {
|
||||
type: RowType.Conversation,
|
||||
conversation: group,
|
||||
type: RowType.SelectSingleGroup,
|
||||
group,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -613,8 +613,25 @@ export const getFilteredComposeGroups = createSelector(
|
|||
searchTerm: string,
|
||||
groups: ReadonlyArray<ConversationType>,
|
||||
regionCode: string | undefined
|
||||
): Array<ConversationType> => {
|
||||
return filterAndSortConversationsByRecent(groups, searchTerm, regionCode);
|
||||
): Array<
|
||||
ConversationType & {
|
||||
membersCount: number;
|
||||
disabledReason: undefined;
|
||||
memberships: ReadonlyArray<unknown>;
|
||||
}
|
||||
> => {
|
||||
return filterAndSortConversationsByRecent(
|
||||
groups,
|
||||
searchTerm,
|
||||
regionCode
|
||||
).map(group => ({
|
||||
...group,
|
||||
// we don't disable groups when composing, already filtered
|
||||
disabledReason: undefined,
|
||||
// should always be populated for a group
|
||||
membersCount: group.membersCount ?? 0,
|
||||
memberships: group.memberships ?? [],
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
import casual from 'casual';
|
||||
import { sample } from 'lodash';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { GroupListItemConversationType } from '../../components/conversationList/GroupListItem';
|
||||
import { getRandomColor } from './getRandomColor';
|
||||
import { ConversationColors } from '../../types/Colors';
|
||||
import { StorySendMode } from '../../types/Stories';
|
||||
|
@ -46,6 +47,18 @@ export function getDefaultConversation(
|
|||
};
|
||||
}
|
||||
|
||||
export function getDefaultGroupListItem(
|
||||
overrideProps: Partial<GroupListItemConversationType> = {}
|
||||
): GroupListItemConversationType {
|
||||
return {
|
||||
...getDefaultGroup(),
|
||||
disabledReason: undefined,
|
||||
membersCount: 24,
|
||||
memberships: [],
|
||||
...overrideProps,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultGroup(
|
||||
overrideProps: Partial<ConversationType> = {}
|
||||
): ConversationType {
|
||||
|
|
|
@ -113,9 +113,7 @@ describe('pnp/send gv2 invite', function needsName() {
|
|||
.locator('.module-left-pane__compose-search-form__input')
|
||||
.fill('ACI');
|
||||
|
||||
await leftPane
|
||||
.locator(`[data-testid="${aciContact.toContact().uuid}"]`)
|
||||
.click();
|
||||
await leftPane.locator('.ListTile >> "ACI Contact"').click();
|
||||
|
||||
debug('inviting PNI member');
|
||||
|
||||
|
@ -123,11 +121,7 @@ describe('pnp/send gv2 invite', function needsName() {
|
|||
.locator('.module-left-pane__compose-search-form__input')
|
||||
.fill('PNI');
|
||||
|
||||
await leftPane
|
||||
.locator(
|
||||
`[data-testid="${pniContact.device.getUUIDByKind(UUIDKind.PNI)}"]`
|
||||
)
|
||||
.click();
|
||||
await leftPane.locator('.ListTile >> "PNI Contact"').click();
|
||||
|
||||
await leftPane
|
||||
.locator('.module-left-pane__footer button >> "Next"')
|
||||
|
|
|
@ -5,7 +5,10 @@ import { assert } from 'chai';
|
|||
import * as sinon from 'sinon';
|
||||
import { RowType } from '../../../components/ConversationList';
|
||||
import { FindDirection } from '../../../components/leftPane/LeftPaneHelper';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultGroupListItem,
|
||||
} from '../../../test-both/helpers/getDefaultConversation';
|
||||
|
||||
import { LeftPaneComposeHelper } from '../../../components/leftPane/LeftPaneComposeHelper';
|
||||
|
||||
|
@ -69,7 +72,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.strictEqual(
|
||||
new LeftPaneComposeHelper({
|
||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: '',
|
||||
isUsernamesEnabled: true,
|
||||
|
@ -83,7 +86,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.strictEqual(
|
||||
new LeftPaneComposeHelper({
|
||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'someone',
|
||||
isUsernamesEnabled: true,
|
||||
|
@ -97,7 +100,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.strictEqual(
|
||||
new LeftPaneComposeHelper({
|
||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'someone',
|
||||
isUsernamesEnabled: false,
|
||||
|
@ -133,7 +136,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.strictEqual(
|
||||
new LeftPaneComposeHelper({
|
||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'foobar',
|
||||
isUsernamesEnabled: true,
|
||||
|
@ -240,8 +243,8 @@ describe('LeftPaneComposeHelper', () => {
|
|||
getDefaultConversation(),
|
||||
];
|
||||
const composeGroups = [
|
||||
getDefaultConversation(),
|
||||
getDefaultConversation(),
|
||||
getDefaultGroupListItem(),
|
||||
getDefaultGroupListItem(),
|
||||
];
|
||||
const helper = new LeftPaneComposeHelper({
|
||||
composeContacts,
|
||||
|
@ -255,6 +258,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.deepEqual(helper.getRow(0), {
|
||||
type: RowType.CreateNewGroup,
|
||||
});
|
||||
|
||||
assert.deepEqual(helper.getRow(1), {
|
||||
type: RowType.Header,
|
||||
i18nKey: 'contactsHeader',
|
||||
|
@ -272,12 +276,12 @@ describe('LeftPaneComposeHelper', () => {
|
|||
i18nKey: 'groupsHeader',
|
||||
});
|
||||
assert.deepEqual(helper.getRow(5), {
|
||||
type: RowType.Conversation,
|
||||
conversation: composeGroups[0],
|
||||
type: RowType.SelectSingleGroup,
|
||||
group: composeGroups[0],
|
||||
});
|
||||
assert.deepEqual(helper.getRow(6), {
|
||||
type: RowType.Conversation,
|
||||
conversation: composeGroups[1],
|
||||
type: RowType.SelectSingleGroup,
|
||||
group: composeGroups[1],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -571,7 +575,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.isTrue(
|
||||
helperContacts.shouldRecomputeRowHeights({
|
||||
composeContacts: [],
|
||||
composeGroups: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'foo bar',
|
||||
isUsernamesEnabled: true,
|
||||
|
@ -581,7 +585,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
|
||||
const helperGroups = new LeftPaneComposeHelper({
|
||||
composeContacts: [],
|
||||
composeGroups: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'foo bar',
|
||||
isUsernamesEnabled: true,
|
||||
|
@ -603,7 +607,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
it('should be true if the headers are in different row indices as before', () => {
|
||||
const helperContacts = new LeftPaneComposeHelper({
|
||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'soup',
|
||||
isUsernamesEnabled: true,
|
||||
|
@ -613,7 +617,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
assert.isTrue(
|
||||
helperContacts.shouldRecomputeRowHeights({
|
||||
composeContacts: [getDefaultConversation()],
|
||||
composeGroups: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'soup',
|
||||
isUsernamesEnabled: true,
|
||||
|
|
Loading…
Reference in a new issue