Reactions: Support limit in channels and groups (#4784)

This commit is contained in:
Alexander Zinchuk 2024-08-06 20:06:41 +02:00
parent 16c442b9cf
commit f3743bd556
13 changed files with 181 additions and 32 deletions

View File

@ -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[],

View File

@ -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,
}); });

View File

@ -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[];

View File

@ -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),

View File

@ -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}

View File

@ -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(() => {

View File

@ -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 }) => {

View File

@ -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;

View File

@ -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();

View File

@ -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"

View File

@ -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;

View File

@ -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();

View File

@ -2335,6 +2335,7 @@ export interface ActionPayloads {
setChatEnabledReactions: { setChatEnabledReactions: {
chatId: string; chatId: string;
enabledReactions?: ApiChatReactions; enabledReactions?: ApiChatReactions;
reactionsLimit?: number;
} & WithTabId; } & WithTabId;
startActiveReaction: { startActiveReaction: {