Reactions: Support limit in channels and groups (#4784)
This commit is contained in:
parent
16c442b9cf
commit
f3743bd556
@ -7,6 +7,7 @@ import type { ApiAppConfig } from '../../types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_LIMITS,
|
DEFAULT_LIMITS,
|
||||||
|
MAX_UNIQUE_REACTIONS,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
STORY_EXPIRE_PERIOD,
|
STORY_EXPIRE_PERIOD,
|
||||||
STORY_VIEWERS_EXPIRE_PERIOD,
|
STORY_VIEWERS_EXPIRE_PERIOD,
|
||||||
@ -116,7 +117,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
readDateExpiresAt: appConfig.pm_read_date_expire_period,
|
readDateExpiresAt: appConfig.pm_read_date_expire_period,
|
||||||
autologinDomains: appConfig.autologin_domains || [],
|
autologinDomains: appConfig.autologin_domains || [],
|
||||||
urlAuthDomains: appConfig.url_auth_domains || [],
|
urlAuthDomains: appConfig.url_auth_domains || [],
|
||||||
maxUniqueReactions: appConfig.reactions_uniq_max,
|
maxUniqueReactions: appConfig.reactions_uniq_max ?? MAX_UNIQUE_REACTIONS,
|
||||||
premiumBotUsername: appConfig.premium_bot_username,
|
premiumBotUsername: appConfig.premium_bot_username,
|
||||||
premiumInvoiceSlug: appConfig.premium_invoice_slug,
|
premiumInvoiceSlug: appConfig.premium_invoice_slug,
|
||||||
premiumPromoOrder: appConfig.premium_promo_order as ApiPremiumSection[],
|
premiumPromoOrder: appConfig.premium_promo_order as ApiPremiumSection[],
|
||||||
|
|||||||
@ -514,6 +514,7 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
|
|||||||
requestsPending,
|
requestsPending,
|
||||||
chatPhoto,
|
chatPhoto,
|
||||||
translationsDisabled,
|
translationsDisabled,
|
||||||
|
reactionsLimit,
|
||||||
} = result.fullChat;
|
} = result.fullChat;
|
||||||
|
|
||||||
if (chatPhoto) {
|
if (chatPhoto) {
|
||||||
@ -538,6 +539,7 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
|
|||||||
inviteLink,
|
inviteLink,
|
||||||
groupCallId: call?.id.toString(),
|
groupCallId: call?.id.toString(),
|
||||||
enabledReactions: buildApiChatReactions(availableReactions),
|
enabledReactions: buildApiChatReactions(availableReactions),
|
||||||
|
reactionsLimit,
|
||||||
requestsPending,
|
requestsPending,
|
||||||
recentRequesterIds: recentRequesters?.map((userId) => buildApiPeerId(userId, 'user')),
|
recentRequesterIds: recentRequesters?.map((userId) => buildApiPeerId(userId, 'user')),
|
||||||
isTranslationDisabled: translationsDisabled,
|
isTranslationDisabled: translationsDisabled,
|
||||||
@ -588,6 +590,7 @@ async function getFullChannelInfo(
|
|||||||
call,
|
call,
|
||||||
botInfo,
|
botInfo,
|
||||||
availableReactions,
|
availableReactions,
|
||||||
|
reactionsLimit,
|
||||||
defaultSendAs,
|
defaultSendAs,
|
||||||
requestsPending,
|
requestsPending,
|
||||||
recentRequesters,
|
recentRequesters,
|
||||||
@ -670,6 +673,7 @@ async function getFullChannelInfo(
|
|||||||
linkedChatId: linkedChatId ? buildApiPeerId(linkedChatId, 'channel') : undefined,
|
linkedChatId: linkedChatId ? buildApiPeerId(linkedChatId, 'channel') : undefined,
|
||||||
botCommands,
|
botCommands,
|
||||||
enabledReactions: buildApiChatReactions(availableReactions),
|
enabledReactions: buildApiChatReactions(availableReactions),
|
||||||
|
reactionsLimit,
|
||||||
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
|
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
|
||||||
requestsPending,
|
requestsPending,
|
||||||
recentRequesterIds: recentRequesters?.map((userId) => buildApiPeerId(userId, 'user')),
|
recentRequesterIds: recentRequesters?.map((userId) => buildApiPeerId(userId, 'user')),
|
||||||
@ -1568,13 +1572,14 @@ export async function importChatInvite({ hash }: { hash: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setChatEnabledReactions({
|
export function setChatEnabledReactions({
|
||||||
chat, enabledReactions,
|
chat, enabledReactions, reactionsLimit,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; enabledReactions?: ApiChatReactions;
|
chat: ApiChat; enabledReactions?: ApiChatReactions; reactionsLimit?: number;
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.messages.SetChatAvailableReactions({
|
return invokeRequest(new GramJs.messages.SetChatAvailableReactions({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
availableReactions: buildInputChatReactions(enabledReactions),
|
availableReactions: buildInputChatReactions(enabledReactions),
|
||||||
|
reactionsLimit,
|
||||||
}), {
|
}), {
|
||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -123,6 +123,7 @@ export interface ApiChatFullInfo {
|
|||||||
linkedChatId?: string;
|
linkedChatId?: string;
|
||||||
botCommands?: ApiBotCommand[];
|
botCommands?: ApiBotCommand[];
|
||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
|
reactionsLimit?: number;
|
||||||
sendAsId?: string;
|
sendAsId?: string;
|
||||||
canViewStatistics?: boolean;
|
canViewStatistics?: boolean;
|
||||||
recentRequesterIds?: string[];
|
recentRequesterIds?: string[];
|
||||||
|
|||||||
@ -124,7 +124,7 @@ type StateProps = {
|
|||||||
canShowSeenBy?: boolean;
|
canShowSeenBy?: boolean;
|
||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
canScheduleUntilOnline?: boolean;
|
canScheduleUntilOnline?: boolean;
|
||||||
maxUniqueReactions?: number;
|
reactionsLimit?: number;
|
||||||
canPlayAnimatedEmojis?: boolean;
|
canPlayAnimatedEmojis?: boolean;
|
||||||
isReactionPickerOpen?: boolean;
|
isReactionPickerOpen?: boolean;
|
||||||
isInSavedMessages?: boolean;
|
isInSavedMessages?: boolean;
|
||||||
@ -161,7 +161,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
canShowReactionList,
|
canShowReactionList,
|
||||||
canEdit,
|
canEdit,
|
||||||
enabledReactions,
|
enabledReactions,
|
||||||
maxUniqueReactions,
|
reactionsLimit,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
canForward,
|
canForward,
|
||||||
@ -576,7 +576,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
canBuyPremium={canBuyPremium}
|
canBuyPremium={canBuyPremium}
|
||||||
isOpen={isMenuOpen}
|
isOpen={isMenuOpen}
|
||||||
enabledReactions={enabledReactions}
|
enabledReactions={enabledReactions}
|
||||||
maxUniqueReactions={maxUniqueReactions}
|
reactionsLimit={reactionsLimit}
|
||||||
anchor={anchor}
|
anchor={anchor}
|
||||||
targetHref={targetHref}
|
targetHref={targetHref}
|
||||||
canShowReactionsCount={canShowReactionsCount}
|
canShowReactionsCount={canShowReactionsCount}
|
||||||
@ -674,9 +674,15 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const activeDownloads = selectActiveDownloads(global);
|
const activeDownloads = selectActiveDownloads(global);
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
|
const isPrivate = chat && isUserId(chat.id);
|
||||||
|
const chatFullInfo = !isPrivate ? selectChatFullInfo(global, message.chatId) : undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions, readDateExpiresAt,
|
seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions, readDateExpiresAt,
|
||||||
} = global.appConfig || {};
|
} = global.appConfig || {};
|
||||||
|
|
||||||
|
const reactionsLimit = chatFullInfo?.reactionsLimit || maxUniqueReactions;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
noOptions,
|
noOptions,
|
||||||
canReply,
|
canReply,
|
||||||
@ -697,7 +703,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canClosePoll,
|
canClosePoll,
|
||||||
} = (threadId && selectAllowedMessageActions(global, message, threadId)) || {};
|
} = (threadId && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||||
|
|
||||||
const isPrivate = chat && isUserId(chat.id);
|
|
||||||
const userStatus = isPrivate ? selectUserStatus(global, chat.id) : undefined;
|
const userStatus = isPrivate ? selectUserStatus(global, chat.id) : undefined;
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
const isMessageUnread = selectIsMessageUnread(global, message);
|
const isMessageUnread = selectIsMessageUnread(global, message);
|
||||||
@ -731,7 +736,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& chat.membersCount <= seenByMaxChatMembers
|
&& chat.membersCount <= seenByMaxChatMembers
|
||||||
&& message.date > Date.now() / 1000 - seenByExpiresAt);
|
&& message.date > Date.now() / 1000 - seenByExpiresAt);
|
||||||
const isAction = isActionMessage(message);
|
const isAction = isActionMessage(message);
|
||||||
const chatFullInfo = !isPrivate ? selectChatFullInfo(global, message.chatId) : undefined;
|
|
||||||
const canShowReactionsCount = !isLocal && !isChannel && !isScheduled && !isAction && !isPrivate && message.reactions
|
const canShowReactionsCount = !isLocal && !isChannel && !isScheduled && !isAction && !isPrivate && message.reactions
|
||||||
&& !areReactionsEmpty(message.reactions) && message.reactions.canSeeList;
|
&& !areReactionsEmpty(message.reactions) && message.reactions.canSeeList;
|
||||||
const isProtected = selectIsMessageProtected(global, message);
|
const isProtected = selectIsMessageProtected(global, message);
|
||||||
@ -781,7 +785,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canLoadReadDate,
|
canLoadReadDate,
|
||||||
shouldRenderShowWhen,
|
shouldRenderShowWhen,
|
||||||
enabledReactions: chat?.isForbidden ? undefined : chatFullInfo?.enabledReactions,
|
enabledReactions: chat?.isForbidden ? undefined : chatFullInfo?.enabledReactions,
|
||||||
maxUniqueReactions,
|
reactionsLimit,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
hasFullInfo: Boolean(chatFullInfo),
|
hasFullInfo: Boolean(chatFullInfo),
|
||||||
|
|||||||
@ -52,7 +52,7 @@ type OwnProps = {
|
|||||||
message: ApiMessage | ApiSponsoredMessage;
|
message: ApiMessage | ApiSponsoredMessage;
|
||||||
canSendNow?: boolean;
|
canSendNow?: boolean;
|
||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
maxUniqueReactions?: number;
|
reactionsLimit?: number;
|
||||||
canReschedule?: boolean;
|
canReschedule?: boolean;
|
||||||
canReply?: boolean;
|
canReply?: boolean;
|
||||||
canQuote?: boolean;
|
canQuote?: boolean;
|
||||||
@ -140,7 +140,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
enabledReactions,
|
enabledReactions,
|
||||||
maxUniqueReactions,
|
reactionsLimit,
|
||||||
anchor,
|
anchor,
|
||||||
targetHref,
|
targetHref,
|
||||||
canSendNow,
|
canSendNow,
|
||||||
@ -364,7 +364,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
allAvailableReactions={availableReactions}
|
allAvailableReactions={availableReactions}
|
||||||
defaultTagReactions={defaultTagReactions}
|
defaultTagReactions={defaultTagReactions}
|
||||||
currentReactions={!isSponsoredMessage ? message.reactions?.results : undefined}
|
currentReactions={!isSponsoredMessage ? message.reactions?.results : undefined}
|
||||||
maxUniqueReactions={maxUniqueReactions}
|
reactionsLimit={reactionsLimit}
|
||||||
onToggleReaction={onToggleReaction!}
|
onToggleReaction={onToggleReaction!}
|
||||||
isPrivate={isPrivate}
|
isPrivate={isPrivate}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ type OwnProps = {
|
|||||||
effectReactions?: ApiReaction[];
|
effectReactions?: ApiReaction[];
|
||||||
allAvailableReactions?: ApiAvailableReaction[];
|
allAvailableReactions?: ApiAvailableReaction[];
|
||||||
currentReactions?: ApiReactionCount[];
|
currentReactions?: ApiReactionCount[];
|
||||||
maxUniqueReactions?: number;
|
reactionsLimit?: number;
|
||||||
isReady?: boolean;
|
isReady?: boolean;
|
||||||
canBuyPremium?: boolean;
|
canBuyPremium?: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
@ -54,7 +54,7 @@ const ReactionSelector: FC<OwnProps> = ({
|
|||||||
defaultTagReactions,
|
defaultTagReactions,
|
||||||
enabledReactions,
|
enabledReactions,
|
||||||
currentReactions,
|
currentReactions,
|
||||||
maxUniqueReactions,
|
reactionsLimit,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isReady,
|
isReady,
|
||||||
canPlayAnimatedEmojis,
|
canPlayAnimatedEmojis,
|
||||||
@ -75,8 +75,8 @@ const ReactionSelector: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const areReactionsLocked = isInSavedMessages && !isCurrentUserPremium && !isInStoryViewer;
|
const areReactionsLocked = isInSavedMessages && !isCurrentUserPremium && !isInStoryViewer;
|
||||||
|
|
||||||
const shouldUseCurrentReactions = Boolean(maxUniqueReactions
|
const shouldUseCurrentReactions = Boolean(reactionsLimit
|
||||||
&& currentReactions && currentReactions.length >= maxUniqueReactions);
|
&& currentReactions && currentReactions.length >= reactionsLimit);
|
||||||
|
|
||||||
const availableReactions = useMemo(() => {
|
const availableReactions = useMemo(() => {
|
||||||
const reactions = (() => {
|
const reactions = (() => {
|
||||||
@ -86,6 +86,7 @@ const ReactionSelector: FC<OwnProps> = ({
|
|||||||
if (enabledReactions?.type === 'some') return enabledReactions.allowed;
|
if (enabledReactions?.type === 'some') return enabledReactions.allowed;
|
||||||
return allAvailableReactions?.map((reaction) => reaction.reaction);
|
return allAvailableReactions?.map((reaction) => reaction.reaction);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const filteredReactions = reactions?.map((reaction) => {
|
const filteredReactions = reactions?.map((reaction) => {
|
||||||
const isCustomReaction = 'documentId' in reaction;
|
const isCustomReaction = 'documentId' in reaction;
|
||||||
const availableReaction = allAvailableReactions?.find((r) => isSameReaction(r.reaction, reaction));
|
const availableReaction = allAvailableReactions?.find((r) => isSameReaction(r.reaction, reaction));
|
||||||
@ -106,6 +107,7 @@ const ReactionSelector: FC<OwnProps> = ({
|
|||||||
}, [
|
}, [
|
||||||
allAvailableReactions, currentReactions, defaultTagReactions, enabledReactions, isInSavedMessages, isPrivate,
|
allAvailableReactions, currentReactions, defaultTagReactions, enabledReactions, isInSavedMessages, isPrivate,
|
||||||
topReactions, isForEffects, effectReactions, shouldUseCurrentReactions,
|
topReactions, isForEffects, effectReactions, shouldUseCurrentReactions,
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const reactionsToRender = useMemo(() => {
|
const reactionsToRender = useMemo(() => {
|
||||||
|
|||||||
@ -9,7 +9,10 @@ import type {
|
|||||||
ApiAvailableReaction, ApiChat, ApiChatReactions, ApiReaction,
|
ApiAvailableReaction, ApiChat, ApiChatReactions, ApiReaction,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { isSameReaction } from '../../../global/helpers';
|
import {
|
||||||
|
MAX_UNIQUE_REACTIONS,
|
||||||
|
} from '../../../config';
|
||||||
|
import { isChatChannel, isSameReaction } from '../../../global/helpers';
|
||||||
import { selectChat, selectChatFullInfo } from '../../../global/selectors';
|
import { selectChat, selectChatFullInfo } from '../../../global/selectors';
|
||||||
|
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
@ -19,6 +22,7 @@ import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
|||||||
import Checkbox from '../../ui/Checkbox';
|
import Checkbox from '../../ui/Checkbox';
|
||||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||||
import RadioGroup from '../../ui/RadioGroup';
|
import RadioGroup from '../../ui/RadioGroup';
|
||||||
|
import RangeSlider from '../../ui/RangeSlider';
|
||||||
import Spinner from '../../ui/Spinner';
|
import Spinner from '../../ui/Spinner';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -31,6 +35,9 @@ type StateProps = {
|
|||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
availableReactions?: ApiAvailableReaction[];
|
availableReactions?: ApiAvailableReaction[];
|
||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
|
maxUniqueReactions: number;
|
||||||
|
reactionsLimit?: number;
|
||||||
|
isChannel?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ManageReactions: FC<OwnProps & StateProps> = ({
|
const ManageReactions: FC<OwnProps & StateProps> = ({
|
||||||
@ -39,6 +46,9 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
|
|||||||
chat,
|
chat,
|
||||||
isActive,
|
isActive,
|
||||||
onClose,
|
onClose,
|
||||||
|
maxUniqueReactions,
|
||||||
|
reactionsLimit,
|
||||||
|
isChannel,
|
||||||
}) => {
|
}) => {
|
||||||
const { setChatEnabledReactions } = getActions();
|
const { setChatEnabledReactions } = getActions();
|
||||||
|
|
||||||
@ -47,6 +57,8 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [localEnabledReactions, setLocalEnabledReactions] = useState<ApiChatReactions | undefined>(enabledReactions);
|
const [localEnabledReactions, setLocalEnabledReactions] = useState<ApiChatReactions | undefined>(enabledReactions);
|
||||||
|
|
||||||
|
const [localReactionsLimit, setLocalReactionsLimit] = useState(reactionsLimit);
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
onBack: onClose,
|
onBack: onClose,
|
||||||
@ -70,33 +82,80 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
|
|||||||
setChatEnabledReactions({
|
setChatEnabledReactions({
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
enabledReactions: localEnabledReactions,
|
enabledReactions: localEnabledReactions,
|
||||||
|
reactionsLimit: localReactionsLimit,
|
||||||
});
|
});
|
||||||
}, [chat, localEnabledReactions, setChatEnabledReactions]);
|
}, [chat, localEnabledReactions, setChatEnabledReactions, localReactionsLimit]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setIsTouched(false);
|
setIsTouched(false);
|
||||||
setLocalEnabledReactions(enabledReactions);
|
setLocalEnabledReactions(enabledReactions);
|
||||||
}, [enabledReactions]);
|
setLocalReactionsLimit(reactionsLimit);
|
||||||
|
}, [enabledReactions, reactionsLimit]);
|
||||||
|
|
||||||
const availableActiveReactions = useMemo<ApiAvailableReaction[] | undefined>(
|
const availableActiveReactions = useMemo<ApiAvailableReaction[] | undefined>(
|
||||||
() => availableReactions?.filter(({ isInactive }) => !isInactive),
|
() => availableReactions?.filter(({ isInactive }) => !isInactive),
|
||||||
[availableReactions],
|
[availableReactions],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localReactionsLimit !== undefined && localReactionsLimit !== reactionsLimit) {
|
||||||
|
setIsTouched(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localEnabledReactions?.type === 'some') {
|
||||||
|
const isReactionsDisabled = enabledReactions?.type !== 'all' && enabledReactions?.type !== 'some';
|
||||||
|
|
||||||
|
if (isReactionsDisabled && localEnabledReactions.allowed.length === 0) {
|
||||||
|
setIsTouched(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localEnabledReactions?.type !== enabledReactions?.type) {
|
||||||
|
setIsTouched(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localEnabledReactions?.type === 'some' && enabledReactions?.type === 'some') {
|
||||||
|
const localAllowedReactions = localEnabledReactions.allowed;
|
||||||
|
const enabledAllowedReactions = enabledReactions?.allowed;
|
||||||
|
|
||||||
|
if (localAllowedReactions.length !== enabledAllowedReactions.length
|
||||||
|
|| localAllowedReactions.reverse().some(
|
||||||
|
(localReaction) => !enabledAllowedReactions.find(
|
||||||
|
(enabledReaction) => isSameReaction(localReaction, enabledReaction),
|
||||||
|
),
|
||||||
|
)) {
|
||||||
|
setIsTouched(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsTouched(false);
|
||||||
|
}, [
|
||||||
|
localReactionsLimit,
|
||||||
|
reactionsLimit,
|
||||||
|
localEnabledReactions,
|
||||||
|
enabledReactions,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleReactionsOptionChange = useCallback((value: string) => {
|
const handleReactionsOptionChange = useCallback((value: string) => {
|
||||||
if (value === 'all') {
|
if (value === 'all') {
|
||||||
setLocalEnabledReactions({ type: 'all' });
|
setLocalEnabledReactions({ type: 'all' });
|
||||||
|
setLocalReactionsLimit(reactionsLimit);
|
||||||
} else if (value === 'some') {
|
} else if (value === 'some') {
|
||||||
setLocalEnabledReactions({
|
setLocalEnabledReactions({
|
||||||
type: 'some',
|
type: 'some',
|
||||||
allowed: enabledReactions?.type === 'some' ? enabledReactions.allowed : [],
|
allowed: enabledReactions?.type === 'some' ? enabledReactions.allowed : [],
|
||||||
});
|
});
|
||||||
|
setLocalReactionsLimit(reactionsLimit);
|
||||||
} else {
|
} else {
|
||||||
setLocalEnabledReactions(undefined);
|
setLocalEnabledReactions(undefined);
|
||||||
|
setLocalReactionsLimit(undefined);
|
||||||
}
|
}
|
||||||
setIsTouched(true);
|
}, [enabledReactions, reactionsLimit]);
|
||||||
}, [enabledReactions]);
|
|
||||||
|
|
||||||
const handleReactionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleReactionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!chat || !availableActiveReactions) return;
|
if (!chat || !availableActiveReactions) return;
|
||||||
@ -116,12 +175,40 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setIsTouched(true);
|
|
||||||
}, [availableActiveReactions, chat, localEnabledReactions]);
|
}, [availableActiveReactions, chat, localEnabledReactions]);
|
||||||
|
|
||||||
|
const handleReactionsLimitChange = useCallback((value: number) => {
|
||||||
|
setLocalReactionsLimit(value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderReactionsMaxCountValue = useCallback((value: number) => {
|
||||||
|
return lang('PeerInfo.AllowedReactions.MaxCountValue', value);
|
||||||
|
}, [lang]);
|
||||||
|
|
||||||
|
const shouldShowReactionsLimit = isChannel
|
||||||
|
&& (localEnabledReactions?.type === 'all' || localEnabledReactions?.type === 'some');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Management">
|
<div className="Management">
|
||||||
<div className="custom-scroll">
|
<div className="custom-scroll">
|
||||||
|
{ localReactionsLimit && shouldShowReactionsLimit && (
|
||||||
|
<div className="section">
|
||||||
|
<h3 className="section-heading">
|
||||||
|
{lang('MaximumReactionsHeader')}
|
||||||
|
</h3>
|
||||||
|
<RangeSlider
|
||||||
|
min={1}
|
||||||
|
max={maxUniqueReactions}
|
||||||
|
value={localReactionsLimit}
|
||||||
|
onChange={handleReactionsLimitChange}
|
||||||
|
renderValue={renderReactionsMaxCountValue}
|
||||||
|
isCenteredLayout
|
||||||
|
/>
|
||||||
|
<p className="section-info mt-4">
|
||||||
|
{lang('ChannelReactions.MaxCount.Info')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<h3 className="section-heading">
|
<h3 className="section-heading">
|
||||||
{lang('AvailableReactions')}
|
{lang('AvailableReactions')}
|
||||||
@ -141,7 +228,7 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
|
|||||||
{localEnabledReactions?.type === 'some' && (
|
{localEnabledReactions?.type === 'some' && (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<h3 className="section-heading">
|
<h3 className="section-heading">
|
||||||
{lang('AvailableReactions')}
|
{lang('OnlyAllowThisReactions')}
|
||||||
</h3>
|
</h3>
|
||||||
{availableActiveReactions?.map(({ reaction, title }) => (
|
{availableActiveReactions?.map(({ reaction, title }) => (
|
||||||
<div className="ListItem">
|
<div className="ListItem">
|
||||||
@ -181,11 +268,19 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId)!;
|
const chat = selectChat(global, chatId)!;
|
||||||
|
const { maxUniqueReactions = MAX_UNIQUE_REACTIONS } = global.appConfig || {};
|
||||||
|
|
||||||
|
const chatFullInfo = selectChatFullInfo(global, chatId);
|
||||||
|
const reactionsLimit = chatFullInfo?.reactionsLimit || maxUniqueReactions;
|
||||||
|
const isChannel = isChatChannel(chat);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabledReactions: selectChatFullInfo(global, chatId)?.enabledReactions,
|
enabledReactions: chatFullInfo?.enabledReactions,
|
||||||
availableReactions: global.reactions.availableReactions,
|
availableReactions: global.reactions.availableReactions,
|
||||||
chat,
|
chat,
|
||||||
|
maxUniqueReactions,
|
||||||
|
reactionsLimit,
|
||||||
|
isChannel,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(global, { chatId }) => {
|
(global, { chatId }) => {
|
||||||
|
|||||||
@ -135,6 +135,10 @@
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RangeSlider {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.radio-group {
|
.radio-group {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
|
|
||||||
|
|||||||
@ -30,12 +30,19 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
|
.value-min,
|
||||||
|
.value-max,
|
||||||
.value {
|
.value {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.value-min,
|
||||||
|
.value-max {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&[dir="rtl"] {
|
&[dir="rtl"] {
|
||||||
.value {
|
.value {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@ -90,7 +97,8 @@
|
|||||||
|
|
||||||
// Apply custom styles
|
// Apply custom styles
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
// Note that while we're repeating code here, that's necessary as you can't comma-separate these type of selectors.
|
// Note that while we're repeating code here, that's
|
||||||
|
// necessary as you can't comma-separate these type of selectors.
|
||||||
// Browsers will drop the entire selector if it doesn't understand a part of it.
|
// Browsers will drop the entire selector if it doesn't understand a part of it.
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
@include thumb-styles();
|
@include thumb-styles();
|
||||||
|
|||||||
@ -20,6 +20,7 @@ type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
renderValue?: (value: number) => string;
|
renderValue?: (value: number) => string;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
|
isCenteredLayout?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RangeSlider: FC<OwnProps> = ({
|
const RangeSlider: FC<OwnProps> = ({
|
||||||
@ -34,6 +35,7 @@ const RangeSlider: FC<OwnProps> = ({
|
|||||||
className,
|
className,
|
||||||
renderValue,
|
renderValue,
|
||||||
onChange,
|
onChange,
|
||||||
|
isCenteredLayout,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -56,16 +58,38 @@ const RangeSlider: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [options, value, max, min, step]);
|
}, [options, value, max, min, step]);
|
||||||
|
|
||||||
return (
|
function renderTopRow() {
|
||||||
<div className={mainClassName}>
|
if (isCenteredLayout) {
|
||||||
{label && (
|
return (
|
||||||
<div className="slider-top-row" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="slider-top-row" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<span className="label" dir="auto">{label}</span>
|
|
||||||
{!options && (
|
{!options && (
|
||||||
<span className="value" dir="auto">{renderValue ? renderValue(value) : value}</span>
|
<>
|
||||||
|
<span className="value-min" dir="auto">{min}</span>
|
||||||
|
<span className="label" dir="auto">{renderValue ? renderValue(value) : value}</span>
|
||||||
|
<span className="value-max" dir="auto">{max}</span>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!label) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="slider-top-row" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
<span className="label" dir="auto">{label}</span>
|
||||||
|
{!options && (
|
||||||
|
<span className="value" dir="auto">{renderValue ? renderValue(value) : value}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={mainClassName}>
|
||||||
|
{renderTopRow()}
|
||||||
<div className="slider-main">
|
<div className="slider-main">
|
||||||
<div
|
<div
|
||||||
className="slider-fill-track"
|
className="slider-fill-track"
|
||||||
|
|||||||
@ -339,6 +339,7 @@ export const PEER_COLOR_BG_OPACITY = '1a';
|
|||||||
export const PEER_COLOR_BG_ACTIVE_OPACITY = '2b';
|
export const PEER_COLOR_BG_ACTIVE_OPACITY = '2b';
|
||||||
export const PEER_COLOR_GRADIENT_STEP = 5; // px
|
export const PEER_COLOR_GRADIENT_STEP = 5; // px
|
||||||
export const MAX_UPLOAD_FILEPART_SIZE = 524288;
|
export const MAX_UPLOAD_FILEPART_SIZE = 524288;
|
||||||
|
export const MAX_UNIQUE_REACTIONS = 11;
|
||||||
|
|
||||||
// Group calls
|
// Group calls
|
||||||
export const GROUP_CALL_VOLUME_MULTIPLIER = 100;
|
export const GROUP_CALL_VOLUME_MULTIPLIER = 100;
|
||||||
|
|||||||
@ -1953,13 +1953,16 @@ addActionHandler('toggleIsProtected', (global, actions, payload): ActionReturnTy
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('setChatEnabledReactions', async (global, actions, payload): Promise<void> => {
|
addActionHandler('setChatEnabledReactions', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, enabledReactions, tabId = getCurrentTabId() } = payload;
|
const {
|
||||||
|
chatId, enabledReactions, reactionsLimit, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
await callApi('setChatEnabledReactions', {
|
await callApi('setChatEnabledReactions', {
|
||||||
chat,
|
chat,
|
||||||
enabledReactions,
|
enabledReactions,
|
||||||
|
reactionsLimit,
|
||||||
});
|
});
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|||||||
@ -2335,6 +2335,7 @@ export interface ActionPayloads {
|
|||||||
setChatEnabledReactions: {
|
setChatEnabledReactions: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
|
reactionsLimit?: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
startActiveReaction: {
|
startActiveReaction: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user