Management / New Admin: Allow to search globally (#1674)
This commit is contained in:
parent
62e716f97a
commit
6142f1c44e
@ -1,7 +1,7 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect, useMemo, useState,
|
FC, memo, useCallback, useEffect, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
import { getDispatch, getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiChat, ApiChatAdminRights, ApiUser } from '../../../api/types';
|
import { ApiChat, ApiChatAdminRights, ApiUser } from '../../../api/types';
|
||||||
import { ManagementScreens } from '../../../types';
|
import { ManagementScreens } from '../../../types';
|
||||||
@ -22,7 +22,7 @@ import InputText from '../../ui/InputText';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
selectedChatMemberId?: string;
|
selectedUserId?: string;
|
||||||
isPromotedByCurrentUser?: boolean;
|
isPromotedByCurrentUser?: boolean;
|
||||||
isNewAdmin?: boolean;
|
isNewAdmin?: boolean;
|
||||||
onScreenSelect: (screen: ManagementScreens) => void;
|
onScreenSelect: (screen: ManagementScreens) => void;
|
||||||
@ -43,7 +43,7 @@ const CUSTOM_TITLE_MAX_LENGTH = 16;
|
|||||||
|
|
||||||
const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
||||||
isNewAdmin,
|
isNewAdmin,
|
||||||
selectedChatMemberId,
|
selectedUserId,
|
||||||
defaultRights,
|
defaultRights,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
chat,
|
chat,
|
||||||
@ -66,28 +66,38 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
|||||||
useHistoryBack(isActive, onClose);
|
useHistoryBack(isActive, onClose);
|
||||||
|
|
||||||
const selectedChatMember = useMemo(() => {
|
const selectedChatMember = useMemo(() => {
|
||||||
const selectedAdminMember = chat.fullInfo?.adminMembers?.find(({ userId }) => userId === selectedChatMemberId);
|
const selectedAdminMember = chat.fullInfo?.adminMembers?.find(({ userId }) => userId === selectedUserId);
|
||||||
|
|
||||||
|
// If `selectedAdminMember` variable is filled with a value, then we have already saved the administrator,
|
||||||
|
// so now we need to return to the list of administrators
|
||||||
|
if (isNewAdmin && (selectedAdminMember || !selectedUserId)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNewAdmin) {
|
if (isNewAdmin) {
|
||||||
// If selectedAdminMember is fullfilled, it means that we are editing an existing admin (after a user
|
const user = getGlobal().users.byId[selectedUserId!];
|
||||||
// has been promoted as admin)
|
|
||||||
return selectedAdminMember
|
return user ? {
|
||||||
? undefined
|
userId: user.id,
|
||||||
: chat.fullInfo?.members?.find(({ userId }) => userId === selectedChatMemberId);
|
adminRights: defaultRights,
|
||||||
|
customTitle: lang('ChannelAdmin'),
|
||||||
|
isOwner: false,
|
||||||
|
promotedByUserId: undefined,
|
||||||
|
} : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedAdminMember;
|
return selectedAdminMember;
|
||||||
}, [chat.fullInfo, isNewAdmin, selectedChatMemberId]);
|
}, [chat.fullInfo?.adminMembers, defaultRights, isNewAdmin, lang, selectedUserId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chat?.fullInfo && selectedChatMemberId && !selectedChatMember) {
|
if (chat?.fullInfo && selectedUserId && !selectedChatMember) {
|
||||||
onScreenSelect(ManagementScreens.ChatAdministrators);
|
onScreenSelect(ManagementScreens.ChatAdministrators);
|
||||||
}
|
}
|
||||||
}, [chat, onScreenSelect, selectedChatMember, selectedChatMemberId]);
|
}, [chat, onScreenSelect, selectedChatMember, selectedUserId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPermissions((isNewAdmin ? defaultRights : selectedChatMember?.adminRights) || {});
|
setPermissions(selectedChatMember?.adminRights || {});
|
||||||
setCustomTitle(((isNewAdmin ? 'admin' : selectedChatMember?.customTitle) || '').substr(0, CUSTOM_TITLE_MAX_LENGTH));
|
setCustomTitle((selectedChatMember?.customTitle || '').substr(0, CUSTOM_TITLE_MAX_LENGTH));
|
||||||
setIsTouched(Boolean(isNewAdmin));
|
setIsTouched(Boolean(isNewAdmin));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [defaultRights, isNewAdmin, selectedChatMember]);
|
}, [defaultRights, isNewAdmin, selectedChatMember]);
|
||||||
@ -107,31 +117,31 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSavePermissions = useCallback(() => {
|
const handleSavePermissions = useCallback(() => {
|
||||||
if (!selectedChatMemberId) {
|
if (!selectedUserId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
updateChatAdmin({
|
updateChatAdmin({
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
userId: selectedChatMemberId,
|
userId: selectedUserId,
|
||||||
adminRights: permissions,
|
adminRights: permissions,
|
||||||
customTitle,
|
customTitle,
|
||||||
});
|
});
|
||||||
}, [selectedChatMemberId, updateChatAdmin, chat.id, permissions, customTitle]);
|
}, [selectedUserId, updateChatAdmin, chat.id, permissions, customTitle]);
|
||||||
|
|
||||||
const handleDismissAdmin = useCallback(() => {
|
const handleDismissAdmin = useCallback(() => {
|
||||||
if (!selectedChatMemberId) {
|
if (!selectedUserId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChatAdmin({
|
updateChatAdmin({
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
userId: selectedChatMemberId,
|
userId: selectedUserId,
|
||||||
adminRights: {},
|
adminRights: {},
|
||||||
});
|
});
|
||||||
closeDismissConfirmationDialog();
|
closeDismissConfirmationDialog();
|
||||||
}, [chat.id, closeDismissConfirmationDialog, selectedChatMemberId, updateChatAdmin]);
|
}, [chat.id, closeDismissConfirmationDialog, selectedUserId, updateChatAdmin]);
|
||||||
|
|
||||||
const getControlIsDisabled = useCallback((key: keyof ApiChatAdminRights) => {
|
const getControlIsDisabled = useCallback((key: keyof ApiChatAdminRights) => {
|
||||||
if (isChatBasicGroup(chat)) {
|
if (isChatBasicGroup(chat)) {
|
||||||
@ -317,7 +327,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentUserId !== selectedChatMemberId && !isFormFullyDisabled && !isNewAdmin && (
|
{currentUserId !== selectedUserId && !isFormFullyDisabled && !isNewAdmin && (
|
||||||
<ListItem icon="delete" ripple destructive onClick={openDismissConfirmationDialog}>
|
<ListItem icon="delete" ripple destructive onClick={openDismissConfirmationDialog}>
|
||||||
{lang('EditAdminRemoveAdmin')}
|
{lang('EditAdminRemoveAdmin')}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@ -1,18 +1,27 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useMemo,
|
FC, memo, useCallback, useMemo, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getDispatch, getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
import { getDispatch, getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiChatMember, ApiUserStatus } from '../../../api/types';
|
import { ApiChatMember, ApiUserStatus } from '../../../api/types';
|
||||||
import { ManagementScreens } from '../../../types';
|
import { ManagementScreens } from '../../../types';
|
||||||
|
|
||||||
|
import { unique } from '../../../util/iteratees';
|
||||||
import { selectChat } from '../../../modules/selectors';
|
import { selectChat } from '../../../modules/selectors';
|
||||||
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
import {
|
||||||
|
sortUserIds, isChatChannel, filterUsersByName, sortChatIds, isUserBot,
|
||||||
|
} from '../../../modules/helpers';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
|
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
|
||||||
|
|
||||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||||
import NothingFound from '../../common/NothingFound';
|
import NothingFound from '../../common/NothingFound';
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import InputText from '../../ui/InputText';
|
||||||
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -28,6 +37,11 @@ type StateProps = {
|
|||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
adminMembers?: ApiChatMember[];
|
adminMembers?: ApiChatMember[];
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
|
localContactIds?: string[];
|
||||||
|
searchQuery?: string;
|
||||||
|
isSearching?: boolean;
|
||||||
|
localUserIds?: string[];
|
||||||
|
globalUserIds?: string[];
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,20 +52,33 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
|||||||
userStatusesById,
|
userStatusesById,
|
||||||
isChannel,
|
isChannel,
|
||||||
isActive,
|
isActive,
|
||||||
|
globalUserIds,
|
||||||
|
localContactIds,
|
||||||
|
localUserIds,
|
||||||
|
isSearching,
|
||||||
|
searchQuery,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
onClose,
|
onClose,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onChatMemberSelect,
|
onChatMemberSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const { openUserInfo } = getDispatch();
|
const { openUserInfo, setUserSearchQuery, loadContactList } = getDispatch();
|
||||||
|
const lang = useLang();
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const adminIds = useMemo(() => {
|
||||||
|
return noAdmins ? adminMembers?.map(({ userId }) => userId) || [] : [];
|
||||||
|
}, [adminMembers, noAdmins]);
|
||||||
|
|
||||||
const memberIds = useMemo(() => {
|
const memberIds = useMemo(() => {
|
||||||
// No need for expensive global updates on users, so we avoid them
|
// No need for expensive global updates on users, so we avoid them
|
||||||
const usersById = getGlobal().users.byId;
|
const usersById = getGlobal().users.byId;
|
||||||
if (!members || !usersById) {
|
if (!members || !usersById) {
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
const adminIds = noAdmins ? adminMembers?.map(({ userId }) => userId) || [] : [];
|
|
||||||
|
|
||||||
const userIds = sortUserIds(
|
const userIds = sortUserIds(
|
||||||
members.map(({ userId }) => userId),
|
members.map(({ userId }) => userId),
|
||||||
@ -62,7 +89,38 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return noAdmins ? userIds.filter((userId) => !adminIds.includes(userId)) : userIds;
|
return noAdmins ? userIds.filter((userId) => !adminIds.includes(userId)) : userIds;
|
||||||
}, [members, noAdmins, adminMembers, userStatusesById, serverTimeOffset]);
|
}, [members, userStatusesById, serverTimeOffset, noAdmins, adminIds]);
|
||||||
|
|
||||||
|
const displayedIds = useMemo(() => {
|
||||||
|
// No need for expensive global updates on users, so we avoid them
|
||||||
|
const usersById = getGlobal().users.byId;
|
||||||
|
const chatsById = getGlobal().chats.byId;
|
||||||
|
const shouldUseSearchResults = !!searchQuery;
|
||||||
|
const listedIds = !shouldUseSearchResults
|
||||||
|
? memberIds
|
||||||
|
: (localContactIds ? filterUsersByName(localContactIds, usersById, searchQuery) : []);
|
||||||
|
|
||||||
|
return sortChatIds(
|
||||||
|
unique([
|
||||||
|
...listedIds,
|
||||||
|
...(shouldUseSearchResults ? localUserIds || [] : []),
|
||||||
|
...(shouldUseSearchResults ? globalUserIds || [] : []),
|
||||||
|
]).filter((contactId) => {
|
||||||
|
const user = usersById[contactId];
|
||||||
|
if (!user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !user.isSelf
|
||||||
|
&& (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
|
||||||
|
&& (!noAdmins || !adminIds.includes(contactId));
|
||||||
|
}),
|
||||||
|
chatsById,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}, [memberIds, localContactIds, searchQuery, localUserIds, globalUserIds, isChannel, noAdmins, adminIds]);
|
||||||
|
|
||||||
|
const [viewportIds, getMore] = useInfiniteScroll(loadContactList, displayedIds, Boolean(searchQuery));
|
||||||
|
|
||||||
const handleMemberClick = useCallback((id: string) => {
|
const handleMemberClick = useCallback((id: string) => {
|
||||||
if (noAdmins) {
|
if (noAdmins) {
|
||||||
@ -73,29 +131,62 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [noAdmins, onChatMemberSelect, onScreenSelect, openUserInfo]);
|
}, [noAdmins, onChatMemberSelect, onScreenSelect, openUserInfo]);
|
||||||
|
|
||||||
|
const handleFilterChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setUserSearchQuery({ query: e.target.value });
|
||||||
|
}, [setUserSearchQuery]);
|
||||||
|
const handleKeyDown = useKeyboardListNavigation(containerRef, isActive, (index) => {
|
||||||
|
if (viewportIds && viewportIds.length > 0) {
|
||||||
|
handleMemberClick(viewportIds[index === -1 ? 0 : index]);
|
||||||
|
}
|
||||||
|
}, '.ListItem-button', true);
|
||||||
|
|
||||||
useHistoryBack(isActive, onClose);
|
useHistoryBack(isActive, onClose);
|
||||||
|
|
||||||
|
function renderSearchField() {
|
||||||
|
return (
|
||||||
|
<div className="Management__filter" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
<InputText
|
||||||
|
ref={inputRef}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleFilterChange}
|
||||||
|
placeholder={lang('Search')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Management">
|
<div className="Management">
|
||||||
|
{noAdmins && renderSearchField()}
|
||||||
<div className="custom-scroll">
|
<div className="custom-scroll">
|
||||||
<div className="section" teactFastList>
|
<div className="section">
|
||||||
{memberIds ? (
|
{viewportIds?.length ? (
|
||||||
memberIds.map((id, i) => (
|
<InfiniteScroll
|
||||||
<ListItem
|
className="picker-list custom-scroll"
|
||||||
key={id}
|
items={displayedIds}
|
||||||
teactOrderKey={i}
|
onLoadMore={getMore}
|
||||||
className="chat-item-clickable scroll-item"
|
noScrollRestore={Boolean(searchQuery)}
|
||||||
onClick={() => handleMemberClick(id)}
|
ref={containerRef}
|
||||||
>
|
onKeyDown={handleKeyDown}
|
||||||
<PrivateChatInfo userId={id} forceShowSelf />
|
>
|
||||||
</ListItem>
|
{viewportIds.map((id) => (
|
||||||
))
|
<ListItem
|
||||||
) : (
|
key={id}
|
||||||
|
className="chat-item-clickable scroll-item"
|
||||||
|
onClick={() => handleMemberClick(id)}
|
||||||
|
>
|
||||||
|
<PrivateChatInfo userId={id} forceShowSelf />
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</InfiniteScroll>
|
||||||
|
) : !isSearching && viewportIds && !viewportIds.length ? (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
teactOrderKey={0}
|
teactOrderKey={0}
|
||||||
key="nothing-found"
|
key="nothing-found"
|
||||||
text={isChannel ? 'No subscribers found' : 'No members found'}
|
text={isChannel ? 'No subscribers found' : 'No members found'}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<Loading />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -110,12 +201,25 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const members = chat?.fullInfo?.members;
|
const members = chat?.fullInfo?.members;
|
||||||
const adminMembers = chat?.fullInfo?.adminMembers;
|
const adminMembers = chat?.fullInfo?.adminMembers;
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
|
const { userIds: localContactIds } = global.contactList || {};
|
||||||
|
|
||||||
|
const {
|
||||||
|
query: searchQuery,
|
||||||
|
fetchingStatus,
|
||||||
|
globalUserIds,
|
||||||
|
localUserIds,
|
||||||
|
} = global.userSearch;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members,
|
members,
|
||||||
adminMembers,
|
adminMembers,
|
||||||
userStatusesById,
|
userStatusesById,
|
||||||
isChannel,
|
isChannel,
|
||||||
|
localContactIds,
|
||||||
|
searchQuery,
|
||||||
|
isSearching: fetchingStatus,
|
||||||
|
globalUserIds,
|
||||||
|
localUserIds,
|
||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -158,6 +158,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__filter {
|
||||||
|
padding: 0 1rem 0.25rem 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--color-borders);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 20rem;
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
height: 2rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ManageGroupMembers {
|
.ManageGroupMembers {
|
||||||
|
|||||||
@ -198,24 +198,13 @@ const Management: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case ManagementScreens.ChatNewAdminRights:
|
||||||
case ManagementScreens.ChatAdminRights:
|
case ManagementScreens.ChatAdminRights:
|
||||||
return (
|
return (
|
||||||
<ManageGroupAdminRights
|
<ManageGroupAdminRights
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
selectedChatMemberId={selectedChatMemberId}
|
isNewAdmin={currentScreen === ManagementScreens.ChatNewAdminRights}
|
||||||
isPromotedByCurrentUser={isPromotedByCurrentUser}
|
selectedUserId={selectedChatMemberId}
|
||||||
onScreenSelect={onScreenSelect}
|
|
||||||
isActive={isActive}
|
|
||||||
onClose={onClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case ManagementScreens.ChatNewAdminRights:
|
|
||||||
return (
|
|
||||||
<ManageGroupAdminRights
|
|
||||||
chatId={chatId}
|
|
||||||
isNewAdmin
|
|
||||||
selectedChatMemberId={selectedChatMemberId}
|
|
||||||
isPromotedByCurrentUser={isPromotedByCurrentUser}
|
isPromotedByCurrentUser={isPromotedByCurrentUser}
|
||||||
onScreenSelect={onScreenSelect}
|
onScreenSelect={onScreenSelect}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user