283 lines
8.3 KiB
TypeScript
283 lines
8.3 KiB
TypeScript
import type { FC } from '../../lib/teact/teact';
|
|
import React, {
|
|
memo,
|
|
useMemo,
|
|
useState,
|
|
} from '../../lib/teact/teact';
|
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
|
|
|
import type { ApiChat, ApiChatMember, ApiUserStatus } from '../../api/types';
|
|
|
|
import {
|
|
filterChatsByName,
|
|
filterUsersByName, isChatChannel, isChatPublic, isChatSuperGroup, isUserBot, sortUserIds,
|
|
} from '../../global/helpers';
|
|
import { selectChat, selectChatFullInfo } from '../../global/selectors';
|
|
import buildClassName from '../../util/buildClassName';
|
|
import { unique } from '../../util/iteratees';
|
|
import sortChatIds from '../common/helpers/sortChatIds';
|
|
|
|
import useFlag from '../../hooks/useFlag';
|
|
import useLang from '../../hooks/useLang';
|
|
import useLastCallback from '../../hooks/useLastCallback';
|
|
|
|
import Icon from '../common/icons/Icon';
|
|
import Picker from '../common/Picker';
|
|
import Button from '../ui/Button';
|
|
import ConfirmDialog from '../ui/ConfirmDialog';
|
|
import Modal from '../ui/Modal';
|
|
|
|
import styles from './AppendEntityPicker.module.scss';
|
|
|
|
export type OwnProps = {
|
|
isOpen?: boolean;
|
|
onClose: () => void;
|
|
chatId?: string;
|
|
entityType: 'members' | 'channels' | undefined;
|
|
onSubmit: (value: string[]) => void;
|
|
selectionLimit: number;
|
|
};
|
|
|
|
interface StateProps {
|
|
chatId?: string;
|
|
members?: ApiChatMember[];
|
|
adminMembersById?: Record<string, ApiChatMember>;
|
|
userStatusesById: Record<string, ApiUserStatus>;
|
|
channelList?: (ApiChat | undefined)[] | undefined;
|
|
isChannel?: boolean;
|
|
isSuperGroup?: boolean;
|
|
currentUserId?: string | undefined;
|
|
}
|
|
|
|
const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
|
|
chatId,
|
|
isOpen,
|
|
onClose,
|
|
members,
|
|
adminMembersById,
|
|
userStatusesById,
|
|
entityType,
|
|
isChannel,
|
|
isSuperGroup,
|
|
onSubmit,
|
|
currentUserId,
|
|
selectionLimit,
|
|
}) => {
|
|
const { showNotification } = getActions();
|
|
|
|
const lang = useLang();
|
|
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
|
|
|
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
const [pendingChannelId, setPendingChannelId] = useState<string | undefined>(undefined);
|
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
|
|
|
const channelsIds = useMemo(() => {
|
|
const chatsById = getGlobal().chats.byId;
|
|
const activeChatIds = getGlobal().chats.listIds.active;
|
|
|
|
return activeChatIds!.map((id) => chatsById[id])
|
|
.filter((chat) => chat && (isChatChannel(chat)
|
|
|| isChatSuperGroup(chat)) && chat.id !== chatId)
|
|
.map((chat) => chat!.id);
|
|
}, [chatId]);
|
|
|
|
const adminIds = useMemo(() => {
|
|
return adminMembersById && Object.keys(adminMembersById);
|
|
}, [adminMembersById]);
|
|
|
|
const memberIds = useMemo(() => {
|
|
const usersById = getGlobal().users.byId;
|
|
if (!members || !usersById) {
|
|
return [];
|
|
}
|
|
|
|
const userIds = sortUserIds(
|
|
members.map(({ userId }) => userId),
|
|
usersById,
|
|
userStatusesById,
|
|
);
|
|
|
|
return adminIds ? userIds.filter((userId) => userId !== currentUserId) : userIds;
|
|
}, [adminIds, currentUserId, members, userStatusesById]);
|
|
|
|
const displayedMembersIds = useMemo(() => {
|
|
const usersById = getGlobal().users.byId;
|
|
const filteredContactIds = memberIds ? filterUsersByName(memberIds, usersById, searchQuery) : [];
|
|
|
|
return sortChatIds(unique(filteredContactIds).filter((userId) => {
|
|
const user = usersById[userId];
|
|
if (!user) {
|
|
return true;
|
|
}
|
|
|
|
return !isUserBot(user);
|
|
}));
|
|
}, [memberIds, searchQuery]);
|
|
|
|
const displayedChannelIds = useMemo(() => {
|
|
const chatsById = getGlobal().chats.byId;
|
|
const foundChannelIds = channelsIds ? filterChatsByName(lang, channelsIds, chatsById, searchQuery) : [];
|
|
|
|
return sortChatIds(unique(foundChannelIds).filter((contactId) => {
|
|
const chat = chatsById[contactId];
|
|
if (!chat) {
|
|
return true;
|
|
}
|
|
|
|
return isChannel || isSuperGroup;
|
|
}),
|
|
false,
|
|
selectedIds);
|
|
}, [channelsIds, lang, searchQuery, selectedIds, isSuperGroup, isChannel]);
|
|
|
|
const handleCloseButtonClick = useLastCallback(() => {
|
|
onSubmit([]);
|
|
onClose();
|
|
});
|
|
|
|
const handleSendIdList = useLastCallback(() => {
|
|
onSubmit(selectedIds);
|
|
onClose();
|
|
});
|
|
|
|
const confirmPrivateLinkChannelSelection = useLastCallback(() => {
|
|
if (pendingChannelId) {
|
|
setSelectedIds((prevIds) => unique([...prevIds, pendingChannelId]));
|
|
}
|
|
closeConfirmModal();
|
|
});
|
|
|
|
const handleSelectedMemberIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
|
if (newSelectedIds.length > selectionLimit) {
|
|
showNotification({
|
|
message: lang('BoostingSelectUpToWarningUsers', selectionLimit),
|
|
});
|
|
return;
|
|
}
|
|
setSelectedIds(newSelectedIds);
|
|
});
|
|
|
|
const handleSelectedChannelIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
|
const chatsById = getGlobal().chats.byId;
|
|
const newlyAddedIds = newSelectedIds.filter((id) => !selectedIds.includes(id));
|
|
const privateLinkChannelId = newlyAddedIds.find((id) => {
|
|
const chat = chatsById[id];
|
|
return chat && !isChatPublic(chat);
|
|
});
|
|
|
|
if (selectedIds?.length >= selectionLimit) {
|
|
showNotification({
|
|
message: lang('BoostingSelectUpToWarningChannelsPlural', selectionLimit),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (privateLinkChannelId) {
|
|
setPendingChannelId(privateLinkChannelId);
|
|
openConfirmModal();
|
|
} else {
|
|
setSelectedIds(newSelectedIds);
|
|
}
|
|
});
|
|
|
|
const handleClose = useLastCallback(() => {
|
|
onSubmit([]);
|
|
onClose();
|
|
});
|
|
|
|
function renderSearchField() {
|
|
return (
|
|
<div className={styles.filter} dir={lang.isRtl ? 'rtl' : undefined}>
|
|
<Button
|
|
round
|
|
size="smaller"
|
|
color="translucent"
|
|
// eslint-disable-next-line react/jsx-no-bind
|
|
onClick={handleCloseButtonClick}
|
|
ariaLabel={lang('Close')}
|
|
>
|
|
<Icon name="close" />
|
|
</Button>
|
|
<h3 className={styles.title}>{lang(entityType === 'channels'
|
|
? 'RequestPeer.ChooseChannelTitle' : 'BoostingAwardSpecificUsers')}
|
|
</h3>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
className={styles.root}
|
|
isOpen={isOpen}
|
|
onClose={handleClose}
|
|
onEnter={handleSendIdList}
|
|
>
|
|
<div className={styles.main}>
|
|
{renderSearchField()}
|
|
<div className={buildClassName(styles.main, 'custom-scroll')}>
|
|
<Picker
|
|
className={styles.picker}
|
|
itemIds={entityType === 'members' ? displayedMembersIds : displayedChannelIds}
|
|
selectedIds={selectedIds}
|
|
filterValue={searchQuery}
|
|
filterPlaceholder={lang('Search')}
|
|
searchInputId={`${entityType}-picker-search`}
|
|
onSelectedIdsChange={entityType === 'channels'
|
|
? handleSelectedChannelIdsChange : handleSelectedMemberIdsChange}
|
|
onFilterChange={setSearchQuery}
|
|
isSearchable
|
|
/>
|
|
</div>
|
|
<div className={styles.buttons}>
|
|
<Button size="smaller" onClick={handleSendIdList}>
|
|
{lang('Save')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<ConfirmDialog
|
|
title={lang('BoostingGiveawayPrivateChannel')}
|
|
text={lang('BoostingGiveawayPrivateChannelWarning')}
|
|
confirmLabel={lang('Add')}
|
|
isOpen={isConfirmModalOpen}
|
|
onClose={closeConfirmModal}
|
|
confirmHandler={confirmPrivateLinkChannelSelection}
|
|
/>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default memo(withGlobal<OwnProps>((global, { chatId, entityType }): StateProps => {
|
|
const { statusesById: userStatusesById } = global.users;
|
|
let isChannel;
|
|
let isSuperGroup;
|
|
let members: ApiChatMember[] | undefined;
|
|
let adminMembersById: Record<string, ApiChatMember> | undefined;
|
|
let currentUserId: string | undefined;
|
|
|
|
if (entityType === 'members') {
|
|
currentUserId = global.currentUserId;
|
|
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
|
|
if (chatFullInfo) {
|
|
members = chatFullInfo.members;
|
|
adminMembersById = chatFullInfo.adminMembersById;
|
|
}
|
|
} if (entityType === 'channels') {
|
|
const chat = chatId ? selectChat(global, chatId) : undefined;
|
|
if (chat) {
|
|
isChannel = isChatChannel(chat);
|
|
isSuperGroup = isChatSuperGroup(chat);
|
|
}
|
|
}
|
|
|
|
return {
|
|
chatId,
|
|
members,
|
|
adminMembersById,
|
|
userStatusesById,
|
|
isChannel,
|
|
isSuperGroup,
|
|
currentUserId,
|
|
};
|
|
})(AppendEntityPickerModal));
|