Topic management: Display icon picker (#2247)
This commit is contained in:
parent
9e4ec8286c
commit
b4f283d207
@ -1516,7 +1516,7 @@ export function editTopic({
|
|||||||
channel: buildInputPeer(id, accessHash),
|
channel: buildInputPeer(id, accessHash),
|
||||||
topicId,
|
topicId,
|
||||||
title,
|
title,
|
||||||
iconEmojiId: iconEmojiId ? BigInt(iconEmojiId) : undefined,
|
iconEmojiId: BigInt(iconEmojiId || '0'),
|
||||||
closed: isClosed,
|
closed: isClosed,
|
||||||
hidden: isHidden,
|
hidden: isHidden,
|
||||||
}), true);
|
}), true);
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export {
|
|||||||
faveSticker, fetchStickers, fetchSavedGifs, saveGif, searchStickers, installStickerSet, uninstallStickerSet,
|
faveSticker, fetchStickers, fetchSavedGifs, saveGif, searchStickers, installStickerSet, uninstallStickerSet,
|
||||||
searchGifs, fetchAnimatedEmojis, fetchStickersForEmoji, fetchEmojiKeywords, fetchAnimatedEmojiEffects,
|
searchGifs, fetchAnimatedEmojis, fetchStickersForEmoji, fetchEmojiKeywords, fetchAnimatedEmojiEffects,
|
||||||
removeRecentSticker, clearRecentStickers, fetchCustomEmoji, fetchPremiumGifts, fetchCustomEmojiSets,
|
removeRecentSticker, clearRecentStickers, fetchCustomEmoji, fetchPremiumGifts, fetchCustomEmojiSets,
|
||||||
fetchFeaturedEmojiStickers, fetchGenericEmojiEffects,
|
fetchFeaturedEmojiStickers, fetchGenericEmojiEffects, fetchDefaultTopicIcons,
|
||||||
} from './symbols';
|
} from './symbols';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -248,6 +248,21 @@ export async function fetchPremiumGifts() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchDefaultTopicIcons() {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||||
|
stickerset: new GramJs.InputStickerSetEmojiDefaultTopicIcons(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!(result instanceof GramJs.messages.StickerSet)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
set: buildStickerSet(result.set),
|
||||||
|
stickers: processStickerResult(result.documents),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function searchStickers({ query, hash = '0' }: { query: string; hash?: string }) {
|
export async function searchStickers({ query, hash = '0' }: { query: string; hash?: string }) {
|
||||||
const result = await invokeRequest(new GramJs.messages.SearchStickerSets({
|
const result = await invokeRequest(new GramJs.messages.SearchStickerSets({
|
||||||
q: query,
|
q: query,
|
||||||
|
|||||||
@ -76,6 +76,8 @@ export { default as GifSearch } from '../components/right/GifSearch';
|
|||||||
export { default as Statistics } from '../components/right/statistics/Statistics';
|
export { default as Statistics } from '../components/right/statistics/Statistics';
|
||||||
export { default as MessageStatistics } from '../components/right/statistics/MessageStatistics';
|
export { default as MessageStatistics } from '../components/right/statistics/MessageStatistics';
|
||||||
export { default as PollResults } from '../components/right/PollResults';
|
export { default as PollResults } from '../components/right/PollResults';
|
||||||
|
export { default as CreateTopic } from '../components/right/CreateTopic';
|
||||||
|
export { default as EditTopic } from '../components/right/EditTopic';
|
||||||
|
|
||||||
export { default as Management } from '../components/right/management/Management';
|
export { default as Management } from '../components/right/management/Management';
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatMessages, selectChatOnlineCount, selectThreadInfo,
|
selectChat, selectChatMessages, selectChatOnlineCount, selectThreadInfo, selectThreadMessagesCount,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
@ -61,6 +61,7 @@ type StateProps =
|
|||||||
onlineCount?: number;
|
onlineCount?: number;
|
||||||
areMessagesLoaded: boolean;
|
areMessagesLoaded: boolean;
|
||||||
animationLevel: AnimationLevel;
|
animationLevel: AnimationLevel;
|
||||||
|
messagesCount?: number;
|
||||||
}
|
}
|
||||||
& Pick<GlobalState, 'lastSyncTime'>;
|
& Pick<GlobalState, 'lastSyncTime'>;
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
animationLevel,
|
animationLevel,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
topic,
|
topic,
|
||||||
|
messagesCount,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@ -148,7 +150,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
if (isTopic) {
|
if (isTopic) {
|
||||||
return (
|
return (
|
||||||
<span className="status" dir="auto">
|
<span className="status" dir="auto">
|
||||||
{threadInfo?.messagesCount ? lang('messages', threadInfo.messagesCount, 'i') : renderText(chat.title)}
|
{messagesCount ? lang('messages', messagesCount, 'i') : renderText(chat.title)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -227,6 +229,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
|
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
||||||
const topic = threadId ? chat?.topics?.[threadId] : undefined;
|
const topic = threadId ? chat?.topics?.[threadId] : undefined;
|
||||||
|
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
@ -236,6 +239,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
topic,
|
topic,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
animationLevel: global.settings.byKey.animationLevel,
|
animationLevel: global.settings.byKey.animationLevel,
|
||||||
|
messagesCount,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(GroupChatInfo));
|
)(GroupChatInfo));
|
||||||
|
|||||||
@ -11,11 +11,10 @@ import type { GlobalState } from '../../global/types';
|
|||||||
import type { AnimationLevel } from '../../types';
|
import type { AnimationLevel } from '../../types';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import { GENERAL_TOPIC_ID } from '../../config';
|
|
||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import {
|
import {
|
||||||
selectChat, selectCurrentMessageList, selectThreadInfo, selectUser, selectUserStatus,
|
selectChat, selectCurrentMessageList, selectThreadMessagesCount, selectUser, selectUserStatus,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { getUserStatus, isChatChannel, isUserOnline } from '../../global/helpers';
|
import { getUserStatus, isChatChannel, isUserOnline } from '../../global/helpers';
|
||||||
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
||||||
@ -187,9 +186,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
<h3 className={styles.topicTitle} dir={lang.isRtl ? 'rtl' : undefined}>{renderText(topic!.title)}</h3>
|
<h3 className={styles.topicTitle} dir={lang.isRtl ? 'rtl' : undefined}>{renderText(topic!.title)}</h3>
|
||||||
<p className={styles.topicMessagesCounter}>
|
<p className={styles.topicMessagesCounter}>
|
||||||
{messagesCount && messagesCount > 1
|
{messagesCount ? lang('Chat.Title.Topic', messagesCount, 'i') : lang('lng_forum_no_messages')}
|
||||||
? lang('Chat.Title.Topic', messagesCount + (topic!.id === GENERAL_TOPIC_ID ? 1 : -1), 'i')
|
|
||||||
: lang('lng_forum_no_messages')}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -308,7 +305,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { mediaId, avatarOwnerId } = global.mediaViewer;
|
const { mediaId, avatarOwnerId } = global.mediaViewer;
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
||||||
const threadInfo = currentTopicId ? selectThreadInfo(global, userId, currentTopicId) : undefined;
|
|
||||||
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -323,7 +319,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
avatarOwnerId,
|
avatarOwnerId,
|
||||||
...(topic && {
|
...(topic && {
|
||||||
topic,
|
topic,
|
||||||
messagesCount: threadInfo?.messagesCount,
|
messagesCount: selectThreadMessagesCount(global, userId, currentTopicId!),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -44,12 +44,16 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
color: white;
|
color: white;
|
||||||
background: var(--premium-gradient);
|
background: var(--premium-gradient);
|
||||||
|
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.interactive {
|
&.interactive {
|
||||||
|
|||||||
@ -124,7 +124,6 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
openForumPanel,
|
|
||||||
focusLastMessage,
|
focusLastMessage,
|
||||||
loadTopics,
|
loadTopics,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
@ -156,19 +155,12 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (isForum) {
|
|
||||||
openForumPanel({ chatId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openChat({ id: chatId, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true });
|
openChat({ id: chatId, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true });
|
||||||
|
|
||||||
if (isSelected && canScrollDown) {
|
if (isSelected && canScrollDown) {
|
||||||
focusLastMessage();
|
focusLastMessage();
|
||||||
}
|
}
|
||||||
}, [
|
}, [openChat, chatId, isSelected, canScrollDown, focusLastMessage]);
|
||||||
isForum, openChat, chatId, isSelected, canScrollDown, openForumPanel, focusLastMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleDragEnter = useCallback((e) => {
|
const handleDragEnter = useCallback((e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@ -180,6 +180,7 @@ const Main: FC<StateProps> = ({
|
|||||||
loadAvailableReactions,
|
loadAvailableReactions,
|
||||||
loadStickerSets,
|
loadStickerSets,
|
||||||
loadPremiumGifts,
|
loadPremiumGifts,
|
||||||
|
loadDefaultTopicIcons,
|
||||||
loadAddedStickers,
|
loadAddedStickers,
|
||||||
loadFavoriteStickers,
|
loadFavoriteStickers,
|
||||||
ensureTimeFormat,
|
ensureTimeFormat,
|
||||||
@ -222,12 +223,13 @@ const Main: FC<StateProps> = ({
|
|||||||
loadAttachBots();
|
loadAttachBots();
|
||||||
loadContactList();
|
loadContactList();
|
||||||
loadPremiumGifts();
|
loadPremiumGifts();
|
||||||
|
loadDefaultTopicIcons();
|
||||||
checkAppVersion();
|
checkAppVersion();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
|
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
|
||||||
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
|
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
|
||||||
loadPremiumGifts, checkAppVersion, loadConfig, loadGenericEmojiEffects,
|
loadPremiumGifts, checkAppVersion, loadConfig, loadGenericEmojiEffects, loadDefaultTopicIcons,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Language-based API calls
|
// Language-based API calls
|
||||||
|
|||||||
@ -27,8 +27,8 @@ import {
|
|||||||
getCanAddContact,
|
getCanAddContact,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
getHasAdminRight,
|
|
||||||
getCanManageTopic,
|
getCanManageTopic,
|
||||||
|
isUserRightBanned,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
||||||
@ -91,6 +91,7 @@ type StateProps = {
|
|||||||
isPrivate?: boolean;
|
isPrivate?: boolean;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
isTopic?: boolean;
|
isTopic?: boolean;
|
||||||
|
isForum?: boolean;
|
||||||
canAddContact?: boolean;
|
canAddContact?: boolean;
|
||||||
canReportChat?: boolean;
|
canReportChat?: boolean;
|
||||||
canDeleteChat?: boolean;
|
canDeleteChat?: boolean;
|
||||||
@ -113,6 +114,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
botCommands,
|
botCommands,
|
||||||
withForumActions,
|
withForumActions,
|
||||||
isTopic,
|
isTopic,
|
||||||
|
isForum,
|
||||||
isChatInfoShown,
|
isChatInfoShown,
|
||||||
canStartBot,
|
canStartBot,
|
||||||
canRestartBot,
|
canRestartBot,
|
||||||
@ -157,6 +159,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
openChatWithInfo,
|
openChatWithInfo,
|
||||||
openCreateTopicPanel,
|
openCreateTopicPanel,
|
||||||
openEditTopicPanel,
|
openEditTopicPanel,
|
||||||
|
openChat,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||||
@ -166,7 +169,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useShowTransition(isOpen, onCloseAnimationEnd, undefined, false);
|
useShowTransition(isOpen, onCloseAnimationEnd, undefined, false);
|
||||||
const isViewGroupInfoShown = usePrevDuringAnimation(
|
const isViewGroupInfoShown = usePrevDuringAnimation(
|
||||||
(!isChatInfoShown && (withForumActions || isTopic)) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
|
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleReport = useCallback(() => {
|
const handleReport = useCallback(() => {
|
||||||
@ -222,6 +225,11 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
closeMenu();
|
closeMenu();
|
||||||
}, [openEditTopicPanel, chatId, threadId, closeMenu]);
|
}, [openEditTopicPanel, chatId, threadId, closeMenu]);
|
||||||
|
|
||||||
|
const handleViewAsTopicsClick = useCallback(() => {
|
||||||
|
openChat({ id: undefined });
|
||||||
|
closeMenu();
|
||||||
|
}, [closeMenu, openChat]);
|
||||||
|
|
||||||
const handleEnterVoiceChatClick = useCallback(() => {
|
const handleEnterVoiceChatClick = useCallback(() => {
|
||||||
if (canCreateVoiceChat) {
|
if (canCreateVoiceChat) {
|
||||||
// TODO show popup to schedule
|
// TODO show popup to schedule
|
||||||
@ -352,6 +360,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
{lang('lng_forum_topic_edit')}
|
{lang('lng_forum_topic_edit')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{IS_SINGLE_COLUMN_LAYOUT && !withForumActions && isForum && !isTopic && (
|
||||||
|
<MenuItem
|
||||||
|
icon="forums"
|
||||||
|
onClick={handleViewAsTopicsClick}
|
||||||
|
>
|
||||||
|
{lang('Chat.ContextViewAsTopics')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{withForumActions && Boolean(pendingJoinRequests) && (
|
{withForumActions && Boolean(pendingJoinRequests) && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="user"
|
icon="user"
|
||||||
@ -538,7 +554,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const topic = chat?.topics?.[threadId];
|
const topic = chat?.topics?.[threadId];
|
||||||
const canCreateTopic = chat.isForum && (chat.isCreator || getHasAdminRight(chat, 'manageTopics'));
|
const canCreateTopic = chat.isForum && !isUserRightBanned(chat, 'manageTopics');
|
||||||
const canEditTopic = topic && getCanManageTopic(chat, topic);
|
const canEditTopic = topic && getCanManageTopic(chat, topic);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -546,6 +562,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isTopic: chat?.isForum && !isMainThread,
|
isTopic: chat?.isForum && !isMainThread,
|
||||||
|
isForum: chat?.isForum,
|
||||||
canAddContact,
|
canAddContact,
|
||||||
canReportChat,
|
canReportChat,
|
||||||
canDeleteChat: getCanDeleteChat(chat),
|
canDeleteChat: getCanDeleteChat(chat),
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getGlobal, withGlobal } from '../../../global';
|
import { getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiStickerSet, ApiSticker, ApiChat } from '../../../api/types';
|
import type { ApiStickerSet, ApiSticker } from '../../../api/types';
|
||||||
import type { StickerSetOrRecent } from '../../../types';
|
import type { StickerSetOrRecent } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -41,17 +41,18 @@ import StickerSetCover from './StickerSetCover';
|
|||||||
import './StickerPicker.scss';
|
import './StickerPicker.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId?: string;
|
||||||
className: string;
|
className?: string;
|
||||||
loadAndPlay: boolean;
|
loadAndPlay: boolean;
|
||||||
|
withDefaultTopicIcons?: boolean;
|
||||||
onCustomEmojiSelect: (sticker: ApiSticker) => void;
|
onCustomEmojiSelect: (sticker: ApiSticker) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
|
||||||
stickerSetsById: Record<string, ApiStickerSet>;
|
stickerSetsById: Record<string, ApiStickerSet>;
|
||||||
addedCustomEmojiIds?: string[];
|
addedCustomEmojiIds?: string[];
|
||||||
recentCustomEmoji: ApiSticker[];
|
recentCustomEmoji: ApiSticker[];
|
||||||
|
defaultTopicIconsId?: string;
|
||||||
featuredCustomEmojiIds?: string[];
|
featuredCustomEmojiIds?: string[];
|
||||||
canAnimate?: boolean;
|
canAnimate?: boolean;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
@ -74,6 +75,8 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
canAnimate,
|
canAnimate,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
|
withDefaultTopicIcons,
|
||||||
|
defaultTopicIconsId,
|
||||||
onCustomEmojiSelect,
|
onCustomEmojiSelect,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -124,7 +127,16 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const defaultSets = [];
|
const defaultSets = [];
|
||||||
|
|
||||||
if (recentCustomEmoji.length) {
|
if (withDefaultTopicIcons) {
|
||||||
|
const defaultTopicIconsPack = stickerSetsById[defaultTopicIconsId!];
|
||||||
|
if (defaultTopicIconsPack.stickers?.length) {
|
||||||
|
defaultSets.push({
|
||||||
|
...defaultTopicIconsPack,
|
||||||
|
id: RECENT_SYMBOL_SET_ID,
|
||||||
|
title: lang('RecentStickers'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (recentCustomEmoji.length) {
|
||||||
defaultSets.push({
|
defaultSets.push({
|
||||||
id: RECENT_SYMBOL_SET_ID,
|
id: RECENT_SYMBOL_SET_ID,
|
||||||
title: lang('RecentStickers'),
|
title: lang('RecentStickers'),
|
||||||
@ -144,7 +156,10 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
...existingAddedSetIds,
|
...existingAddedSetIds,
|
||||||
...featuredSetIds,
|
...featuredSetIds,
|
||||||
];
|
];
|
||||||
}, [addedCustomEmojiIds, featuredCustomEmojiIds, lang, recentCustomEmoji, stickerSetsById]);
|
}, [
|
||||||
|
addedCustomEmojiIds, defaultTopicIconsId, featuredCustomEmojiIds, lang, recentCustomEmoji, stickerSetsById,
|
||||||
|
withDefaultTopicIcons,
|
||||||
|
]);
|
||||||
|
|
||||||
const noPopulatedSets = useMemo(() => (
|
const noPopulatedSets = useMemo(() => (
|
||||||
areAddedLoaded
|
areAddedLoaded
|
||||||
@ -280,6 +295,8 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
shouldRender={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
|
shouldRender={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
|
shouldHideRecentHeader={withDefaultTopicIcons}
|
||||||
|
withDefaultTopicIcon={withDefaultTopicIcons}
|
||||||
isCustomEmojiPicker
|
isCustomEmojiPicker
|
||||||
isCurrentUserPremium={isCurrentUserPremium}
|
isCurrentUserPremium={isCurrentUserPremium}
|
||||||
onStickerSelect={handleEmojiSelect}
|
onStickerSelect={handleEmojiSelect}
|
||||||
@ -296,7 +313,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
setsById,
|
setsById,
|
||||||
} = global.stickers;
|
} = global.stickers;
|
||||||
|
|
||||||
const isSavedMessages = selectIsChatWithSelf(global, chatId);
|
const isSavedMessages = Boolean(chatId && selectIsChatWithSelf(global, chatId));
|
||||||
|
|
||||||
const recentCustomEmoji = Object.values(pickTruthy(global.customEmojis.byId, global.recentCustomEmojis));
|
const recentCustomEmoji = Object.values(pickTruthy(global.customEmojis.byId, global.recentCustomEmojis));
|
||||||
|
|
||||||
@ -308,6 +325,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
recentCustomEmoji,
|
recentCustomEmoji,
|
||||||
featuredCustomEmojiIds: global.customEmojis.featuredIds,
|
featuredCustomEmojiIds: global.customEmojis.featuredIds,
|
||||||
|
defaultTopicIconsId: global.defaultTopicIconsId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(CustomEmojiPicker));
|
)(CustomEmojiPicker));
|
||||||
|
|||||||
@ -5,11 +5,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: calc(100% - 3rem);
|
height: calc(100% - 3rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem 0.25rem;
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
padding: 0.5rem 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
@ -92,12 +88,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.symbol-set-container {
|
.symbol-set-container {
|
||||||
width: 25rem;
|
width: 100%;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticker-set-button {
|
.sticker-set-button {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useMemo, useRef,
|
memo, useCallback, useLayoutEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -10,21 +10,24 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
|||||||
import { useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_TOPIC_ICON_STICKER_ID,
|
||||||
EMOJI_SIZE_PICKER, FAVORITE_SYMBOL_SET_ID, RECENT_SYMBOL_SET_ID, STICKER_SIZE_PICKER,
|
EMOJI_SIZE_PICKER, FAVORITE_SYMBOL_SET_ID, RECENT_SYMBOL_SET_ID, STICKER_SIZE_PICKER,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import windowSize from '../../../util/windowSize';
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../../global/selectors';
|
import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../../global/selectors';
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||||
|
import { useResizeObserver } from '../../../hooks/useResizeObserver';
|
||||||
|
|
||||||
import StickerButton from '../../common/StickerButton';
|
import StickerButton from '../../common/StickerButton';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
|
import grey from '../../../assets/icons/forumTopic/grey.svg';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
stickerSet: StickerSetOrRecent;
|
stickerSet: StickerSetOrRecent;
|
||||||
loadAndPlay: boolean;
|
loadAndPlay: boolean;
|
||||||
@ -34,6 +37,8 @@ type OwnProps = {
|
|||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
isCustomEmojiPicker?: boolean;
|
isCustomEmojiPicker?: boolean;
|
||||||
|
shouldHideRecentHeader?: boolean;
|
||||||
|
withDefaultTopicIcon?: boolean;
|
||||||
observeIntersection: ObserveFn;
|
observeIntersection: ObserveFn;
|
||||||
onStickerSelect?: (sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
onStickerSelect?: (sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||||
onStickerUnfave?: (sticker: ApiSticker) => void;
|
onStickerUnfave?: (sticker: ApiSticker) => void;
|
||||||
@ -41,11 +46,11 @@ type OwnProps = {
|
|||||||
onStickerRemoveRecent?: (sticker: ApiSticker) => void;
|
onStickerRemoveRecent?: (sticker: ApiSticker) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const STICKERS_PER_ROW_ON_DESKTOP = 5;
|
|
||||||
const EMOJI_PER_ROW_ON_DESKTOP = 8;
|
|
||||||
const STICKER_MARGIN = IS_SINGLE_COLUMN_LAYOUT ? 8 : 16;
|
const STICKER_MARGIN = IS_SINGLE_COLUMN_LAYOUT ? 8 : 16;
|
||||||
const EMOJI_MARGIN = IS_SINGLE_COLUMN_LAYOUT ? 8 : 10;
|
const EMOJI_MARGIN = IS_SINGLE_COLUMN_LAYOUT ? 8 : 10;
|
||||||
const MOBILE_CONTAINER_PADDING = 8;
|
const CONTAINER_PADDING = 8;
|
||||||
|
|
||||||
|
const ITEMS_PER_ROW_FALLBACK = 8;
|
||||||
|
|
||||||
const StickerSet: FC<OwnProps> = ({
|
const StickerSet: FC<OwnProps> = ({
|
||||||
stickerSet,
|
stickerSet,
|
||||||
@ -56,6 +61,8 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
isCustomEmojiPicker,
|
isCustomEmojiPicker,
|
||||||
|
shouldHideRecentHeader,
|
||||||
|
withDefaultTopicIcon,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
onStickerSelect,
|
onStickerSelect,
|
||||||
onStickerUnfave,
|
onStickerUnfave,
|
||||||
@ -75,11 +82,13 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const sharedCanvasRef = useRef<HTMLCanvasElement>(null);
|
const sharedCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const sharedCanvas2Ref = useRef<HTMLCanvasElement>(null);
|
const sharedCanvasHqRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
|
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
const [itemsPerRow, setItemsPerRow] = useState(ITEMS_PER_ROW_FALLBACK);
|
||||||
|
|
||||||
useOnIntersect(ref, observeIntersection);
|
useOnIntersect(ref, observeIntersection);
|
||||||
|
|
||||||
const transitionClassNames = useMediaTransition(shouldRender);
|
const transitionClassNames = useMediaTransition(shouldRender);
|
||||||
@ -110,21 +119,48 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isCurrentUserPremium, isPremiumSet, openPremiumModal, stickerSet, toggleStickerSet]);
|
}, [isCurrentUserPremium, isPremiumSet, openPremiumModal, stickerSet, toggleStickerSet]);
|
||||||
|
|
||||||
const isLocked = !isSavedMessages && !isRecent && isEmoji && !isCurrentUserPremium
|
const handleDefaultTopicIconClick = useCallback(() => {
|
||||||
&& stickerSet.stickers?.some(({ isFree }) => !isFree);
|
onStickerSelect?.({
|
||||||
|
id: DEFAULT_TOPIC_ICON_STICKER_ID,
|
||||||
|
isLottie: false,
|
||||||
|
isVideo: false,
|
||||||
|
stickerSetInfo: {
|
||||||
|
shortName: 'dummy',
|
||||||
|
},
|
||||||
|
} satisfies ApiSticker);
|
||||||
|
}, [onStickerSelect]);
|
||||||
|
|
||||||
const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER;
|
const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER;
|
||||||
const itemsPerRow = isEmoji ? EMOJI_PER_ROW_ON_DESKTOP : STICKERS_PER_ROW_ON_DESKTOP;
|
|
||||||
const margin = isEmoji ? EMOJI_MARGIN : STICKER_MARGIN;
|
const margin = isEmoji ? EMOJI_MARGIN : STICKER_MARGIN;
|
||||||
|
|
||||||
const stickersPerRow = IS_SINGLE_COLUMN_LAYOUT
|
const calculateItemsPerRow = useCallback((width: number) => {
|
||||||
? Math.floor((windowSize.get().width - MOBILE_CONTAINER_PADDING) / (itemSize + margin))
|
if (!width) return ITEMS_PER_ROW_FALLBACK;
|
||||||
: itemsPerRow;
|
|
||||||
|
return Math.floor((width - CONTAINER_PADDING) / (itemSize + margin));
|
||||||
|
}, [itemSize, margin]);
|
||||||
|
|
||||||
|
const handleResize = useCallback((entry: ResizeObserverEntry) => {
|
||||||
|
setItemsPerRow(calculateItemsPerRow(entry.contentRect.width));
|
||||||
|
}, [calculateItemsPerRow]);
|
||||||
|
useResizeObserver(ref, handleResize);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
setItemsPerRow(calculateItemsPerRow(ref.current.clientWidth));
|
||||||
|
}, [calculateItemsPerRow]);
|
||||||
|
|
||||||
|
const isLocked = !isSavedMessages && !isRecent && isEmoji && !isCurrentUserPremium
|
||||||
|
&& stickerSet.stickers?.some(({ isFree }) => !isFree);
|
||||||
|
|
||||||
const canCut = !stickerSet.installedDate && stickerSet.id !== RECENT_SYMBOL_SET_ID;
|
const canCut = !stickerSet.installedDate && stickerSet.id !== RECENT_SYMBOL_SET_ID;
|
||||||
const [isCut, , expand] = useFlag(canCut);
|
const [isCut, , expand] = useFlag(canCut);
|
||||||
const itemsBeforeCutout = stickersPerRow * 3 - 1;
|
const itemsBeforeCutout = itemsPerRow * 3 - 1;
|
||||||
const heightWhenCut = Math.ceil(Math.min(itemsBeforeCutout, stickerSet.count) / stickersPerRow) * (itemSize + margin);
|
const totalItemsCount = withDefaultTopicIcon ? stickerSet.count + 1 : stickerSet.count;
|
||||||
const height = isCut ? heightWhenCut : Math.ceil(stickerSet.count / stickersPerRow) * (itemSize + margin);
|
|
||||||
|
const heightWhenCut = Math.ceil(Math.min(itemsBeforeCutout, totalItemsCount) / itemsPerRow) * (itemSize + margin);
|
||||||
|
const height = isCut ? heightWhenCut : Math.ceil(totalItemsCount / itemsPerRow) * (itemSize + margin);
|
||||||
|
|
||||||
|
const shouldHideHeader = isRecent && shouldHideRecentHeader;
|
||||||
|
|
||||||
const favoriteStickerIdsSet = useMemo(() => (
|
const favoriteStickerIdsSet = useMemo(() => (
|
||||||
favoriteStickers ? new Set(favoriteStickers.map(({ id }) => id)) : undefined
|
favoriteStickers ? new Set(favoriteStickers.map(({ id }) => id)) : undefined
|
||||||
@ -139,27 +175,29 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
buildClassName('symbol-set', isLocked && 'symbol-set-locked')
|
buildClassName('symbol-set', isLocked && 'symbol-set-locked')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="symbol-set-header">
|
{!shouldHideHeader && (
|
||||||
<p className="symbol-set-name">
|
<div className="symbol-set-header">
|
||||||
{isLocked && <i className="symbol-set-locked-icon icon-lock-badge" />}
|
<p className="symbol-set-name">
|
||||||
{stickerSet.title}
|
{isLocked && <i className="symbol-set-locked-icon icon-lock-badge" />}
|
||||||
</p>
|
{stickerSet.title}
|
||||||
{isRecent && (
|
</p>
|
||||||
<i className="symbol-set-remove icon-close" onClick={openConfirmModal} />
|
{isRecent && (
|
||||||
)}
|
<i className="symbol-set-remove icon-close" onClick={openConfirmModal} />
|
||||||
{!isRecent && isEmoji && !stickerSet.installedDate && (
|
)}
|
||||||
<Button
|
{!isRecent && isEmoji && !stickerSet.installedDate && (
|
||||||
className="symbol-set-add-button"
|
<Button
|
||||||
withPremiumGradient={isPremiumSet && !isCurrentUserPremium}
|
className="symbol-set-add-button"
|
||||||
onClick={handleAddClick}
|
withPremiumGradient={isPremiumSet && !isCurrentUserPremium}
|
||||||
pill
|
onClick={handleAddClick}
|
||||||
size="tiny"
|
pill
|
||||||
fluid
|
size="tiny"
|
||||||
>
|
fluid
|
||||||
{isPremiumSet && isLocked ? lang('Unlock') : lang('Add')}
|
>
|
||||||
</Button>
|
{isPremiumSet && isLocked ? lang('Unlock') : lang('Add')}
|
||||||
)}
|
</Button>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className={buildClassName('symbol-set-container shared-canvas-container', transitionClassNames)}
|
className={buildClassName('symbol-set-container shared-canvas-container', transitionClassNames)}
|
||||||
style={`height: ${height}px;`}
|
style={`height: ${height}px;`}
|
||||||
@ -169,14 +207,24 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
className="shared-canvas"
|
className="shared-canvas"
|
||||||
style={canCut ? `height: ${heightWhenCut}px;` : undefined}
|
style={canCut ? `height: ${heightWhenCut}px;` : undefined}
|
||||||
/>
|
/>
|
||||||
{(isRecent || isFavorite || canCut) && <canvas ref={sharedCanvas2Ref} className="shared-canvas" />}
|
{(isRecent || isFavorite || canCut) && <canvas ref={sharedCanvasHqRef} className="shared-canvas" />}
|
||||||
|
{withDefaultTopicIcon && (
|
||||||
|
<Button
|
||||||
|
className="StickerButton custom-emoji"
|
||||||
|
color="translucent"
|
||||||
|
onClick={handleDefaultTopicIconClick}
|
||||||
|
key="default-topic-icon"
|
||||||
|
>
|
||||||
|
<img src={grey} alt="Reset" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{shouldRender && stickerSet.stickers && stickerSet.stickers
|
{shouldRender && stickerSet.stickers && stickerSet.stickers
|
||||||
.slice(0, isCut ? itemsBeforeCutout : stickerSet.stickers.length)
|
.slice(0, isCut ? itemsBeforeCutout : stickerSet.stickers.length)
|
||||||
.map((sticker, i) => {
|
.map((sticker, i) => {
|
||||||
const isHqEmoji = (isRecent || isFavorite)
|
const isHqEmoji = (isRecent || isFavorite)
|
||||||
&& selectIsAlwaysHighPriorityEmoji(getGlobal(), sticker.stickerSetInfo);
|
&& selectIsAlwaysHighPriorityEmoji(getGlobal(), sticker.stickerSetInfo);
|
||||||
const canvasRef = (canCut && i >= itemsBeforeCutout) || isHqEmoji
|
const canvasRef = (canCut && i >= itemsBeforeCutout) || isHqEmoji
|
||||||
? sharedCanvas2Ref
|
? sharedCanvasHqRef
|
||||||
: sharedCanvasRef;
|
: sharedCanvasRef;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -198,9 +246,15 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{isCut && stickerSet.count > itemsBeforeCutout && (
|
{isCut && totalItemsCount > itemsBeforeCutout && (
|
||||||
<Button className="StickerButton custom-emoji set-expand" round color="translucent" onClick={expand}>
|
<Button
|
||||||
+{stickerSet.count - itemsBeforeCutout}
|
className="StickerButton custom-emoji set-expand"
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
onClick={expand}
|
||||||
|
key="more"
|
||||||
|
>
|
||||||
|
+{totalItemsCount - itemsBeforeCutout}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
16
src/components/right/CreateTopic.async.tsx
Normal file
16
src/components/right/CreateTopic.async.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import type { OwnProps } from './CreateTopic';
|
||||||
|
import { Bundles } from '../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||||
|
import Loading from '../ui/Loading';
|
||||||
|
|
||||||
|
const CreateTopicAsync: FC<OwnProps> = (props) => {
|
||||||
|
const CreateTopic = useModuleLoader(Bundles.Extra, 'CreateTopic');
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return CreateTopic ? <CreateTopic {...props} /> : <Loading />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CreateTopicAsync);
|
||||||
@ -4,10 +4,11 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { ApiChat } from '../../api/types';
|
import type { ApiChat, ApiSticker } from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import { selectChat } from '../../global/selectors';
|
import { DEFAULT_TOPIC_ICON_STICKER_ID } from '../../config';
|
||||||
|
import { selectChat, selectIsCurrentUserPremium } from '../../global/selectors';
|
||||||
import { getTopicColors } from '../../util/forumColors';
|
import { getTopicColors } from '../../util/forumColors';
|
||||||
import cycleRestrict from '../../util/cycleRestrict';
|
import cycleRestrict from '../../util/cycleRestrict';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -20,12 +21,14 @@ import TopicIcon from '../common/TopicIcon';
|
|||||||
import InputText from '../ui/InputText';
|
import InputText from '../ui/InputText';
|
||||||
import FloatingActionButton from '../ui/FloatingActionButton';
|
import FloatingActionButton from '../ui/FloatingActionButton';
|
||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
|
import CustomEmojiPicker from '../middle/composer/CustomEmojiPicker';
|
||||||
|
import Transition from '../ui/Transition';
|
||||||
|
|
||||||
import styles from './ManageTopic.module.scss';
|
import styles from './ManageTopic.module.scss';
|
||||||
|
|
||||||
const ICON_SIZE = 5 * REM;
|
const ICON_SIZE = 5 * REM;
|
||||||
|
|
||||||
type OwnProps = {
|
export type OwnProps = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
@ -33,17 +36,20 @@ type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
createTopicPanel?: GlobalState['createTopicPanel'];
|
createTopicPanel?: GlobalState['createTopicPanel'];
|
||||||
|
isCurrentUserPremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreateTopic: FC<OwnProps & StateProps> = ({
|
const CreateTopic: FC<OwnProps & StateProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
chat,
|
chat,
|
||||||
createTopicPanel,
|
createTopicPanel,
|
||||||
|
isCurrentUserPremium,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { createTopic, closeCreateTopicPanel } = getActions();
|
const { createTopic, openPremiumModal } = getActions();
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [iconColorIndex, setIconColorIndex] = useState(0);
|
const [iconColorIndex, setIconColorIndex] = useState(0);
|
||||||
|
const [iconEmojiId, setIconEmojiId] = useState<string | undefined>(undefined);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const isTouched = Boolean(title);
|
const isTouched = Boolean(title);
|
||||||
@ -67,17 +73,32 @@ const CreateTopic: FC<OwnProps & StateProps> = ({
|
|||||||
chatId: chat!.id,
|
chatId: chat!.id,
|
||||||
title,
|
title,
|
||||||
iconColor: getTopicColors()[iconColorIndex],
|
iconColor: getTopicColors()[iconColorIndex],
|
||||||
|
iconEmojiId,
|
||||||
});
|
});
|
||||||
closeCreateTopicPanel();
|
}, [chat, createTopic, iconColorIndex, iconEmojiId, title]);
|
||||||
}, [chat, closeCreateTopicPanel, createTopic, iconColorIndex, title]);
|
|
||||||
|
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
||||||
|
if (!emoji.isFree && !isCurrentUserPremium) {
|
||||||
|
openPremiumModal({ initialSection: 'animated_emoji' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emoji.id === DEFAULT_TOPIC_ICON_STICKER_ID) {
|
||||||
|
setIconEmojiId(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIconEmojiId(emoji.id);
|
||||||
|
}, [isCurrentUserPremium, openPremiumModal]);
|
||||||
|
|
||||||
const dummyTopic = useMemo(() => {
|
const dummyTopic = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
title,
|
title,
|
||||||
iconColor: getTopicColors()[iconColorIndex],
|
iconColor: getTopicColors()[iconColorIndex],
|
||||||
|
iconEmojiId,
|
||||||
};
|
};
|
||||||
}, [iconColorIndex, title]);
|
}, [iconColorIndex, iconEmojiId, title]);
|
||||||
|
|
||||||
if (!chat?.isForum) {
|
if (!chat?.isForum) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -85,15 +106,24 @@ const CreateTopic: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<div className="custom-scroll">
|
<div className={buildClassName(styles.content, 'custom-scroll')}>
|
||||||
<div className={buildClassName(styles.top, 'section')}>
|
<div className={buildClassName(styles.section, styles.top)}>
|
||||||
<span className={styles.heading}>{lang('CreateTopicTitle')}</span>
|
<span className={styles.heading}>{lang('CreateTopicTitle')}</span>
|
||||||
<TopicIcon
|
<Transition
|
||||||
topic={dummyTopic}
|
name="zoom-fade"
|
||||||
className={buildClassName(styles.icon, styles.clickable)}
|
activeKey={Number(dummyTopic.iconEmojiId) || 0}
|
||||||
onClick={handleIconClick}
|
shouldCleanup
|
||||||
size={ICON_SIZE}
|
direction={1}
|
||||||
/>
|
className={styles.iconWrapper}
|
||||||
|
>
|
||||||
|
<TopicIcon
|
||||||
|
topic={dummyTopic}
|
||||||
|
className={buildClassName(styles.icon, styles.clickable)}
|
||||||
|
onClick={handleIconClick}
|
||||||
|
size={ICON_SIZE}
|
||||||
|
noLoopLimit
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
<InputText
|
<InputText
|
||||||
value={title}
|
value={title}
|
||||||
onChange={handleTitleChange}
|
onChange={handleTitleChange}
|
||||||
@ -102,6 +132,14 @@ const CreateTopic: FC<OwnProps & StateProps> = ({
|
|||||||
teactExperimentControlled
|
teactExperimentControlled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={buildClassName(styles.section, styles.bottom)}>
|
||||||
|
<CustomEmojiPicker
|
||||||
|
loadAndPlay={isActive}
|
||||||
|
onCustomEmojiSelect={handleCustomEmojiSelect}
|
||||||
|
className={styles.iconPicker}
|
||||||
|
withDefaultTopicIcons
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
isShown={isTouched}
|
isShown={isTouched}
|
||||||
@ -125,6 +163,7 @@ export default memo(withGlobal(
|
|||||||
return {
|
return {
|
||||||
chat: createTopicPanel?.chatId ? selectChat(global, createTopicPanel.chatId) : undefined,
|
chat: createTopicPanel?.chatId ? selectChat(global, createTopicPanel.chatId) : undefined,
|
||||||
createTopicPanel,
|
createTopicPanel,
|
||||||
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(CreateTopic));
|
)(CreateTopic));
|
||||||
|
|||||||
16
src/components/right/EditTopic.async.tsx
Normal file
16
src/components/right/EditTopic.async.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import type { OwnProps } from './EditTopic';
|
||||||
|
import { Bundles } from '../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||||
|
import Loading from '../ui/Loading';
|
||||||
|
|
||||||
|
const EditTopicAsync: FC<OwnProps> = (props) => {
|
||||||
|
const EditTopic = useModuleLoader(Bundles.Extra, 'EditTopic');
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return EditTopic ? <EditTopic {...props} /> : <Loading />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(EditTopicAsync);
|
||||||
@ -4,10 +4,11 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { ApiChat, ApiTopic } from '../../api/types';
|
import type { ApiChat, ApiSticker, ApiTopic } from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import { selectChat } from '../../global/selectors';
|
import { DEFAULT_TOPIC_ICON_STICKER_ID, GENERAL_TOPIC_ID } from '../../config';
|
||||||
|
import { selectChat, selectIsCurrentUserPremium } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { REM } from '../common/helpers/mediaDimensions';
|
import { REM } from '../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
@ -19,12 +20,14 @@ import InputText from '../ui/InputText';
|
|||||||
import FloatingActionButton from '../ui/FloatingActionButton';
|
import FloatingActionButton from '../ui/FloatingActionButton';
|
||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
|
import CustomEmojiPicker from '../middle/composer/CustomEmojiPicker';
|
||||||
|
import Transition from '../ui/Transition';
|
||||||
|
|
||||||
import styles from './ManageTopic.module.scss';
|
import styles from './ManageTopic.module.scss';
|
||||||
|
|
||||||
const ICON_SIZE = 5 * REM;
|
const ICON_SIZE = 5 * REM;
|
||||||
|
|
||||||
type OwnProps = {
|
export type OwnProps = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
@ -33,6 +36,7 @@ type StateProps = {
|
|||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
topic?: ApiTopic;
|
topic?: ApiTopic;
|
||||||
editTopicPanel?: GlobalState['editTopicPanel'];
|
editTopicPanel?: GlobalState['editTopicPanel'];
|
||||||
|
isCurrentUserPremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditTopic: FC<OwnProps & StateProps> = ({
|
const EditTopic: FC<OwnProps & StateProps> = ({
|
||||||
@ -40,14 +44,16 @@ const EditTopic: FC<OwnProps & StateProps> = ({
|
|||||||
chat,
|
chat,
|
||||||
topic,
|
topic,
|
||||||
editTopicPanel,
|
editTopicPanel,
|
||||||
|
isCurrentUserPremium,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { editTopic, closeEditTopicPanel } = getActions();
|
const { editTopic, openPremiumModal } = getActions();
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [isTouched, setIsTouched] = useState(false);
|
const [iconEmojiId, setIconEmojiId] = useState<string | undefined>(undefined);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const isLoading = Boolean(editTopicPanel?.isLoading);
|
const isLoading = Boolean(editTopicPanel?.isLoading);
|
||||||
|
const isGeneral = topic?.id === GENERAL_TOPIC_ID;
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
@ -55,33 +61,51 @@ const EditTopic: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (topic?.title) {
|
if (topic?.title || topic?.iconEmojiId) {
|
||||||
setTitle(topic.title);
|
setTitle(topic.title);
|
||||||
setIsTouched(false);
|
setIconEmojiId(topic.iconEmojiId);
|
||||||
}
|
}
|
||||||
}, [topic?.title]);
|
}, [topic]);
|
||||||
|
|
||||||
|
const isTouched = useMemo(() => {
|
||||||
|
return title !== topic?.title || iconEmojiId !== topic?.iconEmojiId;
|
||||||
|
}, [iconEmojiId, title, topic?.iconEmojiId, topic?.title]);
|
||||||
|
|
||||||
const handleTitleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleTitleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newTitle = e.target.value;
|
const newTitle = e.target.value;
|
||||||
setTitle(newTitle);
|
setTitle(newTitle);
|
||||||
setIsTouched(newTitle !== topic?.title);
|
}, []);
|
||||||
}, [topic?.title]);
|
|
||||||
|
|
||||||
const handleEditTopic = useCallback(() => {
|
const handleEditTopic = useCallback(() => {
|
||||||
editTopic({
|
editTopic({
|
||||||
chatId: chat!.id,
|
chatId: chat!.id,
|
||||||
title,
|
title,
|
||||||
topicId: topic!.id,
|
topicId: topic!.id,
|
||||||
|
iconEmojiId,
|
||||||
});
|
});
|
||||||
closeEditTopicPanel();
|
}, [chat, editTopic, iconEmojiId, title, topic]);
|
||||||
}, [chat, closeEditTopicPanel, editTopic, title, topic]);
|
|
||||||
|
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
||||||
|
if (!emoji.isFree && !isCurrentUserPremium) {
|
||||||
|
openPremiumModal({ initialSection: 'animated_emoji' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emoji.id === DEFAULT_TOPIC_ICON_STICKER_ID) {
|
||||||
|
setIconEmojiId(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIconEmojiId(emoji.id);
|
||||||
|
}, [isCurrentUserPremium, openPremiumModal]);
|
||||||
|
|
||||||
const dummyTopic = useMemo(() => {
|
const dummyTopic = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...topic!,
|
...topic!,
|
||||||
title,
|
title,
|
||||||
|
iconEmojiId,
|
||||||
};
|
};
|
||||||
}, [title, topic]);
|
}, [iconEmojiId, title, topic]);
|
||||||
|
|
||||||
if (!chat?.isForum) {
|
if (!chat?.isForum) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -89,24 +113,45 @@ const EditTopic: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<div className="custom-scroll">
|
<div className={buildClassName(styles.content, 'custom-scroll')}>
|
||||||
{!topic && <Loading />}
|
{!topic && <Loading />}
|
||||||
{topic && (
|
{topic && (
|
||||||
<div className={buildClassName(styles.top, 'section')}>
|
<>
|
||||||
<span className={styles.heading}>{lang('CreateTopicTitle')}</span>
|
<div className={buildClassName(styles.section, styles.top)}>
|
||||||
<TopicIcon
|
<span className={styles.heading}>{lang('CreateTopicTitle')}</span>
|
||||||
topic={dummyTopic}
|
<Transition
|
||||||
className={styles.icon}
|
name="zoom-fade"
|
||||||
size={ICON_SIZE}
|
activeKey={Number(dummyTopic.iconEmojiId) || 0}
|
||||||
/>
|
shouldCleanup
|
||||||
<InputText
|
direction={1}
|
||||||
value={title}
|
className={styles.iconWrapper}
|
||||||
onChange={handleTitleChange}
|
>
|
||||||
label={lang('lng_forum_topic_title')}
|
<TopicIcon
|
||||||
disabled={isLoading}
|
topic={dummyTopic}
|
||||||
teactExperimentControlled
|
className={styles.icon}
|
||||||
/>
|
size={ICON_SIZE}
|
||||||
</div>
|
noLoopLimit
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<InputText
|
||||||
|
value={title}
|
||||||
|
onChange={handleTitleChange}
|
||||||
|
label={lang('lng_forum_topic_title')}
|
||||||
|
disabled={isLoading}
|
||||||
|
teactExperimentControlled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!isGeneral && (
|
||||||
|
<div className={buildClassName(styles.section, styles.bottom)}>
|
||||||
|
<CustomEmojiPicker
|
||||||
|
loadAndPlay={isActive}
|
||||||
|
onCustomEmojiSelect={handleCustomEmojiSelect}
|
||||||
|
className={styles.iconPicker}
|
||||||
|
withDefaultTopicIcons
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
@ -134,6 +179,7 @@ export default memo(withGlobal(
|
|||||||
chat,
|
chat,
|
||||||
topic,
|
topic,
|
||||||
editTopicPanel,
|
editTopicPanel,
|
||||||
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(EditTopic));
|
)(EditTopic));
|
||||||
|
|||||||
@ -2,26 +2,48 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--color-background-secondary);
|
background-color: var(--color-background-secondary);
|
||||||
|
--topic-icon-size: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top {
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
|
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.top {
|
||||||
--custom-emoji-size: 5rem;
|
padding: 1rem 1.5rem;
|
||||||
width: 5rem;
|
}
|
||||||
height: 5rem;
|
|
||||||
font-size: 3rem;
|
.bottom {
|
||||||
align-self: center;
|
flex-grow: 1;
|
||||||
|
min-height: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconWrapper {
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
width: var(--topic-icon-size);
|
||||||
|
height: var(--topic-icon-size);
|
||||||
|
--custom-emoji-size: var(--topic-icon-size);
|
||||||
|
font-size: calc(var(--topic-icon-size) - 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
@ -33,3 +55,7 @@
|
|||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-picker {
|
||||||
|
min-height: 10rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -33,8 +33,8 @@ import StickerSearch from './StickerSearch.async';
|
|||||||
import GifSearch from './GifSearch.async';
|
import GifSearch from './GifSearch.async';
|
||||||
import PollResults from './PollResults.async';
|
import PollResults from './PollResults.async';
|
||||||
import AddChatMembers from './AddChatMembers';
|
import AddChatMembers from './AddChatMembers';
|
||||||
import CreateTopic from './CreateTopic';
|
import CreateTopic from './CreateTopic.async';
|
||||||
import EditTopic from './EditTopic';
|
import EditTopic from './EditTopic.async';
|
||||||
|
|
||||||
import './RightColumn.scss';
|
import './RightColumn.scss';
|
||||||
|
|
||||||
|
|||||||
@ -171,6 +171,7 @@ export const RECENT_SYMBOL_SET_ID = 'recent';
|
|||||||
export const FAVORITE_SYMBOL_SET_ID = 'favorite';
|
export const FAVORITE_SYMBOL_SET_ID = 'favorite';
|
||||||
export const CHAT_STICKER_SET_ID = 'chatStickers';
|
export const CHAT_STICKER_SET_ID = 'chatStickers';
|
||||||
export const PREMIUM_STICKER_SET_ID = 'premium';
|
export const PREMIUM_STICKER_SET_ID = 'premium';
|
||||||
|
export const DEFAULT_TOPIC_ICON_STICKER_ID = 'topic-default-icon';
|
||||||
export const EMOJI_IMG_REGEX = /<img[^>]+alt="([^"]+)"(?![^>]*data-document-id)[^>]*>/gm;
|
export const EMOJI_IMG_REGEX = /<img[^>]+alt="([^"]+)"(?![^>]*data-document-id)[^>]*>/gm;
|
||||||
|
|
||||||
export const BASE_EMOJI_KEYWORD_LANG = 'en';
|
export const BASE_EMOJI_KEYWORD_LANG = 'en';
|
||||||
|
|||||||
@ -1373,13 +1373,16 @@ addActionHandler('loadTopics', async (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadTopicById', async (global, actions, payload) => {
|
addActionHandler('loadTopicById', async (global, actions, payload) => {
|
||||||
const { chatId, topicId } = payload;
|
const { chatId, topicId, shouldCloseChatOnError } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
const result = await callApi('fetchTopicById', { chat, topicId });
|
const result = await callApi('fetchTopicById', { chat, topicId });
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
if (shouldCloseChatOnError) {
|
||||||
|
actions.openChat({ id: undefined });
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1413,7 +1416,9 @@ addActionHandler('toggleForum', async (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('createTopic', async (global, actions, payload) => {
|
addActionHandler('createTopic', async (global, actions, payload) => {
|
||||||
const { chatId, title, iconColor } = payload;
|
const {
|
||||||
|
chatId, title, iconColor, iconEmojiId,
|
||||||
|
} = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
@ -1425,10 +1430,13 @@ addActionHandler('createTopic', async (global, actions, payload) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const topicId = await callApi('createTopic', { chat, title, iconColor });
|
const topicId = await callApi('createTopic', {
|
||||||
|
chat, title, iconColor, iconEmojiId,
|
||||||
|
});
|
||||||
if (topicId) {
|
if (topicId) {
|
||||||
actions.openChat({ id: chatId, threadId: topicId, shouldReplaceHistory: true });
|
actions.openChat({ id: chatId, threadId: topicId, shouldReplaceHistory: true });
|
||||||
}
|
}
|
||||||
|
actions.closeCreateTopicPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('deleteTopic', async (global, actions, payload) => {
|
addActionHandler('deleteTopic', async (global, actions, payload) => {
|
||||||
@ -1451,12 +1459,23 @@ addActionHandler('editTopic', async (global, actions, payload) => {
|
|||||||
const topic = chat?.topics?.[topicId];
|
const topic = chat?.topics?.[topicId];
|
||||||
if (!chat || !topic) return;
|
if (!chat || !topic) return;
|
||||||
|
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
editTopicPanel: {
|
||||||
|
chatId,
|
||||||
|
topicId,
|
||||||
|
isLoading: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const result = await callApi('editTopic', { chat, topicId, ...rest });
|
const result = await callApi('editTopic', { chat, topicId, ...rest });
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateTopic(global, chatId, topicId, rest);
|
global = updateTopic(global, chatId, topicId, rest);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.closeEditTopicPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('toggleTopicPinned', (global, actions, payload) => {
|
addActionHandler('toggleTopicPinned', (global, actions, payload) => {
|
||||||
|
|||||||
@ -198,6 +198,24 @@ addActionHandler('loadPremiumGifts', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadDefaultTopicIcons', async (global) => {
|
||||||
|
const stickerSet = await callApi('fetchDefaultTopicIcons');
|
||||||
|
if (!stickerSet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
const { set, stickers } = stickerSet;
|
||||||
|
|
||||||
|
const fullSet = { ...set, stickers };
|
||||||
|
|
||||||
|
global = updateStickerSet(global, fullSet.id, fullSet);
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
defaultTopicIconsId: fullSet.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadStickers', (global, actions, payload) => {
|
addActionHandler('loadStickers', (global, actions, payload) => {
|
||||||
const { stickerSetInfo } = payload;
|
const { stickerSetInfo } = payload;
|
||||||
const cachedSet = selectStickerSet(global, stickerSetInfo);
|
const cachedSet = selectStickerSet(global, stickerSetInfo);
|
||||||
|
|||||||
@ -104,9 +104,6 @@ async function loadAndReplaceMessages() {
|
|||||||
|
|
||||||
const currentChat = activeCurrentChatId ? global.chats.byId[activeCurrentChatId] : undefined;
|
const currentChat = activeCurrentChatId ? global.chats.byId[activeCurrentChatId] : undefined;
|
||||||
if (activeCurrentChatId && currentChat) {
|
if (activeCurrentChatId && currentChat) {
|
||||||
if (currentChat.isForum) {
|
|
||||||
getActions().loadTopics({ chatId: activeCurrentChatId, force: true });
|
|
||||||
}
|
|
||||||
const result = await loadTopMessages(currentChat, activeThreadId, threadInfo?.lastReadInboxMessageId);
|
const result = await loadTopMessages(currentChat, activeThreadId, threadInfo?.lastReadInboxMessageId);
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const { chatId: newCurrentChatId } = selectCurrentMessageList(global) || {};
|
const { chatId: newCurrentChatId } = selectCurrentMessageList(global) || {};
|
||||||
@ -162,6 +159,15 @@ async function loadAndReplaceMessages() {
|
|||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
if (currentChat?.isForum) {
|
||||||
|
getActions().loadTopics({ chatId: activeCurrentChatId!, force: true });
|
||||||
|
if (currentThreadId && currentThreadId !== MAIN_THREAD_ID) {
|
||||||
|
getActions().loadTopicById({
|
||||||
|
chatId: activeCurrentChatId!, topicId: currentThreadId, shouldCloseChatOnError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer;
|
const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer;
|
||||||
if (audioChatId && audioMessageId && !selectChatMessage(global, audioChatId, audioMessageId)) {
|
if (audioChatId && audioMessageId && !selectChatMessage(global, audioChatId, audioMessageId)) {
|
||||||
getActions().closeAudioPlayer();
|
getActions().closeAudioPlayer();
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
deleteChatScheduledMessages,
|
deleteChatScheduledMessages,
|
||||||
updateThreadUnreadFromForwardedMessage,
|
updateThreadUnreadFromForwardedMessage,
|
||||||
updateTopic,
|
updateTopic,
|
||||||
|
deleteTopic,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
@ -376,7 +377,7 @@ addActionHandler('apiUpdate', (global, actions, update) => {
|
|||||||
case 'deleteMessages': {
|
case 'deleteMessages': {
|
||||||
const { ids, chatId } = update;
|
const { ids, chatId } = update;
|
||||||
|
|
||||||
deleteMessages(chatId, ids, actions, global);
|
deleteMessages(global, chatId, ids, actions);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,7 +403,7 @@ addActionHandler('apiUpdate', (global, actions, update) => {
|
|||||||
|
|
||||||
if (chatMessages) {
|
if (chatMessages) {
|
||||||
const ids = Object.keys(chatMessages.byId).map(Number);
|
const ids = Object.keys(chatMessages.byId).map(Number);
|
||||||
deleteMessages(chatId, ids, actions, getGlobal());
|
deleteMessages(global, chatId, ids, actions);
|
||||||
} else {
|
} else {
|
||||||
actions.requestChatUpdate({ chatId });
|
actions.requestChatUpdate({ chatId });
|
||||||
}
|
}
|
||||||
@ -734,7 +735,7 @@ function updateListedAndViewportIds(global: GlobalState, actions: GlobalActions,
|
|||||||
global = replaceThreadParam(global, chatId, threadInfo.threadId, 'threadInfo', {
|
global = replaceThreadParam(global, chatId, threadInfo.threadId, 'threadInfo', {
|
||||||
...threadInfo,
|
...threadInfo,
|
||||||
lastMessageId: message.id,
|
lastMessageId: message.id,
|
||||||
messagesCount: threadInfo.messagesCount + 1,
|
messagesCount: (threadInfo.messagesCount || 0) + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -808,10 +809,13 @@ function findLastMessage(global: GlobalState, chatId: string) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteMessages(chatId: string | undefined, ids: number[], actions: GlobalActions, global: GlobalState) {
|
function deleteMessages(global: GlobalState, chatId: string | undefined, ids: number[], actions: GlobalActions) {
|
||||||
// Channel update
|
// Channel update
|
||||||
|
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
global = updateChatMessage(global, chatId, id, {
|
global = updateChatMessage(global, chatId, id, {
|
||||||
isDeleting: true,
|
isDeleting: true,
|
||||||
@ -821,6 +825,10 @@ function deleteMessages(chatId: string | undefined, ids: number[], actions: Glob
|
|||||||
if (newLastMessage) {
|
if (newLastMessage) {
|
||||||
global = updateChatLastMessage(global, chatId, newLastMessage, true);
|
global = updateChatLastMessage(global, chatId, newLastMessage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chat.topics?.[id]) {
|
||||||
|
global = deleteTopic(global, chatId, id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.requestChatUpdate({ chatId });
|
actions.requestChatUpdate({ chatId });
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import { MAIN_THREAD_ID } from '../../../api/types';
|
|||||||
import {
|
import {
|
||||||
exitMessageSelectMode, replaceThreadParam, updateCurrentMessageList,
|
exitMessageSelectMode, replaceThreadParam, updateCurrentMessageList,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { selectCurrentMessageList } from '../../selectors';
|
import { selectChat, selectCurrentMessageList } from '../../selectors';
|
||||||
import { closeLocalTextSearch } from './localSearch';
|
import { closeLocalTextSearch } from './localSearch';
|
||||||
|
|
||||||
addActionHandler('openChat', (global, actions, payload) => {
|
addActionHandler('openChat', (global, actions, payload) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
threadId = MAIN_THREAD_ID,
|
threadId,
|
||||||
type = 'thread',
|
type = 'thread',
|
||||||
shouldReplaceHistory = false,
|
shouldReplaceHistory = false,
|
||||||
} = payload;
|
} = payload;
|
||||||
@ -35,7 +35,7 @@ addActionHandler('openChat', (global, actions, payload) => {
|
|||||||
|| currentMessageList.type !== type
|
|| currentMessageList.type !== type
|
||||||
)) {
|
)) {
|
||||||
if (id) {
|
if (id) {
|
||||||
global = replaceThreadParam(global, id, threadId, 'replyStack', []);
|
global = replaceThreadParam(global, id, threadId || MAIN_THREAD_ID, 'replyStack', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
global = exitMessageSelectMode(global);
|
global = exitMessageSelectMode(global);
|
||||||
@ -54,10 +54,16 @@ addActionHandler('openChat', (global, actions, payload) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id !== global.forumPanelChatId) {
|
if (id && id !== global.forumPanelChatId) {
|
||||||
actions.closeForumPanel();
|
actions.closeForumPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id && !threadId) {
|
||||||
|
const chat = selectChat(global, id);
|
||||||
|
// Prevent chat opening on forum click
|
||||||
|
if (chat?.isForum) return global;
|
||||||
|
}
|
||||||
|
|
||||||
return updateCurrentMessageList(global, id, threadId, type, shouldReplaceHistory);
|
return updateCurrentMessageList(global, id, threadId, type, shouldReplaceHistory);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
|
|
||||||
import type { ApiError, ApiNotification } from '../../../api/types';
|
import type { ApiError, ApiNotification } from '../../../api/types';
|
||||||
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { APP_VERSION, DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT } from '../../../config';
|
import { APP_VERSION, DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT } from '../../../config';
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import getReadableErrorText from '../../../util/getReadableErrorText';
|
import getReadableErrorText from '../../../util/getReadableErrorText';
|
||||||
import { selectChatMessage, selectCurrentMessageList, selectIsTrustedBot } from '../../selectors';
|
import {
|
||||||
|
selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectIsTrustedBot,
|
||||||
|
} from '../../selectors';
|
||||||
import generateIdFor from '../../../util/generateIdFor';
|
import generateIdFor from '../../../util/generateIdFor';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
|
|
||||||
@ -443,6 +446,10 @@ addActionHandler('updateLastRenderedCustomEmojis', (global, actions, payload) =>
|
|||||||
addActionHandler('openCreateTopicPanel', (global, actions, payload) => {
|
addActionHandler('openCreateTopicPanel', (global, actions, payload) => {
|
||||||
const { chatId } = payload;
|
const { chatId } = payload;
|
||||||
|
|
||||||
|
// Topic panel can be opened only if there is a selected chat
|
||||||
|
const currentChat = selectCurrentChat(global);
|
||||||
|
if (!currentChat) actions.openChat({ id: chatId, threadId: MAIN_THREAD_ID });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
createTopicPanel: {
|
createTopicPanel: {
|
||||||
@ -461,6 +468,10 @@ addActionHandler('closeCreateTopicPanel', (global) => {
|
|||||||
addActionHandler('openEditTopicPanel', (global, actions, payload) => {
|
addActionHandler('openEditTopicPanel', (global, actions, payload) => {
|
||||||
const { chatId, topicId } = payload;
|
const { chatId, topicId } = payload;
|
||||||
|
|
||||||
|
// Topic panel can be opened only if there is a selected chat
|
||||||
|
const currentChat = selectCurrentChat(global);
|
||||||
|
if (!currentChat) actions.openChat({ id: chatId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
editTopicPanel: {
|
editTopicPanel: {
|
||||||
|
|||||||
@ -250,8 +250,7 @@ export function deleteChatMessages(
|
|||||||
global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'pinnedIds', mainPinnedIds);
|
global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'pinnedIds', mainPinnedIds);
|
||||||
|
|
||||||
if (threadInfo && newMessageCount !== undefined) {
|
if (threadInfo && newMessageCount !== undefined) {
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'threadInfo', {
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
...threadInfo,
|
|
||||||
messagesCount: newMessageCount,
|
messagesCount: newMessageCount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -581,7 +580,7 @@ export function updateThreadUnreadFromForwardedMessage(
|
|||||||
global = replaceThreadParam(global, chatId, channelPostId, 'threadInfo', {
|
global = replaceThreadParam(global, chatId, channelPostId, 'threadInfo', {
|
||||||
...threadInfoOld,
|
...threadInfoOld,
|
||||||
lastMessageId,
|
lastMessageId,
|
||||||
messagesCount: threadInfoOld.messagesCount + (isDeleting ? -1 : 1),
|
messagesCount: (threadInfoOld.messagesCount || 0) + (isDeleting ? -1 : 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,6 +173,15 @@ export function selectReplyStack(global: GlobalState, chatId: string, threadId:
|
|||||||
return selectThreadParam(global, chatId, threadId, 'replyStack');
|
return selectThreadParam(global, chatId, threadId, 'replyStack');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectThreadMessagesCount(global: GlobalState, chatId: string, threadId: number) {
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
|
if (!chat || !threadInfo || threadInfo.messagesCount === undefined) return undefined;
|
||||||
|
// In forum topics first message is ignored, but not in General
|
||||||
|
if (chat.isForum && threadId !== GENERAL_TOPIC_ID) return threadInfo.messagesCount - 1;
|
||||||
|
return threadInfo.messagesCount;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectThreadOriginChat(global: GlobalState, chatId: string, threadId: number) {
|
export function selectThreadOriginChat(global: GlobalState, chatId: string, threadId: number) {
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
return selectChat(global, chatId);
|
return selectChat(global, chatId);
|
||||||
|
|||||||
@ -349,6 +349,7 @@ export type GlobalState = {
|
|||||||
animatedEmojiEffects?: ApiStickerSet;
|
animatedEmojiEffects?: ApiStickerSet;
|
||||||
genericEmojiEffects?: ApiStickerSet;
|
genericEmojiEffects?: ApiStickerSet;
|
||||||
premiumGifts?: ApiStickerSet;
|
premiumGifts?: ApiStickerSet;
|
||||||
|
defaultTopicIconsId?: string;
|
||||||
emojiKeywords: Partial<Record<LangCode, EmojiKeywords>>;
|
emojiKeywords: Partial<Record<LangCode, EmojiKeywords>>;
|
||||||
|
|
||||||
gifs: {
|
gifs: {
|
||||||
@ -1305,6 +1306,7 @@ export interface ActionPayloads {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadPremiumGifts: never;
|
loadPremiumGifts: never;
|
||||||
|
loadDefaultTopicIcons: never;
|
||||||
loadPremiumStickers: {
|
loadPremiumStickers: {
|
||||||
hash?: string;
|
hash?: string;
|
||||||
};
|
};
|
||||||
@ -1335,6 +1337,7 @@ export interface ActionPayloads {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
title: string;
|
title: string;
|
||||||
iconColor?: number;
|
iconColor?: number;
|
||||||
|
iconEmojiId?: string;
|
||||||
};
|
};
|
||||||
loadTopics: {
|
loadTopics: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -1343,6 +1346,7 @@ export interface ActionPayloads {
|
|||||||
loadTopicById: {
|
loadTopicById: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
topicId: number;
|
topicId: number;
|
||||||
|
shouldCloseChatOnError?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteTopic: {
|
deleteTopic: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user