Chat: Implement folder badges (#444)
This commit is contained in:
parent
e169b09d0b
commit
6d6249487e
@ -425,6 +425,7 @@ export function buildApiChatFolder(filter: GramJs.DialogFilter | GramJs.DialogFi
|
|||||||
hasMyInvites: filter.hasMyInvites,
|
hasMyInvites: filter.hasMyInvites,
|
||||||
isChatList: true,
|
isChatList: true,
|
||||||
noTitleAnimations: filter.titleNoanimate,
|
noTitleAnimations: filter.titleNoanimate,
|
||||||
|
color: filter.color,
|
||||||
title: buildApiFormattedText(filter.title),
|
title: buildApiFormattedText(filter.title),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -438,6 +439,7 @@ export function buildApiChatFolder(filter: GramJs.DialogFilter | GramJs.DialogFi
|
|||||||
pinnedChatIds: filter.pinnedPeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
pinnedChatIds: filter.pinnedPeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||||
includedChatIds: filter.includePeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
includedChatIds: filter.includePeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||||
excludedChatIds: filter.excludePeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
excludedChatIds: filter.excludePeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||||
|
color: filter.color,
|
||||||
title: buildApiFormattedText(filter.title),
|
title: buildApiFormattedText(filter.title),
|
||||||
noTitleAnimations: filter.titleNoanimate,
|
noTitleAnimations: filter.titleNoanimate,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -296,6 +296,7 @@ export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFi
|
|||||||
groups,
|
groups,
|
||||||
channels,
|
channels,
|
||||||
bots,
|
bots,
|
||||||
|
color,
|
||||||
excludeArchived,
|
excludeArchived,
|
||||||
excludeMuted,
|
excludeMuted,
|
||||||
excludeRead,
|
excludeRead,
|
||||||
@ -321,6 +322,7 @@ export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFi
|
|||||||
return new GramJs.DialogFilterChatlist({
|
return new GramJs.DialogFilterChatlist({
|
||||||
id: folder.id,
|
id: folder.id,
|
||||||
title: buildInputTextWithEntities(folder.title),
|
title: buildInputTextWithEntities(folder.title),
|
||||||
|
color,
|
||||||
emoticon: emoticon || undefined,
|
emoticon: emoticon || undefined,
|
||||||
pinnedPeers,
|
pinnedPeers,
|
||||||
includePeers,
|
includePeers,
|
||||||
@ -337,6 +339,7 @@ export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFi
|
|||||||
nonContacts: nonContacts || undefined,
|
nonContacts: nonContacts || undefined,
|
||||||
groups: groups || undefined,
|
groups: groups || undefined,
|
||||||
bots: bots || undefined,
|
bots: bots || undefined,
|
||||||
|
color,
|
||||||
excludeArchived: excludeArchived || undefined,
|
excludeArchived: excludeArchived || undefined,
|
||||||
excludeMuted: excludeMuted || undefined,
|
excludeMuted: excludeMuted || undefined,
|
||||||
excludeRead: excludeRead || undefined,
|
excludeRead: excludeRead || undefined,
|
||||||
|
|||||||
@ -1076,7 +1076,7 @@ export async function fetchChatFolders() {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const { filters } = result;
|
const { filters, tagsEnabled: areTagsEnabled } = result;
|
||||||
|
|
||||||
const defaultFolderPosition = filters.findIndex((folder) => folder instanceof GramJs.DialogFilterDefault);
|
const defaultFolderPosition = filters.findIndex((folder) => folder instanceof GramJs.DialogFilterDefault);
|
||||||
const dialogFilters = filters.filter(isChatFolder);
|
const dialogFilters = filters.filter(isChatFolder);
|
||||||
@ -1090,6 +1090,7 @@ export async function fetchChatFolders() {
|
|||||||
.map(buildApiChatFolder), 'id',
|
.map(buildApiChatFolder), 'id',
|
||||||
) as Record<number, ApiChatFolder>,
|
) as Record<number, ApiChatFolder>,
|
||||||
orderedIds,
|
orderedIds,
|
||||||
|
areTagsEnabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1182,6 +1183,12 @@ export function sortChatFolders(ids: number[]) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleDialogFilterTags(isEnabled: boolean) {
|
||||||
|
return invokeRequest(new GramJs.messages.ToggleDialogFilterTags({
|
||||||
|
enabled: isEnabled,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export async function toggleDialogUnread({
|
export async function toggleDialogUnread({
|
||||||
chat, hasUnreadMark,
|
chat, hasUnreadMark,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@ -223,6 +223,7 @@ export interface ApiChatFolder {
|
|||||||
groups?: true;
|
groups?: true;
|
||||||
channels?: true;
|
channels?: true;
|
||||||
bots?: true;
|
bots?: true;
|
||||||
|
color?: number;
|
||||||
excludeMuted?: true;
|
excludeMuted?: true;
|
||||||
excludeRead?: true;
|
excludeRead?: true;
|
||||||
excludeArchived?: true;
|
excludeArchived?: true;
|
||||||
|
|||||||
@ -364,6 +364,10 @@
|
|||||||
"BlockedUsersBlockUser" = "Block User...";
|
"BlockedUsersBlockUser" = "Block User...";
|
||||||
"FilterChatTypes" = "Chat types";
|
"FilterChatTypes" = "Chat types";
|
||||||
"FilterChats" = "Chats";
|
"FilterChats" = "Chats";
|
||||||
|
"FilterColorTitle" = "Folder Color";
|
||||||
|
"FilterColorHint" = "This color will be used for the folder's tag in the chat list";
|
||||||
|
"ShowFolderTags" = "Show Folder Tags";
|
||||||
|
"ShowFolderTagsHint" = "Display folder names for each chat in the chat list.";
|
||||||
"FilterIncludeInfo" = "Choose chats or types of chats that will appear in this folder.";
|
"FilterIncludeInfo" = "Choose chats or types of chats that will appear in this folder.";
|
||||||
"FilterNameHint" = "Folder name";
|
"FilterNameHint" = "Folder name";
|
||||||
"FilterInclude" = "Included Chats";
|
"FilterInclude" = "Included Chats";
|
||||||
|
|||||||
@ -30,6 +30,7 @@ type OwnProps = {
|
|||||||
topics?: Record<number, ApiTopic>;
|
topics?: Record<number, ApiTopic>;
|
||||||
renderLastMessage: () => React.ReactNode;
|
renderLastMessage: () => React.ReactNode;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
|
noForumTitle?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NO_CORNER_THRESHOLD = Number(REM);
|
const NO_CORNER_THRESHOLD = Number(REM);
|
||||||
@ -40,6 +41,7 @@ const ChatForumLastMessage: FC<OwnProps> = ({
|
|||||||
topics,
|
topics,
|
||||||
renderLastMessage,
|
renderLastMessage,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
|
noForumTitle,
|
||||||
}) => {
|
}) => {
|
||||||
const { openThread } = getActions();
|
const { openThread } = getActions();
|
||||||
|
|
||||||
@ -102,53 +104,63 @@ const ChatForumLastMessage: FC<OwnProps> = ({
|
|||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
style={overwrittenWidth ? `--overwritten-width: ${overwrittenWidth}px` : undefined}
|
style={overwrittenWidth ? `--overwritten-width: ${overwrittenWidth}px` : undefined}
|
||||||
>
|
>
|
||||||
{lastActiveTopic && (
|
{
|
||||||
<div className={styles.titleRow}>
|
!noForumTitle && (
|
||||||
<div
|
<>
|
||||||
className={buildClassName(
|
{lastActiveTopic && (
|
||||||
styles.mainColumn,
|
<div className={styles.titleRow}>
|
||||||
lastActiveTopic.unreadCount && styles.unread,
|
<div
|
||||||
)}
|
className={buildClassName(
|
||||||
ref={mainColumnRef}
|
styles.mainColumn,
|
||||||
onClick={handleOpenTopicClick}
|
lastActiveTopic.unreadCount && styles.unread,
|
||||||
onMouseDown={handleOpenTopicMouseDown}
|
)}
|
||||||
>
|
ref={mainColumnRef}
|
||||||
<TopicIcon
|
onClick={handleOpenTopicClick}
|
||||||
topic={lastActiveTopic}
|
onMouseDown={handleOpenTopicMouseDown}
|
||||||
observeIntersection={observeIntersection}
|
>
|
||||||
/>
|
<TopicIcon
|
||||||
<div className={styles.title}>{renderText(lastActiveTopic.title)}</div>
|
topic={lastActiveTopic}
|
||||||
{!overwrittenWidth && isReversedCorner && (
|
observeIntersection={observeIntersection}
|
||||||
<div className={styles.afterWrapper}>
|
/>
|
||||||
<div className={styles.after} />
|
<div className={styles.title}>{renderText(lastActiveTopic.title)}</div>
|
||||||
|
{!overwrittenWidth && isReversedCorner && (
|
||||||
|
<div className={styles.afterWrapper}>
|
||||||
|
<div className={styles.after} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.otherColumns}>
|
||||||
|
{otherTopics.map((topic) => (
|
||||||
|
<div
|
||||||
|
className={buildClassName(
|
||||||
|
styles.otherColumn, topic.unreadCount && styles.unread,
|
||||||
|
)}
|
||||||
|
key={topic.id}
|
||||||
|
>
|
||||||
|
<TopicIcon
|
||||||
|
topic={topic}
|
||||||
|
className={styles.otherColumnIcon}
|
||||||
|
observeIntersection={observeIntersection}
|
||||||
|
/>
|
||||||
|
<span className={styles.otherColumnTitle}>{renderText(topic.title)}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.ellipsis} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
{!lastActiveTopic && (
|
||||||
|
<div className={buildClassName(styles.titleRow, styles.loading)}>
|
||||||
<div className={styles.otherColumns}>
|
{lang('Loading')}
|
||||||
{otherTopics.map((topic) => (
|
|
||||||
<div
|
|
||||||
className={buildClassName(
|
|
||||||
styles.otherColumn, topic.unreadCount && styles.unread,
|
|
||||||
)}
|
|
||||||
key={topic.id}
|
|
||||||
>
|
|
||||||
<TopicIcon
|
|
||||||
topic={topic}
|
|
||||||
className={styles.otherColumnIcon}
|
|
||||||
observeIntersection={observeIntersection}
|
|
||||||
/>
|
|
||||||
<span className={styles.otherColumnTitle}>{renderText(topic.title)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>
|
</>
|
||||||
|
)
|
||||||
<div className={styles.ellipsis} />
|
}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!lastActiveTopic && <div className={buildClassName(styles.titleRow, styles.loading)}>{lang('Loading')}</div>}
|
|
||||||
<div
|
<div
|
||||||
className={buildClassName(styles.lastMessage, lastActiveTopic?.unreadCount && styles.unread)}
|
className={buildClassName(styles.lastMessage, lastActiveTopic?.unreadCount && !noForumTitle && styles.unread)}
|
||||||
ref={lastMessageRef}
|
ref={lastMessageRef}
|
||||||
onClick={handleOpenTopicClick}
|
onClick={handleOpenTopicClick}
|
||||||
onMouseDown={handleOpenTopicMouseDown}
|
onMouseDown={handleOpenTopicMouseDown}
|
||||||
|
|||||||
@ -335,6 +335,13 @@
|
|||||||
line-height: initial;
|
line-height: initial;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-tags {
|
||||||
|
line-height: 1.25rem;
|
||||||
|
.info-row {
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[dir="rtl"] {
|
&[dir="rtl"] {
|
||||||
@ -456,6 +463,11 @@
|
|||||||
color: var(--color-chat-active);
|
color: var(--color-chat-active);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ChatTags {
|
||||||
|
color: var(--color-chat-active);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
|
ApiChatFolder,
|
||||||
ApiDraft,
|
ApiDraft,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
@ -33,6 +34,7 @@ import {
|
|||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectDraft,
|
selectDraft,
|
||||||
selectIsCurrentUserFrozen,
|
selectIsCurrentUserFrozen,
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
selectIsForumPanelClosed,
|
selectIsForumPanelClosed,
|
||||||
selectIsForumPanelOpen,
|
selectIsForumPanelOpen,
|
||||||
selectMonoforumChannel,
|
selectMonoforumChannel,
|
||||||
@ -52,6 +54,7 @@ import {
|
|||||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { isUserId } from '../../../util/entities/ids';
|
import { isUserId } from '../../../util/entities/ids';
|
||||||
|
import { getChatFolderIds } from '../../../util/folderManager';
|
||||||
import { createLocationHash } from '../../../util/routing';
|
import { createLocationHash } from '../../../util/routing';
|
||||||
|
|
||||||
import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
|
import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
|
||||||
@ -75,9 +78,9 @@ import ChatFolderModal from '../ChatFolderModal.async';
|
|||||||
import MuteChatModal from '../MuteChatModal.async';
|
import MuteChatModal from '../MuteChatModal.async';
|
||||||
import ChatBadge from './ChatBadge';
|
import ChatBadge from './ChatBadge';
|
||||||
import ChatCallStatus from './ChatCallStatus';
|
import ChatCallStatus from './ChatCallStatus';
|
||||||
|
import ChatTags from './ChatTags';
|
||||||
|
|
||||||
import './Chat.scss';
|
import './Chat.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
@ -91,6 +94,7 @@ type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onDragEnter?: (chatId: string) => void;
|
onDragEnter?: (chatId: string) => void;
|
||||||
|
withTags?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -118,6 +122,11 @@ type StateProps = {
|
|||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
isSynced?: boolean;
|
isSynced?: boolean;
|
||||||
isAccountFrozen?: boolean;
|
isAccountFrozen?: boolean;
|
||||||
|
folderIds?: number[];
|
||||||
|
orderedIds?: number[];
|
||||||
|
chatFoldersById?: Record<number, ApiChatFolder>;
|
||||||
|
activeChatFolder?: number;
|
||||||
|
areTagsEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Chat: FC<OwnProps & StateProps> = ({
|
const Chat: FC<OwnProps & StateProps> = ({
|
||||||
@ -157,6 +166,12 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
isSynced,
|
isSynced,
|
||||||
onDragEnter,
|
onDragEnter,
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
|
folderIds,
|
||||||
|
orderedIds,
|
||||||
|
chatFoldersById,
|
||||||
|
activeChatFolder,
|
||||||
|
areTagsEnabled,
|
||||||
|
withTags,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -184,6 +199,8 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useEnsureMessage(isSavedDialog ? currentUserId : chatId, lastMessageId, lastMessage);
|
useEnsureMessage(isSavedDialog ? currentUserId : chatId, lastMessageId, lastMessage);
|
||||||
|
|
||||||
|
const shouldRenderTags = areTagsEnabled && withTags && folderIds && folderIds.length > 1;
|
||||||
|
|
||||||
const { renderSubtitle, ref } = useChatListEntry({
|
const { renderSubtitle, ref } = useChatListEntry({
|
||||||
chat,
|
chat,
|
||||||
chatId,
|
chatId,
|
||||||
@ -200,6 +217,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isPreview,
|
isPreview,
|
||||||
topics,
|
topics,
|
||||||
|
noForumTitle: shouldRenderTags,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
||||||
@ -388,7 +406,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
|
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="info">
|
<div className={buildClassName('info', shouldRenderTags && 'has-tags')}>
|
||||||
<div className="info-row">
|
<div className="info-row">
|
||||||
<FullNameTitle
|
<FullNameTitle
|
||||||
peer={isMonoforum ? monoforumChannel! : peer}
|
peer={isMonoforum ? monoforumChannel! : peer}
|
||||||
@ -423,6 +441,14 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{shouldRenderTags && (
|
||||||
|
<ChatTags
|
||||||
|
folderIds={folderIds}
|
||||||
|
orderedIds={orderedIds}
|
||||||
|
chatFoldersById={chatFoldersById}
|
||||||
|
activeChatFolder={activeChatFolder}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{shouldRenderDeleteModal && (
|
{shouldRenderDeleteModal && (
|
||||||
<DeleteChatModal
|
<DeleteChatModal
|
||||||
@ -465,6 +491,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const folderIds = getChatFolderIds(chatId);
|
||||||
|
const { areTagsEnabled } = global.chatFolders;
|
||||||
|
const isPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
const lastMessageId = previewMessageId || selectChatLastMessageId(global, chatId, isSavedDialog ? 'saved' : 'all');
|
const lastMessageId = previewMessageId || selectChatLastMessageId(global, chatId, isSavedDialog ? 'saved' : 'all');
|
||||||
const lastMessage = previewMessageId
|
const lastMessage = previewMessageId
|
||||||
? selectChatMessage(global, chatId, previewMessageId)
|
? selectChatMessage(global, chatId, previewMessageId)
|
||||||
@ -497,6 +527,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const monoforumChannel = selectMonoforumChannel(global, chatId);
|
const monoforumChannel = selectMonoforumChannel(global, chatId);
|
||||||
|
|
||||||
|
const activeChatFolder = selectTabState(global).activeChatFolder;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
isMuted: getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id)),
|
isMuted: getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id)),
|
||||||
@ -524,6 +556,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
lastMessageStory,
|
lastMessageStory,
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
monoforumChannel,
|
monoforumChannel,
|
||||||
|
folderIds,
|
||||||
|
orderedIds: global.chatFolders.orderedIds,
|
||||||
|
activeChatFolder,
|
||||||
|
chatFoldersById: global.chatFolders.byId,
|
||||||
|
areTagsEnabled: areTagsEnabled && isPremium,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Chat));
|
)(Chat));
|
||||||
|
|||||||
@ -368,6 +368,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
archiveSettings={archiveSettings}
|
archiveSettings={archiveSettings}
|
||||||
sessions={sessions}
|
sessions={sessions}
|
||||||
isAccountFrozen={isAccountFrozen}
|
isAccountFrozen={isAccountFrozen}
|
||||||
|
withTags
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,7 @@ type OwnProps = {
|
|||||||
isAccountFrozen?: boolean;
|
isAccountFrozen?: boolean;
|
||||||
isMainList?: boolean;
|
isMainList?: boolean;
|
||||||
foldersDispatch?: FolderEditDispatch;
|
foldersDispatch?: FolderEditDispatch;
|
||||||
|
withTags?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const INTERSECTION_THROTTLE = 200;
|
const INTERSECTION_THROTTLE = 200;
|
||||||
@ -72,6 +73,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
isMainList,
|
isMainList,
|
||||||
foldersDispatch,
|
foldersDispatch,
|
||||||
|
withTags,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -238,6 +240,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
offsetTop={offsetTop}
|
offsetTop={offsetTop}
|
||||||
observeIntersection={observe}
|
observeIntersection={observe}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
|
withTags={withTags}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
33
src/components/left/main/ChatTags.module.scss
Normal file
33
src/components/left/main/ChatTags.module.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
min-width: 1.5rem;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
|
font-size: 0.5625rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 0.75rem;
|
||||||
|
color: var(--accent-color);
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
background-color: var(--accent-background-color);
|
||||||
|
|
||||||
|
:global(.emoji-small) {
|
||||||
|
width: 0.5625rem;
|
||||||
|
height: 0.5625rem;
|
||||||
|
margin-top: -0.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&ColorMore {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/components/left/main/ChatTags.tsx
Normal file
74
src/components/left/main/ChatTags.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { ApiChatFolder } from '../../../api/types';
|
||||||
|
|
||||||
|
import { ALL_FOLDER_ID } from '../../../config';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { getApiPeerColorClass } from '../../common/helpers/peerColor';
|
||||||
|
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import styles from './ChatTags.module.scss';
|
||||||
|
|
||||||
|
const MAX_VISIBLE_TAGS = 3;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
folderIds?: number[];
|
||||||
|
orderedIds?: number[];
|
||||||
|
chatFoldersById?: Record<number, ApiChatFolder>;
|
||||||
|
activeChatFolder?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChatTags = ({
|
||||||
|
folderIds,
|
||||||
|
orderedIds,
|
||||||
|
chatFoldersById,
|
||||||
|
activeChatFolder,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const activeFolderId = activeChatFolder !== undefined && orderedIds ? orderedIds[activeChatFolder] : undefined;
|
||||||
|
|
||||||
|
const orderedFolderIds = useMemo(() => orderedIds?.filter((id) => {
|
||||||
|
const isFolder = folderIds?.includes(id);
|
||||||
|
const isActive = id === activeFolderId;
|
||||||
|
const isAll = id === ALL_FOLDER_ID;
|
||||||
|
|
||||||
|
const folder = chatFoldersById?.[id];
|
||||||
|
const hasColor = folder?.color !== undefined && folder.color !== -1;
|
||||||
|
|
||||||
|
return isFolder && !isActive && !isAll && hasColor;
|
||||||
|
}) || [], [orderedIds, folderIds, activeFolderId, chatFoldersById]);
|
||||||
|
|
||||||
|
const visibleFolderIds = orderedFolderIds.slice(0, MAX_VISIBLE_TAGS);
|
||||||
|
const remainingCount = orderedFolderIds.length - visibleFolderIds.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
{visibleFolderIds.map((folderId) => {
|
||||||
|
const folder = chatFoldersById?.[folderId];
|
||||||
|
return folder && (
|
||||||
|
<div
|
||||||
|
key={folder.id}
|
||||||
|
className={buildClassName(
|
||||||
|
'ChatTags',
|
||||||
|
styles.tag,
|
||||||
|
getApiPeerColorClass({ color: folder.color }),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderTextWithEntities({
|
||||||
|
text: folder.title.text,
|
||||||
|
entities: folder.title.entities,
|
||||||
|
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{remainingCount > 0 && (
|
||||||
|
<div className={`ChatTags ${styles.tag} ${styles.tagColorMore}`}>
|
||||||
|
+
|
||||||
|
{remainingCount}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ChatTags);
|
||||||
@ -52,6 +52,7 @@ export default function useChatListEntry({
|
|||||||
isTopic,
|
isTopic,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isPreview,
|
isPreview,
|
||||||
|
noForumTitle,
|
||||||
}: {
|
}: {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
topics?: Record<number, ApiTopic>;
|
topics?: Record<number, ApiTopic>;
|
||||||
@ -70,6 +71,7 @@ export default function useChatListEntry({
|
|||||||
animationType: ChatAnimationTypes;
|
animationType: ChatAnimationTypes;
|
||||||
orderDiff: number;
|
orderDiff: number;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
|
noForumTitle?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
@ -153,6 +155,7 @@ export default function useChatListEntry({
|
|||||||
renderLastMessage={renderLastMessageOrTyping}
|
renderLastMessage={renderLastMessageOrTyping}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
topics={topics}
|
topics={topics}
|
||||||
|
noForumTitle={noForumTitle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,3 +116,82 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
min-height: 3rem;
|
||||||
|
padding: 0 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-item {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border: 0.125rem solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
color: var(--accent-color);
|
||||||
|
|
||||||
|
background-color: currentColor;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
transition: border 100ms ease-in-out, outline 100ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-item-disabled {
|
||||||
|
--accent-color: var(--color-topic-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-selected-color {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-item-none {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--color-topic-grey);
|
||||||
|
|
||||||
|
.color-picker-item-none-icon {
|
||||||
|
color: var(--color-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-item:not(.color-picker-item-hover-disabled) {
|
||||||
|
&.color-picker-item-active,
|
||||||
|
&:focus,
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
border: 0.125rem solid var(--color-background);
|
||||||
|
outline: 0.125rem solid currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-folders-lock-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
padding: 1.3125rem;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-folders-title {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|||||||
@ -12,13 +12,16 @@ import type {
|
|||||||
} from '../../../../hooks/reducers/useFoldersReducer';
|
} from '../../../../hooks/reducers/useFoldersReducer';
|
||||||
|
|
||||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||||
import { selectCanShareFolder } from '../../../../global/selectors';
|
import { selectCanShareFolder, selectIsCurrentUserPremium } from '../../../../global/selectors';
|
||||||
import { selectCurrentLimit } from '../../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../../global/selectors/limits';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { isUserId } from '../../../../util/entities/ids';
|
import { isUserId } from '../../../../util/entities/ids';
|
||||||
import { findIntersectionWithSet } from '../../../../util/iteratees';
|
import { findIntersectionWithSet } from '../../../../util/iteratees';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||||
import { CUSTOM_PEER_EXCLUDED_CHAT_TYPES, CUSTOM_PEER_INCLUDED_CHAT_TYPES } from '../../../../util/objects/customPeer';
|
import { CUSTOM_PEER_EXCLUDED_CHAT_TYPES, CUSTOM_PEER_INCLUDED_CHAT_TYPES } from '../../../../util/objects/customPeer';
|
||||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
|
import { getApiPeerColorClass } from '../../../common/helpers/peerColor';
|
||||||
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
import { selectChatFilters } from '../../../../hooks/reducers/useFoldersReducer';
|
import { selectChatFilters } from '../../../../hooks/reducers/useFoldersReducer';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
@ -55,12 +58,15 @@ type StateProps = {
|
|||||||
maxInviteLinks: number;
|
maxInviteLinks: number;
|
||||||
maxChatLists: number;
|
maxChatLists: number;
|
||||||
chatListCount: number;
|
chatListCount: number;
|
||||||
|
isCurrentUserPremium: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUBMIT_TIMEOUT = 500;
|
const SUBMIT_TIMEOUT = 500;
|
||||||
|
|
||||||
const INITIAL_CHATS_LIMIT = 5;
|
const INITIAL_CHATS_LIMIT = 5;
|
||||||
|
|
||||||
|
const FOLDER_COLORS = [0, 1, 2, 3, 4, 5, 6];
|
||||||
|
|
||||||
export const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
export const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
||||||
export const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
export const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
||||||
|
|
||||||
@ -83,11 +89,13 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
maxChatLists,
|
maxChatLists,
|
||||||
chatListCount,
|
chatListCount,
|
||||||
onSaveFolder,
|
onSaveFolder,
|
||||||
|
isCurrentUserPremium,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadChatlistInvites,
|
loadChatlistInvites,
|
||||||
openLimitReachedModal,
|
openLimitReachedModal,
|
||||||
showNotification,
|
showNotification,
|
||||||
|
openPremiumModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const isCreating = state.mode === 'create';
|
const isCreating = state.mode === 'create';
|
||||||
@ -346,6 +354,73 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="settings-item pt-3">
|
||||||
|
<h4 className="settings-item-header mb-3 color-picker-title" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('FilterColorTitle')}
|
||||||
|
<div className={buildClassName(
|
||||||
|
'color-picker-selected-color',
|
||||||
|
isCurrentUserPremium && state.folder.color !== undefined && state.folder.color !== -1
|
||||||
|
? getApiPeerColorClass({ color: state.folder.color })
|
||||||
|
: 'color-picker-item-disabled',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderTextWithEntities({
|
||||||
|
text: state.folder.title.text,
|
||||||
|
entities: state.folder.title.entities,
|
||||||
|
noCustomEmojiPlayback: state.folder.noTitleAnimations,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<div className="color-picker">
|
||||||
|
{FOLDER_COLORS.map((color) => (
|
||||||
|
<button
|
||||||
|
key={color}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (!isCurrentUserPremium) {
|
||||||
|
openPremiumModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: 'setColor', payload: color });
|
||||||
|
}}
|
||||||
|
className={buildClassName(
|
||||||
|
'color-picker-item',
|
||||||
|
getApiPeerColorClass({ color }),
|
||||||
|
!isCurrentUserPremium && 'color-picker-item-hover-disabled',
|
||||||
|
color === state.folder.color && isCurrentUserPremium && 'color-picker-item-active',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (!isCurrentUserPremium) {
|
||||||
|
openPremiumModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: 'setColor', payload: undefined });
|
||||||
|
}}
|
||||||
|
className={buildClassName(
|
||||||
|
'color-picker-item',
|
||||||
|
'color-picker-item-none',
|
||||||
|
(state.folder.color === undefined || state.folder.color === -1 || !isCurrentUserPremium)
|
||||||
|
&& 'color-picker-item-active',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isCurrentUserPremium ? (
|
||||||
|
<Icon name="close" className="color-picker-item-none-icon" />
|
||||||
|
) : (
|
||||||
|
<Icon name="lock-badge" className="color-picker-item-none-icon" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="settings-item-description mb-0" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('FilterColorHint')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="settings-item pt-3">
|
<div className="settings-item pt-3">
|
||||||
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{lang('FolderLinkScreen.Title')}
|
{lang('FolderLinkScreen.Title')}
|
||||||
@ -401,6 +476,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { byId, invites } = global.chatFolders;
|
const { byId, invites } = global.chatFolders;
|
||||||
const chatListCount = Object.values(byId).reduce((acc, el) => acc + (el.isChatList ? 1 : 0), 0);
|
const chatListCount = Object.values(byId).reduce((acc, el) => acc + (el.isChatList ? 1 : 0), 0);
|
||||||
|
|
||||||
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loadedActiveChatIds: listIds.active,
|
loadedActiveChatIds: listIds.active,
|
||||||
loadedArchivedChatIds: listIds.archived,
|
loadedArchivedChatIds: listIds.archived,
|
||||||
@ -409,6 +486,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
maxInviteLinks: selectCurrentLimit(global, 'chatlistInvites'),
|
maxInviteLinks: selectCurrentLimit(global, 'chatlistInvites'),
|
||||||
maxChatLists: selectCurrentLimit(global, 'chatlistJoined'),
|
maxChatLists: selectCurrentLimit(global, 'chatlistJoined'),
|
||||||
chatListCount,
|
chatListCount,
|
||||||
|
isCurrentUserPremium,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(SettingsFoldersEdit));
|
)(SettingsFoldersEdit));
|
||||||
|
|||||||
@ -10,10 +10,12 @@ import { ALL_FOLDER_ID, STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config'
|
|||||||
import { getFolderDescriptionText } from '../../../../global/helpers';
|
import { getFolderDescriptionText } from '../../../../global/helpers';
|
||||||
import { selectIsCurrentUserPremium } from '../../../../global/selectors';
|
import { selectIsCurrentUserPremium } from '../../../../global/selectors';
|
||||||
import { selectCurrentLimit } from '../../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../../global/selectors/limits';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { isBetween } from '../../../../util/math';
|
import { isBetween } from '../../../../util/math';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||||
import { throttle } from '../../../../util/schedulers';
|
import { throttle } from '../../../../util/schedulers';
|
||||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
|
import { getApiPeerColorClass } from '../../../common/helpers/peerColor';
|
||||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManager';
|
import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManager';
|
||||||
@ -24,6 +26,7 @@ import usePreviousDeprecated from '../../../../hooks/usePreviousDeprecated';
|
|||||||
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
|
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../../common/icons/Icon';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
|
import Checkbox from '../../../ui/Checkbox';
|
||||||
import Draggable from '../../../ui/Draggable';
|
import Draggable from '../../../ui/Draggable';
|
||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../../ui/ListItem';
|
||||||
import Loading from '../../../ui/Loading';
|
import Loading from '../../../ui/Loading';
|
||||||
@ -41,6 +44,7 @@ type StateProps = {
|
|||||||
recommendedChatFolders?: ApiChatFolder[];
|
recommendedChatFolders?: ApiChatFolder[];
|
||||||
maxFolders: number;
|
maxFolders: number;
|
||||||
isPremium?: boolean;
|
isPremium?: boolean;
|
||||||
|
areTagsEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SortState = {
|
type SortState = {
|
||||||
@ -62,6 +66,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
isPremium,
|
isPremium,
|
||||||
recommendedChatFolders,
|
recommendedChatFolders,
|
||||||
maxFolders,
|
maxFolders,
|
||||||
|
areTagsEnabled,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadRecommendedChatFolders,
|
loadRecommendedChatFolders,
|
||||||
@ -69,6 +74,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
openLimitReachedModal,
|
openLimitReachedModal,
|
||||||
openDeleteChatFolderModal,
|
openDeleteChatFolderModal,
|
||||||
sortChatFolders,
|
sortChatFolders,
|
||||||
|
toggleDialogFilterTags,
|
||||||
|
openPremiumModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [state, setState] = useState<SortState>({
|
const [state, setState] = useState<SortState>({
|
||||||
@ -145,6 +152,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
title: folder.title,
|
title: folder.title,
|
||||||
subtitle: getFolderDescriptionText(lang, folder, chatsCountByFolderId[folder.id]),
|
subtitle: getFolderDescriptionText(lang, folder, chatsCountByFolderId[folder.id]),
|
||||||
isChatList: folder.isChatList,
|
isChatList: folder.isChatList,
|
||||||
|
color: folder.color,
|
||||||
noTitleAnimations: folder.noTitleAnimations,
|
noTitleAnimations: folder.noTitleAnimations,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -162,6 +170,14 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
addChatFolder({ folder });
|
addChatFolder({ folder });
|
||||||
}, [foldersById, maxFolders, addChatFolder, openLimitReachedModal]);
|
}, [foldersById, maxFolders, addChatFolder, openLimitReachedModal]);
|
||||||
|
|
||||||
|
const handleToggleTags = useCallback(() => {
|
||||||
|
if (!isPremium) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDialogFilterTags({ isEnabled: !areTagsEnabled });
|
||||||
|
}, [areTagsEnabled, isPremium, toggleDialogFilterTags]);
|
||||||
|
|
||||||
const handleDrag = useCallback((translation: { x: number; y: number }, id: string | number) => {
|
const handleDrag = useCallback((translation: { x: number; y: number }, id: string | number) => {
|
||||||
const delta = Math.round(translation.y / FOLDER_HEIGHT_PX);
|
const delta = Math.round(translation.y / FOLDER_HEIGHT_PX);
|
||||||
const index = state.orderedFolderIds?.indexOf(id as number) || 0;
|
const index = state.orderedFolderIds?.indexOf(id as number) || 0;
|
||||||
@ -303,7 +319,12 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="title">
|
<span className={buildClassName(
|
||||||
|
'title',
|
||||||
|
folder?.color !== undefined && folder.color !== -1 && isPremium
|
||||||
|
&& `${getApiPeerColorClass({ color: folder.color })} settings-folders-title`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
{renderTextWithEntities({
|
{renderTextWithEntities({
|
||||||
text: folder.title.text,
|
text: folder.title.text,
|
||||||
entities: folder.title.entities,
|
entities: folder.title.entities,
|
||||||
@ -365,6 +386,23 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="settings-item pt-3">
|
||||||
|
<div className="settings-item-relative">
|
||||||
|
<Checkbox
|
||||||
|
label={lang('ShowFolderTags')}
|
||||||
|
subLabel={lang('ShowFolderTagsHint')}
|
||||||
|
checked={isPremium && areTagsEnabled}
|
||||||
|
onChange={handleToggleTags}
|
||||||
|
onClickLabel={(event) => {
|
||||||
|
if (!isPremium) {
|
||||||
|
event.preventDefault();
|
||||||
|
openPremiumModal();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!isPremium && <Icon name="lock-badge" className="settings-folders-lock-icon" />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -375,6 +413,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
orderedIds: folderIds,
|
orderedIds: folderIds,
|
||||||
byId: foldersById,
|
byId: foldersById,
|
||||||
recommended: recommendedChatFolders,
|
recommended: recommendedChatFolders,
|
||||||
|
areTagsEnabled,
|
||||||
} = global.chatFolders;
|
} = global.chatFolders;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -383,6 +422,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isPremium: selectIsCurrentUserPremium(global),
|
isPremium: selectIsCurrentUserPremium(global),
|
||||||
recommendedChatFolders,
|
recommendedChatFolders,
|
||||||
maxFolders: selectCurrentLimit(global, 'dialogFilters'),
|
maxFolders: selectCurrentLimit(global, 'dialogFilters'),
|
||||||
|
areTagsEnabled,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(SettingsFoldersMain));
|
)(SettingsFoldersMain));
|
||||||
|
|||||||
@ -1115,6 +1115,24 @@ addActionHandler('loadRecommendedChatFolders', async (global): Promise<void> =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleDialogFilterTags', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { isEnabled } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('toggleDialogFilterTags', isEnabled);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
global = getGlobal();
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
chatFolders: {
|
||||||
|
...global.chatFolders,
|
||||||
|
areTagsEnabled: isEnabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('editChatFolders', (global, actions, payload): ActionReturnType => {
|
addActionHandler('editChatFolders', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, idsToRemove, idsToAdd, tabId = getCurrentTabId(),
|
chatId, idsToRemove, idsToAdd, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -198,6 +198,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
chatFolders: {
|
chatFolders: {
|
||||||
byId: {},
|
byId: {},
|
||||||
invites: {},
|
invites: {},
|
||||||
|
areTagsEnabled: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
fileUploads: {
|
fileUploads: {
|
||||||
|
|||||||
@ -396,6 +396,9 @@ export interface ActionPayloads {
|
|||||||
deleteChatFolder: {
|
deleteChatFolder: {
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
toggleDialogFilterTags: {
|
||||||
|
isEnabled: boolean;
|
||||||
|
};
|
||||||
openSupportChat: WithTabId | undefined;
|
openSupportChat: WithTabId | undefined;
|
||||||
openChatByPhoneNumber: {
|
openChatByPhoneNumber: {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
|
|||||||
@ -276,6 +276,7 @@ export type GlobalState = {
|
|||||||
byId: Record<number, ApiChatFolder>;
|
byId: Record<number, ApiChatFolder>;
|
||||||
invites: Record<number, ApiChatlistExportedInvite[]>;
|
invites: Record<number, ApiChatlistExportedInvite[]>;
|
||||||
recommended?: ApiChatFolder[];
|
recommended?: ApiChatFolder[];
|
||||||
|
areTagsEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
phoneCall?: ApiPhoneCall;
|
phoneCall?: ApiPhoneCall;
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export type FoldersState = {
|
|||||||
export type FoldersActions = (
|
export type FoldersActions = (
|
||||||
'setTitle' | 'saveFilters' | 'editFolder' | 'reset' | 'setChatFilter' | 'setIsLoading' | 'setError' |
|
'setTitle' | 'saveFilters' | 'editFolder' | 'reset' | 'setChatFilter' | 'setIsLoading' | 'setError' |
|
||||||
'editIncludeFilters' | 'editExcludeFilters' | 'setIncludeFilters' | 'setExcludeFilters' | 'setIsTouched' |
|
'editIncludeFilters' | 'editExcludeFilters' | 'setIncludeFilters' | 'setExcludeFilters' | 'setIsTouched' |
|
||||||
'setFolderId' | 'setIsChatlist'
|
'setFolderId' | 'setIsChatlist' | 'setColor'
|
||||||
);
|
);
|
||||||
export type FolderEditDispatch = Dispatch<FoldersState, FoldersActions>;
|
export type FolderEditDispatch = Dispatch<FoldersState, FoldersActions>;
|
||||||
|
|
||||||
@ -248,6 +248,15 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
|||||||
isChatList: action.payload,
|
isChatList: action.payload,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
case 'setColor':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
folder: {
|
||||||
|
...state.folder,
|
||||||
|
color: action.payload,
|
||||||
|
},
|
||||||
|
isTouched: true,
|
||||||
|
};
|
||||||
case 'reset':
|
case 'reset':
|
||||||
return INITIAL_STATE;
|
return INITIAL_STATE;
|
||||||
default:
|
default:
|
||||||
|
|||||||
5
src/lib/gramjs/tl/api.d.ts
vendored
5
src/lib/gramjs/tl/api.d.ts
vendored
@ -24829,6 +24829,11 @@ namespace Api {
|
|||||||
}, Bool> {
|
}, Bool> {
|
||||||
order: int[];
|
order: int[];
|
||||||
}
|
}
|
||||||
|
export class ToggleDialogFilterTags extends Request<{
|
||||||
|
enabled: Bool;
|
||||||
|
}, Bool> {
|
||||||
|
enabled: Bool;
|
||||||
|
}
|
||||||
export class GetOldFeaturedStickers extends Request<{
|
export class GetOldFeaturedStickers extends Request<{
|
||||||
offset: int;
|
offset: int;
|
||||||
limit: int;
|
limit: int;
|
||||||
|
|||||||
@ -1616,6 +1616,7 @@ messages.getDialogFilters#efd48c89 = messages.DialogFilters;
|
|||||||
messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
|
messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
|
||||||
messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
|
messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
|
||||||
messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
|
messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
|
||||||
|
messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = Bool;
|
||||||
messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
||||||
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
|
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
|
||||||
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
|
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
|
||||||
@ -1671,6 +1672,7 @@ messages.getOutboxReadDate#8c4bfe5d peer:InputPeer msg_id:int = OutboxReadDate;
|
|||||||
messages.getQuickReplies#d483f2a8 hash:long = messages.QuickReplies;
|
messages.getQuickReplies#d483f2a8 hash:long = messages.QuickReplies;
|
||||||
messages.getQuickReplyMessages#94a495c3 flags:# shortcut_id:int id:flags.0?Vector<int> hash:long = messages.Messages;
|
messages.getQuickReplyMessages#94a495c3 flags:# shortcut_id:int id:flags.0?Vector<int> hash:long = messages.Messages;
|
||||||
messages.sendQuickReplyMessages#6c750de1 peer:InputPeer shortcut_id:int id:Vector<int> random_id:Vector<long> = Updates;
|
messages.sendQuickReplyMessages#6c750de1 peer:InputPeer shortcut_id:int id:Vector<int> random_id:Vector<long> = Updates;
|
||||||
|
messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = Bool;
|
||||||
messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects;
|
messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects;
|
||||||
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
||||||
messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true fullscreen:flags.8?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult;
|
messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true fullscreen:flags.8?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult;
|
||||||
|
|||||||
@ -163,6 +163,7 @@
|
|||||||
"messages.getSuggestedDialogFilters",
|
"messages.getSuggestedDialogFilters",
|
||||||
"messages.updateDialogFilter",
|
"messages.updateDialogFilter",
|
||||||
"messages.updateDialogFiltersOrder",
|
"messages.updateDialogFiltersOrder",
|
||||||
|
"messages.toggleDialogFilterTags",
|
||||||
"messages.getReplies",
|
"messages.getReplies",
|
||||||
"messages.getDiscussionMessage",
|
"messages.getDiscussionMessage",
|
||||||
"messages.readDiscussion",
|
"messages.readDiscussion",
|
||||||
|
|||||||
@ -2311,6 +2311,7 @@ messages.getDialogFilters#efd48c89 = messages.DialogFilters;
|
|||||||
messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
|
messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
|
||||||
messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
|
messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
|
||||||
messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
|
messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
|
||||||
|
messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = Bool;
|
||||||
messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messages.FeaturedStickers;
|
messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messages.FeaturedStickers;
|
||||||
messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
||||||
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
|
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
|
||||||
|
|||||||
4
src/types/language.d.ts
vendored
4
src/types/language.d.ts
vendored
@ -320,6 +320,10 @@ export interface LangPair {
|
|||||||
'BlockedUsersBlockUser': undefined;
|
'BlockedUsersBlockUser': undefined;
|
||||||
'FilterChatTypes': undefined;
|
'FilterChatTypes': undefined;
|
||||||
'FilterChats': undefined;
|
'FilterChats': undefined;
|
||||||
|
'FilterColorTitle': undefined;
|
||||||
|
'FilterColorHint': undefined;
|
||||||
|
'ShowFolderTags': undefined;
|
||||||
|
'ShowFolderTagsHint': undefined;
|
||||||
'FilterIncludeInfo': undefined;
|
'FilterIncludeInfo': undefined;
|
||||||
'FilterNameHint': undefined;
|
'FilterNameHint': undefined;
|
||||||
'FilterInclude': undefined;
|
'FilterInclude': undefined;
|
||||||
|
|||||||
@ -175,6 +175,10 @@ export default {
|
|||||||
FilterChannels: 'Channels',
|
FilterChannels: 'Channels',
|
||||||
FilterBots: 'Bots',
|
FilterBots: 'Bots',
|
||||||
FilterChats: 'Chats',
|
FilterChats: 'Chats',
|
||||||
|
FilterColorTitle: 'Folder Color',
|
||||||
|
FilterColorHint: 'This color will be used for the folder\'s tag in the chat list',
|
||||||
|
ShowFolderTags: 'Show Folder Tags',
|
||||||
|
ShowFolderTagsHint: 'Display folder names for each chat in the chat list.',
|
||||||
AccDescrChannel: 'Channel',
|
AccDescrChannel: 'Channel',
|
||||||
AccDescrGroup: 'Group',
|
AccDescrGroup: 'Group',
|
||||||
Bot: 'bot',
|
Bot: 'bot',
|
||||||
|
|||||||
@ -177,6 +177,10 @@ export function getOrderKey(chatId: string, isForSaved?: boolean) {
|
|||||||
return isForSaved ? summary.orderInSaved : summary.orderInAll;
|
return isForSaved ? summary.orderInSaved : summary.orderInAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getChatFolderIds(chatId: string) {
|
||||||
|
return prepared.folderIdsByChatId[chatId];
|
||||||
|
}
|
||||||
|
|
||||||
/* Callback managers */
|
/* Callback managers */
|
||||||
|
|
||||||
export function addOrderedIdsCallback(folderId: number, callback: (orderedIds: string[]) => void) {
|
export function addOrderedIdsCallback(folderId: number, callback: (orderedIds: string[]) => void) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user