Statistics: Monetization Stats for Channels (#4843)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
c7092ce13c
commit
9daa5f1a19
@ -1,11 +1,13 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
ApiChannelMonetizationStatistics,
|
||||
ApiChannelStatistics,
|
||||
ApiGroupStatistics,
|
||||
ApiMessagePublicForward,
|
||||
ApiPostStatistics,
|
||||
ApiStoryPublicForward,
|
||||
ChannelMonetizationBalances,
|
||||
PrepaidGiveaway, StatisticsGraph,
|
||||
StatisticsMessageInteractionCounter,
|
||||
StatisticsOverviewItem,
|
||||
@ -17,6 +19,8 @@ import type {
|
||||
import { buildApiUsernames, buildAvatarPhotoId } from './common';
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||
|
||||
const DECIMALS = 10 ** 9;
|
||||
|
||||
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
||||
return {
|
||||
// Graphs
|
||||
@ -49,6 +53,20 @@ export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiC
|
||||
};
|
||||
}
|
||||
|
||||
export function buildChannelMonetizationStatistics(
|
||||
stats: GramJs.stats.BroadcastRevenueStats,
|
||||
): ApiChannelMonetizationStatistics {
|
||||
return {
|
||||
// Graphs
|
||||
topHoursGraph: buildGraph(stats.topHoursGraph),
|
||||
revenueGraph: buildGraph(stats.revenueGraph, undefined, true, stats.usdRate),
|
||||
|
||||
// Statistics overview
|
||||
balances: buildChannelMonetizationBalances(stats.balances),
|
||||
usdRate: stats.usdRate,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiPostInteractionCounter(
|
||||
interaction: GramJs.TypePostInteractionCounters,
|
||||
): StatisticsMessageInteractionCounter | StatisticsStoryInteractionCounter | undefined {
|
||||
@ -136,7 +154,7 @@ export function buildStoryPublicForwards(
|
||||
}
|
||||
|
||||
export function buildGraph(
|
||||
result: GramJs.TypeStatsGraph, isPercentage?: boolean,
|
||||
result: GramJs.TypeStatsGraph, isPercentage?: boolean, isCurrency?: boolean, currencyRate?: number,
|
||||
): StatisticsGraph | undefined {
|
||||
if ((result as GramJs.StatsGraphError).error) {
|
||||
return undefined;
|
||||
@ -156,6 +174,8 @@ export function buildGraph(
|
||||
hasSecondYAxis,
|
||||
isStacked: data.stacked && !hasSecondYAxis,
|
||||
isPercentage,
|
||||
isCurrency,
|
||||
currencyRate,
|
||||
datasets: y.map((item: any) => {
|
||||
const key = item[0];
|
||||
|
||||
@ -249,3 +269,15 @@ function buildApiMessagePublicForward(message: GramJs.TypeMessage, chats: GramJs
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildChannelMonetizationBalances({
|
||||
currentBalance,
|
||||
availableBalance,
|
||||
overallRevenue,
|
||||
}: GramJs.BroadcastRevenueBalances): ChannelMonetizationBalances {
|
||||
return {
|
||||
currentBalance: Number(currentBalance) / DECIMALS,
|
||||
availableBalance: Number(availableBalance) / DECIMALS,
|
||||
overallRevenue: Number(overallRevenue) / DECIMALS,
|
||||
};
|
||||
}
|
||||
|
||||
@ -640,6 +640,7 @@ async function getFullChannelInfo(
|
||||
emojiset,
|
||||
boostsApplied,
|
||||
boostsUnrestrict,
|
||||
canViewRevenue: canViewMonetization,
|
||||
} = result.fullChat;
|
||||
|
||||
if (chatPhoto) {
|
||||
@ -700,6 +701,7 @@ async function getFullChannelInfo(
|
||||
} : undefined,
|
||||
canViewMembers: canViewParticipants,
|
||||
canViewStatistics: canViewStats,
|
||||
canViewMonetization,
|
||||
isPreHistoryHidden: hiddenPrehistory,
|
||||
members,
|
||||
kickedMembers,
|
||||
|
||||
@ -84,6 +84,7 @@ export * from './reactions';
|
||||
export {
|
||||
fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics,
|
||||
fetchMessagePublicForwards, fetchStatisticsAsyncGraph, fetchStoryStatistics, fetchStoryPublicForwards,
|
||||
fetchChannelMonetizationStatistics,
|
||||
} from './statistics';
|
||||
|
||||
export {
|
||||
|
||||
@ -8,6 +8,7 @@ import type {
|
||||
import { STATISTICS_PUBLIC_FORWARDS_LIMIT } from '../../../config';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import {
|
||||
buildChannelMonetizationStatistics,
|
||||
buildChannelStatistics,
|
||||
buildGraph,
|
||||
buildGroupStatistics,
|
||||
@ -39,6 +40,22 @@ export async function fetchChannelStatistics({
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchChannelMonetizationStatistics({
|
||||
chat, dcId,
|
||||
}: { chat: ApiChat; dcId?: number }) {
|
||||
const result = await invokeRequest(new GramJs.stats.GetBroadcastRevenueStats({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
}), {
|
||||
dcId,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildChannelMonetizationStatistics(result);
|
||||
}
|
||||
|
||||
export async function fetchGroupStatistics({
|
||||
chat, dcId,
|
||||
}: { chat: ApiChat; dcId?: number }) {
|
||||
|
||||
@ -126,6 +126,7 @@ export interface ApiChatFullInfo {
|
||||
reactionsLimit?: number;
|
||||
sendAsId?: string;
|
||||
canViewStatistics?: boolean;
|
||||
canViewMonetization?: boolean;
|
||||
recentRequesterIds?: string[];
|
||||
requestsPending?: number;
|
||||
statisticsDcId?: number;
|
||||
|
||||
@ -23,6 +23,13 @@ export interface ApiChannelStatistics {
|
||||
recentPosts: Array<StatisticsMessageInteractionCounter | StatisticsStoryInteractionCounter>;
|
||||
}
|
||||
|
||||
export interface ApiChannelMonetizationStatistics {
|
||||
topHoursGraph?: StatisticsGraph | string;
|
||||
revenueGraph?: StatisticsGraph | string;
|
||||
balances?: ChannelMonetizationBalances;
|
||||
usdRate?: number;
|
||||
}
|
||||
|
||||
export interface ApiGroupStatistics {
|
||||
growthGraph?: StatisticsGraph | string;
|
||||
membersGraph?: StatisticsGraph | string;
|
||||
@ -79,6 +86,8 @@ export interface StatisticsGraph {
|
||||
labels: Array<string | number>;
|
||||
isStacked: boolean;
|
||||
isPercentage?: boolean;
|
||||
isCurrency?: boolean;
|
||||
currencyRate?: number;
|
||||
hideCaption: boolean;
|
||||
hasSecondYAxis: boolean;
|
||||
minimapRange: {
|
||||
@ -131,3 +140,9 @@ export interface StatisticsStoryInteractionCounter {
|
||||
forwardsCount: number;
|
||||
reactionsCount: number;
|
||||
}
|
||||
|
||||
export interface ChannelMonetizationBalances {
|
||||
currentBalance: number;
|
||||
availableBalance: number;
|
||||
overallRevenue: number;
|
||||
}
|
||||
|
||||
3
src/assets/font-icons/cash-circle.svg
Normal file
3
src/assets/font-icons/cash-circle.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.50166 10C2.50166 5.85878 5.85878 2.50166 10 2.50166C14.1412 2.50166 17.4983 5.85878 17.4983 10C17.4983 14.1412 14.1412 17.4983 10 17.4983C5.85878 17.4983 2.50166 14.1412 2.50166 10ZM10 0.831665C4.93647 0.831665 0.831665 4.93647 0.831665 10C0.831665 15.0635 4.93647 19.1683 10 19.1683C15.0635 19.1683 19.1683 15.0635 19.1683 10C19.1683 4.93647 15.0635 0.831665 10 0.831665ZM10.831 4.16656C10.831 3.70541 10.4572 3.33156 9.99605 3.33156C9.53489 3.33156 9.16105 3.70541 9.16105 4.16656V4.83875C7.7101 5.09895 6.609 6.3676 6.609 7.89342V8.1249C6.609 9.62159 7.82231 10.8349 9.319 10.8349H10.6732C11.2475 10.8349 11.7132 11.3005 11.7132 11.8749V12.1064C11.7132 12.8981 11.0714 13.5399 10.2796 13.5399H9.99732L9.99605 13.5399L9.99477 13.5399H9.6315C8.88453 13.5399 8.279 12.9344 8.279 12.1874C8.279 11.7262 7.90516 11.3524 7.444 11.3524C6.98284 11.3524 6.609 11.7262 6.609 12.1874C6.609 13.6967 7.7152 14.9475 9.16105 15.1735V15.8332C9.16105 16.2944 9.53489 16.6682 9.99605 16.6682C10.4572 16.6682 10.831 16.2944 10.831 15.8332V15.1611C12.282 14.9009 13.3832 13.6322 13.3832 12.1064V11.8749C13.3832 10.3782 12.1699 9.1649 10.6732 9.1649H9.319C8.74462 9.1649 8.279 8.69927 8.279 8.1249V7.89342C8.279 7.1017 8.92081 6.4599 9.71252 6.4599H9.99605H10.4127C11.1309 6.4599 11.7132 7.04211 11.7132 7.76031C11.7132 8.22147 12.087 8.59531 12.5482 8.59531C13.0093 8.59531 13.3832 8.22147 13.3832 7.76031C13.3832 6.26178 12.2735 5.02242 10.831 4.81912V4.16656Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
src/assets/font-icons/toncoin.svg
Normal file
3
src/assets/font-icons/toncoin.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<path fill="#fff" transform="translate(-2, -8)" d="M27.32 10.628H8.44c-3.516 0-5.745 3.792-3.976 6.858l11.801 20.455c.77 1.335 2.7 1.335 3.47 0l11.804-20.455c1.767-3.06-.462-6.858-3.975-6.858zM16.255 31.807l-2.57-4.974-6.202-11.092c-.409-.71.096-1.62.953-1.62h7.816V31.81zM28.51 15.739l-6.2 11.096-2.57 4.972V14.119h7.817c.857 0 1.362.91.953 1.62"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@ -1271,6 +1271,10 @@
|
||||
"MenuInstallApp" = "Install App";
|
||||
"RemoveEffect" = "Remove effect";
|
||||
"ReplyInPrivateMessage" = "Reply In Private Message";
|
||||
"MonetizationInfoTONTitle" = "What is 💎 TON?";
|
||||
"ChannelEarnLearnCoinAbout" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
|
||||
"MonetizationBalanceZeroInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by advertisers to pay for ads. {link}";
|
||||
"ChannelEarnAbout" = "Telegram shares 50% of the revenue from ads displayed in your channel as rewards. {link}";
|
||||
"AriaSearchOlderResult" = "Focus next result";
|
||||
"AriaSearchNewerResult" = "Focus previous result";
|
||||
"CreditsBoxHistoryEntryGiftOutAbout" = "With Stars, {user} will be able to unlock content and services on Telegram. {link}"
|
||||
|
||||
@ -31,6 +31,7 @@ export { default as StarsBalanceModal } from '../components/modals/stars/StarsBa
|
||||
export { default as StarPaymentModal } from '../components/modals/stars/StarsPaymentModal';
|
||||
|
||||
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
||||
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
.root :global(.modal-dialog) {
|
||||
width: 26.25rem;
|
||||
}
|
||||
|
||||
.title, .description {
|
||||
text-align: center !important;
|
||||
text-wrap: pretty;
|
||||
@ -8,26 +12,6 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-block: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.topIcon {
|
||||
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--premium-gradient);
|
||||
|
||||
font-size: 4rem;
|
||||
color: white;
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||
|
||||
import type { TableAboutData } from '../modals/common/TableAboutModal';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
@ -8,27 +10,25 @@ import useSelectorSignal from '../../hooks/data/useSelectorSignal';
|
||||
import useDerivedState from '../../hooks/useDerivedState';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import TableAboutModal from '../modals/common/TableAboutModal';
|
||||
import Button from '../ui/Button';
|
||||
import ListItem from '../ui/ListItem';
|
||||
import Modal from '../ui/Modal';
|
||||
import Separator from '../ui/Separator';
|
||||
import Icon from './icons/Icon';
|
||||
import SafeLink from './SafeLink';
|
||||
|
||||
import styles from './AboutAdsModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
isRevenueSharing?: boolean;
|
||||
isMonetizationSharing?: boolean;
|
||||
onClose: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const AboutAdsModal: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
isRevenueSharing,
|
||||
isMonetizationSharing,
|
||||
onClose,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const minLevelSignal = useSelectorSignal((global) => global.appConfig?.channelRestrictAdsLevelMin);
|
||||
const minLevelToRestrictAds = useDerivedState(minLevelSignal);
|
||||
@ -36,84 +36,89 @@ const AboutAdsModal: FC<OwnProps> = ({
|
||||
const regularAdContent = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<h3>{lang('SponsoredMessageInfoScreen.Title')}</h3>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription1'), ['br'])}</p>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription2'), ['br'])}</p>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription3'), ['br'])}</p>
|
||||
<h3>{oldLang('SponsoredMessageInfoScreen.Title')}</h3>
|
||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription1'), ['br'])}</p>
|
||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription2'), ['br'])}</p>
|
||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription3'), ['br'])}</p>
|
||||
<p>
|
||||
<SafeLink
|
||||
url={lang('SponsoredMessageAlertLearnMoreUrl')}
|
||||
text={lang('SponsoredMessageAlertLearnMoreUrl')}
|
||||
url={oldLang('SponsoredMessageAlertLearnMoreUrl')}
|
||||
text={oldLang('SponsoredMessageAlertLearnMoreUrl')}
|
||||
/>
|
||||
</p>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription4'), ['br'])}</p>
|
||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription4'), ['br'])}</p>
|
||||
</>
|
||||
);
|
||||
}, [lang]);
|
||||
}, [oldLang]);
|
||||
|
||||
const revenueSharingAdContent = useMemo(() => {
|
||||
return (
|
||||
const modalData = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<div className={styles.topIcon}><Icon name="channel" /></div>
|
||||
<h3 className={styles.title}>{lang('AboutRevenueSharingAds')}</h3>
|
||||
<h3 className={styles.title}>{oldLang('AboutRevenueSharingAds')}</h3>
|
||||
<p className={buildClassName(styles.description, styles.secondary)}>
|
||||
{lang('RevenueSharingAdsAlertSubtitle')}
|
||||
{oldLang('RevenueSharingAdsAlertSubtitle')}
|
||||
</p>
|
||||
<ListItem
|
||||
isStatic
|
||||
multiline
|
||||
icon="lock"
|
||||
>
|
||||
<span className="title">{lang('RevenueSharingAdsInfo1Title')}</span>
|
||||
<span className="subtitle">
|
||||
{renderText(lang('RevenueSharingAdsInfo1Subtitle'), ['simple_markdown'])}
|
||||
</span>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
isStatic
|
||||
multiline
|
||||
icon="revenue-split"
|
||||
>
|
||||
<span className="title">{lang('RevenueSharingAdsInfo2Title')}</span>
|
||||
<span className="subtitle">
|
||||
{renderText(lang('RevenueSharingAdsInfo2Subtitle'), ['simple_markdown'])}
|
||||
</span>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
isStatic
|
||||
multiline
|
||||
icon="nochannel"
|
||||
>
|
||||
<span className="title">{lang('RevenueSharingAdsInfo3Title')}</span>
|
||||
<span className="subtitle">
|
||||
{renderText(lang('RevenueSharingAdsInfo3Subtitle', minLevelToRestrictAds), ['simple_markdown'])}
|
||||
</span>
|
||||
</ListItem>
|
||||
<Separator className={styles.separator} />
|
||||
<h3 className={styles.title}>{renderText(lang('RevenueSharingAdsInfo4Title'), ['simple_markdown'])}</h3>
|
||||
</>
|
||||
);
|
||||
|
||||
const listItemData = [
|
||||
['lock', oldLang('RevenueSharingAdsInfo1Title'),
|
||||
renderText(oldLang('RevenueSharingAdsInfo1Subtitle'), ['simple_markdown'])],
|
||||
['revenue-split', oldLang('RevenueSharingAdsInfo2Title'),
|
||||
renderText(oldLang('RevenueSharingAdsInfo2Subtitle'), ['simple_markdown'])],
|
||||
['nochannel', oldLang('RevenueSharingAdsInfo3Title'),
|
||||
renderText(oldLang('RevenueSharingAdsInfo3Subtitle', minLevelToRestrictAds), ['simple_markdown'])],
|
||||
] satisfies TableAboutData;
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<h3 className={styles.title}>{renderText(oldLang('RevenueSharingAdsInfo4Title'), ['simple_markdown'])}</h3>
|
||||
<p className={styles.description}>
|
||||
{renderText(lang('RevenueSharingAdsInfo4Subtitle2', ''), ['simple_markdown'])}
|
||||
{renderText(oldLang('RevenueSharingAdsInfo4Subtitle2', ''), ['simple_markdown'])}
|
||||
<SafeLink
|
||||
url={lang('PromoteUrl')}
|
||||
text={lang('LearnMoreArrow')}
|
||||
url={oldLang('PromoteUrl')}
|
||||
text={oldLang('LearnMoreArrow')}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}, [lang, minLevelToRestrictAds]);
|
||||
|
||||
return {
|
||||
header,
|
||||
listItemData,
|
||||
footer,
|
||||
};
|
||||
}, [isOpen, oldLang, minLevelToRestrictAds]);
|
||||
|
||||
if (isMonetizationSharing && modalData) {
|
||||
return (
|
||||
<TableAboutModal
|
||||
isOpen={isOpen}
|
||||
listItemData={modalData.listItemData}
|
||||
headerIconName="channel"
|
||||
header={modalData.header}
|
||||
footer={modalData.footer}
|
||||
buttonText={oldLang('RevenueSharingAdsUnderstood')}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
className={styles.root}
|
||||
contentClassName={styles.content}
|
||||
onClose={onClose}
|
||||
>
|
||||
{isRevenueSharing ? revenueSharingAdContent : regularAdContent}
|
||||
{regularAdContent}
|
||||
<Button
|
||||
size="smaller"
|
||||
onClick={onClose}
|
||||
>
|
||||
{lang('RevenueSharingAdsUnderstood')}
|
||||
{oldLang('RevenueSharingAdsUnderstood')}
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
18
src/components/common/AboutMonetizationModal.async.tsx
Normal file
18
src/components/common/AboutMonetizationModal.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 './AboutMonetizationModal';
|
||||
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const AboutMonetizationModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const AboutMonetizationModal = useModuleLoader(Bundles.Extra, 'AboutMonetizationModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return AboutMonetizationModal ? <AboutMonetizationModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default AboutMonetizationModalAsync;
|
||||
10
src/components/common/AboutMonetizationModal.module.scss
Normal file
10
src/components/common/AboutMonetizationModal.module.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.title, .description {
|
||||
text-align: center !important;
|
||||
text-wrap: pretty;
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.toncoin {
|
||||
font-size: 1rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
105
src/components/common/AboutMonetizationModal.tsx
Normal file
105
src/components/common/AboutMonetizationModal.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||
|
||||
import type { TableAboutData } from '../modals/common/TableAboutModal';
|
||||
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import TableAboutModal from '../modals/common/TableAboutModal';
|
||||
import Icon from './icons/Icon';
|
||||
import SafeLink from './SafeLink';
|
||||
|
||||
import styles from './AboutMonetizationModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
onClose: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const AboutMonetizationModal: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
const blockchainText = useMemo(() => {
|
||||
const linkText = oldLang('LearnMore');
|
||||
return lang(
|
||||
'ChannelEarnLearnCoinAbout',
|
||||
{
|
||||
link: (
|
||||
<SafeLink url={oldLang('MonetizationInfoTONLink')} text={linkText}>
|
||||
{linkText}
|
||||
<Icon name="next" />
|
||||
</SafeLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
withNodes: true,
|
||||
},
|
||||
);
|
||||
}, [lang, oldLang]);
|
||||
|
||||
const monetizationTitle = useMemo(() => {
|
||||
return lang(
|
||||
'MonetizationInfoTONTitle',
|
||||
undefined,
|
||||
{
|
||||
withNodes: true,
|
||||
specialReplacement: { '💎': <Icon className={styles.toncoin} name="toncoin" /> },
|
||||
},
|
||||
);
|
||||
}, [lang]);
|
||||
|
||||
const modalData = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
const header = (
|
||||
<h3 className={styles.title}>{oldLang('lng_channel_earn_learn_title')}</h3>
|
||||
);
|
||||
|
||||
const listItemData = [
|
||||
['channel', oldLang('lng_channel_earn_learn_in_subtitle'),
|
||||
renderText(oldLang('lng_channel_earn_learn_in_about'), ['simple_markdown'])],
|
||||
['revenue-split', oldLang('lng_channel_earn_learn_split_subtitle'),
|
||||
renderText(oldLang('Monetization.Intro.Split.Text'), ['simple_markdown'])],
|
||||
['cash-circle', oldLang('lng_channel_earn_learn_out_subtitle'),
|
||||
renderText(oldLang('lng_channel_earn_learn_out_about'), ['simple_markdown'])],
|
||||
] satisfies TableAboutData;
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<h3 className={styles.title}>{monetizationTitle}</h3>
|
||||
<p className={styles.description}>{blockchainText}</p>
|
||||
</>
|
||||
);
|
||||
|
||||
return {
|
||||
header,
|
||||
listItemData,
|
||||
footer,
|
||||
};
|
||||
}, [isOpen, oldLang, monetizationTitle, blockchainText]);
|
||||
|
||||
if (!modalData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableAboutModal
|
||||
isOpen={isOpen}
|
||||
listItemData={modalData.listItemData}
|
||||
headerIconName="cash-circle"
|
||||
header={modalData.header}
|
||||
footer={modalData.footer}
|
||||
buttonText={oldLang('RevenueSharingAdsUnderstood')}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AboutMonetizationModal);
|
||||
@ -66,6 +66,7 @@ interface StateProps {
|
||||
canCall?: boolean;
|
||||
canMute?: boolean;
|
||||
canViewStatistics?: boolean;
|
||||
canViewMonetization?: boolean;
|
||||
canViewBoosts?: boolean;
|
||||
canShowBoostModal?: boolean;
|
||||
canLeave?: boolean;
|
||||
@ -100,6 +101,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
canCall,
|
||||
canMute,
|
||||
canViewStatistics,
|
||||
canViewMonetization,
|
||||
canViewBoosts,
|
||||
canShowBoostModal,
|
||||
canLeave,
|
||||
@ -433,6 +435,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
canMute={canMute}
|
||||
canViewStatistics={canViewStatistics}
|
||||
canViewBoosts={canViewBoosts}
|
||||
canViewMonetization={canViewMonetization}
|
||||
canShowBoostModal={canShowBoostModal}
|
||||
canLeave={canLeave}
|
||||
canEnterVoiceChat={canEnterVoiceChat}
|
||||
@ -499,6 +502,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
|
||||
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
||||
const canViewStatistics = isMainThread && chatFullInfo?.canViewStatistics;
|
||||
const canViewMonetization = isMainThread && chatFullInfo?.canViewMonetization;
|
||||
const canViewBoosts = isMainThread
|
||||
&& (isSuperGroup || isChannel) && (canViewStatistics || getHasAdminRight(chat, 'postStories'));
|
||||
const canShowBoostModal = !canViewBoosts && (isSuperGroup || isChannel);
|
||||
@ -521,6 +525,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canCall,
|
||||
canMute,
|
||||
canViewStatistics,
|
||||
canViewMonetization,
|
||||
canViewBoosts,
|
||||
canShowBoostModal,
|
||||
canLeave,
|
||||
|
||||
@ -84,6 +84,7 @@ export type OwnProps = {
|
||||
canMute?: boolean;
|
||||
canViewStatistics?: boolean;
|
||||
canViewBoosts?: boolean;
|
||||
canViewMonetization?: boolean;
|
||||
canShowBoostModal?: boolean;
|
||||
withForumActions?: boolean;
|
||||
canLeave?: boolean;
|
||||
@ -145,6 +146,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
canCall,
|
||||
canMute,
|
||||
canViewStatistics,
|
||||
canViewMonetization,
|
||||
canViewBoosts,
|
||||
pendingJoinRequests,
|
||||
canLeave,
|
||||
@ -186,6 +188,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
openAddContactDialog,
|
||||
requestMasterAndRequestCall,
|
||||
toggleStatistics,
|
||||
openMonetizationStatistics,
|
||||
openBoostStatistics,
|
||||
openPremiumGiftModal,
|
||||
openThreadWithInfo,
|
||||
@ -348,6 +351,12 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
const handleMonetizationClick = useLastCallback(() => {
|
||||
openMonetizationStatistics({ chatId });
|
||||
setShouldCloseFast(!isRightColumnShown);
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
const handleBoostClick = useLastCallback(() => {
|
||||
if (canViewBoosts) {
|
||||
openBoostStatistics({ chatId });
|
||||
@ -618,6 +627,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
{lang('Statistics')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{isChannel && canViewMonetization && (
|
||||
<MenuItem
|
||||
icon="cash-circle"
|
||||
onClick={handleMonetizationClick}
|
||||
>
|
||||
{lang('lng_channel_earn_title')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canTranslate && (
|
||||
<MenuItem
|
||||
icon="language"
|
||||
|
||||
@ -115,7 +115,7 @@ type OwnProps = {
|
||||
onClosePoll?: NoneToVoidFunction;
|
||||
onShowSeenBy?: NoneToVoidFunction;
|
||||
onShowReactors?: NoneToVoidFunction;
|
||||
onAboutAds?: NoneToVoidFunction;
|
||||
onAboutAdsClick?: NoneToVoidFunction;
|
||||
onSponsoredHide?: NoneToVoidFunction;
|
||||
onSponsorInfo?: NoneToVoidFunction;
|
||||
onSponsoredReport?: NoneToVoidFunction;
|
||||
@ -205,7 +205,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
onShowReactors,
|
||||
onToggleReaction,
|
||||
onCopyMessages,
|
||||
onAboutAds,
|
||||
onAboutAdsClick,
|
||||
onSponsoredHide,
|
||||
onSponsorInfo,
|
||||
onSponsoredReport,
|
||||
@ -461,7 +461,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
<MenuItem icon="channel" onClick={onSponsorInfo}>{lang('SponsoredMessageSponsor')}</MenuItem>
|
||||
)}
|
||||
{isSponsoredMessage && (
|
||||
<MenuItem icon="info" onClick={onAboutAds}>
|
||||
<MenuItem icon="info" onClick={onAboutAdsClick}>
|
||||
{lang(message.canReport ? 'AboutRevenueSharingAds' : 'SponsoredMessageInfo')}
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@ -188,7 +188,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
isOpen={isContextMenuOpen}
|
||||
anchor={contextMenuPosition}
|
||||
message={message!}
|
||||
onAboutAds={openAboutAdsModal}
|
||||
onAboutAdsClick={openAboutAdsModal}
|
||||
onReportAd={handleReportSponsoredMessage}
|
||||
onClose={handleContextMenuClose}
|
||||
onCloseAnimationEnd={handleContextMenuHide}
|
||||
@ -196,7 +196,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
<AboutAdsModal
|
||||
isOpen={isAboutAdsModalOpen}
|
||||
isRevenueSharing={message.canReport}
|
||||
isMonetizationSharing={message.canReport}
|
||||
onClose={closeAboutAdsModal}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -17,7 +17,7 @@ export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
message: ApiSponsoredMessage;
|
||||
anchor: IAnchorPosition;
|
||||
onAboutAds: NoneToVoidFunction;
|
||||
onAboutAdsClick: NoneToVoidFunction;
|
||||
onReportAd: NoneToVoidFunction;
|
||||
onClose: NoneToVoidFunction;
|
||||
onCloseAnimationEnd: NoneToVoidFunction;
|
||||
@ -26,7 +26,7 @@ export type OwnProps = {
|
||||
const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
|
||||
message,
|
||||
anchor,
|
||||
onAboutAds,
|
||||
onAboutAdsClick,
|
||||
onReportAd,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
@ -37,7 +37,7 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
|
||||
const { transitionClassNames } = useShowTransition(isMenuOpen, onCloseAnimationEnd, undefined, false);
|
||||
|
||||
const handleAboutAdsOpen = useLastCallback(() => {
|
||||
onAboutAds();
|
||||
onAboutAdsClick();
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
@ -73,7 +73,7 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
|
||||
message={message}
|
||||
onClose={closeMenu}
|
||||
onCloseAnimationEnd={closeMenu}
|
||||
onAboutAds={handleAboutAdsOpen}
|
||||
onAboutAdsClick={handleAboutAdsOpen}
|
||||
onSponsoredHide={handleSponsoredHide}
|
||||
onSponsorInfo={handleSponsorInfo}
|
||||
onSponsoredReport={handleReportSponsoredMessage}
|
||||
|
||||
39
src/components/modals/common/TableAboutModal.module.scss
Normal file
39
src/components/modals/common/TableAboutModal.module.scss
Normal file
@ -0,0 +1,39 @@
|
||||
.root :global(.modal-dialog) {
|
||||
width: 26.25rem;
|
||||
}
|
||||
|
||||
.title, .description {
|
||||
text-align: center !important;
|
||||
text-wrap: pretty;
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.topIcon {
|
||||
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--premium-gradient);
|
||||
|
||||
font-size: 4rem;
|
||||
color: white;
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-block: 1rem;
|
||||
width: 110%;
|
||||
}
|
||||
68
src/components/modals/common/TableAboutModal.tsx
Normal file
68
src/components/modals/common/TableAboutModal.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React, { memo, type TeactNode } from '../../../lib/teact/teact';
|
||||
|
||||
import type { IconName } from '../../../types/icons';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Modal from '../../ui/Modal';
|
||||
import Separator from '../../ui/Separator';
|
||||
|
||||
import styles from './TableAboutModal.module.scss';
|
||||
|
||||
export type TableAboutData = [IconName | undefined, TeactNode, TeactNode][];
|
||||
|
||||
type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
listItemData?: TableAboutData;
|
||||
headerIconName: IconName;
|
||||
header?: TeactNode;
|
||||
footer?: TeactNode;
|
||||
buttonText?: string;
|
||||
onClose: NoneToVoidFunction;
|
||||
onButtonClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const TableAboutModal = ({
|
||||
isOpen,
|
||||
listItemData,
|
||||
headerIconName,
|
||||
header,
|
||||
footer,
|
||||
buttonText,
|
||||
onClose,
|
||||
onButtonClick,
|
||||
}: OwnProps) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
className={styles.root}
|
||||
contentClassName={styles.content}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.topIcon}><Icon name={headerIconName} /></div>
|
||||
{header}
|
||||
<div>
|
||||
{listItemData?.map(([icon, title, subtitle]) => {
|
||||
return (
|
||||
<ListItem
|
||||
isStatic
|
||||
multiline
|
||||
icon={icon}
|
||||
>
|
||||
<span className="title">{title}</span>
|
||||
<span className="subtitle">{subtitle}</span>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Separator className={styles.separator} />
|
||||
{footer}
|
||||
{buttonText && (
|
||||
<Button onClick={onButtonClick || onClose}>{buttonText}</Button>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TableAboutModal);
|
||||
@ -35,6 +35,7 @@ import Profile from './Profile';
|
||||
import RightHeader from './RightHeader';
|
||||
import BoostStatistics from './statistics/BoostStatistics';
|
||||
import MessageStatistics from './statistics/MessageStatistics.async';
|
||||
import MonetizationStatistics from './statistics/MonetizationStatistics';
|
||||
import Statistics from './statistics/Statistics.async';
|
||||
import StoryStatistics from './statistics/StoryStatistics.async';
|
||||
import StickerSearch from './StickerSearch.async';
|
||||
@ -102,6 +103,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
||||
closeEditTopicPanel,
|
||||
closeBoostStatistics,
|
||||
setShouldCloseRightColumn,
|
||||
closeMonetizationStatistics,
|
||||
} = getActions();
|
||||
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
@ -120,6 +122,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
||||
const isMessageStatistics = contentKey === RightColumnContent.MessageStatistics;
|
||||
const isStoryStatistics = contentKey === RightColumnContent.StoryStatistics;
|
||||
const isBoostStatistics = contentKey === RightColumnContent.BoostStatistics;
|
||||
const isMonetizationStatistics = contentKey === RightColumnContent.MonetizationStatistics;
|
||||
const isStickerSearch = contentKey === RightColumnContent.StickerSearch;
|
||||
const isGifSearch = contentKey === RightColumnContent.GifSearch;
|
||||
const isPollResults = contentKey === RightColumnContent.PollResults;
|
||||
@ -197,6 +200,9 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
||||
case RightColumnContent.BoostStatistics:
|
||||
closeBoostStatistics();
|
||||
break;
|
||||
case RightColumnContent.MonetizationStatistics:
|
||||
closeMonetizationStatistics();
|
||||
break;
|
||||
case RightColumnContent.StickerSearch:
|
||||
blurSearchInput();
|
||||
setStickerSearchQuery({ query: undefined });
|
||||
@ -329,6 +335,8 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
||||
return <Statistics chatId={chatId!} />;
|
||||
case RightColumnContent.BoostStatistics:
|
||||
return <BoostStatistics />;
|
||||
case RightColumnContent.MonetizationStatistics:
|
||||
return <MonetizationStatistics />;
|
||||
case RightColumnContent.MessageStatistics:
|
||||
return <MessageStatistics chatId={chatId!} isActive={isOpen && isActive} />;
|
||||
case RightColumnContent.StoryStatistics:
|
||||
@ -365,6 +373,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
||||
isManagement={isManagement}
|
||||
isStatistics={isStatistics}
|
||||
isBoostStatistics={isBoostStatistics}
|
||||
isMonetizationStatistics={isMonetizationStatistics}
|
||||
isMessageStatistics={isMessageStatistics}
|
||||
isStoryStatistics={isStoryStatistics}
|
||||
isStickerSearch={isStickerSearch}
|
||||
|
||||
@ -47,6 +47,7 @@ type OwnProps = {
|
||||
isStatistics?: boolean;
|
||||
isBoostStatistics?: boolean;
|
||||
isMessageStatistics?: boolean;
|
||||
isMonetizationStatistics?: boolean;
|
||||
isStoryStatistics?: boolean;
|
||||
isStickerSearch?: boolean;
|
||||
isGifSearch?: boolean;
|
||||
@ -91,6 +92,7 @@ enum HeaderContent {
|
||||
MessageStatistics,
|
||||
StoryStatistics,
|
||||
BoostStatistics,
|
||||
MonetizationStatistics,
|
||||
Management,
|
||||
ManageInitial,
|
||||
ManageChannelSubscribers,
|
||||
@ -130,6 +132,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
isStatistics,
|
||||
isMessageStatistics,
|
||||
isStoryStatistics,
|
||||
isMonetizationStatistics,
|
||||
isBoostStatistics,
|
||||
isStickerSearch,
|
||||
isGifSearch,
|
||||
@ -297,6 +300,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
HeaderContent.CreateTopic
|
||||
) : isEditingTopic ? (
|
||||
HeaderContent.EditTopic
|
||||
) : isMonetizationStatistics ? (
|
||||
HeaderContent.MonetizationStatistics
|
||||
) : undefined; // When column is closed
|
||||
|
||||
const renderingContentKey = useCurrentOrPrev(contentKey, true) ?? -1;
|
||||
@ -430,6 +435,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
return <h3 className="title">{lang('Stats.StoryTitle')}</h3>;
|
||||
case HeaderContent.BoostStatistics:
|
||||
return <h3 className="title">{lang('Boosts')}</h3>;
|
||||
case HeaderContent.MonetizationStatistics:
|
||||
return <h3 className="title">{lang('lng_channel_earn_title')}</h3>;
|
||||
case HeaderContent.SharedMedia:
|
||||
return <h3 className="title">{lang('SharedMedia')}</h3>;
|
||||
case HeaderContent.ManageChannelSubscribers:
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.graph {
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ready {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem 0.75rem;
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
}
|
||||
|
||||
.topText {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.availableReward {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.6875rem;
|
||||
}
|
||||
|
||||
.rewardValue {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.decimalPart {
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
|
||||
.integer {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.decimalUsdPart {
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
|
||||
.toncoinIcon {
|
||||
font-size: 1.5rem;
|
||||
margin-inline: 0 0.5rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.textBottom {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
237
src/components/right/statistics/MonetizationStatistics.tsx
Normal file
237
src/components/right/statistics/MonetizationStatistics.tsx
Normal file
@ -0,0 +1,237 @@
|
||||
import React, {
|
||||
memo, useEffect, useMemo, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChannelMonetizationStatistics, StatisticsGraph } from '../../../api/types';
|
||||
|
||||
import { FRAGMENT_ADS_URL } from '../../../config';
|
||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import AboutMonetizationModal from '../../common/AboutMonetizationModal.async';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import Button from '../../ui/Button';
|
||||
import Link from '../../ui/Link';
|
||||
import Loading from '../../ui/Loading';
|
||||
import StatisticsOverview from './StatisticsOverview';
|
||||
|
||||
import styles from './MonetizationStatistics.module.scss';
|
||||
|
||||
type ILovelyChart = { create: Function };
|
||||
let lovelyChartPromise: Promise<ILovelyChart>;
|
||||
let LovelyChart: ILovelyChart;
|
||||
|
||||
async function ensureLovelyChart() {
|
||||
if (!lovelyChartPromise) {
|
||||
lovelyChartPromise = import('../../../lib/lovely-chart/LovelyChart') as Promise<ILovelyChart>;
|
||||
LovelyChart = await lovelyChartPromise;
|
||||
}
|
||||
|
||||
return lovelyChartPromise;
|
||||
}
|
||||
|
||||
const MONETIZATION_GRAPHS_TITLES = {
|
||||
topHoursGraph: 'ChannelStats.Graph.ViewsByHours',
|
||||
revenueGraph: 'lng_channel_earn_chart_revenue',
|
||||
};
|
||||
const MONETIZATION_GRAPHS = Object.keys(MONETIZATION_GRAPHS_TITLES) as (keyof ApiChannelMonetizationStatistics)[];
|
||||
|
||||
type StateProps = {
|
||||
chatId: string;
|
||||
dcId?: number;
|
||||
statistics?: ApiChannelMonetizationStatistics;
|
||||
canCollect?: boolean;
|
||||
};
|
||||
|
||||
const MonetizationStatistics = ({
|
||||
chatId,
|
||||
dcId,
|
||||
statistics,
|
||||
canCollect,
|
||||
}: StateProps) => {
|
||||
const { loadChannelMonetizationStatistics } = getActions();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const loadedCharts = useRef<string[]>([]);
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false);
|
||||
const hasAvailableBalance = Boolean(statistics?.balances?.availableBalance !== 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatId) {
|
||||
loadChannelMonetizationStatistics({ chatId });
|
||||
}
|
||||
}, [chatId, loadChannelMonetizationStatistics]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await ensureLovelyChart();
|
||||
|
||||
if (!isReady) {
|
||||
setIsReady(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!statistics || !containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
MONETIZATION_GRAPHS.forEach((name, index: number) => {
|
||||
const graph = statistics[name as keyof typeof statistics];
|
||||
const isAsync = typeof graph === 'string';
|
||||
|
||||
if (isAsync || loadedCharts.current.includes(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!graph) {
|
||||
loadedCharts.current.push(name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LovelyChart.create(containerRef.current!.children[index], {
|
||||
title: oldLang((MONETIZATION_GRAPHS_TITLES as Record<string, string>)[name]),
|
||||
...graph as StatisticsGraph,
|
||||
});
|
||||
|
||||
loadedCharts.current.push(name);
|
||||
|
||||
containerRef.current!.children[index].classList.remove(styles.hidden);
|
||||
});
|
||||
|
||||
forceUpdate();
|
||||
})();
|
||||
}, [isReady, statistics, oldLang, chatId, dcId, forceUpdate]);
|
||||
|
||||
function renderAvailableReward() {
|
||||
const availableBalance = statistics?.balances?.availableBalance;
|
||||
const [integerTonPart, decimalTonPart] = availableBalance ? availableBalance.toFixed(4).split('.') : [0];
|
||||
const [integerUsdPart, decimalUsdPart] = availableBalance
|
||||
&& statistics?.usdRate ? (availableBalance * statistics.usdRate).toFixed(2).split('.') : [0];
|
||||
|
||||
return (
|
||||
<div className={styles.availableReward}>
|
||||
<div className={styles.toncoin}>
|
||||
<Icon className={styles.toncoinIcon} name="toncoin" />
|
||||
<b className={styles.rewardValue}>
|
||||
{integerTonPart}<span className={styles.decimalPart}>.{decimalTonPart}</span>
|
||||
</b>
|
||||
</div>
|
||||
{' '}
|
||||
<span className={styles.integer}>
|
||||
≈ ${integerUsdPart}<span className={styles.decimalUsdPart}>.{decimalUsdPart}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const topText = useMemo(() => {
|
||||
const linkText = oldLang('LearnMore');
|
||||
return lang(
|
||||
'ChannelEarnAbout',
|
||||
{
|
||||
link: (
|
||||
<Link isPrimary onClick={openAboutMonetizationModal}>
|
||||
{linkText}
|
||||
<Icon name="next" />
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
withNodes: true,
|
||||
},
|
||||
);
|
||||
}, [lang, oldLang]);
|
||||
|
||||
const rewardsText = useMemo(() => {
|
||||
const linkText = oldLang('LearnMore');
|
||||
return lang(
|
||||
'MonetizationBalanceZeroInfo',
|
||||
{
|
||||
link: (
|
||||
<SafeLink url={oldLang('MonetizationProceedsInfoLink')} text={linkText}>
|
||||
{linkText}
|
||||
<Icon name="next" />
|
||||
</SafeLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
withNodes: true,
|
||||
},
|
||||
);
|
||||
}, [lang, oldLang]);
|
||||
|
||||
if (!isReady || !statistics) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}>
|
||||
<div className={buildClassName(styles.section, styles.topText)}>{topText}</div>
|
||||
|
||||
<StatisticsOverview
|
||||
statistics={statistics}
|
||||
isToncoin
|
||||
type="monetization"
|
||||
title={oldLang('MonetizationOverview')}
|
||||
/>
|
||||
|
||||
{!loadedCharts.current.length && <Loading />}
|
||||
|
||||
<div ref={containerRef} className={styles.section}>
|
||||
{MONETIZATION_GRAPHS.map((graph) => (
|
||||
<div key={graph} className={buildClassName(styles.graph, styles.hidden)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{hasAvailableBalance && (
|
||||
<div className={styles.section}>
|
||||
{oldLang('lng_channel_earn_balance_title')}
|
||||
|
||||
{renderAvailableReward()}
|
||||
|
||||
<Button size="smaller" type="button" href={FRAGMENT_ADS_URL} disabled={canCollect && hasAvailableBalance}>
|
||||
{oldLang('MonetizationWithdraw')}
|
||||
</Button>
|
||||
|
||||
<div className={styles.textBottom}>{rewardsText}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AboutMonetizationModal
|
||||
isOpen={isAboutMonetizationModalOpen}
|
||||
onClose={closeAboutMonetizationModal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => {
|
||||
const tabState = selectTabState(global);
|
||||
const monetizationStatistics = tabState.monetizationStatistics;
|
||||
const chatId = monetizationStatistics && monetizationStatistics.chatId;
|
||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||
const canCollect = chat && chat.isCreator;
|
||||
const dcId = selectChatFullInfo(global, chatId!)?.statisticsDcId;
|
||||
const statistics = tabState.statistics.monetization;
|
||||
|
||||
return {
|
||||
chatId: chatId!,
|
||||
dcId,
|
||||
statistics,
|
||||
canCollect,
|
||||
};
|
||||
},
|
||||
)(MonetizationStatistics));
|
||||
@ -1,7 +1,7 @@
|
||||
.root {
|
||||
padding: 1rem 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--color-borders);
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -13,10 +13,10 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-right: 2em;
|
||||
font-size: 16px;
|
||||
margin-right: 2rem;
|
||||
font-size: 1rem;
|
||||
color: var(--text-color);
|
||||
line-height: 30px;
|
||||
line-height: 1.875rem;
|
||||
text-transform: lowercase;
|
||||
|
||||
&:first-letter {
|
||||
@ -61,3 +61,16 @@
|
||||
color: var(--color-error);
|
||||
}
|
||||
}
|
||||
|
||||
.decimalPart {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.decimalUsdPart {
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
|
||||
.toncoin {
|
||||
margin-inline: -0.125rem 0.3125rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import type {
|
||||
ApiBoostStatistics,
|
||||
ApiBoostStatistics, ApiChannelMonetizationStatistics,
|
||||
ApiChannelStatistics, ApiGroupStatistics, ApiPostStatistics, StatisticsOverviewItem,
|
||||
} from '../../../api/types';
|
||||
|
||||
@ -12,6 +12,8 @@ import { formatInteger, formatIntegerCompact } from '../../../util/textFormat';
|
||||
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
|
||||
import styles from './StatisticsOverview.module.scss';
|
||||
|
||||
type OverviewCell = {
|
||||
@ -94,19 +96,26 @@ const BOOST_OVERVIEW: OverviewCell[][] = [
|
||||
],
|
||||
];
|
||||
|
||||
type StatisticsType = 'channel' | 'group' | 'message' | 'boost' | 'story';
|
||||
type StatisticsType = 'channel' | 'group' | 'message' | 'boost' | 'story' | 'monetization';
|
||||
|
||||
export type OwnProps = {
|
||||
type: StatisticsType;
|
||||
title?: string;
|
||||
className?: string;
|
||||
statistics: ApiChannelStatistics | ApiGroupStatistics | ApiPostStatistics | ApiBoostStatistics;
|
||||
isToncoin?: boolean;
|
||||
statistics:
|
||||
ApiChannelStatistics |
|
||||
ApiGroupStatistics |
|
||||
ApiPostStatistics |
|
||||
ApiBoostStatistics |
|
||||
ApiChannelMonetizationStatistics;
|
||||
};
|
||||
|
||||
const StatisticsOverview: FC<OwnProps> = ({
|
||||
title,
|
||||
type,
|
||||
statistics,
|
||||
isToncoin,
|
||||
className,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
@ -131,7 +140,26 @@ const StatisticsOverview: FC<OwnProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderBalanceCell = (balance: number, usdRate: number, text: string) => {
|
||||
const [integerTonPart, decimalTonPart] = balance.toFixed(4).split('.');
|
||||
const [integerUsdPart, decimalUsdPart] = (balance * usdRate).toFixed(2).split('.');
|
||||
return (
|
||||
<div>
|
||||
<Icon className={styles.toncoin} name="toncoin" />
|
||||
<b className={styles.tableValue}>
|
||||
{integerTonPart}<span className={styles.decimalPart}>.{decimalTonPart}</span>
|
||||
</b>
|
||||
{' '}
|
||||
<span className={styles.tableHeading}>
|
||||
≈ ${integerUsdPart}<span className={styles.decimalUsdPart}>.{decimalUsdPart}</span>
|
||||
</span>
|
||||
<h3 className={styles.tableHeading}>{lang(text)}</h3>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { period } = (statistics as ApiGroupStatistics);
|
||||
const { balances, usdRate } = (statistics as ApiChannelMonetizationStatistics);
|
||||
|
||||
const schema = getSchemaByType(type);
|
||||
|
||||
@ -152,7 +180,15 @@ const StatisticsOverview: FC<OwnProps> = ({
|
||||
</div>
|
||||
|
||||
<table className={styles.table}>
|
||||
{schema.map((row) => (
|
||||
{isToncoin ? (
|
||||
<tr>
|
||||
<td className={styles.tableCell}>
|
||||
{renderBalanceCell(balances?.availableBalance || 0, usdRate || 0, 'lng_channel_earn_available')}
|
||||
{renderBalanceCell(balances?.currentBalance || 0, usdRate || 0, 'lng_channel_earn_reward')}
|
||||
{renderBalanceCell(balances?.overallRevenue || 0, usdRate || 0, 'lng_channel_earn_total')}
|
||||
</td>
|
||||
</tr>
|
||||
) : schema.map((row) => (
|
||||
<tr>
|
||||
{row.map((cell: OverviewCell) => {
|
||||
const field = (statistics as any)[cell.name];
|
||||
|
||||
@ -179,6 +179,8 @@ const Button: FC<OwnProps> = ({
|
||||
aria-controls={ariaControls}
|
||||
style={style}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{children}
|
||||
{!isNotInteractive && ripple && (
|
||||
|
||||
@ -319,6 +319,7 @@ export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
|
||||
export const FAQ_URL = 'https://telegram.org/faq';
|
||||
export const PRIVACY_URL = 'https://telegram.org/privacy';
|
||||
export const MINI_APP_TOS_URL = 'https://telegram.org/tos/mini-apps';
|
||||
export const FRAGMENT_ADS_URL = 'https://fragment.com/ads';
|
||||
export const GENERAL_TOPIC_ID = 1;
|
||||
export const STORY_EXPIRE_PERIOD = 86400; // 1 day
|
||||
export const STORY_VIEWERS_EXPIRE_PERIOD = 86400; // 1 day
|
||||
|
||||
@ -761,6 +761,20 @@ addActionHandler('openBoostStatistics', async (global, actions, payload): Promis
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openMonetizationStatistics', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
global = updateTabState(global, {
|
||||
monetizationStatistics: {
|
||||
chatId,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<void> => {
|
||||
const { isGifts, tabId = getCurrentTabId() } = payload || {};
|
||||
let tabState = selectTabState(global, tabId);
|
||||
|
||||
@ -6,6 +6,7 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import {
|
||||
addChats,
|
||||
addUsers,
|
||||
updateChannelMonetizationStatistics,
|
||||
updateMessageStatistics,
|
||||
updateStatistics,
|
||||
updateStatisticsGraph,
|
||||
@ -43,6 +44,28 @@ addActionHandler('loadStatistics', async (global, actions, payload): Promise<voi
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadChannelMonetizationStatistics', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chatId, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
const fullInfo = selectChatFullInfo(global, chatId);
|
||||
if (!chat || !fullInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dcId = fullInfo.statisticsDcId;
|
||||
const stats = await callApi('fetchChannelMonetizationStatistics', { chat, dcId });
|
||||
|
||||
if (!stats) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = updateChannelMonetizationStatistics(global, stats, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadMessageStatistics', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
@ -430,3 +430,11 @@ addActionHandler('closeBoostStatistics', (global, actions, payload): ActionRetur
|
||||
boostStatistics: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeMonetizationStatistics', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
monetizationStatistics: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ApiChannelMonetizationStatistics,
|
||||
ApiChannelStatistics, ApiGroupStatistics, ApiPostStatistics, StatisticsGraph,
|
||||
} from '../../api/types';
|
||||
import type { GlobalState, TabArgs } from '../types';
|
||||
@ -65,3 +66,15 @@ export function updateStatisticsGraph<T extends GlobalState>(
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
export function updateChannelMonetizationStatistics<T extends GlobalState>(
|
||||
global: T, statistics: ApiChannelMonetizationStatistics,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
return updateTabState(global, {
|
||||
statistics: {
|
||||
...selectTabState(global, tabId).statistics,
|
||||
monetization: statistics,
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
@ -47,6 +47,8 @@ export function selectRightColumnContentKey<T extends GlobalState>(
|
||||
RightColumnContent.Statistics
|
||||
) : tabState.boostStatistics ? (
|
||||
RightColumnContent.BoostStatistics
|
||||
) : tabState.monetizationStatistics ? (
|
||||
RightColumnContent.MonetizationStatistics
|
||||
) : tabState.stickerSearch.query !== undefined ? (
|
||||
RightColumnContent.StickerSearch
|
||||
) : tabState.gifSearch.query !== undefined ? (
|
||||
|
||||
@ -6,6 +6,7 @@ import type {
|
||||
ApiAvailableReaction,
|
||||
ApiBoost,
|
||||
ApiBoostsStatus,
|
||||
ApiChannelMonetizationStatistics,
|
||||
ApiChannelStatistics,
|
||||
ApiChat,
|
||||
ApiChatAdminRights,
|
||||
@ -603,6 +604,7 @@ export type TabState = {
|
||||
currentMessageId?: number;
|
||||
currentStory?: ApiPostStatistics;
|
||||
currentStoryId?: number;
|
||||
monetization?: ApiChannelMonetizationStatistics;
|
||||
};
|
||||
|
||||
newContact?: {
|
||||
@ -790,6 +792,10 @@ export type TabState = {
|
||||
};
|
||||
};
|
||||
|
||||
monetizationStatistics?: {
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
giftCodeModal?: {
|
||||
slug: string;
|
||||
message?: {
|
||||
@ -1800,6 +1806,9 @@ export interface ActionPayloads {
|
||||
name: string;
|
||||
isPercentage?: boolean;
|
||||
} & WithTabId;
|
||||
loadChannelMonetizationStatistics: {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
|
||||
// ui
|
||||
dismissDialog: WithTabId | undefined;
|
||||
@ -2533,6 +2542,11 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
|
||||
openMonetizationStatistics: {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
closeMonetizationStatistics: WithTabId | undefined;
|
||||
|
||||
// Media Viewer & Audio Player
|
||||
openMediaViewer: {
|
||||
chatId?: string;
|
||||
|
||||
@ -1665,6 +1665,7 @@ stats.getMessagePublicForwards#5f150144 channel:InputChannel msg_id:int offset:s
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
stats.getStoryStats#374fef40 flags:# dark:flags.0?true peer:InputPeer id:int = stats.StoryStats;
|
||||
stats.getStoryPublicForwards#a6437ef6 peer:InputPeer id:int offset:string limit:int = stats.PublicForwards;
|
||||
stats.getBroadcastRevenueStats#75dfb671 flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastRevenueStats;
|
||||
chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector<InputPeer> = chatlists.ExportedChatlistInvite;
|
||||
chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool;
|
||||
chatlists.editExportedInvite#653db63d flags:# chatlist:InputChatlist slug:string title:flags.1?string peers:flags.2?Vector<InputPeer> = ExportedChatlistInvite;
|
||||
|
||||
@ -271,6 +271,7 @@
|
||||
"help.getPeerColors",
|
||||
"help.getTimezonesList",
|
||||
"stats.getBroadcastStats",
|
||||
"stats.getBroadcastRevenueStats",
|
||||
"stats.getMegagroupStats",
|
||||
"stats.getMessagePublicForwards",
|
||||
"stats.getMessageStats",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { GUTTER, AXES_FONT, X_AXIS_HEIGHT, X_AXIS_SHIFT_START, PLOT_TOP_PADDING } from './constants';
|
||||
import { humanize } from './format';
|
||||
import { formatCryptoValue, humanize } from './format';
|
||||
import { getCssColor } from './skin';
|
||||
import { applyXEdgeOpacity, applyYEdgeOpacity, xScaleLevelToStep, yScaleLevelToStep } from './formulas';
|
||||
import { toPixels } from './Projection';
|
||||
@ -47,6 +47,8 @@ export function createAxes(context, data, plotSize, colors) {
|
||||
|
||||
if (data.isPercentage) {
|
||||
_drawYAxisPercents(projection);
|
||||
} else if (data.isCurrency) {
|
||||
_drawYAxisCurrency(projection, data);
|
||||
} else {
|
||||
_drawYAxisScaled(
|
||||
state,
|
||||
@ -169,5 +171,49 @@ export function createAxes(context, data, plotSize, colors) {
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
function _drawYAxisCurrency(projection, data) {
|
||||
const formatValue = data.datasets[0].values.map(value => formatCryptoValue(value));
|
||||
|
||||
const total = formatValue.reduce((sum, value) => sum + value, 0);
|
||||
const avg1 = total / formatValue.length;
|
||||
const avg2 = total / (formatValue.length / 2);
|
||||
const avg3 = total / (formatValue.length / 3);
|
||||
|
||||
const averageRate1 = avg1 * data.currencyRate;
|
||||
const averageRate2 = avg2 * data.currencyRate;
|
||||
const averageRate3 = avg3 * data.currencyRate;
|
||||
|
||||
const totalAvg = [0, avg1, avg2, avg3];
|
||||
const totalRate = [0, averageRate1, averageRate2, averageRate3];
|
||||
|
||||
const [, height] = projection.getSize();
|
||||
|
||||
context.font = AXES_FONT;
|
||||
context.textAlign = 'left';
|
||||
context.textBaseline = 'bottom';
|
||||
context.lineWidth = 1;
|
||||
|
||||
context.beginPath();
|
||||
|
||||
totalAvg.forEach((value, index) => {
|
||||
const yPx = height - height * (value / Math.max(...formatValue)) + PLOT_TOP_PADDING;
|
||||
|
||||
context.fillStyle = getCssColor(colors, 'y-axis-text', 1);
|
||||
|
||||
context.fillText(`${value.toFixed(2)} TON`, GUTTER, yPx - GUTTER / 4);
|
||||
|
||||
context.textAlign = 'right';
|
||||
context.fillText(`$${totalRate[index].toFixed(2)}`, plotSize.width - GUTTER, yPx - GUTTER / 4);
|
||||
|
||||
context.textAlign = 'left';
|
||||
|
||||
context.moveTo(GUTTER, yPx);
|
||||
context.strokeStyle = getCssColor(colors, 'grid-lines', 1);
|
||||
context.lineTo(plotSize.width - GUTTER, yPx);
|
||||
});
|
||||
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
return { drawXAxis, drawYAxis };
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { setupCanvas, clearCanvas } from './canvas';
|
||||
import { BALLOON_OFFSET, X_AXIS_HEIGHT } from './constants';
|
||||
import { getPieRadius } from './formulas';
|
||||
import { formatInteger, getLabelDate, getLabelTime, statsFormatDayHourFull } from './format';
|
||||
import {formatCryptoValue, formatInteger, getLabelDate, getLabelTime, statsFormatDayHourFull} from './format';
|
||||
import { getCssColor } from './skin';
|
||||
import { throttle, throttleWithRaf } from './utils';
|
||||
import { addEventListener, createElement } from './minifiers';
|
||||
@ -353,7 +353,12 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
||||
currentDataSet.setAttribute('data-present', 'true');
|
||||
|
||||
const valueElement = currentDataSet.querySelector(`.lovely-chart--tooltip-dataset-value.lovely-chart--color-${data.colors[key].slice(1)}:not(.lovely-chart--state-hidden)`);
|
||||
valueElement.innerHTML = formatInteger(value);
|
||||
|
||||
if (data.isCurrency) {
|
||||
valueElement.innerHTML = formatCryptoValue(value);
|
||||
} else {
|
||||
valueElement.innerHTML = formatInteger(value);
|
||||
}
|
||||
|
||||
_renderPercentageValue(currentDataSet, value, totalValue);
|
||||
}
|
||||
@ -413,6 +418,10 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
||||
_renderTotal(dataSetContainer, formatInteger(totalValue));
|
||||
}
|
||||
|
||||
if (data.isCurrency) {
|
||||
_renderCurrencyRate(dataSetContainer, formatCryptoValue(totalValue));
|
||||
}
|
||||
|
||||
Array.from(dataSetContainer.querySelectorAll('[data-present="false"]'))
|
||||
.forEach((dataSet) => {
|
||||
dataSet.remove();
|
||||
@ -442,6 +451,28 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
||||
}
|
||||
}
|
||||
|
||||
function _renderCurrencyRate(dataSetContainer, totalValue) {
|
||||
const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
|
||||
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;
|
||||
|
||||
const totalUsd = (parseFloat(totalValue) * data.currencyRate).toFixed(2);
|
||||
|
||||
if (!totalText) {
|
||||
const newTotalText = createElement();
|
||||
newTotalText.className = 'lovely-chart--tooltip-dataset';
|
||||
newTotalText.setAttribute('data-present', 'true');
|
||||
newTotalText.setAttribute('data-total', 'true');
|
||||
newTotalText.innerHTML = `<span>USD ≈</span><span class="${className}">$${totalUsd}</span>`;
|
||||
dataSetContainer.appendChild(newTotalText);
|
||||
} else {
|
||||
totalText.setAttribute('data-present', 'true');
|
||||
|
||||
const valueElement = totalText.querySelector(`.lovely-chart--tooltip-dataset-value:not(.lovely-chart--state-hidden)`);
|
||||
valueElement.innerHTML = `$${totalUsd}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _hideBalloon() {
|
||||
_balloon.classList.remove('lovely-chart--state-shown');
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { getMaxMin } from './utils';
|
||||
import { statsFormatHour, statsFormatDay, statsFormatDayHour, statsFormatText, statsFormatMin } from './format';
|
||||
import { statsFormatDay, statsFormatDayHour, statsFormatText, statsFormatMin } from './format';
|
||||
|
||||
export function analyzeData(data) {
|
||||
const { title, labelFormatter, tooltipFormatter, isStacked, isPercentage, hasSecondYAxis, onZoom, minimapRange, hideCaption, zoomOutLabel } = data;
|
||||
const { title, labelFormatter, tooltipFormatter, isStacked, isPercentage, isCurrency, currencyRate, hasSecondYAxis, onZoom, minimapRange, hideCaption, zoomOutLabel } = data;
|
||||
const { datasets, labels } = prepareDatasets(data);
|
||||
|
||||
const colors = {};
|
||||
@ -20,8 +20,13 @@ export function analyzeData(data) {
|
||||
}
|
||||
});
|
||||
|
||||
let effectiveLabelFormatter = labelFormatter;
|
||||
if (isCurrency) {
|
||||
effectiveLabelFormatter = 'statsFormat(\'day\')';
|
||||
}
|
||||
|
||||
let xLabels;
|
||||
switch (labelFormatter) {
|
||||
switch (effectiveLabelFormatter) {
|
||||
case 'statsFormatDayHour':
|
||||
xLabels = statsFormatDayHour(labels);
|
||||
break;
|
||||
@ -45,6 +50,8 @@ export function analyzeData(data) {
|
||||
datasets,
|
||||
isStacked,
|
||||
isPercentage,
|
||||
isCurrency,
|
||||
currencyRate,
|
||||
hasSecondYAxis,
|
||||
onZoom,
|
||||
isLines: data.type === 'line',
|
||||
|
||||
@ -62,6 +62,10 @@ export function formatInteger(n) {
|
||||
return String(n).replace(/\d(?=(\d{3})+$)/g, '$& ');
|
||||
}
|
||||
|
||||
export function formatCryptoValue(n) {
|
||||
return Number(n / 10 ** 9);
|
||||
}
|
||||
|
||||
export function getFullLabelDate(label, { isShort = false } = {}) {
|
||||
return getLabelDate(label, { isShort, displayWeekDay: true });
|
||||
}
|
||||
|
||||
@ -67,208 +67,210 @@ $icons-map: (
|
||||
"camera": "\f124",
|
||||
"car": "\f125",
|
||||
"card": "\f126",
|
||||
"channel-filled": "\f127",
|
||||
"channel": "\f128",
|
||||
"channelviews": "\f129",
|
||||
"chat-badge": "\f12a",
|
||||
"chats-badge": "\f12b",
|
||||
"check": "\f12c",
|
||||
"clock": "\f12d",
|
||||
"close-circle": "\f12e",
|
||||
"close-topic": "\f12f",
|
||||
"close": "\f130",
|
||||
"cloud-download": "\f131",
|
||||
"collapse": "\f132",
|
||||
"colorize": "\f133",
|
||||
"comments-sticker": "\f134",
|
||||
"comments": "\f135",
|
||||
"copy-media": "\f136",
|
||||
"copy": "\f137",
|
||||
"darkmode": "\f138",
|
||||
"data": "\f139",
|
||||
"delete-filled": "\f13a",
|
||||
"delete-left": "\f13b",
|
||||
"delete-user": "\f13c",
|
||||
"delete": "\f13d",
|
||||
"document": "\f13e",
|
||||
"double-badge": "\f13f",
|
||||
"down": "\f140",
|
||||
"download": "\f141",
|
||||
"eats": "\f142",
|
||||
"edit": "\f143",
|
||||
"email": "\f144",
|
||||
"enter": "\f145",
|
||||
"expand": "\f146",
|
||||
"eye-closed-outline": "\f147",
|
||||
"eye-closed": "\f148",
|
||||
"eye-outline": "\f149",
|
||||
"eye": "\f14a",
|
||||
"favorite-filled": "\f14b",
|
||||
"favorite": "\f14c",
|
||||
"file-badge": "\f14d",
|
||||
"flag": "\f14e",
|
||||
"folder-badge": "\f14f",
|
||||
"folder": "\f150",
|
||||
"fontsize": "\f151",
|
||||
"forums": "\f152",
|
||||
"forward": "\f153",
|
||||
"fullscreen": "\f154",
|
||||
"gifs": "\f155",
|
||||
"gift": "\f156",
|
||||
"group-filled": "\f157",
|
||||
"group": "\f158",
|
||||
"grouped-disable": "\f159",
|
||||
"grouped": "\f15a",
|
||||
"hand-stop": "\f15b",
|
||||
"hashtag": "\f15c",
|
||||
"heart-outline": "\f15d",
|
||||
"heart": "\f15e",
|
||||
"help": "\f15f",
|
||||
"info-filled": "\f160",
|
||||
"info": "\f161",
|
||||
"install": "\f162",
|
||||
"italic": "\f163",
|
||||
"key": "\f164",
|
||||
"keyboard": "\f165",
|
||||
"lamp": "\f166",
|
||||
"language": "\f167",
|
||||
"large-pause": "\f168",
|
||||
"large-play": "\f169",
|
||||
"link-badge": "\f16a",
|
||||
"link-broken": "\f16b",
|
||||
"link": "\f16c",
|
||||
"location": "\f16d",
|
||||
"lock-badge": "\f16e",
|
||||
"lock": "\f16f",
|
||||
"logout": "\f170",
|
||||
"loop": "\f171",
|
||||
"mention": "\f172",
|
||||
"message-failed": "\f173",
|
||||
"message-pending": "\f174",
|
||||
"message-read": "\f175",
|
||||
"message-succeeded": "\f176",
|
||||
"message": "\f177",
|
||||
"microphone-alt": "\f178",
|
||||
"microphone": "\f179",
|
||||
"monospace": "\f17a",
|
||||
"more-circle": "\f17b",
|
||||
"more": "\f17c",
|
||||
"move-caption-down": "\f17d",
|
||||
"move-caption-up": "\f17e",
|
||||
"mute": "\f17f",
|
||||
"muted": "\f180",
|
||||
"my-notes": "\f181",
|
||||
"new-chat-filled": "\f182",
|
||||
"next": "\f183",
|
||||
"nochannel": "\f184",
|
||||
"noise-suppression": "\f185",
|
||||
"non-contacts": "\f186",
|
||||
"one-filled": "\f187",
|
||||
"open-in-new-tab": "\f188",
|
||||
"password-off": "\f189",
|
||||
"pause": "\f18a",
|
||||
"permissions": "\f18b",
|
||||
"phone-discard-outline": "\f18c",
|
||||
"phone-discard": "\f18d",
|
||||
"phone": "\f18e",
|
||||
"photo": "\f18f",
|
||||
"pin-badge": "\f190",
|
||||
"pin-list": "\f191",
|
||||
"pin": "\f192",
|
||||
"pinned-chat": "\f193",
|
||||
"pinned-message": "\f194",
|
||||
"pip": "\f195",
|
||||
"play-story": "\f196",
|
||||
"play": "\f197",
|
||||
"poll": "\f198",
|
||||
"previous": "\f199",
|
||||
"privacy-policy": "\f19a",
|
||||
"quote-text": "\f19b",
|
||||
"quote": "\f19c",
|
||||
"readchats": "\f19d",
|
||||
"recent": "\f19e",
|
||||
"reload": "\f19f",
|
||||
"remove-quote": "\f1a0",
|
||||
"remove": "\f1a1",
|
||||
"reopen-topic": "\f1a2",
|
||||
"replace": "\f1a3",
|
||||
"replies": "\f1a4",
|
||||
"reply-filled": "\f1a5",
|
||||
"reply": "\f1a6",
|
||||
"revenue-split": "\f1a7",
|
||||
"revote": "\f1a8",
|
||||
"save-story": "\f1a9",
|
||||
"saved-messages": "\f1aa",
|
||||
"schedule": "\f1ab",
|
||||
"search": "\f1ac",
|
||||
"select": "\f1ad",
|
||||
"send-outline": "\f1ae",
|
||||
"send": "\f1af",
|
||||
"settings-filled": "\f1b0",
|
||||
"settings": "\f1b1",
|
||||
"share-filled": "\f1b2",
|
||||
"share-screen-outlined": "\f1b3",
|
||||
"share-screen-stop": "\f1b4",
|
||||
"share-screen": "\f1b5",
|
||||
"show-message": "\f1b6",
|
||||
"sidebar": "\f1b7",
|
||||
"skip-next": "\f1b8",
|
||||
"skip-previous": "\f1b9",
|
||||
"smallscreen": "\f1ba",
|
||||
"smile": "\f1bb",
|
||||
"sort": "\f1bc",
|
||||
"speaker-muted-story": "\f1bd",
|
||||
"speaker-outline": "\f1be",
|
||||
"speaker-story": "\f1bf",
|
||||
"speaker": "\f1c0",
|
||||
"spoiler-disable": "\f1c1",
|
||||
"spoiler": "\f1c2",
|
||||
"sport": "\f1c3",
|
||||
"star": "\f1c4",
|
||||
"stars-lock": "\f1c5",
|
||||
"stats": "\f1c6",
|
||||
"stealth-future": "\f1c7",
|
||||
"stealth-past": "\f1c8",
|
||||
"stickers": "\f1c9",
|
||||
"stop-raising-hand": "\f1ca",
|
||||
"stop": "\f1cb",
|
||||
"story-caption": "\f1cc",
|
||||
"story-expired": "\f1cd",
|
||||
"story-priority": "\f1ce",
|
||||
"story-reply": "\f1cf",
|
||||
"strikethrough": "\f1d0",
|
||||
"tag-add": "\f1d1",
|
||||
"tag-crossed": "\f1d2",
|
||||
"tag-filter": "\f1d3",
|
||||
"tag-name": "\f1d4",
|
||||
"tag": "\f1d5",
|
||||
"timer": "\f1d6",
|
||||
"transcribe": "\f1d7",
|
||||
"truck": "\f1d8",
|
||||
"unarchive": "\f1d9",
|
||||
"underlined": "\f1da",
|
||||
"unlock-badge": "\f1db",
|
||||
"unlock": "\f1dc",
|
||||
"unmute": "\f1dd",
|
||||
"unpin": "\f1de",
|
||||
"unread": "\f1df",
|
||||
"up": "\f1e0",
|
||||
"user-filled": "\f1e1",
|
||||
"user-online": "\f1e2",
|
||||
"user": "\f1e3",
|
||||
"video-outlined": "\f1e4",
|
||||
"video-stop": "\f1e5",
|
||||
"video": "\f1e6",
|
||||
"view-once": "\f1e7",
|
||||
"voice-chat": "\f1e8",
|
||||
"volume-1": "\f1e9",
|
||||
"volume-2": "\f1ea",
|
||||
"volume-3": "\f1eb",
|
||||
"web": "\f1ec",
|
||||
"webapp": "\f1ed",
|
||||
"word-wrap": "\f1ee",
|
||||
"zoom-in": "\f1ef",
|
||||
"zoom-out": "\f1f0",
|
||||
"cash-circle": "\f127",
|
||||
"channel-filled": "\f128",
|
||||
"channel": "\f129",
|
||||
"channelviews": "\f12a",
|
||||
"chat-badge": "\f12b",
|
||||
"chats-badge": "\f12c",
|
||||
"check": "\f12d",
|
||||
"clock": "\f12e",
|
||||
"close-circle": "\f12f",
|
||||
"close-topic": "\f130",
|
||||
"close": "\f131",
|
||||
"cloud-download": "\f132",
|
||||
"collapse": "\f133",
|
||||
"colorize": "\f134",
|
||||
"comments-sticker": "\f135",
|
||||
"comments": "\f136",
|
||||
"copy-media": "\f137",
|
||||
"copy": "\f138",
|
||||
"darkmode": "\f139",
|
||||
"data": "\f13a",
|
||||
"delete-filled": "\f13b",
|
||||
"delete-left": "\f13c",
|
||||
"delete-user": "\f13d",
|
||||
"delete": "\f13e",
|
||||
"document": "\f13f",
|
||||
"double-badge": "\f140",
|
||||
"down": "\f141",
|
||||
"download": "\f142",
|
||||
"eats": "\f143",
|
||||
"edit": "\f144",
|
||||
"email": "\f145",
|
||||
"enter": "\f146",
|
||||
"expand": "\f147",
|
||||
"eye-closed-outline": "\f148",
|
||||
"eye-closed": "\f149",
|
||||
"eye-outline": "\f14a",
|
||||
"eye": "\f14b",
|
||||
"favorite-filled": "\f14c",
|
||||
"favorite": "\f14d",
|
||||
"file-badge": "\f14e",
|
||||
"flag": "\f14f",
|
||||
"folder-badge": "\f150",
|
||||
"folder": "\f151",
|
||||
"fontsize": "\f152",
|
||||
"forums": "\f153",
|
||||
"forward": "\f154",
|
||||
"fullscreen": "\f155",
|
||||
"gifs": "\f156",
|
||||
"gift": "\f157",
|
||||
"group-filled": "\f158",
|
||||
"group": "\f159",
|
||||
"grouped-disable": "\f15a",
|
||||
"grouped": "\f15b",
|
||||
"hand-stop": "\f15c",
|
||||
"hashtag": "\f15d",
|
||||
"heart-outline": "\f15e",
|
||||
"heart": "\f15f",
|
||||
"help": "\f160",
|
||||
"info-filled": "\f161",
|
||||
"info": "\f162",
|
||||
"install": "\f163",
|
||||
"italic": "\f164",
|
||||
"key": "\f165",
|
||||
"keyboard": "\f166",
|
||||
"lamp": "\f167",
|
||||
"language": "\f168",
|
||||
"large-pause": "\f169",
|
||||
"large-play": "\f16a",
|
||||
"link-badge": "\f16b",
|
||||
"link-broken": "\f16c",
|
||||
"link": "\f16d",
|
||||
"location": "\f16e",
|
||||
"lock-badge": "\f16f",
|
||||
"lock": "\f170",
|
||||
"logout": "\f171",
|
||||
"loop": "\f172",
|
||||
"mention": "\f173",
|
||||
"message-failed": "\f174",
|
||||
"message-pending": "\f175",
|
||||
"message-read": "\f176",
|
||||
"message-succeeded": "\f177",
|
||||
"message": "\f178",
|
||||
"microphone-alt": "\f179",
|
||||
"microphone": "\f17a",
|
||||
"monospace": "\f17b",
|
||||
"more-circle": "\f17c",
|
||||
"more": "\f17d",
|
||||
"move-caption-down": "\f17e",
|
||||
"move-caption-up": "\f17f",
|
||||
"mute": "\f180",
|
||||
"muted": "\f181",
|
||||
"my-notes": "\f182",
|
||||
"new-chat-filled": "\f183",
|
||||
"next": "\f184",
|
||||
"nochannel": "\f185",
|
||||
"noise-suppression": "\f186",
|
||||
"non-contacts": "\f187",
|
||||
"one-filled": "\f188",
|
||||
"open-in-new-tab": "\f189",
|
||||
"password-off": "\f18a",
|
||||
"pause": "\f18b",
|
||||
"permissions": "\f18c",
|
||||
"phone-discard-outline": "\f18d",
|
||||
"phone-discard": "\f18e",
|
||||
"phone": "\f18f",
|
||||
"photo": "\f190",
|
||||
"pin-badge": "\f191",
|
||||
"pin-list": "\f192",
|
||||
"pin": "\f193",
|
||||
"pinned-chat": "\f194",
|
||||
"pinned-message": "\f195",
|
||||
"pip": "\f196",
|
||||
"play-story": "\f197",
|
||||
"play": "\f198",
|
||||
"poll": "\f199",
|
||||
"previous": "\f19a",
|
||||
"privacy-policy": "\f19b",
|
||||
"quote-text": "\f19c",
|
||||
"quote": "\f19d",
|
||||
"readchats": "\f19e",
|
||||
"recent": "\f19f",
|
||||
"reload": "\f1a0",
|
||||
"remove-quote": "\f1a1",
|
||||
"remove": "\f1a2",
|
||||
"reopen-topic": "\f1a3",
|
||||
"replace": "\f1a4",
|
||||
"replies": "\f1a5",
|
||||
"reply-filled": "\f1a6",
|
||||
"reply": "\f1a7",
|
||||
"revenue-split": "\f1a8",
|
||||
"revote": "\f1a9",
|
||||
"save-story": "\f1aa",
|
||||
"saved-messages": "\f1ab",
|
||||
"schedule": "\f1ac",
|
||||
"search": "\f1ad",
|
||||
"select": "\f1ae",
|
||||
"send-outline": "\f1af",
|
||||
"send": "\f1b0",
|
||||
"settings-filled": "\f1b1",
|
||||
"settings": "\f1b2",
|
||||
"share-filled": "\f1b3",
|
||||
"share-screen-outlined": "\f1b4",
|
||||
"share-screen-stop": "\f1b5",
|
||||
"share-screen": "\f1b6",
|
||||
"show-message": "\f1b7",
|
||||
"sidebar": "\f1b8",
|
||||
"skip-next": "\f1b9",
|
||||
"skip-previous": "\f1ba",
|
||||
"smallscreen": "\f1bb",
|
||||
"smile": "\f1bc",
|
||||
"sort": "\f1bd",
|
||||
"speaker-muted-story": "\f1be",
|
||||
"speaker-outline": "\f1bf",
|
||||
"speaker-story": "\f1c0",
|
||||
"speaker": "\f1c1",
|
||||
"spoiler-disable": "\f1c2",
|
||||
"spoiler": "\f1c3",
|
||||
"sport": "\f1c4",
|
||||
"star": "\f1c5",
|
||||
"stars-lock": "\f1c6",
|
||||
"stats": "\f1c7",
|
||||
"stealth-future": "\f1c8",
|
||||
"stealth-past": "\f1c9",
|
||||
"stickers": "\f1ca",
|
||||
"stop-raising-hand": "\f1cb",
|
||||
"stop": "\f1cc",
|
||||
"story-caption": "\f1cd",
|
||||
"story-expired": "\f1ce",
|
||||
"story-priority": "\f1cf",
|
||||
"story-reply": "\f1d0",
|
||||
"strikethrough": "\f1d1",
|
||||
"tag-add": "\f1d2",
|
||||
"tag-crossed": "\f1d3",
|
||||
"tag-filter": "\f1d4",
|
||||
"tag-name": "\f1d5",
|
||||
"tag": "\f1d6",
|
||||
"timer": "\f1d7",
|
||||
"toncoin": "\f1d8",
|
||||
"transcribe": "\f1d9",
|
||||
"truck": "\f1da",
|
||||
"unarchive": "\f1db",
|
||||
"underlined": "\f1dc",
|
||||
"unlock-badge": "\f1dd",
|
||||
"unlock": "\f1de",
|
||||
"unmute": "\f1df",
|
||||
"unpin": "\f1e0",
|
||||
"unread": "\f1e1",
|
||||
"up": "\f1e2",
|
||||
"user-filled": "\f1e3",
|
||||
"user-online": "\f1e4",
|
||||
"user": "\f1e5",
|
||||
"video-outlined": "\f1e6",
|
||||
"video-stop": "\f1e7",
|
||||
"video": "\f1e8",
|
||||
"view-once": "\f1e9",
|
||||
"voice-chat": "\f1ea",
|
||||
"volume-1": "\f1eb",
|
||||
"volume-2": "\f1ec",
|
||||
"volume-3": "\f1ed",
|
||||
"web": "\f1ee",
|
||||
"webapp": "\f1ef",
|
||||
"word-wrap": "\f1f0",
|
||||
"zoom-in": "\f1f1",
|
||||
"zoom-out": "\f1f2",
|
||||
);
|
||||
|
||||
.icon-active-sessions::before {
|
||||
@ -385,6 +387,9 @@ $icons-map: (
|
||||
.icon-card::before {
|
||||
content: map.get($icons-map, "card");
|
||||
}
|
||||
.icon-cash-circle::before {
|
||||
content: map.get($icons-map, "cash-circle");
|
||||
}
|
||||
.icon-channel-filled::before {
|
||||
content: map.get($icons-map, "channel-filled");
|
||||
}
|
||||
@ -913,6 +918,9 @@ $icons-map: (
|
||||
.icon-timer::before {
|
||||
content: map.get($icons-map, "timer");
|
||||
}
|
||||
.icon-toncoin::before {
|
||||
content: map.get($icons-map, "toncoin");
|
||||
}
|
||||
.icon-transcribe::before {
|
||||
content: map.get($icons-map, "transcribe");
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -37,6 +37,7 @@ export type FontIconName =
|
||||
| 'camera'
|
||||
| 'car'
|
||||
| 'card'
|
||||
| 'cash-circle'
|
||||
| 'channel-filled'
|
||||
| 'channel'
|
||||
| 'channelviews'
|
||||
@ -213,6 +214,7 @@ export type FontIconName =
|
||||
| 'tag-name'
|
||||
| 'tag'
|
||||
| 'timer'
|
||||
| 'toncoin'
|
||||
| 'transcribe'
|
||||
| 'truck'
|
||||
| 'unarchive'
|
||||
|
||||
@ -311,6 +311,7 @@ export enum RightColumnContent {
|
||||
AddingMembers,
|
||||
CreateTopic,
|
||||
EditTopic,
|
||||
MonetizationStatistics,
|
||||
}
|
||||
|
||||
export type MediaViewerMedia = ApiPhoto | ApiVideo | ApiDocument;
|
||||
|
||||
10
src/types/language.d.ts
vendored
10
src/types/language.d.ts
vendored
@ -1512,6 +1512,16 @@ export interface LangPair {
|
||||
'MenuInstallApp': undefined;
|
||||
'RemoveEffect': undefined;
|
||||
'ReplyInPrivateMessage': undefined;
|
||||
'MonetizationInfoTONTitle': undefined;
|
||||
'ChannelEarnLearnCoinAbout': {
|
||||
'link': string | number;
|
||||
};
|
||||
'MonetizationBalanceZeroInfo': {
|
||||
'link': string | number;
|
||||
};
|
||||
'ChannelEarnAbout': {
|
||||
'link': string | number;
|
||||
};
|
||||
'AriaSearchOlderResult': undefined;
|
||||
'AriaSearchNewerResult': undefined;
|
||||
'CreditsBoxHistoryEntryGiftOutAbout': {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user