From 3ba0a90d6322aa0c243f04a1a9d66d12d34767f9 Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:54:21 +0100 Subject: [PATCH] Folders Sidebar: Follow-up (#6501) --- src/components/common/CustomEmoji.tsx | 4 +- src/components/common/CustomEmojiPicker.tsx | 12 ++---- src/components/common/FolderIcon.tsx | 24 +++++------ src/components/common/StickerButton.scss | 31 +++---------- src/components/common/StickerButton.tsx | 43 ++++++++----------- src/components/common/StickerSet.tsx | 20 +++------ src/components/common/UiLoader.module.scss | 2 +- src/components/common/UiLoader.tsx | 6 +-- src/components/left/LeftColumn.scss | 2 +- .../left/main/StatusPickerMenu.module.scss | 2 +- src/components/left/main/StatusPickerMenu.tsx | 18 +++----- .../settings/folders/FolderIconPickerMenu.tsx | 3 +- .../settings/folders/SettingsFoldersMain.tsx | 39 +++++++++-------- .../main/FoldersSidebar.module.scss | 4 +- src/components/main/FoldersSidebar.tsx | 11 +---- src/components/main/Main.scss | 6 +-- src/components/main/Main.tsx | 8 ++-- src/components/ui/Folder.module.scss | 2 +- src/components/ui/Folder.tsx | 14 +++++- src/config.ts | 6 +-- src/global/cache.ts | 18 +++++--- src/global/initialState.ts | 4 +- src/global/types/sharedState.ts | 6 ++- src/hooks/useFolderTabs.ts | 2 +- src/styles/_variables.scss | 2 +- src/types/index.ts | 2 +- 26 files changed, 129 insertions(+), 162 deletions(-) diff --git a/src/components/common/CustomEmoji.tsx b/src/components/common/CustomEmoji.tsx index e6b484456..b62718593 100644 --- a/src/components/common/CustomEmoji.tsx +++ b/src/components/common/CustomEmoji.tsx @@ -29,6 +29,7 @@ type OwnProps = { noPlay?: boolean; noVideoOnMobile?: boolean; loopLimit?: number; + shouldNotLoop?: boolean; isSelectable?: boolean; withSharedAnimation?: boolean; sharedCanvasRef?: ElementRef; @@ -55,6 +56,7 @@ const CustomEmoji: FC = ({ noPlay, noVideoOnMobile, loopLimit, + shouldNotLoop, isSelectable, withSharedAnimation, sharedCanvasRef, @@ -146,7 +148,7 @@ const CustomEmoji: FC = ({ noVideoOnMobile={noVideoOnMobile} thumbClassName={styles.thumb} fullMediaClassName={styles.media} - shouldLoop + shouldLoop={!shouldNotLoop} loopLimit={loopLimit} shouldPreloadPreview={shouldPreloadPreview || noPlay || !canPlay} forceOnHeavyAnimation={forceOnHeavyAnimation} diff --git a/src/components/common/CustomEmojiPicker.tsx b/src/components/common/CustomEmojiPicker.tsx index 17389bad6..89dfdb6c7 100644 --- a/src/components/common/CustomEmojiPicker.tsx +++ b/src/components/common/CustomEmojiPicker.tsx @@ -71,9 +71,7 @@ type OwnProps = { onCustomEmojiSelect: (sticker: ApiSticker) => void; onReactionSelect?: (reaction: ApiReactionWithPaid) => void; onReactionContext?: (reaction: ApiReactionWithPaid) => void; - onContextMenuOpen?: NoneToVoidFunction; - onContextMenuClose?: NoneToVoidFunction; - onContextMenuClick?: NoneToVoidFunction; + onDismiss?: NoneToVoidFunction; }; type StateProps = { @@ -142,9 +140,7 @@ const CustomEmojiPicker: FC = ({ onCustomEmojiSelect, onReactionSelect, onReactionContext, - onContextMenuOpen, - onContextMenuClose, - onContextMenuClick, + onDismiss, }) => { const containerRef = useRef(); const headerRef = useRef(); @@ -466,9 +462,7 @@ const CustomEmojiPicker: FC = ({ onReactionSelect={onReactionSelect} onReactionContext={onReactionContext} onStickerSelect={handleEmojiSelect} - onContextMenuOpen={onContextMenuOpen} - onContextMenuClose={onContextMenuClose} - onContextMenuClick={onContextMenuClick} + onDismiss={onDismiss} forcePlayback /> ); diff --git a/src/components/common/FolderIcon.tsx b/src/components/common/FolderIcon.tsx index 014ba7883..7b14363b3 100644 --- a/src/components/common/FolderIcon.tsx +++ b/src/components/common/FolderIcon.tsx @@ -11,19 +11,19 @@ import styles from './FolderIcon.module.scss'; const ICON_SIZE = 2.25 * REM; -const FolderIcon = ( - { - emoji, - customEmojiId, - shouldAnimate, - }: { - emoji?: string; - customEmojiId?: string; - shouldAnimate?: boolean; - }, -) => { +type OwnProps = { + emoji?: string; + customEmojiId?: string; + shouldAnimate?: boolean; +}; + +const FolderIcon = ({ + emoji, + customEmojiId, + shouldAnimate, +}: OwnProps) => { if (customEmojiId) { - return ; + return ; } if (!emoji) { diff --git a/src/components/common/StickerButton.scss b/src/components/common/StickerButton.scss index d390df6df..ac9244310 100644 --- a/src/components/common/StickerButton.scss +++ b/src/components/common/StickerButton.scss @@ -45,7 +45,7 @@ vertical-align: bottom; } - .sticker-locked { + .sticker-locked, .sticker-premium { position: absolute; z-index: 2; right: 0; @@ -55,30 +55,11 @@ align-items: center; justify-content: center; - width: 1.25rem; - height: 1.25rem; - border-radius: 50%; - - color: white; - - opacity: 0.75; - background: var(--premium-gradient); - } - - .sticker-premium { - position: absolute; - z-index: 1; - right: 0; - bottom: 0; - - display: flex; - align-items: center; - justify-content: center; - - width: 1.25rem; - height: 1.25rem; + width: 1rem; + height: 1rem; border-radius: 50%; + font-size: 0.875rem; color: white; background: var(--premium-gradient); @@ -135,8 +116,8 @@ top: -0.125rem; right: -0.125rem; - width: 1.25rem; - height: 1.25rem; + width: 1rem; + height: 1rem; padding: 0.125rem; opacity: 0; diff --git a/src/components/common/StickerButton.tsx b/src/components/common/StickerButton.tsx index a6fd46e80..cbd039b6e 100644 --- a/src/components/common/StickerButton.tsx +++ b/src/components/common/StickerButton.tsx @@ -45,18 +45,17 @@ type OwnProps = { sharedCanvasRef?: ElementRef; withTranslucentThumb?: boolean; forcePlayback?: boolean; + isEffectEmoji?: boolean; + noShowPremium?: boolean; + noIcons?: boolean; + clickArg: T; + onClick?: (arg: OwnProps['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void; observeIntersection: ObserveFn; observeIntersectionForShowing?: ObserveFn; - noShowPremium?: boolean; - onClick?: (arg: OwnProps['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void; - clickArg: T; onFaveClick?: (sticker: ApiSticker) => void; onUnfaveClick?: (sticker: ApiSticker) => void; onRemoveRecentClick?: (sticker: ApiSticker) => void; - onContextMenuOpen?: NoneToVoidFunction; - onContextMenuClose?: NoneToVoidFunction; - onContextMenuClick?: NoneToVoidFunction; - isEffectEmoji?: boolean; + onDismiss?: NoneToVoidFunction; }; const contentForStatusMenuContext = [ @@ -77,8 +76,6 @@ const StickerButton = ) => { const { openStickerSet, openPremiumModal, setEmojiStatus } = getActions(); const ref = useRef(); @@ -109,6 +107,7 @@ const StickerButton = ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true })); - useEffect(() => { - if (isContextMenuOpen) { - onContextMenuOpen?.(); - } else { - onContextMenuClose?.(); - } - }, [isContextMenuOpen, onContextMenuClose, onContextMenuOpen]); - useEffect(() => { if (!isIntersecting) handleContextMenuClose(); }, [handleContextMenuClose, isIntersecting]); @@ -153,7 +144,7 @@ const StickerButton = )} - {!noShowPremium && isLocked && ( + {!noIcons && !noShowPremium && isLocked && (
)} - {!noShowPremium && isPremium && !isLocked && ( + {!noIcons && !noShowPremium && isPremiumSticker && !isLocked && (
diff --git a/src/components/common/StickerSet.tsx b/src/components/common/StickerSet.tsx index ea282ffa1..e1b703e8d 100644 --- a/src/components/common/StickerSet.tsx +++ b/src/components/common/StickerSet.tsx @@ -1,4 +1,3 @@ -import type { FC } from '../../lib/teact/teact'; import { memo, useEffect, useMemo, useRef, useState, } from '../../lib/teact/teact'; @@ -74,9 +73,7 @@ type OwnProps = { onStickerUnfave?: (sticker: ApiSticker) => void; onStickerFave?: (sticker: ApiSticker) => void; onStickerRemoveRecent?: (sticker: ApiSticker) => void; - onContextMenuOpen?: NoneToVoidFunction; - onContextMenuClose?: NoneToVoidFunction; - onContextMenuClick?: NoneToVoidFunction; + onDismiss?: NoneToVoidFunction; }; type StateProps = { @@ -89,7 +86,7 @@ const ITEMS_MINI_MOBILE_PER_ROW_FALLBACK = 6; const MOBILE_WIDTH_THRESHOLD_PX = 440; const MINI_MOBILE_WIDTH_THRESHOLD_PX = 362; -const StickerSet: FC = ({ +const StickerSet = ({ stickerSet, loadAndPlay, index, @@ -110,6 +107,7 @@ const StickerSet: FC = ({ isTranslucent, noContextMenus, forcePlayback, + collectibleStatuses, observeIntersection, observeIntersectionForPlayingItems, observeIntersectionForShowingItems, @@ -119,11 +117,8 @@ const StickerSet: FC = ({ onStickerUnfave, onStickerFave, onStickerRemoveRecent, - onContextMenuOpen, - onContextMenuClose, - onContextMenuClick, - collectibleStatuses, -}) => { + onDismiss, +}: OwnProps & StateProps) => { const { clearRecentStickers, clearRecentCustomEmoji, @@ -401,13 +396,12 @@ const StickerSet: FC = ({ withTranslucentThumb={isTranslucent} onClick={onStickerSelect} clickArg={sticker} + noIcons={isEmoji && !isRecent} isSelected={isSelected} onUnfaveClick={isFavorite && favoriteStickerIdsSet?.has(sticker.id) ? onStickerUnfave : undefined} onFaveClick={!favoriteStickerIdsSet?.has(sticker.id) ? onStickerFave : undefined} onRemoveRecentClick={isRecent ? onStickerRemoveRecent : undefined} - onContextMenuOpen={onContextMenuOpen} - onContextMenuClose={onContextMenuClose} - onContextMenuClick={onContextMenuClick} + onDismiss={onDismiss} forcePlayback={forcePlayback} isEffectEmoji={stickerSet.id === EFFECT_EMOJIS_SET_ID} noShowPremium={isCurrentUserPremium diff --git a/src/components/common/UiLoader.module.scss b/src/components/common/UiLoader.module.scss index 1d461afea..772a3ea35 100644 --- a/src/components/common/UiLoader.module.scss +++ b/src/components/common/UiLoader.module.scss @@ -26,7 +26,7 @@ } .foldersSidebar { - width: var(--tabs-sidebar-width); + width: var(--folders-sidebar-width); height: 100%; background: var(--color-background-sidebar); } diff --git a/src/components/common/UiLoader.tsx b/src/components/common/UiLoader.tsx index fb8a0d5c8..00ff3ed3b 100644 --- a/src/components/common/UiLoader.tsx +++ b/src/components/common/UiLoader.tsx @@ -5,7 +5,7 @@ import { getActions, getGlobal, withGlobal } from '../../global'; import type { TabState } from '../../global/types'; import { ApiMediaFormat } from '../../api/types'; -import { TABS_POSITION_LEFT } from '../../config'; +import { FOLDERS_POSITION_LEFT } from '../../config'; import { getChatAvatarHash } from '../../global/helpers/chats'; // Direct import for better module splitting import { selectAreFoldersPresent, selectIsRightColumnShown, selectTabState } from '../../global/selectors'; import { selectSharedSettings } from '../../global/selectors/sharedState'; @@ -179,14 +179,14 @@ export default withGlobal( (global, { isMobile }): Complete => { const tabState = selectTabState(global); - const { tabsPosition } = selectSharedSettings(global); + const { foldersPosition } = selectSharedSettings(global); return { shouldSkipHistoryAnimations: tabState.shouldSkipHistoryAnimations, uiReadyState: tabState.uiReadyState, isRightColumnShown: selectIsRightColumnShown(global, isMobile), leftColumnWidth: global.leftColumnWidth, - isFoldersSidebarShown: tabsPosition === TABS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global), + isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global), }; }, )(UiLoader); diff --git a/src/components/left/LeftColumn.scss b/src/components/left/LeftColumn.scss index a0ccd7c9a..03e923ec2 100644 --- a/src/components/left/LeftColumn.scss +++ b/src/components/left/LeftColumn.scss @@ -40,7 +40,7 @@ } } - body.is-tauri.is-macos #Main:not(.is-fullscreen):not(.tabs-sidebar-visible) &:not(#TopicListHeader) { + body.is-tauri.is-macos #Main:not(.is-fullscreen):not(.folders-sidebar-visible) &:not(#TopicListHeader) { padding-left: var(--window-controls-width); } diff --git a/src/components/left/main/StatusPickerMenu.module.scss b/src/components/left/main/StatusPickerMenu.module.scss index f4cd521b0..908ef48ac 100644 --- a/src/components/left/main/StatusPickerMenu.module.scss +++ b/src/components/left/main/StatusPickerMenu.module.scss @@ -1,6 +1,6 @@ .menuContent { --offset-y: 3.25rem !important; - --offset-x: auto !important; + --offset-x: var(--folders-sidebar-width) !important; --color-text: var(--color-primary); --border-radius-default: 1.25rem; diff --git a/src/components/left/main/StatusPickerMenu.tsx b/src/components/left/main/StatusPickerMenu.tsx index 0df6b91c4..aa23a5f1f 100644 --- a/src/components/left/main/StatusPickerMenu.tsx +++ b/src/components/left/main/StatusPickerMenu.tsx @@ -1,4 +1,4 @@ -import type { ElementRef, FC } from '../../../lib/teact/teact'; +import type { ElementRef } from '../../../lib/teact/teact'; import { memo, useCallback, useEffect, useRef, } from '../../../lib/teact/teact'; @@ -8,8 +8,6 @@ import type { ApiSticker } from '../../../api/types'; import { selectIsContextMenuTranslucent } from '../../../global/selectors'; -import useFlag from '../../../hooks/useFlag'; - import CustomEmojiPicker from '../../common/CustomEmojiPicker'; import Menu from '../../ui/Menu'; import Portal from '../../ui/Portal'; @@ -28,18 +26,17 @@ interface StateProps { isTranslucent?: boolean; } -const StatusPickerMenu: FC = ({ +const StatusPickerMenu = ({ isOpen, statusButtonRef, areFeaturedStickersLoaded, isTranslucent, onEmojiStatusSelect, onClose, -}) => { +}: OwnProps & StateProps) => { const { loadFeaturedEmojiStickers } = getActions(); - const transformOriginX = useRef(); - const [isContextMenuShown, markContextMenuShown, unmarkContextMenuShown] = useFlag(); + const transformOriginX = useRef(0); useEffect(() => { transformOriginX.current = statusButtonRef.current!.getBoundingClientRect().right; }, [isOpen, statusButtonRef]); @@ -60,11 +57,10 @@ const StatusPickerMenu: FC = ({ = ({ isHidden={!isOpen} isStatusPicker isTranslucent={isTranslucent} - onContextMenuOpen={markContextMenuShown} - onContextMenuClose={unmarkContextMenuShown} + onDismiss={onClose} onCustomEmojiSelect={handleEmojiSelect} - onContextMenuClick={onClose} /> diff --git a/src/components/left/settings/folders/FolderIconPickerMenu.tsx b/src/components/left/settings/folders/FolderIconPickerMenu.tsx index 5a51df7a3..5aebcae61 100644 --- a/src/components/left/settings/folders/FolderIconPickerMenu.tsx +++ b/src/components/left/settings/folders/FolderIconPickerMenu.tsx @@ -47,8 +47,7 @@ const FolderIconPickerMenu = ({ loadAndPlay={isOpen} isHidden={!isOpen} onCustomEmojiSelect={(emoji) => handleEmojiSelect(emoji)} - onContextMenuClick={onClose} - onContextMenuClose={onClose} + onDismiss={onClose} /> diff --git a/src/components/left/settings/folders/SettingsFoldersMain.tsx b/src/components/left/settings/folders/SettingsFoldersMain.tsx index 723ffd388..816d85722 100644 --- a/src/components/left/settings/folders/SettingsFoldersMain.tsx +++ b/src/components/left/settings/folders/SettingsFoldersMain.tsx @@ -1,13 +1,14 @@ -import type { FC } from '../../../../lib/teact/teact'; import { memo, useCallback, useEffect, useMemo, useState, } from '../../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../../global'; import type { ApiChatFolder } from '../../../../api/types'; -import type { TabsPosition } from '../../../../types'; +import type { FoldersPosition } from '../../../../types'; -import { ALL_FOLDER_ID, STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config'; +import { + ALL_FOLDER_ID, FOLDERS_POSITION_LEFT, FOLDERS_POSITION_TOP, STICKER_SIZE_FOLDER_SETTINGS, +} from '../../../../config'; import { getFolderDescriptionText } from '../../../../global/helpers'; import { selectIsCurrentUserPremium } from '../../../../global/selectors'; import { selectCurrentLimit } from '../../../../global/selectors/limits'; @@ -36,10 +37,10 @@ import RadioGroup from '../../../ui/RadioGroup'; type OwnProps = { isActive?: boolean; + isMobile?: boolean; onCreateFolder: () => void; onEditFolder: (folder: ApiChatFolder) => void; onReset: () => void; - isMobile?: boolean; }; type StateProps = { @@ -49,7 +50,7 @@ type StateProps = { maxFolders: number; isPremium?: boolean; areTagsEnabled?: boolean; - tabsPosition: TabsPosition; + foldersPosition: FoldersPosition; }; type SortState = { @@ -61,20 +62,20 @@ type SortState = { const FOLDER_HEIGHT_PX = 56; const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true); -const SettingsFoldersMain: FC = ({ +const SettingsFoldersMain = ({ isActive, - onCreateFolder, - onEditFolder, - onReset, - folderIds, foldersById, isPremium, recommendedChatFolders, maxFolders, areTagsEnabled, - tabsPosition, + foldersPosition, isMobile, -}) => { + onCreateFolder, + onEditFolder, + onReset, + folderIds, +}: OwnProps & StateProps) => { const { loadRecommendedChatFolders, addChatFolder, @@ -215,8 +216,8 @@ const SettingsFoldersMain: FC = ({ }); }, [sortChatFolders]); - const handleTabsPositionChange = useLastCallback((value: string) => { - setSharedSettingOption({ tabsPosition: value as TabsPosition }); + const handleFoldersPositionChange = useLastCallback((value: string) => { + setSharedSettingOption({ foldersPosition: value as FoldersPosition }); }); const canCreateNewFolder = useMemo(() => { @@ -430,13 +431,13 @@ const SettingsFoldersMain: FC = ({ name="tabsPosition" options={[{ label: lang('TabsPositionLeft'), - value: 'left', + value: FOLDERS_POSITION_LEFT, }, { label: lang('TabsPositionTop'), - value: 'top', + value: FOLDERS_POSITION_TOP, }]} - selected={tabsPosition} - onChange={handleTabsPositionChange} + selected={foldersPosition} + onChange={handleFoldersPositionChange} /> )} @@ -460,7 +461,7 @@ export default memo(withGlobal( recommendedChatFolders, maxFolders: selectCurrentLimit(global, 'dialogFilters'), areTagsEnabled, - tabsPosition: global.sharedState.settings.tabsPosition, + foldersPosition: global.sharedState.settings.foldersPosition, }; }, )(SettingsFoldersMain)); diff --git a/src/components/main/FoldersSidebar.module.scss b/src/components/main/FoldersSidebar.module.scss index 7522d105a..a864b71a5 100644 --- a/src/components/main/FoldersSidebar.module.scss +++ b/src/components/main/FoldersSidebar.module.scss @@ -4,7 +4,7 @@ align-items: center; justify-content: space-between; - width: var(--tabs-sidebar-width); + width: var(--folders-sidebar-width); height: 100%; background-color: var(--color-background-sidebar); @@ -78,7 +78,7 @@ } .menuButton { - width: var(--tabs-sidebar-width); + width: var(--folders-sidebar-width); height: 3.5rem; border-radius: 0; } diff --git a/src/components/main/FoldersSidebar.tsx b/src/components/main/FoldersSidebar.tsx index 45cbb8b6f..52b95ce1a 100644 --- a/src/components/main/FoldersSidebar.tsx +++ b/src/components/main/FoldersSidebar.tsx @@ -1,7 +1,7 @@ import { memo, useEffect, useMemo, useRef } from '@teact'; import { getActions, withGlobal } from '../../global'; -import type { ApiChatFolder, ApiChatlistExportedInvite, ApiMessageEntityCustomEmoji } from '../../api/types'; +import type { ApiChatFolder, ApiChatlistExportedInvite } from '../../api/types'; import { LeftColumnContent, SettingsScreens } from '../../types'; import { selectTabState } from '../../global/selectors'; @@ -15,7 +15,6 @@ import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import useScrolledState from '../../hooks/useScrolledState'; -import FolderIcon from '../common/FolderIcon'; import MainMenuDropdown from '../common/MainMenuDropdown'; import Button from '../ui/Button'; import Folder from '../ui/Folder'; @@ -158,14 +157,8 @@ const FoldersSidebar = ({ clickArg={i} contextActions={tab.contextActions} contextRootElementSelector="#FoldersSidebar" + icon={tab.emoticon} className={styles.tab} - icon={( - - )} /> ))} diff --git a/src/components/main/Main.scss b/src/components/main/Main.scss index 8c873cb09..cc852c4bc 100644 --- a/src/components/main/Main.scss +++ b/src/components/main/Main.scss @@ -3,7 +3,7 @@ height: 100%; text-align: left; - &.tabs-sidebar-visible { + &.folders-sidebar-visible { grid-template-columns: auto auto 1fr; } @@ -218,9 +218,9 @@ } @media (max-width: 925px) { - .tabs-sidebar-visible { + .folders-sidebar-visible { #LeftColumn { - left: var(--tabs-sidebar-width) !important; + left: var(--folders-sidebar-width) !important; width: 21.5rem !important; } } diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 21d079e3f..ecc05a063 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -11,7 +11,7 @@ import { getActions, getGlobal, withGlobal } from '../../global'; import type { ApiChatFolder, ApiLimitTypeWithModal, ApiUser } from '../../api/types'; import type { TabState } from '../../global/types'; -import { BASE_EMOJI_KEYWORD_LANG, DEBUG, INACTIVE_MARKER, TABS_POSITION_LEFT } from '../../config'; +import { BASE_EMOJI_KEYWORD_LANG, DEBUG, FOLDERS_POSITION_LEFT, INACTIVE_MARKER } from '../../config'; import { requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { selectAreFoldersPresent, @@ -522,7 +522,7 @@ const Main = ({ isNarrowMessageList && 'narrow-message-list', shouldSkipHistoryAnimations && 'history-animation-disabled', isFullscreen && 'is-fullscreen', - isFoldersSidebarShown && 'tabs-sidebar-visible', + isFoldersSidebarShown && 'folders-sidebar-visible', ); const handleBlur = useLastCallback(() => { @@ -643,7 +643,7 @@ export default memo(withGlobal( deleteFolderDialogModal, } = selectTabState(global); - const { wasTimeFormatSetManually, tabsPosition } = selectSharedSettings(global); + const { wasTimeFormatSetManually, foldersPosition } = selectSharedSettings(global); const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId); const gameTitle = gameMessage?.content.game?.title; @@ -700,7 +700,7 @@ export default memo(withGlobal( isSynced: global.isSynced, isAccountFrozen, isAppConfigLoaded: global.isAppConfigLoaded, - isFoldersSidebarShown: tabsPosition === TABS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global), + isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global), }; }, )(Main)); diff --git a/src/components/ui/Folder.module.scss b/src/components/ui/Folder.module.scss index a448412a4..886b67e0b 100644 --- a/src/components/ui/Folder.module.scss +++ b/src/components/ui/Folder.module.scss @@ -10,7 +10,7 @@ align-items: center; justify-content: center; - width: var(--tabs-sidebar-width); + width: var(--folders-sidebar-width); min-height: 4.5rem; padding-right: 0.25rem; padding-left: 0.375rem; diff --git a/src/components/ui/Folder.tsx b/src/components/ui/Folder.tsx index fa9b5ad92..e9a4f047a 100644 --- a/src/components/ui/Folder.tsx +++ b/src/components/ui/Folder.tsx @@ -1,6 +1,7 @@ import type { TeactNode } from '../../lib/teact/teact'; import { useRef } from '../../lib/teact/teact'; +import type { ApiMessageEntityCustomEmoji } from '../../api/types'; import type { MenuItemContextAction } from './ListItem'; import { MouseButton } from '../../util/browser/windowEnvironment'; @@ -8,8 +9,10 @@ import buildClassName from '../../util/buildClassName'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import { useFastClick } from '../../hooks/useFastClick'; +import useFlag from '../../hooks/useFlag'; import useLastCallback from '../../hooks/useLastCallback'; +import FolderIcon from '../common/FolderIcon'; import Icon from '../common/icons/Icon'; import Menu from './Menu'; import MenuItem from './MenuItem'; @@ -26,7 +29,7 @@ type OwnProps = { isBadgeActive?: boolean; contextActions?: MenuItemContextAction[]; contextRootElementSelector?: string; - icon?: TeactNode; + icon?: string | ApiMessageEntityCustomEmoji; clickArg?: number; onClick?: (arg: number) => void; }; @@ -45,6 +48,7 @@ const Folder = ({ onClick, }: OwnProps) => { const folderRef = useRef(); + const [isHovering, markHovering, unmarkHovering] = useFlag(); const { contextMenuAnchor, handleContextMenu, handleBeforeContextMenu, handleContextMenuClose, @@ -78,10 +82,16 @@ const Folder = ({ onClick={handleClick} onMouseDown={handleMouseDown} onContextMenu={handleContextMenu} + onMouseEnter={markHovering} + onMouseLeave={unmarkHovering} ref={folderRef} >
- {icon} + {Boolean(badgeCount) && ( {badgeCount} )} diff --git a/src/config.ts b/src/config.ts index 9029c5059..1bc8875b9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -141,9 +141,9 @@ export const DEFAULT_MESSAGE_TEXT_SIZE_PX = 16; export const IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX = 17; export const MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX = 15; -export const TABS_POSITION_TOP = 'top'; -export const TABS_POSITION_LEFT = 'left'; -export const TABS_POSITION_DEFAULT = TABS_POSITION_TOP; +export const FOLDERS_POSITION_TOP = 'top'; +export const FOLDERS_POSITION_LEFT = 'left'; +export const FOLDERS_POSITION_DEFAULT = FOLDERS_POSITION_TOP; export const PREVIEW_AVATAR_COUNT = 3; diff --git a/src/global/cache.ts b/src/global/cache.ts index e44c25272..a4e80d71b 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -7,12 +7,13 @@ import type { } from '../api/types'; import type { MessageList, ThreadId } from '../types'; import type { ActionReturnType, GlobalState, SharedState } from './types'; -import { MAIN_THREAD_ID } from '../api/types'; +import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../api/types'; import { ALL_FOLDER_ID, ANIMATION_LEVEL_DEFAULT, ARCHIVED_FOLDER_ID, DEBUG, + FOLDERS_POSITION_DEFAULT, GLOBAL_STATE_CACHE_ARCHIVED_CHAT_LIST_LIMIT, GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT, @@ -21,7 +22,6 @@ import { IS_SCREEN_LOCKED_CACHE_KEY, SAVED_FOLDER_ID, SHARED_STATE_CACHE_KEY, - TABS_POSITION_DEFAULT, } from '../config'; import { MAIN_IDB_STORE } from '../util/browser/idb'; import { isUserId } from '../util/entities/ids'; @@ -314,7 +314,7 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) { cached.sharedState.settings = { canDisplayChatInTitle: untypedCached.settings.byKey.canDisplayChatInTitle, animationLevel: untypedCached.settings.byKey.animationLevel, - tabsPosition: untypedCached.settings.byKey.tabsPosition, + foldersPosition: FOLDERS_POSITION_DEFAULT, messageSendKeyCombo: untypedCached.settings.byKey.messageSendKeyCombo, messageTextSize: untypedCached.settings.byKey.messageTextSize, performance: untypedCached.settings.performance, @@ -350,8 +350,8 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) { cachedSharedSettings.performance = INITIAL_PERFORMANCE_STATE_MED; } - if (!cachedSharedSettings.tabsPosition) { - cachedSharedSettings.tabsPosition = TABS_POSITION_DEFAULT; + if (!cachedSharedSettings.foldersPosition) { + cachedSharedSettings.foldersPosition = FOLDERS_POSITION_DEFAULT; } if (!cached.appConfig) { @@ -473,7 +473,13 @@ export function serializeGlobal(global: T) { function reduceCustomEmojis(global: T): GlobalState['customEmojis'] { const { lastRendered, byId } = global.customEmojis; - const idsToSave = lastRendered.slice(0, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT); + const folderEmojiIds = Object.values(global.chatFolders.byId) + .flatMap((folder) => ( + folder.title.entities + ?.filter((entity) => entity.type === ApiMessageEntityTypes.CustomEmoji) + ?.map((entity) => entity.documentId) || [] + )); + const idsToSave = unique([...folderEmojiIds, ...lastRendered]).slice(0, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT); const byIdToSave = pick(byId, idsToSave); return { diff --git a/src/global/initialState.ts b/src/global/initialState.ts index ede9582b8..014a79096 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -11,9 +11,9 @@ import { DEFAULT_PLAYBACK_RATE, DEFAULT_RESALE_GIFTS_FILTER_OPTIONS, DEFAULT_VOLUME, + FOLDERS_POSITION_DEFAULT, IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX, MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX, - TABS_POSITION_DEFAULT, } from '../config'; import { IS_IOS, IS_MAC_OS } from '../util/browser/windowEnvironment'; import { DEFAULT_APP_CONFIG } from '../limits'; @@ -80,7 +80,7 @@ export const INITIAL_SHARED_STATE: SharedState = { ? IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX : (IS_MAC_OS ? MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX : DEFAULT_MESSAGE_TEXT_SIZE_PX), animationLevel: ANIMATION_LEVEL_DEFAULT, - tabsPosition: TABS_POSITION_DEFAULT, + foldersPosition: FOLDERS_POSITION_DEFAULT, messageSendKeyCombo: 'enter', performance: INITIAL_PERFORMANCE_STATE_MAX, shouldSkipWebAppCloseConfirmation: false, diff --git a/src/global/types/sharedState.ts b/src/global/types/sharedState.ts index 018746633..385695f4d 100644 --- a/src/global/types/sharedState.ts +++ b/src/global/types/sharedState.ts @@ -1,5 +1,7 @@ import type { ApiLanguage } from '../../api/types'; -import type { AnimationLevel, PerformanceType, Point, Size, TabsPosition, ThemeKey, TimeFormat } from '../../types'; +import type { + AnimationLevel, FoldersPosition, PerformanceType, Point, Size, ThemeKey, TimeFormat, +} from '../../types'; export interface SharedState { settings: SharedSettings; @@ -14,7 +16,7 @@ export interface SharedSettings { performance: PerformanceType; messageTextSize: number; animationLevel: AnimationLevel; - tabsPosition: TabsPosition; + foldersPosition: FoldersPosition; // This can be deleted after September 2025, along with the corresponding migration wasAnimationLevelSetManually?: boolean; messageSendKeyCombo: 'enter' | 'ctrl-enter'; diff --git a/src/hooks/useFolderTabs.ts b/src/hooks/useFolderTabs.ts index de0d9a096..331a0ddf1 100644 --- a/src/hooks/useFolderTabs.ts +++ b/src/hooks/useFolderTabs.ts @@ -177,7 +177,7 @@ const useFolderTabs = ({ title: sidebarMode ? lang('TabsPositionTop') : lang('TabsPositionLeft'), icon: 'forums', handler: () => { - setSharedSettingOption({ tabsPosition: sidebarMode ? 'top' : 'left' }); + setSharedSettingOption({ foldersPosition: sidebarMode ? 'top' : 'left' }); }, }); } diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index b00076b85..82919118a 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -221,7 +221,7 @@ $color-message-story-mention-to: #74bcff; --border-radius-forum-avatar: 33.3333%; --messages-container-width: 45.5rem; --right-column-width: 26.5rem; - --tabs-sidebar-width: 5rem; + --folders-sidebar-width: 5rem; --window-controls-width: 0rem; --header-height: 3.5rem; --custom-emoji-size: 1.25rem; diff --git a/src/types/index.ts b/src/types/index.ts index 833e205f5..d2872c57f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -100,7 +100,7 @@ export type ThreadId = string | number; export type ThemeKey = 'light' | 'dark'; export type AnimationLevel = 0 | 1 | 2; -export type TabsPosition = 'top' | 'left'; +export type FoldersPosition = 'top' | 'left'; export type PerformanceTypeKey = ( 'pageTransitions' | 'messageSendingAnimations' | 'mediaViewerAnimations' | 'messageComposerAnimations' | 'contextMenuAnimations' | 'contextMenuBlur' | 'rightColumnAnimations'