Profile: Display gift and story collections (#6190)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
parent
4fa82cc83c
commit
ca8260c309
@ -9,6 +9,7 @@ import type {
|
||||
ApiStarGiftAttribute,
|
||||
ApiStarGiftAttributeCounter,
|
||||
ApiStarGiftAttributeId,
|
||||
ApiStarGiftCollection,
|
||||
ApiTypeResaleStarGifts,
|
||||
} from '../../types';
|
||||
|
||||
@ -287,3 +288,21 @@ GramJs.TypeStarGiftAttributeId[] {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function buildApiStarGiftCollection(collection: GramJs.StarGiftCollection): ApiStarGiftCollection | undefined {
|
||||
if (!collection) return undefined;
|
||||
|
||||
const { collectionId, title, icon, giftsCount, hash } = collection;
|
||||
|
||||
if (icon) {
|
||||
addDocumentToLocalDb(icon);
|
||||
}
|
||||
|
||||
return {
|
||||
collectionId,
|
||||
title,
|
||||
icon: icon && buildStickerFromDocument(icon),
|
||||
giftsCount,
|
||||
hash: hash.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import type {
|
||||
ApiMediaAreaCoordinates,
|
||||
ApiStealthMode,
|
||||
ApiStory,
|
||||
ApiStoryAlbum,
|
||||
ApiStoryForwardInfo,
|
||||
ApiStoryView,
|
||||
ApiStoryViews,
|
||||
@ -14,8 +15,10 @@ import type {
|
||||
} from '../../types';
|
||||
|
||||
import { buildCollectionByCallback, omitUndefined } from '../../../util/iteratees';
|
||||
import { buildPrivacyRules } from './common';
|
||||
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||
import { addPhotoToLocalDb } from '../helpers/localDb';
|
||||
import { buildApiPhoto, buildPrivacyRules } from './common';
|
||||
import { buildApiDocument, buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||
import { buildApiMessage } from './messages';
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||
import { buildApiReaction, buildReactionCount } from './reactions';
|
||||
@ -279,3 +282,23 @@ export function buildApiStoryForwardInfo(forwardHeader: GramJs.TypeStoryFwdHeade
|
||||
isModified: modified,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStoryAlbum(album: GramJs.StoryAlbum): ApiStoryAlbum {
|
||||
const {
|
||||
albumId, title, iconPhoto, iconVideo,
|
||||
} = album;
|
||||
|
||||
if (iconPhoto) {
|
||||
addPhotoToLocalDb(iconPhoto);
|
||||
}
|
||||
if (iconVideo) {
|
||||
addDocumentToLocalDb(iconVideo);
|
||||
}
|
||||
|
||||
return {
|
||||
albumId,
|
||||
title,
|
||||
iconPhoto: iconPhoto && iconPhoto instanceof GramJs.Photo ? buildApiPhoto(iconPhoto) : undefined,
|
||||
iconVideo: iconVideo ? buildApiDocument(iconVideo) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import type {
|
||||
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift,
|
||||
buildApiStarGiftAttribute, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts';
|
||||
buildApiStarGiftAttribute, buildApiStarGiftCollection, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts';
|
||||
import {
|
||||
buildApiCurrencyAmount,
|
||||
buildApiStarsGiftOptions,
|
||||
@ -112,11 +112,13 @@ export async function fetchSavedStarGifts({
|
||||
offset = DEFAULT_PRIMITIVES.STRING,
|
||||
limit = DEFAULT_PRIMITIVES.INT,
|
||||
filter,
|
||||
collectionId,
|
||||
}: {
|
||||
peer: ApiPeer;
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
filter?: GiftProfileFilterOptions;
|
||||
collectionId?: number;
|
||||
}) {
|
||||
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
||||
|
||||
@ -124,6 +126,7 @@ export async function fetchSavedStarGifts({
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
offset,
|
||||
limit,
|
||||
collectionId,
|
||||
...(filter && {
|
||||
sortByValue: filter.sortType === 'byValue' || undefined,
|
||||
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
||||
@ -476,3 +479,24 @@ export async function fetchStarGiftWithdrawalUrl({
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function fetchStarGiftCollections({
|
||||
peer,
|
||||
hash,
|
||||
}: {
|
||||
peer: ApiPeer;
|
||||
hash?: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftCollections({
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
hash: hash ? bigInt(hash) : DEFAULT_PRIMITIVES.BIGINT,
|
||||
}));
|
||||
|
||||
if (!result || result instanceof GramJs.payments.StarGiftCollectionsNotModified) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
collections: result.collections.map(buildApiStarGiftCollection).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import type {
|
||||
ApiPeerStories,
|
||||
ApiReaction,
|
||||
ApiStealthMode,
|
||||
ApiStoryAlbum,
|
||||
ApiTypeStory,
|
||||
} from '../../types';
|
||||
|
||||
@ -19,6 +20,7 @@ import {
|
||||
buildApiPeerStories,
|
||||
buildApiStealthMode,
|
||||
buildApiStory,
|
||||
buildApiStoryAlbum,
|
||||
buildApiStoryView,
|
||||
buildApiStoryViews,
|
||||
} from '../apiBuilders/stories';
|
||||
@ -463,3 +465,63 @@ export function activateStealthMode({
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchAlbums({
|
||||
peer,
|
||||
}: {
|
||||
peer: ApiPeer;
|
||||
}): Promise<ApiStoryAlbum[] | undefined> {
|
||||
const result = await invokeRequest(new GramJs.stories.GetAlbums({
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
hash: DEFAULT_PRIMITIVES.BIGINT,
|
||||
}));
|
||||
|
||||
if (!result || result instanceof GramJs.stories.AlbumsNotModified) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.albums.map(buildApiStoryAlbum);
|
||||
}
|
||||
|
||||
export async function fetchAlbumStories({
|
||||
peer,
|
||||
albumId,
|
||||
offset = 0,
|
||||
limit = STORY_LIST_LIMIT,
|
||||
}: {
|
||||
peer: ApiPeer;
|
||||
albumId: number;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
stories: Record<number, ApiTypeStory>;
|
||||
pinnedIds?: number[];
|
||||
count: number;
|
||||
} | undefined> {
|
||||
const result = await invokeRequest(new GramJs.stories.GetAlbumStories({
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
albumId,
|
||||
offset,
|
||||
limit,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const stories = buildCollectionByCallback(result.stories, (story) => (
|
||||
[story.id, buildApiStory(peer.id, story)]
|
||||
));
|
||||
|
||||
result.stories.forEach((story) => {
|
||||
if (story && story instanceof GramJs.StoryItem) {
|
||||
addStoryToLocalDb(story, peer.id);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
stories,
|
||||
pinnedIds: result.pinnedToTop,
|
||||
count: result.count,
|
||||
};
|
||||
}
|
||||
|
||||
@ -282,6 +282,14 @@ export interface ApiDisallowedGiftsSettings {
|
||||
shouldDisallowPremiumGifts?: true;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftCollection {
|
||||
collectionId: number;
|
||||
title: string;
|
||||
icon?: ApiSticker;
|
||||
giftsCount: number;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface ApiStarsRating {
|
||||
level: number;
|
||||
currentLevelStars: number;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type {
|
||||
ApiDocument,
|
||||
ApiGeoPoint,
|
||||
ApiMessage,
|
||||
ApiPhoto,
|
||||
ApiReaction,
|
||||
ApiReactionCount,
|
||||
ApiSticker,
|
||||
@ -69,6 +71,10 @@ export type ApiPeerStories = {
|
||||
isArchiveFullyLoaded?: boolean;
|
||||
lastUpdatedAt?: number;
|
||||
lastReadId?: number;
|
||||
idsByAlbumId?: Record<number, {
|
||||
ids: number[];
|
||||
isFullyLoaded?: boolean;
|
||||
}>; // Story IDs grouped by album ID with loading state
|
||||
};
|
||||
|
||||
export type ApiMessageStoryData = {
|
||||
@ -184,3 +190,10 @@ export type ApiMediaAreaUniqueGift = {
|
||||
|
||||
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction
|
||||
| ApiMediaAreaChannelPost | ApiMediaAreaUrl | ApiMediaAreaWeather | ApiMediaAreaUniqueGift;
|
||||
|
||||
export type ApiStoryAlbum = {
|
||||
albumId: number;
|
||||
title: string;
|
||||
iconPhoto?: ApiPhoto;
|
||||
iconVideo?: ApiDocument;
|
||||
};
|
||||
|
||||
@ -2235,6 +2235,8 @@
|
||||
"PublicPostsPremiumFeatureSubtitle" = "Global search is a Premium feature.";
|
||||
"PublicPostsSubscribeToPremium" = "Subscribe to Premium";
|
||||
"NotificationPaidExtraSearch" = "{stars} spent on extra search.";
|
||||
"PostsSearchTransaction" = "Posts Search";
|
||||
"AllStoriesCategory" = "All stories";
|
||||
"PostsSearchTransaction" = "Public Post Search";
|
||||
"TitleRating" = "Rating";
|
||||
"RatingReflectsActivity" = "This rating reflects {name}'s activity on Telegram. It is based on:";
|
||||
@ -2258,3 +2260,4 @@
|
||||
"ErrorFocusInaccessibleMessage" = "Unfortunately, you can't access this message. You aren't a member of the chat where it was posted.";
|
||||
"ContextMenuHintMouse" = "To edit or reply, close this menu. Then right click on a message.";
|
||||
"ContextMenuHintTouch" = "To edit or reply, close this menu. Then long tap on a message.";
|
||||
|
||||
|
||||
22
src/components/common/AnimatedTabItem.module.scss
Normal file
22
src/components/common/AnimatedTabItem.module.scss
Normal file
@ -0,0 +1,22 @@
|
||||
.item {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
padding: 0.375rem 0.75rem;
|
||||
|
||||
font-weight: var(--font-weight-medium);
|
||||
white-space: nowrap;
|
||||
|
||||
transition: opacity 0.15s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 0.375rem;
|
||||
}
|
||||
46
src/components/common/AnimatedTabItem.tsx
Normal file
46
src/components/common/AnimatedTabItem.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { memo } from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiSticker } from '../../api/types';
|
||||
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconFromSticker from './AnimatedIconFromSticker';
|
||||
|
||||
import styles from './AnimatedTabItem.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
sticker?: ApiSticker;
|
||||
onClick?: (id: string) => void;
|
||||
};
|
||||
|
||||
const AnimatedTabItem = ({
|
||||
id,
|
||||
title,
|
||||
sticker,
|
||||
onClick,
|
||||
}: OwnProps) => {
|
||||
const handleClick = useLastCallback(() => {
|
||||
onClick?.(id);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{sticker && (
|
||||
<AnimatedIconFromSticker
|
||||
className={styles.icon}
|
||||
sticker={sticker}
|
||||
size={20}
|
||||
forcePreview
|
||||
/>
|
||||
)}
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AnimatedTabItem);
|
||||
72
src/components/common/AnimatedTabList.module.scss
Normal file
72
src/components/common/AnimatedTabList.module.scss
Normal file
@ -0,0 +1,72 @@
|
||||
.container,
|
||||
.clipPathContainer {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--tab-gap-size, 0.25rem);
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding-inline: 0.5rem;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.container {
|
||||
user-select: none;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
overflow-x: auto;
|
||||
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: background-color 150ms, opacity 150ms;
|
||||
|
||||
mask-image: linear-gradient(to right, transparent, black 0.5rem, black calc(100% - 0.5rem), transparent);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
// `box-shadow` prevents repaint on macOS when hovering out of scrollable container
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.01);
|
||||
}
|
||||
|
||||
&.isVisible {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.clipPathContainer {
|
||||
// Hardware acceleration for clip-path animations
|
||||
will-change: clip-path;
|
||||
// Use GPU compositing for better performance
|
||||
isolation: isolate;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
// Optimize for animations
|
||||
contain: layout style paint;
|
||||
overflow: hidden;
|
||||
|
||||
width: fit-content;
|
||||
|
||||
color: var(--color-primary);
|
||||
|
||||
background-color: var(--color-interactive-element-hover);
|
||||
|
||||
transition: var(--slide-transition) clip-path;
|
||||
|
||||
&.noAnimation {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
114
src/components/common/AnimatedTabList.tsx
Normal file
114
src/components/common/AnimatedTabList.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import { memo, useEffect, useRef, useState } from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiSticker } from '../../api/types';
|
||||
import type { AnimationLevel } from '../../types';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||
|
||||
import AnimatedTabItem from './AnimatedTabItem';
|
||||
|
||||
import styles from './AnimatedTabList.module.scss';
|
||||
|
||||
export type TabItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
sticker?: ApiSticker;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
items: TabItem[];
|
||||
selectedItemId?: string;
|
||||
className?: string;
|
||||
animationLevel?: AnimationLevel;
|
||||
onItemSelect?: (itemId: string) => void;
|
||||
};
|
||||
|
||||
const AnimatedTabList = ({
|
||||
items,
|
||||
selectedItemId,
|
||||
animationLevel = 1,
|
||||
onItemSelect,
|
||||
className,
|
||||
}: OwnProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const clipPathContainerRef = useRef<HTMLDivElement>();
|
||||
const selectedIndex = items.findIndex((item) => item.id === selectedItemId) || 0;
|
||||
const [clipPath, setClipPath] = useState<string>('');
|
||||
const shouldAnimate = animationLevel > 0;
|
||||
|
||||
useHorizontalScroll(containerRef, !items.length, true);
|
||||
|
||||
const updateClipPath = useLastCallback(() => {
|
||||
const clipPathContainer = clipPathContainerRef.current;
|
||||
const activeTab = selectedIndex >= 0 && clipPathContainer?.childNodes[selectedIndex] as HTMLElement | null;
|
||||
|
||||
if (clipPathContainer && activeTab && clipPathContainer.offsetWidth > 0) {
|
||||
const { offsetLeft, offsetWidth } = activeTab;
|
||||
const containerWidth = clipPathContainer.offsetWidth;
|
||||
const left = (offsetLeft / containerWidth * 100).toFixed(1);
|
||||
const right = ((containerWidth - (offsetLeft + offsetWidth)) / containerWidth * 100).toFixed(1);
|
||||
|
||||
const newClipPath = `inset(0 ${right}% 0 ${left}% round 1rem)`;
|
||||
setClipPath(newClipPath);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateClipPath();
|
||||
}, [selectedIndex, items]);
|
||||
|
||||
useResizeObserver(clipPathContainerRef, updateClipPath);
|
||||
|
||||
if (!items.length) return undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={
|
||||
buildClassName(
|
||||
styles.container,
|
||||
'no-scrollbar',
|
||||
className,
|
||||
clipPath && styles.isVisible,
|
||||
)
|
||||
}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<AnimatedTabItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
sticker={item.sticker}
|
||||
onClick={onItemSelect}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div
|
||||
ref={clipPathContainerRef}
|
||||
className={buildClassName(
|
||||
styles.clipPathContainer,
|
||||
'clip-path-container',
|
||||
!shouldAnimate && styles.noAnimation,
|
||||
)}
|
||||
style={clipPath ? `clip-path: ${clipPath}` : undefined}
|
||||
aria-hidden
|
||||
>
|
||||
{items.map((item, i) => (
|
||||
<AnimatedTabItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
sticker={item.sticker}
|
||||
onClick={onItemSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AnimatedTabList);
|
||||
@ -101,7 +101,20 @@
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.shared-media-transition {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
transition: transform 0.3s;
|
||||
&.showContentPanel {
|
||||
transform: translateY(3rem);
|
||||
padding-bottom: 3.5rem !important;
|
||||
}
|
||||
&.noTransition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&.empty-list {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@ -210,4 +223,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contentCategoriesPanel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
|
||||
&.hiddenPanel {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.noTransition {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ import type {
|
||||
ApiChatMember,
|
||||
ApiMessage,
|
||||
ApiSavedStarGift,
|
||||
ApiStarGiftCollection,
|
||||
ApiStoryAlbum,
|
||||
ApiTypeStory,
|
||||
ApiUser,
|
||||
ApiUserStatus,
|
||||
@ -19,6 +21,8 @@ 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';
|
||||
|
||||
const CONTENT_PANEL_SHOW_DELAY = 300;
|
||||
import {
|
||||
getHasAdminRight,
|
||||
getIsDownloading,
|
||||
@ -53,6 +57,7 @@ import {
|
||||
import { selectPremiumLimit } from '../../global/selectors/limits';
|
||||
import { selectMessageDownloadableMedia } from '../../global/selectors/media';
|
||||
import { selectSharedSettings } from '../../global/selectors/sharedState';
|
||||
import { areDeepEqual } from '../../util/areDeepEqual';
|
||||
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
||||
@ -71,6 +76,7 @@ import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||
import useAsyncRendering from './hooks/useAsyncRendering';
|
||||
import useProfileState from './hooks/useProfileState';
|
||||
import useProfileViewportIds from './hooks/useProfileViewportIds';
|
||||
@ -100,6 +106,8 @@ import Spinner from '../ui/Spinner';
|
||||
import TabList from '../ui/TabList';
|
||||
import Transition from '../ui/Transition';
|
||||
import DeleteMemberModal from './DeleteMemberModal';
|
||||
import StarGiftCollectionList from './gifts/StarGiftCollectionList';
|
||||
import StoryAlbumList from './stories/StoryAlbumList';
|
||||
|
||||
import './Profile.scss';
|
||||
|
||||
@ -127,6 +135,8 @@ type StateProps = {
|
||||
hasPreviewMediaTab?: boolean;
|
||||
hasGiftsTab?: boolean;
|
||||
gifts?: ApiSavedStarGift[];
|
||||
storyAlbums?: ApiStoryAlbum[];
|
||||
giftCollections?: ApiStarGiftCollection[];
|
||||
areMembersHidden?: boolean;
|
||||
canAddMembers?: boolean;
|
||||
canDeleteMembers?: boolean;
|
||||
@ -137,6 +147,9 @@ type StateProps = {
|
||||
pinnedStoryIds?: number[];
|
||||
archiveStoryIds?: number[];
|
||||
storyByIds?: Record<number, ApiTypeStory>;
|
||||
selectedStoryAlbumId?: number;
|
||||
activeCollectionId?: number;
|
||||
giftsFilter?: any;
|
||||
chatsById: Record<string, ApiChat>;
|
||||
usersById: Record<string, ApiUser>;
|
||||
userStatusesById: Record<string, ApiUserStatus>;
|
||||
@ -189,6 +202,9 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
pinnedStoryIds,
|
||||
archiveStoryIds,
|
||||
storyByIds,
|
||||
selectedStoryAlbumId,
|
||||
activeCollectionId,
|
||||
giftsFilter,
|
||||
mediaSearchType,
|
||||
hasCommonChatsTab,
|
||||
hasStoriesTab,
|
||||
@ -196,6 +212,8 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
hasPreviewMediaTab,
|
||||
hasGiftsTab,
|
||||
gifts,
|
||||
storyAlbums,
|
||||
giftCollections,
|
||||
botPreviewMedia,
|
||||
areMembersHidden,
|
||||
canAddMembers,
|
||||
@ -241,6 +259,9 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
loadPreviewMedias,
|
||||
loadPeerSavedGifts,
|
||||
resetGiftProfileFilter,
|
||||
loadStarGiftCollections,
|
||||
loadStoryAlbums,
|
||||
resetSelectedStoryAlbum,
|
||||
} = getActions();
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
@ -250,10 +271,13 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
const lang = useLang();
|
||||
|
||||
const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
|
||||
const [isViewTransitionEnabled, enableViewTransition, disableViewTransition] = useFlag();
|
||||
|
||||
const profileId = isSavedDialog ? String(threadId) : chatId;
|
||||
const isSavedMessages = profileId === currentUserId && !isSavedDialog;
|
||||
|
||||
const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0);
|
||||
|
||||
const tabs = useMemo(() => {
|
||||
const arr: TabProps[] = [];
|
||||
if (isSavedMessages && !isSavedDialog) {
|
||||
@ -352,6 +376,25 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [chatId, isBot, similarBots, isSynced]);
|
||||
|
||||
useEffect(() => {
|
||||
resetSelectedStoryAlbum();
|
||||
}, [chatId]);
|
||||
|
||||
useSyncEffect(() => {
|
||||
enableViewTransition();
|
||||
}, [giftsFilter]);
|
||||
|
||||
useSyncEffect(() => {
|
||||
disableViewTransition();
|
||||
}, [gifts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasGiftsTab && isSynced) {
|
||||
loadStarGiftCollections({ peerId: chatId });
|
||||
loadStoryAlbums({ peerId: chatId });
|
||||
}
|
||||
}, [chatId, hasGiftsTab, isSynced]);
|
||||
|
||||
const [renderingGifts, setRenderingGifts] = useState(gifts);
|
||||
const { startViewTransition, shouldApplyVtn } = useViewTransition();
|
||||
|
||||
@ -371,12 +414,17 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
const handleLoadGifts = useCallback(() => {
|
||||
loadPeerSavedGifts({ peerId: chatId });
|
||||
}, [chatId]);
|
||||
|
||||
const handleLoadMoreMembers = useCallback(() => {
|
||||
loadMoreMembers({ chatId });
|
||||
}, [chatId, loadMoreMembers]);
|
||||
|
||||
useEffectWithPrevDeps(([prevGifts]) => {
|
||||
if (!gifts || !prevGifts) {
|
||||
if (areDeepEqual(gifts, prevGifts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gifts || !prevGifts || !isViewTransitionEnabled) {
|
||||
setRenderingGifts(gifts);
|
||||
return;
|
||||
}
|
||||
@ -385,14 +433,14 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
const newGiftIds = gifts.map((gift) => getSavedGiftKey(gift));
|
||||
const hasOrderChanged = prevGiftIds.some((id, index) => id !== newGiftIds[index]);
|
||||
|
||||
if (hasOrderChanged) {
|
||||
if (hasOrderChanged && animationLevel > 0) {
|
||||
startViewTransition(() => {
|
||||
setRenderingGifts(gifts);
|
||||
});
|
||||
} else {
|
||||
setRenderingGifts(gifts);
|
||||
}
|
||||
}, [gifts, startViewTransition]);
|
||||
}, [gifts, startViewTransition, animationLevel, isViewTransitionEnabled]);
|
||||
|
||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
|
||||
loadMoreMembers: handleLoadMoreMembers,
|
||||
@ -418,12 +466,40 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
similarChannels,
|
||||
similarBots,
|
||||
});
|
||||
|
||||
const isFirstTab = (isSavedMessages && resultType === 'dialogs')
|
||||
|| (hasStoriesTab && resultType === 'stories')
|
||||
|| resultType === 'members'
|
||||
|| (!hasMembersTab && resultType === 'media');
|
||||
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
||||
|
||||
const [isGiftCollectionsShowed, markGiftCollectionsShowed, unmarkGiftCollectionsShowed] = useFlag(false);
|
||||
const [isStoryAlbumsShowed, markStoryAlbumsShowed, unmarkStoryAlbums] = useFlag(false);
|
||||
|
||||
const hasGiftsCollections = giftCollections && giftCollections.length > 0;
|
||||
const hasStoryAlbums = storyAlbums && storyAlbums.length > 0;
|
||||
const isGiftsResult = resultType === 'gifts';
|
||||
const isStoriesResult = resultType === 'stories';
|
||||
const shouldShowContentPanel = (isGiftsResult && hasGiftsCollections) || (isStoriesResult && hasStoryAlbums);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasGiftsCollections) {
|
||||
setTimeout(() => {
|
||||
markGiftCollectionsShowed();
|
||||
}, CONTENT_PANEL_SHOW_DELAY);
|
||||
} else {
|
||||
unmarkGiftCollectionsShowed();
|
||||
}
|
||||
|
||||
if (hasStoryAlbums) {
|
||||
setTimeout(() => {
|
||||
markStoryAlbumsShowed();
|
||||
}, CONTENT_PANEL_SHOW_DELAY);
|
||||
} else {
|
||||
unmarkStoryAlbums();
|
||||
}
|
||||
}, [hasGiftsCollections, hasStoryAlbums, markGiftCollectionsShowed, markStoryAlbumsShowed]);
|
||||
|
||||
usePeerStoriesPolling(resultType === 'members' ? viewportIds as string[] : undefined);
|
||||
|
||||
const handleStopAutoScrollToTabs = useLastCallback(() => {
|
||||
@ -583,16 +659,48 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const noContent = (!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById;
|
||||
const noSpinner = isFirstTab && !canRenderContent;
|
||||
const isSpinner = noContent && !noSpinner;
|
||||
|
||||
return (
|
||||
<Transition activeKey={isSpinner ? 0 : 1} name="fade" shouldCleanup>
|
||||
<div>
|
||||
{renderCategories()}
|
||||
{renderSpinnerOrContent(noContent, noSpinner)}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSpinnerOrContent(noContent: boolean, noSpinner: boolean) {
|
||||
function renderCategories() {
|
||||
if (resultType === 'gifts') {
|
||||
return (
|
||||
<div
|
||||
className={buildClassName(
|
||||
'contentCategoriesPanel',
|
||||
!shouldShowContentPanel && 'hiddenPanel',
|
||||
isGiftCollectionsShowed && 'noTransition',
|
||||
)}
|
||||
>
|
||||
<StarGiftCollectionList peerId={chatId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (resultType === 'stories') {
|
||||
return (
|
||||
<div
|
||||
className={buildClassName(
|
||||
'contentCategoriesPanel',
|
||||
!shouldShowContentPanel && 'hiddenPanel',
|
||||
isStoryAlbumsShowed && 'noTransition',
|
||||
)}
|
||||
>
|
||||
<StoryAlbumList peerId={chatId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function renderSpinnerOrContentBase(noContent: boolean, noSpinner: boolean) {
|
||||
if (noContent) {
|
||||
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
||||
|
||||
@ -656,9 +764,15 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const noTransition = resultType === 'gifts' ? isGiftCollectionsShowed
|
||||
: resultType === 'stories' ? isStoryAlbumsShowed : false;
|
||||
return (
|
||||
<div
|
||||
className={`content ${resultType}-list`}
|
||||
className={buildClassName(
|
||||
`content ${resultType}-list`,
|
||||
shouldShowContentPanel && 'showContentPanel',
|
||||
noTransition && 'noTransition',
|
||||
)}
|
||||
dir={oldLang.isRtl && resultType === 'media' ? 'rtl' : undefined}
|
||||
teactFastList
|
||||
>
|
||||
@ -869,8 +983,69 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const activeListSelector = `.shared-media-transition > .Transition_slide-active .${resultType}-list`;
|
||||
const itemSelector = `${activeListSelector} > .scroll-item`;
|
||||
const shouldUseTransitionForContent = resultType === 'stories' || resultType === 'gifts';
|
||||
const contentTransitionKey = (() => {
|
||||
if (resultType === 'stories') {
|
||||
return selectedStoryAlbumId || 0;
|
||||
}
|
||||
if (resultType === 'gifts') {
|
||||
return activeCollectionId || 0;
|
||||
}
|
||||
return 0;
|
||||
})();
|
||||
|
||||
const handleOnStop = useLastCallback(() => {
|
||||
setRestoreContentHeightKey(restoreContentHeightKey + 1);
|
||||
});
|
||||
|
||||
function renderSpinnerOrContent(noContent: boolean, noSpinner: boolean) {
|
||||
const baseContent = renderSpinnerOrContentBase(noContent, noSpinner);
|
||||
|
||||
const isSpinner = noContent && !noSpinner;
|
||||
|
||||
if (shouldUseTransitionForContent) {
|
||||
return (
|
||||
<Transition
|
||||
className={`${resultType}-list`}
|
||||
activeKey={contentTransitionKey}
|
||||
name={resolveTransitionName('slideOptimized', animationLevel, undefined, oldLang.isRtl)}
|
||||
shouldCleanup
|
||||
shouldRestoreHeight
|
||||
restoreHeightKey={restoreContentHeightKey}
|
||||
contentSelector=".Transition > .Transition_slide-active > .content"
|
||||
>
|
||||
<Transition
|
||||
activeKey={isSpinner ? 0 : 1}
|
||||
name="fade"
|
||||
shouldCleanup
|
||||
shouldRestoreHeight
|
||||
restoreHeightKey={restoreContentHeightKey}
|
||||
contentSelector=".content"
|
||||
onStop={handleOnStop}
|
||||
>
|
||||
{baseContent}
|
||||
</Transition>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
activeKey={isSpinner ? 0 : 1}
|
||||
name="fade"
|
||||
shouldCleanup
|
||||
shouldRestoreHeight
|
||||
>
|
||||
{baseContent}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
const activeListSelector = `.shared-media-transition > .Transition_slide-active`;
|
||||
const itemSelector = !shouldUseTransitionForContent
|
||||
? `${activeListSelector} .${resultType}-list > .scroll-item`
|
||||
/* eslint-disable @stylistic/max-len */
|
||||
: `${activeListSelector} > .Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .gifts-list > .scroll-item`;
|
||||
|
||||
return (
|
||||
<InfiniteScroll
|
||||
@ -908,6 +1083,10 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
className="shared-media-transition"
|
||||
onStart={applyTransitionFix}
|
||||
onStop={handleTransitionStop}
|
||||
restoreHeightKey={shouldUseTransitionForContent ? restoreContentHeightKey : undefined}
|
||||
contentSelector={shouldUseTransitionForContent
|
||||
? '.Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .content'
|
||||
: undefined}
|
||||
>
|
||||
{renderContent()}
|
||||
</Transition>
|
||||
@ -998,13 +1177,21 @@ export default memo(withGlobal<OwnProps>(
|
||||
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
|
||||
&& !isSavedDialog;
|
||||
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
|
||||
const storyIds = peerStories?.profileIds;
|
||||
const tabState = selectTabState(global);
|
||||
const { selectedStoryAlbumId, nextProfileTab, forceScrollProfileTab, savedGifts } = tabState;
|
||||
const storyIds = selectedStoryAlbumId
|
||||
? peerStories?.idsByAlbumId?.[selectedStoryAlbumId]?.ids
|
||||
: peerStories?.profileIds;
|
||||
const pinnedStoryIds = peerStories?.pinnedIds;
|
||||
const storyByIds = peerStories?.byId;
|
||||
const archiveStoryIds = peerStories?.archiveIds;
|
||||
|
||||
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
|
||||
const peerGifts = selectTabState(global).savedGifts.giftsByPeerId[chatId];
|
||||
const activeCollectionId = savedGifts.activeCollectionByPeerId[chatId];
|
||||
const peerGifts = savedGifts.collectionsByPeerId[chatId]?.[activeCollectionId || 'all'];
|
||||
|
||||
const storyAlbums = global.stories.albumsByPeerId?.[chatId];
|
||||
const giftCollections = global.starGiftCollections?.byPeerId?.[chatId];
|
||||
|
||||
const monoforumChannel = selectMonoforumChannel(global, chatId);
|
||||
const isRestricted = chat && selectIsChatRestricted(global, chat.id);
|
||||
@ -1033,12 +1220,17 @@ export default memo(withGlobal<OwnProps>(
|
||||
storyIds,
|
||||
hasGiftsTab,
|
||||
gifts: peerGifts?.gifts,
|
||||
storyAlbums,
|
||||
giftCollections,
|
||||
pinnedStoryIds,
|
||||
archiveStoryIds,
|
||||
storyByIds,
|
||||
selectedStoryAlbumId,
|
||||
activeCollectionId,
|
||||
giftsFilter: savedGifts.filter,
|
||||
isChatProtected: chat?.isProtected,
|
||||
nextProfileTab: selectTabState(global).nextProfileTab,
|
||||
forceScrollProfileTab: selectTabState(global).forceScrollProfileTab,
|
||||
nextProfileTab,
|
||||
forceScrollProfileTab,
|
||||
animationLevel,
|
||||
shouldWarnAboutFiles,
|
||||
similarChannels: similarChannelIds,
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
.tabList {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
90
src/components/right/gifts/StarGiftCollectionList.tsx
Normal file
90
src/components/right/gifts/StarGiftCollectionList.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { memo, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiStarGiftCollection } from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
import type { TabItem } from '../../common/AnimatedTabList';
|
||||
|
||||
import { selectActiveCollectionId } from '../../../global/selectors';
|
||||
import { selectSharedSettings } from '../../../global/selectors/sharedState';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedTabList from '../../common/AnimatedTabList';
|
||||
|
||||
import styles from './StarGiftCollectionList.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
peerId: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
collections?: ApiStarGiftCollection[];
|
||||
activeCollectionId?: number;
|
||||
animationLevel?: AnimationLevel;
|
||||
};
|
||||
|
||||
const StarGiftCollectionList = ({
|
||||
peerId,
|
||||
className,
|
||||
collections,
|
||||
activeCollectionId,
|
||||
animationLevel,
|
||||
}: StateProps & OwnProps) => {
|
||||
const { updateSelectedGiftCollection, resetSelectedGiftCollection } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const handleItemSelect = useLastCallback((itemId?: string) => {
|
||||
if (itemId === 'all') {
|
||||
resetSelectedGiftCollection({ peerId });
|
||||
} else {
|
||||
const collectionId = Number(itemId);
|
||||
updateSelectedGiftCollection({ peerId, collectionId });
|
||||
}
|
||||
});
|
||||
|
||||
if (!collections || collections.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items: TabItem[] = useMemo(() => [
|
||||
{
|
||||
id: 'all',
|
||||
title: lang('AllGiftsCategory'),
|
||||
},
|
||||
...collections.map((collection) => ({
|
||||
id: String(collection.collectionId),
|
||||
title: collection.title,
|
||||
sticker: collection.icon,
|
||||
})),
|
||||
], [collections, lang]);
|
||||
|
||||
const selectedItemId = activeCollectionId ? String(activeCollectionId) : 'all';
|
||||
|
||||
return (
|
||||
<AnimatedTabList
|
||||
items={items}
|
||||
selectedItemId={selectedItemId}
|
||||
animationLevel={animationLevel}
|
||||
onItemSelect={handleItemSelect}
|
||||
className={buildClassName(styles.tabList, className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { peerId }): StateProps => {
|
||||
const { starGiftCollections } = global;
|
||||
const collections = starGiftCollections?.byPeerId?.[peerId];
|
||||
const activeCollectionId = selectActiveCollectionId(global, peerId);
|
||||
|
||||
return {
|
||||
collections,
|
||||
activeCollectionId,
|
||||
animationLevel: selectSharedSettings(global).animationLevel,
|
||||
};
|
||||
},
|
||||
)(StarGiftCollectionList));
|
||||
3
src/components/right/stories/StoryAlbumList.module.scss
Normal file
3
src/components/right/stories/StoryAlbumList.module.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.tabList {
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
90
src/components/right/stories/StoryAlbumList.tsx
Normal file
90
src/components/right/stories/StoryAlbumList.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { memo, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiStoryAlbum } from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
import type { TabItem } from '../../common/AnimatedTabList';
|
||||
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { selectSharedSettings } from '../../../global/selectors/sharedState';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedTabList from '../../common/AnimatedTabList';
|
||||
|
||||
import styles from './StoryAlbumList.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
peerId: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
albums?: ApiStoryAlbum[];
|
||||
selectedAlbumId?: number;
|
||||
animationLevel?: AnimationLevel;
|
||||
};
|
||||
|
||||
const StoryAlbumList = ({
|
||||
peerId,
|
||||
className,
|
||||
albums,
|
||||
selectedAlbumId,
|
||||
animationLevel,
|
||||
}: StateProps & OwnProps) => {
|
||||
const { selectStoryAlbum, resetSelectedStoryAlbum } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const handleItemSelect = useLastCallback((itemId: string) => {
|
||||
if (itemId === 'all') {
|
||||
resetSelectedStoryAlbum();
|
||||
} else {
|
||||
const albumId = Number(itemId);
|
||||
selectStoryAlbum({ peerId, albumId });
|
||||
}
|
||||
});
|
||||
|
||||
if (!albums?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items: TabItem[] = useMemo(() => [
|
||||
{
|
||||
id: 'all',
|
||||
title: lang('AllStoriesCategory'),
|
||||
},
|
||||
...albums.map((album) => ({
|
||||
id: String(album.albumId),
|
||||
title: album.title,
|
||||
})),
|
||||
], [albums, lang]);
|
||||
|
||||
const selectedItemId = selectedAlbumId ? String(selectedAlbumId) : 'all';
|
||||
|
||||
return (
|
||||
<AnimatedTabList
|
||||
items={items}
|
||||
selectedItemId={selectedItemId}
|
||||
animationLevel={animationLevel}
|
||||
onItemSelect={handleItemSelect}
|
||||
className={buildClassName(styles.tabList, className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { peerId }): StateProps => {
|
||||
const { stories } = global;
|
||||
const tabState = selectTabState(global);
|
||||
const albums = stories?.albumsByPeerId?.[peerId];
|
||||
const selectedAlbumId = tabState.selectedStoryAlbumId;
|
||||
|
||||
return {
|
||||
albums,
|
||||
selectedAlbumId,
|
||||
animationLevel: selectSharedSettings(global).animationLevel,
|
||||
};
|
||||
},
|
||||
)(StoryAlbumList));
|
||||
@ -29,8 +29,7 @@
|
||||
&-slideOptimizedBackwards,
|
||||
&-slideOptimizedRtl,
|
||||
&-slideOptimizedRtlBackwards {
|
||||
contain: strict;
|
||||
|
||||
transform: translate3d(0, 0, 0);
|
||||
#root & > .Transition_slide {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@ -47,6 +47,8 @@ export type TransitionProps = {
|
||||
onScroll?: NoneToVoidFunction;
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
children: React.ReactNode | ChildrenFn;
|
||||
contentSelector?: string;
|
||||
restoreHeightKey?: number;
|
||||
};
|
||||
|
||||
const FALLBACK_ANIMATION_END = 1000;
|
||||
@ -89,6 +91,8 @@ function Transition({
|
||||
onScroll,
|
||||
onMouseDown,
|
||||
children,
|
||||
contentSelector,
|
||||
restoreHeightKey,
|
||||
}: TransitionProps) {
|
||||
const currentKeyRef = useRef<number>();
|
||||
// No need for a container to update on change
|
||||
@ -361,7 +365,10 @@ function Transition({
|
||||
return;
|
||||
}
|
||||
|
||||
const { clientHeight, clientWidth } = activeElement || {};
|
||||
const contentElement = contentSelector
|
||||
? activeElement.querySelector<HTMLDivElement>(contentSelector) : activeElement;
|
||||
|
||||
const { clientHeight, clientWidth } = contentElement || activeElement || {};
|
||||
if (!clientHeight || !clientWidth) {
|
||||
return;
|
||||
}
|
||||
@ -373,7 +380,7 @@ function Transition({
|
||||
flexBasis: `${clientHeight}px`,
|
||||
});
|
||||
});
|
||||
}, [shouldRestoreHeight, children]);
|
||||
}, [shouldRestoreHeight, children, restoreHeightKey, contentSelector]);
|
||||
|
||||
const asFastList = !renderCount;
|
||||
const renders = rendersRef.current;
|
||||
|
||||
@ -18,12 +18,14 @@ import {
|
||||
appendStarsTransactions,
|
||||
replacePeerSavedGifts,
|
||||
updateChats,
|
||||
updatePeerStarGiftCollections,
|
||||
updateStarsBalance,
|
||||
updateStarsSubscriptionLoading,
|
||||
updateUsers,
|
||||
} from '../../reducers';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import {
|
||||
selectActiveCollectionId,
|
||||
selectGiftProfileFilter,
|
||||
selectPeer,
|
||||
selectPeerSavedGifts,
|
||||
@ -301,17 +303,20 @@ addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise
|
||||
if (!shouldRefresh && currentGifts && !localNextOffset) return; // Already loaded all
|
||||
|
||||
const fetchingFilter = selectGiftProfileFilter(global, peerId, tabId);
|
||||
const fetchingCollectionId = selectActiveCollectionId(global, peerId, tabId);
|
||||
|
||||
const result = await callApi('fetchSavedStarGifts', {
|
||||
peer,
|
||||
offset: !shouldRefresh ? localNextOffset : '',
|
||||
filter: fetchingFilter,
|
||||
collectionId: fetchingCollectionId ? Number(fetchingCollectionId) : undefined,
|
||||
});
|
||||
|
||||
global = getGlobal();
|
||||
const currentFilter = selectGiftProfileFilter(global, peerId, tabId);
|
||||
const currentCollectionId = selectActiveCollectionId(global, peerId, tabId);
|
||||
|
||||
if (!result || currentFilter !== fetchingFilter) {
|
||||
if (!result || currentCollectionId !== fetchingCollectionId || currentFilter !== fetchingFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -395,7 +400,8 @@ addActionHandler('changeGiftVisibility', async (global, actions, payload): Promi
|
||||
const requestInputGift = getRequestInputSavedStarGift(global, gift);
|
||||
if (!requestInputGift) return;
|
||||
|
||||
const oldGifts = selectTabState(global, tabId).savedGifts.giftsByPeerId[peerId];
|
||||
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||
const oldGifts = selectTabState(global, tabId).savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId];
|
||||
if (oldGifts?.gifts?.length) {
|
||||
const newGifts = oldGifts.gifts.map((g) => {
|
||||
if (g.inputGift && areInputSavedGiftsEqual(g.inputGift, gift)) {
|
||||
@ -530,3 +536,25 @@ addActionHandler('updateStarGiftPrice', async (global, actions, payload): Promis
|
||||
|
||||
actions.reloadPeerSavedGifts({ peerId: global.currentUserId! });
|
||||
});
|
||||
|
||||
addActionHandler('loadStarGiftCollections', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
peerId,
|
||||
hash,
|
||||
} = payload;
|
||||
|
||||
const peer = selectPeer(global, peerId);
|
||||
if (!peer) return;
|
||||
|
||||
const result = await callApi('fetchStarGiftCollections', {
|
||||
peer,
|
||||
hash,
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
global = updatePeerStarGiftCollections(global, peerId, result.collections);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
@ -301,13 +301,43 @@ addActionHandler('loadPeerStories', async (global, actions, payload): Promise<vo
|
||||
addActionHandler('loadPeerProfileStories', async (global, actions, payload): Promise<void> => {
|
||||
if (selectIsCurrentUserFrozen(global)) return;
|
||||
|
||||
const { peerId, offsetId } = payload;
|
||||
const { peerId, offsetId, tabId = getCurrentTabId() } = payload;
|
||||
const peer = selectPeer(global, peerId);
|
||||
let peerStories = selectPeerStories(global, peerId);
|
||||
if (!peer || peerStories?.isFullyLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const selectedAlbumId = tabState.selectedStoryAlbumId;
|
||||
if (selectedAlbumId) {
|
||||
let albumData = peerStories?.idsByAlbumId?.[selectedAlbumId];
|
||||
if (albumData?.isFullyLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('fetchAlbumStories', {
|
||||
peer,
|
||||
albumId: selectedAlbumId,
|
||||
offset: offsetId || 0,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = addStoriesForPeer(global, peerId, result.stories, result.pinnedIds, false, selectedAlbumId);
|
||||
peerStories = selectPeerStories(global, peerId);
|
||||
|
||||
albumData = peerStories?.idsByAlbumId?.[selectedAlbumId];
|
||||
if (Object.values(result.stories).length === 0
|
||||
|| (albumData?.ids?.length && albumData.ids.length >= result.count)) {
|
||||
global = updatePeerStoriesFullyLoaded(global, peerId, true, false, selectedAlbumId);
|
||||
}
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('fetchPeerProfileStories', { peer, offsetId });
|
||||
if (!result) {
|
||||
return;
|
||||
@ -619,3 +649,66 @@ addActionHandler('activateStealthMode', (global, actions, payload): ActionReturn
|
||||
|
||||
callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
|
||||
});
|
||||
|
||||
addActionHandler('loadStoryAlbums', async (global, actions, payload): Promise<void> => {
|
||||
const { peerId } = payload;
|
||||
const peer = selectPeer(global, peerId);
|
||||
if (!peer) return;
|
||||
|
||||
const albums = await callApi('fetchAlbums', { peer });
|
||||
if (!albums) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = {
|
||||
...global,
|
||||
stories: {
|
||||
...global.stories,
|
||||
albumsByPeerId: {
|
||||
...global.stories.albumsByPeerId,
|
||||
[peerId]: albums,
|
||||
},
|
||||
},
|
||||
};
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('selectStoryAlbum', (global, actions, payload): ActionReturnType => {
|
||||
const { peerId, albumId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
if (albumId && peerId) {
|
||||
global = updatePeerStoriesFullyLoaded(global, peerId, false);
|
||||
}
|
||||
|
||||
global = updateTabState(global, {
|
||||
selectedStoryAlbumId: albumId || undefined,
|
||||
}, tabId);
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadPeerProfileStories({ peerId, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('loadAlbumStories', async (global, actions, payload): Promise<void> => {
|
||||
const { peerId, albumId, offsetId } = payload;
|
||||
const peer = selectPeer(global, peerId);
|
||||
if (!peer) return;
|
||||
|
||||
const result = await callApi('fetchAlbumStories', {
|
||||
peer,
|
||||
albumId,
|
||||
offset: offsetId || 0,
|
||||
});
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = addStoriesForPeer(global, peerId, result.stories, result.pinnedIds);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('resetSelectedStoryAlbum', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
selectedStoryAlbumId: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { ApiSavedGifts } from '../../../api/types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { DEFAULT_GIFT_PROFILE_FILTER_OPTIONS } from '../../../config';
|
||||
import { selectActiveCollectionId } from '../../../global/selectors';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { addActionHandler, setGlobal } from '../../index';
|
||||
import {
|
||||
@ -98,11 +100,15 @@ addActionHandler('updateGiftProfileFilter', (global, actions, payload): ActionRe
|
||||
};
|
||||
}
|
||||
|
||||
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||
|
||||
global = updateTabState(global, {
|
||||
savedGifts: {
|
||||
...tabState.savedGifts,
|
||||
giftsByPeerId: {
|
||||
[peerId]: tabState.savedGifts.giftsByPeerId[peerId],
|
||||
collectionsByPeerId: {
|
||||
[peerId]: {
|
||||
[activeCollectionId]: tabState.savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId],
|
||||
} as Record<number | 'all', ApiSavedGifts>,
|
||||
},
|
||||
filter: updatedFilter,
|
||||
},
|
||||
@ -118,11 +124,15 @@ addActionHandler('resetGiftProfileFilter', (global, actions, payload): ActionRet
|
||||
const { peerId, tabId = getCurrentTabId() } = payload || {};
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||
|
||||
global = updateTabState(global, {
|
||||
savedGifts: {
|
||||
...tabState.savedGifts,
|
||||
giftsByPeerId: {
|
||||
[peerId]: tabState.savedGifts.giftsByPeerId[peerId],
|
||||
collectionsByPeerId: {
|
||||
[peerId]: {
|
||||
[activeCollectionId]: tabState.savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId],
|
||||
} as Record<number | 'all', ApiSavedGifts>,
|
||||
},
|
||||
filter: {
|
||||
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,
|
||||
|
||||
@ -6,7 +6,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import * as langProvider from '../../../util/oldLangProvider';
|
||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||
import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments';
|
||||
import { addActionHandler } from '../../index';
|
||||
import { addActionHandler, setGlobal } from '../../index';
|
||||
import {
|
||||
clearStarPayment, openStarsTransactionModal,
|
||||
} from '../../reducers';
|
||||
@ -344,3 +344,43 @@ addActionHandler('openGiftTransferModal', (global, actions, payload): ActionRetu
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftTransferModal', 'giftTransferModal');
|
||||
|
||||
addActionHandler('updateSelectedGiftCollection', (global, actions, payload): ActionReturnType => {
|
||||
const { peerId, collectionId, tabId = getCurrentTabId() } = payload;
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
global = updateTabState(global, {
|
||||
savedGifts: {
|
||||
...tabState.savedGifts,
|
||||
activeCollectionByPeerId: {
|
||||
...tabState.savedGifts.activeCollectionByPeerId,
|
||||
[peerId]: collectionId,
|
||||
},
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadPeerSavedGifts({
|
||||
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('resetSelectedGiftCollection', (global, actions, payload): ActionReturnType => {
|
||||
const { peerId, tabId = getCurrentTabId() } = payload;
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
global = updateTabState(global, {
|
||||
savedGifts: {
|
||||
...tabState.savedGifts,
|
||||
activeCollectionByPeerId: {
|
||||
...tabState.savedGifts.activeCollectionByPeerId,
|
||||
[peerId]: undefined,
|
||||
},
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadPeerSavedGifts({
|
||||
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -165,6 +165,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
|
||||
stories: {
|
||||
byPeerId: {},
|
||||
albumsByPeerId: {},
|
||||
orderedPeerIds: {
|
||||
archived: [],
|
||||
active: [],
|
||||
@ -393,7 +394,8 @@ export const INITIAL_TAB_STATE: TabState = {
|
||||
filter: {
|
||||
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,
|
||||
},
|
||||
giftsByPeerId: {},
|
||||
collectionsByPeerId: {},
|
||||
activeCollectionByPeerId: {},
|
||||
},
|
||||
|
||||
resaleGifts: {
|
||||
|
||||
@ -55,9 +55,10 @@ export function addStoriesForPeer<T extends GlobalState>(
|
||||
newStories: Record<number, ApiTypeStory>,
|
||||
newPinnedIds?: number[],
|
||||
addToArchive?: boolean,
|
||||
albumId?: number,
|
||||
): T {
|
||||
const {
|
||||
byId, orderedIds, profileIds, archiveIds, pinnedIds,
|
||||
byId, orderedIds, profileIds, archiveIds, pinnedIds, idsByAlbumId,
|
||||
} = global.stories.byPeerId[peerId] || {};
|
||||
const deletedIds = Object.keys(newStories).filter((id) => 'isDeleted' in newStories[Number(id)]).map(Number);
|
||||
const updatedById = { ...byId, ...newStories };
|
||||
@ -65,7 +66,7 @@ export function addStoriesForPeer<T extends GlobalState>(
|
||||
let updatedArchiveIds = [...(archiveIds || [])];
|
||||
const updatedProfileIds = unique(
|
||||
[...(profileIds || [])].concat(Object.values(newStories).reduce((ids, story) => {
|
||||
if ('isInProfile' in story && story.isInProfile) {
|
||||
if (('isInProfile' in story && story.isInProfile)) {
|
||||
ids.push(story.id);
|
||||
}
|
||||
|
||||
@ -87,6 +88,20 @@ export function addStoriesForPeer<T extends GlobalState>(
|
||||
.filter((storyId) => !deletedIds.includes(storyId));
|
||||
}
|
||||
|
||||
const updatedIdsByAlbumId = { ...(idsByAlbumId || {}) };
|
||||
if (albumId !== undefined) {
|
||||
const newAlbumStoryIds = Object.keys(newStories).map(Number)
|
||||
.filter((storyId) => !deletedIds.includes(storyId));
|
||||
|
||||
const existingAlbumData = updatedIdsByAlbumId[albumId];
|
||||
const existingIds = existingAlbumData?.ids || [];
|
||||
|
||||
updatedIdsByAlbumId[albumId] = {
|
||||
...updatedIdsByAlbumId[albumId],
|
||||
ids: unique([...existingIds, ...newAlbumStoryIds]).sort((a, b) => b - a),
|
||||
};
|
||||
}
|
||||
|
||||
global = {
|
||||
...global,
|
||||
stories: {
|
||||
@ -100,6 +115,7 @@ export function addStoriesForPeer<T extends GlobalState>(
|
||||
profileIds: updatedProfileIds,
|
||||
pinnedIds: pinnedIds || newPinnedIds,
|
||||
...(addToArchive && { archiveIds: updatedArchiveIds }),
|
||||
...(albumId !== undefined && { idsByAlbumId: updatedIdsByAlbumId }),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -137,7 +153,33 @@ export function updatePeerStoriesFullyLoaded<T extends GlobalState>(
|
||||
peerId: string,
|
||||
isFullyLoaded: boolean,
|
||||
isArchive?: boolean,
|
||||
albumId?: number,
|
||||
): T {
|
||||
const { byPeerId } = global.stories;
|
||||
const peerStories = byPeerId[peerId];
|
||||
|
||||
if (albumId !== undefined && peerStories?.idsByAlbumId?.[albumId]) {
|
||||
return {
|
||||
...global,
|
||||
stories: {
|
||||
...global.stories,
|
||||
byPeerId: {
|
||||
...byPeerId,
|
||||
[peerId]: {
|
||||
...peerStories,
|
||||
idsByAlbumId: {
|
||||
...peerStories.idsByAlbumId,
|
||||
[albumId]: {
|
||||
...peerStories.idsByAlbumId[albumId],
|
||||
isFullyLoaded,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...global,
|
||||
stories: {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ApiMissingInvitedUser,
|
||||
ApiSavedStarGift,
|
||||
ApiStarGiftCollection,
|
||||
ApiUser,
|
||||
ApiUserCommonChats,
|
||||
ApiUserFullInfo,
|
||||
@ -14,6 +15,7 @@ import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { omit, omitUndefined, unique } from '../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
import { getSavedGiftKey } from '../helpers/stars';
|
||||
import { selectActiveCollectionId } from '../selectors';
|
||||
import { selectTabState } from '../selectors';
|
||||
import { updateTabState } from './tabs';
|
||||
|
||||
@ -344,16 +346,38 @@ export function replacePeerSavedGifts<T extends GlobalState>(
|
||||
keyCounts.set(id, count + 1);
|
||||
});
|
||||
|
||||
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||
|
||||
return updateTabState(global, {
|
||||
savedGifts: {
|
||||
...tabState.savedGifts,
|
||||
giftsByPeerId: {
|
||||
...tabState.savedGifts.giftsByPeerId,
|
||||
collectionsByPeerId: {
|
||||
...tabState.savedGifts.collectionsByPeerId,
|
||||
[peerId]: {
|
||||
gifts,
|
||||
nextOffset,
|
||||
...tabState.savedGifts.collectionsByPeerId[peerId],
|
||||
[activeCollectionId]: {
|
||||
gifts,
|
||||
nextOffset,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function updatePeerStarGiftCollections<T extends GlobalState>(
|
||||
global: T,
|
||||
peerId: string,
|
||||
collections: ApiStarGiftCollection[],
|
||||
): T {
|
||||
return {
|
||||
...global,
|
||||
starGiftCollections: {
|
||||
...global.starGiftCollections,
|
||||
byPeerId: {
|
||||
...global.starGiftCollections?.byPeerId,
|
||||
[peerId]: collections,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -96,3 +96,11 @@ export function selectIsGiftProfileFilterDefault<T extends GlobalState>(
|
||||
) {
|
||||
return arePropsShallowEqual(selectTabState(global, tabId).savedGifts.filter, DEFAULT_GIFT_PROFILE_FILTER_OPTIONS);
|
||||
}
|
||||
|
||||
export function selectActiveCollectionId<T extends GlobalState>(
|
||||
global: T,
|
||||
peerId: string,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
return selectTabState(global, tabId).savedGifts.activeCollectionByPeerId?.[peerId];
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiPeer, ApiSavedGifts } from '../../api/types';
|
||||
import type { ApiPeer, ApiSavedGifts, ApiStarGiftCollection } from '../../api/types';
|
||||
import type { GlobalState, TabArgs } from '../types';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||
@ -32,8 +32,17 @@ export function selectPeerSavedGifts<T extends GlobalState>(
|
||||
global: T,
|
||||
peerId: string,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): ApiSavedGifts {
|
||||
return selectTabState(global, tabId).savedGifts.giftsByPeerId[peerId];
|
||||
): ApiSavedGifts | undefined {
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const activeCollectionId = tabState.savedGifts.activeCollectionByPeerId[peerId] || 'all';
|
||||
return tabState.savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId];
|
||||
}
|
||||
|
||||
export function selectPeerStarGiftCollections<T extends GlobalState>(
|
||||
global: T,
|
||||
peerId: string,
|
||||
): ApiStarGiftCollection[] | undefined {
|
||||
return global.starGiftCollections?.byPeerId[peerId];
|
||||
}
|
||||
|
||||
export function selectPeerPaidMessagesStars<T extends GlobalState>(
|
||||
|
||||
@ -1691,6 +1691,19 @@ export interface ActionPayloads {
|
||||
isForPast?: boolean;
|
||||
isForFuture?: boolean;
|
||||
} | undefined;
|
||||
loadStoryAlbums: {
|
||||
peerId: string;
|
||||
};
|
||||
selectStoryAlbum: {
|
||||
peerId: string;
|
||||
albumId?: number;
|
||||
} & WithTabId;
|
||||
loadAlbumStories: {
|
||||
peerId: string;
|
||||
albumId: number;
|
||||
offsetId?: number;
|
||||
};
|
||||
resetSelectedStoryAlbum: WithTabId | undefined;
|
||||
|
||||
openBoostModal: {
|
||||
chatId: string;
|
||||
@ -2596,6 +2609,13 @@ export interface ActionPayloads {
|
||||
recipientId: string;
|
||||
} & WithTabId;
|
||||
closeGiftTransferModal: WithTabId | undefined;
|
||||
updateSelectedGiftCollection: {
|
||||
peerId: string;
|
||||
collectionId: number;
|
||||
} & WithTabId;
|
||||
resetSelectedGiftCollection: {
|
||||
peerId: string;
|
||||
} & WithTabId;
|
||||
|
||||
loadPeerSavedGifts: {
|
||||
peerId: string;
|
||||
@ -2621,6 +2641,11 @@ export interface ActionPayloads {
|
||||
price: ApiTypeCurrencyAmount;
|
||||
} & WithTabId;
|
||||
|
||||
loadStarGiftCollections: {
|
||||
peerId: string;
|
||||
hash?: string;
|
||||
} & WithTabId;
|
||||
|
||||
openStarsGiftModal: ({
|
||||
chatId?: string;
|
||||
forUserId?: string;
|
||||
|
||||
@ -30,12 +30,14 @@ import type {
|
||||
ApiSavedReactionTag,
|
||||
ApiSession,
|
||||
ApiSponsoredMessage,
|
||||
ApiStarGiftCollection,
|
||||
ApiStarGiftRegular,
|
||||
ApiStarsAmount,
|
||||
ApiStarTopupOption,
|
||||
ApiStealthMode,
|
||||
ApiSticker,
|
||||
ApiStickerSet,
|
||||
ApiStoryAlbum,
|
||||
ApiTimezone,
|
||||
ApiTonAmount,
|
||||
ApiTranscription,
|
||||
@ -253,6 +255,7 @@ export type GlobalState = {
|
||||
archived: string[];
|
||||
};
|
||||
stealthMode: ApiStealthMode;
|
||||
albumsByPeerId: Record<string, ApiStoryAlbum[]>;
|
||||
};
|
||||
|
||||
groupCalls: {
|
||||
@ -307,6 +310,9 @@ export type GlobalState = {
|
||||
byId: Record<string, ApiStarGiftRegular>;
|
||||
idsByCategory: Record<StarGiftCategory, string[]>;
|
||||
};
|
||||
starGiftCollections?: {
|
||||
byPeerId: Record<string, ApiStarGiftCollection[]>;
|
||||
};
|
||||
|
||||
stickers: {
|
||||
setsById: Record<string, ApiStickerSet>;
|
||||
|
||||
@ -218,7 +218,8 @@ export type TabState = {
|
||||
};
|
||||
|
||||
savedGifts: {
|
||||
giftsByPeerId: Record<string, ApiSavedGifts>;
|
||||
collectionsByPeerId: Record<string, Record<number | 'all', ApiSavedGifts>>;
|
||||
activeCollectionByPeerId: Record<string, number | undefined>;
|
||||
filter: GiftProfileFilterOptions;
|
||||
};
|
||||
|
||||
@ -342,6 +343,8 @@ export type TabState = {
|
||||
};
|
||||
};
|
||||
|
||||
selectedStoryAlbumId?: number;
|
||||
|
||||
mediaViewer: {
|
||||
chatId?: string;
|
||||
threadId?: ThreadId;
|
||||
|
||||
@ -1796,6 +1796,7 @@ payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:
|
||||
payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<InputSavedStarGift> = Bool;
|
||||
payments.getResaleStarGifts#7a5fa236 flags:# sort_by_price:flags.1?true sort_by_num:flags.2?true attributes_hash:flags.0?long gift_id:long attributes:flags.3?Vector<StarGiftAttributeId> offset:string limit:int = payments.ResaleStarGifts;
|
||||
payments.updateStarGiftPrice#edbe6ccb stargift:InputSavedStarGift resell_amount:StarsAmount = Updates;
|
||||
payments.getStarGiftCollections#981b91dd peer:InputPeer hash:long = payments.StarGiftCollections;
|
||||
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||
@ -1855,6 +1856,8 @@ stories.getPeerStories#2c4ada50 peer:InputPeer = stories.PeerStories;
|
||||
stories.getPeerMaxIDs#535983c3 id:Vector<InputPeer> = Vector<int>;
|
||||
stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool;
|
||||
stories.togglePinnedToTop#b297e9b peer:InputPeer id:Vector<int> = Bool;
|
||||
stories.getAlbums#25b3eac7 peer:InputPeer hash:long = stories.Albums;
|
||||
stories.getAlbumStories#ac806d61 peer:InputPeer album_id:int offset:int limit:int = stories.Stories;
|
||||
premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList;
|
||||
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
||||
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
||||
|
||||
@ -330,6 +330,7 @@
|
||||
"payments.toggleStarGiftsPinnedToTop",
|
||||
"payments.getResaleStarGifts",
|
||||
"payments.updateStarGiftPrice",
|
||||
"payments.getStarGiftCollections",
|
||||
"langpack.getLangPack",
|
||||
"langpack.getStrings",
|
||||
"langpack.getLanguages",
|
||||
@ -404,6 +405,8 @@
|
||||
"stories.getPeerStories",
|
||||
"stories.getStoriesViews",
|
||||
"stories.togglePinnedToTop",
|
||||
"stories.getAlbums",
|
||||
"stories.getAlbumStories",
|
||||
"premium.getBoostsStatus",
|
||||
"premium.getBoostersList",
|
||||
"premium.applyBoost",
|
||||
|
||||
1
src/types/language.d.ts
vendored
1
src/types/language.d.ts
vendored
@ -1666,6 +1666,7 @@ export interface LangPair {
|
||||
'PublicPostsPremiumFeatureSubtitle': undefined;
|
||||
'PublicPostsSubscribeToPremium': undefined;
|
||||
'PostsSearchTransaction': undefined;
|
||||
'AllStoriesCategory': undefined;
|
||||
'TitleRating': undefined;
|
||||
'RatingYourReflectsActivity': undefined;
|
||||
'RatingGiftsFromTelegram': undefined;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user