Introduce Boosts (#3909)
This commit is contained in:
parent
8fc3df855d
commit
b254b08b2c
@ -161,7 +161,7 @@ function buildStatisticsOverview({ current, previous }: GramJs.StatsAbsValueAndP
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStatisticsPercentage(data: GramJs.StatsPercentValue): StatisticsOverviewPercentage {
|
export function buildStatisticsPercentage(data: GramJs.StatsPercentValue): StatisticsOverviewPercentage {
|
||||||
return {
|
return {
|
||||||
percentage: ((data.part / data.total) * 100).toFixed(2),
|
percentage: ((data.part / data.total) * 100).toFixed(2),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs, errors } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiApplyBoostInfo,
|
||||||
|
ApiBoostsStatus,
|
||||||
ApiMediaArea, ApiMediaAreaCoordinates, ApiMessage, ApiStealthMode, ApiStoryView, ApiTypeStory,
|
ApiMediaArea, ApiMediaAreaCoordinates, ApiMessage, ApiStealthMode, ApiStoryView, ApiTypeStory,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildCollectionByCallback } from '../../../util/iteratees';
|
import { buildCollectionByCallback } from '../../../util/iteratees';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { buildPrivacyRules } from './common';
|
import { buildPrivacyRules } from './common';
|
||||||
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { buildApiReaction, buildReactionCount } from './reactions';
|
import { buildApiReaction, buildReactionCount } from './reactions';
|
||||||
|
import { buildStatisticsPercentage } from './statistics';
|
||||||
|
|
||||||
export function buildApiStory(peerId: string, story: GramJs.TypeStoryItem): ApiTypeStory {
|
export function buildApiStory(peerId: string, story: GramJs.TypeStoryItem): ApiTypeStory {
|
||||||
if (story instanceof GramJs.StoryItemDeleted) {
|
if (story instanceof GramJs.StoryItemDeleted) {
|
||||||
@ -161,3 +165,56 @@ export function buildApiPeerStories(peerStories: GramJs.PeerStories) {
|
|||||||
|
|
||||||
return buildCollectionByCallback(peerStories.stories, (story) => [story.id, buildApiStory(peerId, story)]);
|
return buildCollectionByCallback(peerStories.stories, (story) => [story.id, buildApiStory(peerId, story)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiApplyBoostInfo(
|
||||||
|
applyBoostInfo: GramJs.stories.TypeCanApplyBoostResult,
|
||||||
|
): ApiApplyBoostInfo | undefined {
|
||||||
|
if (applyBoostInfo instanceof GramJs.stories.CanApplyBoostOk) {
|
||||||
|
return { type: 'ok' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyBoostInfo instanceof GramJs.stories.CanApplyBoostReplace) {
|
||||||
|
return {
|
||||||
|
type: 'replace',
|
||||||
|
boostedChatId: getApiChatIdFromMtpPeer(applyBoostInfo.currentBoost),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiApplyBoostInfoFromError(
|
||||||
|
error: unknown,
|
||||||
|
): ApiApplyBoostInfo | undefined {
|
||||||
|
if (error instanceof errors.FloodWaitError) {
|
||||||
|
return {
|
||||||
|
type: 'wait',
|
||||||
|
waitUntil: getServerTime() + error.seconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message === 'BOOST_NOT_MODIFIED') {
|
||||||
|
return {
|
||||||
|
type: 'already',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiBoostsStatus(boostStatus: GramJs.stories.BoostsStatus): ApiBoostsStatus {
|
||||||
|
const {
|
||||||
|
level, boostUrl, boosts, myBoost, currentLevelBoosts, nextLevelBoosts, premiumAudience,
|
||||||
|
} = boostStatus;
|
||||||
|
return {
|
||||||
|
level,
|
||||||
|
currentLevelBoosts,
|
||||||
|
boosts,
|
||||||
|
hasMyBoost: Boolean(myBoost),
|
||||||
|
boostUrl,
|
||||||
|
nextLevelBoosts,
|
||||||
|
...(premiumAudience && { premiumAudience: buildStatisticsPercentage(premiumAudience) }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -17,6 +17,9 @@ import { buildCollectionByCallback } from '../../../util/iteratees';
|
|||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
import {
|
import {
|
||||||
|
buildApiApplyBoostInfo,
|
||||||
|
buildApiApplyBoostInfoFromError,
|
||||||
|
buildApiBoostsStatus,
|
||||||
buildApiPeerStories,
|
buildApiPeerStories,
|
||||||
buildApiStealthMode,
|
buildApiStealthMode,
|
||||||
buildApiStory,
|
buildApiStory,
|
||||||
@ -426,3 +429,68 @@ export function activateStealthMode({
|
|||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchCanApplyBoost({
|
||||||
|
chat,
|
||||||
|
} : {
|
||||||
|
chat: ApiChat;
|
||||||
|
}) {
|
||||||
|
let result: GramJs.stories.TypeCanApplyBoostResult | undefined;
|
||||||
|
try {
|
||||||
|
result = await invokeRequest(new GramJs.stories.CanApplyBoost({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
}), {
|
||||||
|
shouldThrow: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const info = buildApiApplyBoostInfoFromError(error);
|
||||||
|
if (!info) return undefined;
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
chats: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mtpChats = 'chats' in result ? result.chats : [];
|
||||||
|
addEntitiesToLocalDb(mtpChats);
|
||||||
|
|
||||||
|
const chats = mtpChats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
|
const info = buildApiApplyBoostInfo(result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
chats,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyBoost({
|
||||||
|
chat,
|
||||||
|
} : {
|
||||||
|
chat: ApiChat;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.stories.ApplyBoost({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchBoostsStatus({
|
||||||
|
chat,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.stories.GetBoostsStatus({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiBoostsStatus(result);
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { ApiPrivacySettings } from '../../types';
|
|||||||
import type {
|
import type {
|
||||||
ApiGeoPoint, ApiMessage, ApiReaction, ApiReactionCount,
|
ApiGeoPoint, ApiMessage, ApiReaction, ApiReactionCount,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
import type { StatisticsOverviewPercentage } from './statistics';
|
||||||
|
|
||||||
export interface ApiStory {
|
export interface ApiStory {
|
||||||
'@type'?: 'story';
|
'@type'?: 'story';
|
||||||
@ -108,3 +109,33 @@ export type ApiMediaAreaSuggestedReaction = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction;
|
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction;
|
||||||
|
|
||||||
|
export type ApiApplyBoostOk = {
|
||||||
|
type: 'ok';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiApplyBoostReplace = {
|
||||||
|
type: 'replace';
|
||||||
|
boostedChatId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiApplyBoostWait = {
|
||||||
|
type: 'wait';
|
||||||
|
waitUntil: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiApplyBoostAlready = {
|
||||||
|
type: 'already';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiApplyBoostInfo = ApiApplyBoostOk | ApiApplyBoostReplace | ApiApplyBoostWait | ApiApplyBoostAlready;
|
||||||
|
|
||||||
|
export type ApiBoostsStatus = {
|
||||||
|
level: number;
|
||||||
|
currentLevelBoosts: number;
|
||||||
|
boosts: number;
|
||||||
|
nextLevelBoosts?: number;
|
||||||
|
hasMyBoost?: boolean;
|
||||||
|
boostUrl: string;
|
||||||
|
premiumAudience?: StatisticsOverviewPercentage;
|
||||||
|
};
|
||||||
|
|||||||
1
src/assets/font-icons/boost.svg
Normal file
1
src/assets/font-icons/boost.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M19.322 13.194c-.69 0-1.216-.616-1.109-1.298l1.5-9.498c.187-1.182-1.36-1.797-2.035-.809L7.1 17.05c-.51.746.023 1.757.926 1.757h4.652c.69 0 1.216.616 1.109 1.298l-1.5 9.498c-.187 1.182 1.36 1.797 2.035.809L24.9 14.95a1.122 1.122 0 0 0-.926-1.757Z" style="stroke-width:1.68365"/></svg>
|
||||||
|
After Width: | Height: | Size: 355 B |
1
src/assets/font-icons/boostcircle.svg
Normal file
1
src/assets/font-icons/boostcircle.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M16 31c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C7.716 1 1 7.716 1 16c0 8.284 6.716 15 15 15Zm1.476-17.743a.75.75 0 0 0 .74.867h3.109a.75.75 0 0 1 .619 1.173l-7.069 10.33c-.451.66-1.484.25-1.36-.54l1.003-6.347a.75.75 0 0 0-.741-.867h-3.108a.75.75 0 0 1-.62-1.173l7.069-10.33c.452-.66 1.484-.25 1.36.54z"/></svg>
|
||||||
|
After Width: | Height: | Size: 387 B |
@ -19,6 +19,7 @@ export { default as PremiumMainModal } from '../components/main/premium/PremiumM
|
|||||||
export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal';
|
export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal';
|
||||||
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
||||||
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
||||||
|
export { default as BoostModal } from '../components/modals/boost/BoostModal';
|
||||||
|
|
||||||
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
||||||
|
|
||||||
|
|||||||
@ -132,7 +132,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
<div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{lockedSelectedIds.map((id, i) => (
|
{lockedSelectedIds.map((id, i) => (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
chatOrUserId={id}
|
peerId={id}
|
||||||
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
|
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
|
||||||
forceShowSelf={forceShowSelf}
|
forceShowSelf={forceShowSelf}
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
@ -141,7 +141,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
))}
|
))}
|
||||||
{unlockedSelectedIds.map((id, i) => (
|
{unlockedSelectedIds.map((id, i) => (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
chatOrUserId={id}
|
peerId={id}
|
||||||
isMinimized={
|
isMinimized={
|
||||||
shouldMinimize && i + lockedSelectedIds.length < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT
|
shouldMinimize && i + lockedSelectedIds.length < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
max-width: calc(50% - 0.5rem);
|
max-width: calc(50% - 0.5rem);
|
||||||
|
|
||||||
|
&.standalone {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&.minimized {
|
&.minimized {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,13 +17,14 @@ import Avatar from './Avatar';
|
|||||||
import './PickerSelectedItem.scss';
|
import './PickerSelectedItem.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatOrUserId?: string;
|
peerId?: string;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
title?: string;
|
title?: string;
|
||||||
isMinimized?: boolean;
|
isMinimized?: boolean;
|
||||||
|
isStandalone?: boolean;
|
||||||
canClose?: boolean;
|
canClose?: boolean;
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
clickArg: any;
|
clickArg?: any;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick: (arg: any) => void;
|
onClick: (arg: any) => void;
|
||||||
};
|
};
|
||||||
@ -38,6 +39,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
isMinimized,
|
isMinimized,
|
||||||
|
isStandalone,
|
||||||
canClose,
|
canClose,
|
||||||
clickArg,
|
clickArg,
|
||||||
chat,
|
chat,
|
||||||
@ -81,6 +83,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
chat?.isForum && 'forum-avatar',
|
chat?.isForum && 'forum-avatar',
|
||||||
isMinimized && 'minimized',
|
isMinimized && 'minimized',
|
||||||
canClose && 'closeable',
|
canClose && 'closeable',
|
||||||
|
isStandalone && 'standalone',
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -106,13 +109,13 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatOrUserId, forceShowSelf }): StateProps => {
|
(global, { peerId, forceShowSelf }): StateProps => {
|
||||||
if (!chatOrUserId) {
|
if (!peerId) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, chatOrUserId);
|
const chat = selectChat(global, peerId);
|
||||||
const user = selectUser(global, chatOrUserId);
|
const user = selectUser(global, peerId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
123
src/components/common/PremiumProgress.module.scss
Normal file
123
src/components/common/PremiumProgress.module.scss
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
.root {
|
||||||
|
--percent: calc(var(--progress, 0.5) * 100%);
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
height: 2rem;
|
||||||
|
background: #F1F3F5;
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.withBadge {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badgeContainer {
|
||||||
|
--shift-x: calc(clamp(10%, var(--percent), 90%) - 50%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: -1.5rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translate(var(--shift-x), -20px);
|
||||||
|
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
animation: slide-in 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in {
|
||||||
|
from {
|
||||||
|
transform: translate(-50%, -20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(var(--shift-x), -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: #7E85FF;
|
||||||
|
animation: rotate-in 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-in {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
// Rotate more if progress is higher
|
||||||
|
transform: rotate(calc(-20deg * var(--progress)));
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-badge-triangle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-badge-icon {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-badge-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left, .right {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
--multiplier: calc(1 / var(--progress) - 1);
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: var(--percent);
|
||||||
|
border-top-left-radius: 0.625rem;
|
||||||
|
border-bottom-left-radius: 0.625rem;
|
||||||
|
background-image: var(--premium-gradient);
|
||||||
|
background-size: calc(1 / var(--progress) * 100%) 100%;
|
||||||
|
|
||||||
|
.left, .right {
|
||||||
|
color: white;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
right: calc(-100% * var(--multiplier) + 0.75rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullProgress {
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
}
|
||||||
79
src/components/common/PremiumProgress.tsx
Normal file
79
src/components/common/PremiumProgress.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { IconName } from '../../types/icons';
|
||||||
|
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import buildStyle from '../../util/buildStyle';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
|
import styles from './PremiumProgress.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
leftText?: string;
|
||||||
|
rightText?: string;
|
||||||
|
floatingBadgeIcon?: IconName;
|
||||||
|
floatingBadgeText?: string;
|
||||||
|
progress?: number;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LimitPreview: FC<OwnProps> = ({
|
||||||
|
leftText,
|
||||||
|
rightText,
|
||||||
|
floatingBadgeText,
|
||||||
|
floatingBadgeIcon,
|
||||||
|
progress,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const hasFloatingBadge = Boolean(floatingBadgeIcon || floatingBadgeText);
|
||||||
|
const isProgressFull = Boolean(progress) && progress > 0.99;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(
|
||||||
|
styles.root,
|
||||||
|
hasFloatingBadge && styles.withBadge,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={buildStyle(progress !== undefined && `--progress: ${progress}`)}
|
||||||
|
>
|
||||||
|
{hasFloatingBadge && (
|
||||||
|
<div className={styles.badgeContainer}>
|
||||||
|
<div className={styles.floatingBadge}>
|
||||||
|
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
|
||||||
|
{floatingBadgeText && (
|
||||||
|
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>{floatingBadgeText}</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.floatingBadgeTriangle}>
|
||||||
|
<svg width="26" height="9" viewBox="0 0 26 9" fill="none">
|
||||||
|
<path d="M0 0H26H24.4853C22.894 0 21.3679 0.632141 20.2426 1.75736L14.4142 7.58579C13.6332 8.36684 12.3668 8.36683 11.5858 7.58579L5.75736 1.75736C4.63214 0.632139 3.10602 0 1.51472 0H0Z" fill="#7E85FF" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.left}>
|
||||||
|
<span>{leftText}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.right}>
|
||||||
|
<span>{rightText}</span>
|
||||||
|
</div>
|
||||||
|
<div className={buildClassName(styles.progress, isProgressFull && styles.fullProgress)}>
|
||||||
|
<div className={styles.left}>
|
||||||
|
<span>{leftText}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.right}>
|
||||||
|
<span>{rightText}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(LimitPreview);
|
||||||
24
src/components/common/helpers/boostInfo.ts
Normal file
24
src/components/common/helpers/boostInfo.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { ApiBoostsStatus } from '../../../api/types';
|
||||||
|
|
||||||
|
export function getBoostProgressInfo(boostInfo: ApiBoostsStatus) {
|
||||||
|
const {
|
||||||
|
level, boosts, currentLevelBoosts, nextLevelBoosts, hasMyBoost,
|
||||||
|
} = boostInfo;
|
||||||
|
|
||||||
|
const currentLevel = level;
|
||||||
|
const hasNextLevel = Boolean(nextLevelBoosts);
|
||||||
|
|
||||||
|
const isJustUpgraded = boosts === currentLevelBoosts && hasMyBoost;
|
||||||
|
|
||||||
|
const levelProgress = (!nextLevelBoosts || isJustUpgraded) ? 1
|
||||||
|
: (boosts - currentLevelBoosts) / (nextLevelBoosts - currentLevelBoosts);
|
||||||
|
const remainingBoosts = nextLevelBoosts ? nextLevelBoosts - boosts : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentLevel,
|
||||||
|
hasNextLevel,
|
||||||
|
boosts,
|
||||||
|
levelProgress,
|
||||||
|
remainingBoosts,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -229,7 +229,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{globalSearchChatId && (
|
{globalSearchChatId && (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
chatOrUserId={globalSearchChatId}
|
peerId={globalSearchChatId}
|
||||||
onClick={setGlobalSearchChatId}
|
onClick={setGlobalSearchChatId}
|
||||||
canClose
|
canClose
|
||||||
clickArg={CLEAR_CHAT_SEARCH_PARAM}
|
clickArg={CLEAR_CHAT_SEARCH_PARAM}
|
||||||
|
|||||||
@ -232,7 +232,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
{localResults.map((id) => (
|
{localResults.map((id) => (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
chatOrUserId={id}
|
peerId={id}
|
||||||
onClick={handlePickerItemClick}
|
onClick={handlePickerItemClick}
|
||||||
clickArg={id}
|
clickArg={id}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -196,7 +196,7 @@ const SettingsFoldersChatsPicker: FC<OwnProps & StateProps> = ({
|
|||||||
{selectedChatTypes.map(renderSelectedChatType)}
|
{selectedChatTypes.map(renderSelectedChatType)}
|
||||||
{selectedIds.map((id, i) => (
|
{selectedIds.map((id, i) => (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
chatOrUserId={id}
|
peerId={id}
|
||||||
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
|
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
|
||||||
canClose
|
canClose
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
|
|||||||
@ -76,6 +76,7 @@ import ReactionPicker from '../middle/message/ReactionPicker.async';
|
|||||||
import MessageListHistoryHandler from '../middle/MessageListHistoryHandler';
|
import MessageListHistoryHandler from '../middle/MessageListHistoryHandler';
|
||||||
import MiddleColumn from '../middle/MiddleColumn';
|
import MiddleColumn from '../middle/MiddleColumn';
|
||||||
import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallModal.async';
|
import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallModal.async';
|
||||||
|
import BoostModal from '../modals/boost/BoostModal.async';
|
||||||
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
||||||
import MapModal from '../modals/map/MapModal.async';
|
import MapModal from '../modals/map/MapModal.async';
|
||||||
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
||||||
@ -153,6 +154,7 @@ type StateProps = {
|
|||||||
isReactionPickerOpen: boolean;
|
isReactionPickerOpen: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
chatlistModal?: TabState['chatlistModal'];
|
chatlistModal?: TabState['chatlistModal'];
|
||||||
|
boostModal?: TabState['boostModal'];
|
||||||
noRightColumnAnimation?: boolean;
|
noRightColumnAnimation?: boolean;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
isSynced?: boolean;
|
isSynced?: boolean;
|
||||||
@ -212,6 +214,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
deleteFolderDialog,
|
deleteFolderDialog,
|
||||||
isMasterTab,
|
isMasterTab,
|
||||||
chatlistModal,
|
chatlistModal,
|
||||||
|
boostModal,
|
||||||
noRightColumnAnimation,
|
noRightColumnAnimation,
|
||||||
isSynced,
|
isSynced,
|
||||||
}) => {
|
}) => {
|
||||||
@ -554,6 +557,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
userId={newContactUserId}
|
userId={newContactUserId}
|
||||||
isByPhoneNumber={newContactByPhoneNumber}
|
isByPhoneNumber={newContactByPhoneNumber}
|
||||||
/>
|
/>
|
||||||
|
<BoostModal info={boostModal} />
|
||||||
<ChatlistModal info={chatlistModal} />
|
<ChatlistModal info={chatlistModal} />
|
||||||
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
||||||
<WebAppModal webApp={webApp} />
|
<WebAppModal webApp={webApp} />
|
||||||
@ -616,6 +620,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
limitReachedModal,
|
limitReachedModal,
|
||||||
deleteFolderDialogModal,
|
deleteFolderDialogModal,
|
||||||
chatlistModal,
|
chatlistModal,
|
||||||
|
boostModal,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
|
|
||||||
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
||||||
@ -678,6 +683,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isMasterTab,
|
isMasterTab,
|
||||||
requestedDraft,
|
requestedDraft,
|
||||||
chatlistModal,
|
chatlistModal,
|
||||||
|
boostModal,
|
||||||
noRightColumnAnimation,
|
noRightColumnAnimation,
|
||||||
isSynced: global.isSynced,
|
isSynced: global.isSynced,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -25,7 +25,9 @@ export function getWebpageButtonText(type?: string) {
|
|||||||
case 'telegram_chatlist':
|
case 'telegram_chatlist':
|
||||||
return 'ViewChatList';
|
return 'ViewChatList';
|
||||||
case 'telegram_story':
|
case 'telegram_story':
|
||||||
return 'ViewStory';
|
return 'lng_view_button_story';
|
||||||
|
case 'telegram_channel_boost':
|
||||||
|
return 'lng_view_button_boost';
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/components/modals/boost/BoostModal.async.tsx
Normal file
18
src/components/modals/boost/BoostModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './BoostModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const BoostModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { info } = props;
|
||||||
|
const BoostModal = useModuleLoader(Bundles.Extra, 'BoostModal', !info);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return BoostModal ? <BoostModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoostModalAsync;
|
||||||
69
src/components/modals/boost/BoostModal.module.scss
Normal file
69
src/components/modals/boost/BoostModal.module.scss
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
min-height: 14rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
margin-block: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
text-wrap: balance
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
padding: 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replaceModal :global(.modal-dialog) {
|
||||||
|
max-width: 22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replaceModalContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarContainer {
|
||||||
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boostedWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boostedMark {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -0.125rem;
|
||||||
|
right: -0.125rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
padding: 0.125rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-image: var(--premium-gradient);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
311
src/components/modals/boost/BoostModal.tsx
Normal file
311
src/components/modals/boost/BoostModal.tsx
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiApplyBoostInfo, ApiChat } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { getChatTitle } from '../../../global/helpers';
|
||||||
|
import { selectChat } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatDateInFuture } from '../../../util/dateFormat';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
|
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||||
|
import PremiumProgress from '../../common/PremiumProgress';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
|
||||||
|
import styles from './BoostModal.module.scss';
|
||||||
|
|
||||||
|
type LoadedParams = {
|
||||||
|
applyInfo?: ApiApplyBoostInfo;
|
||||||
|
leftText: string;
|
||||||
|
rightText?: string;
|
||||||
|
value: string;
|
||||||
|
progress: number;
|
||||||
|
descriptionText: string;
|
||||||
|
isBoosted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BoostInfo = ({
|
||||||
|
isStatusLoaded: false;
|
||||||
|
title: string;
|
||||||
|
} & Undefined<LoadedParams>) | ({
|
||||||
|
isStatusLoaded: true;
|
||||||
|
title: string;
|
||||||
|
} & LoadedParams);
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
info: TabState['boostModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
chat?: ApiChat;
|
||||||
|
boostedChat?: ApiChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BoostModal = ({
|
||||||
|
info,
|
||||||
|
chat,
|
||||||
|
boostedChat,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const {
|
||||||
|
openChat,
|
||||||
|
applyBoost,
|
||||||
|
closeBoostModal,
|
||||||
|
requestConfetti,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const [isReplaceModalOpen, openReplaceModal, closeReplaceModal] = useFlag();
|
||||||
|
const [isWaitDialogOpen, openWaitDialog, closeWaitDialog] = useFlag();
|
||||||
|
|
||||||
|
const isOpen = Boolean(info);
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const chatTitle = useMemo(() => {
|
||||||
|
if (!chat) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getChatTitle(lang, chat);
|
||||||
|
}, [chat, lang]);
|
||||||
|
|
||||||
|
const boostedChatTitle = useMemo(() => {
|
||||||
|
if (!boostedChat) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getChatTitle(lang, boostedChat);
|
||||||
|
}, [boostedChat, lang]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isStatusLoaded,
|
||||||
|
isBoosted,
|
||||||
|
applyInfo,
|
||||||
|
title,
|
||||||
|
leftText,
|
||||||
|
rightText,
|
||||||
|
value,
|
||||||
|
progress,
|
||||||
|
descriptionText,
|
||||||
|
}: BoostInfo = useMemo(() => {
|
||||||
|
if (!info?.boostStatus || !chat) {
|
||||||
|
return {
|
||||||
|
isStatusLoaded: false,
|
||||||
|
title: lang('Loading'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
level, currentLevelBoosts, hasMyBoost,
|
||||||
|
} = info.boostStatus;
|
||||||
|
|
||||||
|
const {
|
||||||
|
boosts,
|
||||||
|
currentLevel,
|
||||||
|
hasNextLevel,
|
||||||
|
levelProgress,
|
||||||
|
remainingBoosts,
|
||||||
|
} = getBoostProgressInfo(info.boostStatus);
|
||||||
|
|
||||||
|
const hasBoost = hasMyBoost || info.applyInfo?.type === 'already';
|
||||||
|
const isJustUpgraded = boosts === currentLevelBoosts && hasBoost;
|
||||||
|
|
||||||
|
const left = lang('BoostsLevel', currentLevel);
|
||||||
|
const right = hasNextLevel ? lang('BoostsLevel', currentLevel + 1) : undefined;
|
||||||
|
|
||||||
|
const moreBoosts = lang('ChannelBoost.MoreBoosts', remainingBoosts);
|
||||||
|
const currentStoriesPerDay = lang('ChannelBoost.StoriesPerDay', level);
|
||||||
|
const nextLevelStoriesPerDay = lang('ChannelBoost.StoriesPerDay', level + 1);
|
||||||
|
|
||||||
|
const modalTitle = hasBoost ? lang('ChannelBoost.YouBoostedOtherChannel')
|
||||||
|
: level === 0 ? lang('lng_boost_channel_title_first') : lang('lng_boost_channel_title_more');
|
||||||
|
|
||||||
|
let description: string | undefined;
|
||||||
|
if (level === 0) {
|
||||||
|
if (!hasBoost) {
|
||||||
|
description = lang('ChannelBoost.EnableStoriesForChannelText', [chatTitle, moreBoosts]);
|
||||||
|
} else {
|
||||||
|
description = lang('ChannelBoost.EnableStoriesMoreRequired', moreBoosts);
|
||||||
|
}
|
||||||
|
} else if (isJustUpgraded) {
|
||||||
|
if (level === 1) {
|
||||||
|
description = lang('ChannelBoost.EnabledStoriesForChannelText');
|
||||||
|
} else {
|
||||||
|
description = lang('ChannelBoost.BoostedChannelReachedLevel', [level, currentStoriesPerDay]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
description = lang('ChannelBoost.HelpUpgradeChannelText', [chatTitle, moreBoosts, nextLevelStoriesPerDay]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isStatusLoaded: true,
|
||||||
|
title: modalTitle,
|
||||||
|
leftText: left,
|
||||||
|
rightText: right,
|
||||||
|
value: boosts.toString(),
|
||||||
|
progress: levelProgress,
|
||||||
|
remainingBoosts,
|
||||||
|
descriptionText: description,
|
||||||
|
applyInfo: info.applyInfo,
|
||||||
|
isBoosted: hasBoost,
|
||||||
|
};
|
||||||
|
}, [chat, chatTitle, info, lang]);
|
||||||
|
|
||||||
|
const handleOpenChat = useLastCallback(() => {
|
||||||
|
openChat({ id: chat!.id });
|
||||||
|
closeBoostModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleApplyBoost = useLastCallback(() => {
|
||||||
|
closeReplaceModal();
|
||||||
|
applyBoost({ chatId: chat!.id });
|
||||||
|
requestConfetti();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleButtonClick = useLastCallback(() => {
|
||||||
|
if (applyInfo?.type === 'ok') {
|
||||||
|
handleApplyBoost();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyInfo?.type === 'replace') {
|
||||||
|
openReplaceModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyInfo?.type === 'wait') {
|
||||||
|
openWaitDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBoosted) {
|
||||||
|
closeBoostModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderContent() {
|
||||||
|
if (!isStatusLoaded) {
|
||||||
|
return <Loading className={styles.loading} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{chat && (
|
||||||
|
<PickerSelectedItem
|
||||||
|
className={styles.chip}
|
||||||
|
peerId={chat.id}
|
||||||
|
isStandalone
|
||||||
|
onClick={handleOpenChat}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<PremiumProgress
|
||||||
|
leftText={leftText}
|
||||||
|
rightText={rightText}
|
||||||
|
progress={progress}
|
||||||
|
floatingBadgeText={value}
|
||||||
|
floatingBadgeIcon="boost"
|
||||||
|
/>
|
||||||
|
<div className={buildClassName(styles.description, styles.textCenter)}>
|
||||||
|
{renderText(descriptionText, ['simple_markdown', 'emoji'])}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className={styles.button}
|
||||||
|
size="smaller"
|
||||||
|
withPremiumGradient
|
||||||
|
isShiny
|
||||||
|
isLoading={!applyInfo}
|
||||||
|
ripple
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
>
|
||||||
|
{!isBoosted ? (
|
||||||
|
<>
|
||||||
|
<Icon name="boost" />
|
||||||
|
{lang('ChannelBoost.BoostChannel')}
|
||||||
|
</>
|
||||||
|
) : lang('OK')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
title={title}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
onClose={closeBoostModal}
|
||||||
|
isSlim
|
||||||
|
hasCloseButton
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
{applyInfo?.type === 'replace' && boostedChatTitle && (
|
||||||
|
<Modal
|
||||||
|
isOpen={isReplaceModalOpen}
|
||||||
|
className={styles.replaceModal}
|
||||||
|
contentClassName={styles.replaceModalContent}
|
||||||
|
onClose={closeReplaceModal}
|
||||||
|
>
|
||||||
|
<div className={styles.avatarContainer}>
|
||||||
|
<div className={styles.boostedWrapper}>
|
||||||
|
<Avatar peer={boostedChat} size="large" />
|
||||||
|
<Icon name="boostcircle" className={styles.boostedMark} />
|
||||||
|
</div>
|
||||||
|
<Icon name="next" className={styles.arrow} />
|
||||||
|
<Avatar peer={chat} size="large" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.textCenter}>
|
||||||
|
{renderText(lang('ChannelBoost.ReplaceBoost', [boostedChatTitle, chatTitle]), ['simple_markdown', 'emoji'])}
|
||||||
|
</div>
|
||||||
|
<div className="dialog-buttons">
|
||||||
|
<Button isText className="confirm-dialog-button" onClick={handleApplyBoost}>
|
||||||
|
{lang('Replace')}
|
||||||
|
</Button>
|
||||||
|
<Button isText className="confirm-dialog-button" onClick={closeReplaceModal}>
|
||||||
|
{lang('Cancel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
{applyInfo?.type === 'wait' && (
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isWaitDialogOpen}
|
||||||
|
isOnlyConfirm
|
||||||
|
confirmLabel={lang('OK')}
|
||||||
|
title={lang('ChannelBoost.Error.BoostTooOftenTitle')}
|
||||||
|
onClose={closeWaitDialog}
|
||||||
|
confirmHandler={closeWaitDialog}
|
||||||
|
>
|
||||||
|
{renderText(
|
||||||
|
lang(
|
||||||
|
'ChannelBoost.Error.BoostTooOftenText',
|
||||||
|
formatDateInFuture(lang, getServerTime(), applyInfo.waitUntil),
|
||||||
|
),
|
||||||
|
['simple_markdown', 'emoji'],
|
||||||
|
)}
|
||||||
|
</ConfirmDialog>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { info }): StateProps => {
|
||||||
|
const chat = info && selectChat(global, info?.chatId);
|
||||||
|
const boostedChat = info?.applyInfo?.type === 'replace'
|
||||||
|
? selectChat(global, info.applyInfo.boostedChatId) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
chat,
|
||||||
|
boostedChat,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(BoostModal));
|
||||||
@ -359,7 +359,8 @@
|
|||||||
.Spinner {
|
.Spinner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.875rem;
|
right: 0.875rem;
|
||||||
top: 0.875rem;
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
--spinner-size: 1.8125rem;
|
--spinner-size: 1.8125rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ type OwnProps = {
|
|||||||
confirmLabel?: string;
|
confirmLabel?: string;
|
||||||
confirmIsDestructive?: boolean;
|
confirmIsDestructive?: boolean;
|
||||||
isConfirmDisabled?: boolean;
|
isConfirmDisabled?: boolean;
|
||||||
|
isOnlyConfirm?: boolean;
|
||||||
areButtonsInColumn?: boolean;
|
areButtonsInColumn?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -37,6 +38,7 @@ const ConfirmDialog: FC<OwnProps> = ({
|
|||||||
confirmLabel = 'Confirm',
|
confirmLabel = 'Confirm',
|
||||||
confirmIsDestructive,
|
confirmIsDestructive,
|
||||||
isConfirmDisabled,
|
isConfirmDisabled,
|
||||||
|
isOnlyConfirm,
|
||||||
areButtonsInColumn,
|
areButtonsInColumn,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
@ -82,7 +84,7 @@ const ConfirmDialog: FC<OwnProps> = ({
|
|||||||
>
|
>
|
||||||
{confirmLabel}
|
{confirmLabel}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Cancel')}</Button>
|
{!isOnlyConfirm && <Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Cancel')}</Button>}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,12 +9,15 @@ import './Loading.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
color?: 'blue' | 'white' | 'black' | 'yellow';
|
color?: 'blue' | 'white' | 'black' | 'yellow';
|
||||||
backgroundColor?: 'light' | 'dark';
|
backgroundColor?: 'light' | 'dark';
|
||||||
|
className?: string;
|
||||||
onClick?: NoneToVoidFunction;
|
onClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Loading = ({ color = 'blue', backgroundColor, onClick }: OwnProps) => {
|
const Loading = ({
|
||||||
|
color = 'blue', backgroundColor, className, onClick,
|
||||||
|
}: OwnProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('Loading', onClick && 'interactive')} onClick={onClick}>
|
<div className={buildClassName('Loading', onClick && 'interactive', className)} onClick={onClick}>
|
||||||
<Spinner color={color} backgroundColor={backgroundColor} />
|
<Spinner color={color} backgroundColor={backgroundColor} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import {
|
|||||||
isChatSummaryOnly,
|
isChatSummaryOnly,
|
||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
isUserBot,
|
isUserBot,
|
||||||
|
toChannelId,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getGlobal, setGlobal,
|
||||||
@ -973,6 +974,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
checkChatlistInvite,
|
checkChatlistInvite,
|
||||||
openChatByUsername: openChatByUsernameAction,
|
openChatByUsername: openChatByUsernameAction,
|
||||||
openStoryViewerByUsername,
|
openStoryViewerByUsername,
|
||||||
|
processBoostParameters,
|
||||||
} = actions;
|
} = actions;
|
||||||
|
|
||||||
if (url.match(RE_TG_LINK)) {
|
if (url.match(RE_TG_LINK)) {
|
||||||
@ -1002,6 +1004,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
const hasStartApp = params.hasOwnProperty('startapp');
|
const hasStartApp = params.hasOwnProperty('startapp');
|
||||||
const choose = parseChooseParameter(params.choose);
|
const choose = parseChooseParameter(params.choose);
|
||||||
const storyId = part2 === 's' && (Number(part3) || undefined);
|
const storyId = part2 === 's' && (Number(part3) || undefined);
|
||||||
|
const hasBoost = params.hasOwnProperty('boost');
|
||||||
|
|
||||||
if (part1.match(/^\+([0-9]+)(\?|$)/)) {
|
if (part1.match(/^\+([0-9]+)(\?|$)/)) {
|
||||||
openChatByPhoneNumber({
|
openChatByPhoneNumber({
|
||||||
@ -1064,19 +1067,39 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
inviteHash: params.voicechat || params.livestream,
|
inviteHash: params.voicechat || params.livestream,
|
||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
|
} else if (part1 === 'boost') {
|
||||||
|
const username = part2;
|
||||||
|
const id = params.c;
|
||||||
|
|
||||||
|
const isPrivate = !username && Boolean(id);
|
||||||
|
|
||||||
|
processBoostParameters({
|
||||||
|
usernameOrId: username || id,
|
||||||
|
isPrivate,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
} else if (hasBoost) {
|
||||||
|
const isPrivate = part1 === 'c' && Boolean(chatOrChannelPostId);
|
||||||
|
processBoostParameters({
|
||||||
|
usernameOrId: chatOrChannelPostId || part1,
|
||||||
|
isPrivate,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
} else if (part1 === 'c' && chatOrChannelPostId && messageId) {
|
} else if (part1 === 'c' && chatOrChannelPostId && messageId) {
|
||||||
const chatId = `-100${chatOrChannelPostId}`;
|
const chatId = toChannelId(chatOrChannelPostId);
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
showNotification({ message: 'Chat does not exist', tabId });
|
showNotification({ message: 'Chat does not exist', tabId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
focusMessage({
|
if (messageId) {
|
||||||
chatId: chat.id,
|
focusMessage({
|
||||||
messageId,
|
chatId: chat.id,
|
||||||
tabId,
|
messageId,
|
||||||
});
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (part1.startsWith('$')) {
|
} else if (part1.startsWith('$')) {
|
||||||
openInvoice({
|
openInvoice({
|
||||||
slug: part1.substring(1),
|
slug: part1.substring(1),
|
||||||
@ -1110,6 +1133,37 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('processBoostParameters', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { usernameOrId, isPrivate, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
let chat: ApiChat | undefined;
|
||||||
|
|
||||||
|
if (isPrivate) {
|
||||||
|
const chatId = toChannelId(usernameOrId);
|
||||||
|
chat = selectChat(global, chatId);
|
||||||
|
if (!chat) {
|
||||||
|
actions.showNotification({ message: 'Chat does not exist', tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chat = await fetchChatByUsername(global, usernameOrId);
|
||||||
|
if (!chat) {
|
||||||
|
actions.showNotification({ message: 'User does not exist', tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isChatChannel(chat)) {
|
||||||
|
actions.openChat({ id: chat.id, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.openBoostModal({
|
||||||
|
chatId: chat.id,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('acceptInviteConfirmation', async (global, actions, payload): Promise<void> => {
|
addActionHandler('acceptInviteConfirmation', async (global, actions, payload): Promise<void> => {
|
||||||
const { hash, tabId = getCurrentTabId() } = payload!;
|
const { hash, tabId = getCurrentTabId() } = payload!;
|
||||||
const result = await callApi('importChatInvite', { hash });
|
const result = await callApi('importChatInvite', { hash });
|
||||||
@ -1139,7 +1193,9 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isWebApp) {
|
if (!isWebApp) {
|
||||||
await openChatByUsername(global, actions, username, threadId, messageId, startParam, startAttach, attach, tabId);
|
await openChatByUsername(
|
||||||
|
global, actions, username, threadId, messageId, startParam, startAttach, attach, tabId,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { buildCollectionByKey } from '../../../util/iteratees';
|
|||||||
import { translate } from '../../../util/langProvider';
|
import { translate } from '../../../util/langProvider';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { buildApiInputPrivacyRules } from '../../helpers';
|
import { buildApiInputPrivacyRules, isChatChannel } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
addChats,
|
addChats,
|
||||||
@ -26,8 +26,10 @@ import {
|
|||||||
updateStoryViews,
|
updateStoryViews,
|
||||||
updateStoryViewsLoading,
|
updateStoryViewsLoading,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectPeer, selectPeerStories, selectPeerStory,
|
selectChat,
|
||||||
|
selectPeer, selectPeerStories, selectPeerStory, selectTabState,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
const INFINITE_LOOP_MARKER = 100;
|
const INFINITE_LOOP_MARKER = 100;
|
||||||
@ -502,3 +504,89 @@ addActionHandler('activateStealthMode', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
|
callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openBoostModal', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat || !isChatChannel(chat)) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
chatId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const result = await callApi('fetchBoostsStatus', {
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
actions.closeBoostModal({ tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
chatId,
|
||||||
|
boostStatus: result,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const applyInfoResult = await callApi('fetchCanApplyBoost', {
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!applyInfoResult?.info) return;
|
||||||
|
|
||||||
|
const applyInfo = applyInfoResult.info;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.boostModal) return;
|
||||||
|
|
||||||
|
global = addChats(global, buildCollectionByKey(applyInfoResult.chats, 'id'));
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
...tabState.boostModal,
|
||||||
|
applyInfo,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('applyBoost', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
const result = await callApi('applyBoost', {
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStatusResult = await callApi('fetchBoostsStatus', {
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newStatusResult) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.boostModal?.boostStatus) return;
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
...tabState.boostModal,
|
||||||
|
boostStatus: newStatusResult,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|||||||
@ -411,3 +411,11 @@ addActionHandler('updateStoryView', (global, actions, payload): ActionReturnType
|
|||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeBoostModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
boostModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -35,6 +35,10 @@ export function isChannelId(entityId: string) {
|
|||||||
return entityId.length === CHANNEL_ID_LENGTH && entityId.startsWith('-100');
|
return entityId.length === CHANNEL_ID_LENGTH && entityId.startsWith('-100');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toChannelId(mtpId: string) {
|
||||||
|
return `-100${mtpId}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function isChatGroup(chat: ApiChat) {
|
export function isChatGroup(chat: ApiChat) {
|
||||||
return isChatBasicGroup(chat) || isChatSuperGroup(chat);
|
return isChatBasicGroup(chat) || isChatSuperGroup(chat);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiAppConfig,
|
ApiAppConfig,
|
||||||
|
ApiApplyBoostInfo,
|
||||||
ApiAttachBot,
|
ApiAttachBot,
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
|
ApiBoostsStatus,
|
||||||
ApiChannelStatistics,
|
ApiChannelStatistics,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiChatAdminRights,
|
ApiChatAdminRights,
|
||||||
@ -617,6 +619,12 @@ export type TabState = {
|
|||||||
suggestedPeerIds?: string[];
|
suggestedPeerIds?: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
boostModal?: {
|
||||||
|
chatId: string;
|
||||||
|
boostStatus?: ApiBoostsStatus;
|
||||||
|
applyInfo?: ApiApplyBoostInfo;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
@ -1358,6 +1366,10 @@ export interface ActionPayloads {
|
|||||||
startApp?: string;
|
startApp?: string;
|
||||||
originalParts?: string[];
|
originalParts?: string[];
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
processBoostParameters: {
|
||||||
|
usernameOrId: string;
|
||||||
|
isPrivate?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
requestThreadInfoUpdate: {
|
requestThreadInfoUpdate: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: number;
|
||||||
@ -2068,6 +2080,14 @@ export interface ActionPayloads {
|
|||||||
isForFuture?: boolean;
|
isForFuture?: boolean;
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
|
||||||
|
openBoostModal: {
|
||||||
|
chatId: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeBoostModal: WithTabId | undefined;
|
||||||
|
applyBoost: {
|
||||||
|
chatId: string;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
// Media Viewer & Audio Player
|
// Media Viewer & Audio Player
|
||||||
openMediaViewer: {
|
openMediaViewer: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
|
|||||||
20
src/lib/gramjs/tl/api.d.ts
vendored
20
src/lib/gramjs/tl/api.d.ts
vendored
@ -348,7 +348,7 @@ namespace Api {
|
|||||||
export type TypeAccessPointRule = AccessPointRule;
|
export type TypeAccessPointRule = AccessPointRule;
|
||||||
export type TypeTlsClientHello = TlsClientHello;
|
export type TypeTlsClientHello = TlsClientHello;
|
||||||
export type TypeTlsBlock = TlsBlockString | TlsBlockRandom | TlsBlockZero | TlsBlockDomain | TlsBlockGrease | TlsBlockScope;
|
export type TypeTlsBlock = TlsBlockString | TlsBlockRandom | TlsBlockZero | TlsBlockDomain | TlsBlockGrease | TlsBlockScope;
|
||||||
|
|
||||||
|
|
||||||
export namespace storage {
|
export namespace storage {
|
||||||
export type TypeFileType = storage.FileUnknown | storage.FilePartial | storage.FileJpeg | storage.FileGif | storage.FilePng | storage.FilePdf | storage.FileMp3 | storage.FileMov | storage.FileMp4 | storage.FileWebp;
|
export type TypeFileType = storage.FileUnknown | storage.FilePartial | storage.FileJpeg | storage.FileGif | storage.FilePng | storage.FilePdf | storage.FileMp3 | storage.FileMov | storage.FileMp4 | storage.FileWebp;
|
||||||
@ -423,6 +423,7 @@ namespace Api {
|
|||||||
export type TypeEmojiGroups = messages.EmojiGroupsNotModified | messages.EmojiGroups;
|
export type TypeEmojiGroups = messages.EmojiGroupsNotModified | messages.EmojiGroups;
|
||||||
export type TypeTranslatedText = messages.TranslateResult;
|
export type TypeTranslatedText = messages.TranslateResult;
|
||||||
export type TypeBotApp = messages.BotApp;
|
export type TypeBotApp = messages.BotApp;
|
||||||
|
export type TypeWebPage = messages.WebPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace updates {
|
export namespace updates {
|
||||||
@ -8915,7 +8916,7 @@ namespace Api {
|
|||||||
}> {
|
}> {
|
||||||
entries: Api.TypeTlsBlock[];
|
entries: Api.TypeTlsBlock[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export namespace storage {
|
export namespace storage {
|
||||||
export class FileUnknown extends VirtualClass<void> {};
|
export class FileUnknown extends VirtualClass<void> {};
|
||||||
@ -9753,6 +9754,15 @@ namespace Api {
|
|||||||
hasSettings?: true;
|
hasSettings?: true;
|
||||||
app: Api.TypeBotApp;
|
app: Api.TypeBotApp;
|
||||||
};
|
};
|
||||||
|
export class WebPage extends VirtualClass<{
|
||||||
|
webpage: Api.TypeWebPage;
|
||||||
|
chats: Api.TypeChat[];
|
||||||
|
users: Api.TypeUser[];
|
||||||
|
}> {
|
||||||
|
webpage: Api.TypeWebPage;
|
||||||
|
chats: Api.TypeChat[];
|
||||||
|
users: Api.TypeUser[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace updates {
|
export namespace updates {
|
||||||
@ -10807,6 +10817,7 @@ namespace Api {
|
|||||||
boosts: int;
|
boosts: int;
|
||||||
nextLevelBoosts?: int;
|
nextLevelBoosts?: int;
|
||||||
premiumAudience?: Api.TypeStatsPercentValue;
|
premiumAudience?: Api.TypeStatsPercentValue;
|
||||||
|
boostUrl: string;
|
||||||
}> {
|
}> {
|
||||||
// flags: undefined;
|
// flags: undefined;
|
||||||
myBoost?: true;
|
myBoost?: true;
|
||||||
@ -10815,6 +10826,7 @@ namespace Api {
|
|||||||
boosts: int;
|
boosts: int;
|
||||||
nextLevelBoosts?: int;
|
nextLevelBoosts?: int;
|
||||||
premiumAudience?: Api.TypeStatsPercentValue;
|
premiumAudience?: Api.TypeStatsPercentValue;
|
||||||
|
boostUrl: string;
|
||||||
};
|
};
|
||||||
export class CanApplyBoostOk extends VirtualClass<void> {};
|
export class CanApplyBoostOk extends VirtualClass<void> {};
|
||||||
export class CanApplyBoostReplace extends VirtualClass<{
|
export class CanApplyBoostReplace extends VirtualClass<{
|
||||||
@ -10971,7 +10983,7 @@ namespace Api {
|
|||||||
}>, Api.TypeDestroySessionRes> {
|
}>, Api.TypeDestroySessionRes> {
|
||||||
sessionId: long;
|
sessionId: long;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export namespace auth {
|
export namespace auth {
|
||||||
export class SendCode extends Request<Partial<{
|
export class SendCode extends Request<Partial<{
|
||||||
@ -12727,7 +12739,7 @@ namespace Api {
|
|||||||
export class GetWebPage extends Request<Partial<{
|
export class GetWebPage extends Request<Partial<{
|
||||||
url: string;
|
url: string;
|
||||||
hash: int;
|
hash: int;
|
||||||
}>, Api.TypeWebPage> {
|
}>, messages.TypeWebPage> {
|
||||||
url: string;
|
url: string;
|
||||||
hash: int;
|
hash: int;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1148,11 +1148,12 @@ mediaAreaGeoPoint#df8b3b22 coordinates:MediaAreaCoordinates geo:GeoPoint = Media
|
|||||||
mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?true coordinates:MediaAreaCoordinates reaction:Reaction = MediaArea;
|
mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?true coordinates:MediaAreaCoordinates reaction:Reaction = MediaArea;
|
||||||
peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector<StoryItem> = PeerStories;
|
peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector<StoryItem> = PeerStories;
|
||||||
stories.peerStories#cae68768 stories:PeerStories chats:Vector<Chat> users:Vector<User> = stories.PeerStories;
|
stories.peerStories#cae68768 stories:PeerStories chats:Vector<Chat> users:Vector<User> = stories.PeerStories;
|
||||||
stories.boostsStatus#66ea1fef flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue = stories.BoostsStatus;
|
stories.boostsStatus#e5c1aa5c flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string = stories.BoostsStatus;
|
||||||
stories.canApplyBoostOk#c3173587 = stories.CanApplyBoostResult;
|
stories.canApplyBoostOk#c3173587 = stories.CanApplyBoostResult;
|
||||||
stories.canApplyBoostReplace#712c4655 current_boost:Peer chats:Vector<Chat> = stories.CanApplyBoostResult;
|
stories.canApplyBoostReplace#712c4655 current_boost:Peer chats:Vector<Chat> = stories.CanApplyBoostResult;
|
||||||
booster#e9e6380 user_id:long expires:int = Booster;
|
booster#e9e6380 user_id:long expires:int = Booster;
|
||||||
stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector<Booster> next_offset:flags.0?string users:Vector<User> = stories.BoostersList;
|
stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector<Booster> next_offset:flags.0?string users:Vector<User> = stories.BoostersList;
|
||||||
|
messages.webPage#fd5e12bd webpage:WebPage chats:Vector<Chat> users:Vector<User> = messages.WebPage;
|
||||||
---functions---
|
---functions---
|
||||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||||
initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
|
initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
|
||||||
@ -1279,7 +1280,7 @@ messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = me
|
|||||||
messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
|
messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
|
||||||
messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
|
messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
|
||||||
messages.getCommonChats#e40ca104 user_id:InputUser max_id:long limit:int = messages.Chats;
|
messages.getCommonChats#e40ca104 user_id:InputUser max_id:long limit:int = messages.Chats;
|
||||||
messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
|
messages.getWebPage#8d9692a3 url:string hash:int = messages.WebPage;
|
||||||
messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
||||||
messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs;
|
messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs;
|
||||||
messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
|
messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
|
||||||
@ -1468,4 +1469,8 @@ stories.activateStealthMode#57bbd166 flags:# past:flags.0?true future:flags.1?tr
|
|||||||
stories.sendReaction#7fd736b2 flags:# add_to_recent:flags.0?true peer:InputPeer story_id:int reaction:Reaction = Updates;
|
stories.sendReaction#7fd736b2 flags:# add_to_recent:flags.0?true peer:InputPeer story_id:int reaction:Reaction = Updates;
|
||||||
stories.getPeerStories#2c4ada50 peer:InputPeer = stories.PeerStories;
|
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.getBoostsStatus#4c449472 peer:InputPeer = stories.BoostsStatus;
|
||||||
|
stories.getBoostersList#337ef980 peer:InputPeer offset:string limit:int = stories.BoostersList;
|
||||||
|
stories.canApplyBoost#db05c1bd peer:InputPeer = stories.CanApplyBoostResult;
|
||||||
|
stories.applyBoost#f29d7c2b peer:InputPeer = Bool;`;
|
||||||
|
|||||||
@ -314,5 +314,9 @@
|
|||||||
"stories.sendReaction",
|
"stories.sendReaction",
|
||||||
"stories.getPeerMaxIDs",
|
"stories.getPeerMaxIDs",
|
||||||
"stories.togglePeerStoriesHidden",
|
"stories.togglePeerStoriesHidden",
|
||||||
"stories.getPeerStories"
|
"stories.getPeerStories",
|
||||||
|
"stories.getBoostsStatus",
|
||||||
|
"stories.getBoostersList",
|
||||||
|
"stories.canApplyBoost",
|
||||||
|
"stories.applyBoost"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -288,6 +288,8 @@ $color-message-story-mention-to: #74bcff;
|
|||||||
--drag-target-border: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%23DDDFE0' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e");
|
--drag-target-border: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%23DDDFE0' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e");
|
||||||
--drag-target-border-hovered: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%2363A2E3' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e");
|
--drag-target-border-hovered: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%2363A2E3' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e");
|
||||||
|
|
||||||
|
--premium-gradient: linear-gradient(84.4deg, #6C93FF -4.85%, #976FFF 51.72%, #DF69D1 110.7%);
|
||||||
|
|
||||||
--layer-blackout-opacity: 0.3;
|
--layer-blackout-opacity: 0.3;
|
||||||
|
|
||||||
--layer-transition: 300ms cubic-bezier(0.33, 1, 0.68, 1);
|
--layer-transition: 300ms cubic-bezier(0.33, 1, 0.68, 1);
|
||||||
|
|||||||
@ -3,8 +3,8 @@ $icons-font: "icons";
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: $icons-font;
|
font-family: $icons-font;
|
||||||
src: url("./icons.woff2?2e8e2fec4b27141c4d298083615a0665") format("woff2"),
|
src: url("./icons.woff2?aa9c231863df4bab22759fc9f141c077") format("woff2"),
|
||||||
url("./icons.woff?2e8e2fec4b27141c4d298083615a0665") format("woff");
|
url("./icons.woff?aa9c231863df4bab22759fc9f141c077") format("woff");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
@ -58,200 +58,202 @@ $icons-map: (
|
|||||||
"avatar-deleted-account": "\f115",
|
"avatar-deleted-account": "\f115",
|
||||||
"avatar-saved-messages": "\f116",
|
"avatar-saved-messages": "\f116",
|
||||||
"bold": "\f117",
|
"bold": "\f117",
|
||||||
"bot-command": "\f118",
|
"boost": "\f118",
|
||||||
"bot-commands-filled": "\f119",
|
"boostcircle": "\f119",
|
||||||
"bots": "\f11a",
|
"bot-command": "\f11a",
|
||||||
"bug": "\f11b",
|
"bot-commands-filled": "\f11b",
|
||||||
"calendar-filter": "\f11c",
|
"bots": "\f11c",
|
||||||
"calendar": "\f11d",
|
"bug": "\f11d",
|
||||||
"camera-add": "\f11e",
|
"calendar-filter": "\f11e",
|
||||||
"camera": "\f11f",
|
"calendar": "\f11f",
|
||||||
"car": "\f120",
|
"camera-add": "\f120",
|
||||||
"card": "\f121",
|
"camera": "\f121",
|
||||||
"channel-filled": "\f122",
|
"car": "\f122",
|
||||||
"channel": "\f123",
|
"card": "\f123",
|
||||||
"channelviews": "\f124",
|
"channel-filled": "\f124",
|
||||||
"chat-badge": "\f125",
|
"channel": "\f125",
|
||||||
"chats-badge": "\f126",
|
"channelviews": "\f126",
|
||||||
"check": "\f127",
|
"chat-badge": "\f127",
|
||||||
"close-circle": "\f128",
|
"chats-badge": "\f128",
|
||||||
"close-topic": "\f129",
|
"check": "\f129",
|
||||||
"close": "\f12a",
|
"close-circle": "\f12a",
|
||||||
"cloud-download": "\f12b",
|
"close-topic": "\f12b",
|
||||||
"collapse": "\f12c",
|
"close": "\f12c",
|
||||||
"colorize": "\f12d",
|
"cloud-download": "\f12d",
|
||||||
"comments-sticker": "\f12e",
|
"collapse": "\f12e",
|
||||||
"comments": "\f12f",
|
"colorize": "\f12f",
|
||||||
"copy-media": "\f130",
|
"comments-sticker": "\f130",
|
||||||
"copy": "\f131",
|
"comments": "\f131",
|
||||||
"darkmode": "\f132",
|
"copy-media": "\f132",
|
||||||
"data": "\f133",
|
"copy": "\f133",
|
||||||
"delete-filled": "\f134",
|
"darkmode": "\f134",
|
||||||
"delete-left": "\f135",
|
"data": "\f135",
|
||||||
"delete-user": "\f136",
|
"delete-filled": "\f136",
|
||||||
"delete": "\f137",
|
"delete-left": "\f137",
|
||||||
"document": "\f138",
|
"delete-user": "\f138",
|
||||||
"double-badge": "\f139",
|
"delete": "\f139",
|
||||||
"down": "\f13a",
|
"document": "\f13a",
|
||||||
"download": "\f13b",
|
"double-badge": "\f13b",
|
||||||
"eats": "\f13c",
|
"down": "\f13c",
|
||||||
"edit": "\f13d",
|
"download": "\f13d",
|
||||||
"email": "\f13e",
|
"eats": "\f13e",
|
||||||
"enter": "\f13f",
|
"edit": "\f13f",
|
||||||
"expand": "\f140",
|
"email": "\f140",
|
||||||
"eye-closed-outline": "\f141",
|
"enter": "\f141",
|
||||||
"eye-closed": "\f142",
|
"expand": "\f142",
|
||||||
"eye-outline": "\f143",
|
"eye-closed-outline": "\f143",
|
||||||
"eye": "\f144",
|
"eye-closed": "\f144",
|
||||||
"favorite-filled": "\f145",
|
"eye-outline": "\f145",
|
||||||
"favorite": "\f146",
|
"eye": "\f146",
|
||||||
"file-badge": "\f147",
|
"favorite-filled": "\f147",
|
||||||
"flag": "\f148",
|
"favorite": "\f148",
|
||||||
"folder-badge": "\f149",
|
"file-badge": "\f149",
|
||||||
"folder": "\f14a",
|
"flag": "\f14a",
|
||||||
"fontsize": "\f14b",
|
"folder-badge": "\f14b",
|
||||||
"forums": "\f14c",
|
"folder": "\f14c",
|
||||||
"forward": "\f14d",
|
"fontsize": "\f14d",
|
||||||
"fullscreen": "\f14e",
|
"forums": "\f14e",
|
||||||
"gifs": "\f14f",
|
"forward": "\f14f",
|
||||||
"gift": "\f150",
|
"fullscreen": "\f150",
|
||||||
"group-filled": "\f151",
|
"gifs": "\f151",
|
||||||
"group": "\f152",
|
"gift": "\f152",
|
||||||
"grouped-disable": "\f153",
|
"group-filled": "\f153",
|
||||||
"grouped": "\f154",
|
"group": "\f154",
|
||||||
"hand-stop": "\f155",
|
"grouped-disable": "\f155",
|
||||||
"hashtag": "\f156",
|
"grouped": "\f156",
|
||||||
"heart-outline": "\f157",
|
"hand-stop": "\f157",
|
||||||
"heart": "\f158",
|
"hashtag": "\f158",
|
||||||
"help": "\f159",
|
"heart-outline": "\f159",
|
||||||
"info-filled": "\f15a",
|
"heart": "\f15a",
|
||||||
"info": "\f15b",
|
"help": "\f15b",
|
||||||
"install": "\f15c",
|
"info-filled": "\f15c",
|
||||||
"italic": "\f15d",
|
"info": "\f15d",
|
||||||
"key": "\f15e",
|
"install": "\f15e",
|
||||||
"keyboard": "\f15f",
|
"italic": "\f15f",
|
||||||
"lamp": "\f160",
|
"key": "\f160",
|
||||||
"language": "\f161",
|
"keyboard": "\f161",
|
||||||
"large-pause": "\f162",
|
"lamp": "\f162",
|
||||||
"large-play": "\f163",
|
"language": "\f163",
|
||||||
"link-badge": "\f164",
|
"large-pause": "\f164",
|
||||||
"link-broken": "\f165",
|
"large-play": "\f165",
|
||||||
"link": "\f166",
|
"link-badge": "\f166",
|
||||||
"location": "\f167",
|
"link-broken": "\f167",
|
||||||
"lock-badge": "\f168",
|
"link": "\f168",
|
||||||
"lock": "\f169",
|
"location": "\f169",
|
||||||
"logout": "\f16a",
|
"lock-badge": "\f16a",
|
||||||
"loop": "\f16b",
|
"lock": "\f16b",
|
||||||
"mention": "\f16c",
|
"logout": "\f16c",
|
||||||
"message-failed": "\f16d",
|
"loop": "\f16d",
|
||||||
"message-pending": "\f16e",
|
"mention": "\f16e",
|
||||||
"message-read": "\f16f",
|
"message-failed": "\f16f",
|
||||||
"message-succeeded": "\f170",
|
"message-pending": "\f170",
|
||||||
"message": "\f171",
|
"message-read": "\f171",
|
||||||
"microphone-alt": "\f172",
|
"message-succeeded": "\f172",
|
||||||
"microphone": "\f173",
|
"message": "\f173",
|
||||||
"monospace": "\f174",
|
"microphone-alt": "\f174",
|
||||||
"more-circle": "\f175",
|
"microphone": "\f175",
|
||||||
"more": "\f176",
|
"monospace": "\f176",
|
||||||
"mute": "\f177",
|
"more-circle": "\f177",
|
||||||
"muted": "\f178",
|
"more": "\f178",
|
||||||
"new-chat-filled": "\f179",
|
"mute": "\f179",
|
||||||
"next": "\f17a",
|
"muted": "\f17a",
|
||||||
"noise-suppression": "\f17b",
|
"new-chat-filled": "\f17b",
|
||||||
"non-contacts": "\f17c",
|
"next": "\f17c",
|
||||||
"open-in-new-tab": "\f17d",
|
"noise-suppression": "\f17d",
|
||||||
"password-off": "\f17e",
|
"non-contacts": "\f17e",
|
||||||
"pause": "\f17f",
|
"open-in-new-tab": "\f17f",
|
||||||
"permissions": "\f180",
|
"password-off": "\f180",
|
||||||
"phone-discard-outline": "\f181",
|
"pause": "\f181",
|
||||||
"phone-discard": "\f182",
|
"permissions": "\f182",
|
||||||
"phone": "\f183",
|
"phone-discard-outline": "\f183",
|
||||||
"photo": "\f184",
|
"phone-discard": "\f184",
|
||||||
"pin-badge": "\f185",
|
"phone": "\f185",
|
||||||
"pin-list": "\f186",
|
"photo": "\f186",
|
||||||
"pin": "\f187",
|
"pin-badge": "\f187",
|
||||||
"pinned-chat": "\f188",
|
"pin-list": "\f188",
|
||||||
"pinned-message": "\f189",
|
"pin": "\f189",
|
||||||
"pip": "\f18a",
|
"pinned-chat": "\f18a",
|
||||||
"play-story": "\f18b",
|
"pinned-message": "\f18b",
|
||||||
"play": "\f18c",
|
"pip": "\f18c",
|
||||||
"poll": "\f18d",
|
"play-story": "\f18d",
|
||||||
"premium": "\f18e",
|
"play": "\f18e",
|
||||||
"previous": "\f18f",
|
"poll": "\f18f",
|
||||||
"privacy-policy": "\f190",
|
"premium": "\f190",
|
||||||
"readchats": "\f191",
|
"previous": "\f191",
|
||||||
"recent": "\f192",
|
"privacy-policy": "\f192",
|
||||||
"reload": "\f193",
|
"readchats": "\f193",
|
||||||
"remove": "\f194",
|
"recent": "\f194",
|
||||||
"reopen-topic": "\f195",
|
"reload": "\f195",
|
||||||
"replace": "\f196",
|
"remove": "\f196",
|
||||||
"replies": "\f197",
|
"reopen-topic": "\f197",
|
||||||
"reply-filled": "\f198",
|
"replace": "\f198",
|
||||||
"reply": "\f199",
|
"replies": "\f199",
|
||||||
"revote": "\f19a",
|
"reply-filled": "\f19a",
|
||||||
"save-story": "\f19b",
|
"reply": "\f19b",
|
||||||
"saved-messages": "\f19c",
|
"revote": "\f19c",
|
||||||
"schedule": "\f19d",
|
"save-story": "\f19d",
|
||||||
"search": "\f19e",
|
"saved-messages": "\f19e",
|
||||||
"select": "\f19f",
|
"schedule": "\f19f",
|
||||||
"send-outline": "\f1a0",
|
"search": "\f1a0",
|
||||||
"send": "\f1a1",
|
"select": "\f1a1",
|
||||||
"settings-filled": "\f1a2",
|
"send-outline": "\f1a2",
|
||||||
"settings": "\f1a3",
|
"send": "\f1a3",
|
||||||
"share-filled": "\f1a4",
|
"settings-filled": "\f1a4",
|
||||||
"share-screen-outlined": "\f1a5",
|
"settings": "\f1a5",
|
||||||
"share-screen-stop": "\f1a6",
|
"share-filled": "\f1a6",
|
||||||
"share-screen": "\f1a7",
|
"share-screen-outlined": "\f1a7",
|
||||||
"sidebar": "\f1a8",
|
"share-screen-stop": "\f1a8",
|
||||||
"skip-next": "\f1a9",
|
"share-screen": "\f1a9",
|
||||||
"skip-previous": "\f1aa",
|
"sidebar": "\f1aa",
|
||||||
"smallscreen": "\f1ab",
|
"skip-next": "\f1ab",
|
||||||
"smile": "\f1ac",
|
"skip-previous": "\f1ac",
|
||||||
"sort": "\f1ad",
|
"smallscreen": "\f1ad",
|
||||||
"speaker-muted-story": "\f1ae",
|
"smile": "\f1ae",
|
||||||
"speaker-outline": "\f1af",
|
"sort": "\f1af",
|
||||||
"speaker-story": "\f1b0",
|
"speaker-muted-story": "\f1b0",
|
||||||
"speaker": "\f1b1",
|
"speaker-outline": "\f1b1",
|
||||||
"spoiler-disable": "\f1b2",
|
"speaker-story": "\f1b2",
|
||||||
"spoiler": "\f1b3",
|
"speaker": "\f1b3",
|
||||||
"sport": "\f1b4",
|
"spoiler-disable": "\f1b4",
|
||||||
"stats": "\f1b5",
|
"spoiler": "\f1b5",
|
||||||
"stealth-future": "\f1b6",
|
"sport": "\f1b6",
|
||||||
"stealth-past": "\f1b7",
|
"stats": "\f1b7",
|
||||||
"stickers": "\f1b8",
|
"stealth-future": "\f1b8",
|
||||||
"stop-raising-hand": "\f1b9",
|
"stealth-past": "\f1b9",
|
||||||
"stop": "\f1ba",
|
"stickers": "\f1ba",
|
||||||
"story-caption": "\f1bb",
|
"stop-raising-hand": "\f1bb",
|
||||||
"story-expired": "\f1bc",
|
"stop": "\f1bc",
|
||||||
"story-priority": "\f1bd",
|
"story-caption": "\f1bd",
|
||||||
"story-reply": "\f1be",
|
"story-expired": "\f1be",
|
||||||
"strikethrough": "\f1bf",
|
"story-priority": "\f1bf",
|
||||||
"timer": "\f1c0",
|
"story-reply": "\f1c0",
|
||||||
"transcribe": "\f1c1",
|
"strikethrough": "\f1c1",
|
||||||
"truck": "\f1c2",
|
"timer": "\f1c2",
|
||||||
"unarchive": "\f1c3",
|
"transcribe": "\f1c3",
|
||||||
"underlined": "\f1c4",
|
"truck": "\f1c4",
|
||||||
"unlock-badge": "\f1c5",
|
"unarchive": "\f1c5",
|
||||||
"unlock": "\f1c6",
|
"underlined": "\f1c6",
|
||||||
"unmute": "\f1c7",
|
"unlock-badge": "\f1c7",
|
||||||
"unpin": "\f1c8",
|
"unlock": "\f1c8",
|
||||||
"unread": "\f1c9",
|
"unmute": "\f1c9",
|
||||||
"up": "\f1ca",
|
"unpin": "\f1ca",
|
||||||
"user-filled": "\f1cb",
|
"unread": "\f1cb",
|
||||||
"user-online": "\f1cc",
|
"up": "\f1cc",
|
||||||
"user": "\f1cd",
|
"user-filled": "\f1cd",
|
||||||
"video-outlined": "\f1ce",
|
"user-online": "\f1ce",
|
||||||
"video-stop": "\f1cf",
|
"user": "\f1cf",
|
||||||
"video": "\f1d0",
|
"video-outlined": "\f1d0",
|
||||||
"voice-chat": "\f1d1",
|
"video-stop": "\f1d1",
|
||||||
"volume-1": "\f1d2",
|
"video": "\f1d2",
|
||||||
"volume-2": "\f1d3",
|
"voice-chat": "\f1d3",
|
||||||
"volume-3": "\f1d4",
|
"volume-1": "\f1d4",
|
||||||
"web": "\f1d5",
|
"volume-2": "\f1d5",
|
||||||
"webapp": "\f1d6",
|
"volume-3": "\f1d6",
|
||||||
"word-wrap": "\f1d7",
|
"web": "\f1d7",
|
||||||
"zoom-in": "\f1d8",
|
"webapp": "\f1d8",
|
||||||
"zoom-out": "\f1d9",
|
"word-wrap": "\f1d9",
|
||||||
|
"zoom-in": "\f1da",
|
||||||
|
"zoom-out": "\f1db",
|
||||||
);
|
);
|
||||||
|
|
||||||
.icon-active-sessions::before {
|
.icon-active-sessions::before {
|
||||||
@ -323,6 +325,12 @@ $icons-map: (
|
|||||||
.icon-bold::before {
|
.icon-bold::before {
|
||||||
content: map.get($icons-map, "bold");
|
content: map.get($icons-map, "bold");
|
||||||
}
|
}
|
||||||
|
.icon-boost::before {
|
||||||
|
content: map.get($icons-map, "boost");
|
||||||
|
}
|
||||||
|
.icon-boostcircle::before {
|
||||||
|
content: map.get($icons-map, "boostcircle");
|
||||||
|
}
|
||||||
.icon-bot-command::before {
|
.icon-bot-command::before {
|
||||||
content: map.get($icons-map, "bot-command");
|
content: map.get($icons-map, "bot-command");
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -22,6 +22,8 @@ export type FontIconName =
|
|||||||
| 'avatar-deleted-account'
|
| 'avatar-deleted-account'
|
||||||
| 'avatar-saved-messages'
|
| 'avatar-saved-messages'
|
||||||
| 'bold'
|
| 'bold'
|
||||||
|
| 'boost'
|
||||||
|
| 'boostcircle'
|
||||||
| 'bot-command'
|
| 'bot-command'
|
||||||
| 'bot-commands-filled'
|
| 'bot-commands-filled'
|
||||||
| 'bots'
|
| 'bots'
|
||||||
|
|||||||
@ -363,6 +363,31 @@ export function formatDateAtTime(
|
|||||||
return lang('formatDateAtTime', [formattedDate, time]);
|
return lang('formatDateAtTime', [formattedDate, time]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDateInFuture(
|
||||||
|
lang: LangFn,
|
||||||
|
currentTime: number,
|
||||||
|
datetime: number,
|
||||||
|
) {
|
||||||
|
const diff = Math.ceil(datetime - currentTime);
|
||||||
|
if (diff < 0) {
|
||||||
|
return lang('RightNow');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff < 60) {
|
||||||
|
return lang('Seconds', diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff < 60 * 60) {
|
||||||
|
return lang('Minutes', Math.ceil(diff / 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff < 60 * 60 * 24) {
|
||||||
|
return lang('Hours', Math.ceil(diff / (60 * 60)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang('Days', Math.ceil(diff / (60 * 60 * 24)));
|
||||||
|
}
|
||||||
|
|
||||||
function isValidDate(day: number, month: number, year = 2021): boolean {
|
function isValidDate(day: number, month: number, year = 2021): boolean {
|
||||||
if (month > (MAX_MONTH_IN_YEAR - 1) || day > MAX_DAY_IN_MONTH) {
|
if (month > (MAX_MONTH_IN_YEAR - 1) || day > MAX_DAY_IN_MONTH) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { IS_SAFARI } from './windowEnvironment';
|
|||||||
|
|
||||||
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||||
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
||||||
'invoice' | 'addlist';
|
'invoice' | 'addlist' | 'boost';
|
||||||
|
|
||||||
export const processDeepLink = (url: string) => {
|
export const processDeepLink = (url: string) => {
|
||||||
const {
|
const {
|
||||||
@ -28,6 +28,7 @@ export const processDeepLink = (url: string) => {
|
|||||||
openChatWithDraft,
|
openChatWithDraft,
|
||||||
checkChatlistInvite,
|
checkChatlistInvite,
|
||||||
openStoryViewerByUsername,
|
openStoryViewerByUsername,
|
||||||
|
processBoostParameters,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
// Safari thinks the path in tg://path links is hostname for some reason
|
// Safari thinks the path in tg://path links is hostname for some reason
|
||||||
@ -43,6 +44,7 @@ export const processDeepLink = (url: string) => {
|
|||||||
|
|
||||||
const hasStartAttach = params.hasOwnProperty('startattach');
|
const hasStartAttach = params.hasOwnProperty('startattach');
|
||||||
const hasStartApp = params.hasOwnProperty('startapp');
|
const hasStartApp = params.hasOwnProperty('startapp');
|
||||||
|
const hasBoost = params.hasOwnProperty('boost');
|
||||||
const choose = parseChooseParameter(params.choose);
|
const choose = parseChooseParameter(params.choose);
|
||||||
const threadId = Number(thread) || Number(topic) || undefined;
|
const threadId = Number(thread) || Number(topic) || undefined;
|
||||||
|
|
||||||
@ -64,6 +66,8 @@ export const processDeepLink = (url: string) => {
|
|||||||
username: domain,
|
username: domain,
|
||||||
inviteHash: voicechat || livestream,
|
inviteHash: voicechat || livestream,
|
||||||
});
|
});
|
||||||
|
} else if (hasBoost) {
|
||||||
|
processBoostParameters({ usernameOrId: domain });
|
||||||
} else if (phone) {
|
} else if (phone) {
|
||||||
openChatByPhoneNumber({ phoneNumber: phone, startAttach: startattach, attach });
|
openChatByPhoneNumber({ phoneNumber: phone, startAttach: startattach, attach });
|
||||||
} else if (story) {
|
} else if (story) {
|
||||||
@ -87,6 +91,13 @@ export const processDeepLink = (url: string) => {
|
|||||||
post, channel,
|
post, channel,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
|
const hasBoost = params.hasOwnProperty('boost');
|
||||||
|
|
||||||
|
if (hasBoost) {
|
||||||
|
processBoostParameters({ usernameOrId: channel, isPrivate: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
focusMessage({
|
focusMessage({
|
||||||
chatId: `-${channel}`,
|
chatId: `-${channel}`,
|
||||||
messageId: Number(post),
|
messageId: Number(post),
|
||||||
@ -138,6 +149,14 @@ export const processDeepLink = (url: string) => {
|
|||||||
openInvoice({ slug });
|
openInvoice({ slug });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'boost': {
|
||||||
|
const { channel, domain } = params;
|
||||||
|
const isPrivate = Boolean(channel);
|
||||||
|
|
||||||
|
processBoostParameters({ usernameOrId: channel || domain, isPrivate });
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Unsupported deeplink
|
// Unsupported deeplink
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user