Statistics: Fix statistics overview (#5963)
This commit is contained in:
parent
f4240a93ab
commit
fb79e088fe
@ -8,12 +8,12 @@ import type {
|
|||||||
ApiPostStatistics,
|
ApiPostStatistics,
|
||||||
ApiStoryPublicForward,
|
ApiStoryPublicForward,
|
||||||
ChannelMonetizationBalances,
|
ChannelMonetizationBalances,
|
||||||
StatisticsGraph,
|
|
||||||
StatisticsMessageInteractionCounter,
|
StatisticsMessageInteractionCounter,
|
||||||
StatisticsOverviewItem,
|
StatisticsOverviewItem,
|
||||||
StatisticsOverviewPercentage,
|
StatisticsOverviewPercentage,
|
||||||
StatisticsOverviewPeriod,
|
StatisticsOverviewPeriod,
|
||||||
StatisticsStoryInteractionCounter,
|
StatisticsStoryInteractionCounter,
|
||||||
|
TypeStatisticsGraph,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildApiUsernames, buildAvatarPhotoId } from './common';
|
import { buildApiUsernames, buildAvatarPhotoId } from './common';
|
||||||
@ -23,20 +23,19 @@ const DECIMALS = 10 ** 9;
|
|||||||
|
|
||||||
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
||||||
return {
|
return {
|
||||||
|
type: 'channel',
|
||||||
// Graphs
|
// Graphs
|
||||||
growthGraph: buildGraph(stats.growthGraph),
|
growthGraph: buildGraph(stats.growthGraph),
|
||||||
followersGraph: buildGraph(stats.followersGraph),
|
followersGraph: buildGraph(stats.followersGraph),
|
||||||
muteGraph: buildGraph(stats.muteGraph),
|
muteGraph: buildGraph(stats.muteGraph),
|
||||||
topHoursGraph: buildGraph(stats.topHoursGraph),
|
topHoursGraph: buildGraph(stats.topHoursGraph),
|
||||||
|
languagesGraph: buildGraph(stats.languagesGraph),
|
||||||
// Async graphs
|
viewsBySourceGraph: buildGraph(stats.viewsBySourceGraph),
|
||||||
languagesGraph: (stats.languagesGraph as GramJs.StatsGraphAsync).token,
|
newFollowersBySourceGraph: buildGraph(stats.newFollowersBySourceGraph),
|
||||||
viewsBySourceGraph: (stats.viewsBySourceGraph as GramJs.StatsGraphAsync).token,
|
interactionsGraph: buildGraph(stats.interactionsGraph),
|
||||||
newFollowersBySourceGraph: (stats.newFollowersBySourceGraph as GramJs.StatsGraphAsync).token,
|
reactionsByEmotionGraph: buildGraph(stats.reactionsByEmotionGraph),
|
||||||
interactionsGraph: (stats.interactionsGraph as GramJs.StatsGraphAsync).token,
|
storyInteractionsGraph: buildGraph(stats.storyInteractionsGraph),
|
||||||
reactionsByEmotionGraph: (stats.reactionsByEmotionGraph as GramJs.StatsGraphAsync).token,
|
storyReactionsByEmotionGraph: buildGraph(stats.storyReactionsByEmotionGraph),
|
||||||
storyInteractionsGraph: (stats.storyInteractionsGraph as GramJs.StatsGraphAsync).token,
|
|
||||||
storyReactionsByEmotionGraph: (stats.storyReactionsByEmotionGraph as GramJs.StatsGraphAsync).token,
|
|
||||||
|
|
||||||
// Statistics overview
|
// Statistics overview
|
||||||
followers: buildStatisticsOverview(stats.followers),
|
followers: buildStatisticsOverview(stats.followers),
|
||||||
@ -72,6 +71,7 @@ export function buildApiPostInteractionCounter(
|
|||||||
): StatisticsMessageInteractionCounter | StatisticsStoryInteractionCounter | undefined {
|
): StatisticsMessageInteractionCounter | StatisticsStoryInteractionCounter | undefined {
|
||||||
if (interaction instanceof GramJs.PostInteractionCountersMessage) {
|
if (interaction instanceof GramJs.PostInteractionCountersMessage) {
|
||||||
return {
|
return {
|
||||||
|
type: 'message',
|
||||||
msgId: interaction.msgId,
|
msgId: interaction.msgId,
|
||||||
forwardsCount: interaction.forwards,
|
forwardsCount: interaction.forwards,
|
||||||
viewsCount: interaction.views,
|
viewsCount: interaction.views,
|
||||||
@ -81,6 +81,7 @@ export function buildApiPostInteractionCounter(
|
|||||||
|
|
||||||
if (interaction instanceof GramJs.PostInteractionCountersStory) {
|
if (interaction instanceof GramJs.PostInteractionCountersStory) {
|
||||||
return {
|
return {
|
||||||
|
type: 'story',
|
||||||
storyId: interaction.storyId,
|
storyId: interaction.storyId,
|
||||||
reactionsCount: interaction.reactions,
|
reactionsCount: interaction.reactions,
|
||||||
viewsCount: interaction.views,
|
viewsCount: interaction.views,
|
||||||
@ -93,15 +94,14 @@ export function buildApiPostInteractionCounter(
|
|||||||
|
|
||||||
export function buildGroupStatistics(stats: GramJs.stats.MegagroupStats): ApiGroupStatistics {
|
export function buildGroupStatistics(stats: GramJs.stats.MegagroupStats): ApiGroupStatistics {
|
||||||
return {
|
return {
|
||||||
|
type: 'group',
|
||||||
// Graphs
|
// Graphs
|
||||||
growthGraph: buildGraph(stats.growthGraph),
|
growthGraph: buildGraph(stats.growthGraph),
|
||||||
membersGraph: buildGraph(stats.membersGraph),
|
membersGraph: buildGraph(stats.membersGraph),
|
||||||
topHoursGraph: buildGraph(stats.topHoursGraph),
|
topHoursGraph: buildGraph(stats.topHoursGraph),
|
||||||
|
languagesGraph: buildGraph(stats.languagesGraph),
|
||||||
// Async graphs
|
messagesGraph: buildGraph(stats.messagesGraph),
|
||||||
languagesGraph: (stats.languagesGraph as GramJs.StatsGraphAsync).token,
|
actionsGraph: buildGraph(stats.actionsGraph),
|
||||||
messagesGraph: (stats.messagesGraph as GramJs.StatsGraphAsync).token,
|
|
||||||
actionsGraph: (stats.actionsGraph as GramJs.StatsGraphAsync).token,
|
|
||||||
|
|
||||||
// Statistics overview
|
// Statistics overview
|
||||||
period: getOverviewPeriod(stats.period),
|
period: getOverviewPeriod(stats.period),
|
||||||
@ -158,18 +158,29 @@ export function buildStoryPublicForwards(
|
|||||||
|
|
||||||
export function buildGraph(
|
export function buildGraph(
|
||||||
result: GramJs.TypeStatsGraph, isPercentage?: boolean, isCurrency?: boolean, currencyRate?: number,
|
result: GramJs.TypeStatsGraph, isPercentage?: boolean, isCurrency?: boolean, currencyRate?: number,
|
||||||
): StatisticsGraph | undefined {
|
): TypeStatisticsGraph {
|
||||||
if ((result as GramJs.StatsGraphError).error) {
|
if (result instanceof GramJs.StatsGraphError) {
|
||||||
return undefined;
|
return {
|
||||||
|
graphType: 'error',
|
||||||
|
error: result.error,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = JSON.parse((result as GramJs.StatsGraph).json.data);
|
if (result instanceof GramJs.StatsGraphAsync) {
|
||||||
|
return {
|
||||||
|
graphType: 'async',
|
||||||
|
token: result.token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(result.json.data);
|
||||||
const [x, ...y] = data.columns;
|
const [x, ...y] = data.columns;
|
||||||
const hasSecondYAxis = data.y_scaled;
|
const hasSecondYAxis = data.y_scaled;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
graphType: 'graph',
|
||||||
type: isPercentage ? 'area' : data.types.y0,
|
type: isPercentage ? 'area' : data.types.y0,
|
||||||
zoomToken: (result as GramJs.StatsGraph).zoomToken,
|
zoomToken: result.zoomToken,
|
||||||
labelFormatter: data.xTickFormatter,
|
labelFormatter: data.xTickFormatter,
|
||||||
tooltipFormatter: data.xTooltipFormatter,
|
tooltipFormatter: data.xTooltipFormatter,
|
||||||
labels: x.slice(1),
|
labels: x.slice(1),
|
||||||
|
|||||||
@ -270,6 +270,30 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
|
|||||||
return { message };
|
return { message };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchMessagesById({ chat, messageIds }: { chat: ApiChat; messageIds: number[] }) {
|
||||||
|
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
||||||
|
|
||||||
|
const result = await invokeRequest(
|
||||||
|
isChannel
|
||||||
|
? new GramJs.channels.GetMessages({
|
||||||
|
channel: buildInputChannel(chat.id, chat.accessHash),
|
||||||
|
id: messageIds.map((id) => new GramJs.InputMessageID({ id })),
|
||||||
|
})
|
||||||
|
: new GramJs.messages.GetMessages({
|
||||||
|
id: messageIds.map((id) => new GramJs.InputMessageID({ id })),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
shouldThrow: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result || result instanceof GramJs.messages.MessagesNotModified) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
let mediaQueue = Promise.resolve();
|
let mediaQueue = Promise.resolve();
|
||||||
|
|
||||||
export function sendMessageLocal(
|
export function sendMessageLocal(
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import type {
|
|||||||
ApiChat, ApiMessagePublicForward, ApiPeer, ApiPostStatistics, ApiStoryPublicForward, StatisticsGraph,
|
ApiChat, ApiMessagePublicForward, ApiPeer, ApiPostStatistics, ApiStoryPublicForward, StatisticsGraph,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { STATISTICS_PUBLIC_FORWARDS_LIMIT } from '../../../config';
|
|
||||||
import {
|
import {
|
||||||
buildChannelMonetizationStatistics,
|
buildChannelMonetizationStatistics,
|
||||||
buildChannelStatistics,
|
buildChannelStatistics,
|
||||||
@ -104,11 +103,13 @@ export async function fetchMessagePublicForwards({
|
|||||||
messageId,
|
messageId,
|
||||||
dcId,
|
dcId,
|
||||||
offset = DEFAULT_PRIMITIVES.STRING,
|
offset = DEFAULT_PRIMITIVES.STRING,
|
||||||
|
limit = DEFAULT_PRIMITIVES.INT,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
dcId?: number;
|
dcId?: number;
|
||||||
offset?: string;
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
forwards?: ApiMessagePublicForward[];
|
forwards?: ApiMessagePublicForward[];
|
||||||
count?: number;
|
count?: number;
|
||||||
@ -118,7 +119,7 @@ export async function fetchMessagePublicForwards({
|
|||||||
channel: buildInputChannel(chat.id, chat.accessHash),
|
channel: buildInputChannel(chat.id, chat.accessHash),
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
offset,
|
offset,
|
||||||
limit: STATISTICS_PUBLIC_FORWARDS_LIMIT,
|
limit,
|
||||||
}), {
|
}), {
|
||||||
dcId,
|
dcId,
|
||||||
});
|
});
|
||||||
@ -156,7 +157,10 @@ export async function fetchStatisticsAsyncGraph({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildGraph(result as GramJs.StatsGraph, isPercentage);
|
const graph = buildGraph(result, isPercentage);
|
||||||
|
|
||||||
|
if (graph.graphType !== 'graph') return undefined;
|
||||||
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchStoryStatistics({
|
export async function fetchStoryStatistics({
|
||||||
@ -187,11 +191,13 @@ export async function fetchStoryPublicForwards({
|
|||||||
storyId,
|
storyId,
|
||||||
dcId,
|
dcId,
|
||||||
offset = DEFAULT_PRIMITIVES.STRING,
|
offset = DEFAULT_PRIMITIVES.STRING,
|
||||||
|
limit = DEFAULT_PRIMITIVES.INT,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
storyId: number;
|
storyId: number;
|
||||||
dcId?: number;
|
dcId?: number;
|
||||||
offset?: string;
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
publicForwards: (ApiMessagePublicForward | ApiStoryPublicForward)[] | undefined;
|
publicForwards: (ApiMessagePublicForward | ApiStoryPublicForward)[] | undefined;
|
||||||
count?: number;
|
count?: number;
|
||||||
@ -201,7 +207,7 @@ export async function fetchStoryPublicForwards({
|
|||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
id: storyId,
|
id: storyId,
|
||||||
offset,
|
offset,
|
||||||
limit: STATISTICS_PUBLIC_FORWARDS_LIMIT,
|
limit,
|
||||||
}), {
|
}), {
|
||||||
dcId,
|
dcId,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,17 +2,18 @@ import type { ApiChat } from './chats';
|
|||||||
import type { ApiTypePrepaidGiveaway } from './payments';
|
import type { ApiTypePrepaidGiveaway } from './payments';
|
||||||
|
|
||||||
export interface ApiChannelStatistics {
|
export interface ApiChannelStatistics {
|
||||||
growthGraph?: StatisticsGraph | string;
|
type: 'channel';
|
||||||
followersGraph?: StatisticsGraph | string;
|
growthGraph?: TypeStatisticsGraph;
|
||||||
muteGraph?: StatisticsGraph | string;
|
followersGraph?: TypeStatisticsGraph;
|
||||||
topHoursGraph?: StatisticsGraph | string;
|
muteGraph?: TypeStatisticsGraph;
|
||||||
reactionsByEmotionGraph?: StatisticsGraph | string;
|
topHoursGraph?: TypeStatisticsGraph;
|
||||||
storyInteractionsGraph?: StatisticsGraph | string;
|
reactionsByEmotionGraph?: TypeStatisticsGraph;
|
||||||
storyReactionsByEmotionGraph?: StatisticsGraph | string;
|
storyInteractionsGraph?: TypeStatisticsGraph;
|
||||||
interactionsGraph: StatisticsGraph | string;
|
storyReactionsByEmotionGraph?: TypeStatisticsGraph;
|
||||||
viewsBySourceGraph: StatisticsGraph | string;
|
interactionsGraph: TypeStatisticsGraph;
|
||||||
newFollowersBySourceGraph: StatisticsGraph | string;
|
viewsBySourceGraph: TypeStatisticsGraph;
|
||||||
languagesGraph: StatisticsGraph | string;
|
newFollowersBySourceGraph: TypeStatisticsGraph;
|
||||||
|
languagesGraph: TypeStatisticsGraph;
|
||||||
followers: StatisticsOverviewItem;
|
followers: StatisticsOverviewItem;
|
||||||
viewsPerPost: StatisticsOverviewItem;
|
viewsPerPost: StatisticsOverviewItem;
|
||||||
sharesPerPost: StatisticsOverviewItem;
|
sharesPerPost: StatisticsOverviewItem;
|
||||||
@ -25,19 +26,20 @@ export interface ApiChannelStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiChannelMonetizationStatistics {
|
export interface ApiChannelMonetizationStatistics {
|
||||||
topHoursGraph?: StatisticsGraph | string;
|
topHoursGraph?: TypeStatisticsGraph;
|
||||||
revenueGraph?: StatisticsGraph | string;
|
revenueGraph?: TypeStatisticsGraph;
|
||||||
balances?: ChannelMonetizationBalances;
|
balances?: ChannelMonetizationBalances;
|
||||||
usdRate?: number;
|
usdRate?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiGroupStatistics {
|
export interface ApiGroupStatistics {
|
||||||
growthGraph?: StatisticsGraph | string;
|
type: 'group';
|
||||||
membersGraph?: StatisticsGraph | string;
|
growthGraph?: TypeStatisticsGraph;
|
||||||
topHoursGraph?: StatisticsGraph | string;
|
membersGraph?: TypeStatisticsGraph;
|
||||||
languagesGraph: StatisticsGraph | string;
|
topHoursGraph?: TypeStatisticsGraph;
|
||||||
messagesGraph: StatisticsGraph | string;
|
languagesGraph: TypeStatisticsGraph;
|
||||||
actionsGraph: StatisticsGraph | string;
|
messagesGraph: TypeStatisticsGraph;
|
||||||
|
actionsGraph: TypeStatisticsGraph;
|
||||||
period: StatisticsOverviewPeriod;
|
period: StatisticsOverviewPeriod;
|
||||||
members: StatisticsOverviewItem;
|
members: StatisticsOverviewItem;
|
||||||
viewers: StatisticsOverviewItem;
|
viewers: StatisticsOverviewItem;
|
||||||
@ -46,8 +48,8 @@ export interface ApiGroupStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiPostStatistics {
|
export interface ApiPostStatistics {
|
||||||
viewsGraph?: StatisticsGraph | string;
|
viewsGraph?: TypeStatisticsGraph;
|
||||||
reactionsGraph?: StatisticsGraph | string;
|
reactionsGraph?: TypeStatisticsGraph;
|
||||||
forwardsCount?: number;
|
forwardsCount?: number;
|
||||||
viewsCount?: number;
|
viewsCount?: number;
|
||||||
reactionsCount?: number;
|
reactionsCount?: number;
|
||||||
@ -80,6 +82,7 @@ export interface ApiStoryPublicForward {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsGraph {
|
export interface StatisticsGraph {
|
||||||
|
graphType: 'graph';
|
||||||
type: string;
|
type: string;
|
||||||
zoomToken?: string;
|
zoomToken?: string;
|
||||||
labelFormatter: string;
|
labelFormatter: string;
|
||||||
@ -104,6 +107,18 @@ export interface StatisticsGraph {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StatisticsGraphError {
|
||||||
|
graphType: 'error';
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatisticsGraphAsync {
|
||||||
|
graphType: 'async';
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TypeStatisticsGraph = StatisticsGraph | StatisticsGraphError | StatisticsGraphAsync;
|
||||||
|
|
||||||
export interface StatisticsOverviewItem {
|
export interface StatisticsOverviewItem {
|
||||||
current?: number;
|
current?: number;
|
||||||
change?: number;
|
change?: number;
|
||||||
@ -122,6 +137,7 @@ export interface StatisticsOverviewPeriod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsMessageInteractionCounter {
|
export interface StatisticsMessageInteractionCounter {
|
||||||
|
type: 'message';
|
||||||
msgId: number;
|
msgId: number;
|
||||||
forwardsCount: number;
|
forwardsCount: number;
|
||||||
viewsCount: number;
|
viewsCount: number;
|
||||||
@ -129,6 +145,7 @@ export interface StatisticsMessageInteractionCounter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsStoryInteractionCounter {
|
export interface StatisticsStoryInteractionCounter {
|
||||||
|
type: 'story';
|
||||||
storyId: number;
|
storyId: number;
|
||||||
viewsCount: number;
|
viewsCount: number;
|
||||||
forwardsCount: number;
|
forwardsCount: number;
|
||||||
|
|||||||
@ -1026,7 +1026,11 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('quick-reaction', isQuickReactionVisible && !hasActiveReactions && 'visible')}
|
className={buildClassName(
|
||||||
|
'quick-reaction',
|
||||||
|
'no-selection',
|
||||||
|
isQuickReactionVisible && !hasActiveReactions && 'visible',
|
||||||
|
)}
|
||||||
onClick={handleSendQuickReaction}
|
onClick={handleSendQuickReaction}
|
||||||
ref={quickReactionRef}
|
ref={quickReactionRef}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -6,14 +6,13 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiMessagePublicForward,
|
ApiMessagePublicForward,
|
||||||
ApiPostStatistics,
|
ApiPostStatistics,
|
||||||
StatisticsGraph,
|
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { STATISTICS_PUBLIC_FORWARDS_LIMIT } from '../../../config';
|
|
||||||
import { selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
import { selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import { isGraph } from './helpers/isGraph';
|
||||||
|
|
||||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
@ -56,7 +55,7 @@ export type StateProps = {
|
|||||||
dcId?: number;
|
dcId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Statistics({
|
function MessageStatistics({
|
||||||
chatId,
|
chatId,
|
||||||
isActive,
|
isActive,
|
||||||
statistics,
|
statistics,
|
||||||
@ -66,7 +65,8 @@ function Statistics({
|
|||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const loadedCharts = useRef<string[]>([]);
|
const loadedCharts = useRef<Set<string>>(new Set());
|
||||||
|
const errorCharts = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
const { loadMessageStatistics, loadMessagePublicForwards, loadStatisticsAsyncGraph } = getActions();
|
const { loadMessageStatistics, loadMessagePublicForwards, loadStatisticsAsyncGraph } = getActions();
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
@ -79,7 +79,8 @@ function Statistics({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isActive || messageId) {
|
if (!isActive || messageId) {
|
||||||
loadedCharts.current = [];
|
loadedCharts.current.clear();
|
||||||
|
errorCharts.current.clear();
|
||||||
setIsReady(false);
|
setIsReady(false);
|
||||||
}
|
}
|
||||||
}, [isActive, messageId]);
|
}, [isActive, messageId]);
|
||||||
@ -92,10 +93,13 @@ function Statistics({
|
|||||||
|
|
||||||
GRAPHS.forEach((name) => {
|
GRAPHS.forEach((name) => {
|
||||||
const graph = statistics[name];
|
const graph = statistics[name];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAsync = graph.graphType === 'async';
|
||||||
|
|
||||||
if (isAsync) {
|
if (isAsync) {
|
||||||
loadStatisticsAsyncGraph({ name, chatId, token: graph });
|
loadStatisticsAsyncGraph({ name, chatId, token: graph.token });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [chatId, statistics, loadStatisticsAsyncGraph]);
|
}, [chatId, statistics, loadStatisticsAsyncGraph]);
|
||||||
@ -115,19 +119,24 @@ function Statistics({
|
|||||||
|
|
||||||
GRAPHS.forEach((name, index: number) => {
|
GRAPHS.forEach((name, index: number) => {
|
||||||
const graph = statistics[name];
|
const graph = statistics[name];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAsync = graph.graphType === 'async';
|
||||||
|
const isError = graph.graphType === 'error';
|
||||||
|
|
||||||
if (isAsync || loadedCharts.current.includes(name)) {
|
if (isAsync || loadedCharts.current.has(name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!graph) {
|
if (isError) {
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
|
errorCharts.current.add(name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { zoomToken } = graph as StatisticsGraph;
|
const { zoomToken } = graph;
|
||||||
|
|
||||||
LovelyChart.create(
|
LovelyChart.create(
|
||||||
containerRef.current!.children[index] as HTMLElement,
|
containerRef.current!.children[index] as HTMLElement,
|
||||||
@ -137,11 +146,11 @@ function Statistics({
|
|||||||
onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }),
|
onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }),
|
||||||
zoomOutLabel: lang('Graph.ZoomOut'),
|
zoomOutLabel: lang('Graph.ZoomOut'),
|
||||||
} : {},
|
} : {},
|
||||||
...graph as StatisticsGraph,
|
...graph,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
@ -161,15 +170,21 @@ function Statistics({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}>
|
<div
|
||||||
|
key={`${chatId}-${messageId}`}
|
||||||
|
className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}
|
||||||
|
>
|
||||||
<StatisticsOverview statistics={statistics} type="message" title={lang('StatisticOverview')} />
|
<StatisticsOverview statistics={statistics} type="message" title={lang('StatisticOverview')} />
|
||||||
|
|
||||||
{!loadedCharts.current.length && <Loading />}
|
{(!loadedCharts.current.size || !statistics.publicForwardsData) && <Loading />}
|
||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{GRAPHS.map((graph) => (
|
{GRAPHS.map((graph) => {
|
||||||
<div className={buildClassName(styles.graph, !loadedCharts.current.includes(graph) && styles.hidden)} />
|
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
||||||
))}
|
return (
|
||||||
|
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Boolean(statistics.publicForwards) && (
|
{Boolean(statistics.publicForwards) && (
|
||||||
@ -180,7 +195,6 @@ function Statistics({
|
|||||||
items={statistics.publicForwardsData}
|
items={statistics.publicForwardsData}
|
||||||
itemSelector=".statistic-public-forward"
|
itemSelector=".statistic-public-forward"
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
preloadBackwards={STATISTICS_PUBLIC_FORWARDS_LIMIT}
|
|
||||||
noFastList
|
noFastList
|
||||||
>
|
>
|
||||||
{(statistics.publicForwardsData as ApiMessagePublicForward[]).map((item) => (
|
{(statistics.publicForwardsData as ApiMessagePublicForward[]).map((item) => (
|
||||||
@ -202,4 +216,4 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return { statistics, dcId, messageId };
|
return { statistics, dcId, messageId };
|
||||||
},
|
},
|
||||||
)(Statistics));
|
)(MessageStatistics));
|
||||||
|
|||||||
@ -3,11 +3,12 @@ import {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChannelMonetizationStatistics, StatisticsGraph } from '../../../api/types';
|
import type { ApiChannelMonetizationStatistics } from '../../../api/types';
|
||||||
|
|
||||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
import { isGraph } from './helpers/isGraph';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||||
@ -68,7 +69,9 @@ const MonetizationStatistics = ({
|
|||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const loadedCharts = useRef<string[]>([]);
|
const loadedCharts = useRef<Set<string>>(new Set());
|
||||||
|
const errorCharts = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false);
|
const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false);
|
||||||
const [isConfirmPasswordDialogOpen, openConfirmPasswordDialogOpen, closeConfirmPasswordDialogOpen] = useFlag();
|
const [isConfirmPasswordDialogOpen, openConfirmPasswordDialogOpen, closeConfirmPasswordDialogOpen] = useFlag();
|
||||||
@ -100,7 +103,8 @@ const MonetizationStatistics = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedCharts.current = [];
|
loadedCharts.current.clear();
|
||||||
|
errorCharts.current.clear();
|
||||||
|
|
||||||
if (!statistics || !containerRef.current) {
|
if (!statistics || !containerRef.current) {
|
||||||
return;
|
return;
|
||||||
@ -108,24 +112,29 @@ const MonetizationStatistics = ({
|
|||||||
|
|
||||||
MONETIZATION_GRAPHS.forEach((name, index: number) => {
|
MONETIZATION_GRAPHS.forEach((name, index: number) => {
|
||||||
const graph = statistics[name];
|
const graph = statistics[name];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAsync = graph.graphType === 'async';
|
||||||
|
const isError = graph.graphType === 'error';
|
||||||
|
|
||||||
if (isAsync || loadedCharts.current.includes(name)) {
|
if (isAsync || loadedCharts.current.has(name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!graph) {
|
if (isError) {
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
|
errorCharts.current.add(name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LovelyChart.create(containerRef.current!.children[index] as HTMLElement, {
|
LovelyChart.create(containerRef.current!.children[index] as HTMLElement, {
|
||||||
title: oldLang((MONETIZATION_GRAPHS_TITLES as Record<string, string>)[name]),
|
title: oldLang((MONETIZATION_GRAPHS_TITLES as Record<string, string>)[name]),
|
||||||
...graph as StatisticsGraph,
|
...graph,
|
||||||
});
|
});
|
||||||
|
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
|
|
||||||
containerRef.current!.children[index].classList.remove(styles.hidden);
|
containerRef.current!.children[index].classList.remove(styles.hidden);
|
||||||
});
|
});
|
||||||
@ -232,7 +241,7 @@ const MonetizationStatistics = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!loadedCharts.current.length && <Loading />}
|
{!loadedCharts.current.size && <Loading />}
|
||||||
|
|
||||||
<div ref={containerRef} className={styles.section}>
|
<div ref={containerRef} className={styles.section}>
|
||||||
{MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (
|
{MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (
|
||||||
|
|||||||
@ -36,7 +36,6 @@
|
|||||||
|
|
||||||
.messages, .publicForwards {
|
.messages, .publicForwards {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
border-top: 1px solid var(--color-borders);
|
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
padding-left: 0.75rem;
|
padding-left: 0.75rem;
|
||||||
@ -69,6 +68,7 @@
|
|||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
border-bottom: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import {
|
import {
|
||||||
memo, useEffect, useMemo,
|
memo, useEffect, useMemo,
|
||||||
useRef, useState,
|
useRef, useState,
|
||||||
@ -22,6 +21,7 @@ import {
|
|||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import { isGraph } from './helpers/isGraph';
|
||||||
|
|
||||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
@ -84,7 +84,7 @@ export type StateProps = {
|
|||||||
storiesById?: Record<string, ApiTypeStory>;
|
storiesById?: Record<string, ApiTypeStory>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Statistics: FC<OwnProps & StateProps> = ({
|
const Statistics = ({
|
||||||
chatId,
|
chatId,
|
||||||
chat,
|
chat,
|
||||||
statistics,
|
statistics,
|
||||||
@ -92,11 +92,12 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
isGroup,
|
isGroup,
|
||||||
messagesById,
|
messagesById,
|
||||||
storiesById,
|
storiesById,
|
||||||
}) => {
|
}: OwnProps & StateProps) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const loadedCharts = useRef<string[]>([]);
|
const loadedCharts = useRef<Set<string>>(new Set());
|
||||||
|
const errorCharts = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
const { loadStatistics, loadStatisticsAsyncGraph } = getActions();
|
const { loadStatistics, loadStatisticsAsyncGraph } = getActions();
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
@ -121,13 +122,16 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
graphs.forEach((name) => {
|
graphs.forEach((name) => {
|
||||||
const graph = statistics[name as keyof typeof statistics];
|
const graph = statistics[name as keyof typeof statistics];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAsync = graph.graphType === 'async';
|
||||||
|
|
||||||
if (isAsync) {
|
if (isAsync) {
|
||||||
loadStatisticsAsyncGraph({
|
loadStatisticsAsyncGraph({
|
||||||
name,
|
name,
|
||||||
chatId,
|
chatId,
|
||||||
token: graph,
|
token: graph.token,
|
||||||
// Hardcode percentage for languages graph, since API does not return `percentage` flag
|
// Hardcode percentage for languages graph, since API does not return `percentage` flag
|
||||||
isPercentage: name === 'languagesGraph',
|
isPercentage: name === 'languagesGraph',
|
||||||
});
|
});
|
||||||
@ -150,14 +154,20 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
graphs.forEach((name, index: number) => {
|
graphs.forEach((name, index: number) => {
|
||||||
const graph = statistics[name as keyof typeof statistics];
|
const graph = statistics[name as keyof typeof statistics];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
|
||||||
if (isAsync || loadedCharts.current.includes(name)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!graph) {
|
const isAsync = graph.graphType === 'async';
|
||||||
loadedCharts.current.push(name);
|
const isError = graph.graphType === 'error';
|
||||||
|
|
||||||
|
if (isAsync || loadedCharts.current.has(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
loadedCharts.current.add(name);
|
||||||
|
errorCharts.current.add(name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -176,7 +186,7 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
|
|
||||||
containerRef.current!.children[index].classList.remove(styles.hidden);
|
containerRef.current!.children[index].classList.remove(styles.hidden);
|
||||||
});
|
});
|
||||||
@ -197,12 +207,15 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loadedCharts.current.length && <Loading />}
|
{!loadedCharts.current.size && <Loading />}
|
||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{graphs.map((graph) => (
|
{graphs.map((graph) => {
|
||||||
<div key={graph} className={buildClassName(styles.graph, styles.hidden)} />
|
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
||||||
))}
|
return (
|
||||||
|
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Boolean((statistics as ApiChannelStatistics)?.recentPosts?.length) && (
|
{Boolean((statistics as ApiChannelStatistics)?.recentPosts?.length) && (
|
||||||
|
|||||||
@ -100,6 +100,8 @@ const BOOST_OVERVIEW: OverviewCell[][] = [
|
|||||||
|
|
||||||
type StatisticsType = 'channel' | 'group' | 'message' | 'boost' | 'story' | 'monetization';
|
type StatisticsType = 'channel' | 'group' | 'message' | 'boost' | 'story' | 'monetization';
|
||||||
|
|
||||||
|
const DEFAULT_VALUE = 0;
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
type: StatisticsType;
|
type: StatisticsType;
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -212,13 +214,13 @@ const StatisticsOverview: FC<OwnProps> = ({
|
|||||||
) : schema.map((row) => (
|
) : schema.map((row) => (
|
||||||
<tr>
|
<tr>
|
||||||
{row.map((cell: OverviewCell) => {
|
{row.map((cell: OverviewCell) => {
|
||||||
const field = (statistics as any)[cell.name];
|
const field = (statistics as any)?.[cell.name];
|
||||||
|
|
||||||
if (cell.isPlain) {
|
if (cell.isPlain) {
|
||||||
return (
|
return (
|
||||||
<td className={styles.tableCell}>
|
<td className={styles.tableCell}>
|
||||||
<b className={styles.tableValue}>
|
<b className={styles.tableValue}>
|
||||||
{`${cell.isApproximate ? '≈' : ''}${formatInteger(field)}`}
|
{`${cell.isApproximate ? '≈ ' : ''}${formatInteger(field ?? DEFAULT_VALUE)}`}
|
||||||
</b>
|
</b>
|
||||||
<h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
|
<h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
|
||||||
</td>
|
</td>
|
||||||
@ -226,15 +228,18 @@ const StatisticsOverview: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cell.isPercentage) {
|
if (cell.isPercentage) {
|
||||||
|
const part = field?.part ?? DEFAULT_VALUE;
|
||||||
|
const percentage = field?.percentage ?? DEFAULT_VALUE;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<td className={styles.tableCell}>
|
<td className={styles.tableCell}>
|
||||||
{cell.withAbsoluteValue && (
|
{cell.withAbsoluteValue && (
|
||||||
<span className={styles.tableValue}>
|
<span className={styles.tableValue}>
|
||||||
{`${cell.isApproximate ? '≈' : ''}${formatInteger(field.part)}`}
|
{`${cell.isApproximate ? '≈ ' : ''}${formatInteger(part)}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className={cell.withAbsoluteValue ? styles.tableSecondaryValue : styles.tableValue}>
|
<span className={cell.withAbsoluteValue ? styles.tableSecondaryValue : styles.tableValue}>
|
||||||
{field.percentage}
|
{percentage}
|
||||||
%
|
%
|
||||||
</span>
|
</span>
|
||||||
<h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
|
<h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
|
||||||
@ -245,7 +250,7 @@ const StatisticsOverview: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<td className={styles.tableCell}>
|
<td className={styles.tableCell}>
|
||||||
<b className={styles.tableValue}>
|
<b className={styles.tableValue}>
|
||||||
{formatIntegerCompact(lang, field.current)}
|
{formatIntegerCompact(lang, field?.current ?? DEFAULT_VALUE)}
|
||||||
</b>
|
</b>
|
||||||
{' '}
|
{' '}
|
||||||
{renderOverviewItemValue(field)}
|
{renderOverviewItemValue(field)}
|
||||||
|
|||||||
@ -7,13 +7,12 @@ import type {
|
|||||||
ApiChat,
|
ApiChat,
|
||||||
ApiPostStatistics,
|
ApiPostStatistics,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
StatisticsGraph,
|
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { STATISTICS_PUBLIC_FORWARDS_LIMIT } from '../../../config';
|
|
||||||
import { selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
import { selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import { isGraph } from './helpers/isGraph';
|
||||||
|
|
||||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
@ -71,7 +70,8 @@ function StoryStatistics({
|
|||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const loadedCharts = useRef<string[]>([]);
|
const loadedCharts = useRef<Set<string>>(new Set());
|
||||||
|
const errorCharts = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
const { loadStoryStatistics, loadStoryPublicForwards, loadStatisticsAsyncGraph } = getActions();
|
const { loadStoryStatistics, loadStoryPublicForwards, loadStatisticsAsyncGraph } = getActions();
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
@ -84,7 +84,8 @@ function StoryStatistics({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isActive || storyId) {
|
if (!isActive || storyId) {
|
||||||
loadedCharts.current = [];
|
loadedCharts.current.clear();
|
||||||
|
errorCharts.current.clear();
|
||||||
setIsReady(false);
|
setIsReady(false);
|
||||||
}
|
}
|
||||||
}, [isActive, storyId]);
|
}, [isActive, storyId]);
|
||||||
@ -97,10 +98,13 @@ function StoryStatistics({
|
|||||||
|
|
||||||
GRAPHS.forEach((name) => {
|
GRAPHS.forEach((name) => {
|
||||||
const graph = statistics[name];
|
const graph = statistics[name];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAsync = graph.graphType === 'async';
|
||||||
|
|
||||||
if (isAsync) {
|
if (isAsync) {
|
||||||
loadStatisticsAsyncGraph({ name, chatId, token: graph });
|
loadStatisticsAsyncGraph({ name, chatId, token: graph.token });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [chatId, statistics, loadStatisticsAsyncGraph]);
|
}, [chatId, statistics, loadStatisticsAsyncGraph]);
|
||||||
@ -120,19 +124,24 @@ function StoryStatistics({
|
|||||||
|
|
||||||
GRAPHS.forEach((name, index: number) => {
|
GRAPHS.forEach((name, index: number) => {
|
||||||
const graph = statistics[name];
|
const graph = statistics[name];
|
||||||
const isAsync = typeof graph === 'string';
|
if (!isGraph(graph)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAsync = graph.graphType === 'async';
|
||||||
|
const isError = graph.graphType === 'error';
|
||||||
|
|
||||||
if (isAsync || loadedCharts.current.includes(name)) {
|
if (isAsync || loadedCharts.current.has(name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!graph) {
|
if (isError) {
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
|
errorCharts.current.add(name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { zoomToken } = graph as StatisticsGraph;
|
const { zoomToken } = graph;
|
||||||
|
|
||||||
LovelyChart.create(
|
LovelyChart.create(
|
||||||
containerRef.current!.children[index] as HTMLElement,
|
containerRef.current!.children[index] as HTMLElement,
|
||||||
@ -142,11 +151,11 @@ function StoryStatistics({
|
|||||||
onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }),
|
onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }),
|
||||||
zoomOutLabel: lang('Graph.ZoomOut'),
|
zoomOutLabel: lang('Graph.ZoomOut'),
|
||||||
} : {},
|
} : {},
|
||||||
...graph as StatisticsGraph,
|
...graph,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.add(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
@ -166,15 +175,21 @@ function StoryStatistics({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}>
|
<div
|
||||||
|
key={`${chatId}-${storyId}`}
|
||||||
|
className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}
|
||||||
|
>
|
||||||
<StatisticsOverview statistics={statistics} type="story" title={lang('StatisticOverview')} />
|
<StatisticsOverview statistics={statistics} type="story" title={lang('StatisticOverview')} />
|
||||||
|
|
||||||
{!loadedCharts.current.length && <Loading />}
|
{!loadedCharts.current.size && <Loading />}
|
||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{GRAPHS.map((graph) => (
|
{GRAPHS.map((graph) => {
|
||||||
<div className={buildClassName(styles.graph, !loadedCharts.current.includes(graph) && styles.hidden)} />
|
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
||||||
))}
|
return (
|
||||||
|
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Boolean(statistics.publicForwards) && (
|
{Boolean(statistics.publicForwards) && (
|
||||||
@ -185,7 +200,6 @@ function StoryStatistics({
|
|||||||
items={statistics.publicForwardsData}
|
items={statistics.publicForwardsData}
|
||||||
itemSelector=".statistic-public-forward"
|
itemSelector=".statistic-public-forward"
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
preloadBackwards={STATISTICS_PUBLIC_FORWARDS_LIMIT}
|
|
||||||
noFastList
|
noFastList
|
||||||
>
|
>
|
||||||
{statistics.publicForwardsData!.map((item) => {
|
{statistics.publicForwardsData!.map((item) => {
|
||||||
|
|||||||
6
src/components/right/statistics/helpers/isGraph.ts
Normal file
6
src/components/right/statistics/helpers/isGraph.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { TypeStatisticsGraph } from '../../../../api/types';
|
||||||
|
|
||||||
|
export function isGraph(obj: unknown): obj is TypeStatisticsGraph {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
return typeof obj === 'object' && obj !== null && 'graphType' in obj;
|
||||||
|
}
|
||||||
@ -104,7 +104,6 @@ export const TOPIC_LIST_SENSITIVE_AREA = 600;
|
|||||||
export const GROUP_CALL_PARTICIPANTS_LIMIT = 100;
|
export const GROUP_CALL_PARTICIPANTS_LIMIT = 100;
|
||||||
export const STORY_LIST_LIMIT = 100;
|
export const STORY_LIST_LIMIT = 100;
|
||||||
export const API_GENERAL_ID_LIMIT = 100;
|
export const API_GENERAL_ID_LIMIT = 100;
|
||||||
export const STATISTICS_PUBLIC_FORWARDS_LIMIT = 50;
|
|
||||||
export const RESALE_GIFTS_LIMIT = 50;
|
export const RESALE_GIFTS_LIMIT = 50;
|
||||||
export const TODO_ITEMS_LIMIT = 30;
|
export const TODO_ITEMS_LIMIT = 30;
|
||||||
export const TODO_TITLE_LENGTH_LIMIT = 32;
|
export const TODO_TITLE_LENGTH_LIMIT = 32;
|
||||||
|
|||||||
@ -314,6 +314,24 @@ addActionHandler('loadMessage', async (global, actions, payload): Promise<void>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadMessagesById', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, messageIds } = payload;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = await callApi('fetchMessagesById', {
|
||||||
|
chat,
|
||||||
|
messageIds,
|
||||||
|
});
|
||||||
|
if (!messages) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = addChatMessagesById(global, chatId, buildCollectionByKey(messages, 'id'));
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('sendMessage', async (global, actions, payload): Promise<void> => {
|
addActionHandler('sendMessage', async (global, actions, payload): Promise<void> => {
|
||||||
const { messageList, tabId = getCurrentTabId() } = payload;
|
const { messageList, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { areDeepEqual } from '../../../util/areDeepEqual';
|
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
@ -39,6 +38,25 @@ addActionHandler('loadStatistics', async (global, actions, payload): Promise<voi
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateStatistics(global, chatId, stats, tabId);
|
global = updateStatistics(global, chatId, stats, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
if (stats.type === 'channel') {
|
||||||
|
const messageInteractions = stats.recentPosts.filter((post) => post.type === 'message');
|
||||||
|
const storyInteractions = stats.recentPosts.filter((post) => post.type === 'story');
|
||||||
|
|
||||||
|
if (messageInteractions.length > 0) {
|
||||||
|
actions.loadMessagesById({
|
||||||
|
chatId,
|
||||||
|
messageIds: messageInteractions.map((interaction) => interaction.msgId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storyInteractions.length > 0) {
|
||||||
|
actions.loadPeerStoriesByIds({
|
||||||
|
peerId: chatId,
|
||||||
|
storyIds: storyInteractions.map((interaction) => interaction.storyId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadChannelMonetizationStatistics', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadChannelMonetizationStatistics', async (global, actions, payload): Promise<void> => {
|
||||||
@ -122,17 +140,11 @@ addActionHandler('loadMessagePublicForwards', async (global, actions, payload):
|
|||||||
count,
|
count,
|
||||||
} = publicForwards || {};
|
} = publicForwards || {};
|
||||||
|
|
||||||
// Api returns the last element from the previous page as the first element
|
|
||||||
const shouldOmitFirstElement = stats.publicForwardsData?.length && forwards?.length
|
|
||||||
&& areDeepEqual(stats.publicForwardsData[stats.publicForwardsData.length - 1], forwards[0]);
|
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateMessageStatistics(global, {
|
global = updateMessageStatistics(global, {
|
||||||
...stats,
|
...stats,
|
||||||
publicForwards: count || forwards?.length,
|
publicForwards: count || forwards?.length,
|
||||||
publicForwardsData: (stats.publicForwardsData || []).concat(
|
publicForwardsData: (stats.publicForwardsData || []).concat((forwards || [])),
|
||||||
shouldOmitFirstElement ? forwards.slice(1) : (forwards || []),
|
|
||||||
),
|
|
||||||
nextOffset,
|
nextOffset,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -184,7 +184,9 @@ addActionHandler('toggleMessageStatistics', (global, actions, payload): ActionRe
|
|||||||
statistics: {
|
statistics: {
|
||||||
...selectTabState(global, tabId).statistics,
|
...selectTabState(global, tabId).statistics,
|
||||||
currentMessageId: messageId,
|
currentMessageId: messageId,
|
||||||
|
currentMessage: undefined,
|
||||||
currentStoryId: undefined,
|
currentStoryId: undefined,
|
||||||
|
currentStory: undefined,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
@ -196,6 +198,8 @@ addActionHandler('toggleStoryStatistics', (global, actions, payload): ActionRetu
|
|||||||
...selectTabState(global, tabId).statistics,
|
...selectTabState(global, tabId).statistics,
|
||||||
currentStoryId: storyId,
|
currentStoryId: storyId,
|
||||||
currentMessageId: undefined,
|
currentMessageId: undefined,
|
||||||
|
currentMessage: undefined,
|
||||||
|
currentStory: undefined,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -504,6 +504,10 @@ export interface ActionPayloads {
|
|||||||
isDeleting?: boolean;
|
isDeleting?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
loadMessagesById: {
|
||||||
|
chatId: string;
|
||||||
|
messageIds: number[];
|
||||||
|
};
|
||||||
editMessage: {
|
editMessage: {
|
||||||
messageList?: MessageList;
|
messageList?: MessageList;
|
||||||
text: string;
|
text: string;
|
||||||
@ -1570,7 +1574,7 @@ export interface ActionPayloads {
|
|||||||
loadPeerStoriesByIds: {
|
loadPeerStoriesByIds: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
storyIds: number[];
|
storyIds: number[];
|
||||||
} & WithTabId;
|
};
|
||||||
viewStory: {
|
viewStory: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
storyId: number;
|
storyId: number;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user