Management / Join Requests: Add animated stickers, various fixes (#1679)

This commit is contained in:
Alexander Zinchuk 2022-02-02 22:49:18 +01:00
parent 71ef93e993
commit bb0d846838
20 changed files with 255 additions and 60 deletions

View File

@ -46,6 +46,7 @@ type EmojiWithSkins = Record<number, Emoji>;
type AllEmojis = Record<string, Emoji | EmojiWithSkins>;
declare module '*.png';
declare module '*.tgs';
declare module 'pako/dist/pako_inflate' {
function inflate(...args: any[]): string;

Binary file not shown.

Binary file not shown.

View File

@ -2,43 +2,31 @@ import { ApiMediaFormat } from '../../../api/types';
import * as mediaLoader from '../../../util/mediaLoader';
// @ts-ignore
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
// @ts-ignore
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
// @ts-ignore
import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.tgs';
// @ts-ignore
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
// @ts-ignore
import FoldersAll from '../../../assets/tgs/settings/FoldersAll.tgs';
// @ts-ignore
import FoldersNew from '../../../assets/tgs/settings/FoldersNew.tgs';
// @ts-ignore
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
// @ts-ignore
import CameraFlip from '../../../assets/tgs/calls/CameraFlip.tgs';
// @ts-ignore
import HandFilled from '../../../assets/tgs/calls/HandFilled.tgs';
// @ts-ignore
import HandOutline from '../../../assets/tgs/calls/HandOutline.tgs';
// @ts-ignore
import Speaker from '../../../assets/tgs/calls/Speaker.tgs';
// @ts-ignore
import VoiceAllowTalk from '../../../assets/tgs/calls/VoiceAllowTalk.tgs';
// @ts-ignore
import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
// @ts-ignore
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
// @ts-ignore
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
// @ts-ignore
import Peach from '../../../assets/tgs/animatedEmojis/Peach.tgs';
// @ts-ignore
import Eggplant from '../../../assets/tgs/animatedEmojis/Eggplant.tgs';
// @ts-ignore
import Cumshot from '../../../assets/tgs/animatedEmojis/Cumshot.tgs';
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
import Invite from '../../../assets/tgs/invites/Invite.tgs';
export const ANIMATED_STICKERS_PATHS = {
MonkeyIdle,
MonkeyTracking,
@ -58,6 +46,8 @@ export const ANIMATED_STICKERS_PATHS = {
Peach,
Eggplant,
Cumshot,
JoinRequest,
Invite,
};
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {

View File

@ -22,7 +22,12 @@ type StateProps = {
};
const Dialogs: FC<StateProps> = ({ dialogs }) => {
const { dismissDialog, acceptInviteConfirmation, sendMessage } = getDispatch();
const {
dismissDialog,
acceptInviteConfirmation,
sendMessage,
showNotification,
} = getDispatch();
const [isModalOpen, openModal, closeModal] = useFlag();
const lang = useLang();
@ -60,6 +65,9 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
acceptInviteConfirmation({
hash,
});
showNotification({
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
});
closeModal();
};
@ -79,8 +87,8 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
header={renderInviteHeader(title, photo)}
onCloseAnimationEnd={dismissDialog}
>
{about && <p className="modal-about">{renderText(about)}</p>}
{participantsCount !== undefined && <p>{participantsText}</p>}
{participantsCount !== undefined && <p className="modal-help">{participantsText}</p>}
{about && <p className="modal-about">{renderText(about, ['br'])}</p>}
{isRequestNeeded && (
<p className="modal-help">
{isChannel

View File

@ -22,12 +22,15 @@ import {
} from '../../modules/helpers';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import { getDayStartAt } from '../../util/dateFormat';
import SearchInput from '../ui/SearchInput';
import Button from '../ui/Button';
import Transition from '../ui/Transition';
import ConfirmDialog from '../ui/ConfirmDialog';
import './RightHeader.scss';
import { getDayStartAt } from '../../util/dateFormat';
type OwnProps = {
chatId?: string;
@ -131,6 +134,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line no-null/no-null
const backButtonRef = useRef<HTMLDivElement>(null);
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
const handleEditInviteClick = useCallback(() => {
setEditingExportedInvite({ chatId: chatId!, invite: currentInviteInfo! });
@ -140,7 +144,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
const handleDeleteInviteClick = useCallback(() => {
deleteExportedChatInvite({ chatId: chatId!, link: currentInviteInfo!.link });
onScreenSelect(ManagementScreens.Invites);
}, [chatId, currentInviteInfo, deleteExportedChatInvite, onScreenSelect]);
closeDeleteDialog();
}, [chatId, closeDeleteDialog, currentInviteInfo, deleteExportedChatInvite, onScreenSelect]);
const handleMessageSearchQueryChange = useCallback((query: string) => {
setLocalTextSearchQuery({ query });
@ -305,15 +310,26 @@ const RightHeader: FC<OwnProps & StateProps> = ({
</Button>
)}
{currentInviteInfo && currentInviteInfo.isRevoked && (
<Button
round
color="danger"
size="smaller"
ariaLabel={lang('Delete')}
onClick={handleDeleteInviteClick}
>
<i className="icon-delete" />
</Button>
<>
<Button
round
color="danger"
size="smaller"
ariaLabel={lang('Delete')}
onClick={openDeleteDialog}
>
<i className="icon-delete" />
</Button>
<ConfirmDialog
isOpen={isDeleteDialogOpen}
onClose={closeDeleteDialog}
title={lang('DeleteLink')}
text={lang('DeleteLinkHelp')}
confirmIsDestructive
confirmLabel={lang('Delete')}
confirmHandler={handleDeleteInviteClick}
/>
</>
)}
</section>
</>

View File

@ -21,17 +21,29 @@
&__user {
display: flex;
flex-grow: 1;
min-width: 0;
overflow: hidden;
}
&__user-info {
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
margin-left: 1rem;
}
&__user-subtitle {
color: var(--color-text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__user-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__date {

View File

@ -50,7 +50,7 @@ const JoinRequest: FC<OwnProps & StateProps> = ({
? formatTime(lang, fixedDate) : formatHumanDate(lang, fixedDate, true, false, true);
const handleUserClick = () => {
openUserInfo({ userId });
openUserInfo({ id: userId });
};
const handleAcceptRequest = useCallback(() => {

View File

@ -111,8 +111,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
return true;
}
return !user.isSelf
&& (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
return (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
&& (!noAdmins || !adminIds.includes(contactId));
}),
chatsById,

View File

@ -59,6 +59,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
const [selectedExpireOption, setSelectedExpireOption] = useState('unlimited');
const [customUsageLimit, setCustomUsageLimit] = useState<number | undefined>(10);
const [selectedUsageOption, setSelectedUsageOption] = useState('0');
const [isSubmitBlocked, setIsSubmitBlocked] = useState(false);
useHistoryBack(isActive, onClose);
@ -81,8 +82,9 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
setCustomUsageLimit(usageLimit);
}
if (expireDate) {
const minSafeDate = getServerTime(serverTimeOffset) + DEFAULT_CUSTOM_EXPIRE_DATE;
setSelectedExpireOption('custom');
setCustomExpireDate(expireDate * 1000);
setCustomExpireDate(Math.max(expireDate, minSafeDate) * 1000);
}
if (editingIsRequestNeeded) {
setIsRequestNeeded(true);
@ -108,6 +110,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
}, [closeCalendar]);
const handleSaveClick = useCallback(() => {
setIsSubmitBlocked(true);
const usageLimit = selectedUsageOption === 'custom' ? customUsageLimit : selectedUsageOption;
let expireDate;
switch (selectedExpireOption) {
@ -240,6 +243,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
<FloatingActionButton
isShown
onClick={handleSaveClick}
disabled={isSubmitBlocked}
ariaLabel={editingInvite ? lang('SaveLink') : lang('CreateLink')}
>
<i className="icon-check" />

View File

@ -10,10 +10,13 @@ import useLang from '../../../hooks/useLang';
import { copyTextToClipboard } from '../../../util/clipboard';
import { getServerTime } from '../../../util/serverTime';
import { formatFullDate, formatMediaDateTime, formatTime } from '../../../util/dateFormat';
import { isChatChannel } from '../../../modules/helpers';
import { selectChat } from '../../../modules/selectors';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import ListItem from '../../ui/ListItem';
import Button from '../../ui/Button';
import Spinner from '../../ui/Spinner';
type OwnProps = {
chatId: string;
@ -24,7 +27,9 @@ type OwnProps = {
type StateProps = {
invite?: ApiExportedInvite;
importers?: ApiChatInviteImporter[];
requesters?: ApiChatInviteImporter[];
admin?: ApiUser;
isChannel?: boolean;
serverTimeOffset: number;
};
@ -32,6 +37,8 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
chatId,
invite,
importers,
requesters,
isChannel,
isActive,
serverTimeOffset,
onClose,
@ -39,6 +46,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
const {
showNotification,
loadChatInviteImporters,
loadChatInviteRequesters,
openUserInfo,
} = getDispatch();
@ -50,8 +58,11 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
const isExpired = ((invite?.expireDate || 0) - getServerTime(serverTimeOffset)) < 0;
useEffect(() => {
if (link) loadChatInviteImporters({ chatId, link });
}, [chatId, link, loadChatInviteImporters]);
if (link) {
loadChatInviteImporters({ chatId, link });
loadChatInviteRequesters({ chatId, link });
}
}, [chatId, link, loadChatInviteImporters, loadChatInviteRequesters]);
const handleCopyClicked = useCallback(() => {
copyTextToClipboard(invite!.link);
@ -63,8 +74,8 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
useHistoryBack(isActive, onClose);
const renderImporters = () => {
if (invite?.isRevoked) return undefined;
if (!importers) return <p className="text-muted">{lang('Loading')}</p>;
if (!importers?.length && requesters?.length) return undefined;
if (!importers) return <Spinner />;
return (
<div className="section">
<p>{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</p>
@ -89,6 +100,31 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
);
};
const renderRequesters = () => {
if (invite?.isRevoked) return undefined;
if (!requesters && importers) return <Spinner />;
if (!requesters?.length) return undefined;
return (
<div className="section">
<p>{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</p>
<p className="text-muted">
{requesters.map((requester) => (
<ListItem
className="chat-item-clickable scroll-item small-icon"
onClick={() => openUserInfo({ id: requester.userId })}
>
<PrivateChatInfo
userId={requester.userId}
status={formatMediaDateTime(lang, requester.date * 1000, true)}
forceShowSelf
/>
</ListItem>
))}
</p>
</div>
);
};
return (
<div className="Management ManageInviteInfo">
<div className="custom-scroll">
@ -98,7 +134,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
{invite && (
<>
<div className="section">
<h3>{invite.title || invite.link}</h3>
<h3 className="link-title">{invite.title || invite.link}</h3>
<input
className="form-control"
value={invite.link}
@ -130,6 +166,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
</div>
)}
{renderImporters()}
{renderRequesters()}
</>
)}
</div>
@ -140,12 +177,15 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { chatId }): StateProps => {
const { inviteInfo } = global.management.byChatId[chatId];
const invite = inviteInfo?.invite;
const importers = inviteInfo?.importers;
const { invite, importers, requesters } = inviteInfo || {};
const chat = selectChat(global, chatId);
const isChannel = chat && isChatChannel(chat);
return {
invite,
importers,
requesters,
isChannel,
serverTimeOffset: global.serverTimeOffset,
};
},

View File

@ -1,11 +1,13 @@
import React, {
FC, memo, useCallback, useMemo, useState,
FC, memo, useCallback, useEffect, useMemo, useState,
} from '../../../lib/teact/teact';
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
import { ApiChat, ApiExportedInvite } from '../../../api/types';
import { ManagementScreens } from '../../../types';
import { STICKER_SIZE_INVITES } from '../../../config';
import getAnimationData from '../../common/helpers/animatedAssets';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang';
import { formatCountdown, MILLISECONDS_IN_DAY } from '../../../util/dateFormat';
@ -16,6 +18,7 @@ import { copyTextToClipboard } from '../../../util/clipboard';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import { getServerTime } from '../../../util/serverTime';
import useFlag from '../../../hooks/useFlag';
import { isChatChannel } from '../../../modules/helpers';
import ListItem from '../../ui/ListItem';
import NothingFound from '../../common/NothingFound';
@ -23,6 +26,7 @@ import Button from '../../ui/Button';
import DropdownMenu from '../../ui/DropdownMenu';
import MenuItem from '../../ui/MenuItem';
import ConfirmDialog from '../../ui/ConfirmDialog';
import AnimatedSticker from '../../common/AnimatedSticker';
type OwnProps = {
chatId: string;
@ -33,6 +37,7 @@ type OwnProps = {
type StateProps = {
chat?: ApiChat;
isChannel?: boolean;
exportedInvites?: ApiExportedInvite[];
revokedExportedInvites?: ApiExportedInvite[];
serverTimeOffset: number;
@ -54,6 +59,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
exportedInvites,
revokedExportedInvites,
isActive,
isChannel,
serverTimeOffset,
onClose,
onScreenSelect,
@ -66,14 +72,26 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
deleteRevokedExportedChatInvites,
setOpenedInviteInfo,
} = getDispatch();
const lang = useLang();
const [isDeleteRevokeAllDialogOpen, openDeleteRevokeAllDialog, closeDeleteRevokeAllDialog] = useFlag();
const [isRevokeDialogOpen, openRevokeDialog, closeRevokeDialog] = useFlag();
const [revokingInvite, setRevokingInvite] = useState<ApiExportedInvite | undefined>();
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
const [deletingInvite, setDeletingInvite] = useState<ApiExportedInvite | undefined>();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
useEffect(() => {
if (!animationData) {
getAnimationData('Invite').then(setAnimationData);
}
}, [animationData]);
useHistoryBack(isActive, onClose);
const lang = useLang();
const hasDetailedCountdown = useMemo(() => {
if (!exportedInvites) return undefined;
@ -265,6 +283,20 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
return (
<div className="Management ManageInvites">
<div className="custom-scroll">
<div className="section">
<div className="section-icon">
{animationData && (
<AnimatedSticker
id="inviteDuck"
size={STICKER_SIZE_INVITES}
animationData={animationData}
play={isAnimationLoaded}
onLoad={handleAnimationLoad}
/>
)}
</div>
<p className='text-muted'>{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
</div>
{primaryInviteLink && (
<div className="section">
<p className="text-muted">
@ -347,6 +379,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
onClose={closeDeleteRevokeAllDialog}
title={lang('DeleteAllRevokedLinks')}
text={lang('DeleteAllRevokedLinkHelp')}
confirmIsDestructive
confirmLabel={lang('DeleteAll')}
confirmHandler={handleDeleteAllRevoked}
/>
<ConfirmDialog
@ -354,6 +388,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
onClose={closeRevokeDialog}
title={lang('RevokeLink')}
text={lang('RevokeAlert')}
confirmIsDestructive
confirmLabel={lang('RevokeButton')}
confirmHandler={handleRevoke}
/>
<ConfirmDialog
@ -361,6 +397,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
onClose={closeDeleteDialog}
title={lang('DeleteLink')}
text={lang('DeleteLinkHelp')}
confirmIsDestructive
confirmLabel={lang('Delete')}
confirmHandler={handleDelete}
/>
</div>
@ -371,12 +409,14 @@ export default memo(withGlobal<OwnProps>(
(global, { chatId }): StateProps => {
const { invites, revokedInvites } = global.management.byChatId[chatId];
const chat = selectChat(global, chatId);
const isChannel = chat && isChatChannel(chat);
return {
exportedInvites: invites,
revokedExportedInvites: revokedInvites,
chat,
serverTimeOffset: global.serverTimeOffset,
isChannel,
};
},
)(ManageInvites));

View File

@ -1,19 +1,23 @@
import React, {
FC, memo, useCallback, useEffect,
FC, memo, useCallback, useEffect, useState,
} from '../../../lib/teact/teact';
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
import { ApiChat } from '../../../api/types';
import { STICKER_SIZE_JOIN_REQUESTS } from '../../../config';
import useHistoryBack from '../../../hooks/useHistoryBack';
import { selectChat } from '../../../modules/selectors';
import { isChatChannel } from '../../../modules/helpers';
import { isChatChannel, isUserId } from '../../../modules/helpers';
import useLang from '../../../hooks/useLang';
import useFlag from '../../../hooks/useFlag';
import getAnimationData from '../../common/helpers/animatedAssets';
import JoinRequest from './JoinRequest';
import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog';
import AnimatedSticker from '../../common/AnimatedSticker';
import Spinner from '../../ui/Spinner';
type OwnProps = {
chatId: string;
@ -40,10 +44,20 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
const lang = useLang();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
useEffect(() => {
if (!animationData) {
getAnimationData('JoinRequest').then(setAnimationData);
}
}, [animationData]);
useHistoryBack(isActive, onClose);
useEffect(() => {
if (!chat?.joinRequests) {
if (!chat?.joinRequests && !isUserId(chatId)) {
loadChatJoinRequests({ chatId });
}
}, [chat, chatId, loadChatJoinRequests]);
@ -60,17 +74,34 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
return (
<div className="Management ManageJoinRequests">
{Boolean(chat?.joinRequests?.length) && (
<div className="section bulk-actions">
<Button className="bulk-action-button" onClick={openAcceptAllDialog}>Accept all</Button>
<Button className="bulk-action-button" onClick={openRejectAllDialog} isText>Dismiss all</Button>
<div className="section">
<div className="section-icon">
{animationData && (
<AnimatedSticker
id="joinRequestDucks"
size={STICKER_SIZE_JOIN_REQUESTS}
animationData={animationData}
play={isAnimationLoaded}
onLoad={handleAnimationLoad}
/>
)}
</div>
)}
{Boolean(chat?.joinRequests?.length) && (
<div className="bulk-actions">
<Button className="bulk-action-button" onClick={openAcceptAllDialog}>Accept all</Button>
<Button className="bulk-action-button" onClick={openRejectAllDialog} isText>Dismiss all</Button>
</div>
)}
</div>
<div className="section">
<div className="custom-scroll" teactFastList>
<p key="title">
{chat?.joinRequests?.length ? lang('JoinRequests', chat?.joinRequests?.length) : lang('NoMemberRequests')}
{!chat?.joinRequests ? lang('Loading') : chat.joinRequests.length
? lang('JoinRequests', chat.joinRequests.length) : lang('NoMemberRequests')}
</p>
{!chat?.joinRequests && (
<Spinner key="loading" />
)}
{chat?.joinRequests?.length === 0 && (
<p className="text-muted" key="empty">
{isChannel ? lang('NoSubscribeRequestsDescription') : lang('NoMemberRequestsDescription')}

View File

@ -183,6 +183,10 @@
box-shadow: none;
}
}
.Spinner {
margin: 2rem auto;
}
}
.ManageGroupMembers {
@ -197,6 +201,7 @@
.primary-link-input {
cursor: pointer;
margin-bottom: 1rem;
padding-right: 3rem;
}
.primary-link-more-menu {
@ -230,6 +235,10 @@
-webkit-appearance: none;
}
}
.custom-scroll {
padding-bottom: 4rem;
}
}
.ManageInviteInfo {
@ -237,6 +246,10 @@
margin-top: 1rem;
margin-bottom: 1rem;
}
.link-title {
text-align: center;
}
}
.ManageJoinRequests {
@ -251,7 +264,8 @@
}
}
.ManageInvite, .ManageInvites {
.ManageInvite,
.ManageInvites {
.hint {
font-size: 0.875rem;
margin-bottom: 0;

View File

@ -114,7 +114,6 @@ const ListItem: FC<OwnProps> = ({
const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (disabled || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return;
e.stopPropagation();
if (onSecondaryIconClick) {
onSecondaryIconClick(e);
@ -188,7 +187,8 @@ const ListItem: FC<OwnProps> = ({
round
color="translucent"
size="smaller"
onMouseDown={handleSecondaryIconClick}
onClick={IS_TOUCH_ENV ? handleSecondaryIconClick : undefined}
onMouseDown={!IS_TOUCH_ENV ? handleSecondaryIconClick : undefined}
>
<i className={`icon-${secondaryIcon}`} />
</Button>

View File

@ -140,7 +140,11 @@
}
.modal-about {
font-weight: bold;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
text-overflow: ellipsis;
overflow: hidden;
}
.modal-help {

View File

@ -121,8 +121,10 @@ export const STICKER_SIZE_SEARCH = 64;
export const STICKER_SIZE_MODAL = 64;
export const STICKER_SIZE_TWO_FA = 160;
export const STICKER_SIZE_DISCUSSION_GROUPS = 140;
export const STICKER_SIZE_FOLDER_SETTINGS = 80;
export const STICKER_SIZE_FOLDER_SETTINGS = 100;
export const STICKER_SIZE_INLINE_BOT_RESULT = 100;
export const STICKER_SIZE_JOIN_REQUESTS = 140;
export const STICKER_SIZE_INVITES = 140;
export const RECENT_STICKERS_LIMIT = 20;
export const MEMOJI_STICKER_ID = 'MEMOJI_STICKER';

View File

@ -558,6 +558,7 @@ export type ActionTypes = (
'setEditingExportedInvite' | 'loadExportedChatInvites' | 'editExportedChatInvite' | 'exportChatInvite' |
'deleteExportedChatInvite' | 'deleteRevokedExportedChatInvites' | 'setOpenedInviteInfo' | 'loadChatInviteImporters' |
'loadChatJoinRequests' | 'hideChatJoinRequest' | 'hideAllChatJoinRequests' | 'requestNextManagementScreen' |
'loadChatInviteRequesters' |
// groups
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
'acceptInviteConfirmation' |

View File

@ -243,7 +243,7 @@ addReducer('loadChatInviteImporters', (global, actions, payload) => {
}
global = getGlobal();
const currentInviteInfo = global.management.byChatId[chatId]?.inviteInfo;
if (!currentInviteInfo?.invite) return;
if (!currentInviteInfo?.invite || currentInviteInfo.invite.link !== link) return;
setGlobal(updateManagement(global, chatId, {
inviteInfo: {
...currentInviteInfo,
@ -253,6 +253,38 @@ addReducer('loadChatInviteImporters', (global, actions, payload) => {
})();
});
addReducer('loadChatInviteRequesters', (global, actions, payload) => {
const {
chatId, link, offsetDate, offsetUserId, limit,
} = payload!;
const peer = selectChat(global, chatId);
const offsetUser = selectUser(global, offsetUserId);
if (!peer || (offsetUserId && !offsetUser)) return;
(async () => {
const result = await callApi('fetchChatInviteImporters', {
peer,
link,
offsetDate,
offsetUser,
limit,
isRequested: true,
});
if (!result) {
return;
}
global = getGlobal();
const currentInviteInfo = global.management.byChatId[chatId]?.inviteInfo;
if (!currentInviteInfo?.invite || currentInviteInfo.invite.link !== link) return;
setGlobal(updateManagement(global, chatId, {
inviteInfo: {
...currentInviteInfo,
requesters: result,
},
}));
})();
});
addReducer('loadChatJoinRequests', (global, actions, payload) => {
const {
chatId, offsetDate, offsetUserId, limit,

View File

@ -293,6 +293,7 @@ export interface ManagementState {
inviteInfo?: {
invite: ApiExportedInvite;
importers?: ApiChatInviteImporter[];
requesters?: ApiChatInviteImporter[];
};
}