diff --git a/src/components/radioField.ts b/src/components/radioField.ts index 1121f2d1..9c9723ab 100644 --- a/src/components/radioField.ts +++ b/src/components/radioField.ts @@ -18,10 +18,15 @@ export default class RadioField { langKey?: LangPackKey, name: string, value?: string, - stateKey?: string + stateKey?: string, + alignRight?: boolean }) { const label = this.label = document.createElement('label'); label.classList.add('radio-field'); + + if(options.alignRight) { + label.classList.add('radio-field-right'); + } const input = this.input = document.createElement('input'); input.type = 'radio'; diff --git a/src/components/row.ts b/src/components/row.ts index ff326a6a..77cab554 100644 --- a/src/components/row.ts +++ b/src/components/row.ts @@ -17,6 +17,7 @@ export default class Row { public container: HTMLElement; public title: HTMLDivElement; public subtitle: HTMLElement; + public media: HTMLElement; public checkboxField: CheckboxField; public radioField: RadioField; @@ -31,7 +32,7 @@ export default class Row { radioField: Row['radioField'], checkboxField: Row['checkboxField'], noCheckboxSubtitle: boolean, - title: string, + title: string | HTMLElement, titleLangKey: LangPackKey, titleRight: string | HTMLElement, clickable: boolean | ((e: Event) => void), @@ -58,10 +59,10 @@ export default class Row { let havePadding = !!options.havePadding; if(options.radioField || options.checkboxField) { - havePadding = true; if(options.radioField) { this.radioField = options.radioField; this.container.append(this.radioField.label); + havePadding = true; } if(options.checkboxField) { @@ -72,6 +73,7 @@ export default class Row { this.container.classList.add('row-with-toggle'); options.titleRight = this.checkboxField.label; } else { + havePadding = true; this.container.append(this.checkboxField.label); } @@ -100,7 +102,11 @@ export default class Row { this.title.classList.add('row-title'); this.title.setAttribute('dir', 'auto'); if(options.title) { - this.title.innerHTML = options.title; + if(typeof(options.title) === 'string') { + this.title.innerHTML = options.title; + } else { + this.title.append(options.title); + } } else { this.title.append(i18n(options.titleLangKey)); } @@ -154,7 +160,20 @@ export default class Row { } } + public createMedia(size?: 'small') { + this.container.classList.add('row-with-padding'); + + const media = this.media = document.createElement('div'); + media.classList.add('row-media'); + if(size) { + media.classList.add('row-media-' + size); + } + + this.container.append(media); + + return media; + } } export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void) => { diff --git a/src/components/sidebarLeft/tabs/generalSettings.ts b/src/components/sidebarLeft/tabs/generalSettings.ts index 2266b78b..6b9afd1f 100644 --- a/src/components/sidebarLeft/tabs/generalSettings.ts +++ b/src/components/sidebarLeft/tabs/generalSettings.ts @@ -20,13 +20,14 @@ import appStickersManager from "../../../lib/appManagers/appStickersManager"; import assumeType from "../../../helpers/assumeType"; import { MessagesAllStickers, StickerSet } from "../../../layer"; import RichTextProcessor from "../../../lib/richtextprocessor"; -import { wrapSticker, wrapStickerSetThumb } from "../../wrappers"; +import { wrapStickerSetThumb, wrapStickerToRow } from "../../wrappers"; import LazyLoadQueue from "../../lazyLoadQueue"; import PopupStickers from "../../popups/stickers"; import eachMinute from "../../../helpers/eachMinute"; import { SliderSuperTabEventable } from "../../sliderTab"; import IS_GEOLOCATION_SUPPORTED from "../../../environment/geolocationSupport"; import appReactionsManager from "../../../lib/appManagers/appReactionsManager"; +import AppQuickReactionTab from "./quickReaction"; export class RangeSettingSelector { public container: HTMLDivElement; @@ -287,26 +288,26 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { const container = section('Telegram.InstalledStickerPacksController'); const reactionsRow = new Row({ - titleLangKey: 'Reactions', + titleLangKey: 'DoubleTapSetting', havePadding: true, clickable: () => { - + new AppQuickReactionTab(this.slider).open(); } }); - const quickReactionMediaDiv = document.createElement('div'); - quickReactionMediaDiv.classList.add('row-media', 'row-media-small'); - - appReactionsManager.getQuickReaction().then(reaction => { - wrapSticker({ - div: quickReactionMediaDiv, - doc: reaction.static_icon, - width: 32, - height: 32 + const renderQuickReaction = () => { + appReactionsManager.getQuickReaction().then(reaction => { + wrapStickerToRow({ + row: reactionsRow, + doc: reaction.static_icon, + size: 'small' + }); }); - }); + }; - reactionsRow.container.append(quickReactionMediaDiv); + renderQuickReaction(); + + this.listenerSetter.add(rootScope)('quick_reaction', renderQuickReaction); const suggestCheckboxField = new CheckboxField({ text: 'Stickers.SuggestStickers', diff --git a/src/components/sidebarLeft/tabs/quickReaction.ts b/src/components/sidebarLeft/tabs/quickReaction.ts new file mode 100644 index 00000000..408aa3e0 --- /dev/null +++ b/src/components/sidebarLeft/tabs/quickReaction.ts @@ -0,0 +1,65 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { SettingSection } from ".."; +import appReactionsManager from "../../../lib/appManagers/appReactionsManager"; +import RadioField from "../../radioField"; +import Row, { RadioFormFromRows } from "../../row"; +import SliderSuperTab from "../../sliderTab"; +import { wrapStickerToRow } from "../../wrappers"; + +export default class AppQuickReactionTab extends SliderSuperTab { + protected init() { + this.header.classList.add('with-border'); + this.setTitle('DoubleTapSetting'); + this.container.classList.add('quick-reaction-container'); + + return Promise.all([ + appReactionsManager.getQuickReaction(), + appReactionsManager.getAvailableReactions() + ]).then(([quickReaction, availableReactions]) => { + availableReactions = availableReactions.filter(reaction => !reaction.pFlags.inactive); + + const section = new SettingSection(); + + const name = 'quick-reaction'; + const rows = availableReactions.map((availableReaction) => { + const radioField = new RadioField({ + name, + text: availableReaction.title, + value: availableReaction.reaction, + alignRight: true + }); + + const row = new Row({ + radioField, + havePadding: true + }); + + radioField.main.classList.add('quick-reaction-title'); + + wrapStickerToRow({ + row, + doc: availableReaction.static_icon, + size: 'small' + }); + + if(availableReaction === quickReaction) { + radioField.setValueSilently(true); + } + + return row; + }); + + const form = RadioFormFromRows(rows, (value) => { + appReactionsManager.setDefaultReaction(value); + }); + + section.content.append(form); + this.scrollable.append(section.container); + }); + } +} diff --git a/src/components/sidebarRight/tabs/chatReactions.ts b/src/components/sidebarRight/tabs/chatReactions.ts new file mode 100644 index 00000000..c897acfd --- /dev/null +++ b/src/components/sidebarRight/tabs/chatReactions.ts @@ -0,0 +1,100 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import appChatsManager from "../../../lib/appManagers/appChatsManager"; +import appProfileManager from "../../../lib/appManagers/appProfileManager"; +import appReactionsManager from "../../../lib/appManagers/appReactionsManager"; +import CheckboxField from "../../checkboxField"; +import Row from "../../row"; +import { SettingSection } from "../../sidebarLeft"; +import { SliderSuperTabEventable } from "../../sliderTab"; +import { wrapStickerToRow } from "../../wrappers"; + +export default class AppChatReactionsTab extends SliderSuperTabEventable { + public chatId: ChatId; + + protected async init() { + this.setTitle('Reactions'); + + const availableReactions = await appReactionsManager.getActiveAvailableReactions(); + const chatFull = await appProfileManager.getChatFull(this.chatId); + const originalReactions = chatFull.available_reactions ?? []; + const enabledReactions = new Set(originalReactions); + + const toggleSection = new SettingSection({ + caption: appChatsManager.isBroadcast(this.chatId) ? 'EnableReactionsChannelInfo' : 'EnableReactionsGroupInfo' + }); + + const toggleCheckboxField = new CheckboxField({toggle: true, checked: !!enabledReactions.size}); + const toggleRow = new Row({ + checkboxField: toggleCheckboxField, + titleLangKey: 'EnableReactions' + }); + + toggleSection.content.append(toggleRow.container); + + const reactionsSection = new SettingSection({ + name: 'AvailableReactions' + }); + + const checkboxFields = availableReactions.map(availableReaction => { + const checkboxField = new CheckboxField({ + toggle: true, + checked: enabledReactions.has(availableReaction.reaction) + }); + + this.listenerSetter.add(checkboxField.input)('change', () => { + if(checkboxField.checked) { + enabledReactions.add(availableReaction.reaction); + + if(!toggleCheckboxField.checked) { + toggleCheckboxField.setValueSilently(true); + } + } else enabledReactions.delete(availableReaction.reaction); + }); + + const row = new Row({ + checkboxField, + title: availableReaction.title, + havePadding: true + }); + + wrapStickerToRow({ + row, + doc: availableReaction.static_icon, + size: 'small' + }); + + reactionsSection.content.append(row.container); + + return checkboxField; + }); + + this.listenerSetter.add(toggleRow.checkboxField.input)('change', () => { + if(!toggleCheckboxField.checked) { + checkboxFields.forEach(checkboxField => checkboxField.setValueSilently(false)); + } else if(checkboxFields.every(checkboxField => !checkboxField.checked)) { + checkboxFields.forEach(checkboxField => checkboxField.setValueSilently(true)); + } + }); + + this.eventListener.addEventListener('destroy', () => { + const newReactions = Array.from(enabledReactions); + if([...newReactions].sort().join() === [...originalReactions].sort().join()) { + return; + } + + const chatFull = appProfileManager.getCachedFullChat(this.chatId); + if(chatFull) { + chatFull.available_reactions = newReactions; + } + + appChatsManager.setChatAvailableReactions(this.chatId, newReactions); + }, {once: true}); + + this.scrollable.append(toggleSection.container, reactionsSection.container); + } +} diff --git a/src/components/sidebarRight/tabs/editChat.ts b/src/components/sidebarRight/tabs/editChat.ts index 68be9123..d528b2ed 100644 --- a/src/components/sidebarRight/tabs/editChat.ts +++ b/src/components/sidebarRight/tabs/editChat.ts @@ -21,22 +21,27 @@ import PopupDeleteDialog from "../../popups/deleteDialog"; import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import toggleDisability from "../../../helpers/dom/toggleDisability"; import CheckboxField from "../../checkboxField"; +import appReactionsManager from "../../../lib/appManagers/appReactionsManager"; +import AppChatReactionsTab from "./chatReactions"; export default class AppEditChatTab extends SliderSuperTab { private chatNameInputField: InputField; private descriptionInputField: InputField; private editPeer: EditPeer; + private tempId: number; public chatId: ChatId; protected async _init() { // * cleanup prev this.listenerSetter.removeAll(); this.scrollable.container.innerHTML = ''; + this.tempId ??= 0; + const tempId = ++this.tempId; this.container.classList.add('edit-peer-container', 'edit-group-container'); this.setTitle('Edit'); - const chatFull = await appProfileManager.getChatFull(this.chatId, true); + let chatFull = await appProfileManager.getChatFull(this.chatId, true); const chat: Chat.chat | Chat.channel = appChatsManager.getChat(this.chatId); const isBroadcast = appChatsManager.isBroadcast(this.chatId); @@ -53,6 +58,12 @@ export default class AppEditChatTab extends SliderSuperTab { } }); + this.listenerSetter.add(rootScope)('chat_full_update', (chatId) => { + if(this.chatId === chatId) { + chatFull = appProfileManager.getCachedFullChat(chatId) || chatFull; + } + }); + const peerId = this.chatId.toPeerId(true); { @@ -119,6 +130,32 @@ export default class AppEditChatTab extends SliderSuperTab { setChatTypeSubtitle(); section.content.append(chatTypeRow.container); + + const reactionsRow = new Row({ + titleLangKey: 'Reactions', + icon: 'tip', + clickable: () => { + const tab = new AppChatReactionsTab(this.slider); + tab.chatId = this.chatId; + tab.open().then(() => { + if(this.tempId !== tempId) { + return; + } + + this.listenerSetter.add(tab.eventListener)('destroy', setReactionsLength); + }); + } + }); + + const availableReactions = await appReactionsManager.getAvailableReactions(); + const availableReactionsLength = availableReactions.filter(availableReaction => !availableReaction.pFlags.inactive).length; + const setReactionsLength = () => { + reactionsRow.subtitle.innerHTML = chatFull.available_reactions.length + '/' + availableReactionsLength; + }; + + setReactionsLength(); + + section.content.append(reactionsRow.container); } if(appChatsManager.hasRights(this.chatId, 'change_permissions') && !isBroadcast) { diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 920a9b13..93011cad 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -56,6 +56,7 @@ import appMessagesIdsManager from '../lib/appManagers/appMessagesIdsManager'; import throttle from '../helpers/schedulers/throttle'; import { SendMessageEmojiInteractionData } from '../types'; import IS_VIBRATE_SUPPORTED from '../environment/vibrateSupport'; +import Row from './row'; const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB @@ -1637,6 +1638,37 @@ export async function wrapStickerSetThumb({set, lazyLoadQueue, container, group, } } +export function wrapStickerToRow({doc, row, size}: { + doc: MyDocument, + row: Row, + size?: 'small' | 'large', +}) { + const previousMedia = row.media; + const media = row.createMedia('small'); + + if(previousMedia) { + media.classList.add('hide'); + } + + const loadPromises: Promise[] = previousMedia ? [] : undefined; + + const _size = size === 'small' ? 32 : 48; + const result = wrapSticker({ + div: media, + doc: doc, + width: _size, + height: _size, + loadPromises + }); + + loadPromises && Promise.all(loadPromises).then(() => { + media.classList.remove('hide'); + previousMedia.remove(); + }); + + return result; +} + export function wrapLocalSticker({emoji, width, height}: { doc?: MyDocument, url?: string, diff --git a/src/helpers/callbackify.ts b/src/helpers/callbackify.ts new file mode 100644 index 00000000..7e9f71c3 --- /dev/null +++ b/src/helpers/callbackify.ts @@ -0,0 +1,9 @@ +import {Awaited} from '../types'; + +export default function callbackify, R extends any>(smth: T, callback: (result: Awaited) => R): PromiseLike | R { + if(smth instanceof Promise) { + return smth.then(callback); + } else { + return callback(smth as any); + } +} diff --git a/src/lang.ts b/src/lang.ts index 55d1567b..1e2ed9cb 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -642,6 +642,10 @@ const lang = { "Update": "UPDATE", "Reactions": "Reactions", "DoubleTapSetting": "Quick Reaction", + "EnableReactions": "Enable Reactions", + "EnableReactionsChannelInfo": "Allow subscribers to react to channel posts.", + "EnableReactionsGroupInfo": "Allow members to react to group messages.", + "AvailableReactions": "Available reactions", // * macos "AccountSettings.Filters": "Chat Folders", @@ -833,22 +837,25 @@ const lang = { "Emoji.Objects": "Objects", //"Emoji.Symbols": "Symbols", "Emoji.Flags": "Flags", + "InstalledStickers.LoopAnimated": "Loop Animated Stickers", "LastSeen.HoursAgo": { "one_value": "last seen %d hour ago", "other_value": "last seen %d hours ago" }, "Login.Register.LastName.Placeholder": "Last Name", + "Message.Context.Select": "Select", + "Message.Context.Pin": "Pin", + "Message.Context.Unpin": "Unpin", + "Message.Context.Goto": "Show Message", + "MessageContext.CopyMessageLink1": "Copy Message Link", "Modal.Send": "Send", - "Telegram.GeneralSettingsViewController": "General Settings", - "Telegram.InstalledStickerPacksController": "Stickers", - "Telegram.NotificationSettingsViewController": "Notifications", - "Telegram.LanguageViewController": "Language", - "Stickers.SearchAdd": "Add", - "Stickers.SearchAdded": "Added", - "Stickers.SuggestStickers": "Suggest Stickers by Emoji", - "ShareModal.Search.Placeholder": "Share to...", - "ShareModal.Search.ForwardPlaceholder": "Forward to...", - "InstalledStickers.LoopAnimated": "Loop Animated Stickers", + "NewPoll.Anonymous": "Anonymous Voting", + "NewPoll.Explanation.Placeholder": "Add a Comment (Optional)", + "NewPoll.OptionsAddOption": "Add an Option", + "NewPoll.MultipleChoice": "Multiple Answers", + "NewPoll.Quiz": "Quiz Mode", + "Notification.Contact.Reacted": "%1$@ to your \"%2$@\"", + // "Notification.Group.Reacted": "%1$@: %2$@ to your \"%3$@\"", "Peer.Activity.User.PlayingGame": "playing a game", "Peer.Activity.User.TypingText": "typing", "Peer.Activity.User.SendingPhoto": "sending a photo", @@ -958,16 +965,15 @@ const lang = { }, "RecentSessions.Error.FreshReset": "For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours.", "RequestJoin.Button": "Request to Join", - "Message.Context.Select": "Select", - "Message.Context.Pin": "Pin", - "Message.Context.Unpin": "Unpin", - "Message.Context.Goto": "Show Message", - "MessageContext.CopyMessageLink1": "Copy Message Link", - "NewPoll.Anonymous": "Anonymous Voting", - "NewPoll.Explanation.Placeholder": "Add a Comment (Optional)", - "NewPoll.OptionsAddOption": "Add an Option", - "NewPoll.MultipleChoice": "Multiple Answers", - "NewPoll.Quiz": "Quiz Mode", + "Stickers.SearchAdd": "Add", + "Stickers.SearchAdded": "Added", + "Stickers.SuggestStickers": "Suggest Stickers by Emoji", + "ShareModal.Search.Placeholder": "Share to...", + "ShareModal.Search.ForwardPlaceholder": "Forward to...", + "Telegram.GeneralSettingsViewController": "General Settings", + "Telegram.InstalledStickerPacksController": "Stickers", + "Telegram.NotificationSettingsViewController": "Notifications", + "Telegram.LanguageViewController": "Language", "GeneralSettings.BigEmoji": "Large Emoji", "GeneralSettings.EmojiPrediction": "Suggest Emoji", "GroupPermission.Delete": "Delete Exception", @@ -981,6 +987,23 @@ const lang = { "Stickers.Recent": "Recent", //"Stickers.Favorite": "Favorite", "StickerSet.DontExist": "Sorry, this sticker set doesn't seem to exist.", + "Text.Context.Copy.Username": "Copy Username", + "Text.Context.Copy.Hashtag": "Copy Hashtag", + "Time.TomorrowAt": "tomorrow at %@", + "TwoStepAuth.SetPasswordHelp": "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.", + "TwoStepAuth.GenericHelp": "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account.", + "TwoStepAuth.ChangePassword": "Change Password", + "TwoStepAuth.RemovePassword": "Turn Password Off", + "TwoStepAuth.SetupEmail": "Set Recovery Email", + "TwoStepAuth.ChangeEmail": "Change Recovery Email", + "TwoStepAuth.ConfirmEmailCodeDesc": "Please enter the code we've just emailed to %@.", + "TwoStepAuth.RecoveryTitle": "Email Code", + "TwoStepAuth.RecoveryCode": "Code", + "TwoStepAuth.RecoveryCodeInvalid": "Invalid code. Please try again.", + "TwoStepAuth.RecoveryCodeExpired": "Code Expired", + "TwoStepAuth.SetupHintTitle": "Password Hint", + "TwoStepAuth.SetupHintPlaceholder": "Hint", + "UsernameSettings.ChangeDescription": "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters.", "VoiceChat.Chat.StartNew": "Video chat ended. Start a new one?", "VoiceChat.Chat.StartNew.OK": "Start", "VoiceChat.Chat.Ended": "Video chat ended.", @@ -1012,24 +1035,7 @@ const lang = { "VoiceChat.UnmuteForMe": "Unmute For Me", "VoiceChat.RemovePeer.Confirm.Channel": "Do you want to remove %1$@ from the channel?", "VoiceChat.RemovePeer.Confirm": "Are you sure you want to remove %1$@ from the group?", - "VoiceChat.RemovePeer.Confirm.OK": "Remove", - "Text.Context.Copy.Username": "Copy Username", - "Text.Context.Copy.Hashtag": "Copy Hashtag", - "Time.TomorrowAt": "tomorrow at %@", - "TwoStepAuth.SetPasswordHelp": "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.", - "TwoStepAuth.GenericHelp": "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account.", - "TwoStepAuth.ChangePassword": "Change Password", - "TwoStepAuth.RemovePassword": "Turn Password Off", - "TwoStepAuth.SetupEmail": "Set Recovery Email", - "TwoStepAuth.ChangeEmail": "Change Recovery Email", - "TwoStepAuth.ConfirmEmailCodeDesc": "Please enter the code we've just emailed to %@.", - "TwoStepAuth.RecoveryTitle": "Email Code", - "TwoStepAuth.RecoveryCode": "Code", - "TwoStepAuth.RecoveryCodeInvalid": "Invalid code. Please try again.", - "TwoStepAuth.RecoveryCodeExpired": "Code Expired", - "TwoStepAuth.SetupHintTitle": "Password Hint", - "TwoStepAuth.SetupHintPlaceholder": "Hint", - "UsernameSettings.ChangeDescription": "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters." + "VoiceChat.RemovePeer.Confirm.OK": "Remove" }; export default lang; diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 2120bf5e..10b5aa25 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -770,6 +770,15 @@ export class AppChatsManager { apiUpdatesManager.processUpdateMessage(updates); }); } + + public setChatAvailableReactions(id: ChatId, reactions: Array) { + return apiManager.invokeApi('messages.setChatAvailableReactions', { + peer: this.getInputPeer(id), + available_reactions: reactions + }).then(updates => { + apiUpdatesManager.processUpdateMessage(updates); + }); + } } const appChatsManager = new AppChatsManager(); diff --git a/src/lib/appManagers/appReactionsManager.ts b/src/lib/appManagers/appReactionsManager.ts index e1e7d5ae..3496329a 100644 --- a/src/lib/appManagers/appReactionsManager.ts +++ b/src/lib/appManagers/appReactionsManager.ts @@ -6,6 +6,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; import assumeType from "../../helpers/assumeType"; +import callbackify from "../../helpers/callbackify"; import { AvailableReaction, MessagesAvailableReactions } from "../../layer"; import apiManager from "../mtproto/mtprotoworker"; import { ReferenceContext } from "../mtproto/referenceDatabase"; @@ -37,7 +38,7 @@ export class AppReactionsManager { } public getAvailableReactions() { - if(this.availableReactions) return Promise.resolve(this.availableReactions); + if(this.availableReactions) return this.availableReactions; return apiManager.invokeApiSingleProcess({ method: 'messages.getAvailableReactions', processResult: (messagesAvailableReactions) => { @@ -62,6 +63,17 @@ export class AppReactionsManager { }); } + public getActiveAvailableReactions() { + return callbackify(this.getAvailableReactions(), (availableReactions) => { + return availableReactions.filter(availableReaction => !availableReaction.pFlags.inactive); + }); + } + + public isReactionActive(reaction: string) { + if(!this.availableReactions) return false; + return !!this.availableReactions.find(availableReaction => availableReaction.reaction === reaction); + } + public getQuickReaction() { return Promise.all([ apiManager.getAppConfig(), @@ -70,6 +82,49 @@ export class AppReactionsManager { return availableReactions.find(reaction => reaction.reaction === appConfig.reactions_default); }); } + + public getReactionCached(reaction: string) { + return this.availableReactions.find(availableReaction => availableReaction.reaction === reaction); + } + + public getReaction(reaction: string) { + return callbackify(this.getAvailableReactions(), () => { + return this.getReactionCached(reaction); + }); + } + + /* public getMessagesReactions(peerId: PeerId, mids: number[]) { + return apiManager.invokeApiSingleProcess({ + method: 'messages.getMessagesReactions', + params: { + id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid)), + peer: appPeersManager.getInputPeerById(peerId) + }, + processResult: (updates) => { + apiUpdatesManager.processUpdateMessage(updates); + + // const update = (updates as Updates.updates).updates.find(update => update._ === 'updateMessageReactions') as Update.updateMessageReactions; + // return update.reactions; + } + }); + } */ + + public setDefaultReaction(reaction: string) { + return apiManager.invokeApi('messages.setDefaultReaction', {reaction}).then(value => { + if(value) { + const appConfig = rootScope.appConfig; + if(appConfig) { + appConfig.reactions_default = reaction; + } else { // if no config or loading it - overwrite + apiManager.getAppConfig(true); + } + + rootScope.dispatchEvent('quick_reaction', reaction); + } + + return value; + }); + } } const appReactionsManager = new AppReactionsManager(); diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index fa26c2db..f2e2232c 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -162,7 +162,11 @@ export class AppStickersManager { public getAnimatedEmojiSounds(overwrite?: boolean) { if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise; - return this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => { + const promise = this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => { + if(this.getAnimatedEmojiSoundsPromise !== promise) { + return; + } + for(const emoji in appConfig.emojies_sounds) { const sound = appConfig.emojies_sounds[emoji]; const bytesStr = atob(fixBase64String(sound.file_reference_base64, false)); @@ -206,6 +210,8 @@ export class AppStickersManager { // TEST_FILE_REFERENCE_REFRESH = false; // } }); + + return promise; } public async getRecentStickers(): Promise { if(this.getAppConfigPromise && !overwrite) return this.getAppConfigPromise; - return this.getAppConfigPromise = this.invokeApi('help.getAppConfig').then(config => { + const promise = this.getAppConfigPromise = this.invokeApi('help.getAppConfig').then(config => { + if(this.getAppConfigPromise !== promise) { + return; + } + rootScope.appConfig = config; return config; }); + + return promise; } } diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index ce85955c..4e93f585 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -4,7 +4,7 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall, MethodDeclMap } from "../layer"; +import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall, MethodDeclMap, MessageReactions } from "../layer"; import type { MyDocument } from "./appManagers/appDocsManager"; import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager"; import type { MyDialogFilter } from "./storages/filters"; @@ -76,6 +76,7 @@ export type BroadcastEvents = { 'message_edit': {storage: MessagesStorage, peerId: PeerId, mid: number}, 'message_views': {peerId: PeerId, mid: number, views: number}, 'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number, message: MyMessage}, + 'message_reactions': Message.message, 'messages_pending': void, 'messages_read': void, 'messages_downloaded': {peerId: PeerId, mids: number[]}, @@ -156,6 +157,8 @@ export type BroadcastEvents = { // 'group_call_video_track_added': {instance: GroupCallInstance} 'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */}, + + 'quick_reaction': string }; export class RootScope extends EventListenerBase<{