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,
|
ApiStarGiftAttribute,
|
||||||
ApiStarGiftAttributeCounter,
|
ApiStarGiftAttributeCounter,
|
||||||
ApiStarGiftAttributeId,
|
ApiStarGiftAttributeId,
|
||||||
|
ApiStarGiftCollection,
|
||||||
ApiTypeResaleStarGifts,
|
ApiTypeResaleStarGifts,
|
||||||
} from '../../types';
|
} 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,
|
ApiMediaAreaCoordinates,
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
ApiStory,
|
ApiStory,
|
||||||
|
ApiStoryAlbum,
|
||||||
ApiStoryForwardInfo,
|
ApiStoryForwardInfo,
|
||||||
ApiStoryView,
|
ApiStoryView,
|
||||||
ApiStoryViews,
|
ApiStoryViews,
|
||||||
@ -14,8 +15,10 @@ import type {
|
|||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildCollectionByCallback, omitUndefined } from '../../../util/iteratees';
|
import { buildCollectionByCallback, omitUndefined } from '../../../util/iteratees';
|
||||||
import { buildPrivacyRules } from './common';
|
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||||
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
import { addPhotoToLocalDb } from '../helpers/localDb';
|
||||||
|
import { buildApiPhoto, buildPrivacyRules } from './common';
|
||||||
|
import { buildApiDocument, buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||||
import { buildApiMessage } from './messages';
|
import { buildApiMessage } from './messages';
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { buildApiReaction, buildReactionCount } from './reactions';
|
import { buildApiReaction, buildReactionCount } from './reactions';
|
||||||
@ -279,3 +282,23 @@ export function buildApiStoryForwardInfo(forwardHeader: GramJs.TypeStoryFwdHeade
|
|||||||
isModified: modified,
|
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 { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift,
|
import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift,
|
||||||
buildApiStarGiftAttribute, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts';
|
buildApiStarGiftAttribute, buildApiStarGiftCollection, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts';
|
||||||
import {
|
import {
|
||||||
buildApiCurrencyAmount,
|
buildApiCurrencyAmount,
|
||||||
buildApiStarsGiftOptions,
|
buildApiStarsGiftOptions,
|
||||||
@ -112,11 +112,13 @@ export async function fetchSavedStarGifts({
|
|||||||
offset = DEFAULT_PRIMITIVES.STRING,
|
offset = DEFAULT_PRIMITIVES.STRING,
|
||||||
limit = DEFAULT_PRIMITIVES.INT,
|
limit = DEFAULT_PRIMITIVES.INT,
|
||||||
filter,
|
filter,
|
||||||
|
collectionId,
|
||||||
}: {
|
}: {
|
||||||
peer: ApiPeer;
|
peer: ApiPeer;
|
||||||
offset?: string;
|
offset?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
filter?: GiftProfileFilterOptions;
|
filter?: GiftProfileFilterOptions;
|
||||||
|
collectionId?: number;
|
||||||
}) {
|
}) {
|
||||||
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
||||||
|
|
||||||
@ -124,6 +126,7 @@ export async function fetchSavedStarGifts({
|
|||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
|
collectionId,
|
||||||
...(filter && {
|
...(filter && {
|
||||||
sortByValue: filter.sortType === 'byValue' || undefined,
|
sortByValue: filter.sortType === 'byValue' || undefined,
|
||||||
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
||||||
@ -476,3 +479,24 @@ export async function fetchStarGiftWithdrawalUrl({
|
|||||||
|
|
||||||
return undefined;
|
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,
|
ApiPeerStories,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
|
ApiStoryAlbum,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import {
|
|||||||
buildApiPeerStories,
|
buildApiPeerStories,
|
||||||
buildApiStealthMode,
|
buildApiStealthMode,
|
||||||
buildApiStory,
|
buildApiStory,
|
||||||
|
buildApiStoryAlbum,
|
||||||
buildApiStoryView,
|
buildApiStoryView,
|
||||||
buildApiStoryViews,
|
buildApiStoryViews,
|
||||||
} from '../apiBuilders/stories';
|
} from '../apiBuilders/stories';
|
||||||
@ -463,3 +465,63 @@ export function activateStealthMode({
|
|||||||
shouldReturnTrue: true,
|
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;
|
shouldDisallowPremiumGifts?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiStarGiftCollection {
|
||||||
|
collectionId: number;
|
||||||
|
title: string;
|
||||||
|
icon?: ApiSticker;
|
||||||
|
giftsCount: number;
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiStarsRating {
|
export interface ApiStarsRating {
|
||||||
level: number;
|
level: number;
|
||||||
currentLevelStars: number;
|
currentLevelStars: number;
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
|
ApiDocument,
|
||||||
ApiGeoPoint,
|
ApiGeoPoint,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
|
ApiPhoto,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiReactionCount,
|
ApiReactionCount,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
@ -69,6 +71,10 @@ export type ApiPeerStories = {
|
|||||||
isArchiveFullyLoaded?: boolean;
|
isArchiveFullyLoaded?: boolean;
|
||||||
lastUpdatedAt?: number;
|
lastUpdatedAt?: number;
|
||||||
lastReadId?: number;
|
lastReadId?: number;
|
||||||
|
idsByAlbumId?: Record<number, {
|
||||||
|
ids: number[];
|
||||||
|
isFullyLoaded?: boolean;
|
||||||
|
}>; // Story IDs grouped by album ID with loading state
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiMessageStoryData = {
|
export type ApiMessageStoryData = {
|
||||||
@ -184,3 +190,10 @@ export type ApiMediaAreaUniqueGift = {
|
|||||||
|
|
||||||
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction
|
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction
|
||||||
| ApiMediaAreaChannelPost | ApiMediaAreaUrl | ApiMediaAreaWeather | ApiMediaAreaUniqueGift;
|
| 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.";
|
"PublicPostsPremiumFeatureSubtitle" = "Global search is a Premium feature.";
|
||||||
"PublicPostsSubscribeToPremium" = "Subscribe to Premium";
|
"PublicPostsSubscribeToPremium" = "Subscribe to Premium";
|
||||||
"NotificationPaidExtraSearch" = "{stars} spent on extra search.";
|
"NotificationPaidExtraSearch" = "{stars} spent on extra search.";
|
||||||
|
"PostsSearchTransaction" = "Posts Search";
|
||||||
|
"AllStoriesCategory" = "All stories";
|
||||||
"PostsSearchTransaction" = "Public Post Search";
|
"PostsSearchTransaction" = "Public Post Search";
|
||||||
"TitleRating" = "Rating";
|
"TitleRating" = "Rating";
|
||||||
"RatingReflectsActivity" = "This rating reflects {name}'s activity on Telegram. It is based on:";
|
"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.";
|
"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.";
|
"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.";
|
"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;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shared-media-transition {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
&.showContentPanel {
|
||||||
|
transform: translateY(3rem);
|
||||||
|
padding-bottom: 3.5rem !important;
|
||||||
|
}
|
||||||
|
&.noTransition {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.empty-list {
|
&.empty-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
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,
|
ApiChatMember,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiSavedStarGift,
|
ApiSavedStarGift,
|
||||||
|
ApiStarGiftCollection,
|
||||||
|
ApiStoryAlbum,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiUserStatus,
|
ApiUserStatus,
|
||||||
@ -19,6 +21,8 @@ import { MAIN_THREAD_ID } from '../../api/types';
|
|||||||
import { AudioOrigin, MediaViewerOrigin, NewChatMembersProgress } from '../../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';
|
||||||
|
|
||||||
|
const CONTENT_PANEL_SHOW_DELAY = 300;
|
||||||
import {
|
import {
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
getIsDownloading,
|
getIsDownloading,
|
||||||
@ -53,6 +57,7 @@ import {
|
|||||||
import { selectPremiumLimit } from '../../global/selectors/limits';
|
import { selectPremiumLimit } from '../../global/selectors/limits';
|
||||||
import { selectMessageDownloadableMedia } from '../../global/selectors/media';
|
import { selectMessageDownloadableMedia } from '../../global/selectors/media';
|
||||||
import { selectSharedSettings } from '../../global/selectors/sharedState';
|
import { selectSharedSettings } from '../../global/selectors/sharedState';
|
||||||
|
import { areDeepEqual } from '../../util/areDeepEqual';
|
||||||
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
||||||
@ -71,6 +76,7 @@ import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
|||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||||
import useAsyncRendering from './hooks/useAsyncRendering';
|
import useAsyncRendering from './hooks/useAsyncRendering';
|
||||||
import useProfileState from './hooks/useProfileState';
|
import useProfileState from './hooks/useProfileState';
|
||||||
import useProfileViewportIds from './hooks/useProfileViewportIds';
|
import useProfileViewportIds from './hooks/useProfileViewportIds';
|
||||||
@ -100,6 +106,8 @@ import Spinner from '../ui/Spinner';
|
|||||||
import TabList from '../ui/TabList';
|
import TabList from '../ui/TabList';
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
import DeleteMemberModal from './DeleteMemberModal';
|
import DeleteMemberModal from './DeleteMemberModal';
|
||||||
|
import StarGiftCollectionList from './gifts/StarGiftCollectionList';
|
||||||
|
import StoryAlbumList from './stories/StoryAlbumList';
|
||||||
|
|
||||||
import './Profile.scss';
|
import './Profile.scss';
|
||||||
|
|
||||||
@ -127,6 +135,8 @@ type StateProps = {
|
|||||||
hasPreviewMediaTab?: boolean;
|
hasPreviewMediaTab?: boolean;
|
||||||
hasGiftsTab?: boolean;
|
hasGiftsTab?: boolean;
|
||||||
gifts?: ApiSavedStarGift[];
|
gifts?: ApiSavedStarGift[];
|
||||||
|
storyAlbums?: ApiStoryAlbum[];
|
||||||
|
giftCollections?: ApiStarGiftCollection[];
|
||||||
areMembersHidden?: boolean;
|
areMembersHidden?: boolean;
|
||||||
canAddMembers?: boolean;
|
canAddMembers?: boolean;
|
||||||
canDeleteMembers?: boolean;
|
canDeleteMembers?: boolean;
|
||||||
@ -137,6 +147,9 @@ type StateProps = {
|
|||||||
pinnedStoryIds?: number[];
|
pinnedStoryIds?: number[];
|
||||||
archiveStoryIds?: number[];
|
archiveStoryIds?: number[];
|
||||||
storyByIds?: Record<number, ApiTypeStory>;
|
storyByIds?: Record<number, ApiTypeStory>;
|
||||||
|
selectedStoryAlbumId?: number;
|
||||||
|
activeCollectionId?: number;
|
||||||
|
giftsFilter?: any;
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
usersById: Record<string, ApiUser>;
|
usersById: Record<string, ApiUser>;
|
||||||
userStatusesById: Record<string, ApiUserStatus>;
|
userStatusesById: Record<string, ApiUserStatus>;
|
||||||
@ -189,6 +202,9 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
pinnedStoryIds,
|
pinnedStoryIds,
|
||||||
archiveStoryIds,
|
archiveStoryIds,
|
||||||
storyByIds,
|
storyByIds,
|
||||||
|
selectedStoryAlbumId,
|
||||||
|
activeCollectionId,
|
||||||
|
giftsFilter,
|
||||||
mediaSearchType,
|
mediaSearchType,
|
||||||
hasCommonChatsTab,
|
hasCommonChatsTab,
|
||||||
hasStoriesTab,
|
hasStoriesTab,
|
||||||
@ -196,6 +212,8 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
hasPreviewMediaTab,
|
hasPreviewMediaTab,
|
||||||
hasGiftsTab,
|
hasGiftsTab,
|
||||||
gifts,
|
gifts,
|
||||||
|
storyAlbums,
|
||||||
|
giftCollections,
|
||||||
botPreviewMedia,
|
botPreviewMedia,
|
||||||
areMembersHidden,
|
areMembersHidden,
|
||||||
canAddMembers,
|
canAddMembers,
|
||||||
@ -241,6 +259,9 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
loadPreviewMedias,
|
loadPreviewMedias,
|
||||||
loadPeerSavedGifts,
|
loadPeerSavedGifts,
|
||||||
resetGiftProfileFilter,
|
resetGiftProfileFilter,
|
||||||
|
loadStarGiftCollections,
|
||||||
|
loadStoryAlbums,
|
||||||
|
resetSelectedStoryAlbum,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
@ -250,10 +271,13 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
|
const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
|
||||||
|
const [isViewTransitionEnabled, enableViewTransition, disableViewTransition] = useFlag();
|
||||||
|
|
||||||
const profileId = isSavedDialog ? String(threadId) : chatId;
|
const profileId = isSavedDialog ? String(threadId) : chatId;
|
||||||
const isSavedMessages = profileId === currentUserId && !isSavedDialog;
|
const isSavedMessages = profileId === currentUserId && !isSavedDialog;
|
||||||
|
|
||||||
|
const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0);
|
||||||
|
|
||||||
const tabs = useMemo(() => {
|
const tabs = useMemo(() => {
|
||||||
const arr: TabProps[] = [];
|
const arr: TabProps[] = [];
|
||||||
if (isSavedMessages && !isSavedDialog) {
|
if (isSavedMessages && !isSavedDialog) {
|
||||||
@ -352,6 +376,25 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [chatId, isBot, similarBots, isSynced]);
|
}, [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 [renderingGifts, setRenderingGifts] = useState(gifts);
|
||||||
const { startViewTransition, shouldApplyVtn } = useViewTransition();
|
const { startViewTransition, shouldApplyVtn } = useViewTransition();
|
||||||
|
|
||||||
@ -371,12 +414,17 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
const handleLoadGifts = useCallback(() => {
|
const handleLoadGifts = useCallback(() => {
|
||||||
loadPeerSavedGifts({ peerId: chatId });
|
loadPeerSavedGifts({ peerId: chatId });
|
||||||
}, [chatId]);
|
}, [chatId]);
|
||||||
|
|
||||||
const handleLoadMoreMembers = useCallback(() => {
|
const handleLoadMoreMembers = useCallback(() => {
|
||||||
loadMoreMembers({ chatId });
|
loadMoreMembers({ chatId });
|
||||||
}, [chatId, loadMoreMembers]);
|
}, [chatId, loadMoreMembers]);
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevGifts]) => {
|
useEffectWithPrevDeps(([prevGifts]) => {
|
||||||
if (!gifts || !prevGifts) {
|
if (areDeepEqual(gifts, prevGifts)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gifts || !prevGifts || !isViewTransitionEnabled) {
|
||||||
setRenderingGifts(gifts);
|
setRenderingGifts(gifts);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -385,14 +433,14 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
const newGiftIds = gifts.map((gift) => getSavedGiftKey(gift));
|
const newGiftIds = gifts.map((gift) => getSavedGiftKey(gift));
|
||||||
const hasOrderChanged = prevGiftIds.some((id, index) => id !== newGiftIds[index]);
|
const hasOrderChanged = prevGiftIds.some((id, index) => id !== newGiftIds[index]);
|
||||||
|
|
||||||
if (hasOrderChanged) {
|
if (hasOrderChanged && animationLevel > 0) {
|
||||||
startViewTransition(() => {
|
startViewTransition(() => {
|
||||||
setRenderingGifts(gifts);
|
setRenderingGifts(gifts);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setRenderingGifts(gifts);
|
setRenderingGifts(gifts);
|
||||||
}
|
}
|
||||||
}, [gifts, startViewTransition]);
|
}, [gifts, startViewTransition, animationLevel, isViewTransitionEnabled]);
|
||||||
|
|
||||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
|
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
|
||||||
loadMoreMembers: handleLoadMoreMembers,
|
loadMoreMembers: handleLoadMoreMembers,
|
||||||
@ -418,12 +466,40 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
similarChannels,
|
similarChannels,
|
||||||
similarBots,
|
similarBots,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFirstTab = (isSavedMessages && resultType === 'dialogs')
|
const isFirstTab = (isSavedMessages && resultType === 'dialogs')
|
||||||
|| (hasStoriesTab && resultType === 'stories')
|
|| (hasStoriesTab && resultType === 'stories')
|
||||||
|| resultType === 'members'
|
|| resultType === 'members'
|
||||||
|| (!hasMembersTab && resultType === 'media');
|
|| (!hasMembersTab && resultType === 'media');
|
||||||
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
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);
|
usePeerStoriesPolling(resultType === 'members' ? viewportIds as string[] : undefined);
|
||||||
|
|
||||||
const handleStopAutoScrollToTabs = useLastCallback(() => {
|
const handleStopAutoScrollToTabs = useLastCallback(() => {
|
||||||
@ -583,16 +659,48 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const noContent = (!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById;
|
const noContent = (!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById;
|
||||||
const noSpinner = isFirstTab && !canRenderContent;
|
const noSpinner = isFirstTab && !canRenderContent;
|
||||||
const isSpinner = noContent && !noSpinner;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition activeKey={isSpinner ? 0 : 1} name="fade" shouldCleanup>
|
<div>
|
||||||
|
{renderCategories()}
|
||||||
{renderSpinnerOrContent(noContent, noSpinner)}
|
{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) {
|
if (noContent) {
|
||||||
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
||||||
|
|
||||||
@ -656,9 +764,15 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const noTransition = resultType === 'gifts' ? isGiftCollectionsShowed
|
||||||
|
: resultType === 'stories' ? isStoryAlbumsShowed : false;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`content ${resultType}-list`}
|
className={buildClassName(
|
||||||
|
`content ${resultType}-list`,
|
||||||
|
shouldShowContentPanel && 'showContentPanel',
|
||||||
|
noTransition && 'noTransition',
|
||||||
|
)}
|
||||||
dir={oldLang.isRtl && resultType === 'media' ? 'rtl' : undefined}
|
dir={oldLang.isRtl && resultType === 'media' ? 'rtl' : undefined}
|
||||||
teactFastList
|
teactFastList
|
||||||
>
|
>
|
||||||
@ -869,8 +983,69 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeListSelector = `.shared-media-transition > .Transition_slide-active .${resultType}-list`;
|
const shouldUseTransitionForContent = resultType === 'stories' || resultType === 'gifts';
|
||||||
const itemSelector = `${activeListSelector} > .scroll-item`;
|
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 (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
@ -908,6 +1083,10 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
className="shared-media-transition"
|
className="shared-media-transition"
|
||||||
onStart={applyTransitionFix}
|
onStart={applyTransitionFix}
|
||||||
onStop={handleTransitionStop}
|
onStop={handleTransitionStop}
|
||||||
|
restoreHeightKey={shouldUseTransitionForContent ? restoreContentHeightKey : undefined}
|
||||||
|
contentSelector={shouldUseTransitionForContent
|
||||||
|
? '.Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .content'
|
||||||
|
: undefined}
|
||||||
>
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</Transition>
|
</Transition>
|
||||||
@ -998,13 +1177,21 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
|
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
|
||||||
&& !isSavedDialog;
|
&& !isSavedDialog;
|
||||||
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
|
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 pinnedStoryIds = peerStories?.pinnedIds;
|
||||||
const storyByIds = peerStories?.byId;
|
const storyByIds = peerStories?.byId;
|
||||||
const archiveStoryIds = peerStories?.archiveIds;
|
const archiveStoryIds = peerStories?.archiveIds;
|
||||||
|
|
||||||
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
|
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 monoforumChannel = selectMonoforumChannel(global, chatId);
|
||||||
const isRestricted = chat && selectIsChatRestricted(global, chat.id);
|
const isRestricted = chat && selectIsChatRestricted(global, chat.id);
|
||||||
@ -1033,12 +1220,17 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
storyIds,
|
storyIds,
|
||||||
hasGiftsTab,
|
hasGiftsTab,
|
||||||
gifts: peerGifts?.gifts,
|
gifts: peerGifts?.gifts,
|
||||||
|
storyAlbums,
|
||||||
|
giftCollections,
|
||||||
pinnedStoryIds,
|
pinnedStoryIds,
|
||||||
archiveStoryIds,
|
archiveStoryIds,
|
||||||
storyByIds,
|
storyByIds,
|
||||||
|
selectedStoryAlbumId,
|
||||||
|
activeCollectionId,
|
||||||
|
giftsFilter: savedGifts.filter,
|
||||||
isChatProtected: chat?.isProtected,
|
isChatProtected: chat?.isProtected,
|
||||||
nextProfileTab: selectTabState(global).nextProfileTab,
|
nextProfileTab,
|
||||||
forceScrollProfileTab: selectTabState(global).forceScrollProfileTab,
|
forceScrollProfileTab,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
shouldWarnAboutFiles,
|
shouldWarnAboutFiles,
|
||||||
similarChannels: similarChannelIds,
|
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,
|
&-slideOptimizedBackwards,
|
||||||
&-slideOptimizedRtl,
|
&-slideOptimizedRtl,
|
||||||
&-slideOptimizedRtlBackwards {
|
&-slideOptimizedRtlBackwards {
|
||||||
contain: strict;
|
transform: translate3d(0, 0, 0);
|
||||||
|
|
||||||
#root & > .Transition_slide {
|
#root & > .Transition_slide {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@ -47,6 +47,8 @@ export type TransitionProps = {
|
|||||||
onScroll?: NoneToVoidFunction;
|
onScroll?: NoneToVoidFunction;
|
||||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
children: React.ReactNode | ChildrenFn;
|
children: React.ReactNode | ChildrenFn;
|
||||||
|
contentSelector?: string;
|
||||||
|
restoreHeightKey?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FALLBACK_ANIMATION_END = 1000;
|
const FALLBACK_ANIMATION_END = 1000;
|
||||||
@ -89,6 +91,8 @@ function Transition({
|
|||||||
onScroll,
|
onScroll,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
children,
|
children,
|
||||||
|
contentSelector,
|
||||||
|
restoreHeightKey,
|
||||||
}: TransitionProps) {
|
}: TransitionProps) {
|
||||||
const currentKeyRef = useRef<number>();
|
const currentKeyRef = useRef<number>();
|
||||||
// No need for a container to update on change
|
// No need for a container to update on change
|
||||||
@ -361,7 +365,10 @@ function Transition({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { clientHeight, clientWidth } = activeElement || {};
|
const contentElement = contentSelector
|
||||||
|
? activeElement.querySelector<HTMLDivElement>(contentSelector) : activeElement;
|
||||||
|
|
||||||
|
const { clientHeight, clientWidth } = contentElement || activeElement || {};
|
||||||
if (!clientHeight || !clientWidth) {
|
if (!clientHeight || !clientWidth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -373,7 +380,7 @@ function Transition({
|
|||||||
flexBasis: `${clientHeight}px`,
|
flexBasis: `${clientHeight}px`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [shouldRestoreHeight, children]);
|
}, [shouldRestoreHeight, children, restoreHeightKey, contentSelector]);
|
||||||
|
|
||||||
const asFastList = !renderCount;
|
const asFastList = !renderCount;
|
||||||
const renders = rendersRef.current;
|
const renders = rendersRef.current;
|
||||||
|
|||||||
@ -18,12 +18,14 @@ import {
|
|||||||
appendStarsTransactions,
|
appendStarsTransactions,
|
||||||
replacePeerSavedGifts,
|
replacePeerSavedGifts,
|
||||||
updateChats,
|
updateChats,
|
||||||
|
updatePeerStarGiftCollections,
|
||||||
updateStarsBalance,
|
updateStarsBalance,
|
||||||
updateStarsSubscriptionLoading,
|
updateStarsSubscriptionLoading,
|
||||||
updateUsers,
|
updateUsers,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
|
selectActiveCollectionId,
|
||||||
selectGiftProfileFilter,
|
selectGiftProfileFilter,
|
||||||
selectPeer,
|
selectPeer,
|
||||||
selectPeerSavedGifts,
|
selectPeerSavedGifts,
|
||||||
@ -301,17 +303,20 @@ addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise
|
|||||||
if (!shouldRefresh && currentGifts && !localNextOffset) return; // Already loaded all
|
if (!shouldRefresh && currentGifts && !localNextOffset) return; // Already loaded all
|
||||||
|
|
||||||
const fetchingFilter = selectGiftProfileFilter(global, peerId, tabId);
|
const fetchingFilter = selectGiftProfileFilter(global, peerId, tabId);
|
||||||
|
const fetchingCollectionId = selectActiveCollectionId(global, peerId, tabId);
|
||||||
|
|
||||||
const result = await callApi('fetchSavedStarGifts', {
|
const result = await callApi('fetchSavedStarGifts', {
|
||||||
peer,
|
peer,
|
||||||
offset: !shouldRefresh ? localNextOffset : '',
|
offset: !shouldRefresh ? localNextOffset : '',
|
||||||
filter: fetchingFilter,
|
filter: fetchingFilter,
|
||||||
|
collectionId: fetchingCollectionId ? Number(fetchingCollectionId) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const currentFilter = selectGiftProfileFilter(global, peerId, tabId);
|
const currentFilter = selectGiftProfileFilter(global, peerId, tabId);
|
||||||
|
const currentCollectionId = selectActiveCollectionId(global, peerId, tabId);
|
||||||
|
|
||||||
if (!result || currentFilter !== fetchingFilter) {
|
if (!result || currentCollectionId !== fetchingCollectionId || currentFilter !== fetchingFilter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +400,8 @@ addActionHandler('changeGiftVisibility', async (global, actions, payload): Promi
|
|||||||
const requestInputGift = getRequestInputSavedStarGift(global, gift);
|
const requestInputGift = getRequestInputSavedStarGift(global, gift);
|
||||||
if (!requestInputGift) return;
|
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) {
|
if (oldGifts?.gifts?.length) {
|
||||||
const newGifts = oldGifts.gifts.map((g) => {
|
const newGifts = oldGifts.gifts.map((g) => {
|
||||||
if (g.inputGift && areInputSavedGiftsEqual(g.inputGift, gift)) {
|
if (g.inputGift && areInputSavedGiftsEqual(g.inputGift, gift)) {
|
||||||
@ -530,3 +536,25 @@ addActionHandler('updateStarGiftPrice', async (global, actions, payload): Promis
|
|||||||
|
|
||||||
actions.reloadPeerSavedGifts({ peerId: global.currentUserId! });
|
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> => {
|
addActionHandler('loadPeerProfileStories', async (global, actions, payload): Promise<void> => {
|
||||||
if (selectIsCurrentUserFrozen(global)) return;
|
if (selectIsCurrentUserFrozen(global)) return;
|
||||||
|
|
||||||
const { peerId, offsetId } = payload;
|
const { peerId, offsetId, tabId = getCurrentTabId() } = payload;
|
||||||
const peer = selectPeer(global, peerId);
|
const peer = selectPeer(global, peerId);
|
||||||
let peerStories = selectPeerStories(global, peerId);
|
let peerStories = selectPeerStories(global, peerId);
|
||||||
if (!peer || peerStories?.isFullyLoaded) {
|
if (!peer || peerStories?.isFullyLoaded) {
|
||||||
return;
|
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 });
|
const result = await callApi('fetchPeerProfileStories', { peer, offsetId });
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
@ -619,3 +649,66 @@ addActionHandler('activateStealthMode', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
|
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 type { ActionReturnType } from '../../types';
|
||||||
|
|
||||||
import { DEFAULT_GIFT_PROFILE_FILTER_OPTIONS } from '../../../config';
|
import { DEFAULT_GIFT_PROFILE_FILTER_OPTIONS } from '../../../config';
|
||||||
|
import { selectActiveCollectionId } from '../../../global/selectors';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { addActionHandler, setGlobal } from '../../index';
|
import { addActionHandler, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
@ -98,11 +100,15 @@ addActionHandler('updateGiftProfileFilter', (global, actions, payload): ActionRe
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
savedGifts: {
|
savedGifts: {
|
||||||
...tabState.savedGifts,
|
...tabState.savedGifts,
|
||||||
giftsByPeerId: {
|
collectionsByPeerId: {
|
||||||
[peerId]: tabState.savedGifts.giftsByPeerId[peerId],
|
[peerId]: {
|
||||||
|
[activeCollectionId]: tabState.savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId],
|
||||||
|
} as Record<number | 'all', ApiSavedGifts>,
|
||||||
},
|
},
|
||||||
filter: updatedFilter,
|
filter: updatedFilter,
|
||||||
},
|
},
|
||||||
@ -118,11 +124,15 @@ addActionHandler('resetGiftProfileFilter', (global, actions, payload): ActionRet
|
|||||||
const { peerId, tabId = getCurrentTabId() } = payload || {};
|
const { peerId, tabId = getCurrentTabId() } = payload || {};
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
|
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
savedGifts: {
|
savedGifts: {
|
||||||
...tabState.savedGifts,
|
...tabState.savedGifts,
|
||||||
giftsByPeerId: {
|
collectionsByPeerId: {
|
||||||
[peerId]: tabState.savedGifts.giftsByPeerId[peerId],
|
[peerId]: {
|
||||||
|
[activeCollectionId]: tabState.savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId],
|
||||||
|
} as Record<number | 'all', ApiSavedGifts>,
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,
|
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
|||||||
import * as langProvider from '../../../util/oldLangProvider';
|
import * as langProvider from '../../../util/oldLangProvider';
|
||||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||||
import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments';
|
import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments';
|
||||||
import { addActionHandler } from '../../index';
|
import { addActionHandler, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
clearStarPayment, openStarsTransactionModal,
|
clearStarPayment, openStarsTransactionModal,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
@ -344,3 +344,43 @@ addActionHandler('openGiftTransferModal', (global, actions, payload): ActionRetu
|
|||||||
});
|
});
|
||||||
|
|
||||||
addTabStateResetterAction('closeGiftTransferModal', 'giftTransferModal');
|
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: {
|
stories: {
|
||||||
byPeerId: {},
|
byPeerId: {},
|
||||||
|
albumsByPeerId: {},
|
||||||
orderedPeerIds: {
|
orderedPeerIds: {
|
||||||
archived: [],
|
archived: [],
|
||||||
active: [],
|
active: [],
|
||||||
@ -393,7 +394,8 @@ export const INITIAL_TAB_STATE: TabState = {
|
|||||||
filter: {
|
filter: {
|
||||||
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,
|
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,
|
||||||
},
|
},
|
||||||
giftsByPeerId: {},
|
collectionsByPeerId: {},
|
||||||
|
activeCollectionByPeerId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
resaleGifts: {
|
resaleGifts: {
|
||||||
|
|||||||
@ -55,9 +55,10 @@ export function addStoriesForPeer<T extends GlobalState>(
|
|||||||
newStories: Record<number, ApiTypeStory>,
|
newStories: Record<number, ApiTypeStory>,
|
||||||
newPinnedIds?: number[],
|
newPinnedIds?: number[],
|
||||||
addToArchive?: boolean,
|
addToArchive?: boolean,
|
||||||
|
albumId?: number,
|
||||||
): T {
|
): T {
|
||||||
const {
|
const {
|
||||||
byId, orderedIds, profileIds, archiveIds, pinnedIds,
|
byId, orderedIds, profileIds, archiveIds, pinnedIds, idsByAlbumId,
|
||||||
} = global.stories.byPeerId[peerId] || {};
|
} = global.stories.byPeerId[peerId] || {};
|
||||||
const deletedIds = Object.keys(newStories).filter((id) => 'isDeleted' in newStories[Number(id)]).map(Number);
|
const deletedIds = Object.keys(newStories).filter((id) => 'isDeleted' in newStories[Number(id)]).map(Number);
|
||||||
const updatedById = { ...byId, ...newStories };
|
const updatedById = { ...byId, ...newStories };
|
||||||
@ -65,7 +66,7 @@ export function addStoriesForPeer<T extends GlobalState>(
|
|||||||
let updatedArchiveIds = [...(archiveIds || [])];
|
let updatedArchiveIds = [...(archiveIds || [])];
|
||||||
const updatedProfileIds = unique(
|
const updatedProfileIds = unique(
|
||||||
[...(profileIds || [])].concat(Object.values(newStories).reduce((ids, story) => {
|
[...(profileIds || [])].concat(Object.values(newStories).reduce((ids, story) => {
|
||||||
if ('isInProfile' in story && story.isInProfile) {
|
if (('isInProfile' in story && story.isInProfile)) {
|
||||||
ids.push(story.id);
|
ids.push(story.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +88,20 @@ export function addStoriesForPeer<T extends GlobalState>(
|
|||||||
.filter((storyId) => !deletedIds.includes(storyId));
|
.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 = {
|
||||||
...global,
|
...global,
|
||||||
stories: {
|
stories: {
|
||||||
@ -100,6 +115,7 @@ export function addStoriesForPeer<T extends GlobalState>(
|
|||||||
profileIds: updatedProfileIds,
|
profileIds: updatedProfileIds,
|
||||||
pinnedIds: pinnedIds || newPinnedIds,
|
pinnedIds: pinnedIds || newPinnedIds,
|
||||||
...(addToArchive && { archiveIds: updatedArchiveIds }),
|
...(addToArchive && { archiveIds: updatedArchiveIds }),
|
||||||
|
...(albumId !== undefined && { idsByAlbumId: updatedIdsByAlbumId }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -137,7 +153,33 @@ export function updatePeerStoriesFullyLoaded<T extends GlobalState>(
|
|||||||
peerId: string,
|
peerId: string,
|
||||||
isFullyLoaded: boolean,
|
isFullyLoaded: boolean,
|
||||||
isArchive?: boolean,
|
isArchive?: boolean,
|
||||||
|
albumId?: number,
|
||||||
): T {
|
): 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 {
|
return {
|
||||||
...global,
|
...global,
|
||||||
stories: {
|
stories: {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiMissingInvitedUser,
|
ApiMissingInvitedUser,
|
||||||
ApiSavedStarGift,
|
ApiSavedStarGift,
|
||||||
|
ApiStarGiftCollection,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiUserCommonChats,
|
ApiUserCommonChats,
|
||||||
ApiUserFullInfo,
|
ApiUserFullInfo,
|
||||||
@ -14,6 +15,7 @@ import { getCurrentTabId } from '../../util/establishMultitabRole';
|
|||||||
import { omit, omitUndefined, unique } from '../../util/iteratees';
|
import { omit, omitUndefined, unique } from '../../util/iteratees';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import { getSavedGiftKey } from '../helpers/stars';
|
import { getSavedGiftKey } from '../helpers/stars';
|
||||||
|
import { selectActiveCollectionId } from '../selectors';
|
||||||
import { selectTabState } from '../selectors';
|
import { selectTabState } from '../selectors';
|
||||||
import { updateTabState } from './tabs';
|
import { updateTabState } from './tabs';
|
||||||
|
|
||||||
@ -344,16 +346,38 @@ export function replacePeerSavedGifts<T extends GlobalState>(
|
|||||||
keyCounts.set(id, count + 1);
|
keyCounts.set(id, count + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
savedGifts: {
|
savedGifts: {
|
||||||
...tabState.savedGifts,
|
...tabState.savedGifts,
|
||||||
giftsByPeerId: {
|
collectionsByPeerId: {
|
||||||
...tabState.savedGifts.giftsByPeerId,
|
...tabState.savedGifts.collectionsByPeerId,
|
||||||
[peerId]: {
|
[peerId]: {
|
||||||
gifts,
|
...tabState.savedGifts.collectionsByPeerId[peerId],
|
||||||
nextOffset,
|
[activeCollectionId]: {
|
||||||
|
gifts,
|
||||||
|
nextOffset,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, 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);
|
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 type { GlobalState, TabArgs } from '../types';
|
||||||
|
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||||
@ -32,8 +32,17 @@ export function selectPeerSavedGifts<T extends GlobalState>(
|
|||||||
global: T,
|
global: T,
|
||||||
peerId: string,
|
peerId: string,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): ApiSavedGifts {
|
): ApiSavedGifts | undefined {
|
||||||
return selectTabState(global, tabId).savedGifts.giftsByPeerId[peerId];
|
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>(
|
export function selectPeerPaidMessagesStars<T extends GlobalState>(
|
||||||
|
|||||||
@ -1691,6 +1691,19 @@ export interface ActionPayloads {
|
|||||||
isForPast?: boolean;
|
isForPast?: boolean;
|
||||||
isForFuture?: boolean;
|
isForFuture?: boolean;
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
loadStoryAlbums: {
|
||||||
|
peerId: string;
|
||||||
|
};
|
||||||
|
selectStoryAlbum: {
|
||||||
|
peerId: string;
|
||||||
|
albumId?: number;
|
||||||
|
} & WithTabId;
|
||||||
|
loadAlbumStories: {
|
||||||
|
peerId: string;
|
||||||
|
albumId: number;
|
||||||
|
offsetId?: number;
|
||||||
|
};
|
||||||
|
resetSelectedStoryAlbum: WithTabId | undefined;
|
||||||
|
|
||||||
openBoostModal: {
|
openBoostModal: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -2596,6 +2609,13 @@ export interface ActionPayloads {
|
|||||||
recipientId: string;
|
recipientId: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeGiftTransferModal: WithTabId | undefined;
|
closeGiftTransferModal: WithTabId | undefined;
|
||||||
|
updateSelectedGiftCollection: {
|
||||||
|
peerId: string;
|
||||||
|
collectionId: number;
|
||||||
|
} & WithTabId;
|
||||||
|
resetSelectedGiftCollection: {
|
||||||
|
peerId: string;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
loadPeerSavedGifts: {
|
loadPeerSavedGifts: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
@ -2621,6 +2641,11 @@ export interface ActionPayloads {
|
|||||||
price: ApiTypeCurrencyAmount;
|
price: ApiTypeCurrencyAmount;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
|
loadStarGiftCollections: {
|
||||||
|
peerId: string;
|
||||||
|
hash?: string;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
openStarsGiftModal: ({
|
openStarsGiftModal: ({
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
forUserId?: string;
|
forUserId?: string;
|
||||||
|
|||||||
@ -30,12 +30,14 @@ import type {
|
|||||||
ApiSavedReactionTag,
|
ApiSavedReactionTag,
|
||||||
ApiSession,
|
ApiSession,
|
||||||
ApiSponsoredMessage,
|
ApiSponsoredMessage,
|
||||||
|
ApiStarGiftCollection,
|
||||||
ApiStarGiftRegular,
|
ApiStarGiftRegular,
|
||||||
ApiStarsAmount,
|
ApiStarsAmount,
|
||||||
ApiStarTopupOption,
|
ApiStarTopupOption,
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStickerSet,
|
ApiStickerSet,
|
||||||
|
ApiStoryAlbum,
|
||||||
ApiTimezone,
|
ApiTimezone,
|
||||||
ApiTonAmount,
|
ApiTonAmount,
|
||||||
ApiTranscription,
|
ApiTranscription,
|
||||||
@ -253,6 +255,7 @@ export type GlobalState = {
|
|||||||
archived: string[];
|
archived: string[];
|
||||||
};
|
};
|
||||||
stealthMode: ApiStealthMode;
|
stealthMode: ApiStealthMode;
|
||||||
|
albumsByPeerId: Record<string, ApiStoryAlbum[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
groupCalls: {
|
groupCalls: {
|
||||||
@ -307,6 +310,9 @@ export type GlobalState = {
|
|||||||
byId: Record<string, ApiStarGiftRegular>;
|
byId: Record<string, ApiStarGiftRegular>;
|
||||||
idsByCategory: Record<StarGiftCategory, string[]>;
|
idsByCategory: Record<StarGiftCategory, string[]>;
|
||||||
};
|
};
|
||||||
|
starGiftCollections?: {
|
||||||
|
byPeerId: Record<string, ApiStarGiftCollection[]>;
|
||||||
|
};
|
||||||
|
|
||||||
stickers: {
|
stickers: {
|
||||||
setsById: Record<string, ApiStickerSet>;
|
setsById: Record<string, ApiStickerSet>;
|
||||||
|
|||||||
@ -218,7 +218,8 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
savedGifts: {
|
savedGifts: {
|
||||||
giftsByPeerId: Record<string, ApiSavedGifts>;
|
collectionsByPeerId: Record<string, Record<number | 'all', ApiSavedGifts>>;
|
||||||
|
activeCollectionByPeerId: Record<string, number | undefined>;
|
||||||
filter: GiftProfileFilterOptions;
|
filter: GiftProfileFilterOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -342,6 +343,8 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectedStoryAlbumId?: number;
|
||||||
|
|
||||||
mediaViewer: {
|
mediaViewer: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: ThreadId;
|
threadId?: ThreadId;
|
||||||
|
|||||||
@ -1796,6 +1796,7 @@ payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:
|
|||||||
payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<InputSavedStarGift> = Bool;
|
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.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.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.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.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;
|
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.getPeerMaxIDs#535983c3 id:Vector<InputPeer> = Vector<int>;
|
||||||
stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool;
|
stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool;
|
||||||
stories.togglePinnedToTop#b297e9b peer:InputPeer id:Vector<int> = 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.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList;
|
||||||
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
||||||
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
||||||
|
|||||||
@ -330,6 +330,7 @@
|
|||||||
"payments.toggleStarGiftsPinnedToTop",
|
"payments.toggleStarGiftsPinnedToTop",
|
||||||
"payments.getResaleStarGifts",
|
"payments.getResaleStarGifts",
|
||||||
"payments.updateStarGiftPrice",
|
"payments.updateStarGiftPrice",
|
||||||
|
"payments.getStarGiftCollections",
|
||||||
"langpack.getLangPack",
|
"langpack.getLangPack",
|
||||||
"langpack.getStrings",
|
"langpack.getStrings",
|
||||||
"langpack.getLanguages",
|
"langpack.getLanguages",
|
||||||
@ -404,6 +405,8 @@
|
|||||||
"stories.getPeerStories",
|
"stories.getPeerStories",
|
||||||
"stories.getStoriesViews",
|
"stories.getStoriesViews",
|
||||||
"stories.togglePinnedToTop",
|
"stories.togglePinnedToTop",
|
||||||
|
"stories.getAlbums",
|
||||||
|
"stories.getAlbumStories",
|
||||||
"premium.getBoostsStatus",
|
"premium.getBoostsStatus",
|
||||||
"premium.getBoostersList",
|
"premium.getBoostersList",
|
||||||
"premium.applyBoost",
|
"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;
|
'PublicPostsPremiumFeatureSubtitle': undefined;
|
||||||
'PublicPostsSubscribeToPremium': undefined;
|
'PublicPostsSubscribeToPremium': undefined;
|
||||||
'PostsSearchTransaction': undefined;
|
'PostsSearchTransaction': undefined;
|
||||||
|
'AllStoriesCategory': undefined;
|
||||||
'TitleRating': undefined;
|
'TitleRating': undefined;
|
||||||
'RatingYourReflectsActivity': undefined;
|
'RatingYourReflectsActivity': undefined;
|
||||||
'RatingGiftsFromTelegram': undefined;
|
'RatingGiftsFromTelegram': undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user