Support pinned gifts (#5744)
This commit is contained in:
parent
905124fe96
commit
440001b938
7
package-lock.json
generated
7
package-lock.json
generated
@ -44,6 +44,7 @@
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@twbs/fantasticon": "^3.0.0",
|
||||
"@types/croppie": "^2.6.4",
|
||||
"@types/dom-view-transitions": "^1.0.6",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "^18.3.1",
|
||||
@ -5146,6 +5147,12 @@
|
||||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dom-view-transitions": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-view-transitions/-/dom-view-transitions-1.0.6.tgz",
|
||||
"integrity": "sha512-Q5IoDouTOcZKEs4nDpmUti2MXP48MQDBkS2nUQKFrsDeTFIaArVmhWUon28vlDfNkbbaK4EROu4Fv0iKObWjSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.56.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
|
||||
|
||||
@ -66,6 +66,7 @@
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@twbs/fantasticon": "^3.0.0",
|
||||
"@types/croppie": "^2.6.4",
|
||||
"@types/dom-view-transitions": "^1.0.6",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "^18.3.1",
|
||||
|
||||
@ -87,6 +87,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
stargifts_convert_period_max?: number;
|
||||
starref_start_param_prefixes?: string[];
|
||||
ton_blockchain_explorer_url?: string;
|
||||
stargifts_pinned_to_top_limit?: number;
|
||||
}
|
||||
|
||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
@ -174,5 +175,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
starGiftMaxConvertPeriod: appConfig.stargifts_convert_period_max,
|
||||
starRefStartPrefixes: appConfig.starref_start_param_prefixes,
|
||||
tonExplorerUrl: appConfig.ton_blockchain_explorer_url,
|
||||
savedGiftPinLimit: appConfig.stargifts_pinned_to_top_limit,
|
||||
};
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
|
||||
export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
|
||||
const {
|
||||
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
|
||||
savedId, canExportAt,
|
||||
savedId, canExportAt, pinnedToTop,
|
||||
} = userStarGift;
|
||||
|
||||
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
|
||||
@ -153,5 +153,6 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
|
||||
inputGift,
|
||||
savedId: savedId?.toString(),
|
||||
canExportAt,
|
||||
isPinned: pinnedToTop,
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,3 +42,5 @@ export * from './stories';
|
||||
export * from './payments';
|
||||
|
||||
export * from './fragment';
|
||||
|
||||
export * from './stars';
|
||||
|
||||
@ -1,25 +1,15 @@
|
||||
import BigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
GiftProfileFilterOptions,
|
||||
} from '../../../types';
|
||||
import type {
|
||||
ApiChat,
|
||||
ApiInputStorePaymentPurpose,
|
||||
ApiPeer,
|
||||
ApiRequestInputInvoice,
|
||||
ApiRequestInputSavedStarGift,
|
||||
ApiStarGiftRegular,
|
||||
ApiThemeParameters,
|
||||
} from '../../types';
|
||||
|
||||
import { DEBUG } from '../../../config';
|
||||
import {
|
||||
buildApiSavedStarGift,
|
||||
buildApiStarGift,
|
||||
buildApiStarGiftAttribute,
|
||||
} from '../apiBuilders/gifts';
|
||||
import {
|
||||
buildApiBoost,
|
||||
buildApiBoostsStatus,
|
||||
@ -30,33 +20,24 @@ import {
|
||||
buildApiPremiumGiftCodeOption,
|
||||
buildApiPremiumPromo,
|
||||
buildApiReceipt,
|
||||
buildApiStarsAmount,
|
||||
buildApiStarsGiftOptions,
|
||||
buildApiStarsGiveawayOptions,
|
||||
buildApiStarsSubscription,
|
||||
buildApiStarsTransaction,
|
||||
buildApiStarTopupOption,
|
||||
buildShippingOptions,
|
||||
} from '../apiBuilders/payments';
|
||||
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||
import {
|
||||
buildInputInvoice,
|
||||
buildInputPeer,
|
||||
buildInputSavedStarGift,
|
||||
buildInputStorePaymentPurpose,
|
||||
buildInputThemeParams,
|
||||
buildShippingInfo,
|
||||
} from '../gramjsBuilders';
|
||||
import {
|
||||
checkErrorType,
|
||||
deserializeBytes,
|
||||
serializeBytes,
|
||||
wrapError,
|
||||
} from '../helpers/misc';
|
||||
import localDb from '../localDb';
|
||||
import { sendApiUpdate } from '../updates/apiUpdateEmitter';
|
||||
import { handleGramJsUpdate, invokeRequest } from './client';
|
||||
import { getPassword, getTemporaryPaymentPassword } from './twoFaSettings';
|
||||
import { getTemporaryPaymentPassword } from './twoFaSettings';
|
||||
|
||||
export async function validateRequestedInfo({
|
||||
inputInvoice,
|
||||
@ -413,107 +394,6 @@ export async function getPremiumGiftCodeOptions({
|
||||
return result.map(buildApiPremiumGiftCodeOption);
|
||||
}
|
||||
|
||||
export async function getStarsGiftOptions({
|
||||
chat,
|
||||
}: {
|
||||
chat?: ApiChat;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsGiftOptions({
|
||||
userId: chat && buildInputPeer(chat.id, chat.accessHash),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarsGiftOptions);
|
||||
}
|
||||
|
||||
export async function fetchStarsGiveawayOptions() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsGiveawayOptions());
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarsGiveawayOptions);
|
||||
}
|
||||
|
||||
export async function fetchStarGifts() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGifts({}));
|
||||
|
||||
if (!result || result instanceof GramJs.payments.StarGiftsNotModified) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Right now, only regular star gifts can be bought, but API are not specific
|
||||
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
|
||||
}
|
||||
|
||||
export async function fetchSavedStarGifts({
|
||||
peer,
|
||||
offset = '',
|
||||
limit,
|
||||
filter,
|
||||
}: {
|
||||
peer: ApiPeer;
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
filter?: GiftProfileFilterOptions;
|
||||
}) {
|
||||
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
||||
|
||||
const params : GetSavedStarGiftsParams = {
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
offset,
|
||||
limit,
|
||||
...(filter && {
|
||||
sortByValue: filter.sortType === 'byValue' || undefined,
|
||||
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
||||
excludeLimited: !filter.shouldIncludeLimited || undefined,
|
||||
excludeUnique: !filter.shouldIncludeUnique || undefined,
|
||||
excludeSaved: !filter.shouldIncludeDisplayed || undefined,
|
||||
excludeUnsaved: !filter.shouldIncludeHidden || undefined,
|
||||
} satisfies GetSavedStarGiftsParams),
|
||||
};
|
||||
|
||||
const result = await invokeRequest(new GramJs.payments.GetSavedStarGifts(params));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const gifts = result.gifts.map((g) => buildApiSavedStarGift(g, peer.id));
|
||||
|
||||
return {
|
||||
gifts,
|
||||
nextOffset: result.nextOffset,
|
||||
};
|
||||
}
|
||||
|
||||
export function saveStarGift({
|
||||
inputGift,
|
||||
shouldUnsave,
|
||||
}: {
|
||||
inputGift: ApiRequestInputSavedStarGift;
|
||||
shouldUnsave?: boolean;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.SaveStarGift({
|
||||
stargift: buildInputSavedStarGift(inputGift),
|
||||
unsave: shouldUnsave || undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export function convertStarGift({
|
||||
inputSavedGift,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.ConvertStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
}));
|
||||
}
|
||||
|
||||
export function launchPrepaidGiveaway({
|
||||
chat,
|
||||
giveawayId,
|
||||
@ -531,234 +411,3 @@ export function launchPrepaidGiveaway({
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchStarsStatus() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
|
||||
peer: new GramJs.InputPeerSelf(),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
nextHistoryOffset: result.nextOffset,
|
||||
history: result.history?.map(buildApiStarsTransaction),
|
||||
nextSubscriptionOffset: result.subscriptionsNextOffset,
|
||||
subscriptions: result.subscriptions?.map(buildApiStarsSubscription),
|
||||
balance: buildApiStarsAmount(result.balance),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsTransactions({
|
||||
peer,
|
||||
offset,
|
||||
isInbound,
|
||||
isOutbound,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
offset?: string;
|
||||
isInbound?: true;
|
||||
isOutbound?: true;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
|
||||
peer: inputPeer,
|
||||
offset,
|
||||
inbound: isInbound,
|
||||
outbound: isOutbound,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
nextOffset: result.nextOffset,
|
||||
history: result.history?.map(buildApiStarsTransaction),
|
||||
balance: buildApiStarsAmount(result.balance),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsTransactionById({
|
||||
id, peer,
|
||||
}: {
|
||||
id: string;
|
||||
peer?: ApiPeer;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({
|
||||
peer: inputPeer,
|
||||
id: [new GramJs.InputStarsTransaction({
|
||||
id,
|
||||
})],
|
||||
}));
|
||||
|
||||
if (!result?.history?.[0]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
transaction: buildApiStarsTransaction(result?.history[0]),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsSubscriptions({
|
||||
offset, peer,
|
||||
}: {
|
||||
offset?: string;
|
||||
peer?: ApiPeer;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsSubscriptions({
|
||||
peer: inputPeer,
|
||||
offset,
|
||||
}));
|
||||
|
||||
if (!result?.subscriptions) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
nextOffset: result.subscriptionsNextOffset,
|
||||
subscriptions: result.subscriptions.map(buildApiStarsSubscription),
|
||||
balance: buildApiStarsAmount(result.balance),
|
||||
};
|
||||
}
|
||||
|
||||
export async function changeStarsSubscription({
|
||||
peer, subscriptionId, isCancelled,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
subscriptionId: string;
|
||||
isCancelled: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.ChangeStarsSubscription({
|
||||
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
||||
subscriptionId,
|
||||
canceled: isCancelled,
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fulfillStarsSubscription({
|
||||
peer, subscriptionId,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
subscriptionId: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.FulfillStarsSubscription({
|
||||
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
||||
subscriptionId,
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fetchStarsTopupOptions() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions());
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarTopupOption);
|
||||
}
|
||||
|
||||
export async function fetchUniqueStarGift({ slug }: {
|
||||
slug: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetUniqueStarGift({ slug }));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
const gift = buildApiStarGift(result.gift);
|
||||
if (gift.type !== 'starGiftUnique') return undefined;
|
||||
return gift;
|
||||
}
|
||||
|
||||
export async function fetchStarGiftUpgradePreview({
|
||||
giftId,
|
||||
}: {
|
||||
giftId: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradePreview({
|
||||
giftId: BigInt(giftId),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean);
|
||||
}
|
||||
|
||||
export function upgradeStarGift({
|
||||
inputSavedGift,
|
||||
shouldKeepOriginalDetails,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
shouldKeepOriginalDetails?: true;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.UpgradeStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
keepOriginalDetails: shouldKeepOriginalDetails,
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function transferStarGift({
|
||||
inputSavedGift,
|
||||
toPeer,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
toPeer: ApiPeer;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.TransferStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
toId: buildInputPeer(toPeer.id, toPeer.accessHash),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchStarGiftWithdrawalUrl({
|
||||
inputGift,
|
||||
password,
|
||||
}: {
|
||||
inputGift: ApiRequestInputSavedStarGift;
|
||||
password: string;
|
||||
}) {
|
||||
try {
|
||||
const passwordCheck = await getPassword(password);
|
||||
|
||||
if (!passwordCheck) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if ('error' in passwordCheck) {
|
||||
return passwordCheck;
|
||||
}
|
||||
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftWithdrawalUrl({
|
||||
stargift: buildInputSavedStarGift(inputGift),
|
||||
password: passwordCheck,
|
||||
}), {
|
||||
shouldThrow: true,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { url: result.url };
|
||||
} catch (err: unknown) {
|
||||
if (!checkErrorType(err)) return undefined;
|
||||
|
||||
return wrapError(err);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
371
src/api/gramjs/methods/stars.ts
Normal file
371
src/api/gramjs/methods/stars.ts
Normal file
@ -0,0 +1,371 @@
|
||||
import bigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { GiftProfileFilterOptions } from '../../../types';
|
||||
import type {
|
||||
ApiChat,
|
||||
ApiPeer,
|
||||
ApiRequestInputSavedStarGift,
|
||||
ApiStarGiftRegular,
|
||||
} from '../../types';
|
||||
|
||||
import { buildApiSavedStarGift, buildApiStarGift, buildApiStarGiftAttribute } from '../apiBuilders/gifts';
|
||||
import {
|
||||
buildApiStarsAmount,
|
||||
buildApiStarsGiftOptions,
|
||||
buildApiStarsGiveawayOptions,
|
||||
buildApiStarsSubscription,
|
||||
buildApiStarsTransaction,
|
||||
buildApiStarTopupOption,
|
||||
} from '../apiBuilders/payments';
|
||||
import { buildInputPeer, buildInputSavedStarGift } from '../gramjsBuilders';
|
||||
import { checkErrorType, wrapError } from '../helpers/misc';
|
||||
import { invokeRequest } from './client';
|
||||
import { getPassword } from './twoFaSettings';
|
||||
|
||||
export async function fetchStarsGiveawayOptions() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsGiveawayOptions());
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarsGiveawayOptions);
|
||||
}
|
||||
|
||||
export async function fetchStarGifts() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGifts({}));
|
||||
|
||||
if (!result || result instanceof GramJs.payments.StarGiftsNotModified) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Right now, only regular star gifts can be bought, but API are not specific
|
||||
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
|
||||
}
|
||||
|
||||
export async function fetchSavedStarGifts({
|
||||
peer,
|
||||
offset = '',
|
||||
limit,
|
||||
filter,
|
||||
}: {
|
||||
peer: ApiPeer;
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
filter?: GiftProfileFilterOptions;
|
||||
}) {
|
||||
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
||||
|
||||
const params : GetSavedStarGiftsParams = {
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
offset,
|
||||
limit,
|
||||
...(filter && {
|
||||
sortByValue: filter.sortType === 'byValue' || undefined,
|
||||
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
||||
excludeLimited: !filter.shouldIncludeLimited || undefined,
|
||||
excludeUnique: !filter.shouldIncludeUnique || undefined,
|
||||
excludeSaved: !filter.shouldIncludeDisplayed || undefined,
|
||||
excludeUnsaved: !filter.shouldIncludeHidden || undefined,
|
||||
} satisfies GetSavedStarGiftsParams),
|
||||
};
|
||||
|
||||
const result = await invokeRequest(new GramJs.payments.GetSavedStarGifts(params));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const gifts = result.gifts.map((g) => buildApiSavedStarGift(g, peer.id));
|
||||
|
||||
return {
|
||||
gifts,
|
||||
nextOffset: result.nextOffset,
|
||||
};
|
||||
}
|
||||
|
||||
export function saveStarGift({
|
||||
inputGift,
|
||||
shouldUnsave,
|
||||
}: {
|
||||
inputGift: ApiRequestInputSavedStarGift;
|
||||
shouldUnsave?: boolean;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.SaveStarGift({
|
||||
stargift: buildInputSavedStarGift(inputGift),
|
||||
unsave: shouldUnsave || undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export function convertStarGift({
|
||||
inputSavedGift,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.ConvertStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getStarsGiftOptions({
|
||||
chat,
|
||||
}: {
|
||||
chat?: ApiChat;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsGiftOptions({
|
||||
userId: chat && buildInputPeer(chat.id, chat.accessHash),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarsGiftOptions);
|
||||
}
|
||||
|
||||
export async function fetchStarsStatus() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
|
||||
peer: new GramJs.InputPeerSelf(),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
nextHistoryOffset: result.nextOffset,
|
||||
history: result.history?.map(buildApiStarsTransaction),
|
||||
nextSubscriptionOffset: result.subscriptionsNextOffset,
|
||||
subscriptions: result.subscriptions?.map(buildApiStarsSubscription),
|
||||
balance: buildApiStarsAmount(result.balance),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsTransactions({
|
||||
peer,
|
||||
offset,
|
||||
isInbound,
|
||||
isOutbound,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
offset?: string;
|
||||
isInbound?: true;
|
||||
isOutbound?: true;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
|
||||
peer: inputPeer,
|
||||
offset,
|
||||
inbound: isInbound,
|
||||
outbound: isOutbound,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
nextOffset: result.nextOffset,
|
||||
history: result.history?.map(buildApiStarsTransaction),
|
||||
balance: buildApiStarsAmount(result.balance),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsTransactionById({
|
||||
id, peer,
|
||||
}: {
|
||||
id: string;
|
||||
peer?: ApiPeer;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({
|
||||
peer: inputPeer,
|
||||
id: [new GramJs.InputStarsTransaction({
|
||||
id,
|
||||
})],
|
||||
}));
|
||||
|
||||
if (!result?.history?.[0]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
transaction: buildApiStarsTransaction(result?.history[0]),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsSubscriptions({
|
||||
offset, peer,
|
||||
}: {
|
||||
offset?: string;
|
||||
peer?: ApiPeer;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsSubscriptions({
|
||||
peer: inputPeer,
|
||||
offset,
|
||||
}));
|
||||
|
||||
if (!result?.subscriptions) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
nextOffset: result.subscriptionsNextOffset,
|
||||
subscriptions: result.subscriptions.map(buildApiStarsSubscription),
|
||||
balance: buildApiStarsAmount(result.balance),
|
||||
};
|
||||
}
|
||||
|
||||
export async function changeStarsSubscription({
|
||||
peer, subscriptionId, isCancelled,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
subscriptionId: string;
|
||||
isCancelled: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.ChangeStarsSubscription({
|
||||
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
||||
subscriptionId,
|
||||
canceled: isCancelled,
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fulfillStarsSubscription({
|
||||
peer, subscriptionId,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
subscriptionId: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.FulfillStarsSubscription({
|
||||
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
||||
subscriptionId,
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fetchStarsTopupOptions() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions());
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarTopupOption);
|
||||
}
|
||||
|
||||
export async function fetchUniqueStarGift({ slug }: {
|
||||
slug: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetUniqueStarGift({ slug }));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
const gift = buildApiStarGift(result.gift);
|
||||
if (gift.type !== 'starGiftUnique') return undefined;
|
||||
return gift;
|
||||
}
|
||||
|
||||
export async function fetchStarGiftUpgradePreview({
|
||||
giftId,
|
||||
}: {
|
||||
giftId: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradePreview({
|
||||
giftId: bigInt(giftId),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean);
|
||||
}
|
||||
|
||||
export function upgradeStarGift({
|
||||
inputSavedGift,
|
||||
shouldKeepOriginalDetails,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
shouldKeepOriginalDetails?: true;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.UpgradeStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
keepOriginalDetails: shouldKeepOriginalDetails,
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function transferStarGift({
|
||||
inputSavedGift,
|
||||
toPeer,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
toPeer: ApiPeer;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.TransferStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
toId: buildInputPeer(toPeer.id, toPeer.accessHash),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleSavedGiftPinned({
|
||||
inputSavedGifts,
|
||||
peer,
|
||||
}: {
|
||||
inputSavedGifts: ApiRequestInputSavedStarGift[];
|
||||
peer: ApiPeer;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.ToggleStarGiftsPinnedToTop({
|
||||
stargift: inputSavedGifts.map(buildInputSavedStarGift),
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchStarGiftWithdrawalUrl({
|
||||
inputGift,
|
||||
password,
|
||||
}: {
|
||||
inputGift: ApiRequestInputSavedStarGift;
|
||||
password: string;
|
||||
}) {
|
||||
try {
|
||||
const passwordCheck = await getPassword(password);
|
||||
|
||||
if (!passwordCheck) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if ('error' in passwordCheck) {
|
||||
return passwordCheck;
|
||||
}
|
||||
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftWithdrawalUrl({
|
||||
stargift: buildInputSavedStarGift(inputGift),
|
||||
password: passwordCheck,
|
||||
}), {
|
||||
shouldThrow: true,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { url: result.url };
|
||||
} catch (err: unknown) {
|
||||
if (!checkErrorType(err)) return undefined;
|
||||
|
||||
return wrapError(err);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@ -11,3 +11,4 @@ export * from './calls';
|
||||
export * from './statistics';
|
||||
export * from './stories';
|
||||
export * from './business';
|
||||
export * from './stars';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls';
|
||||
import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages';
|
||||
import type { ApiStarGiftRegular, ApiStarGiftUnique } from './payments';
|
||||
import type { ApiStarGiftRegular, ApiStarGiftUnique } from './stars';
|
||||
|
||||
interface ActionMediaType {
|
||||
mediaType: 'action';
|
||||
|
||||
@ -8,8 +8,8 @@ import type { ApiPeerColor } from './chats';
|
||||
import type { ApiMessageAction } from './messageActions';
|
||||
import type {
|
||||
ApiLabeledPrice,
|
||||
ApiStarGiftUnique,
|
||||
} from './payments';
|
||||
import type { ApiStarGiftUnique } from './stars';
|
||||
import type {
|
||||
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
||||
} from './stories';
|
||||
|
||||
@ -4,7 +4,8 @@ import type { CallbackAction } from '../../global/types';
|
||||
import type { IconName } from '../../types/icons';
|
||||
import type { RegularLangFnParameters } from '../../util/localization';
|
||||
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
||||
import type { ApiPremiumSection, ApiStarsSubscriptionPricing } from './payments';
|
||||
import type { ApiPremiumSection } from './payments';
|
||||
import type { ApiStarsSubscriptionPricing } from './stars';
|
||||
import type { ApiUser } from './users';
|
||||
|
||||
export interface ApiInitialArgs {
|
||||
@ -235,6 +236,7 @@ export interface ApiAppConfig {
|
||||
starGiftMaxConvertPeriod?: number;
|
||||
starRefStartPrefixes?: string[];
|
||||
tonExplorerUrl?: string;
|
||||
savedGiftPinLimit?: number;
|
||||
}
|
||||
|
||||
export interface ApiConfig {
|
||||
|
||||
@ -7,9 +7,10 @@ import type {
|
||||
ApiInvoice,
|
||||
ApiMessageEntity,
|
||||
ApiPaymentCredentials,
|
||||
ApiSticker,
|
||||
BoughtPaidMedia,
|
||||
} from './messages';
|
||||
import type {
|
||||
ApiInputSavedStarGift, ApiRequestInputSavedStarGift, ApiStarsGiveawayWinnerOption,
|
||||
} from './stars';
|
||||
import type { StatisticsOverviewPercentage } from './statistics';
|
||||
import type { ApiUser } from './users';
|
||||
|
||||
@ -190,114 +191,6 @@ export type ApiInputStorePaymentStarsGiveaway = {
|
||||
export type ApiInputStorePaymentPurpose = ApiInputStorePaymentGiveaway | ApiInputStorePaymentGiftcode |
|
||||
ApiInputStorePaymentStarsTopup | ApiInputStorePaymentStarsGift | ApiInputStorePaymentStarsGiveaway;
|
||||
|
||||
export interface ApiStarGiftRegular {
|
||||
type: 'starGift';
|
||||
isLimited?: true;
|
||||
id: string;
|
||||
sticker: ApiSticker;
|
||||
stars: number;
|
||||
availabilityRemains?: number;
|
||||
availabilityTotal?: number;
|
||||
starsToConvert: number;
|
||||
isSoldOut?: true;
|
||||
firstSaleDate?: number;
|
||||
lastSaleDate?: number;
|
||||
isBirthday?: true;
|
||||
upgradeStars?: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftUnique {
|
||||
type: 'starGiftUnique';
|
||||
id: string;
|
||||
title: string;
|
||||
number: number;
|
||||
ownerId?: string;
|
||||
ownerName?: string;
|
||||
ownerAddress?: string;
|
||||
issuedCount: number;
|
||||
totalCount: number;
|
||||
attributes: ApiStarGiftAttribute[];
|
||||
slug: string;
|
||||
giftAddress?: string;
|
||||
}
|
||||
|
||||
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
||||
|
||||
export interface ApiStarGiftAttributeModel {
|
||||
type: 'model';
|
||||
name: string;
|
||||
rarityPercent: number;
|
||||
sticker: ApiSticker;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAttributePattern {
|
||||
type: 'pattern';
|
||||
name: string;
|
||||
rarityPercent: number;
|
||||
sticker: ApiSticker;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAttributeBackdrop {
|
||||
type: 'backdrop';
|
||||
name: string;
|
||||
centerColor: string;
|
||||
edgeColor: string;
|
||||
patternColor: string;
|
||||
textColor: string;
|
||||
rarityPercent: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAttributeOriginalDetails {
|
||||
type: 'originalDetails';
|
||||
senderId?: string;
|
||||
recipientId: string;
|
||||
date: number;
|
||||
message?: ApiFormattedText;
|
||||
}
|
||||
|
||||
export type ApiStarGiftAttribute = ApiStarGiftAttributeModel | ApiStarGiftAttributePattern
|
||||
| ApiStarGiftAttributeBackdrop | ApiStarGiftAttributeOriginalDetails;
|
||||
|
||||
export interface ApiSavedStarGift {
|
||||
isNameHidden?: boolean;
|
||||
isUnsaved?: boolean;
|
||||
fromId?: string;
|
||||
date: number;
|
||||
gift: ApiStarGift;
|
||||
inputGift?: ApiInputSavedStarGift;
|
||||
savedId?: string;
|
||||
message?: ApiFormattedText;
|
||||
messageId?: number;
|
||||
starsToConvert?: number;
|
||||
canUpgrade?: true;
|
||||
alreadyPaidUpgradeStars?: number;
|
||||
transferStars?: number;
|
||||
canExportAt?: number;
|
||||
isConverted?: boolean; // Local field, used for Action Message
|
||||
upgradeMsgId?: number; // Local field, used for Action Message
|
||||
}
|
||||
|
||||
export interface ApiInputSavedStarGiftUser {
|
||||
type: 'user';
|
||||
messageId: number;
|
||||
}
|
||||
|
||||
export interface ApiInputSavedStarGiftChat {
|
||||
type: 'chat';
|
||||
chatId: string;
|
||||
savedId: string;
|
||||
}
|
||||
|
||||
export type ApiInputSavedStarGift = ApiInputSavedStarGiftUser | ApiInputSavedStarGiftChat;
|
||||
|
||||
export type ApiRequestInputSavedStarGiftUser = ApiInputSavedStarGiftUser;
|
||||
export type ApiRequestInputSavedStarGiftChat = {
|
||||
type: 'chat';
|
||||
chat: ApiChat;
|
||||
savedId: string;
|
||||
};
|
||||
export type ApiRequestInputSavedStarGift = ApiRequestInputSavedStarGiftUser | ApiRequestInputSavedStarGiftChat;
|
||||
|
||||
export interface ApiPremiumGiftCodeOption {
|
||||
users: number;
|
||||
months: number;
|
||||
@ -387,110 +280,6 @@ export type ApiCheckedGiftCode = {
|
||||
usedAt?: number;
|
||||
};
|
||||
|
||||
export interface ApiStarsAmount {
|
||||
amount: number;
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerUnsupported {
|
||||
type: 'unsupported';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerAppStore {
|
||||
type: 'appStore';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPlayMarket {
|
||||
type: 'playMarket';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPremiumBot {
|
||||
type: 'premiumBot';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerFragment {
|
||||
type: 'fragment';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerAds {
|
||||
type: 'ads';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionApi {
|
||||
type: 'api';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPeer {
|
||||
type: 'peer';
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type ApiStarsTransactionPeer =
|
||||
| ApiStarsTransactionPeerUnsupported
|
||||
| ApiStarsTransactionPeerAppStore
|
||||
| ApiStarsTransactionPeerPlayMarket
|
||||
| ApiStarsTransactionPeerPremiumBot
|
||||
| ApiStarsTransactionPeerFragment
|
||||
| ApiStarsTransactionPeerAds
|
||||
| ApiStarsTransactionApi
|
||||
| ApiStarsTransactionPeerPeer;
|
||||
|
||||
export interface ApiStarsTransaction {
|
||||
id?: string;
|
||||
peer: ApiStarsTransactionPeer;
|
||||
messageId?: number;
|
||||
stars: ApiStarsAmount;
|
||||
isRefund?: true;
|
||||
isGift?: true;
|
||||
starGift?: ApiStarGift;
|
||||
giveawayPostId?: number;
|
||||
isMyGift?: true; // Used only for outgoing star gift messages
|
||||
isReaction?: true;
|
||||
hasFailed?: true;
|
||||
isPending?: true;
|
||||
date: number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
photo?: ApiWebDocument;
|
||||
extendedMedia?: BoughtPaidMedia[];
|
||||
subscriptionPeriod?: number;
|
||||
starRefCommision?: number;
|
||||
isGiftUpgrade?: true;
|
||||
}
|
||||
|
||||
export interface ApiStarsSubscription {
|
||||
id: string;
|
||||
peerId: string;
|
||||
until: number;
|
||||
pricing: ApiStarsSubscriptionPricing;
|
||||
isCancelled?: true;
|
||||
canRefulfill?: true;
|
||||
hasMissingBalance?: true;
|
||||
chatInviteHash?: string;
|
||||
hasBotCancelled?: true;
|
||||
title?: string;
|
||||
photo?: ApiWebDocument;
|
||||
invoiceSlug?: string;
|
||||
}
|
||||
|
||||
export type ApiStarsSubscriptionPricing = {
|
||||
period: number;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export interface ApiStarTopupOption {
|
||||
isExtended?: true;
|
||||
stars: number;
|
||||
currency: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsGiveawayWinnerOption {
|
||||
isDefault?: true;
|
||||
users: number;
|
||||
perUserStars: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiveawayOption {
|
||||
isExtended?: true;
|
||||
isDefault?: true;
|
||||
|
||||
216
src/api/types/stars.ts
Normal file
216
src/api/types/stars.ts
Normal file
@ -0,0 +1,216 @@
|
||||
import type { ApiWebDocument } from './bots';
|
||||
import type { ApiChat } from './chats';
|
||||
import type { ApiFormattedText, ApiSticker, BoughtPaidMedia } from './messages';
|
||||
|
||||
export interface ApiStarGiftRegular {
|
||||
type: 'starGift';
|
||||
isLimited?: true;
|
||||
id: string;
|
||||
sticker: ApiSticker;
|
||||
stars: number;
|
||||
availabilityRemains?: number;
|
||||
availabilityTotal?: number;
|
||||
starsToConvert: number;
|
||||
isSoldOut?: true;
|
||||
firstSaleDate?: number;
|
||||
lastSaleDate?: number;
|
||||
isBirthday?: true;
|
||||
upgradeStars?: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftUnique {
|
||||
type: 'starGiftUnique';
|
||||
id: string;
|
||||
title: string;
|
||||
number: number;
|
||||
ownerId?: string;
|
||||
ownerName?: string;
|
||||
ownerAddress?: string;
|
||||
issuedCount: number;
|
||||
totalCount: number;
|
||||
attributes: ApiStarGiftAttribute[];
|
||||
slug: string;
|
||||
giftAddress?: string;
|
||||
}
|
||||
|
||||
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
||||
|
||||
export interface ApiStarGiftAttributeModel {
|
||||
type: 'model';
|
||||
name: string;
|
||||
rarityPercent: number;
|
||||
sticker: ApiSticker;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAttributePattern {
|
||||
type: 'pattern';
|
||||
name: string;
|
||||
rarityPercent: number;
|
||||
sticker: ApiSticker;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAttributeBackdrop {
|
||||
type: 'backdrop';
|
||||
name: string;
|
||||
centerColor: string;
|
||||
edgeColor: string;
|
||||
patternColor: string;
|
||||
textColor: string;
|
||||
rarityPercent: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAttributeOriginalDetails {
|
||||
type: 'originalDetails';
|
||||
senderId?: string;
|
||||
recipientId: string;
|
||||
date: number;
|
||||
message?: ApiFormattedText;
|
||||
}
|
||||
|
||||
export type ApiStarGiftAttribute = ApiStarGiftAttributeModel | ApiStarGiftAttributePattern
|
||||
| ApiStarGiftAttributeBackdrop | ApiStarGiftAttributeOriginalDetails;
|
||||
|
||||
export interface ApiSavedStarGift {
|
||||
isNameHidden?: boolean;
|
||||
isUnsaved?: boolean;
|
||||
fromId?: string;
|
||||
date: number;
|
||||
gift: ApiStarGift;
|
||||
inputGift?: ApiInputSavedStarGift;
|
||||
savedId?: string;
|
||||
message?: ApiFormattedText;
|
||||
messageId?: number;
|
||||
starsToConvert?: number;
|
||||
canUpgrade?: true;
|
||||
alreadyPaidUpgradeStars?: number;
|
||||
transferStars?: number;
|
||||
canExportAt?: number;
|
||||
isPinned?: boolean;
|
||||
isConverted?: boolean; // Local field, used for Action Message
|
||||
upgradeMsgId?: number; // Local field, used for Action Message
|
||||
}
|
||||
|
||||
export interface ApiInputSavedStarGiftUser {
|
||||
type: 'user';
|
||||
messageId: number;
|
||||
}
|
||||
|
||||
export interface ApiInputSavedStarGiftChat {
|
||||
type: 'chat';
|
||||
chatId: string;
|
||||
savedId: string;
|
||||
}
|
||||
|
||||
export type ApiInputSavedStarGift = ApiInputSavedStarGiftUser | ApiInputSavedStarGiftChat;
|
||||
|
||||
export type ApiRequestInputSavedStarGiftUser = ApiInputSavedStarGiftUser;
|
||||
export type ApiRequestInputSavedStarGiftChat = {
|
||||
type: 'chat';
|
||||
chat: ApiChat;
|
||||
savedId: string;
|
||||
};
|
||||
export type ApiRequestInputSavedStarGift = ApiRequestInputSavedStarGiftUser | ApiRequestInputSavedStarGiftChat;
|
||||
|
||||
export interface ApiStarsAmount {
|
||||
amount: number;
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerUnsupported {
|
||||
type: 'unsupported';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerAppStore {
|
||||
type: 'appStore';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPlayMarket {
|
||||
type: 'playMarket';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPremiumBot {
|
||||
type: 'premiumBot';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerFragment {
|
||||
type: 'fragment';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerAds {
|
||||
type: 'ads';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionApi {
|
||||
type: 'api';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPeer {
|
||||
type: 'peer';
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type ApiStarsTransactionPeer =
|
||||
| ApiStarsTransactionPeerUnsupported
|
||||
| ApiStarsTransactionPeerAppStore
|
||||
| ApiStarsTransactionPeerPlayMarket
|
||||
| ApiStarsTransactionPeerPremiumBot
|
||||
| ApiStarsTransactionPeerFragment
|
||||
| ApiStarsTransactionPeerAds
|
||||
| ApiStarsTransactionApi
|
||||
| ApiStarsTransactionPeerPeer;
|
||||
|
||||
export interface ApiStarsTransaction {
|
||||
id?: string;
|
||||
peer: ApiStarsTransactionPeer;
|
||||
messageId?: number;
|
||||
stars: ApiStarsAmount;
|
||||
isRefund?: true;
|
||||
isGift?: true;
|
||||
starGift?: ApiStarGift;
|
||||
giveawayPostId?: number;
|
||||
isMyGift?: true; // Used only for outgoing star gift messages
|
||||
isReaction?: true;
|
||||
hasFailed?: true;
|
||||
isPending?: true;
|
||||
date: number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
photo?: ApiWebDocument;
|
||||
extendedMedia?: BoughtPaidMedia[];
|
||||
subscriptionPeriod?: number;
|
||||
starRefCommision?: number;
|
||||
isGiftUpgrade?: true;
|
||||
}
|
||||
|
||||
export interface ApiStarsSubscription {
|
||||
id: string;
|
||||
peerId: string;
|
||||
until: number;
|
||||
pricing: ApiStarsSubscriptionPricing;
|
||||
isCancelled?: true;
|
||||
canRefulfill?: true;
|
||||
hasMissingBalance?: true;
|
||||
chatInviteHash?: string;
|
||||
hasBotCancelled?: true;
|
||||
title?: string;
|
||||
photo?: ApiWebDocument;
|
||||
invoiceSlug?: string;
|
||||
}
|
||||
|
||||
export type ApiStarsSubscriptionPricing = {
|
||||
period: number;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export interface ApiStarTopupOption {
|
||||
isExtended?: true;
|
||||
stars: number;
|
||||
currency: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsGiveawayWinnerOption {
|
||||
isDefault?: true;
|
||||
users: number;
|
||||
perUserStars: number;
|
||||
}
|
||||
@ -41,8 +41,8 @@ import type {
|
||||
ApiPeerNotifySettings,
|
||||
ApiSessionData,
|
||||
} from './misc';
|
||||
import type { ApiStarsAmount } from './payments';
|
||||
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
|
||||
import type { ApiStarsAmount } from './stars';
|
||||
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
||||
import type {
|
||||
ApiEmojiStatusType, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
||||
|
||||
@ -4,7 +4,7 @@ import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from
|
||||
import type { ApiPeerColor } from './chats';
|
||||
import type { ApiDocument, ApiPhoto } from './messages';
|
||||
import type { ApiBotVerification } from './misc';
|
||||
import type { ApiSavedStarGift } from './payments';
|
||||
import type { ApiSavedStarGift } from './stars';
|
||||
|
||||
export interface ApiUser {
|
||||
id: string;
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
@ -1413,6 +1413,8 @@
|
||||
"GiftInfoChannelHidden" = "This gift is hidden from visitors of your channel. {link}";
|
||||
"GiftInfoSavedHide" = "Hide >";
|
||||
"GiftInfoSavedShow" = "Show >";
|
||||
"GiftActionShow" = "Show";
|
||||
"GiftActionHide" = "Hide";
|
||||
"GiftInfoTonText" = "This gift is on the TON Blockchain. {link}";
|
||||
"GiftInfoTonLinkText" = "View >";
|
||||
"GiftInfoAvailability" = "Availability";
|
||||
|
||||
@ -29,5 +29,11 @@
|
||||
}
|
||||
|
||||
.transition {
|
||||
width: 1.5rem;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
.transitionSlide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@ -141,10 +141,11 @@ const FullNameTitle: FC<OwnProps> = ({
|
||||
{canShowEmojiStatus && emojiStatus && (
|
||||
<Transition
|
||||
className={styles.transition}
|
||||
slideClassName={styles.transitionSlide}
|
||||
activeKey={Number(emojiStatus.documentId)}
|
||||
name="fade"
|
||||
name="slideFade"
|
||||
direction={-1}
|
||||
shouldCleanup
|
||||
shouldRestoreHeight
|
||||
>
|
||||
<CustomEmoji
|
||||
forceAlways
|
||||
|
||||
@ -150,7 +150,7 @@ const PasswordForm: FC<OwnProps> = ({
|
||||
title="Toggle password visibility"
|
||||
aria-label="Toggle password visibility"
|
||||
>
|
||||
<Icon name={isPasswordVisible ? 'eye' : 'eye-closed'} />
|
||||
<Icon name={isPasswordVisible ? 'eye' : 'eye-crossed'} />
|
||||
</div>
|
||||
</div>
|
||||
{description && <p className="description">{description}</p>}
|
||||
|
||||
145
src/components/common/gift/GiftMenuItems.tsx
Normal file
145
src/components/common/gift/GiftMenuItems.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiEmojiStatusCollectible, ApiEmojiStatusType, ApiSavedStarGift, ApiStarGift,
|
||||
} from '../../../api/types';
|
||||
|
||||
import { DEFAULT_STATUS_ICON_ID, TME_LINK_PREFIX } from '../../../config';
|
||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
|
||||
type OwnProps = {
|
||||
peerId: string;
|
||||
canManage?: boolean;
|
||||
gift: ApiSavedStarGift | ApiStarGift;
|
||||
currentUserEmojiStatus?: ApiEmojiStatusType;
|
||||
collectibleEmojiStatuses?: ApiEmojiStatusType[];
|
||||
};
|
||||
|
||||
const GiftMenuItems = ({
|
||||
peerId,
|
||||
canManage,
|
||||
gift: typeGift,
|
||||
currentUserEmojiStatus,
|
||||
collectibleEmojiStatuses,
|
||||
}: OwnProps) => {
|
||||
const {
|
||||
showNotification,
|
||||
openChatWithDraft,
|
||||
openGiftTransferModal,
|
||||
openGiftStatusInfoModal,
|
||||
setEmojiStatus,
|
||||
toggleSavedGiftPinned,
|
||||
changeGiftVisibility,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const isSavedGift = typeGift && 'gift' in typeGift;
|
||||
const savedGift = isSavedGift ? typeGift : undefined;
|
||||
const gift = isSavedGift ? typeGift.gift : typeGift;
|
||||
|
||||
const starGiftUniqueSlug = gift?.type === 'starGiftUnique' ? gift.slug : undefined;
|
||||
const starGiftUniqueLink = useMemo(() => {
|
||||
if (!starGiftUniqueSlug) return undefined;
|
||||
return `${TME_LINK_PREFIX}nft/${starGiftUniqueSlug}`;
|
||||
}, [starGiftUniqueSlug]);
|
||||
const userCollectibleStatus = useMemo(() => {
|
||||
if (!starGiftUniqueSlug) return undefined;
|
||||
return collectibleEmojiStatuses?.find((
|
||||
status,
|
||||
) => status.type === 'collectible' && status.slug === starGiftUniqueSlug) as ApiEmojiStatusCollectible | undefined;
|
||||
}, [starGiftUniqueSlug, collectibleEmojiStatuses]);
|
||||
|
||||
const currenUniqueEmojiStatusSlug = currentUserEmojiStatus?.type === 'collectible'
|
||||
? currentUserEmojiStatus.slug : undefined;
|
||||
|
||||
const isGiftUnique = gift && gift.type === 'starGiftUnique';
|
||||
const canTakeOff = isGiftUnique && currenUniqueEmojiStatusSlug === gift.slug;
|
||||
const canWear = userCollectibleStatus && !canTakeOff;
|
||||
|
||||
const hasPinOptions = canManage && savedGift && !savedGift.isUnsaved && isGiftUnique;
|
||||
|
||||
const handleTriggerVisibility = useLastCallback(() => {
|
||||
const { inputGift, isUnsaved } = savedGift!;
|
||||
changeGiftVisibility({ gift: inputGift!, shouldUnsave: !isUnsaved });
|
||||
});
|
||||
|
||||
const handleCopyLink = useLastCallback(() => {
|
||||
if (!starGiftUniqueLink) return;
|
||||
copyTextToClipboard(starGiftUniqueLink);
|
||||
showNotification({
|
||||
message: lang('LinkCopied'),
|
||||
});
|
||||
});
|
||||
|
||||
const handleLinkShare = useLastCallback(() => {
|
||||
if (!starGiftUniqueLink) return;
|
||||
openChatWithDraft({ text: { text: starGiftUniqueLink } });
|
||||
});
|
||||
|
||||
const handleTransfer = useLastCallback(() => {
|
||||
if (savedGift?.gift.type !== 'starGiftUnique') return;
|
||||
openGiftTransferModal({ gift: savedGift });
|
||||
});
|
||||
|
||||
const handleWear = useLastCallback(() => {
|
||||
if (gift?.type !== 'starGiftUnique' || !userCollectibleStatus) return;
|
||||
openGiftStatusInfoModal({ emojiStatus: userCollectibleStatus });
|
||||
});
|
||||
|
||||
const handleTakeOff = useLastCallback(() => {
|
||||
if (canTakeOff) {
|
||||
setEmojiStatus({
|
||||
emojiStatus: { type: 'regular', documentId: DEFAULT_STATUS_ICON_ID },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleTogglePin = useLastCallback(() => {
|
||||
toggleSavedGiftPinned({ peerId, gift: savedGift! });
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasPinOptions && (
|
||||
<MenuItem icon={savedGift.isPinned ? 'unpin' : 'pin'} onClick={handleTogglePin}>
|
||||
{lang(savedGift.isPinned ? 'UnpinFromTop' : 'PinToTop')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem icon="link-badge" onClick={handleCopyLink}>
|
||||
{lang('CopyLink')}
|
||||
</MenuItem>
|
||||
<MenuItem icon="forward" onClick={handleLinkShare}>
|
||||
{lang('Share')}
|
||||
</MenuItem>
|
||||
{canManage && isGiftUnique && (
|
||||
<MenuItem icon="diamond" onClick={handleTransfer}>
|
||||
{lang('GiftInfoTransfer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canManage && savedGift && (
|
||||
<MenuItem icon={savedGift.isUnsaved ? 'eye-outline' : 'eye-crossed-outline'} onClick={handleTriggerVisibility}>
|
||||
{lang(savedGift.isUnsaved ? 'GiftActionShow' : 'GiftActionHide')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canWear && (
|
||||
<MenuItem icon="crown-wear" onClick={handleWear}>
|
||||
{lang('GiftInfoWear')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canTakeOff && (
|
||||
<MenuItem icon="crown-take-off" onClick={handleTakeOff}>
|
||||
{lang('GiftInfoTakeOff')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(GiftMenuItems);
|
||||
@ -29,10 +29,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
.topIcon {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
left: 0.25rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hiddenGift {
|
||||
|
||||
@ -1,23 +1,27 @@
|
||||
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiPeer, ApiSavedStarGift } from '../../../api/types';
|
||||
import type { ApiEmojiStatusType, ApiPeer, ApiSavedStarGift } from '../../../api/types';
|
||||
|
||||
import { selectPeer } from '../../../global/selectors';
|
||||
import { getHasAdminRight } from '../../../global/helpers';
|
||||
import { selectChat, selectPeer, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { CUSTOM_PEER_HIDDEN } from '../../../util/objects/customPeer';
|
||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||
import { getGiftAttributes, getStickerFromGift, getTotalGiftAvailability } from '../helpers/gifts';
|
||||
|
||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Menu from '../../ui/Menu';
|
||||
import AnimatedIconFromSticker from '../AnimatedIconFromSticker';
|
||||
import Avatar from '../Avatar';
|
||||
import Icon from '../icons/Icon';
|
||||
import RadialPatternBackground from '../profile/RadialPatternBackground';
|
||||
import GiftMenuItems from './GiftMenuItems';
|
||||
import GiftRibbon from './GiftRibbon';
|
||||
|
||||
import styles from './SavedGift.module.scss';
|
||||
@ -25,11 +29,16 @@ import styles from './SavedGift.module.scss';
|
||||
type OwnProps = {
|
||||
peerId: string;
|
||||
gift: ApiSavedStarGift;
|
||||
style?: string;
|
||||
observeIntersection?: ObserveFn;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
fromPeer?: ApiPeer;
|
||||
currentUserId?: string;
|
||||
hasAdminRights?: boolean;
|
||||
currentUserEmojiStatus?: ApiEmojiStatusType;
|
||||
collectibleEmojiStatuses?: ApiEmojiStatusType[];
|
||||
};
|
||||
|
||||
const GIFT_STICKER_SIZE = 90;
|
||||
@ -37,7 +46,12 @@ const GIFT_STICKER_SIZE = 90;
|
||||
const SavedGift = ({
|
||||
peerId,
|
||||
gift,
|
||||
style,
|
||||
fromPeer,
|
||||
currentUserId,
|
||||
hasAdminRights,
|
||||
collectibleEmojiStatuses,
|
||||
currentUserEmojiStatus,
|
||||
observeIntersection,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { openGiftInfoModal } = getActions();
|
||||
@ -49,6 +63,21 @@ const SavedGift = ({
|
||||
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const canManage = peerId === currentUserId || hasAdminRights;
|
||||
|
||||
const {
|
||||
isContextMenuOpen, contextMenuAnchor,
|
||||
handleBeforeContextMenu, handleContextMenu,
|
||||
handleContextMenuClose, handleContextMenuHide,
|
||||
} = useContextMenuHandlers(ref);
|
||||
|
||||
const getTriggerElement = useLastCallback(() => ref.current);
|
||||
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll'));
|
||||
const getMenuElement = useLastCallback(() => (
|
||||
document.querySelector('#portals')?.querySelector('.saved-gift-context-menu .bubble')
|
||||
));
|
||||
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
openGiftInfoModal({
|
||||
peerId,
|
||||
@ -94,10 +123,14 @@ const SavedGift = ({
|
||||
<div
|
||||
ref={ref}
|
||||
className={buildClassName(styles.root, 'scroll-item')}
|
||||
style={style}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseDown={handleBeforeContextMenu}
|
||||
>
|
||||
{radialPatternBackdrop}
|
||||
{!radialPatternBackdrop && <Avatar className={styles.avatar} peer={avatarPeer} size="micro" />}
|
||||
{!radialPatternBackdrop && <Avatar className={styles.topIcon} peer={avatarPeer} size="micro" />}
|
||||
{gift.isPinned && <Icon name="pinned-message" className={styles.topIcon} />}
|
||||
<AnimatedIconFromSticker
|
||||
sticker={sticker}
|
||||
noLoop
|
||||
@ -107,7 +140,7 @@ const SavedGift = ({
|
||||
/>
|
||||
{gift.isUnsaved && (
|
||||
<div className={styles.hiddenGift}>
|
||||
<Icon name="eye-closed-outline" />
|
||||
<Icon name="eye-crossed-outline" />
|
||||
</div>
|
||||
)}
|
||||
{totalIssued && (
|
||||
@ -116,16 +149,50 @@ const SavedGift = ({
|
||||
text={oldLang('Gift2Limited1OfRibbon', formatIntegerCompact(totalIssued))}
|
||||
/>
|
||||
)}
|
||||
{contextMenuAnchor !== undefined && (
|
||||
<Menu
|
||||
isOpen={isContextMenuOpen}
|
||||
anchor={contextMenuAnchor}
|
||||
className="saved-gift-context-menu"
|
||||
autoClose
|
||||
withPortal
|
||||
getMenuElement={getMenuElement}
|
||||
getTriggerElement={getTriggerElement}
|
||||
getRootElement={getRootElement}
|
||||
getLayout={getLayout}
|
||||
onClose={handleContextMenuClose}
|
||||
onCloseAnimationEnd={handleContextMenuHide}
|
||||
>
|
||||
<GiftMenuItems
|
||||
peerId={peerId}
|
||||
gift={gift}
|
||||
canManage={canManage}
|
||||
collectibleEmojiStatuses={collectibleEmojiStatuses}
|
||||
currentUserEmojiStatus={currentUserEmojiStatus}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { gift }): StateProps => {
|
||||
(global, { peerId, gift }): StateProps => {
|
||||
const fromPeer = gift.fromId ? selectPeer(global, gift.fromId) : undefined;
|
||||
const chat = selectChat(global, peerId);
|
||||
const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages');
|
||||
|
||||
const currentUserId = global.currentUserId;
|
||||
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;
|
||||
const currentUserEmojiStatus = currentUser?.emojiStatus;
|
||||
const collectibleEmojiStatuses = global.collectibleEmojiStatuses?.statuses;
|
||||
|
||||
return {
|
||||
fromPeer,
|
||||
hasAdminRights,
|
||||
currentUserId,
|
||||
currentUserEmojiStatus,
|
||||
collectibleEmojiStatuses,
|
||||
};
|
||||
},
|
||||
)(SavedGift));
|
||||
|
||||
@ -44,7 +44,7 @@ const STORY_FEATURE_DESCRIPTIONS = {
|
||||
|
||||
const STORY_FEATURE_ICONS: Record<string, IconName> = {
|
||||
stories_order: 'story-priority',
|
||||
stories_stealth: 'eye-closed-outline',
|
||||
stories_stealth: 'eye-crossed-outline',
|
||||
stories_views: 'eye-outline',
|
||||
stories_timer: 'timer',
|
||||
stories_save: 'arrow-down-circle',
|
||||
|
||||
@ -422,7 +422,7 @@ const TextFormatter: FC<OwnProps> = ({
|
||||
className={getFormatButtonClassName('spoiler')}
|
||||
onClick={handleSpoilerText}
|
||||
>
|
||||
<Icon name="eye-closed" />
|
||||
<Icon name="eye-crossed" />
|
||||
</Button>
|
||||
<div className="TextFormatter-divider" />
|
||||
<Button
|
||||
|
||||
@ -33,7 +33,7 @@ export type StateProps = {
|
||||
isPremium?: boolean;
|
||||
};
|
||||
|
||||
const INTERVAL = 3000;
|
||||
const INTERVAL = 3200;
|
||||
|
||||
const EmojiStatusAccessModal: FC<OwnProps & StateProps> = ({
|
||||
modal,
|
||||
|
||||
@ -3,13 +3,11 @@ import React, { memo, useMemo } from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import type {
|
||||
ApiEmojiStatusCollectible,
|
||||
ApiEmojiStatusType,
|
||||
ApiPeer,
|
||||
} from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { DEFAULT_STATUS_ICON_ID, TME_LINK_PREFIX } from '../../../../config';
|
||||
import { getHasAdminRight, getPeerTitle } from '../../../../global/helpers';
|
||||
import { isApiPeerChat } from '../../../../global/helpers/peers';
|
||||
import { selectPeer, selectUser } from '../../../../global/selectors';
|
||||
@ -32,13 +30,13 @@ import useOldLang from '../../../../hooks/useOldLang';
|
||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||
import Avatar from '../../../common/Avatar';
|
||||
import BadgeButton from '../../../common/BadgeButton';
|
||||
import GiftMenuItems from '../../../common/gift/GiftMenuItems';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
import SafeLink from '../../../common/SafeLink';
|
||||
import Button from '../../../ui/Button';
|
||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||
import DropdownMenu from '../../../ui/DropdownMenu';
|
||||
import Link from '../../../ui/Link';
|
||||
import MenuItem from '../../../ui/MenuItem';
|
||||
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
||||
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||
|
||||
@ -80,10 +78,6 @@ const GiftInfoModal = ({
|
||||
focusMessage,
|
||||
openGiftUpgradeModal,
|
||||
showNotification,
|
||||
openChatWithDraft,
|
||||
openGiftStatusInfoModal,
|
||||
setEmojiStatus,
|
||||
openGiftTransferModal,
|
||||
} = getActions();
|
||||
|
||||
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
||||
@ -111,27 +105,10 @@ const GiftInfoModal = ({
|
||||
const giftSticker = gift && getStickerFromGift(gift);
|
||||
const hasConvertOption = canConvertDifference > 0 && Boolean(savedGift?.starsToConvert);
|
||||
|
||||
const currenUniqueEmojiStatusSlug = currentUserEmojiStatus?.type === 'collectible'
|
||||
? currentUserEmojiStatus.slug : undefined;
|
||||
|
||||
const starGiftUniqueSlug = gift?.type === 'starGiftUnique' ? gift.slug : undefined;
|
||||
const starGiftUniqueLink = useMemo(() => {
|
||||
if (!starGiftUniqueSlug) return undefined;
|
||||
return `${TME_LINK_PREFIX}nft/${starGiftUniqueSlug}`;
|
||||
}, [starGiftUniqueSlug]);
|
||||
const userCollectibleStatus = useMemo(() => {
|
||||
if (!starGiftUniqueSlug) return undefined;
|
||||
return collectibleEmojiStatuses?.find((
|
||||
status,
|
||||
) => status.type === 'collectible' && status.slug === starGiftUniqueSlug) as ApiEmojiStatusCollectible | undefined;
|
||||
}, [starGiftUniqueSlug, collectibleEmojiStatuses]);
|
||||
|
||||
const isGiftUnique = gift && gift.type === 'starGiftUnique';
|
||||
const canTakeOff = isGiftUnique && currenUniqueEmojiStatusSlug === gift.slug;
|
||||
const canWear = userCollectibleStatus && !canTakeOff;
|
||||
|
||||
const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId);
|
||||
const canUpdate = !canFocusUpgrade && savedGift?.inputGift && (
|
||||
const canManage = !canFocusUpgrade && savedGift?.inputGift && (
|
||||
isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId
|
||||
);
|
||||
|
||||
@ -139,38 +116,6 @@ const GiftInfoModal = ({
|
||||
closeGiftInfoModal();
|
||||
});
|
||||
|
||||
const handleCopyLink = useLastCallback(() => {
|
||||
if (!starGiftUniqueLink) return;
|
||||
copyTextToClipboard(starGiftUniqueLink);
|
||||
showNotification({
|
||||
message: lang('LinkCopied'),
|
||||
});
|
||||
});
|
||||
|
||||
const handleLinkShare = useLastCallback(() => {
|
||||
if (!starGiftUniqueLink) return;
|
||||
openChatWithDraft({ text: { text: starGiftUniqueLink } });
|
||||
handleClose();
|
||||
});
|
||||
|
||||
const handleTransfer = useLastCallback(() => {
|
||||
if (savedGift?.gift.type !== 'starGiftUnique') return;
|
||||
openGiftTransferModal({ gift: savedGift });
|
||||
});
|
||||
|
||||
const handleWear = useLastCallback(() => {
|
||||
if (gift?.type !== 'starGiftUnique' || !userCollectibleStatus) return;
|
||||
openGiftStatusInfoModal({ emojiStatus: userCollectibleStatus });
|
||||
});
|
||||
|
||||
const handleTakeOff = useLastCallback(() => {
|
||||
if (canTakeOff) {
|
||||
setEmojiStatus({
|
||||
emojiStatus: { type: 'regular', documentId: DEFAULT_STATUS_ICON_ID },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleFocusUpgraded = useLastCallback(() => {
|
||||
const giftChat = isSender ? renderingTargetPeer : renderingFromPeer;
|
||||
if (!savedGift?.upgradeMsgId || !giftChat) return;
|
||||
@ -225,7 +170,7 @@ const GiftInfoModal = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (canUpdate && savedGift?.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
|
||||
if (canManage && savedGift?.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
|
||||
return (
|
||||
<Button size="smaller" isShiny onClick={handleOpenUpgradeModal}>
|
||||
{lang('GiftInfoUpgradeForFree')}
|
||||
@ -258,13 +203,13 @@ const GiftInfoModal = ({
|
||||
|
||||
if (savedGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded');
|
||||
if (savedGift.canUpgrade && savedGift.alreadyPaidUpgradeStars) {
|
||||
return canUpdate
|
||||
return canManage
|
||||
? lang('GiftInfoDescriptionFreeUpgrade')
|
||||
: lang('GiftInfoPeerDescriptionFreeUpgradeOut', { peer: getPeerTitle(lang, renderingTargetPeer!)! });
|
||||
}
|
||||
if (!canUpdate && !isSender) return undefined;
|
||||
if (!canManage && !isSender) return undefined;
|
||||
if (isConverted && canConvert) {
|
||||
return canUpdate
|
||||
return canManage
|
||||
? lang('GiftInfoDescriptionConverted', {
|
||||
amount: starsToConvert,
|
||||
}, {
|
||||
@ -282,7 +227,7 @@ const GiftInfoModal = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (savedGift.canUpgrade && canUpdate) {
|
||||
if (savedGift.canUpgrade && canManage) {
|
||||
if (canConvert) {
|
||||
return lang('GiftInfoDescriptionUpgrade', {
|
||||
amount: starsToConvert,
|
||||
@ -296,7 +241,7 @@ const GiftInfoModal = ({
|
||||
return lang('GiftInfoDescriptionUpgradeRegular');
|
||||
}
|
||||
|
||||
if (canUpdate) {
|
||||
if (canManage) {
|
||||
if (canConvert) {
|
||||
return lang('GiftInfoDescription', {
|
||||
amount: starsToConvert,
|
||||
@ -328,7 +273,7 @@ const GiftInfoModal = ({
|
||||
if (isGiftUnique) return gift.title;
|
||||
if (!savedGift) return lang('GiftInfoSoldOutTitle');
|
||||
|
||||
return canUpdate ? lang('GiftInfoReceived') : lang('GiftInfoTitle');
|
||||
return canManage ? lang('GiftInfoReceived') : lang('GiftInfoTitle');
|
||||
}
|
||||
|
||||
const uniqueGiftContextMenu = (
|
||||
@ -337,27 +282,13 @@ const GiftInfoModal = ({
|
||||
trigger={SettingsMenuButton}
|
||||
positionX="right"
|
||||
>
|
||||
<MenuItem icon="link-badge" onClick={handleCopyLink}>
|
||||
{lang('CopyLink')}
|
||||
</MenuItem>
|
||||
<MenuItem icon="forward" onClick={handleLinkShare}>
|
||||
{lang('Share')}
|
||||
</MenuItem>
|
||||
{canUpdate && isGiftUnique && (
|
||||
<MenuItem icon="diamond" onClick={handleTransfer}>
|
||||
{lang('GiftInfoTransfer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canWear && (
|
||||
<MenuItem icon="crown-wear" onClick={handleWear}>
|
||||
{lang('GiftInfoWear')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canTakeOff && (
|
||||
<MenuItem icon="crown-take-off" onClick={handleTakeOff}>
|
||||
{lang('GiftInfoTakeOff')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<GiftMenuItems
|
||||
peerId={renderingModal!.peerId!}
|
||||
gift={typeGift}
|
||||
canManage={canManage}
|
||||
collectibleEmojiStatuses={collectibleEmojiStatuses}
|
||||
currentUserEmojiStatus={currentUserEmojiStatus}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@ -452,7 +383,7 @@ const GiftInfoModal = ({
|
||||
lang('GiftInfoValue'),
|
||||
<div className={styles.giftValue}>
|
||||
{formatStarsAsIcon(lang, starsValue, { className: styles.starAmountIcon })}
|
||||
{canUpdate && hasConvertOption && Boolean(starsToConvert) && (
|
||||
{canManage && hasConvertOption && Boolean(starsToConvert) && (
|
||||
<BadgeButton onClick={openConvertConfirm}>
|
||||
{lang('GiftInfoConvert', { amount: starsToConvert }, { pluralValue: starsToConvert })}
|
||||
</BadgeButton>
|
||||
@ -477,7 +408,7 @@ const GiftInfoModal = ({
|
||||
lang('GiftInfoStatus'),
|
||||
<div className={styles.giftValue}>
|
||||
{lang('GiftInfoStatusNonUnique')}
|
||||
{canUpdate && <BadgeButton onClick={handleOpenUpgradeModal}>{lang('GiftInfoUpgradeBadge')}</BadgeButton>}
|
||||
{canManage && <BadgeButton onClick={handleOpenUpgradeModal}>{lang('GiftInfoUpgradeBadge')}</BadgeButton>}
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
@ -627,7 +558,7 @@ const GiftInfoModal = ({
|
||||
|
||||
const footer = (
|
||||
<div className={styles.footer}>
|
||||
{(canUpdate || tonLink) && (
|
||||
{(canManage || tonLink) && (
|
||||
<div className={styles.footerDescription}>
|
||||
{tonLink && (
|
||||
<div>
|
||||
@ -636,7 +567,7 @@ const GiftInfoModal = ({
|
||||
}, { withNodes: true })}
|
||||
</div>
|
||||
)}
|
||||
{canUpdate && (
|
||||
{canManage && (
|
||||
<div>
|
||||
{lang(`GiftInfo${isTargetChat ? 'Channel' : ''}${isUnsaved ? 'Hidden' : 'Saved'}`, {
|
||||
link: (
|
||||
@ -668,9 +599,10 @@ const GiftInfoModal = ({
|
||||
};
|
||||
}, [
|
||||
typeGift, savedGift, renderingTargetPeer, giftSticker, lang,
|
||||
canUpdate, hasConvertOption, isSender, oldLang, tonExplorerUrl,
|
||||
canManage, hasConvertOption, isSender, oldLang, tonExplorerUrl,
|
||||
gift, giftAttributes, renderFooterButton, isTargetChat,
|
||||
SettingsMenuButton, isOpen, isGiftUnique, canWear, canTakeOff,
|
||||
SettingsMenuButton, isOpen, isGiftUnique, renderingModal,
|
||||
collectibleEmojiStatuses, currentUserEmojiStatus,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -67,6 +67,7 @@ import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
import { getSenderName } from '../left/search/helpers/getSenderName';
|
||||
|
||||
import { useViewTransition } from '../../hooks/animations/useViewTransition';
|
||||
import usePeerStoriesPolling from '../../hooks/polling/usePeerStoriesPolling';
|
||||
import useCacheBuster from '../../hooks/useCacheBuster';
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
@ -130,7 +131,6 @@ type StateProps = {
|
||||
hasPreviewMediaTab?: boolean;
|
||||
hasGiftsTab?: boolean;
|
||||
gifts?: ApiSavedStarGift[];
|
||||
giftsTransitionKey: number;
|
||||
areMembersHidden?: boolean;
|
||||
canAddMembers?: boolean;
|
||||
canDeleteMembers?: boolean;
|
||||
@ -198,7 +198,6 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
hasPreviewMediaTab,
|
||||
hasGiftsTab,
|
||||
gifts,
|
||||
giftsTransitionKey,
|
||||
botPreviewMedia,
|
||||
areMembersHidden,
|
||||
canAddMembers,
|
||||
@ -356,9 +355,13 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [chatId, isBot, similarBots, isSynced]);
|
||||
|
||||
const giftIds = useMemo(() => {
|
||||
return gifts?.map(({ date, gift, fromId }) => `${date}-${fromId}-${gift.id}`);
|
||||
}, [gifts]);
|
||||
const [renderingGifts, setRenderingGifts] = useState(gifts);
|
||||
const { startViewTransition, shouldApplyVtn } = useViewTransition();
|
||||
|
||||
const getGiftId = useLastCallback((gift: ApiSavedStarGift) => (
|
||||
`${gift.date}-${gift.fromId}-${gift.gift.id}`
|
||||
));
|
||||
const giftIds = useMemo(() => renderingGifts?.map(getGiftId), [renderingGifts]);
|
||||
|
||||
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
|
||||
const tabType = tabs[renderingActiveTab].type as ProfileTabType;
|
||||
@ -375,6 +378,25 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
loadPeerSavedGifts({ peerId: chatId });
|
||||
}, [chatId]);
|
||||
|
||||
useEffectWithPrevDeps(([prevGifts]) => {
|
||||
if (!gifts || !prevGifts) {
|
||||
setRenderingGifts(gifts);
|
||||
return;
|
||||
}
|
||||
|
||||
const prevGiftIds = prevGifts.map(getGiftId);
|
||||
const newGiftIds = gifts.map(getGiftId);
|
||||
const hasOrderChanged = prevGiftIds.some((id, index) => id !== newGiftIds[index]);
|
||||
|
||||
if (hasOrderChanged) {
|
||||
startViewTransition(() => {
|
||||
setRenderingGifts(gifts);
|
||||
});
|
||||
} else {
|
||||
setRenderingGifts(gifts);
|
||||
}
|
||||
}, [gifts, startViewTransition]);
|
||||
|
||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
|
||||
loadMoreMembers,
|
||||
searchMessages: searchSharedMediaMessages,
|
||||
@ -816,38 +838,24 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
</div>
|
||||
) : resultType === 'gifts' ? (
|
||||
(gifts?.map((gift) => (
|
||||
<SavedGift
|
||||
peerId={chatId}
|
||||
key={`${gift.date}-${gift.fromId}-${gift.gift.id}`}
|
||||
gift={gift}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
/>
|
||||
)))
|
||||
(renderingGifts?.map((gift) => {
|
||||
return (
|
||||
<SavedGift
|
||||
peerId={chatId}
|
||||
key={getGiftId(gift)}
|
||||
style={shouldApplyVtn ? `view-transition-name: vt${getGiftId(gift)}` : undefined}
|
||||
gift={gift}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
/>
|
||||
);
|
||||
}))
|
||||
) : undefined}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const shouldUseTransitionForContent = resultType === 'gifts';
|
||||
const contentTransitionKey = giftsTransitionKey;
|
||||
|
||||
function renderContentWithTransition() {
|
||||
return (
|
||||
<Transition
|
||||
className={`${resultType}-list`}
|
||||
activeKey={contentTransitionKey}
|
||||
name="fade"
|
||||
>
|
||||
{renderContent()}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
const activeListSelector = `.shared-media-transition > .Transition_slide-active.${resultType}-list`;
|
||||
const itemSelector = !shouldUseTransitionForContent
|
||||
? `${activeListSelector} > .scroll-item`
|
||||
: `${activeListSelector} > .Transition_slide-active > .content > .scroll-item`;
|
||||
const itemSelector = `${activeListSelector} > .scroll-item`;
|
||||
|
||||
return (
|
||||
<InfiniteScroll
|
||||
@ -881,7 +889,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
||||
onStart={applyTransitionFix}
|
||||
onStop={handleTransitionStop}
|
||||
>
|
||||
{shouldUseTransitionForContent ? renderContentWithTransition() : renderContent()}
|
||||
{renderContent()}
|
||||
</Transition>
|
||||
<TabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
||||
</div>
|
||||
@ -974,7 +982,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
|
||||
const peerGifts = selectTabState(global).savedGifts.giftsByPeerId[chatId];
|
||||
const giftsTransitionKey = selectTabState(global).savedGifts.transitionKey || 0;
|
||||
|
||||
return {
|
||||
theme: selectTheme(global),
|
||||
@ -1000,7 +1007,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
storyIds,
|
||||
hasGiftsTab,
|
||||
gifts: peerGifts?.gifts,
|
||||
giftsTransitionKey,
|
||||
pinnedStoryIds,
|
||||
archiveStoryIds,
|
||||
storyByIds,
|
||||
|
||||
@ -84,7 +84,7 @@ const StealthModeModal = ({ isOpen, stealthMode, isCurrentUserPremium } : StateP
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<div className={styles.stealthIcon}>
|
||||
<Icon name="eye-closed-outline" />
|
||||
<Icon name="eye-crossed-outline" />
|
||||
</div>
|
||||
<div className={styles.title}>{lang('StealthMode')}</div>
|
||||
<div className={styles.description}>
|
||||
|
||||
@ -730,7 +730,7 @@ function Story({
|
||||
</MenuItem>
|
||||
)}
|
||||
{!isOut && isUserStory && (
|
||||
<MenuItem icon="eye-closed-outline" onClick={handleOpenStealthModal}>
|
||||
<MenuItem icon="eye-crossed-outline" onClick={handleOpenStealthModal}>
|
||||
{lang('StealthMode')}
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
|
||||
&.open {
|
||||
transform: scale(1);
|
||||
view-transition-name: open-menu-bubble;
|
||||
}
|
||||
|
||||
&.closing {
|
||||
@ -115,3 +116,8 @@
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
// Hacky way to fix z-index issues with overlays in View Transitions
|
||||
html::view-transition-group(open-menu-bubble) {
|
||||
z-index: var(--z-portal-menu);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiSavedStarGift } from '../../../api/types';
|
||||
import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
|
||||
import type { StarGiftCategory } from '../../../types';
|
||||
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
@ -138,7 +138,7 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
||||
|
||||
addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
peerId, shouldRefresh, withTransition, tabId = getCurrentTabId(),
|
||||
peerId, shouldRefresh, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const peer = selectPeer(global, peerId);
|
||||
@ -167,17 +167,6 @@ addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise
|
||||
|
||||
const newGifts = currentGifts && !shouldRefresh ? currentGifts.gifts.concat(result.gifts) : result.gifts;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
if (withTransition) {
|
||||
global = updateTabState(global, {
|
||||
savedGifts: {
|
||||
...tabState.savedGifts,
|
||||
transitionKey: (tabState?.savedGifts.transitionKey || 0) + 1,
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
global = replacePeerSavedGifts(global, peerId, newGifts, result.nextOffset, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
@ -295,7 +284,7 @@ addActionHandler('convertGiftToStars', async (global, actions, payload): Promise
|
||||
|
||||
const peerId = gift.type === 'user' ? global.currentUserId! : gift.chatId;
|
||||
Object.values(global.byTabId).forEach((tabState) => {
|
||||
if (selectPeerSavedGifts(global, peerId, tabId)) {
|
||||
if (selectPeerSavedGifts(global, peerId, tabState.id)) {
|
||||
actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id });
|
||||
}
|
||||
});
|
||||
@ -325,3 +314,36 @@ addActionHandler('openGiftUpgradeModal', async (global, actions, payload): Promi
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('toggleSavedGiftPinned', async (global, actions, payload): Promise<void> => {
|
||||
const { gift, peerId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const peer = selectPeer(global, peerId);
|
||||
if (!peer) return;
|
||||
|
||||
const savedGifts = selectPeerSavedGifts(global, peerId, tabId);
|
||||
if (!savedGifts) return;
|
||||
const pinLimit = global.appConfig?.savedGiftPinLimit;
|
||||
const currentPinnedGifts = savedGifts.gifts.filter((g) => g.isPinned);
|
||||
const newPinnedGifts = gift.isPinned
|
||||
? currentPinnedGifts.filter((g) => (g.gift as ApiStarGiftUnique).slug !== (gift.gift as ApiStarGiftUnique).slug)
|
||||
: [...currentPinnedGifts, gift];
|
||||
|
||||
const trimmedPinnedGifts = pinLimit ? newPinnedGifts.slice(-pinLimit) : newPinnedGifts;
|
||||
|
||||
const inputSavedGifts = trimmedPinnedGifts.map((g) => getRequestInputSavedStarGift(global, g.inputGift!))
|
||||
.filter(Boolean);
|
||||
|
||||
const result = await callApi('toggleSavedGiftPinned', {
|
||||
inputSavedGifts,
|
||||
peer,
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
|
||||
Object.values(global.byTabId).forEach((tabState) => {
|
||||
if (selectPeerSavedGifts(global, peerId, tabState.id)) {
|
||||
actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -110,7 +110,7 @@ addActionHandler('updateGiftProfileFilter', (global, actions, payload): ActionRe
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadPeerSavedGifts({
|
||||
peerId, shouldRefresh: true, withTransition: true, tabId: tabState.id,
|
||||
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -132,6 +132,6 @@ addActionHandler('resetGiftProfileFilter', (global, actions, payload): ActionRet
|
||||
setGlobal(global);
|
||||
|
||||
actions.loadPeerSavedGifts({
|
||||
peerId, shouldRefresh: true, withTransition: true, tabId: tabState.id,
|
||||
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -2412,7 +2412,6 @@ export interface ActionPayloads {
|
||||
loadPeerSavedGifts: {
|
||||
peerId: string;
|
||||
shouldRefresh?: boolean;
|
||||
withTransition?: boolean;
|
||||
} & WithTabId;
|
||||
changeGiftVisibility: {
|
||||
gift: ApiInputSavedStarGift;
|
||||
@ -2421,6 +2420,10 @@ export interface ActionPayloads {
|
||||
convertGiftToStars: {
|
||||
gift: ApiInputSavedStarGift;
|
||||
} & WithTabId;
|
||||
toggleSavedGiftPinned: {
|
||||
peerId: string;
|
||||
gift: ApiSavedStarGift;
|
||||
} & WithTabId;
|
||||
|
||||
openStarsGiftModal: ({
|
||||
chatId?: string;
|
||||
|
||||
@ -210,7 +210,6 @@ export type TabState = {
|
||||
savedGifts: {
|
||||
giftsByPeerId: Record<string, ApiSavedGifts>;
|
||||
filter: GiftProfileFilterOptions;
|
||||
transitionKey?: number;
|
||||
};
|
||||
|
||||
globalSearch: {
|
||||
|
||||
75
src/hooks/animations/useViewTransition.ts
Normal file
75
src/hooks/animations/useViewTransition.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||
import Deferred from '../../util/Deferred';
|
||||
import { IS_VIEW_TRANSITION_SUPPORTED } from '../../util/windowEnvironment';
|
||||
|
||||
type TransitionFunction = () => Promise<void> | void;
|
||||
|
||||
type TransitionState = 'idle' | 'capturing-old' | 'capturing-new' | 'animating' | 'skipped';
|
||||
interface ViewTransitionController {
|
||||
transitionState: TransitionState;
|
||||
shouldApplyVtn?: boolean;
|
||||
startViewTransition: (domUpdateCallback?: TransitionFunction) => PromiseLike<void> | void;
|
||||
}
|
||||
|
||||
let hasActiveTransition = false;
|
||||
export function hasActiveViewTransition(): boolean {
|
||||
return hasActiveTransition;
|
||||
}
|
||||
|
||||
export function useViewTransition(): ViewTransitionController {
|
||||
const domUpdaterFn = useRef<TransitionFunction>();
|
||||
const [transitionState, setTransitionState] = useState<TransitionState>('idle');
|
||||
|
||||
useEffect(() => {
|
||||
if (transitionState !== 'capturing-old') return;
|
||||
|
||||
const transition = document.startViewTransition(async () => {
|
||||
setTransitionState('capturing-new');
|
||||
if (domUpdaterFn.current) await domUpdaterFn.current();
|
||||
const deferred = new Deferred<void>();
|
||||
requestNextMutation(() => {
|
||||
deferred.resolve();
|
||||
});
|
||||
return deferred.promise;
|
||||
});
|
||||
|
||||
transition.finished.then(() => {
|
||||
setTransitionState('idle');
|
||||
hasActiveTransition = false;
|
||||
});
|
||||
|
||||
transition.ready.then(() => {
|
||||
setTransitionState('animating');
|
||||
}).catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
setTransitionState('skipped');
|
||||
hasActiveTransition = false;
|
||||
});
|
||||
}, [transitionState]);
|
||||
|
||||
function startViewTransition(updateCallback?: TransitionFunction): PromiseLike<void> | void {
|
||||
// Fallback: simply run the callback immediately if view transitions aren't supported.
|
||||
if (!IS_VIEW_TRANSITION_SUPPORTED) {
|
||||
if (updateCallback) updateCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
domUpdaterFn.current = updateCallback;
|
||||
setTransitionState('capturing-old');
|
||||
hasActiveTransition = true;
|
||||
}
|
||||
|
||||
return {
|
||||
shouldApplyVtn: transitionState === 'capturing-old'
|
||||
|| transitionState === 'capturing-new' || transitionState === 'animating',
|
||||
transitionState,
|
||||
startViewTransition,
|
||||
};
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
|
||||
import { hasActiveViewTransition } from './animations/useViewTransition';
|
||||
|
||||
const BACKDROP_CLASSNAME = 'backdrop';
|
||||
|
||||
// This effect implements closing menus by clicking outside of them
|
||||
@ -20,7 +22,7 @@ export default function useVirtualBackdrop(
|
||||
const handleEvent = (e: MouseEvent) => {
|
||||
const container = containerRef.current;
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (!container || !target || (ignoreRightClick && e.button === 2)) {
|
||||
if (!container || !target || (ignoreRightClick && e.button === 2) || hasActiveViewTransition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1729,6 +1729,7 @@ payments.transferStarGift#7f18176a stargift:InputSavedStarGift to_id:InputPeer =
|
||||
payments.getUniqueStarGift#a1974d72 slug:string = payments.UniqueStarGift;
|
||||
payments.getSavedStarGifts#23830de9 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer offset:string limit:int = payments.SavedStarGifts;
|
||||
payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl;
|
||||
payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<InputSavedStarGift> = Bool;
|
||||
phone.requestCall#a6c4600c flags:# video:flags.0?true user_id:InputUser conference_call:flags.1?InputGroupCall 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;
|
||||
|
||||
@ -314,6 +314,7 @@
|
||||
"payments.transferStarGift",
|
||||
"payments.getUniqueStarGift",
|
||||
"payments.getStarGiftWithdrawalUrl",
|
||||
"payments.toggleStarGiftsPinnedToTop",
|
||||
"langpack.getLangPack",
|
||||
"langpack.getStrings",
|
||||
"langpack.getLanguages",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -113,8 +113,8 @@ $icons-map: (
|
||||
"enter": "\f14c",
|
||||
"expand-modal": "\f14d",
|
||||
"expand": "\f14e",
|
||||
"eye-closed-outline": "\f14f",
|
||||
"eye-closed": "\f150",
|
||||
"eye-crossed-outline": "\f14f",
|
||||
"eye-crossed": "\f150",
|
||||
"eye-outline": "\f151",
|
||||
"eye": "\f152",
|
||||
"favorite-filled": "\f153",
|
||||
@ -525,11 +525,11 @@ $icons-map: (
|
||||
.icon-expand::before {
|
||||
content: map.get($icons-map, "expand");
|
||||
}
|
||||
.icon-eye-closed-outline::before {
|
||||
content: map.get($icons-map, "eye-closed-outline");
|
||||
.icon-eye-crossed-outline::before {
|
||||
content: map.get($icons-map, "eye-crossed-outline");
|
||||
}
|
||||
.icon-eye-closed::before {
|
||||
content: map.get($icons-map, "eye-closed");
|
||||
.icon-eye-crossed::before {
|
||||
content: map.get($icons-map, "eye-crossed");
|
||||
}
|
||||
.icon-eye-outline::before {
|
||||
content: map.get($icons-map, "eye-outline");
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -77,8 +77,8 @@ export type FontIconName =
|
||||
| 'enter'
|
||||
| 'expand-modal'
|
||||
| 'expand'
|
||||
| 'eye-closed-outline'
|
||||
| 'eye-closed'
|
||||
| 'eye-crossed-outline'
|
||||
| 'eye-crossed'
|
||||
| 'eye-outline'
|
||||
| 'eye'
|
||||
| 'favorite-filled'
|
||||
|
||||
2
src/types/language.d.ts
vendored
2
src/types/language.d.ts
vendored
@ -1183,6 +1183,8 @@ export interface LangPair {
|
||||
'GiftInfoConvertDescription2': undefined;
|
||||
'GiftInfoSavedHide': undefined;
|
||||
'GiftInfoSavedShow': undefined;
|
||||
'GiftActionShow': undefined;
|
||||
'GiftActionHide': undefined;
|
||||
'GiftInfoTonLinkText': undefined;
|
||||
'GiftInfoAvailability': undefined;
|
||||
'GiftInfoFirstSale': undefined;
|
||||
|
||||
@ -114,6 +114,7 @@ export const IS_MULTITAB_SUPPORTED = 'BroadcastChannel' in window;
|
||||
export const IS_OPEN_IN_NEW_TAB_SUPPORTED = IS_MULTITAB_SUPPORTED && !(IS_PWA && IS_MOBILE);
|
||||
export const IS_TRANSLATION_SUPPORTED = !IS_TEST;
|
||||
export const IS_INTL_LIST_FORMAT_SUPPORTED = 'ListFormat' in Intl;
|
||||
export const IS_VIEW_TRANSITION_SUPPORTED = 'ViewTransition' in window;
|
||||
|
||||
export const IS_BAD_URL_PARSER = new URL('tg://host').host !== 'host';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user