Message List, Shared Media, Left Search, Symbol Menu: Add transition to loading spinner
This commit is contained in:
parent
c0dbdc35a0
commit
b66048aad2
@ -1,7 +1,7 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { FC } from '@teact';
|
||||
import {
|
||||
memo, useEffect, useMemo, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
} from '@teact';
|
||||
import { getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
@ -48,6 +48,7 @@ import { useStickerPickerObservers } from './hooks/useStickerPickerObservers';
|
||||
import StickerSetCover from '../middle/composer/StickerSetCover';
|
||||
import Button from '../ui/Button';
|
||||
import Loading from '../ui/Loading';
|
||||
import Transition from '../ui/Transition.tsx';
|
||||
import Icon from './icons/Icon';
|
||||
import StickerButton from './StickerButton';
|
||||
import StickerSet from './StickerSet';
|
||||
@ -397,19 +398,6 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const fullClassName = buildClassName('StickerPicker', styles.root, className);
|
||||
|
||||
if (!shouldRenderContent) {
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
{noPopulatedSets ? (
|
||||
<div className={pickerStyles.pickerDisabled}>{oldLang('NoStickers')}</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const headerClassName = buildClassName(
|
||||
pickerStyles.header,
|
||||
'no-scrollbar',
|
||||
@ -423,62 +411,72 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
pickerStyles.hasHeader,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={headerClassName}
|
||||
>
|
||||
<div className="shared-canvas-container">
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
<canvas ref={sharedCanvasHqRef} className="shared-canvas" />
|
||||
{allSets.map(renderCover)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
onScroll={handleContentScroll}
|
||||
className={listClassName}
|
||||
>
|
||||
{allSets.map((stickerSet, i) => {
|
||||
const shouldHideHeader = stickerSet.id === TOP_SYMBOL_SET_ID
|
||||
|| (stickerSet.id === RECENT_SYMBOL_SET_ID && (withDefaultTopicIcons || isStatusPicker));
|
||||
const isChatEmojiSet = stickerSet.id === chatEmojiSetId;
|
||||
const isLoading = !shouldRenderContent && !noPopulatedSets;
|
||||
|
||||
return (
|
||||
<StickerSet
|
||||
key={stickerSet.id}
|
||||
stickerSet={stickerSet}
|
||||
loadAndPlay={Boolean(canAnimate && canLoadAndPlay)}
|
||||
index={i}
|
||||
idPrefix={prefix}
|
||||
observeIntersection={observeIntersectionForSet}
|
||||
observeIntersectionForPlayingItems={observeIntersectionForPlayingItems}
|
||||
observeIntersectionForShowingItems={observeIntersectionForShowingItems}
|
||||
isNearActive={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
|
||||
isSavedMessages={isSavedMessages}
|
||||
isStatusPicker={isStatusPicker}
|
||||
isReactionPicker={isReactionPicker}
|
||||
shouldHideHeader={shouldHideHeader}
|
||||
withDefaultTopicIcon={withDefaultTopicIcons && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
||||
withDefaultStatusIcon={isStatusPicker && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
||||
isChatEmojiSet={isChatEmojiSet}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
selectedReactionIds={selectedReactionIds}
|
||||
availableReactions={availableReactions}
|
||||
isTranslucent={isTranslucent}
|
||||
onReactionSelect={onReactionSelect}
|
||||
onReactionContext={onReactionContext}
|
||||
onStickerSelect={handleEmojiSelect}
|
||||
onContextMenuOpen={onContextMenuOpen}
|
||||
onContextMenuClose={onContextMenuClose}
|
||||
onContextMenuClick={onContextMenuClick}
|
||||
forcePlayback
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<Transition className={fullClassName} name="fade" activeKey={isLoading ? 0 : 1} shouldCleanup>
|
||||
{!shouldRenderContent && !noPopulatedSets ? (
|
||||
<Loading />
|
||||
) : !shouldRenderContent && noPopulatedSets ? (
|
||||
<div className={pickerStyles.pickerDisabled}>{oldLang('NoStickers')}</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={headerClassName}
|
||||
>
|
||||
<div className="shared-canvas-container">
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
<canvas ref={sharedCanvasHqRef} className="shared-canvas" />
|
||||
{allSets.map(renderCover)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
onScroll={handleContentScroll}
|
||||
className={listClassName}
|
||||
>
|
||||
{allSets.map((stickerSet, i) => {
|
||||
const shouldHideHeader = stickerSet.id === TOP_SYMBOL_SET_ID
|
||||
|| (stickerSet.id === RECENT_SYMBOL_SET_ID && (withDefaultTopicIcons || isStatusPicker));
|
||||
const isChatEmojiSet = stickerSet.id === chatEmojiSetId;
|
||||
|
||||
return (
|
||||
<StickerSet
|
||||
key={stickerSet.id}
|
||||
stickerSet={stickerSet}
|
||||
loadAndPlay={Boolean(canAnimate && canLoadAndPlay)}
|
||||
index={i}
|
||||
idPrefix={prefix}
|
||||
observeIntersection={observeIntersectionForSet}
|
||||
observeIntersectionForPlayingItems={observeIntersectionForPlayingItems}
|
||||
observeIntersectionForShowingItems={observeIntersectionForShowingItems}
|
||||
isNearActive={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
|
||||
isSavedMessages={isSavedMessages}
|
||||
isStatusPicker={isStatusPicker}
|
||||
isReactionPicker={isReactionPicker}
|
||||
shouldHideHeader={shouldHideHeader}
|
||||
withDefaultTopicIcon={withDefaultTopicIcons && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
||||
withDefaultStatusIcon={isStatusPicker && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
||||
isChatEmojiSet={isChatEmojiSet}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
selectedReactionIds={selectedReactionIds}
|
||||
availableReactions={availableReactions}
|
||||
isTranslucent={isTranslucent}
|
||||
onReactionSelect={onReactionSelect}
|
||||
onReactionContext={onReactionContext}
|
||||
onStickerSelect={handleEmojiSelect}
|
||||
onContextMenuOpen={onContextMenuOpen}
|
||||
onContextMenuClose={onContextMenuClose}
|
||||
onContextMenuClick={onContextMenuClick}
|
||||
forcePlayback
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ import Audio from '../../common/Audio';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
|
||||
export type OwnProps = {
|
||||
isVoice?: boolean;
|
||||
@ -126,7 +127,12 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div className="LeftSearch--content">
|
||||
<Transition
|
||||
slideClassName="LeftSearch--content"
|
||||
name="fade"
|
||||
activeKey={canRenderContents ? 1 : 0}
|
||||
shouldCleanup
|
||||
>
|
||||
<InfiniteScroll
|
||||
className="search-content documents-list custom-scroll"
|
||||
items={canRenderContents ? foundMessages : undefined}
|
||||
@ -143,7 +149,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{canRenderContents && foundIds && foundIds.length > 0 && renderList()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ import NothingFound from '../../common/NothingFound';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Link from '../../ui/Link';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
import LeftSearchResultChat from './LeftSearchResultChat';
|
||||
|
||||
export type OwnProps = {
|
||||
@ -79,7 +80,13 @@ const BotAppResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch--content">
|
||||
<Transition
|
||||
ref={containerRef}
|
||||
slideClassName="LeftSearch--content"
|
||||
name="fade"
|
||||
activeKey={canRenderContents ? 1 : 0}
|
||||
shouldCleanup
|
||||
>
|
||||
<InfiniteScroll
|
||||
className="search-content custom-scroll"
|
||||
items={canRenderContents ? filteredFoundIds : undefined}
|
||||
@ -134,7 +141,7 @@ const BotAppResults: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ import Document from '../../common/Document';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
|
||||
export type OwnProps = {
|
||||
searchQuery?: string;
|
||||
@ -129,7 +130,13 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch--content">
|
||||
<Transition
|
||||
ref={containerRef}
|
||||
slideClassName="LeftSearch--content"
|
||||
name="fade"
|
||||
activeKey={canRenderContents ? 1 : 0}
|
||||
shouldCleanup
|
||||
>
|
||||
<InfiniteScroll
|
||||
className="search-content documents-list custom-scroll"
|
||||
items={canRenderContents ? foundMessages : undefined}
|
||||
@ -146,7 +153,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{canRenderContents && foundIds && foundIds.length > 0 && renderList()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import NothingFound from '../../common/NothingFound';
|
||||
import WebLink from '../../common/WebLink';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
|
||||
export type OwnProps = {
|
||||
searchQuery?: string;
|
||||
@ -122,7 +123,13 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch--content">
|
||||
<Transition
|
||||
ref={containerRef}
|
||||
slideClassName="LeftSearch--content"
|
||||
name="fade"
|
||||
activeKey={canRenderContents ? 1 : 0}
|
||||
shouldCleanup
|
||||
>
|
||||
<InfiniteScroll
|
||||
className="search-content documents-list custom-scroll"
|
||||
items={canRenderContents ? foundMessages : undefined}
|
||||
@ -139,7 +146,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{canRenderContents && foundIds && foundIds.length > 0 && renderList()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import Media from '../../common/Media';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
export type OwnProps = {
|
||||
@ -122,7 +123,13 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch--content LeftSearch--media">
|
||||
<Transition
|
||||
ref={containerRef}
|
||||
slideClassName="LeftSearch--content LeftSearch--media"
|
||||
name="fade"
|
||||
activeKey={canRenderContents ? 1 : 0}
|
||||
shouldCleanup
|
||||
>
|
||||
<InfiniteScroll
|
||||
className={classNames}
|
||||
items={canRenderContents ? foundMessages : undefined}
|
||||
@ -141,7 +148,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
||||
{isMediaGrid && renderGallery()}
|
||||
{isMessageList && renderSearchResult()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import {
|
||||
beginHeavyAnimation, memo, useEffect, useMemo, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { addExtraClass, removeExtraClass } from '../../lib/teact/teact-dom';
|
||||
import type { FC } from '@teact';
|
||||
import { beginHeavyAnimation, memo, useEffect, useMemo, useRef } from '@teact';
|
||||
import { addExtraClass, removeExtraClass } from '@teact/teact-dom.ts';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
ApiChatFullInfo,
|
||||
ApiMessage,
|
||||
ApiRestrictionReason,
|
||||
ApiTopic,
|
||||
} from '../../api/types';
|
||||
import type { ApiChatFullInfo, ApiMessage, ApiRestrictionReason, ApiTopic } from '../../api/types';
|
||||
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import { LoadMoreDirection, type MessageListType, type ThreadId } from '../../types';
|
||||
@ -83,6 +76,7 @@ import useContainerHeight from './hooks/useContainerHeight';
|
||||
import useStickyDates from './hooks/useStickyDates';
|
||||
|
||||
import Loading from '../ui/Loading';
|
||||
import Transition from '../ui/Transition.tsx';
|
||||
import ContactGreeting from './ContactGreeting';
|
||||
import MessageListAccountInfo from './MessageListAccountInfo';
|
||||
import MessageListContent from './MessageListContent';
|
||||
@ -150,6 +144,17 @@ type StateProps = {
|
||||
shouldAutoTranslate?: boolean;
|
||||
};
|
||||
|
||||
enum Content {
|
||||
Loading,
|
||||
Restricted,
|
||||
StarsRequired,
|
||||
PremiumRequired,
|
||||
AccountInfo,
|
||||
ContactGreeting,
|
||||
NoMessages,
|
||||
MessageList,
|
||||
}
|
||||
|
||||
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
||||
const MESSAGE_COMMENTS_POLLING_INTERVAL = 20 * 1000;
|
||||
const MESSAGE_FACT_CHECK_UPDATE_INTERVAL = 5 * 1000;
|
||||
@ -711,73 +716,98 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
onScrollDownToggle(false);
|
||||
}, [hasMessages, onScrollDownToggle]);
|
||||
|
||||
const activeKey = isRestricted ? (
|
||||
Content.Restricted
|
||||
) : paidMessagesStars && !hasMessages && !hasCustomGreeting ? (
|
||||
Content.StarsRequired
|
||||
) : isContactRequirePremium && !hasMessages ? (
|
||||
Content.PremiumRequired
|
||||
) : (isBot || isNonContact) && !hasMessages ? (
|
||||
Content.AccountInfo
|
||||
) : shouldRenderGreeting ? (
|
||||
Content.ContactGreeting
|
||||
) : messageIds && (!messageGroups || isGroupChatJustCreated || isEmptyTopic) ? (
|
||||
Content.NoMessages
|
||||
) : hasMessages ? (
|
||||
Content.MessageList
|
||||
) : (
|
||||
Content.Loading
|
||||
);
|
||||
|
||||
function renderContent() {
|
||||
return activeKey === Content.Restricted ? (
|
||||
<div className="empty">
|
||||
<span>
|
||||
{restrictionReasons?.[0]?.text || `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
||||
</span>
|
||||
</div>
|
||||
) : activeKey === Content.StarsRequired ? (
|
||||
<RequirementToContactMessage paidMessagesStars={paidMessagesStars} peerId={monoforumChannelId || chatId} />
|
||||
) : activeKey === Content.PremiumRequired ? (
|
||||
<RequirementToContactMessage peerId={chatId} />
|
||||
) : activeKey === Content.AccountInfo ? (
|
||||
<MessageListAccountInfo chatId={chatId} hasMessages={hasMessages} />
|
||||
) : activeKey === Content.ContactGreeting ? (
|
||||
<ContactGreeting key={chatId} userId={chatId} />
|
||||
) : activeKey === Content.NoMessages ? (
|
||||
<NoMessages
|
||||
chatId={chatId}
|
||||
topic={topic}
|
||||
type={type}
|
||||
isChatWithSelf={isChatWithSelf}
|
||||
isGroupChatJustCreated={isGroupChatJustCreated}
|
||||
/>
|
||||
) : activeKey === Content.MessageList ? (
|
||||
<MessageListContent
|
||||
canShowAds={areAdsEnabled && isChannelChat}
|
||||
chatId={chatId}
|
||||
isComments={isComments}
|
||||
isChannelChat={isChannelChat}
|
||||
isChatMonoforum={isChatMonoforum}
|
||||
isSavedDialog={isSavedDialog}
|
||||
messageIds={messageIds || [lastMessage!.id]}
|
||||
messageGroups={messageGroups || groupMessages([lastMessage!])}
|
||||
getContainerHeight={getContainerHeight}
|
||||
isViewportNewest={Boolean(isViewportNewest)}
|
||||
isUnread={Boolean(firstUnreadId)}
|
||||
isEmptyThread={isEmptyThread}
|
||||
withUsers={withUsers}
|
||||
noAvatars={noAvatars}
|
||||
containerRef={containerRef}
|
||||
anchorIdRef={anchorIdRef}
|
||||
memoUnreadDividerBeforeIdRef={memoUnreadDividerBeforeIdRef}
|
||||
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
||||
threadId={threadId}
|
||||
type={type}
|
||||
isReady={isReady}
|
||||
hasLinkedChat={hasLinkedChat}
|
||||
isSchedule={messageGroups ? type === 'scheduled' : false}
|
||||
shouldRenderAccountInfo={isBot || isNonContact}
|
||||
nameChangeDate={nameChangeDate}
|
||||
photoChangeDate={photoChangeDate}
|
||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||
onScrollDownToggle={onScrollDownToggle}
|
||||
onNotchToggle={onNotchToggle}
|
||||
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||
canPost={canPost}
|
||||
/>
|
||||
) : (
|
||||
<Loading color="white" backgroundColor="dark" />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
<Transition
|
||||
ref={containerRef}
|
||||
className={className}
|
||||
name="fade"
|
||||
activeKey={activeKey}
|
||||
shouldCleanup
|
||||
onScroll={handleScroll}
|
||||
onMouseDown={preventMessageInputBlur}
|
||||
>
|
||||
{isRestricted ? (
|
||||
<div className="empty">
|
||||
<span>
|
||||
{restrictionReasons?.[0]?.text || `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
||||
</span>
|
||||
</div>
|
||||
) : paidMessagesStars && !hasMessages && !hasCustomGreeting ? (
|
||||
<RequirementToContactMessage paidMessagesStars={paidMessagesStars} peerId={monoforumChannelId || chatId} />
|
||||
) : isContactRequirePremium && !hasMessages ? (
|
||||
<RequirementToContactMessage peerId={chatId} />
|
||||
) : (isBot || isNonContact) && !hasMessages ? (
|
||||
<MessageListAccountInfo chatId={chatId} hasMessages={hasMessages} />
|
||||
) : shouldRenderGreeting ? (
|
||||
<ContactGreeting key={chatId} userId={chatId} />
|
||||
) : messageIds && (!messageGroups || isGroupChatJustCreated || isEmptyTopic) ? (
|
||||
<NoMessages
|
||||
chatId={chatId}
|
||||
topic={topic}
|
||||
type={type}
|
||||
isChatWithSelf={isChatWithSelf}
|
||||
isGroupChatJustCreated={isGroupChatJustCreated}
|
||||
/>
|
||||
) : hasMessages ? (
|
||||
<MessageListContent
|
||||
canShowAds={areAdsEnabled && isChannelChat}
|
||||
chatId={chatId}
|
||||
isComments={isComments}
|
||||
isChannelChat={isChannelChat}
|
||||
isChatMonoforum={isChatMonoforum}
|
||||
isSavedDialog={isSavedDialog}
|
||||
messageIds={messageIds || [lastMessage!.id]}
|
||||
messageGroups={messageGroups || groupMessages([lastMessage!])}
|
||||
getContainerHeight={getContainerHeight}
|
||||
isViewportNewest={Boolean(isViewportNewest)}
|
||||
isUnread={Boolean(firstUnreadId)}
|
||||
isEmptyThread={isEmptyThread}
|
||||
withUsers={withUsers}
|
||||
noAvatars={noAvatars}
|
||||
containerRef={containerRef}
|
||||
anchorIdRef={anchorIdRef}
|
||||
memoUnreadDividerBeforeIdRef={memoUnreadDividerBeforeIdRef}
|
||||
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
||||
threadId={threadId}
|
||||
type={type}
|
||||
isReady={isReady}
|
||||
hasLinkedChat={hasLinkedChat}
|
||||
isSchedule={messageGroups ? type === 'scheduled' : false}
|
||||
shouldRenderAccountInfo={isBot || isNonContact}
|
||||
nameChangeDate={nameChangeDate}
|
||||
photoChangeDate={photoChangeDate}
|
||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||
onScrollDownToggle={onScrollDownToggle}
|
||||
onNotchToggle={onNotchToggle}
|
||||
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||
canPost={canPost}
|
||||
/>
|
||||
) : (
|
||||
<Loading color="white" backgroundColor="dark" />
|
||||
)}
|
||||
</div>
|
||||
{renderContent()}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,17 +1,10 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo, useEffect, useMemo,
|
||||
useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { memo, useEffect, useMemo, useRef, useState } from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../global';
|
||||
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
import type { IconName } from '../../../types/icons';
|
||||
import type {
|
||||
EmojiData,
|
||||
EmojiModule,
|
||||
EmojiRawData,
|
||||
} from '../../../util/emoji/emoji';
|
||||
import type { EmojiData, EmojiModule, EmojiRawData } from '../../../util/emoji/emoji';
|
||||
|
||||
import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config';
|
||||
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
||||
@ -34,6 +27,7 @@ import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
import EmojiCategory from './EmojiCategory';
|
||||
|
||||
import './EmojiPicker.scss';
|
||||
@ -206,46 +200,43 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const containerClassName = buildClassName('EmojiPicker', className);
|
||||
|
||||
if (!shouldRenderContent) {
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const headerClassName = buildClassName(
|
||||
'EmojiPicker-header',
|
||||
!shouldHideTopBorder && 'with-top-border',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={headerClassName}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
{allCategories.map(renderCategoryButton)}
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
onScroll={handleContentScroll}
|
||||
className={buildClassName('EmojiPicker-main', IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll')}
|
||||
>
|
||||
{allCategories.map((category, i) => (
|
||||
<EmojiCategory
|
||||
category={category}
|
||||
index={i}
|
||||
allEmojis={emojis}
|
||||
observeIntersection={observeIntersection}
|
||||
shouldRender={activeCategoryIndex >= i - 1 && activeCategoryIndex <= i + 1}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Transition className={containerClassName} activeKey={shouldRenderContent ? 1 : 0} name="fade" shouldCleanup>
|
||||
{!shouldRenderContent ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={headerClassName}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
{allCategories.map(renderCategoryButton)}
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
onScroll={handleContentScroll}
|
||||
className={buildClassName('EmojiPicker-main', IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll')}
|
||||
>
|
||||
{allCategories.map((category, i) => (
|
||||
<EmojiCategory
|
||||
category={category}
|
||||
index={i}
|
||||
allEmojis={emojis}
|
||||
observeIntersection={observeIntersection}
|
||||
shouldRender={activeCategoryIndex >= i - 1 && activeCategoryIndex <= i + 1}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -3,11 +3,6 @@
|
||||
top: 0.1875rem;
|
||||
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-auto-flow: dense;
|
||||
grid-auto-rows: 6.25rem;
|
||||
grid-gap: 0.125rem;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
|
||||
height: calc(100% - 0.1875rem);
|
||||
margin: 0 0.1875rem;
|
||||
@ -30,3 +25,11 @@
|
||||
border-radius: var(--border-radius-default) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.GifPickerGrid {
|
||||
display: grid;
|
||||
grid-auto-flow: dense;
|
||||
grid-auto-rows: 6.25rem;
|
||||
grid-gap: 0.125rem;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo, useEffect, useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiVideo } from '../../../api/types';
|
||||
@ -17,6 +15,7 @@ import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
|
||||
import GifButton from '../../common/GifButton';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
|
||||
import './GifPicker.scss';
|
||||
|
||||
@ -61,34 +60,37 @@ const GifPicker: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION);
|
||||
const isLoading = canSendGifs && (!canRenderContents || !savedGifs);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={buildClassName('GifPicker', className, IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll')}
|
||||
>
|
||||
{!canSendGifs ? (
|
||||
<div className="picker-disabled">Sending GIFs is not allowed in this chat.</div>
|
||||
) : canRenderContents && savedGifs && savedGifs.length ? (
|
||||
savedGifs.map((gif) => (
|
||||
<GifButton
|
||||
key={gif.id}
|
||||
gif={gif}
|
||||
observeIntersection={observeIntersection}
|
||||
isDisabled={!loadAndPlay}
|
||||
onClick={canSendGifs ? onGifSelect : undefined}
|
||||
onUnsaveClick={handleUnsaveClick}
|
||||
isSavedMessages={isSavedMessages}
|
||||
/>
|
||||
))
|
||||
) : canRenderContents && savedGifs ? (
|
||||
<div className="picker-disabled">No saved GIFs.</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Transition
|
||||
ref={containerRef}
|
||||
className={buildClassName('GifPicker', className, IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll')}
|
||||
slideClassName="GifPickerGrid"
|
||||
activeKey={isLoading ? 0 : 1}
|
||||
name="fade"
|
||||
shouldCleanup
|
||||
>
|
||||
{!canSendGifs ? (
|
||||
<div className="picker-disabled">Sending GIFs is not allowed in this chat.</div>
|
||||
) : canRenderContents && savedGifs && savedGifs.length ? (
|
||||
savedGifs.map((gif) => (
|
||||
<GifButton
|
||||
key={gif.id}
|
||||
gif={gif}
|
||||
observeIntersection={observeIntersection}
|
||||
isDisabled={!loadAndPlay}
|
||||
onClick={canSendGifs ? onGifSelect : undefined}
|
||||
onUnsaveClick={handleUnsaveClick}
|
||||
isSavedMessages={isSavedMessages}
|
||||
/>
|
||||
))
|
||||
) : canRenderContents && savedGifs ? (
|
||||
<div className="picker-disabled">No saved GIFs.</div>
|
||||
) : (
|
||||
<Loading color="yellow" />
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo, useEffect, useMemo,
|
||||
useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { memo, useEffect, useMemo, useRef } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiSticker, ApiStickerSet } from '../../../api/types';
|
||||
@ -19,7 +16,11 @@ import {
|
||||
STICKER_SIZE_PICKER_HEADER,
|
||||
} from '../../../config';
|
||||
import {
|
||||
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers,
|
||||
selectChat,
|
||||
selectChatFullInfo,
|
||||
selectIsChatWithSelf,
|
||||
selectIsCurrentUserPremium,
|
||||
selectShouldLoopStickers,
|
||||
} from '../../../global/selectors';
|
||||
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
||||
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
|
||||
@ -43,6 +44,7 @@ import StickerButton from '../../common/StickerButton';
|
||||
import StickerSet from '../../common/StickerSet';
|
||||
import Button from '../../ui/Button';
|
||||
import Loading from '../../ui/Loading';
|
||||
import Transition from '../../ui/Transition.tsx';
|
||||
import StickerSetCover from './StickerSetCover';
|
||||
|
||||
import styles from './StickerPicker.module.scss';
|
||||
@ -329,76 +331,75 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const fullClassName = buildClassName(styles.root, className);
|
||||
|
||||
if (!shouldRenderContents) {
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
{!canSendStickers && !isForEffects ? (
|
||||
<div className={styles.pickerDisabled}>{lang('ErrorSendRestrictedStickersAll')}</div>
|
||||
) : noPopulatedSets ? (
|
||||
<div className={styles.pickerDisabled}>{lang('NoStickers')}</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const headerClassName = buildClassName(
|
||||
styles.header,
|
||||
'no-scrollbar',
|
||||
!shouldHideTopBorder && styles.headerWithBorder,
|
||||
);
|
||||
|
||||
const isLoading = !shouldRenderContents && (canSendStickers || isForEffects) && !noPopulatedSets;
|
||||
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
{!isForEffects && (
|
||||
<div ref={headerRef} className={headerClassName}>
|
||||
<div className="shared-canvas-container">
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
{allSets.map(renderCover)}
|
||||
<Transition className={fullClassName} activeKey={isLoading ? 0 : 1} name="fade" shouldCleanup>
|
||||
{!shouldRenderContents ? (
|
||||
!canSendStickers && !isForEffects ? (
|
||||
<div className={styles.pickerDisabled}>{lang('ErrorSendRestrictedStickersAll')}</div>
|
||||
) : noPopulatedSets ? (
|
||||
<div className={styles.pickerDisabled}>{lang('NoStickers')}</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{!isForEffects && (
|
||||
<div ref={headerRef} className={headerClassName}>
|
||||
<div className="shared-canvas-container">
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
{allSets.map(renderCover)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
ref={containerRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
onScroll={handleContentScroll}
|
||||
className={
|
||||
buildClassName(
|
||||
styles.main,
|
||||
IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll',
|
||||
!isForEffects && styles.hasHeader,
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSets.map((stickerSet, i) => (
|
||||
<StickerSet
|
||||
key={stickerSet.id}
|
||||
stickerSet={stickerSet}
|
||||
loadAndPlay={Boolean(canAnimate && loadAndPlay)}
|
||||
noContextMenus={noContextMenus}
|
||||
index={i}
|
||||
idPrefix={prefix}
|
||||
observeIntersection={observeIntersectionForSet}
|
||||
observeIntersectionForPlayingItems={observeIntersectionForPlayingItems}
|
||||
observeIntersectionForShowingItems={observeIntersectionForShowingItems}
|
||||
isNearActive={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
|
||||
favoriteStickers={favoriteStickers}
|
||||
isSavedMessages={isSavedMessages}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
isTranslucent={isTranslucent}
|
||||
isChatStickerSet={stickerSet.id === chatStickerSetId}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
onStickerUnfave={handleStickerUnfave}
|
||||
onStickerFave={handleStickerFave}
|
||||
onStickerRemoveRecent={handleRemoveRecentSticker}
|
||||
forcePlayback
|
||||
shouldHideHeader={stickerSet.id === EFFECT_EMOJIS_SET_ID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
ref={containerRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
onScroll={handleContentScroll}
|
||||
className={
|
||||
buildClassName(
|
||||
styles.main,
|
||||
IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll',
|
||||
!isForEffects && styles.hasHeader,
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSets.map((stickerSet, i) => (
|
||||
<StickerSet
|
||||
key={stickerSet.id}
|
||||
stickerSet={stickerSet}
|
||||
loadAndPlay={Boolean(canAnimate && loadAndPlay)}
|
||||
noContextMenus={noContextMenus}
|
||||
index={i}
|
||||
idPrefix={prefix}
|
||||
observeIntersection={observeIntersectionForSet}
|
||||
observeIntersectionForPlayingItems={observeIntersectionForPlayingItems}
|
||||
observeIntersectionForShowingItems={observeIntersectionForShowingItems}
|
||||
isNearActive={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
|
||||
favoriteStickers={favoriteStickers}
|
||||
isSavedMessages={isSavedMessages}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
isTranslucent={isTranslucent}
|
||||
isChatStickerSet={stickerSet.id === chatStickerSetId}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
onStickerUnfave={handleStickerUnfave}
|
||||
onStickerFave={handleStickerFave}
|
||||
onStickerRemoveRecent={handleRemoveRecentSticker}
|
||||
forcePlayback
|
||||
shouldHideHeader={stickerSet.id === EFFECT_EMOJIS_SET_ID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -331,7 +331,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||
noCompact
|
||||
|
||||
{...(isAttachmentModal ? menuPositionOptions : {
|
||||
positionX: 'left',
|
||||
positionY: 'bottom',
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import {
|
||||
memo, useCallback,
|
||||
useEffect, useMemo, useRef, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import type { FC } from '@teact';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from '@teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
@ -16,20 +13,12 @@ import type {
|
||||
ApiUserStatus,
|
||||
} from '../../api/types';
|
||||
import type { TabState } from '../../global/types';
|
||||
import type {
|
||||
AnimationLevel,
|
||||
ProfileState, ProfileTabType, SharedMediaType, ThemeKey, ThreadId,
|
||||
} from '../../types';
|
||||
import type { AnimationLevel, ProfileState, ProfileTabType, SharedMediaType, ThemeKey, ThreadId } from '../../types';
|
||||
import type { RegularLangKey } from '../../types/language';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import { AudioOrigin, MediaViewerOrigin, NewChatMembersProgress } from '../../types';
|
||||
|
||||
import {
|
||||
MEMBERS_SLICE,
|
||||
PROFILE_SENSITIVE_AREA,
|
||||
SHARED_MEDIA_SLICE,
|
||||
SLIDE_TRANSITION_DURATION,
|
||||
} from '../../config';
|
||||
import { MEMBERS_SLICE, PROFILE_SENSITIVE_AREA, SHARED_MEDIA_SLICE, SLIDE_TRANSITION_DURATION } from '../../config';
|
||||
import {
|
||||
getHasAdminRight,
|
||||
getIsDownloading,
|
||||
@ -592,8 +581,19 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if ((!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById) {
|
||||
const noSpinner = isFirstTab && !canRenderContent;
|
||||
const noContent = (!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById;
|
||||
const noSpinner = isFirstTab && !canRenderContent;
|
||||
const isSpinner = noContent && !noSpinner;
|
||||
|
||||
return (
|
||||
<Transition activeKey={isSpinner ? 0 : 1} name="fade">
|
||||
{renderSpinnerOrContent(noContent, noSpinner)}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSpinnerOrContent(noContent: boolean, noSpinner: boolean) {
|
||||
if (noContent) {
|
||||
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
||||
|
||||
return (
|
||||
@ -651,6 +651,11 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!messagesById) {
|
||||
// A TypeScript assertion, should never be really reached
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`content ${resultType}-list`}
|
||||
|
||||
@ -57,7 +57,6 @@ const ResponsiveHoverButton: FC<OwnProps> = ({ onActivate, ...buttonProps }) =>
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
||||
{...buttonProps}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
|
||||
@ -241,12 +241,12 @@
|
||||
&-fadeBackwards {
|
||||
> .Transition_slide-from {
|
||||
opacity: 1;
|
||||
animation: fade-out-opacity 0.15s ease;
|
||||
animation: fade-out-opacity 0.2s ease;
|
||||
}
|
||||
|
||||
> .Transition_slide-to {
|
||||
opacity: 0;
|
||||
animation: fade-in-opacity 0.15s ease;
|
||||
animation: fade-in-opacity 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,6 +49,8 @@ export type TransitionProps = {
|
||||
isBlockingAnimation?: boolean;
|
||||
onStart?: NoneToVoidFunction;
|
||||
onStop?: NoneToVoidFunction;
|
||||
onScroll?: NoneToVoidFunction;
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
children: React.ReactNode | ChildrenFn;
|
||||
};
|
||||
|
||||
@ -89,6 +91,8 @@ function Transition({
|
||||
isBlockingAnimation,
|
||||
onStart,
|
||||
onStop,
|
||||
onScroll,
|
||||
onMouseDown,
|
||||
children,
|
||||
}: TransitionProps) {
|
||||
const currentKeyRef = useRef<number>();
|
||||
@ -379,6 +383,8 @@ function Transition({
|
||||
id={id}
|
||||
className={buildClassName('Transition', className)}
|
||||
teactFastList={asFastList}
|
||||
onScroll={onScroll}
|
||||
onMouseDown={onMouseDown}
|
||||
>
|
||||
{contents}
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user