Folders Sidebar: Follow-up (#6501)
This commit is contained in:
parent
111c702d73
commit
3ba0a90d63
@ -29,6 +29,7 @@ type OwnProps = {
|
||||
noPlay?: boolean;
|
||||
noVideoOnMobile?: boolean;
|
||||
loopLimit?: number;
|
||||
shouldNotLoop?: boolean;
|
||||
isSelectable?: boolean;
|
||||
withSharedAnimation?: boolean;
|
||||
sharedCanvasRef?: ElementRef<HTMLCanvasElement>;
|
||||
@ -55,6 +56,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
noPlay,
|
||||
noVideoOnMobile,
|
||||
loopLimit,
|
||||
shouldNotLoop,
|
||||
isSelectable,
|
||||
withSharedAnimation,
|
||||
sharedCanvasRef,
|
||||
@ -146,7 +148,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
noVideoOnMobile={noVideoOnMobile}
|
||||
thumbClassName={styles.thumb}
|
||||
fullMediaClassName={styles.media}
|
||||
shouldLoop
|
||||
shouldLoop={!shouldNotLoop}
|
||||
loopLimit={loopLimit}
|
||||
shouldPreloadPreview={shouldPreloadPreview || noPlay || !canPlay}
|
||||
forceOnHeavyAnimation={forceOnHeavyAnimation}
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
onCustomEmojiSelect,
|
||||
onReactionSelect,
|
||||
onReactionContext,
|
||||
onContextMenuOpen,
|
||||
onContextMenuClose,
|
||||
onContextMenuClick,
|
||||
onDismiss,
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const headerRef = useRef<HTMLDivElement>();
|
||||
@ -466,9 +462,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
onReactionSelect={onReactionSelect}
|
||||
onReactionContext={onReactionContext}
|
||||
onStickerSelect={handleEmojiSelect}
|
||||
onContextMenuOpen={onContextMenuOpen}
|
||||
onContextMenuClose={onContextMenuClose}
|
||||
onContextMenuClick={onContextMenuClick}
|
||||
onDismiss={onDismiss}
|
||||
forcePlayback
|
||||
/>
|
||||
);
|
||||
|
||||
@ -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 <CustomEmoji documentId={customEmojiId} size={ICON_SIZE} noPlay={shouldAnimate} />;
|
||||
return <CustomEmoji documentId={customEmojiId} size={ICON_SIZE} shouldNotLoop={!shouldAnimate} />;
|
||||
}
|
||||
|
||||
if (!emoji) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -45,18 +45,17 @@ type OwnProps<T> = {
|
||||
sharedCanvasRef?: ElementRef<HTMLCanvasElement>;
|
||||
withTranslucentThumb?: boolean;
|
||||
forcePlayback?: boolean;
|
||||
isEffectEmoji?: boolean;
|
||||
noShowPremium?: boolean;
|
||||
noIcons?: boolean;
|
||||
clickArg: T;
|
||||
onClick?: (arg: OwnProps<T>['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
observeIntersection: ObserveFn;
|
||||
observeIntersectionForShowing?: ObserveFn;
|
||||
noShowPremium?: boolean;
|
||||
onClick?: (arg: OwnProps<T>['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 = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
isSavedMessages,
|
||||
isStatusPicker,
|
||||
canViewSet,
|
||||
observeIntersection,
|
||||
observeIntersectionForShowing,
|
||||
isSelected,
|
||||
isCurrentUserPremium,
|
||||
shouldIgnorePremium,
|
||||
@ -86,15 +83,16 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
sharedCanvasRef,
|
||||
withTranslucentThumb,
|
||||
forcePlayback,
|
||||
onClick,
|
||||
isEffectEmoji,
|
||||
noIcons,
|
||||
clickArg,
|
||||
onClick,
|
||||
observeIntersection,
|
||||
observeIntersectionForShowing,
|
||||
onFaveClick,
|
||||
onUnfaveClick,
|
||||
onRemoveRecentClick,
|
||||
onContextMenuOpen,
|
||||
onContextMenuClose,
|
||||
onContextMenuClick,
|
||||
isEffectEmoji,
|
||||
onDismiss,
|
||||
}: OwnProps<T>) => {
|
||||
const { openStickerSet, openPremiumModal, setEmojiStatus } = getActions();
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
@ -109,6 +107,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
|
||||
const isPremium = !sticker.isFree || sticker.hasEffect;
|
||||
const isCustomEmoji = sticker.isCustomEmoji || isEffectEmoji;
|
||||
const isPremiumSticker = !isCustomEmoji && isPremium;
|
||||
const isLocked = !isCurrentUserPremium && isPremium && !shouldIgnorePremium;
|
||||
|
||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||
@ -131,14 +130,6 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
});
|
||||
const getLayout = useLastCallback(() => ({ 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 = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
} else {
|
||||
openPremiumModal({ initialSection: 'premium_stickers' });
|
||||
}
|
||||
onContextMenuClose?.();
|
||||
onDismiss?.();
|
||||
return;
|
||||
}
|
||||
onClick?.(clickArg);
|
||||
@ -200,7 +191,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
e.stopPropagation();
|
||||
|
||||
handleContextMenuClose();
|
||||
onContextMenuClick?.();
|
||||
onDismiss?.();
|
||||
setEmojiStatus({
|
||||
emojiStatus: { type: 'regular', documentId: sticker.id, until: getServerTime() + duration },
|
||||
});
|
||||
@ -310,14 +301,14 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
forceAlways={forcePlayback}
|
||||
/>
|
||||
)}
|
||||
{!noShowPremium && isLocked && (
|
||||
{!noIcons && !noShowPremium && isLocked && (
|
||||
<div
|
||||
className="sticker-locked"
|
||||
>
|
||||
<Icon name="lock-badge" />
|
||||
</div>
|
||||
)}
|
||||
{!noShowPremium && isPremium && !isLocked && (
|
||||
{!noIcons && !noShowPremium && isPremiumSticker && !isLocked && (
|
||||
<div className="sticker-premium">
|
||||
<Icon name="star" />
|
||||
</div>
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
const StickerSet = ({
|
||||
stickerSet,
|
||||
loadAndPlay,
|
||||
index,
|
||||
@ -110,6 +107,7 @@ const StickerSet: FC<OwnProps & StateProps> = ({
|
||||
isTranslucent,
|
||||
noContextMenus,
|
||||
forcePlayback,
|
||||
collectibleStatuses,
|
||||
observeIntersection,
|
||||
observeIntersectionForPlayingItems,
|
||||
observeIntersectionForShowingItems,
|
||||
@ -119,11 +117,8 @@ const StickerSet: FC<OwnProps & StateProps> = ({
|
||||
onStickerUnfave,
|
||||
onStickerFave,
|
||||
onStickerRemoveRecent,
|
||||
onContextMenuOpen,
|
||||
onContextMenuClose,
|
||||
onContextMenuClick,
|
||||
collectibleStatuses,
|
||||
}) => {
|
||||
onDismiss,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
clearRecentStickers,
|
||||
clearRecentCustomEmoji,
|
||||
@ -401,13 +396,12 @@ const StickerSet: FC<OwnProps & StateProps> = ({
|
||||
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
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
.foldersSidebar {
|
||||
width: var(--tabs-sidebar-width);
|
||||
width: var(--folders-sidebar-width);
|
||||
height: 100%;
|
||||
background: var(--color-background-sidebar);
|
||||
}
|
||||
|
||||
@ -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<OwnProps>(
|
||||
(global, { isMobile }): Complete<StateProps> => {
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
const StatusPickerMenu = ({
|
||||
isOpen,
|
||||
statusButtonRef,
|
||||
areFeaturedStickersLoaded,
|
||||
isTranslucent,
|
||||
onEmojiStatusSelect,
|
||||
onClose,
|
||||
}) => {
|
||||
}: OwnProps & StateProps) => {
|
||||
const { loadFeaturedEmojiStickers } = getActions();
|
||||
|
||||
const transformOriginX = useRef<number>();
|
||||
const [isContextMenuShown, markContextMenuShown, unmarkContextMenuShown] = useFlag();
|
||||
const transformOriginX = useRef<number>(0);
|
||||
useEffect(() => {
|
||||
transformOriginX.current = statusButtonRef.current!.getBoundingClientRect().right;
|
||||
}, [isOpen, statusButtonRef]);
|
||||
@ -60,11 +57,10 @@ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
|
||||
<Menu
|
||||
isOpen={isOpen}
|
||||
noCompact
|
||||
positionX="right"
|
||||
positionX="left"
|
||||
bubbleClassName={styles.menuContent}
|
||||
onClose={onClose}
|
||||
transformOriginX={transformOriginX.current}
|
||||
noCloseOnBackdrop={isContextMenuShown}
|
||||
>
|
||||
<CustomEmojiPicker
|
||||
idPrefix="status-emoji-set-"
|
||||
@ -72,10 +68,8 @@ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
|
||||
isHidden={!isOpen}
|
||||
isStatusPicker
|
||||
isTranslucent={isTranslucent}
|
||||
onContextMenuOpen={markContextMenuShown}
|
||||
onContextMenuClose={unmarkContextMenuShown}
|
||||
onDismiss={onClose}
|
||||
onCustomEmojiSelect={handleEmojiSelect}
|
||||
onContextMenuClick={onClose}
|
||||
/>
|
||||
</Menu>
|
||||
</Portal>
|
||||
|
||||
@ -47,8 +47,7 @@ const FolderIconPickerMenu = ({
|
||||
loadAndPlay={isOpen}
|
||||
isHidden={!isOpen}
|
||||
onCustomEmojiSelect={(emoji) => handleEmojiSelect(emoji)}
|
||||
onContextMenuClick={onClose}
|
||||
onContextMenuClose={onClose}
|
||||
onDismiss={onClose}
|
||||
/>
|
||||
</div>
|
||||
</Menu>
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
});
|
||||
}, [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<OwnProps & StateProps> = ({
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -460,7 +461,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
recommendedChatFolders,
|
||||
maxFolders: selectCurrentLimit(global, 'dialogFilters'),
|
||||
areTagsEnabled,
|
||||
tabsPosition: global.sharedState.settings.tabsPosition,
|
||||
foldersPosition: global.sharedState.settings.foldersPosition,
|
||||
};
|
||||
},
|
||||
)(SettingsFoldersMain));
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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={(
|
||||
<FolderIcon
|
||||
emoji={(tab.emoticon as string)}
|
||||
customEmojiId={(tab.emoticon as ApiMessageEntityCustomEmoji)?.documentId}
|
||||
shouldAnimate={tab.noTitleAnimations}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<OwnProps>(
|
||||
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<OwnProps>(
|
||||
isSynced: global.isSynced,
|
||||
isAccountFrozen,
|
||||
isAppConfigLoaded: global.isAppConfigLoaded,
|
||||
isFoldersSidebarShown: tabsPosition === TABS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global),
|
||||
isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global),
|
||||
};
|
||||
},
|
||||
)(Main));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<HTMLDivElement>();
|
||||
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}
|
||||
>
|
||||
<div className={styles.icon}>
|
||||
{icon}
|
||||
<FolderIcon
|
||||
emoji={typeof icon === 'string' ? icon : undefined}
|
||||
customEmojiId={typeof icon === 'object' ? icon.documentId : undefined}
|
||||
shouldAnimate={isHovering}
|
||||
/>
|
||||
{Boolean(badgeCount) && (
|
||||
<span className={buildClassName(styles.badge, isBadgeActive && styles.badgeActive)}>{badgeCount}</span>
|
||||
)}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<T extends GlobalState>(global: T) {
|
||||
|
||||
function reduceCustomEmojis<T extends GlobalState>(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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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' });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user