Reactions management
Config, stickers: fix possible race conditions
This commit is contained in:
parent
77d48d764d
commit
a0384f5daf
@ -18,11 +18,16 @@ export default class RadioField {
|
|||||||
langKey?: LangPackKey,
|
langKey?: LangPackKey,
|
||||||
name: string,
|
name: string,
|
||||||
value?: string,
|
value?: string,
|
||||||
stateKey?: string
|
stateKey?: string,
|
||||||
|
alignRight?: boolean
|
||||||
}) {
|
}) {
|
||||||
const label = this.label = document.createElement('label');
|
const label = this.label = document.createElement('label');
|
||||||
label.classList.add('radio-field');
|
label.classList.add('radio-field');
|
||||||
|
|
||||||
|
if(options.alignRight) {
|
||||||
|
label.classList.add('radio-field-right');
|
||||||
|
}
|
||||||
|
|
||||||
const input = this.input = document.createElement('input');
|
const input = this.input = document.createElement('input');
|
||||||
input.type = 'radio';
|
input.type = 'radio';
|
||||||
/* input.id = */input.name = 'input-radio-' + options.name;
|
/* input.id = */input.name = 'input-radio-' + options.name;
|
||||||
|
@ -17,6 +17,7 @@ export default class Row {
|
|||||||
public container: HTMLElement;
|
public container: HTMLElement;
|
||||||
public title: HTMLDivElement;
|
public title: HTMLDivElement;
|
||||||
public subtitle: HTMLElement;
|
public subtitle: HTMLElement;
|
||||||
|
public media: HTMLElement;
|
||||||
|
|
||||||
public checkboxField: CheckboxField;
|
public checkboxField: CheckboxField;
|
||||||
public radioField: RadioField;
|
public radioField: RadioField;
|
||||||
@ -31,7 +32,7 @@ export default class Row {
|
|||||||
radioField: Row['radioField'],
|
radioField: Row['radioField'],
|
||||||
checkboxField: Row['checkboxField'],
|
checkboxField: Row['checkboxField'],
|
||||||
noCheckboxSubtitle: boolean,
|
noCheckboxSubtitle: boolean,
|
||||||
title: string,
|
title: string | HTMLElement,
|
||||||
titleLangKey: LangPackKey,
|
titleLangKey: LangPackKey,
|
||||||
titleRight: string | HTMLElement,
|
titleRight: string | HTMLElement,
|
||||||
clickable: boolean | ((e: Event) => void),
|
clickable: boolean | ((e: Event) => void),
|
||||||
@ -58,10 +59,10 @@ export default class Row {
|
|||||||
|
|
||||||
let havePadding = !!options.havePadding;
|
let havePadding = !!options.havePadding;
|
||||||
if(options.radioField || options.checkboxField) {
|
if(options.radioField || options.checkboxField) {
|
||||||
havePadding = true;
|
|
||||||
if(options.radioField) {
|
if(options.radioField) {
|
||||||
this.radioField = options.radioField;
|
this.radioField = options.radioField;
|
||||||
this.container.append(this.radioField.label);
|
this.container.append(this.radioField.label);
|
||||||
|
havePadding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.checkboxField) {
|
if(options.checkboxField) {
|
||||||
@ -72,6 +73,7 @@ export default class Row {
|
|||||||
this.container.classList.add('row-with-toggle');
|
this.container.classList.add('row-with-toggle');
|
||||||
options.titleRight = this.checkboxField.label;
|
options.titleRight = this.checkboxField.label;
|
||||||
} else {
|
} else {
|
||||||
|
havePadding = true;
|
||||||
this.container.append(this.checkboxField.label);
|
this.container.append(this.checkboxField.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +102,11 @@ export default class Row {
|
|||||||
this.title.classList.add('row-title');
|
this.title.classList.add('row-title');
|
||||||
this.title.setAttribute('dir', 'auto');
|
this.title.setAttribute('dir', 'auto');
|
||||||
if(options.title) {
|
if(options.title) {
|
||||||
|
if(typeof(options.title) === 'string') {
|
||||||
this.title.innerHTML = options.title;
|
this.title.innerHTML = options.title;
|
||||||
|
} else {
|
||||||
|
this.title.append(options.title);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.title.append(i18n(options.titleLangKey));
|
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) => {
|
export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void) => {
|
||||||
|
@ -20,13 +20,14 @@ import appStickersManager from "../../../lib/appManagers/appStickersManager";
|
|||||||
import assumeType from "../../../helpers/assumeType";
|
import assumeType from "../../../helpers/assumeType";
|
||||||
import { MessagesAllStickers, StickerSet } from "../../../layer";
|
import { MessagesAllStickers, StickerSet } from "../../../layer";
|
||||||
import RichTextProcessor from "../../../lib/richtextprocessor";
|
import RichTextProcessor from "../../../lib/richtextprocessor";
|
||||||
import { wrapSticker, wrapStickerSetThumb } from "../../wrappers";
|
import { wrapStickerSetThumb, wrapStickerToRow } from "../../wrappers";
|
||||||
import LazyLoadQueue from "../../lazyLoadQueue";
|
import LazyLoadQueue from "../../lazyLoadQueue";
|
||||||
import PopupStickers from "../../popups/stickers";
|
import PopupStickers from "../../popups/stickers";
|
||||||
import eachMinute from "../../../helpers/eachMinute";
|
import eachMinute from "../../../helpers/eachMinute";
|
||||||
import { SliderSuperTabEventable } from "../../sliderTab";
|
import { SliderSuperTabEventable } from "../../sliderTab";
|
||||||
import IS_GEOLOCATION_SUPPORTED from "../../../environment/geolocationSupport";
|
import IS_GEOLOCATION_SUPPORTED from "../../../environment/geolocationSupport";
|
||||||
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
|
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
|
||||||
|
import AppQuickReactionTab from "./quickReaction";
|
||||||
|
|
||||||
export class RangeSettingSelector {
|
export class RangeSettingSelector {
|
||||||
public container: HTMLDivElement;
|
public container: HTMLDivElement;
|
||||||
@ -287,26 +288,26 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
|
|||||||
const container = section('Telegram.InstalledStickerPacksController');
|
const container = section('Telegram.InstalledStickerPacksController');
|
||||||
|
|
||||||
const reactionsRow = new Row({
|
const reactionsRow = new Row({
|
||||||
titleLangKey: 'Reactions',
|
titleLangKey: 'DoubleTapSetting',
|
||||||
havePadding: true,
|
havePadding: true,
|
||||||
clickable: () => {
|
clickable: () => {
|
||||||
|
new AppQuickReactionTab(this.slider).open();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const quickReactionMediaDiv = document.createElement('div');
|
const renderQuickReaction = () => {
|
||||||
quickReactionMediaDiv.classList.add('row-media', 'row-media-small');
|
|
||||||
|
|
||||||
appReactionsManager.getQuickReaction().then(reaction => {
|
appReactionsManager.getQuickReaction().then(reaction => {
|
||||||
wrapSticker({
|
wrapStickerToRow({
|
||||||
div: quickReactionMediaDiv,
|
row: reactionsRow,
|
||||||
doc: reaction.static_icon,
|
doc: reaction.static_icon,
|
||||||
width: 32,
|
size: 'small'
|
||||||
height: 32
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
reactionsRow.container.append(quickReactionMediaDiv);
|
renderQuickReaction();
|
||||||
|
|
||||||
|
this.listenerSetter.add(rootScope)('quick_reaction', renderQuickReaction);
|
||||||
|
|
||||||
const suggestCheckboxField = new CheckboxField({
|
const suggestCheckboxField = new CheckboxField({
|
||||||
text: 'Stickers.SuggestStickers',
|
text: 'Stickers.SuggestStickers',
|
||||||
|
65
src/components/sidebarLeft/tabs/quickReaction.ts
Normal file
65
src/components/sidebarLeft/tabs/quickReaction.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
100
src/components/sidebarRight/tabs/chatReactions.ts
Normal file
100
src/components/sidebarRight/tabs/chatReactions.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -21,22 +21,27 @@ import PopupDeleteDialog from "../../popups/deleteDialog";
|
|||||||
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
|
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
|
||||||
import toggleDisability from "../../../helpers/dom/toggleDisability";
|
import toggleDisability from "../../../helpers/dom/toggleDisability";
|
||||||
import CheckboxField from "../../checkboxField";
|
import CheckboxField from "../../checkboxField";
|
||||||
|
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
|
||||||
|
import AppChatReactionsTab from "./chatReactions";
|
||||||
|
|
||||||
export default class AppEditChatTab extends SliderSuperTab {
|
export default class AppEditChatTab extends SliderSuperTab {
|
||||||
private chatNameInputField: InputField;
|
private chatNameInputField: InputField;
|
||||||
private descriptionInputField: InputField;
|
private descriptionInputField: InputField;
|
||||||
private editPeer: EditPeer;
|
private editPeer: EditPeer;
|
||||||
|
private tempId: number;
|
||||||
public chatId: ChatId;
|
public chatId: ChatId;
|
||||||
|
|
||||||
protected async _init() {
|
protected async _init() {
|
||||||
// * cleanup prev
|
// * cleanup prev
|
||||||
this.listenerSetter.removeAll();
|
this.listenerSetter.removeAll();
|
||||||
this.scrollable.container.innerHTML = '';
|
this.scrollable.container.innerHTML = '';
|
||||||
|
this.tempId ??= 0;
|
||||||
|
const tempId = ++this.tempId;
|
||||||
|
|
||||||
this.container.classList.add('edit-peer-container', 'edit-group-container');
|
this.container.classList.add('edit-peer-container', 'edit-group-container');
|
||||||
this.setTitle('Edit');
|
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 chat: Chat.chat | Chat.channel = appChatsManager.getChat(this.chatId);
|
||||||
const isBroadcast = appChatsManager.isBroadcast(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);
|
const peerId = this.chatId.toPeerId(true);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -119,6 +130,32 @@ export default class AppEditChatTab extends SliderSuperTab {
|
|||||||
|
|
||||||
setChatTypeSubtitle();
|
setChatTypeSubtitle();
|
||||||
section.content.append(chatTypeRow.container);
|
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) {
|
if(appChatsManager.hasRights(this.chatId, 'change_permissions') && !isBroadcast) {
|
||||||
|
@ -56,6 +56,7 @@ import appMessagesIdsManager from '../lib/appManagers/appMessagesIdsManager';
|
|||||||
import throttle from '../helpers/schedulers/throttle';
|
import throttle from '../helpers/schedulers/throttle';
|
||||||
import { SendMessageEmojiInteractionData } from '../types';
|
import { SendMessageEmojiInteractionData } from '../types';
|
||||||
import IS_VIBRATE_SUPPORTED from '../environment/vibrateSupport';
|
import IS_VIBRATE_SUPPORTED from '../environment/vibrateSupport';
|
||||||
|
import Row from './row';
|
||||||
|
|
||||||
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
|
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<any>[] = 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}: {
|
export function wrapLocalSticker({emoji, width, height}: {
|
||||||
doc?: MyDocument,
|
doc?: MyDocument,
|
||||||
url?: string,
|
url?: string,
|
||||||
|
9
src/helpers/callbackify.ts
Normal file
9
src/helpers/callbackify.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {Awaited} from '../types';
|
||||||
|
|
||||||
|
export default function callbackify<T extends Awaited<any>, R extends any>(smth: T, callback: (result: Awaited<T>) => R): PromiseLike<R> | R {
|
||||||
|
if(smth instanceof Promise) {
|
||||||
|
return smth.then(callback);
|
||||||
|
} else {
|
||||||
|
return callback(smth as any);
|
||||||
|
}
|
||||||
|
}
|
82
src/lang.ts
82
src/lang.ts
@ -642,6 +642,10 @@ const lang = {
|
|||||||
"Update": "UPDATE",
|
"Update": "UPDATE",
|
||||||
"Reactions": "Reactions",
|
"Reactions": "Reactions",
|
||||||
"DoubleTapSetting": "Quick Reaction",
|
"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
|
// * macos
|
||||||
"AccountSettings.Filters": "Chat Folders",
|
"AccountSettings.Filters": "Chat Folders",
|
||||||
@ -833,22 +837,25 @@ const lang = {
|
|||||||
"Emoji.Objects": "Objects",
|
"Emoji.Objects": "Objects",
|
||||||
//"Emoji.Symbols": "Symbols",
|
//"Emoji.Symbols": "Symbols",
|
||||||
"Emoji.Flags": "Flags",
|
"Emoji.Flags": "Flags",
|
||||||
|
"InstalledStickers.LoopAnimated": "Loop Animated Stickers",
|
||||||
"LastSeen.HoursAgo": {
|
"LastSeen.HoursAgo": {
|
||||||
"one_value": "last seen %d hour ago",
|
"one_value": "last seen %d hour ago",
|
||||||
"other_value": "last seen %d hours ago"
|
"other_value": "last seen %d hours ago"
|
||||||
},
|
},
|
||||||
"Login.Register.LastName.Placeholder": "Last Name",
|
"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",
|
"Modal.Send": "Send",
|
||||||
"Telegram.GeneralSettingsViewController": "General Settings",
|
"NewPoll.Anonymous": "Anonymous Voting",
|
||||||
"Telegram.InstalledStickerPacksController": "Stickers",
|
"NewPoll.Explanation.Placeholder": "Add a Comment (Optional)",
|
||||||
"Telegram.NotificationSettingsViewController": "Notifications",
|
"NewPoll.OptionsAddOption": "Add an Option",
|
||||||
"Telegram.LanguageViewController": "Language",
|
"NewPoll.MultipleChoice": "Multiple Answers",
|
||||||
"Stickers.SearchAdd": "Add",
|
"NewPoll.Quiz": "Quiz Mode",
|
||||||
"Stickers.SearchAdded": "Added",
|
"Notification.Contact.Reacted": "%1$@ to your \"%2$@\"",
|
||||||
"Stickers.SuggestStickers": "Suggest Stickers by Emoji",
|
// "Notification.Group.Reacted": "%1$@: %2$@ to your \"%3$@\"",
|
||||||
"ShareModal.Search.Placeholder": "Share to...",
|
|
||||||
"ShareModal.Search.ForwardPlaceholder": "Forward to...",
|
|
||||||
"InstalledStickers.LoopAnimated": "Loop Animated Stickers",
|
|
||||||
"Peer.Activity.User.PlayingGame": "playing a game",
|
"Peer.Activity.User.PlayingGame": "playing a game",
|
||||||
"Peer.Activity.User.TypingText": "typing",
|
"Peer.Activity.User.TypingText": "typing",
|
||||||
"Peer.Activity.User.SendingPhoto": "sending a photo",
|
"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.",
|
"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",
|
"RequestJoin.Button": "Request to Join",
|
||||||
"Message.Context.Select": "Select",
|
"Stickers.SearchAdd": "Add",
|
||||||
"Message.Context.Pin": "Pin",
|
"Stickers.SearchAdded": "Added",
|
||||||
"Message.Context.Unpin": "Unpin",
|
"Stickers.SuggestStickers": "Suggest Stickers by Emoji",
|
||||||
"Message.Context.Goto": "Show Message",
|
"ShareModal.Search.Placeholder": "Share to...",
|
||||||
"MessageContext.CopyMessageLink1": "Copy Message Link",
|
"ShareModal.Search.ForwardPlaceholder": "Forward to...",
|
||||||
"NewPoll.Anonymous": "Anonymous Voting",
|
"Telegram.GeneralSettingsViewController": "General Settings",
|
||||||
"NewPoll.Explanation.Placeholder": "Add a Comment (Optional)",
|
"Telegram.InstalledStickerPacksController": "Stickers",
|
||||||
"NewPoll.OptionsAddOption": "Add an Option",
|
"Telegram.NotificationSettingsViewController": "Notifications",
|
||||||
"NewPoll.MultipleChoice": "Multiple Answers",
|
"Telegram.LanguageViewController": "Language",
|
||||||
"NewPoll.Quiz": "Quiz Mode",
|
|
||||||
"GeneralSettings.BigEmoji": "Large Emoji",
|
"GeneralSettings.BigEmoji": "Large Emoji",
|
||||||
"GeneralSettings.EmojiPrediction": "Suggest Emoji",
|
"GeneralSettings.EmojiPrediction": "Suggest Emoji",
|
||||||
"GroupPermission.Delete": "Delete Exception",
|
"GroupPermission.Delete": "Delete Exception",
|
||||||
@ -981,6 +987,23 @@ const lang = {
|
|||||||
"Stickers.Recent": "Recent",
|
"Stickers.Recent": "Recent",
|
||||||
//"Stickers.Favorite": "Favorite",
|
//"Stickers.Favorite": "Favorite",
|
||||||
"StickerSet.DontExist": "Sorry, this sticker set doesn't seem to exist.",
|
"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": "Video chat ended. Start a new one?",
|
||||||
"VoiceChat.Chat.StartNew.OK": "Start",
|
"VoiceChat.Chat.StartNew.OK": "Start",
|
||||||
"VoiceChat.Chat.Ended": "Video chat ended.",
|
"VoiceChat.Chat.Ended": "Video chat ended.",
|
||||||
@ -1012,24 +1035,7 @@ const lang = {
|
|||||||
"VoiceChat.UnmuteForMe": "Unmute For Me",
|
"VoiceChat.UnmuteForMe": "Unmute For Me",
|
||||||
"VoiceChat.RemovePeer.Confirm.Channel": "Do you want to remove %1$@ from the channel?",
|
"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": "Are you sure you want to remove %1$@ from the group?",
|
||||||
"VoiceChat.RemovePeer.Confirm.OK": "Remove",
|
"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."
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default lang;
|
export default lang;
|
||||||
|
@ -770,6 +770,15 @@ export class AppChatsManager {
|
|||||||
apiUpdatesManager.processUpdateMessage(updates);
|
apiUpdatesManager.processUpdateMessage(updates);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setChatAvailableReactions(id: ChatId, reactions: Array<string>) {
|
||||||
|
return apiManager.invokeApi('messages.setChatAvailableReactions', {
|
||||||
|
peer: this.getInputPeer(id),
|
||||||
|
available_reactions: reactions
|
||||||
|
}).then(updates => {
|
||||||
|
apiUpdatesManager.processUpdateMessage(updates);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const appChatsManager = new AppChatsManager();
|
const appChatsManager = new AppChatsManager();
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||||
import assumeType from "../../helpers/assumeType";
|
import assumeType from "../../helpers/assumeType";
|
||||||
|
import callbackify from "../../helpers/callbackify";
|
||||||
import { AvailableReaction, MessagesAvailableReactions } from "../../layer";
|
import { AvailableReaction, MessagesAvailableReactions } from "../../layer";
|
||||||
import apiManager from "../mtproto/mtprotoworker";
|
import apiManager from "../mtproto/mtprotoworker";
|
||||||
import { ReferenceContext } from "../mtproto/referenceDatabase";
|
import { ReferenceContext } from "../mtproto/referenceDatabase";
|
||||||
@ -37,7 +38,7 @@ export class AppReactionsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getAvailableReactions() {
|
public getAvailableReactions() {
|
||||||
if(this.availableReactions) return Promise.resolve(this.availableReactions);
|
if(this.availableReactions) return this.availableReactions;
|
||||||
return apiManager.invokeApiSingleProcess({
|
return apiManager.invokeApiSingleProcess({
|
||||||
method: 'messages.getAvailableReactions',
|
method: 'messages.getAvailableReactions',
|
||||||
processResult: (messagesAvailableReactions) => {
|
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() {
|
public getQuickReaction() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
apiManager.getAppConfig(),
|
apiManager.getAppConfig(),
|
||||||
@ -70,6 +82,49 @@ export class AppReactionsManager {
|
|||||||
return availableReactions.find(reaction => reaction.reaction === appConfig.reactions_default);
|
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();
|
const appReactionsManager = new AppReactionsManager();
|
||||||
|
@ -162,7 +162,11 @@ export class AppStickersManager {
|
|||||||
|
|
||||||
public getAnimatedEmojiSounds(overwrite?: boolean) {
|
public getAnimatedEmojiSounds(overwrite?: boolean) {
|
||||||
if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise;
|
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) {
|
for(const emoji in appConfig.emojies_sounds) {
|
||||||
const sound = appConfig.emojies_sounds[emoji];
|
const sound = appConfig.emojies_sounds[emoji];
|
||||||
const bytesStr = atob(fixBase64String(sound.file_reference_base64, false));
|
const bytesStr = atob(fixBase64String(sound.file_reference_base64, false));
|
||||||
@ -206,6 +210,8 @@ export class AppStickersManager {
|
|||||||
// TEST_FILE_REFERENCE_REFRESH = false;
|
// TEST_FILE_REFERENCE_REFRESH = false;
|
||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {
|
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {
|
||||||
|
@ -698,10 +698,16 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
|
|||||||
|
|
||||||
public getAppConfig(overwrite?: boolean): Promise<MTAppConfig> {
|
public getAppConfig(overwrite?: boolean): Promise<MTAppConfig> {
|
||||||
if(this.getAppConfigPromise && !overwrite) return this.getAppConfigPromise;
|
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;
|
rootScope.appConfig = config;
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* 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 { MyDocument } from "./appManagers/appDocsManager";
|
||||||
import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager";
|
import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager";
|
||||||
import type { MyDialogFilter } from "./storages/filters";
|
import type { MyDialogFilter } from "./storages/filters";
|
||||||
@ -76,6 +76,7 @@ export type BroadcastEvents = {
|
|||||||
'message_edit': {storage: MessagesStorage, peerId: PeerId, mid: number},
|
'message_edit': {storage: MessagesStorage, peerId: PeerId, mid: number},
|
||||||
'message_views': {peerId: PeerId, mid: number, views: number},
|
'message_views': {peerId: PeerId, mid: number, views: number},
|
||||||
'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number, message: MyMessage},
|
'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number, message: MyMessage},
|
||||||
|
'message_reactions': Message.message,
|
||||||
'messages_pending': void,
|
'messages_pending': void,
|
||||||
'messages_read': void,
|
'messages_read': void,
|
||||||
'messages_downloaded': {peerId: PeerId, mids: number[]},
|
'messages_downloaded': {peerId: PeerId, mids: number[]},
|
||||||
@ -156,6 +157,8 @@ export type BroadcastEvents = {
|
|||||||
// 'group_call_video_track_added': {instance: GroupCallInstance}
|
// 'group_call_video_track_added': {instance: GroupCallInstance}
|
||||||
|
|
||||||
'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */},
|
'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */},
|
||||||
|
|
||||||
|
'quick_reaction': string
|
||||||
};
|
};
|
||||||
|
|
||||||
export class RootScope extends EventListenerBase<{
|
export class RootScope extends EventListenerBase<{
|
||||||
|
Loading…
Reference in New Issue
Block a user