mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-05-03 14:51:18 +01:00
Rename files
This commit is contained in:
250
ts/components/AddUserToAnotherGroupModal.dom.tsx
Normal file
250
ts/components/AddUserToAnotherGroupModal.dom.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import lodash from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
import type { ListRowProps } from 'react-virtualized';
|
||||
|
||||
import type { ConversationType } from '../state/ducks/conversations.preload.js';
|
||||
import type { LocalizerType } from '../types/Util.std.js';
|
||||
import { ToastType } from '../types/Toast.dom.js';
|
||||
import { filterAndSortConversations } from '../util/filterAndSortConversations.std.js';
|
||||
import { ConfirmationDialog } from './ConfirmationDialog.dom.js';
|
||||
import type { GroupListItemConversationType } from './conversationList/GroupListItem.dom.js';
|
||||
import {
|
||||
DisabledReason,
|
||||
GroupListItem,
|
||||
} from './conversationList/GroupListItem.dom.js';
|
||||
import { Modal } from './Modal.dom.js';
|
||||
import { SearchInput } from './SearchInput.dom.js';
|
||||
import { useRestoreFocus } from '../hooks/useRestoreFocus.dom.js';
|
||||
import { ListView } from './ListView.dom.js';
|
||||
import { ListTile } from './ListTile.dom.js';
|
||||
import type { ShowToastAction } from '../state/ducks/toast.preload.js';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver.dom.js';
|
||||
|
||||
const { pick } = lodash;
|
||||
|
||||
type OwnProps = {
|
||||
i18n: LocalizerType;
|
||||
contact: Pick<ConversationType, 'id' | 'title' | 'serviceId' | 'pni'>;
|
||||
candidateConversations: ReadonlyArray<ConversationType>;
|
||||
regionCode: string | undefined;
|
||||
};
|
||||
|
||||
type DispatchProps = {
|
||||
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
||||
addMembersToGroup: (
|
||||
conversationId: string,
|
||||
contactIds: Array<string>,
|
||||
opts: {
|
||||
onSuccess?: () => unknown;
|
||||
onFailure?: () => unknown;
|
||||
}
|
||||
) => void;
|
||||
showToast: ShowToastAction;
|
||||
};
|
||||
|
||||
export type Props = OwnProps & DispatchProps;
|
||||
|
||||
export function AddUserToAnotherGroupModal({
|
||||
i18n,
|
||||
contact,
|
||||
toggleAddUserToAnotherGroupModal,
|
||||
addMembersToGroup,
|
||||
showToast,
|
||||
candidateConversations,
|
||||
regionCode,
|
||||
}: Props): JSX.Element | null {
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [filteredConversations, setFilteredConversations] = React.useState(
|
||||
filterAndSortConversations(candidateConversations, '', undefined)
|
||||
);
|
||||
|
||||
const [selectedGroupId, setSelectedGroupId] = React.useState<
|
||||
undefined | string
|
||||
>(undefined);
|
||||
|
||||
const groupLookup: Map<string, ConversationType> = React.useMemo(() => {
|
||||
const map = new Map();
|
||||
candidateConversations.forEach(conversation => {
|
||||
map.set(conversation.id, conversation);
|
||||
});
|
||||
return map;
|
||||
}, [candidateConversations]);
|
||||
|
||||
const [inputRef] = useRestoreFocus();
|
||||
|
||||
const normalizedSearchTerm = searchTerm.trim();
|
||||
|
||||
React.useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setFilteredConversations(
|
||||
filterAndSortConversations(
|
||||
candidateConversations,
|
||||
normalizedSearchTerm,
|
||||
regionCode
|
||||
)
|
||||
);
|
||||
}, 200);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [
|
||||
candidateConversations,
|
||||
normalizedSearchTerm,
|
||||
setFilteredConversations,
|
||||
regionCode,
|
||||
]);
|
||||
|
||||
const selectedGroup = selectedGroupId
|
||||
? groupLookup.get(selectedGroupId)
|
||||
: undefined;
|
||||
|
||||
const handleSearchInputChange = React.useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchTerm(event.target.value);
|
||||
},
|
||||
[setSearchTerm]
|
||||
);
|
||||
|
||||
const handleGetRow = React.useCallback(
|
||||
(idx: number): GroupListItemConversationType => {
|
||||
const convo = filteredConversations[idx];
|
||||
|
||||
// these are always populated in the case of a group
|
||||
const memberships = convo.memberships ?? [];
|
||||
const pendingApprovalMemberships = convo.pendingApprovalMemberships ?? [];
|
||||
const pendingMemberships = convo.pendingMemberships ?? [];
|
||||
const membersCount = convo.membersCount ?? 0;
|
||||
|
||||
let disabledReason;
|
||||
|
||||
if (memberships.some(c => c.aci === contact.serviceId)) {
|
||||
disabledReason = DisabledReason.AlreadyMember;
|
||||
} else if (
|
||||
pendingApprovalMemberships.some(c => c.aci === contact.serviceId) ||
|
||||
pendingMemberships.some(c => c.serviceId === contact.serviceId) ||
|
||||
pendingMemberships.some(c => c.serviceId === contact.pni)
|
||||
) {
|
||||
disabledReason = DisabledReason.Pending;
|
||||
}
|
||||
|
||||
return {
|
||||
...pick(convo, 'id', 'avatarUrl', 'title', 'hasAvatar', 'color'),
|
||||
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 && (
|
||||
<Modal
|
||||
modalName="AddUserToAnotherGroupModal"
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
onClose={toggleAddUserToAnotherGroupModal}
|
||||
title={i18n('icu:AddUserToAnotherGroupModal__title')}
|
||||
moduleClassName="AddUserToAnotherGroupModal"
|
||||
padded={false}
|
||||
>
|
||||
<div className="AddUserToAnotherGroupModal__main-body">
|
||||
<SearchInput
|
||||
i18n={i18n}
|
||||
placeholder={i18n(
|
||||
'icu:AddUserToAnotherGroupModal__search-placeholder'
|
||||
)}
|
||||
onChange={handleSearchInputChange}
|
||||
ref={inputRef}
|
||||
value={searchTerm}
|
||||
/>
|
||||
<SizeObserver>
|
||||
{(ref, size) => {
|
||||
return (
|
||||
<div
|
||||
className="AddUserToAnotherGroupModal__list-wrapper"
|
||||
ref={ref}
|
||||
>
|
||||
{size != null && (
|
||||
<ListView
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
rowCount={filteredConversations.length}
|
||||
calculateRowHeight={handleCalculateRowHeight}
|
||||
rowRenderer={renderGroupListItem}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</SizeObserver>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{selectedGroupId && selectedGroup && (
|
||||
<ConfirmationDialog
|
||||
dialogName="AddUserToAnotherGroupModal__confirm"
|
||||
title={i18n('icu:AddUserToAnotherGroupModal__confirm-title')}
|
||||
i18n={i18n}
|
||||
onClose={() => setSelectedGroupId(undefined)}
|
||||
actions={[
|
||||
{
|
||||
text: i18n('icu:AddUserToAnotherGroupModal__confirm-add'),
|
||||
style: 'affirmative',
|
||||
action: () => {
|
||||
showToast({
|
||||
toastType: ToastType.AddingUserToGroup,
|
||||
parameters: {
|
||||
contact: contact.title,
|
||||
},
|
||||
});
|
||||
addMembersToGroup(selectedGroupId, [contact.id], {
|
||||
onSuccess: () =>
|
||||
showToast({
|
||||
toastType: ToastType.UserAddedToGroup,
|
||||
parameters: {
|
||||
contact: contact.title,
|
||||
group: selectedGroup.title,
|
||||
},
|
||||
}),
|
||||
});
|
||||
toggleAddUserToAnotherGroupModal(undefined);
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
{i18n('icu:AddUserToAnotherGroupModal__confirm-message', {
|
||||
contact: contact.title,
|
||||
group: selectedGroup.title,
|
||||
})}
|
||||
</ConfirmationDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user