Some fixes

This commit is contained in:
Eduard Kuzmenko 2022-09-25 21:49:33 +04:00
parent 274f611bdd
commit 6a3a923310
21 changed files with 338 additions and 78 deletions

View File

@ -282,7 +282,7 @@ export default class BubbleGroups {
} }
removeItem(item: GroupItem) { removeItem(item: GroupItem) {
item.group.removeItem(item); item.group?.removeItem(item);
this.removeItemFromCache(item); this.removeItemFromCache(item);
} }
@ -298,10 +298,12 @@ export default class BubbleGroups {
const group = item.group; const group = item.group;
this.removeItem(item); this.removeItem(item);
group.unmountItem(item);
const modifiedGroups: Set<BubbleGroup> = new Set(); const modifiedGroups: Set<BubbleGroup> = new Set();
modifiedGroups.add(group); if(group) {
group.unmountItem(item);
modifiedGroups.add(group);
}
const [previousSibling, nextSibling] = siblings; const [previousSibling, nextSibling] = siblings;
if( if(

View File

@ -1214,7 +1214,7 @@ export default class ChatBubbles {
private onBubblesMouseMove = async(e: MouseEvent) => { private onBubblesMouseMove = async(e: MouseEvent) => {
const content = findUpClassName(e.target, 'bubble-content'); const content = findUpClassName(e.target, 'bubble-content');
if(content && !this.chat.selection.isSelecting) { if(content && !this.chat.selection.isSelecting && !findUpClassName(e.target, 'service')) {
const bubble = findUpClassName(content, 'bubble'); const bubble = findUpClassName(content, 'bubble');
if(!this.chat.selection.canSelectBubble(bubble)) { if(!this.chat.selection.canSelectBubble(bubble)) {
this.unhoverPrevious(); this.unhoverPrevious();
@ -1241,7 +1241,12 @@ export default class ChatBubbles {
content.append(hoverReaction); content.append(hoverReaction);
let message = (await this.chat.getMessage(+bubble.dataset.mid)) as Message.message; let message = await this.chat.getMessage(+bubble.dataset.mid);
if(message?._ !== 'message') {
this.unhoverPrevious();
return;
}
message = await this.managers.appMessagesManager.getGroupsFirstMessage(message); message = await this.managers.appMessagesManager.getGroupsFirstMessage(message);
const middleware = this.getMiddleware(() => this.hoverReaction === hoverReaction); const middleware = this.getMiddleware(() => this.hoverReaction === hoverReaction);
@ -1281,7 +1286,7 @@ export default class ChatBubbles {
attachClickEvent(hoverReaction, (e) => { attachClickEvent(hoverReaction, (e) => {
cancelEvent(e); // cancel triggering selection cancelEvent(e); // cancel triggering selection
this.managers.appReactionsManager.sendReaction(message, availableReaction); this.managers.appReactionsManager.sendReaction(message as Message.message, availableReaction);
this.unhoverPrevious(); this.unhoverPrevious();
}, {listenerSetter: this.listenerSetter}); }, {listenerSetter: this.listenerSetter});
}, noop); }, noop);
@ -4407,6 +4412,7 @@ export default class ChatBubbles {
bubble.classList.add('must-have-name'); bubble.classList.add('must-have-name');
} }
const isForward = fwdFromId || fwdFrom;
if(isHidden) { if(isHidden) {
// /////this.log('message to render hidden', message); // /////this.log('message to render hidden', message);
title = document.createElement('span'); title = document.createElement('span');
@ -4415,7 +4421,7 @@ export default class ChatBubbles {
// title = fwdFrom.from_name; // title = fwdFrom.from_name;
bubble.classList.add('hidden-profile'); bubble.classList.add('hidden-profile');
} else { } else {
title = new PeerTitle({peerId: fwdFromId || message.fromId, withPremiumIcon: true}).element; title = new PeerTitle({peerId: fwdFromId || message.fromId, withPremiumIcon: !isForward}).element;
} }
if(message.reply_to_mid && message.reply_to_mid !== this.chat.threadId && isMessage) { if(message.reply_to_mid && message.reply_to_mid !== this.chat.threadId && isMessage) {
@ -4430,7 +4436,7 @@ export default class ChatBubbles {
// this.log(title); // this.log(title);
let nameDiv: HTMLElement; let nameDiv: HTMLElement;
if((fwdFromId || fwdFrom)) { if(isForward) {
if(this.peerId !== rootScope.myId && !isForwardFromChannel) { if(this.peerId !== rootScope.myId && !isForwardFromChannel) {
bubble.classList.add('forwarded'); bubble.classList.add('forwarded');
} }

View File

@ -9,9 +9,9 @@ import type {Dialog} from '../lib/appManagers/appMessagesManager';
import rootScope from '../lib/rootScope'; import rootScope from '../lib/rootScope';
import ButtonMenu, {ButtonMenuItemOptions} from './buttonMenu'; import ButtonMenu, {ButtonMenuItemOptions} from './buttonMenu';
import PopupDeleteDialog from './popups/deleteDialog'; import PopupDeleteDialog from './popups/deleteDialog';
import {i18n} from '../lib/langPack'; import {i18n, LangPackKey, _i18n} from '../lib/langPack';
import findUpTag from '../helpers/dom/findUpTag'; import findUpTag from '../helpers/dom/findUpTag';
import PopupPeer from './popups/peer'; import PopupPeer, {PopupPeerButton} from './popups/peer';
import AppChatFoldersTab from './sidebarLeft/tabs/chatFolders'; import AppChatFoldersTab from './sidebarLeft/tabs/chatFolders';
import appSidebarLeft from './sidebarLeft'; import appSidebarLeft from './sidebarLeft';
import {toastNew} from './toast'; import {toastNew} from './toast';
@ -19,6 +19,7 @@ import PopupMute from './popups/mute';
import {AppManagers} from '../lib/appManagers/managers'; import {AppManagers} from '../lib/appManagers/managers';
import positionMenu from '../helpers/positionMenu'; import positionMenu from '../helpers/positionMenu';
import contextMenuController from '../helpers/contextMenuController'; import contextMenuController from '../helpers/contextMenuController';
import type {ApiLimitType} from '../lib/mtproto/api_methods';
export default class DialogsContextMenu { export default class DialogsContextMenu {
private element: HTMLElement; private element: HTMLElement;
@ -113,6 +114,107 @@ export default class DialogsContextMenu {
if(this.filterId >= 1) { if(this.filterId >= 1) {
toastNew({langPackKey: 'PinFolderLimitReached'}); toastNew({langPackKey: 'PinFolderLimitReached'});
} else { } else {
// const a: {[type in ApiLimitType]?: {
// title: LangPackKey,
// description: LangPackKey,
// descriptionPremium: LangPackKey,
// descriptionLocked: LangPackKey,
// icon: string
// }} = {
// pin: {
// title: 'LimitReached',
// description: 'LimitReachedPinDialogs',
// descriptionPremium: 'LimitReachedPinDialogsPremium',
// descriptionLocked: 'LimitReachedPinDialogsLocked',
// icon: 'limit_pin'
// }
// };
// class P extends PopupPeer {
// constructor(options: {
// isPremium: boolean,
// limit: number,
// limitPremium: number
// }, _a: typeof a[keyof typeof a]) {
// super('popup-limit', {
// buttons: options.isPremium === undefined ? [{
// langKey: 'LimitReached.Ok',
// isCancel: true
// }] : (options.isPremium ? [{
// langKey: 'OK',
// isCancel: true
// }] : [{
// langKey: 'IncreaseLimit',
// callback: () => {
// }
// }, {
// langKey: 'Cancel',
// isCancel: true
// }]),
// descriptionLangKey: options.isPremium === undefined ? _a.descriptionLocked : (options.isPremium ? _a.descriptionPremium : _a.description),
// descriptionLangArgs: options.isPremium ? [options.limitPremium] : [options.limit, options.limitPremium],
// titleLangKey: _a.title
// });
// const isLocked = options.isPremium === undefined;
// if(isLocked) {
// this.element.classList.add('is-locked');
// }
// const limitContainer = document.createElement('div');
// limitContainer.classList.add('popup-limit-line');
// const hint = document.createElement('div');
// hint.classList.add('popup-limit-hint');
// const i = document.createElement('span');
// i.classList.add('popup-limit-hint-icon', 'tgico-' + _a.icon);
// hint.append(i, '' + (options.isPremium ? options.limitPremium : options.limit));
// limitContainer.append(hint);
// if(!isLocked) {
// const limit = document.createElement('div');
// limit.classList.add('limit-line');
// const free = document.createElement('div');
// free.classList.add('limit-line-free');
// const premium = document.createElement('div');
// premium.classList.add('limit-line-premium');
// limit.append(free, premium);
// _i18n(free, 'LimitFree');
// premium.append(i18n('LimitPremium'), '' + options.limitPremium);
// limitContainer.append(limit);
// }
// this.container.insertBefore(limitContainer, this.description);
// if(options.isPremium === false) {
// this.buttons.pop().element.remove();
// }
// }
// }
// async function showLimitPopup(type: keyof typeof a) {
// const _a = a[type];
// const [appConfig, limit, limitPremium] = await Promise.all([
// rootScope.managers.apiManager.getAppConfig(),
// ...[false, true].map((v) => rootScope.managers.apiManager.getLimit(type, v))
// ]);
// const isLocked = appConfig.premium_purchase_blocked;
// new P({
// isPremium: isLocked ? undefined : rootScope.premium,
// limit,
// limitPremium
// }, _a).show();
// }
// showLimitPopup('pin');
const config = await this.managers.apiManager.getConfig(); const config = await this.managers.apiManager.getConfig();
new PopupPeer('pinned-dialogs-too-much', { new PopupPeer('pinned-dialogs-too-much', {
buttons: [{ buttons: [{

View File

@ -298,8 +298,7 @@ class EmoticonsTabC {
export default class StickersTab extends EmoticonsTabC implements EmoticonsTab { export default class StickersTab extends EmoticonsTabC implements EmoticonsTab {
private superStickerRenderer: SuperStickerRenderer; private superStickerRenderer: SuperStickerRenderer;
private setFavedLimit(appConfig: MTAppConfig) { private setFavedLimit(limit: number) {
const limit = rootScope.premium ? appConfig.stickers_faved_limit_premium : appConfig.stickers_faved_limit_default;
const category = this.categories['faved']; const category = this.categories['faved'];
category.limit = limit; category.limit = limit;
} }
@ -494,10 +493,10 @@ export default class StickersTab extends EmoticonsTabC implements EmoticonsTab {
const promises = [ const promises = [
Promise.all([ Promise.all([
this.managers.apiManager.getAppConfig(), this.managers.apiManager.getLimit('favedStickers'),
this.managers.appStickersManager.getFavedStickersStickers() this.managers.appStickersManager.getFavedStickersStickers()
]).then(([appConfig, stickers]) => { ]).then(([limit, stickers]) => {
this.setFavedLimit(appConfig); this.setFavedLimit(limit);
onCategoryStickers(favedCategory, stickers); onCategoryStickers(favedCategory, stickers);
}), }),
@ -604,7 +603,9 @@ export default class StickersTab extends EmoticonsTabC implements EmoticonsTab {
}); });
rootScope.addEventListener('app_config', (appConfig) => { rootScope.addEventListener('app_config', (appConfig) => {
this.setFavedLimit(appConfig); this.managers.apiManager.getLimit('favedStickers').then((limit) => {
this.setFavedLimit(limit);
});
}); });
const resizeCategories = () => { const resizeCategories = () => {

View File

@ -209,6 +209,7 @@ export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void
export const RadioFormFromValues = (values: {langPackKey: LangPackKey, value: number | string, checked?: boolean}[], onChange: Parameters<typeof RadioFormFromRows>[1]) => { export const RadioFormFromValues = (values: {langPackKey: LangPackKey, value: number | string, checked?: boolean}[], onChange: Parameters<typeof RadioFormFromRows>[1]) => {
const name = 'name-' + (Math.random() * 0x7FFFFF | 0); const name = 'name-' + (Math.random() * 0x7FFFFF | 0);
let checkedRadioField: RadioField;
const rows = values.map(({langPackKey, value, checked}) => { const rows = values.map(({langPackKey, value, checked}) => {
const row = new Row({ const row = new Row({
radioField: new RadioField({ radioField: new RadioField({
@ -219,11 +220,15 @@ export const RadioFormFromValues = (values: {langPackKey: LangPackKey, value: nu
}); });
if(checked) { if(checked) {
row.radioField.checked = checked; checkedRadioField = row.radioField;
} }
return row; return row;
}); });
return RadioFormFromRows(rows, onChange); const form = RadioFormFromRows(rows, onChange);
if(checkedRadioField) {
checkedRadioField.checked = true;
}
return form;
}; };

View File

@ -260,13 +260,13 @@ export default class AppChatFoldersTab extends SliderSuperTab {
} }
private async canCreateFolder() { private async canCreateFolder() {
const [appConfig, filters] = await Promise.all([ const [limit, filters] = await Promise.all([
this.managers.apiManager.getAppConfig(), this.managers.apiManager.getLimit('folders'),
this.managers.filtersStorage.getDialogFilters() this.managers.filtersStorage.getDialogFilters()
]); ]);
const filtersLength = filters.filter((filter) => !REAL_FOLDERS.has(filter.id)).length; const filtersLength = filters.filter((filter) => !REAL_FOLDERS.has(filter.id)).length;
return filtersLength < (rootScope.premium ? appConfig.dialog_filters_limit_premium : appConfig.dialog_filters_limit_default); return filtersLength < limit;
} }
private getSuggestedFilters() { private getSuggestedFilters() {

View File

@ -37,7 +37,7 @@ export default class AppEditProfileTab extends SliderSuperTab {
const inputWrapper = document.createElement('div'); const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper'); inputWrapper.classList.add('input-wrapper');
const appConfig = await this.managers.apiManager.getAppConfig(); const bioMaxLength = await this.managers.apiManager.getLimit('bio');
this.firstNameInputField = new InputField({ this.firstNameInputField = new InputField({
label: 'EditProfile.FirstNameLabel', label: 'EditProfile.FirstNameLabel',
name: 'first-name', name: 'first-name',
@ -51,7 +51,7 @@ export default class AppEditProfileTab extends SliderSuperTab {
this.bioInputField = new InputField({ this.bioInputField = new InputField({
label: 'EditProfile.BioLabel', label: 'EditProfile.BioLabel',
name: 'bio', name: 'bio',
maxLength: rootScope.premium ? appConfig.about_length_limit_premium : appConfig.about_length_limit_default maxLength: bioMaxLength
}); });
inputWrapper.append(this.firstNameInputField.container, this.lastNameInputField.container, this.bioInputField.container); inputWrapper.append(this.firstNameInputField.container, this.lastNameInputField.container, this.bioInputField.container);

View File

@ -19,7 +19,7 @@ const App = {
version: process.env.VERSION, version: process.env.VERSION,
versionFull: process.env.VERSION_FULL, versionFull: process.env.VERSION_FULL,
build: +process.env.BUILD, build: +process.env.BUILD,
langPackVersion: '0.4.8', langPackVersion: '0.4.9',
langPack: 'macos', langPack: 'macos',
langPackCode: 'en', langPackCode: 'en',
domains: [MAIN_DOMAIN] as string[], domains: [MAIN_DOMAIN] as string[],

View File

@ -142,6 +142,7 @@ const lang = {
'PaymentInfo.Done': 'PROCEED TO CHECKOUT', 'PaymentInfo.Done': 'PROCEED TO CHECKOUT',
'PaymentCard.Error.Invalid': 'Invalid card number', 'PaymentCard.Error.Invalid': 'Invalid card number',
'PaymentCard.Error.Incomplete': 'Incomplete card number', 'PaymentCard.Error.Incomplete': 'Incomplete card number',
'LimitReached.Ok': 'OK, GOT IT',
// * android // * android
'AccDescrEditing': 'Editing', 'AccDescrEditing': 'Editing',
@ -783,6 +784,13 @@ const lang = {
'AllReactions': 'All reactions', 'AllReactions': 'All reactions',
'SomeReactions': 'Some reactions', 'SomeReactions': 'Some reactions',
'NoReactions': 'No reactions', 'NoReactions': 'No reactions',
'LimitReached': 'Limit Reached',
'LimitReachedPinDialogs': 'You can\'t pin more than %1$d chats to the top. Unpin some that are currently pinned or subscribe to **Telegram Premium** to double the limit to **%2$d** chats.',
'LimitReachedPinDialogsPremium': 'Sorry, you can\'t pin more than %1$d chats to the top. Unpin some that are currently pinned.',
'LimitReachedPinDialogsLocked': 'Sorry, you can\'t pin more than %1$d chats to the top. Unpin some that are currently pinned. We are working to let you increase this limit in the future.',
'IncreaseLimit': 'Increase Limit',
'LimitFree': 'Free',
'LimitPremium': 'Premium',
// * macos // * macos
'AccountSettings.Filters': 'Chat Folders', 'AccountSettings.Filters': 'Chat Folders',

View File

@ -3085,10 +3085,7 @@ export class AppMessagesManager extends AppManager {
const pinned = dialog.pFlags?.pinned ? undefined : true; const pinned = dialog.pFlags?.pinned ? undefined : true;
if(pinned) { if(pinned) {
const appConfig = await this.apiManager.getAppConfig(); const max = await this.apiManager.getLimit(filterId === 1 ? 'folderPin' : 'pin');
const max = filterId === 1 ?
(this.rootScope.premium ? appConfig.dialogs_folder_pinned_limit_premium : appConfig.dialogs_folder_pinned_limit_default) :
(this.rootScope.premium ? appConfig.dialogs_pinned_limit_premium : appConfig.dialogs_pinned_limit_default);
if(this.dialogsStorage.getPinnedOrders(filterId).length >= max) { if(this.dialogsStorage.getPinnedOrders(filterId).length >= max) {
return Promise.reject(makeError('PINNED_DIALOGS_TOO_MUCH')); return Promise.reject(makeError('PINNED_DIALOGS_TOO_MUCH'));
} }
@ -4329,8 +4326,11 @@ export class AppMessagesManager extends AppManager {
message message
}); });
if(isTopMessage || (message as Message.message).grouped_id) { if(isTopMessage) {
this.dialogsStorage.setDialogToState(dialog); this.dialogsStorage.setDialogToState(dialog);
}
if((isTopMessage || (message as Message.message).grouped_id) && dialog) {
this.rootScope.dispatchEvent('dialogs_multiupdate', new Map([[peerId, dialog]])); this.rootScope.dispatchEvent('dialogs_multiupdate', new Map([[peerId, dialog]]));
} }
} }

View File

@ -22,10 +22,13 @@ import {ReferenceContext} from '../mtproto/referenceDatabase';
export type UserTyping = Partial<{userId: UserId, action: SendMessageAction, timeout: number}>; export type UserTyping = Partial<{userId: UserId, action: SendMessageAction, timeout: number}>;
const PEER_FULL_TTL = 3 * 60e3;
export class AppProfileManager extends AppManager { export class AppProfileManager extends AppManager {
// private botInfos: any = {}; // private botInfos: any = {};
private usersFull: {[id: UserId]: UserFull.userFull} = {}; private usersFull: {[id: UserId]: UserFull.userFull} = {};
private chatsFull: {[id: ChatId]: ChatFull} = {}; private chatsFull: {[id: ChatId]: ChatFull} = {};
private fullExpiration: {[peerId: PeerId]: number} = {};
private typingsInPeer: {[peerId: PeerId]: UserTyping[]}; private typingsInPeer: {[peerId: PeerId]: UserTyping[]};
protected after() { protected after() {
@ -161,7 +164,7 @@ export class AppProfileManager extends AppManager {
} */ } */
public getProfile(id: UserId, override?: true) { public getProfile(id: UserId, override?: true) {
if(this.usersFull[id] && !override) { if(this.usersFull[id] && !override && Date.now() < this.fullExpiration[id.toPeerId()]) {
return this.usersFull[id]; return this.usersFull[id];
} }
@ -193,6 +196,7 @@ export class AppProfileManager extends AppManager {
}); });
this.usersFull[id] = userFull; this.usersFull[id] = userFull;
this.fullExpiration[peerId] = Date.now() + PEER_FULL_TTL;
/* if(userFull.bot_info) { /* if(userFull.bot_info) {
userFull.bot_info = this.saveBotInfo(userFull.bot_info) as any; userFull.bot_info = this.saveBotInfo(userFull.bot_info) as any;
@ -263,9 +267,10 @@ export class AppProfileManager extends AppManager {
return this.getChannelFull(id, override); return this.getChannelFull(id, override);
} }
const peerId = id.toPeerId(true);
const fullChat = this.chatsFull[id] as ChatFull.chatFull; const fullChat = this.chatsFull[id] as ChatFull.chatFull;
if(fullChat && !override) { if(fullChat && !override && Date.now() < this.fullExpiration[peerId]) {
const chat = this.appChatsManager.getChat(id); const chat: Chat.chat = this.appChatsManager.getChat(id);
if(chat.version === (fullChat.participants as ChatParticipants.chatParticipants).version || if(chat.version === (fullChat.participants as ChatParticipants.chatParticipants).version ||
chat.pFlags.left) { chat.pFlags.left) {
return fullChat as ChatFull; return fullChat as ChatFull;
@ -281,7 +286,6 @@ export class AppProfileManager extends AppManager {
this.appChatsManager.saveApiChats(result.chats, true); this.appChatsManager.saveApiChats(result.chats, true);
this.appUsersManager.saveApiUsers(result.users); this.appUsersManager.saveApiUsers(result.users);
const chatFull = result.full_chat as ChatFull.chatFull; const chatFull = result.full_chat as ChatFull.chatFull;
const peerId = id.toPeerId(true);
if(chatFull && chatFull.chat_photo && chatFull.chat_photo.id) { if(chatFull && chatFull.chat_photo && chatFull.chat_photo.id) {
chatFull.chat_photo = this.appPhotosManager.savePhoto(chatFull.chat_photo, {type: 'profilePhoto', peerId}); chatFull.chat_photo = this.appPhotosManager.savePhoto(chatFull.chat_photo, {type: 'profilePhoto', peerId});
} }
@ -297,6 +301,7 @@ export class AppProfileManager extends AppManager {
}); });
this.chatsFull[id] = chatFull; this.chatsFull[id] = chatFull;
this.fullExpiration[peerId] = Date.now() + PEER_FULL_TTL;
this.rootScope.dispatchEvent('chat_full_update', id); this.rootScope.dispatchEvent('chat_full_update', id);
return chatFull; return chatFull;
@ -383,7 +388,8 @@ export class AppProfileManager extends AppManager {
} }
public getChannelFull(id: ChatId, override?: true) { public getChannelFull(id: ChatId, override?: true) {
if(this.chatsFull[id] !== undefined && !override) { const peerId = id.toPeerId(true);
if(this.chatsFull[id] !== undefined && !override && Date.now() < this.fullExpiration[peerId]) {
return this.chatsFull[id] as ChatFull.channelFull; return this.chatsFull[id] as ChatFull.channelFull;
} }
@ -393,7 +399,6 @@ export class AppProfileManager extends AppManager {
channel: this.appChatsManager.getChannelInput(id) channel: this.appChatsManager.getChannelInput(id)
}, },
processResult: (result) => { processResult: (result) => {
const peerId = id.toPeerId(true);
this.appChatsManager.saveApiChats(result.chats, true); this.appChatsManager.saveApiChats(result.chats, true);
this.appUsersManager.saveApiUsers(result.users); this.appUsersManager.saveApiUsers(result.users);
const fullChannel = result.full_chat as ChatFull.channelFull; const fullChannel = result.full_chat as ChatFull.channelFull;
@ -412,6 +417,7 @@ export class AppProfileManager extends AppManager {
}); });
this.chatsFull[id] = fullChannel; this.chatsFull[id] = fullChannel;
this.fullExpiration[peerId] = Date.now() + PEER_FULL_TTL;
this.rootScope.dispatchEvent('chat_full_update', id); this.rootScope.dispatchEvent('chat_full_update', id);
return fullChannel; return fullChannel;
@ -533,7 +539,7 @@ export class AppProfileManager extends AppManager {
this.rootScope.dispatchEvent('peer_bio_edit', peerId); this.rootScope.dispatchEvent('peer_bio_edit', peerId);
} }
return this.getProfile(this.appPeersManager.peerId, true); return this.getProfile(user.id, true);
}); });
} }

View File

@ -231,8 +231,7 @@ export class AppReactionsManager extends AppManager {
}; };
} }
const appConfig = await this.apiManager.getAppConfig(); const limit = await this.apiManager.getLimit('reactions');
const limit = this.rootScope.premium ? appConfig.reactions_user_max_premium : appConfig.reactions_user_max_default;
const lastSendingTimeKey = message.peerId + '_' + message.mid; const lastSendingTimeKey = message.peerId + '_' + message.mid;
const lastSendingTime = this.lastSendingTimes.get(lastSendingTimeKey); const lastSendingTime = this.lastSendingTimes.get(lastSendingTimeKey);

View File

@ -521,9 +521,8 @@ export class AppStickersManager extends AppManager {
return this.getFavedStickers().then(() => this.favedStickers); return this.getFavedStickers().then(() => this.favedStickers);
} }
public async getFavedStickersLimit() { public getFavedStickersLimit() {
const appConfig = await this.apiManager.getAppConfig(); return this.apiManager.getLimit('favedStickers');
return this.rootScope.premium ? appConfig.stickers_faved_limit_premium : appConfig.stickers_faved_limit_default;
} }
public async faveSticker(docId: DocId, unfave?: boolean) { public async faveSticker(docId: DocId, unfave?: boolean) {

View File

@ -5,6 +5,7 @@
*/ */
import ctx from '../../environment/ctx'; import ctx from '../../environment/ctx';
import callbackify from '../../helpers/callbackify';
import {ignoreRestrictionReasons} from '../../helpers/restrictions'; import {ignoreRestrictionReasons} from '../../helpers/restrictions';
import {Config, MethodDeclMap, User} from '../../layer'; import {Config, MethodDeclMap, User} from '../../layer';
import {InvokeApiOptions} from '../../types'; import {InvokeApiOptions} from '../../types';
@ -22,6 +23,8 @@ type HashOptions = {
[queryJSON: string]: HashResult [queryJSON: string]: HashResult
}; };
export type ApiLimitType = 'pin' | 'folderPin' | 'folders' | 'favedStickers' | 'reactions' | 'bio';
export default abstract class ApiManagerMethods extends AppManager { export default abstract class ApiManagerMethods extends AppManager {
private afterMessageIdTemp: number; private afterMessageIdTemp: number;
private hashes: {[method: string]: HashOptions} = {}; private hashes: {[method: string]: HashOptions} = {};
@ -282,4 +285,22 @@ export default abstract class ApiManagerMethods extends AppManager {
options: {overwrite} options: {overwrite}
}); });
} }
public getLimit(type: ApiLimitType, isPremium?: boolean) {
return callbackify(this.getAppConfig(), (appConfig) => {
const map: {[type in ApiLimitType]: [keyof MTAppConfig, keyof MTAppConfig]} = {
pin: ['dialogs_pinned_limit_default', 'dialogs_pinned_limit_premium'],
folderPin: ['dialogs_folder_pinned_limit_default', 'dialogs_folder_pinned_limit_premium'],
folders: ['dialog_filters_limit_default', 'dialog_filters_limit_premium'],
favedStickers: ['stickers_faved_limit_default', 'stickers_faved_limit_premium'],
reactions: ['reactions_user_max_default', 'reactions_user_max_premium'],
bio: ['about_length_limit_default', 'about_length_limit_premium']
};
isPremium ??= this.rootScope.premium;
const key = map[type][isPremium ? 1 : 0];
return appConfig[key] as number;
});
}
} }

View File

@ -57,6 +57,7 @@ export interface MTAppConfig {
message_animated_emoji_max?: number; message_animated_emoji_max?: number;
premium_promo_order?: string[]; premium_promo_order?: string[];
premium_bot_username?: string; premium_bot_username?: string;
premium_purchase_blocked?: boolean;
} }
export interface EmojiesSendDiceSuccess { export interface EmojiesSendDiceSuccess {

View File

@ -34,10 +34,10 @@ export class RLottieItem {
constructor( constructor(
private reqId: number, private reqId: number,
private width: number, private width: number,
private height: number/* , private height: number,
private raw?: boolean/* ,
private canvas: OffscreenCanvas */ private canvas: OffscreenCanvas */
) { ) {
} }
public init(json: string, fps: number) { public init(json: string, fps: number) {
@ -67,7 +67,7 @@ export class RLottieItem {
reply(['loaded', this.reqId, this.frameCount, this.fps]); reply(['loaded', this.reqId, this.frameCount, this.fps]);
if(IS_IMAGE_BITMAP_SUPPORTED) { if(!this.raw && IS_IMAGE_BITMAP_SUPPORTED) {
this.imageData = new ImageData(this.width, this.height); this.imageData = new ImageData(this.width, this.height);
} }
} catch(e) { } catch(e) {
@ -105,7 +105,7 @@ export class RLottieItem {
// this.context.putImageData(new ImageData(clamped, this.width, this.height), 0, 0); // this.context.putImageData(new ImageData(clamped, this.width, this.height), 0, 0);
reply(['frame', this.reqId, frameNo, clamped], [clamped]); reply(['frame', this.reqId, frameNo, clamped], [clamped.buffer]);
} }
} catch(e) { } catch(e) {
console.error('Render error:', e); console.error('Render error:', e);
@ -158,8 +158,8 @@ _Module.onRuntimeInitialized = function() {
const items: {[reqId: string]: RLottieItem} = {}; const items: {[reqId: string]: RLottieItem} = {};
const queryableFunctions = { const queryableFunctions = {
loadFromData: function(reqId: number, blob: Blob, width: number, height: number, toneIndex: number/* , canvas: OffscreenCanvas */) { loadFromData: function(reqId: number, blob: Blob, width: number, height: number, toneIndex: number, raw: boolean/* , canvas: OffscreenCanvas */) {
const item = items[reqId] = new RLottieItem(reqId, width, height/* , canvas */); const item = items[reqId] = new RLottieItem(reqId, width, height, raw/* , canvas */);
readBlobAsText(blob).then((json) => { readBlobAsText(blob).then((json) => {
try { try {
if(typeof(toneIndex) === 'number' && toneIndex >= 1 && toneIndex <= 5) { if(typeof(toneIndex) === 'number' && toneIndex >= 1 && toneIndex <= 5) {

View File

@ -120,6 +120,8 @@ export default class RLottiePlayer extends EventListenerBase<{
public overrideRender: (frame: ImageData | HTMLCanvasElement | ImageBitmap) => void; public overrideRender: (frame: ImageData | HTMLCanvasElement | ImageBitmap) => void;
private renderedFirstFrame: boolean; private renderedFirstFrame: boolean;
private raw: boolean;
constructor({el, worker, options}: { constructor({el, worker, options}: {
el: RLottiePlayer['el'], el: RLottiePlayer['el'],
worker: QueryableWorker, worker: QueryableWorker,
@ -148,6 +150,7 @@ export default class RLottiePlayer extends EventListenerBase<{
this.name = options.name; this.name = options.name;
this.skipFirstFrameRendering = options.skipFirstFrameRendering; this.skipFirstFrameRendering = options.skipFirstFrameRendering;
this.toneIndex = options.toneIndex; this.toneIndex = options.toneIndex;
this.raw = this.color !== undefined;
if(this.name) { if(this.name) {
this.cacheName = RLottiePlayer.CACHE.generateName(this.name, this.width, this.height, this.color, this.toneIndex); this.cacheName = RLottiePlayer.CACHE.generateName(this.name, this.width, this.height, this.color, this.toneIndex);
@ -203,7 +206,7 @@ export default class RLottiePlayer extends EventListenerBase<{
this.contexts = this.canvas.map((canvas) => canvas.getContext('2d')); this.contexts = this.canvas.map((canvas) => canvas.getContext('2d'));
if(!IS_IMAGE_BITMAP_SUPPORTED) { if(!IS_IMAGE_BITMAP_SUPPORTED || this.raw) {
this.imageData = new ImageData(this.width, this.height); this.imageData = new ImageData(this.width, this.height);
if(CAN_USE_TRANSFERABLES) { if(CAN_USE_TRANSFERABLES) {
@ -230,12 +233,12 @@ export default class RLottiePlayer extends EventListenerBase<{
this.cache.clearCache(); this.cache.clearCache();
} }
public sendQuery(args: any[]) { public sendQuery(args: any[], transfer?: Transferable[]) {
this.worker.sendQuery([args.shift(), this.reqId, ...args]); this.worker.sendQuery([args.shift(), this.reqId, ...args], transfer);
} }
public loadFromData(data: RLottieOptions['animationData']) { public loadFromData(data: RLottieOptions['animationData']) {
this.sendQuery(['loadFromData', data, this.width, this.height, this.toneIndex/* , this.canvas.transferControlToOffscreen() */]); this.sendQuery(['loadFromData', data, this.width, this.height, this.toneIndex, this.color !== undefined/* , this.canvas.transferControlToOffscreen() */]);
} }
public play() { public play() {
@ -442,7 +445,7 @@ export default class RLottiePlayer extends EventListenerBase<{
this.clamped = new Uint8ClampedArray(this.width * this.height * 4); this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
} }
this.sendQuery(['renderFrame', frameNo, this.clamped]); this.sendQuery(['renderFrame', frameNo], this.clamped ? [this.clamped.buffer] : undefined);
} }
} }

View File

@ -545,13 +545,8 @@ export default class FiltersStorage extends AppManager {
return true; return true;
} }
const isPremium = this.rootScope.premium; const limit = await this.apiManager.getLimit('folders');
let isFolderAvailable = isPremium; const isFolderAvailable = this.filtersArr.filter((filter) => !REAL_FOLDERS.has(filter.id)).slice(0, limit).some((filter) => filter.id === filterId);
if(!isPremium) {
const config = await this.apiManager.getAppConfig();
const limit = config.dialog_filters_limit_default;
isFolderAvailable = this.filtersArr.filter((filter) => !REAL_FOLDERS.has(filter.id)).slice(0, limit).some((filter) => filter.id === filterId);
}
return isFolderAvailable; return isFolderAvailable;
} }

View File

@ -6,7 +6,10 @@
@use "sass:math"; @use "sass:math";
$bubble-margin: .25rem; $bubble-margin: .125rem;
$bubble-margin-big: .5rem;
$bubble-overflow: math.div($bubble-margin-big, 8);
$bubble-overflow-big: math.div($bubble-margin-big, 2);
$bubble-beside-button-width: 38px; $bubble-beside-button-width: 38px;
@keyframes bubbleSelected { @keyframes bubbleSelected {
@ -36,7 +39,7 @@ $bubble-beside-button-width: 38px;
e.g. make the height bigger and adjust the top so observeHeaders()'s e.g. make the height bigger and adjust the top so observeHeaders()'s
IntersectionObserver fires as soon as the bottom of the sentinel crosses the IntersectionObserver fires as soon as the bottom of the sentinel crosses the
top of the intersection container. */ top of the intersection container. */
height: $bubble-margin; height: $bubble-overflow-big;
top: 0; top: 0;
} }
@ -136,8 +139,8 @@ $bubble-beside-button-width: 38px;
left: -50%; left: -50%;
/* top: 0; /* top: 0;
bottom: 0; */ bottom: 0; */
top: -#{math.div($bubble-margin, 2)}; top: -#{$bubble-overflow};
bottom: -#{math.div($bubble-margin, 2)}; bottom: -#{$bubble-overflow};
content: " "; content: " ";
z-index: -1; z-index: -1;
} }
@ -145,13 +148,13 @@ $bubble-beside-button-width: 38px;
/* &.is-highlighted, &.is-selected { /* &.is-highlighted, &.is-selected {
&:not(.is-group-last):after { &:not(.is-group-last):after {
height: calc(100% + #{math.div($bubble-margin, 2)}) !important; height: calc(100% + #{$bubble-overflow}) !important;
} }
& + &:not(.is-group-last) { & + &:not(.is-group-last) {
&:after { &:after {
top: .125rem !important; top: .125rem !important;
height: calc(100% - #{math.div($bubble-margin, 2)}) !important; height: calc(100% - #{$bubble-overflow}) !important;
} }
} }
} */ } */
@ -159,7 +162,7 @@ $bubble-beside-button-width: 38px;
// ! if turn this on, there will be an empty space // ! if turn this on, there will be an empty space
/* &.is-highlighted, &.is-selected { /* &.is-highlighted, &.is-selected {
&.is-group-last:after { &.is-group-last:after {
bottom: #{math.div($bubble-margin, 2)} !important; bottom: #{$bubble-overflow} !important;
} }
} */ } */
@ -180,7 +183,7 @@ $bubble-beside-button-width: 38px;
&:before { &:before {
content: "Unread messages"; content: "Unread messages";
height: 30px; height: 30px;
margin-bottom: $bubble-margin; margin-bottom: $bubble-overflow-big;
margin-left: -50%; margin-left: -50%;
margin-right: -50%; margin-right: -50%;
text-align: center; text-align: center;
@ -193,9 +196,10 @@ $bubble-beside-button-width: 38px;
position: relative; position: relative;
} }
&.is-highlighted, &.is-selected { &.is-highlighted,
&.is-selected {
&:after { &:after {
top: calc(#{math.div($bubble-margin, 2)} + 30px); top: calc(#{$bubble-overflow} + 30px);
} }
} }
} }
@ -391,8 +395,8 @@ $bubble-beside-button-width: 38px;
&.is-date { &.is-date {
position: sticky; position: sticky;
top: $bubble-margin; top: $bubble-overflow-big;
padding-bottom: $bubble-margin; padding-bottom: #{$bubble-overflow-big + $bubble-margin};
//z-index: 3; //z-index: 3;
z-index: 2; z-index: 2;
transition: opacity .3s ease; transition: opacity .3s ease;
@ -500,10 +504,10 @@ $bubble-beside-button-width: 38px;
} */ } */
&.is-group-last { &.is-group-last {
margin-bottom: #{$bubble-margin * 2}; margin-bottom: #{$bubble-margin-big};
&:after { &:after {
bottom: -#{$bubble-margin}; bottom: -#{$bubble-overflow-big};
} }
/* > .bubble-select-checkbox { /* > .bubble-select-checkbox {
@ -519,7 +523,7 @@ $bubble-beside-button-width: 38px;
&.is-group-first { &.is-group-first {
&:after { &:after {
top: -#{$bubble-margin}; top: -#{$bubble-overflow-big};
} }
} }
@ -1618,7 +1622,7 @@ $bubble-beside-button-width: 38px;
&:first-of-type { &:first-of-type {
.document-selection { .document-selection {
top: -#{math.div($bubble-margin, 2)}; // * padding inner + half padding outer top: -#{$bubble-overflow}; // * padding inner + half padding outer
} }
.document-wrapper { .document-wrapper {
@ -1630,7 +1634,7 @@ $bubble-beside-button-width: 38px;
&:last-of-type { &:last-of-type {
.document-selection { .document-selection {
bottom: -#{math.div($bubble-margin, 2)}; bottom: -#{$bubble-overflow};
} }
.document-wrapper { .document-wrapper {
@ -1644,7 +1648,7 @@ $bubble-beside-button-width: 38px;
&.is-group-first .document-container { &.is-group-first .document-container {
&:first-of-type { &:first-of-type {
.document-selection { .document-selection {
top: -$bubble-margin; top: -$bubble-overflow-big;
} }
} }
} }
@ -1652,7 +1656,7 @@ $bubble-beside-button-width: 38px;
&.is-group-last .document-container { &.is-group-last .document-container {
&:last-of-type { &:last-of-type {
.document-selection { .document-selection {
bottom: -$bubble-margin; bottom: -$bubble-overflow-big;
} }
} }
} }

View File

@ -0,0 +1,70 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
.popup-limit {
.popup-container {
min-width: 22.5rem;
}
&-line {
display: flex;
flex-direction: column;
align-items: center;
margin: .75rem 0;
}
&-hint {
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: var(--font-weight-bold);
&-icon {
font-size: 1.25rem;
margin-right: .25rem;
display: flex;
}
}
.limit-line {
align-self: stretch;
margin: 1rem .5rem 0;
}
&:not(.is-locked) &-hint {
height: 2rem;
border-radius: 1rem;
padding: 0 .75rem;
position: relative;
background: linear-gradient(73.4deg, #6C93FF -7.21%, #976FFF 114.57%, #DF69D1 241.52%);
background-size: 200px 2rem;
&:after {
display: block;
content: " ";
width: 100%; // 26
height: 9px;
position: absolute;
bottom: -9px;
clip-path: path("M0 0H26H24.4853C22.894 0 21.3679 0.632141 20.2426 1.75736L14.4142 7.58579C13.6332 8.36684 12.3668 8.36683 11.5858 7.58579L5.75736 1.75736C4.63214 0.632139 3.10602 0 1.51472 0H0Z");
background: inherit;
background-size: inherit;
background-position-x: calc(-50% + -86px);
background-repeat: no-repeat;
left: 50%;
margin-left: -13px;
}
}
&.is-locked &-hint {
height: 2.75rem;
border-radius: 1.375rem;
padding: 0 1.25rem 0 1rem;
margin-right: -.25rem;
background-color: var(--primary-color);
}
}

View File

@ -390,6 +390,7 @@ $chat-input-inner-padding-handhelds: .25rem;
@import "partials/popups/paymentShippingMethods"; @import "partials/popups/paymentShippingMethods";
@import "partials/popups/paymentVerification"; @import "partials/popups/paymentVerification";
@import "partials/popups/paymentCardConfirmation"; @import "partials/popups/paymentCardConfirmation";
@import "partials/popups/limit";
@import "partials/pages/pages"; @import "partials/pages/pages";
@import "partials/pages/authCode"; @import "partials/pages/authCode";
@ -1717,6 +1718,43 @@ hr {
} }
} }
.limit-line {
height: 2rem;
// border-radius: $border-radius-medium;
font-weight: var(--font-weight-bold);
display: flex;
// background: linear-gradient(84.4deg, #6C93FF -4.85%, #976FFF 51.72%, #DF69D1 110.7%), #F1F3F5;
&-free {
color: #000;
}
&-premium {
color: #fff;
}
&-free {
border-top-left-radius: $border-radius-medium;
border-bottom-left-radius: $border-radius-medium;
background-color: #F1F3F5;
}
&-premium {
border-top-right-radius: $border-radius-medium;
border-bottom-right-radius: $border-radius-medium;
background: linear-gradient(84.4deg, #6C93FF -4.85%, #976FFF 51.72%, #DF69D1 110.7%);
}
&-free,
&-premium {
flex: 1 1 0;
padding: 0 .75rem;
display: flex;
align-items: center;
justify-content: space-between;
}
}
.movable-element { .movable-element {
--size: .5rem; --size: .5rem;
position: relative; position: relative;