Browse Source

Fix memory leaks

master
Eduard Kuzmenko 2 years ago
parent
commit
d2d184d242
  1. 45
      src/components/appSearchSuper..ts
  2. 2
      src/components/call/videoCanvasBlur.ts
  3. 8
      src/components/chat/bubbles.ts
  4. 5
      src/components/chat/chat.ts
  5. 4
      src/components/chat/gradientRenderer.ts
  6. 5
      src/components/chat/replyKeyboard.ts
  7. 6
      src/components/chat/selection.ts
  8. 33
      src/components/checkboxField.ts
  9. 3
      src/components/emoticonsDropdown/tabs/emoji.ts
  10. 3
      src/components/emoticonsDropdown/tabs/gifs.ts
  11. 11
      src/components/horizontalMenu.ts
  12. 16
      src/components/peerProfile.ts
  13. 1
      src/components/peerProfileAvatars.ts
  14. 2
      src/components/popups/reactedList.ts
  15. 16
      src/components/row.ts
  16. 18
      src/components/scrollable.ts
  17. 2
      src/components/sidebarLeft/tabs/autoDownload/file.ts
  18. 17
      src/components/sidebarLeft/tabs/autoDownload/photo.ts
  19. 2
      src/components/sidebarLeft/tabs/autoDownload/video.ts
  20. 15
      src/components/sidebarLeft/tabs/dataAndStorage.ts
  21. 19
      src/components/sidebarLeft/tabs/editFolder.ts
  22. 21
      src/components/sidebarLeft/tabs/generalSettings.ts
  23. 5
      src/components/sidebarLeft/tabs/includedChats.ts
  24. 5
      src/components/sidebarLeft/tabs/newChannel.ts
  25. 7
      src/components/sidebarLeft/tabs/newGroup.ts
  26. 6
      src/components/sidebarLeft/tabs/notifications.ts
  27. 27
      src/components/sidebarLeft/tabs/privacyAndSecurity.ts
  28. 25
      src/components/sidebarLeft/tabs/settings.ts
  29. 6
      src/components/sidebarRight/index.ts
  30. 6
      src/components/sidebarRight/tabs/chatReactions.ts
  31. 3
      src/components/sidebarRight/tabs/chatType.ts
  32. 7
      src/components/sidebarRight/tabs/editChat.ts
  33. 3
      src/components/sidebarRight/tabs/editContact.ts
  34. 3
      src/components/sidebarRight/tabs/groupPermissions.ts
  35. 11
      src/components/sidebarRight/tabs/sharedMedia.ts
  36. 3
      src/components/singleTransition.ts
  37. 2
      src/components/slider.ts
  38. 1
      src/components/sliderTab.ts
  39. 28
      src/components/transition.ts
  40. 13
      src/components/wrappers/video.ts
  41. 2
      src/helpers/canvas/getTextWidth.ts
  42. 7
      src/helpers/dom/renderImageWithFadeIn.ts
  43. 2
      src/lib/appManagers/appDialogsManager.ts
  44. 5
      src/lib/appManagers/appImManager.ts
  45. 18
      src/lib/mediaPlayer.ts
  46. 4
      src/lib/rlottie/queryableWorker.ts
  47. 3
      webpack.common.js

45
src/components/appSearchSuper..ts

@ -69,6 +69,8 @@ import { attachContextMenuListener } from "../helpers/dom/attachContextMenuListe
import contextMenuController from "../helpers/contextMenuController"; import contextMenuController from "../helpers/contextMenuController";
import positionMenu from "../helpers/positionMenu"; import positionMenu from "../helpers/positionMenu";
import apiManagerProxy from "../lib/mtproto/mtprotoworker"; import apiManagerProxy from "../lib/mtproto/mtprotoworker";
import ListenerSetter from "../helpers/listenerSetter";
import SwipeHandler from "./swipeHandler";
//const testScroll = false; //const testScroll = false;
@ -107,7 +109,8 @@ class SearchContextMenu {
constructor( constructor(
private attachTo: HTMLElement, private attachTo: HTMLElement,
private searchSuper: AppSearchSuper private searchSuper: AppSearchSuper,
private listenerSetter: ListenerSetter
) { ) {
this.managers = searchSuper.managers; this.managers = searchSuper.managers;
@ -162,7 +165,7 @@ class SearchContextMenu {
if(IS_TOUCH_SUPPORTED) { if(IS_TOUCH_SUPPORTED) {
} else { } else {
attachContextMenuListener(attachTo, onContextMenu as any); attachContextMenuListener(attachTo, onContextMenu as any, listenerSetter);
} }
} }
@ -323,14 +326,18 @@ export default class AppSearchSuper {
public managers: AppManagers; public managers: AppManagers;
private loadFirstTimePromise: Promise<void>; private loadFirstTimePromise: Promise<void>;
private listenerSetter: ListenerSetter;
private swipeHandler: SwipeHandler;
constructor(options: Pick<AppSearchSuper, 'mediaTabs' | 'scrollable' | 'searchGroups' | 'asChatList' | 'groupByMonth' | 'hideEmptyTabs' | 'onChangeTab' | 'showSender' | 'managers'>) { constructor(options: Pick<AppSearchSuper, 'mediaTabs' | 'scrollable' | 'searchGroups' | 'asChatList' | 'groupByMonth' | 'hideEmptyTabs' | 'onChangeTab' | 'showSender' | 'managers'>) {
safeAssign(this, options); safeAssign(this, options);
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('search-super'); this.container.classList.add('search-super');
this.searchContextMenu = new SearchContextMenu(this.container, this); this.listenerSetter = new ListenerSetter();
this.selection = new SearchSelection(this, this.managers); this.searchContextMenu = new SearchContextMenu(this.container, this, this.listenerSetter);
this.selection = new SearchSelection(this, this.managers, this.listenerSetter);
const navScrollableContainer = this.navScrollableContainer = document.createElement('div'); const navScrollableContainer = this.navScrollableContainer = document.createElement('div');
navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky'); navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky');
@ -369,7 +376,7 @@ export default class AppSearchSuper {
let unlockScroll: ReturnType<typeof lockTouchScroll>; let unlockScroll: ReturnType<typeof lockTouchScroll>;
if(IS_TOUCH_SUPPORTED) { if(IS_TOUCH_SUPPORTED) {
handleTabSwipe({ this.swipeHandler = handleTabSwipe({
element: this.tabsContainer, element: this.tabsContainer,
onSwipe: (xDiff, yDiff, e) => { onSwipe: (xDiff, yDiff, e) => {
const prevId = this.selectTab.prevId(); const prevId = this.selectTab.prevId();
@ -521,14 +528,14 @@ export default class AppSearchSuper {
} }
this.onTransitionEnd(); this.onTransitionEnd();
}, undefined, navScrollable); }, undefined, navScrollable, this.listenerSetter);
attachClickEvent(this.tabsContainer, (e) => { attachClickEvent(this.tabsContainer, (e) => {
if(this.selection.isSelecting) { if(this.selection.isSelecting) {
cancelEvent(e); cancelEvent(e);
this.selection.toggleByElement(findUpClassName(e.target, 'search-super-item')); this.selection.toggleByElement(findUpClassName(e.target, 'search-super-item'));
} }
}, {capture: true, passive: false}); }, {capture: true, passive: false, listenerSetter: this.listenerSetter});
const onMediaClick = async(className: string, targetClassName: string, inputFilter: MyInputMessagesFilter, e: MouseEvent) => { const onMediaClick = async(className: string, targetClassName: string, inputFilter: MyInputMessagesFilter, e: MouseEvent) => {
const target = findUpClassName(e.target as HTMLDivElement, className); const target = findUpClassName(e.target as HTMLDivElement, className);
@ -560,8 +567,8 @@ export default class AppSearchSuper {
.openMedia(message, targets[idx].element, 0, false, targets.slice(0, idx), targets.slice(idx + 1)); .openMedia(message, targets[idx].element, 0, false, targets.slice(0, idx), targets.slice(idx + 1));
}; };
attachClickEvent(this.tabs.inputMessagesFilterPhotoVideo, onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo')); attachClickEvent(this.tabs.inputMessagesFilterPhotoVideo, onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo'), {listenerSetter: this.listenerSetter});
attachClickEvent(this.tabs.inputMessagesFilterDocument, onMediaClick.bind(null, 'document-with-thumb', 'media-container', 'inputMessagesFilterDocument')); attachClickEvent(this.tabs.inputMessagesFilterDocument, onMediaClick.bind(null, 'document-with-thumb', 'media-container', 'inputMessagesFilterDocument'), {listenerSetter: this.listenerSetter});
/* attachClickEvent(this.tabs.inputMessagesFilterUrl, (e) => { /* attachClickEvent(this.tabs.inputMessagesFilterUrl, (e) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
@ -581,7 +588,7 @@ export default class AppSearchSuper {
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
}, () => { }, () => {
this.lazyLoadQueue.unlockAndRefresh(); // ! maybe not so efficient this.lazyLoadQueue.unlockAndRefresh(); // ! maybe not so efficient
}); }, this.listenerSetter);
} }
private onTransitionStart = () => { private onTransitionStart = () => {
@ -920,7 +927,7 @@ export default class AppSearchSuper {
element.dataset.peerId = '' + message.peerId; element.dataset.peerId = '' + message.peerId;
monthContainer.items[method](element); monthContainer.items[method](element);
if(this.selection.isSelecting) { if(this.selection?.isSelecting) {
this.selection.toggleElementCheckbox(element, true); this.selection.toggleElementCheckbox(element, true);
} }
}); });
@ -1524,7 +1531,7 @@ export default class AppSearchSuper {
this.usedFromHistory[mediaTab.inputFilter] = -1; this.usedFromHistory[mediaTab.inputFilter] = -1;
}); });
if(this.selection.isSelecting) { if(this.selection?.isSelecting) {
this.selection.cancelSelection(); this.selection.cancelSelection();
} }
@ -1641,4 +1648,18 @@ export default class AppSearchSuper {
this.cleanup(); this.cleanup();
} }
public destroy() {
this.listenerSetter.removeAll();
this.scrollable.destroy();
this.swipeHandler?.removeListeners();
this.selection?.cleanup();
this.scrollStartCallback = undefined;
this.onChangeTab = undefined;
this.selectTab = undefined;
this.searchContextMenu = undefined;
this.swipeHandler = undefined;
this.selection = undefined;
}
} }

2
src/components/call/videoCanvasBlur.ts

@ -13,7 +13,7 @@ export default function callVideoCanvasBlur(video: HTMLVideoElement) {
canvas.width = size; canvas.width = size;
canvas.height = size; canvas.height = size;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d', {alpha: false});
ctx.filter = 'blur(2px)'; ctx.filter = 'blur(2px)';
const renderFrame = () => { const renderFrame = () => {
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height); ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height);

8
src/components/chat/bubbles.ts

@ -1937,7 +1937,7 @@ export default class ChatBubbles {
} }
public onScroll = (ignoreHeavyAnimation?: boolean, scrollDimensions?: ScrollStartCallbackDimensions) => { public onScroll = (ignoreHeavyAnimation?: boolean, scrollDimensions?: ScrollStartCallbackDimensions) => {
//return; // return;
if(this.isHeavyAnimationInProgress) { if(this.isHeavyAnimationInProgress) {
if(this.sliceViewportDebounced) { if(this.sliceViewportDebounced) {
@ -2470,8 +2470,7 @@ export default class ChatBubbles {
} }
private destroyScrollable() { private destroyScrollable() {
this.scrollable.removeListeners(); this.scrollable.destroy();
this.scrollable.onScrolledTop = this.scrollable.onScrolledBottom = this.scrollable.onAdditionalScroll = null;
} }
public destroy() { public destroy() {
@ -2517,6 +2516,7 @@ export default class ChatBubbles {
// clear messages // clear messages
if(bubblesToo) { if(bubblesToo) {
this.scrollable.container.textContent = ''; this.scrollable.container.textContent = '';
this.chatInner.textContent = '';
this.cleanupPlaceholders(); this.cleanupPlaceholders();
} }
@ -2805,7 +2805,7 @@ export default class ChatBubbles {
this.attachPlaceholderOnRender(); this.attachPlaceholderOnRender();
} }
if(!isTarget && this.chat.type === 'chat') { if(!isTarget && this.chat.type === 'chat' && this.chat.topbar.pinnedMessage) {
this.chat.topbar.pinnedMessage.setCorrectIndex(0); this.chat.topbar.pinnedMessage.setCorrectIndex(0);
} }

5
src/components/chat/chat.ts

@ -458,6 +458,11 @@ export default class Chat extends EventListenerBase<{
this.cleanup(true); this.cleanup(true);
this.bubbles.setPeer(false, peerId); this.bubbles.setPeer(false, peerId);
this.appImManager.dispatchEvent('peer_changed', peerId); this.appImManager.dispatchEvent('peer_changed', peerId);
appSidebarRight.replaceSharedMediaTab();
this.destroySharedMediaTab();
this.sharedMediaTab = undefined;
return; return;
} }

4
src/components/chat/gradientRenderer.ts

@ -283,11 +283,11 @@ export default class ChatBackgroundGradientRenderer {
this._hc = document.createElement('canvas'); this._hc = document.createElement('canvas');
this._hc.width = this._width; this._hc.width = this._width;
this._hc.height = this._height; this._hc.height = this._height;
this._hctx = this._hc.getContext('2d'); this._hctx = this._hc.getContext('2d', {alpha: false});
} }
this._canvas = el; this._canvas = el;
this._ctx = this._canvas.getContext('2d'); this._ctx = this._canvas.getContext('2d', {alpha: false});
this.update(); this.update();
} }

5
src/components/chat/replyKeyboard.ts

@ -19,6 +19,7 @@ import safeAssign from "../../helpers/object/safeAssign";
import setInnerHTML from "../../helpers/dom/setInnerHTML"; import setInnerHTML from "../../helpers/dom/setInnerHTML";
import wrapEmojiText from "../../lib/richTextProcessor/wrapEmojiText"; import wrapEmojiText from "../../lib/richTextProcessor/wrapEmojiText";
import { AppManagers } from "../../lib/appManagers/managers"; import { AppManagers } from "../../lib/appManagers/managers";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
export default class ReplyKeyboard extends DropdownHover { export default class ReplyKeyboard extends DropdownHover {
private static BASE_CLASS = 'reply-keyboard'; private static BASE_CLASS = 'reply-keyboard';
@ -74,7 +75,7 @@ export default class ReplyKeyboard extends DropdownHover {
} }
}); });
this.listenerSetter.add(this.element)('click', (e) => { attachClickEvent(this.element, (e) => {
const target = findUpClassName(e.target, 'btn'); const target = findUpClassName(e.target, 'btn');
if(!target) { if(!target) {
return; return;
@ -103,7 +104,7 @@ export default class ReplyKeyboard extends DropdownHover {
} }
this.toggle(false); this.toggle(false);
}); }, {listenerSetter: this.listenerSetter});
return super.init(); return super.init();
} }

6
src/components/chat/selection.ts

@ -533,7 +533,7 @@ export class SearchSelection extends AppSelection {
private isPrivate: boolean; private isPrivate: boolean;
constructor(private searchSuper: AppSearchSuper, managers: AppManagers) { constructor(private searchSuper: AppSearchSuper, managers: AppManagers, listenerSetter: ListenerSetter) {
super({ super({
managers, managers,
verifyTarget: (e, target) => !!target && this.isSelecting, verifyTarget: (e, target) => !!target && this.isSelecting,
@ -544,7 +544,7 @@ export class SearchSelection extends AppSelection {
}); });
this.isPrivate = !searchSuper.showSender; this.isPrivate = !searchSuper.showSender;
this.attachListeners(searchSuper.container, new ListenerSetter()); this.attachListeners(searchSuper.container, listenerSetter);
} }
/* public appendCheckbox(element: HTMLElement, checkboxField: CheckboxField) { /* public appendCheckbox(element: HTMLElement, checkboxField: CheckboxField) {
@ -615,7 +615,7 @@ export class SearchSelection extends AppSelection {
this.selectionContainer.classList.add(BASE_CLASS + '-container'); this.selectionContainer.classList.add(BASE_CLASS + '-container');
const btnCancel = ButtonIcon(`close ${BASE_CLASS}-cancel`, {noRipple: true}); const btnCancel = ButtonIcon(`close ${BASE_CLASS}-cancel`, {noRipple: true});
this.listenerSetter.add(btnCancel)('click', () => this.cancelSelection(), {once: true}); attachClickEvent(btnCancel, () => this.cancelSelection(), {listenerSetter: this.listenerSetter, once: true});
this.selectionCountEl = document.createElement('div'); this.selectionCountEl = document.createElement('div');
this.selectionCountEl.classList.add(BASE_CLASS + '-count'); this.selectionCountEl.classList.add(BASE_CLASS + '-count');

33
src/components/checkboxField.ts

@ -9,6 +9,7 @@ import { LangPackKey, _i18n } from "../lib/langPack";
import getDeepProperty from "../helpers/object/getDeepProperty"; import getDeepProperty from "../helpers/object/getDeepProperty";
import rootScope from "../lib/rootScope"; import rootScope from "../lib/rootScope";
import apiManagerProxy from "../lib/mtproto/mtprotoworker"; import apiManagerProxy from "../lib/mtproto/mtprotoworker";
import ListenerSetter from "../helpers/listenerSetter";
export type CheckboxFieldOptions = { export type CheckboxFieldOptions = {
text?: LangPackKey, text?: LangPackKey,
@ -23,6 +24,7 @@ export type CheckboxFieldOptions = {
restriction?: boolean, restriction?: boolean,
withRipple?: boolean, withRipple?: boolean,
withHover?: boolean, withHover?: boolean,
listenerSetter?: ListenerSetter
}; };
export default class CheckboxField { export default class CheckboxField {
public input: HTMLInputElement; public input: HTMLInputElement;
@ -57,18 +59,12 @@ export default class CheckboxField {
} }
if(options.stateKey) { if(options.stateKey) {
apiManagerProxy.getState().then((state) => { let loaded = false;
const stateValue = getDeepProperty(state, options.stateKey); const onChange = () => {
let checked: boolean; if(!loaded) {
if(options.stateValues) { return;
checked = options.stateValues.indexOf(stateValue) === 1;
} else {
checked = stateValue;
} }
this.setValueSilently(checked);
input.addEventListener('change', () => {
let value: any; let value: any;
if(options.stateValues) { if(options.stateValues) {
value = options.stateValues[input.checked ? 1 : 0]; value = options.stateValues[input.checked ? 1 : 0];
@ -77,8 +73,23 @@ export default class CheckboxField {
} }
rootScope.managers.appStateManager.setByKey(options.stateKey, value); rootScope.managers.appStateManager.setByKey(options.stateKey, value);
};
apiManagerProxy.getState().then((state) => {
loaded = true;
const stateValue = getDeepProperty(state, options.stateKey);
let checked: boolean;
if(options.stateValues) {
checked = options.stateValues.indexOf(stateValue) === 1;
} else {
checked = stateValue;
}
this.setValueSilently(checked);
}); });
});
if(options.listenerSetter) options.listenerSetter.add(input)('change', onChange);
else input.addEventListener('change', onChange);
} }
let span: HTMLSpanElement; let span: HTMLSpanElement;

3
src/components/emoticonsDropdown/tabs/emoji.ts

@ -24,6 +24,7 @@ import { AppManagers } from "../../../lib/appManagers/managers";
import fixEmoji from "../../../lib/richTextProcessor/fixEmoji"; import fixEmoji from "../../../lib/richTextProcessor/fixEmoji";
import wrapEmojiText from "../../../lib/richTextProcessor/wrapEmojiText"; import wrapEmojiText from "../../../lib/richTextProcessor/wrapEmojiText";
import wrapSingleEmoji from "../../../lib/richTextProcessor/wrapSingleEmoji"; import wrapSingleEmoji from "../../../lib/richTextProcessor/wrapSingleEmoji";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
const loadedURLs: Set<string> = new Set(); const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container: HTMLElement, prepend = false, unify = false) { export function appendEmoji(emoji: string, container: HTMLElement, prepend = false, unify = false) {
@ -258,7 +259,7 @@ export default class EmojiTab implements EmoticonsTab {
}); });
}); });
this.content.addEventListener('click', this.onContentClick); attachClickEvent(this.content, this.onContentClick);
this.init = null; this.init = null;
rootScope.addEventListener('emoji_recent', (emoji) => { rootScope.addEventListener('emoji_recent', (emoji) => {

3
src/components/emoticonsDropdown/tabs/gifs.ts

@ -9,6 +9,7 @@ import GifsMasonry from "../../gifsMasonry";
import Scrollable from "../../scrollable"; import Scrollable from "../../scrollable";
import { putPreloader } from "../../putPreloader"; import { putPreloader } from "../../putPreloader";
import { AppManagers } from "../../../lib/appManagers/managers"; import { AppManagers } from "../../../lib/appManagers/managers";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
export default class GifsTab implements EmoticonsTab { export default class GifsTab implements EmoticonsTab {
private content: HTMLElement; private content: HTMLElement;
@ -20,7 +21,7 @@ export default class GifsTab implements EmoticonsTab {
init() { init() {
this.content = document.getElementById('content-gifs'); this.content = document.getElementById('content-gifs');
const gifsContainer = this.content.firstElementChild as HTMLDivElement; const gifsContainer = this.content.firstElementChild as HTMLDivElement;
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick); attachClickEvent(gifsContainer, EmoticonsDropdown.onMediaClick);
const scroll = new Scrollable(this.content, 'GIFS'); const scroll = new Scrollable(this.content, 'GIFS');
const masonry = new GifsMasonry(gifsContainer, EMOTICONSSTICKERGROUP, scroll); const masonry = new GifsMasonry(gifsContainer, EMOTICONSSTICKERGROUP, scroll);

11
src/components/horizontalMenu.ts

@ -11,6 +11,8 @@ import { fastRaf } from "../helpers/schedulers";
import { FocusDirection } from "../helpers/fastSmoothScroll"; import { FocusDirection } from "../helpers/fastSmoothScroll";
import findUpAsChild from "../helpers/dom/findUpAsChild"; import findUpAsChild from "../helpers/dom/findUpAsChild";
import whichChild from "../helpers/dom/whichChild"; import whichChild from "../helpers/dom/whichChild";
import ListenerSetter from "../helpers/listenerSetter";
import { attachClickEvent } from "../helpers/dom/clickEvent";
export function horizontalMenu( export function horizontalMenu(
tabs: HTMLElement, tabs: HTMLElement,
@ -18,9 +20,10 @@ export function horizontalMenu(
onClick?: (id: number, tabContent: HTMLDivElement, animate: boolean) => void | boolean | Promise<void | boolean>, onClick?: (id: number, tabContent: HTMLDivElement, animate: boolean) => void | boolean | Promise<void | boolean>,
onTransitionEnd?: () => void, onTransitionEnd?: () => void,
transitionTime = 250, transitionTime = 250,
scrollableX?: ScrollableX scrollableX?: ScrollableX,
listenerSetter?: ListenerSetter
) { ) {
const selectTab = TransitionSlider(content, tabs || content.dataset.animation === 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd); const selectTab = TransitionSlider(content, tabs || content.dataset.animation === 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd, undefined, listenerSetter);
if(!tabs) { if(!tabs) {
return selectTab; return selectTab;
@ -109,7 +112,7 @@ export function horizontalMenu(
//const tagName = tabs.classList.contains('menu-horizontal-div') ? 'BUTTON' : 'LI'; //const tagName = tabs.classList.contains('menu-horizontal-div') ? 'BUTTON' : 'LI';
const tagName = tabs.firstElementChild.tagName; const tagName = tabs.firstElementChild.tagName;
tabs.addEventListener('click', function(e) { attachClickEvent(tabs, (e) => {
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
target = findUpAsChild(target, tabs); target = findUpAsChild(target, tabs);
@ -129,7 +132,7 @@ export function horizontalMenu(
} }
selectTarget(target, id); selectTarget(target, id);
}); }, {listenerSetter});
return proxy; return proxy;
} }

16
src/components/peerProfile.ts

@ -105,7 +105,8 @@ export default class PeerProfile {
const full = await this.managers.appProfileManager.getProfileByPeerId(this.peerId); const full = await this.managers.appProfileManager.getProfileByPeerId(this.peerId);
copyTextToClipboard(full.about); copyTextToClipboard(full.about);
toast(I18n.format('BioCopied', true)); toast(I18n.format('BioCopied', true));
} },
listenerSetter: this.listenerSetter
}); });
this.bio.title.classList.add('pre-wrap'); this.bio.title.classList.add('pre-wrap');
@ -118,7 +119,8 @@ export default class PeerProfile {
const peer: Channel | User.user = await this.managers.appPeersManager.getPeer(this.peerId); const peer: Channel | User.user = await this.managers.appPeersManager.getPeer(this.peerId);
copyTextToClipboard('@' + peer.username); copyTextToClipboard('@' + peer.username);
toast(I18n.format('UsernameCopied', true)); toast(I18n.format('UsernameCopied', true));
} },
listenerSetter: this.listenerSetter
}); });
this.phone = new Row({ this.phone = new Row({
@ -129,7 +131,8 @@ export default class PeerProfile {
const peer: User = await this.managers.appUsersManager.getUser(this.peerId); const peer: User = await this.managers.appUsersManager.getUser(this.peerId);
copyTextToClipboard('+' + peer.phone); copyTextToClipboard('+' + peer.phone);
toast(I18n.format('PhoneCopied', true)); toast(I18n.format('PhoneCopied', true));
} },
listenerSetter: this.listenerSetter
}); });
this.link = new Row({ this.link = new Row({
@ -142,7 +145,8 @@ export default class PeerProfile {
// copyTextToClipboard(chatFull.exported_invite.link); // copyTextToClipboard(chatFull.exported_invite.link);
toast(I18n.format('LinkCopied', true)); toast(I18n.format('LinkCopied', true));
// }); // });
} },
listenerSetter: this.listenerSetter
}); });
this.location = new Row({ this.location = new Row({
@ -164,7 +168,8 @@ export default class PeerProfile {
this.notifications = new Row({ this.notifications = new Row({
checkboxField: new CheckboxField({toggle: true}), checkboxField: new CheckboxField({toggle: true}),
titleLangKey: 'Notifications', titleLangKey: 'Notifications',
icon: 'unmute' icon: 'unmute',
listenerSetter: this.listenerSetter
}); });
listenerSetter.add(this.notifications.checkboxField.input)('change', (e) => { listenerSetter.add(this.notifications.checkboxField.input)('change', (e) => {
@ -457,5 +462,6 @@ export default class PeerProfile {
public destroy() { public destroy() {
this.clearSetMoreDetailsTimeout(); this.clearSetMoreDetailsTimeout();
clearInterval(this.setPeerStatusInterval); clearInterval(this.setPeerStatusInterval);
this.avatars?.cleanup();
} }
} }

1
src/components/peerProfileAvatars.ts

@ -409,5 +409,6 @@ export default class PeerProfileAvatars {
public cleanup() { public cleanup() {
this.listenerSetter.removeAll(); this.listenerSetter.removeAll();
this.swipeHandler.removeListeners(); this.swipeHandler.removeListeners();
this.intersectionObserver?.disconnect();
} }
} }

2
src/components/popups/reactedList.ts

@ -185,7 +185,7 @@ export default class PopupReactedList extends PopupElement {
const loader = loaders.get(tabContent); const loader = loaders.get(tabContent);
loader.load(); loader.load();
}); }, undefined, undefined, undefined, this.listenerSetter);
// selectTab(hasAllReactions && hasReadParticipants ? 1 : 0, false); // selectTab(hasAllReactions && hasReadParticipants ? 1 : 0, false);
selectTab(0, false); selectTab(0, false);

16
src/components/row.ts

@ -12,6 +12,8 @@ import RadioForm from "./radioForm";
import { i18n, LangPackKey } from "../lib/langPack"; import { i18n, LangPackKey } from "../lib/langPack";
import replaceContent from "../helpers/dom/replaceContent"; import replaceContent from "../helpers/dom/replaceContent";
import setInnerHTML from "../helpers/dom/setInnerHTML"; import setInnerHTML from "../helpers/dom/setInnerHTML";
import ListenerSetter from "../helpers/listenerSetter";
import { attachClickEvent } from "../helpers/dom/clickEvent";
export default class Row { export default class Row {
public container: HTMLElement; public container: HTMLElement;
@ -41,7 +43,8 @@ export default class Row {
navigationTab: SliderSuperTab, navigationTab: SliderSuperTab,
havePadding: boolean, havePadding: boolean,
noRipple: boolean, noRipple: boolean,
noWrap: boolean noWrap: boolean,
listenerSetter: ListenerSetter
}> = {}) { }> = {}) {
this.container = document.createElement(options.radioField || options.checkboxField ? 'label' : 'div'); this.container = document.createElement(options.radioField || options.checkboxField ? 'label' : 'div');
this.container.classList.add('row'); this.container.classList.add('row');
@ -81,9 +84,12 @@ export default class Row {
} }
if(!options.noCheckboxSubtitle && !isToggle) { if(!options.noCheckboxSubtitle && !isToggle) {
this.checkboxField.input.addEventListener('change', () => { const onChange = () => {
replaceContent(this.subtitle, i18n(this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled')); replaceContent(this.subtitle, i18n(this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled'));
}); };
if(options.listenerSetter) options.listenerSetter.add(this.checkboxField.input)('change', onChange);
else this.checkboxField.input.addEventListener('change', onChange);
} }
} }
@ -151,10 +157,10 @@ export default class Row {
if(options.clickable || options.radioField || options.checkboxField) { if(options.clickable || options.radioField || options.checkboxField) {
if(typeof(options.clickable) === 'function') { if(typeof(options.clickable) === 'function') {
this.container.addEventListener('click', (e) => { attachClickEvent(this.container, (e) => {
if(this.freezed) return; if(this.freezed) return;
(options.clickable as any)(e); (options.clickable as any)(e);
}); }, {listenerSetter: options.listenerSetter});
} }
this.container.classList.add('row-clickable', 'hover-effect'); this.container.classList.add('row-clickable', 'hover-effect');

18
src/components/scrollable.ts

@ -143,6 +143,13 @@ export class ScrollableBase {
this.removeHeavyAnimationListener = undefined; this.removeHeavyAnimationListener = undefined;
} }
public destroy() {
this.removeListeners();
this.onAdditionalScroll = undefined;
this.onScrolledTop = undefined;
this.onScrolledBottom = undefined;
}
public append(element: HTMLElement) { public append(element: HTMLElement) {
this.container.append(element); this.container.append(element);
} }
@ -160,7 +167,7 @@ export class ScrollableBase {
//this.log('onScroll call', this.onScrollMeasure); //this.log('onScroll call', this.onScrollMeasure);
//} //}
//return; // return;
if(this.isHeavyAnimationInProgress) { if(this.isHeavyAnimationInProgress) {
this.cancelMeasure(); this.cancelMeasure();
@ -172,7 +179,8 @@ export class ScrollableBase {
if((!this.onScrolledTop && !this.onScrolledBottom) && !this.splitUp && !this.onAdditionalScroll) return; if((!this.onScrolledTop && !this.onScrolledBottom) && !this.splitUp && !this.onAdditionalScroll) return;
if(this.onScrollMeasure) return; if(this.onScrollMeasure) return;
// if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure); // if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure);
this.onScrollMeasure = window.requestAnimationFrame(() => { // this.onScrollMeasure = window.requestAnimationFrame(() => {
this.onScrollMeasure = window.setTimeout(() => {
this.onScrollMeasure = 0; this.onScrollMeasure = 0;
const scrollPosition = this.container[this.scrollProperty]; const scrollPosition = this.container[this.scrollProperty];
@ -187,12 +195,14 @@ export class ScrollableBase {
if(this.checkForTriggers) { if(this.checkForTriggers) {
this.checkForTriggers(); this.checkForTriggers();
} }
}); // });
}, 200);
}; };
public cancelMeasure() { public cancelMeasure() {
if(this.onScrollMeasure) { if(this.onScrollMeasure) {
window.cancelAnimationFrame(this.onScrollMeasure); // window.cancelAnimationFrame(this.onScrollMeasure);
clearTimeout(this.onScrollMeasure);
this.onScrollMeasure = 0; this.onScrollMeasure = 0;
} }
} }

2
src/components/sidebarLeft/tabs/autoDownload/file.ts

@ -21,7 +21,7 @@ export default class AppAutoDownloadFileTab extends SliderSuperTabEventable {
this.managers.appStateManager.setByKey('settings.autoDownloadNew.file_size_max', sizeMax); this.managers.appStateManager.setByKey('settings.autoDownloadNew.file_size_max', sizeMax);
}, 200, false, true); }, 200, false, true);
const section = autoDownloadPeerTypeSection('file', 'AutoDownloadFilesTitle'); const section = autoDownloadPeerTypeSection('file', 'AutoDownloadFilesTitle', this.listenerSetter);
const MIN = 512 * 1024; const MIN = 512 * 1024;
// const MAX = 2 * 1024 * 1024 * 1024; // const MAX = 2 * 1024 * 1024 * 1024;

17
src/components/sidebarLeft/tabs/autoDownload/photo.ts

@ -5,11 +5,12 @@
*/ */
import { SettingSection } from "../.."; import { SettingSection } from "../..";
import ListenerSetter from "../../../../helpers/listenerSetter";
import { LangPackKey } from "../../../../lib/langPack"; import { LangPackKey } from "../../../../lib/langPack";
import CheckboxField from "../../../checkboxField"; import CheckboxField from "../../../checkboxField";
import { SliderSuperTabEventable } from "../../../sliderTab"; import { SliderSuperTabEventable } from "../../../sliderTab";
export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', title: LangPackKey) { export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', title: LangPackKey, listenerSetter: ListenerSetter) {
const section = new SettingSection({name: title}); const section = new SettingSection({name: title});
const key = 'settings.autoDownload.' + type + '.'; const key = 'settings.autoDownload.' + type + '.';
@ -17,25 +18,29 @@ export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', ti
text: 'AutodownloadContacts', text: 'AutodownloadContacts',
name: 'contacts', name: 'contacts',
stateKey: key + 'contacts', stateKey: key + 'contacts',
withRipple: true withRipple: true,
listenerSetter
}); });
const privateCheckboxField = new CheckboxField({ const privateCheckboxField = new CheckboxField({
text: 'AutodownloadPrivateChats', text: 'AutodownloadPrivateChats',
name: 'private', name: 'private',
stateKey: key + 'private', stateKey: key + 'private',
withRipple: true withRipple: true,
listenerSetter
}); });
const groupsCheckboxField = new CheckboxField({ const groupsCheckboxField = new CheckboxField({
text: 'AutodownloadGroupChats', text: 'AutodownloadGroupChats',
name: 'groups', name: 'groups',
stateKey: key + 'groups', stateKey: key + 'groups',
withRipple: true withRipple: true,
listenerSetter
}); });
const channelsCheckboxField = new CheckboxField({ const channelsCheckboxField = new CheckboxField({
text: 'AutodownloadChannels', text: 'AutodownloadChannels',
name: 'channels', name: 'channels',
stateKey: key + 'channels', stateKey: key + 'channels',
withRipple: true withRipple: true,
listenerSetter
}); });
section.content.append( section.content.append(
@ -53,7 +58,7 @@ export default class AppAutoDownloadPhotoTab extends SliderSuperTabEventable {
this.header.classList.add('with-border'); this.header.classList.add('with-border');
this.setTitle('AutoDownloadPhotos'); this.setTitle('AutoDownloadPhotos');
const section = autoDownloadPeerTypeSection('photo', 'AutoDownloadPhotosTitle'); const section = autoDownloadPeerTypeSection('photo', 'AutoDownloadPhotosTitle', this.listenerSetter);
this.scrollable.append(section.container); this.scrollable.append(section.container);
} }
} }

2
src/components/sidebarLeft/tabs/autoDownload/video.ts

@ -12,7 +12,7 @@ export default class AppAutoDownloadVideoTab extends SliderSuperTabEventable {
this.header.classList.add('with-border'); this.header.classList.add('with-border');
this.setTitle('AutoDownloadVideos'); this.setTitle('AutoDownloadVideos');
const section = autoDownloadPeerTypeSection('video', 'AutoDownloadVideosTitle'); const section = autoDownloadPeerTypeSection('video', 'AutoDownloadVideosTitle', this.listenerSetter);
this.scrollable.append(section.container); this.scrollable.append(section.container);
} }
} }

15
src/components/sidebarLeft/tabs/dataAndStorage.ts

@ -75,7 +75,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
subtitle: '', subtitle: '',
clickable: () => { clickable: () => {
openTab(AppAutoDownloadPhotoTab); openTab(AppAutoDownloadPhotoTab);
} },
listenerSetter: this.listenerSetter
}); });
const videoRow = new Row({ const videoRow = new Row({
@ -83,7 +84,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
subtitle: '', subtitle: '',
clickable: () => { clickable: () => {
openTab(AppAutoDownloadVideoTab); openTab(AppAutoDownloadVideoTab);
} },
listenerSetter: this.listenerSetter
}); });
const fileRow = new Row({ const fileRow = new Row({
@ -91,7 +93,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
subtitle: '', subtitle: '',
clickable: () => { clickable: () => {
openTab(AppAutoDownloadFileTab); openTab(AppAutoDownloadFileTab);
} },
listenerSetter: this.listenerSetter
}); });
const resetButton = Button('btn-primary btn-transparent primary', {icon: 'delete', text: 'ResetAutomaticMediaDownload'}); const resetButton = Button('btn-primary btn-transparent primary', {icon: 'delete', text: 'ResetAutomaticMediaDownload'});
@ -154,13 +157,15 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
text: 'AutoplayGIF', text: 'AutoplayGIF',
name: 'gifs', name: 'gifs',
stateKey: 'settings.autoPlay.gifs', stateKey: 'settings.autoPlay.gifs',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
const videosCheckboxField = new CheckboxField({ const videosCheckboxField = new CheckboxField({
text: 'AutoplayVideo', text: 'AutoplayVideo',
name: 'videos', name: 'videos',
stateKey: 'settings.autoPlay.videos', stateKey: 'settings.autoPlay.videos',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
section.content.append(gifsCheckboxField.label, videosCheckboxField.label); section.content.append(gifsCheckboxField.label, videosCheckboxField.label);

19
src/components/sidebarLeft/tabs/editFolder.ts

@ -24,6 +24,7 @@ import deepEqual from "../../../helpers/object/deepEqual";
import documentFragmentToHTML from "../../../helpers/dom/documentFragmentToHTML"; import documentFragmentToHTML from "../../../helpers/dom/documentFragmentToHTML";
import wrapDraftText from "../../../lib/richTextProcessor/wrapDraftText"; import wrapDraftText from "../../../lib/richTextProcessor/wrapDraftText";
import filterAsync from "../../../helpers/array/filterAsync"; import filterAsync from "../../../helpers/array/filterAsync";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
const MAX_FOLDER_NAME_LENGTH = 12; const MAX_FOLDER_NAME_LENGTH = 12;
@ -79,7 +80,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
}).show(); }).show();
} }
}; };
this.menuBtn = ButtonMenuToggle({}, 'bottom-left', [deleteFolderButton]); this.menuBtn = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', [deleteFolderButton]);
this.menuBtn.classList.add('hide'); this.menuBtn.classList.add('hide');
this.header.append(this.confirmBtn, this.menuBtn); this.header.append(this.confirmBtn, this.menuBtn);
@ -174,15 +175,15 @@ export default class AppEditFolderTab extends SliderSuperTab {
const includedFlagsContainer = this.includePeerIds.container.querySelector('.folder-categories'); const includedFlagsContainer = this.includePeerIds.container.querySelector('.folder-categories');
const excludedFlagsContainer = this.excludePeerIds.container.querySelector('.folder-categories'); const excludedFlagsContainer = this.excludePeerIds.container.querySelector('.folder-categories');
includedFlagsContainer.querySelector('.btn').addEventListener('click', () => { attachClickEvent(includedFlagsContainer.querySelector('.btn') as HTMLElement, () => {
this.slider.createTab(AppIncludedChatsTab).open(this.filter, 'included', this); this.slider.createTab(AppIncludedChatsTab).open(this.filter, 'included', this);
}); }, {listenerSetter: this.listenerSetter});
excludedFlagsContainer.querySelector('.btn').addEventListener('click', () => { attachClickEvent(excludedFlagsContainer.querySelector('.btn') as HTMLElement, () => {
this.slider.createTab(AppIncludedChatsTab).open(this.filter, 'excluded', this); this.slider.createTab(AppIncludedChatsTab).open(this.filter, 'excluded', this);
}); }, {listenerSetter: this.listenerSetter});
this.confirmBtn.addEventListener('click', () => { attachClickEvent(this.confirmBtn, () => {
if(this.nameInputField.input.classList.contains('error')) { if(this.nameInputField.input.classList.contains('error')) {
return; return;
} }
@ -222,9 +223,9 @@ export default class AppEditFolderTab extends SliderSuperTab {
}).finally(() => { }).finally(() => {
this.confirmBtn.removeAttribute('disabled'); this.confirmBtn.removeAttribute('disabled');
}); });
}); }, {listenerSetter: this.listenerSetter});
this.nameInputField.input.addEventListener('input', () => { this.listenerSetter.add(this.nameInputField.input)('input', () => {
this.filter.title = this.nameInputField.value; this.filter.title = this.nameInputField.value;
this.editCheckForChange(); this.editCheckForChange();
}); });
@ -336,7 +337,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
const content = section.generateContentElement(); const content = section.generateContentElement();
showMore = Button('folder-category-button btn btn-primary btn-transparent', {icon: 'down'}); showMore = Button('folder-category-button btn btn-primary btn-transparent', {icon: 'down'});
showMore.classList.add('load-more', 'rp-overflow'); showMore.classList.add('load-more', 'rp-overflow');
showMore.addEventListener('click', () => renderMore(20)); attachClickEvent(showMore, () => renderMore(20), {listenerSetter: this.listenerSetter});
showMore.append(i18n('FilterShowMoreChats', [peers.length])); showMore.append(i18n('FilterShowMoreChats', [peers.length]));
content.append(showMore); content.append(showMore);

21
src/components/sidebarLeft/tabs/generalSettings.ts

@ -111,7 +111,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
text: 'EnableAnimations', text: 'EnableAnimations',
name: 'animations', name: 'animations',
stateKey: 'settings.animationsEnabled', stateKey: 'settings.animationsEnabled',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
container.append(range.container, chatBackgroundButton, animationsCheckboxField.label); container.append(range.container, chatBackgroundButton, animationsCheckboxField.label);
@ -231,13 +232,15 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
text: 'GeneralSettings.EmojiPrediction', text: 'GeneralSettings.EmojiPrediction',
name: 'suggest-emoji', name: 'suggest-emoji',
stateKey: 'settings.emoji.suggest', stateKey: 'settings.emoji.suggest',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
const bigCheckboxField = new CheckboxField({ const bigCheckboxField = new CheckboxField({
text: 'GeneralSettings.BigEmoji', text: 'GeneralSettings.BigEmoji',
name: 'emoji-big', name: 'emoji-big',
stateKey: 'settings.emoji.big', stateKey: 'settings.emoji.big',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
container.append(suggestCheckboxField.label, bigCheckboxField.label); container.append(suggestCheckboxField.label, bigCheckboxField.label);
@ -251,7 +254,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
havePadding: true, havePadding: true,
clickable: () => { clickable: () => {
this.slider.createTab(AppQuickReactionTab).open(); this.slider.createTab(AppQuickReactionTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const renderQuickReaction = () => { const renderQuickReaction = () => {
@ -272,13 +276,15 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
text: 'Stickers.SuggestStickers', text: 'Stickers.SuggestStickers',
name: 'suggest', name: 'suggest',
stateKey: 'settings.stickers.suggest', stateKey: 'settings.stickers.suggest',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
const loopCheckboxField = new CheckboxField({ const loopCheckboxField = new CheckboxField({
text: 'InstalledStickers.LoopAnimated', text: 'InstalledStickers.LoopAnimated',
name: 'loop', name: 'loop',
stateKey: 'settings.stickers.loop', stateKey: 'settings.stickers.loop',
withRipple: true withRipple: true,
listenerSetter: this.listenerSetter
}); });
const stickerSets: {[id: string]: Row} = {}; const stickerSets: {[id: string]: Row} = {};
@ -294,7 +300,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
havePadding: true, havePadding: true,
clickable: () => { clickable: () => {
new PopupStickers({id: stickerSet.id, access_hash: stickerSet.access_hash}).show(); new PopupStickers({id: stickerSet.id, access_hash: stickerSet.access_hash}).show();
} },
listenerSetter: this.listenerSetter
}); });
stickerSets[stickerSet.id] = row; stickerSets[stickerSet.id] = row;

5
src/components/sidebarLeft/tabs/includedChats.ts

@ -19,6 +19,7 @@ import copy from "../../../helpers/object/copy";
import forEachReverse from "../../../helpers/array/forEachReverse"; import forEachReverse from "../../../helpers/array/forEachReverse";
import setInnerHTML from "../../../helpers/dom/setInnerHTML"; import setInnerHTML from "../../../helpers/dom/setInnerHTML";
import wrapEmojiText from "../../../lib/richTextProcessor/wrapEmojiText"; import wrapEmojiText from "../../../lib/richTextProcessor/wrapEmojiText";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
export default class AppIncludedChatsTab extends SliderSuperTab { export default class AppIncludedChatsTab extends SliderSuperTab {
private editFolderTab: AppEditFolderTab; private editFolderTab: AppEditFolderTab;
@ -39,7 +40,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
this.header.append(this.confirmBtn); this.header.append(this.confirmBtn);
this.confirmBtn.addEventListener('click', async() => { attachClickEvent(this.confirmBtn, async() => {
const selected = this.selector.getSelected(); const selected = this.selector.getSelected();
//this.filter.pFlags = {}; //this.filter.pFlags = {};
@ -103,7 +104,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
this.editFolderTab.setFilter(this.filter, false); this.editFolderTab.setFilter(this.filter, false);
this.close(); this.close();
}); }, {listenerSetter: this.listenerSetter});
this.dialogsByFilters = new Map(); this.dialogsByFilters = new Map();
return this.managers.filtersStorage.getDialogFilters().then(async(filters) => { return this.managers.filtersStorage.getDialogFilters().then(async(filters) => {

5
src/components/sidebarLeft/tabs/newChannel.ts

@ -13,6 +13,7 @@ import AppAddMembersTab from "./addMembers";
import { _i18n } from "../../../lib/langPack"; import { _i18n } from "../../../lib/langPack";
import ButtonCorner from "../../buttonCorner"; import ButtonCorner from "../../buttonCorner";
import appImManager from "../../../lib/appManagers/appImManager"; import appImManager from "../../../lib/appManagers/appImManager";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
export default class AppNewChannelTab extends SliderSuperTab { export default class AppNewChannelTab extends SliderSuperTab {
private uploadAvatar: () => Promise<InputFile> = null; private uploadAvatar: () => Promise<InputFile> = null;
@ -60,7 +61,7 @@ export default class AppNewChannelTab extends SliderSuperTab {
this.nextBtn = ButtonCorner({icon: 'arrow_next'}); this.nextBtn = ButtonCorner({icon: 'arrow_next'});
this.nextBtn.addEventListener('click', () => { attachClickEvent(this.nextBtn, () => {
const title = this.channelNameInputField.value; const title = this.channelNameInputField.value;
const about = this.channelDescriptionInputField.value; const about = this.channelDescriptionInputField.value;
@ -89,7 +90,7 @@ export default class AppNewChannelTab extends SliderSuperTab {
} }
}); });
}); });
}); }, {listenerSetter: this.listenerSetter});
this.content.append(this.nextBtn); this.content.append(this.nextBtn);
section.content.append(this.avatarEdit.container, inputWrapper); section.content.append(this.avatarEdit.container, inputWrapper);

7
src/components/sidebarLeft/tabs/newGroup.ts

@ -14,6 +14,7 @@ import I18n from "../../../lib/langPack";
import ButtonCorner from "../../buttonCorner"; import ButtonCorner from "../../buttonCorner";
import getUserStatusString from "../../wrappers/getUserStatusString"; import getUserStatusString from "../../wrappers/getUserStatusString";
import appImManager from "../../../lib/appManagers/appImManager"; import appImManager from "../../../lib/appManagers/appImManager";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
interface OpenStreetMapInterface { interface OpenStreetMapInterface {
place_id?: number; place_id?: number;
@ -68,7 +69,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
this.groupLocationInputField.container this.groupLocationInputField.container
); );
this.groupNameInputField.input.addEventListener('input', () => { this.listenerSetter.add(this.groupNameInputField.input)('input', () => {
const value = this.groupNameInputField.value; const value = this.groupNameInputField.value;
let valueCheck = !!value.length && !this.groupNameInputField.input.classList.contains('error'); let valueCheck = !!value.length && !this.groupNameInputField.input.classList.contains('error');
if(this.isGeoChat) valueCheck = valueCheck && !!this.userLocationCoords && !!this.userLocationAddress; if(this.isGeoChat) valueCheck = valueCheck && !!this.userLocationCoords && !!this.userLocationAddress;
@ -77,7 +78,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
this.nextBtn = ButtonCorner({icon: 'arrow_next'}); this.nextBtn = ButtonCorner({icon: 'arrow_next'});
this.nextBtn.addEventListener('click', () => { attachClickEvent(this.nextBtn, () => {
const title = this.groupNameInputField.value; const title = this.groupNameInputField.value;
let promise: Promise<ChatId>; let promise: Promise<ChatId>;
@ -128,7 +129,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
appImManager.setInnerPeer({peerId: chatId.toPeerId(true)}); appImManager.setInnerPeer({peerId: chatId.toPeerId(true)});
}); });
}); }, {listenerSetter: this.listenerSetter});
const chatsSection = new SettingSection({ const chatsSection = new SettingSection({
name: 'Members', name: 'Members',

6
src/components/sidebarLeft/tabs/notifications.ts

@ -36,11 +36,13 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
const enabledRow = new Row({ const enabledRow = new Row({
checkboxField: new CheckboxField({text: options.typeText, checked: true}), checkboxField: new CheckboxField({text: options.typeText, checked: true}),
subtitleLangKey: 'Loading', subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
}); });
const previewEnabledRow = new Row({ const previewEnabledRow = new Row({
checkboxField: new CheckboxField({text: 'MessagePreview', checked: true}), checkboxField: new CheckboxField({text: 'MessagePreview', checked: true}),
subtitleLangKey: 'Loading', subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
}); });
section.content.append(enabledRow.container, previewEnabledRow.container); section.content.append(enabledRow.container, previewEnabledRow.container);
@ -112,11 +114,13 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
const contactsSignUpRow = new Row({ const contactsSignUpRow = new Row({
checkboxField: new CheckboxField({text: 'ContactJoined', checked: true}), checkboxField: new CheckboxField({text: 'ContactJoined', checked: true}),
subtitleLangKey: 'Loading', subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
}); });
const soundRow = new Row({ const soundRow = new Row({
checkboxField: new CheckboxField({text: 'Notifications.Sound', checked: true, stateKey: 'settings.notifications.sound'}), checkboxField: new CheckboxField({text: 'Notifications.Sound', checked: true, stateKey: 'settings.notifications.sound', listenerSetter: this.listenerSetter}),
subtitleLangKey: 'Loading', subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
}); });
apiManagerProxy.getState().then((state) => { apiManagerProxy.getState().then((state) => {

27
src/components/sidebarLeft/tabs/privacyAndSecurity.ts

@ -56,7 +56,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
const tab = this.slider.createTab(AppBlockedUsersTab); const tab = this.slider.createTab(AppBlockedUsersTab);
tab.peerIds = blockedPeerIds; tab.peerIds = blockedPeerIds;
tab.open(); tab.open();
} },
listenerSetter: this.listenerSetter
}); });
blockedUsersRow.freezed = true; blockedUsersRow.freezed = true;
@ -81,7 +82,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
tab.state = passwordState; tab.state = passwordState;
tab.open(); tab.open();
} },
listenerSetter: this.listenerSetter
}; };
const twoFactorRow = new Row(twoFactorRowOptions); const twoFactorRow = new Row(twoFactorRowOptions);
@ -98,7 +100,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
this.updateActiveSessions(); this.updateActiveSessions();
}, {once: true}); }, {once: true});
tab.open(); tab.open();
} },
listenerSetter: this.listenerSetter
}); });
activeSessionsRow.freezed = true; activeSessionsRow.freezed = true;
@ -157,7 +160,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
this.slider.createTab(AppPrivacyPhoneNumberTab).open(); this.slider.createTab(AppPrivacyPhoneNumberTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const lastSeenTimeRow = rowsByKeys['inputPrivacyKeyStatusTimestamp'] = new Row({ const lastSeenTimeRow = rowsByKeys['inputPrivacyKeyStatusTimestamp'] = new Row({
@ -165,7 +169,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
this.slider.createTab(AppPrivacyLastSeenTab).open(); this.slider.createTab(AppPrivacyLastSeenTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const photoVisibilityRow = rowsByKeys['inputPrivacyKeyProfilePhoto'] = new Row({ const photoVisibilityRow = rowsByKeys['inputPrivacyKeyProfilePhoto'] = new Row({
@ -173,7 +178,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
this.slider.createTab(AppPrivacyProfilePhotoTab).open(); this.slider.createTab(AppPrivacyProfilePhotoTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const callRow = rowsByKeys['inputPrivacyKeyPhoneCall'] = new Row({ const callRow = rowsByKeys['inputPrivacyKeyPhoneCall'] = new Row({
@ -181,7 +187,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
this.slider.createTab(AppPrivacyCallsTab).open(); this.slider.createTab(AppPrivacyCallsTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const linkAccountRow = rowsByKeys['inputPrivacyKeyForwards'] = new Row({ const linkAccountRow = rowsByKeys['inputPrivacyKeyForwards'] = new Row({
@ -189,7 +196,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
this.slider.createTab(AppPrivacyForwardMessagesTab).open(); this.slider.createTab(AppPrivacyForwardMessagesTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const groupChatsAddRow = rowsByKeys['inputPrivacyKeyChatInvite'] = new Row({ const groupChatsAddRow = rowsByKeys['inputPrivacyKeyChatInvite'] = new Row({
@ -197,7 +205,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE, subtitleLangKey: SUBTITLE,
clickable: () => { clickable: () => {
this.slider.createTab(AppPrivacyAddToGroupsTab).open(); this.slider.createTab(AppPrivacyAddToGroupsTab).open();
} },
listenerSetter: this.listenerSetter
}); });
const updatePrivacyRow = (key: InputPrivacyKey['_']) => { const updatePrivacyRow = (key: InputPrivacyKey['_']) => {

25
src/components/sidebarLeft/tabs/settings.ts

@ -27,6 +27,7 @@ import { SliderSuperTabConstructable } from "../../sliderTab";
import PopupAvatar from "../../popups/avatar"; import PopupAvatar from "../../popups/avatar";
import { AccountAuthorizations, Authorization } from "../../../layer"; import { AccountAuthorizations, Authorization } from "../../../layer";
import PopupElement from "../../popups"; import PopupElement from "../../popups";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
//import AppMediaViewer from "../../appMediaViewerNew"; //import AppMediaViewer from "../../appMediaViewerNew";
export default class AppSettingsTab extends SliderSuperTab { export default class AppSettingsTab extends SliderSuperTab {
@ -50,7 +51,7 @@ export default class AppSettingsTab extends SliderSuperTab {
this.container.classList.add('settings-container'); this.container.classList.add('settings-container');
this.setTitle('Settings'); this.setTitle('Settings');
const btnMenu = ButtonMenuToggle({}, 'bottom-left', [{ const btnMenu = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', [{
icon: 'logout', icon: 'logout',
text: 'EditAccount.Logout', text: 'EditAccount.Logout',
onClick: () => { onClick: () => {
@ -78,14 +79,14 @@ export default class AppSettingsTab extends SliderSuperTab {
const fillPromise = this.profile.fillProfileElements(); const fillPromise = this.profile.fillProfileElements();
const changeAvatarBtn = Button('btn-circle btn-corner z-depth-1 profile-change-avatar', {icon: 'cameraadd'}); const changeAvatarBtn = Button('btn-circle btn-corner z-depth-1 profile-change-avatar', {icon: 'cameraadd'});
changeAvatarBtn.addEventListener('click', () => { attachClickEvent(changeAvatarBtn, () => {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
PopupElement.createPopup(PopupAvatar).open(canvas, (upload) => { PopupElement.createPopup(PopupAvatar).open(canvas, (upload) => {
upload().then((inputFile) => { upload().then((inputFile) => {
return this.managers.appProfileManager.uploadProfilePhoto(inputFile); return this.managers.appProfileManager.uploadProfilePhoto(inputFile);
}); });
}); });
}); }, {listenerSetter: this.listenerSetter});
this.profile.element.lastElementChild.firstElementChild.append(changeAvatarBtn); this.profile.element.lastElementChild.firstElementChild.append(changeAvatarBtn);
const updateChangeAvatarBtn = async() => { const updateChangeAvatarBtn = async() => {
@ -160,7 +161,8 @@ export default class AppSettingsTab extends SliderSuperTab {
clickable: () => { clickable: () => {
this.slider.createTab(tabConstructor).open(); this.slider.createTab(tabConstructor).open();
// new tabConstructor(this.slider, true).open(); // new tabConstructor(this.slider, true).open();
} },
listenerSetter: this.listenerSetter
}); });
}); });
@ -181,7 +183,8 @@ export default class AppSettingsTab extends SliderSuperTab {
this.updateActiveSessions(true); this.updateActiveSessions(true);
}, {once: true}); }, {once: true});
tab.open(); tab.open();
} },
listenerSetter: this.listenerSetter
}), }),
this.languageRow = new Row({ this.languageRow = new Row({
@ -190,7 +193,8 @@ export default class AppSettingsTab extends SliderSuperTab {
icon: 'language', icon: 'language',
clickable: () => { clickable: () => {
this.slider.createTab(AppLanguageTab).open(); this.slider.createTab(AppLanguageTab).open();
} },
listenerSetter: this.listenerSetter
}) })
); );
@ -204,10 +208,10 @@ export default class AppSettingsTab extends SliderSuperTab {
this.scrollable.append(this.profile.element/* profileSection.container */, buttonsSection.container); this.scrollable.append(this.profile.element/* profileSection.container */, buttonsSection.container);
this.buttons.edit.addEventListener('click', () => { attachClickEvent(this.buttons.edit, () => {
const tab = this.slider.createTab(AppEditProfileTab); const tab = this.slider.createTab(AppEditProfileTab);
tab.open(); tab.open();
}); }, {listenerSetter: this.listenerSetter});
lottieLoader.loadLottieWorkers(); lottieLoader.loadLottieWorkers();
@ -235,4 +239,9 @@ export default class AppSettingsTab extends SliderSuperTab {
this.devicesRow.titleRight.textContent = '' + this.authorizations.length; this.devicesRow.titleRight.textContent = '' + this.authorizations.length;
}); });
} }
public onCloseAfterTimeout() {
this.profile.destroy();
return super.onCloseAfterTimeout();
}
} }

6
src/components/sidebarRight/index.ts

@ -46,15 +46,19 @@ export class AppSidebarRight extends SidebarSlider {
return tab; return tab;
} }
public replaceSharedMediaTab(tab: AppSharedMediaTab) { public replaceSharedMediaTab(tab?: AppSharedMediaTab) {
let previousTab = this.sharedMediaTab; let previousTab = this.sharedMediaTab;
if(previousTab) { if(previousTab) {
if(tab) {
const wasActive = previousTab.container.classList.contains('active'); const wasActive = previousTab.container.classList.contains('active');
if(wasActive) { if(wasActive) {
tab.container.classList.add('active'); tab.container.classList.add('active');
} }
previousTab.container.replaceWith(tab.container); previousTab.container.replaceWith(tab.container);
} else {
previousTab.container.remove();
}
} else { } else {
this.tabsContainer.prepend(tab.container); this.tabsContainer.prepend(tab.container);
} }

6
src/components/sidebarRight/tabs/chatReactions.ts

@ -29,7 +29,8 @@ export default class AppChatReactionsTab extends SliderSuperTabEventable {
const toggleCheckboxField = new CheckboxField({toggle: true, checked: !!enabledReactions.size}); const toggleCheckboxField = new CheckboxField({toggle: true, checked: !!enabledReactions.size});
const toggleRow = new Row({ const toggleRow = new Row({
checkboxField: toggleCheckboxField, checkboxField: toggleCheckboxField,
titleLangKey: 'EnableReactions' titleLangKey: 'EnableReactions',
listenerSetter: this.listenerSetter
}); });
toggleSection.content.append(toggleRow.container); toggleSection.content.append(toggleRow.container);
@ -65,7 +66,8 @@ export default class AppChatReactionsTab extends SliderSuperTabEventable {
const row = new Row({ const row = new Row({
checkboxField, checkboxField,
title: availableReaction.title, title: availableReaction.title,
havePadding: true havePadding: true,
listenerSetter: this.listenerSetter
}); });
wrapStickerToRow({ wrapStickerToRow({

3
src/components/sidebarRight/tabs/chatType.ts

@ -78,7 +78,8 @@ export default class AppChatTypeTab extends SliderSuperTabEventable {
clickable: () => { clickable: () => {
copyTextToClipboard((this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link); copyTextToClipboard((this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link);
toast(I18n.format('LinkCopied', true)); toast(I18n.format('LinkCopied', true));
} },
listenerSetter: this.listenerSetter
}); });
const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'RevokeLink'}); const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'RevokeLink'});

7
src/components/sidebarRight/tabs/editChat.ts

@ -113,7 +113,8 @@ export default class AppEditChatTab extends SliderSuperTab {
this.listenerSetter.add(tab.eventListener)('destroy', setChatTypeSubtitle); this.listenerSetter.add(tab.eventListener)('destroy', setChatTypeSubtitle);
}, },
icon: 'lock' icon: 'lock',
listenerSetter: this.listenerSetter
}); });
const setChatTypeSubtitle = () => { const setChatTypeSubtitle = () => {
@ -147,7 +148,8 @@ export default class AppEditChatTab extends SliderSuperTab {
this.listenerSetter.add(tab.eventListener)('destroy', setReactionsLength); this.listenerSetter.add(tab.eventListener)('destroy', setReactionsLength);
}); });
} },
listenerSetter: this.listenerSetter
}); });
const availableReactions = await this.managers.appReactionsManager.getAvailableReactions(); const availableReactions = await this.managers.appReactionsManager.getAvailableReactions();
@ -182,6 +184,7 @@ export default class AppEditChatTab extends SliderSuperTab {
tab.open(); tab.open();
}, },
icon: 'permissions', icon: 'permissions',
listenerSetter: this.listenerSetter
}); });
const setPermissionsLength = async() => { const setPermissionsLength = async() => {

3
src/components/sidebarRight/tabs/editContact.ts

@ -117,7 +117,8 @@ export default class AppEditContactTab extends SliderSuperTab {
if(!isNew) { if(!isNew) {
const notificationsRow = new Row({ const notificationsRow = new Row({
checkboxField: notificationsCheckboxField checkboxField: notificationsCheckboxField,
listenerSetter: this.listenerSetter
}); });
const enabled = !(await this.managers.appNotificationsManager.isPeerLocalMuted(this.peerId, false)); const enabled = !(await this.managers.appNotificationsManager.isPeerLocalMuted(this.peerId, false));

3
src/components/sidebarRight/tabs/groupPermissions.ts

@ -185,7 +185,8 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
placeholder: 'ExceptionModal.Search.Placeholder', placeholder: 'ExceptionModal.Search.Placeholder',
peerId: -this.chatId, peerId: -this.chatId,
}); });
} },
listenerSetter: this.listenerSetter
}); });
const openPermissions = async(peerId: PeerId) => { const openPermissions = async(peerId: PeerId) => {

11
src/components/sidebarRight/tabs/sharedMedia.ts

@ -85,7 +85,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
// * body // * body
this.profile = new PeerProfile(this.managers, this.scrollable); this.profile = new PeerProfile(this.managers, this.scrollable, this.listenerSetter);
this.profile.init(); this.profile.init();
this.scrollable.append(this.profile.element); this.scrollable.append(this.profile.element);
@ -124,7 +124,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
} else if(!this.scrollable.isHeavyAnimationInProgress) { } else if(!this.scrollable.isHeavyAnimationInProgress) {
this.slider.onCloseBtnClick(); this.slider.onCloseBtnClick();
} }
}); }, {listenerSetter: this.listenerSetter});
attachClickEvent(this.editBtn, (e) => { attachClickEvent(this.editBtn, (e) => {
let tab: AppEditChatTab | AppEditContactTab; let tab: AppEditChatTab | AppEditContactTab;
@ -143,7 +143,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
tab.open(); tab.open();
} }
}); }, {listenerSetter: this.listenerSetter});
this.listenerSetter.add(rootScope)('contacts_update', (userId) => { this.listenerSetter.add(rootScope)('contacts_update', (userId) => {
if(this.peerId === userId) { if(this.peerId === userId) {
@ -217,7 +217,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
const btnAddMembers = ButtonCorner({icon: 'addmember_filled'}); const btnAddMembers = ButtonCorner({icon: 'addmember_filled'});
this.content.append(btnAddMembers); this.content.append(btnAddMembers);
btnAddMembers.addEventListener('click', async() => { attachClickEvent(btnAddMembers, async() => {
const peerId = this.peerId; const peerId = this.peerId;
const id = this.peerId.toChatId(); const id = this.peerId.toChatId();
const isChannel = await this.managers.appChatsManager.isChannel(id); const isChannel = await this.managers.appChatsManager.isChannel(id);
@ -315,7 +315,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
}, },
}); });
} }
}); }, {listenerSetter: this.listenerSetter});
//console.log('construct shared media time:', performance.now() - perf); //console.log('construct shared media time:', performance.now() - perf);
} }
@ -458,5 +458,6 @@ export default class AppSharedMediaTab extends SliderSuperTab {
this.destroyable = true; this.destroyable = true;
this.onCloseAfterTimeout(); this.onCloseAfterTimeout();
this.profile.destroy(); this.profile.destroy();
this.searchSuper.destroy();
} }
} }

3
src/components/singleTransition.ts

@ -19,6 +19,9 @@ const SetTransition = (
clearTimeout(+timeout); clearTimeout(+timeout);
} }
// useRafs = undefined;
// duration = 0;
if(raf !== undefined) { if(raf !== undefined) {
window.cancelAnimationFrame(+raf); window.cancelAnimationFrame(+raf);
if(!useRafs) { if(!useRafs) {

2
src/components/slider.ts

@ -152,7 +152,7 @@ export default class SidebarSlider {
if(tab.onCloseAfterTimeout) { if(tab.onCloseAfterTimeout) {
setTimeout(() => { setTimeout(() => {
tab.onCloseAfterTimeout(); tab.onCloseAfterTimeout();
}, TRANSITION_TIME); }, TRANSITION_TIME + 30);
} }
} }
} }

1
src/components/sliderTab.ts

@ -107,6 +107,7 @@ export default class SliderSuperTab implements SliderTab {
if(this.destroyable) { // ! WARNING, пока что это будет работать только с самой последней внутренней вкладкой ! if(this.destroyable) { // ! WARNING, пока что это будет работать только с самой последней внутренней вкладкой !
this.slider.tabs.delete(this); this.slider.tabs.delete(this);
this.container.remove(); this.container.remove();
this.scrollable.destroy();
} }
if(this.listenerSetter) { if(this.listenerSetter) {

28
src/components/transition.ts

@ -9,6 +9,7 @@ import deferredPromise, { CancellablePromise } from "../helpers/cancellablePromi
import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck"; import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck";
import whichChild from "../helpers/dom/whichChild"; import whichChild from "../helpers/dom/whichChild";
import cancelEvent from "../helpers/dom/cancelEvent"; import cancelEvent from "../helpers/dom/cancelEvent";
import ListenerSetter from "../helpers/listenerSetter";
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) { function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const width = prevTabContent.getBoundingClientRect().width; const width = prevTabContent.getBoundingClientRect().width;
@ -84,7 +85,8 @@ export const TransitionSlider = (
type: 'tabs' | 'navigation' | 'zoom-fade' | 'slide-fade' | 'none'/* | 'counter' */, type: 'tabs' | 'navigation' | 'zoom-fade' | 'slide-fade' | 'none'/* | 'counter' */,
transitionTime: number, transitionTime: number,
onTransitionEnd?: (id: number) => void, onTransitionEnd?: (id: number) => void,
isHeavy = true isHeavy = true,
listenerSetter?: ListenerSetter
) => { ) => {
let animationFunction: TransitionFunction = null; let animationFunction: TransitionFunction = null;
@ -101,7 +103,7 @@ export const TransitionSlider = (
content.dataset.animation = type; content.dataset.animation = type;
return Transition(content, animationFunction, transitionTime, onTransitionEnd, isHeavy); return Transition(content, animationFunction, transitionTime, onTransitionEnd, isHeavy, undefined, undefined, listenerSetter);
}; };
type TransitionFunction = (tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) => void | (() => void); type TransitionFunction = (tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) => void | (() => void);
@ -113,7 +115,8 @@ const Transition = (
onTransitionEnd?: (id: number) => void, onTransitionEnd?: (id: number) => void,
isHeavy = true, isHeavy = true,
once = false, once = false,
withAnimationListener = true withAnimationListener = true,
listenerSetter?: ListenerSetter
) => { ) => {
const onTransitionEndCallbacks: Map<HTMLElement, Function> = new Map(); const onTransitionEndCallbacks: Map<HTMLElement, Function> = new Map();
let animationDeferred: CancellablePromise<void>; let animationDeferred: CancellablePromise<void>;
@ -133,7 +136,7 @@ const Transition = (
//console.log('Transition: transitionend', /* content, */ e, selectTab.prevId, performance.now() - animationStarted); //console.log('Transition: transitionend', /* content, */ e, selectTab.prevId, performance.now() - animationStarted);
const callback = onTransitionEndCallbacks.get(e.target as HTMLElement); const callback = onTransitionEndCallbacks.get(e.target as HTMLElement);
if(callback) callback(); callback?.();
if(e.target !== from) { if(e.target !== from) {
return; return;
@ -153,14 +156,16 @@ const Transition = (
content.classList.remove('animating', 'backwards', 'disable-hover'); content.classList.remove('animating', 'backwards', 'disable-hover');
if(once) { if(once) {
content.removeEventListener(listenerName, onEndEvent/* , {capture: false} */); if(listenerSetter) listenerSetter.removeManual(content, listenerName, onEndEvent);
else content.removeEventListener(listenerName, onEndEvent/* , {capture: false} */);
from = animationDeferred = undefined; from = animationDeferred = undefined;
onTransitionEndCallbacks.clear(); onTransitionEndCallbacks.clear();
} }
}; };
// TODO: check for transition type (transform, etc) using by animationFunction // TODO: check for transition type (transform, etc) using by animationFunction
content.addEventListener(listenerName, onEndEvent/* , {passive: true, capture: false} */); if(listenerSetter) listenerSetter.add(content)(listenerName, onEndEvent);
else content.addEventListener(listenerName, onEndEvent/* , {passive: true, capture: false} */);
} }
function selectTab(id: number | HTMLElement, animate = true, overrideFrom?: typeof from) { function selectTab(id: number | HTMLElement, animate = true, overrideFrom?: typeof from) {
@ -196,9 +201,7 @@ const Transition = (
if(from) from.classList.remove('active', 'to', 'from'); if(from) from.classList.remove('active', 'to', 'from');
else if(to) { // fix instant opening back from closed slider (e.g. instant closening and opening right sidebar) else if(to) { // fix instant opening back from closed slider (e.g. instant closening and opening right sidebar)
const callback = onTransitionEndCallbacks.get(to); const callback = onTransitionEndCallbacks.get(to);
if(callback) { callback?.();
callback();
}
} }
if(to) { if(to) {
@ -254,21 +257,24 @@ const Transition = (
} }
if(from/* && false */) { if(from/* && false */) {
let timeout: number;
const _from = from; const _from = from;
const callback = () => { const callback = () => {
clearTimeout(timeout);
_from.classList.remove('active', 'from'); _from.classList.remove('active', 'from');
if(onTransitionEndCallback) { if(onTransitionEndCallback) {
onTransitionEndCallback(); onTransitionEndCallback?.();
} }
onTransitionEndCallbacks.delete(_from); onTransitionEndCallbacks.delete(_from);
}; };
if(to) { if(to) {
timeout = window.setTimeout(callback, transitionTime + 100); // something happened to container
onTransitionEndCallbacks.set(_from, callback); onTransitionEndCallbacks.set(_from, callback);
} else { } else {
const timeout = window.setTimeout(callback, transitionTime); timeout = window.setTimeout(callback, transitionTime);
onTransitionEndCallbacks.set(_from, () => { onTransitionEndCallbacks.set(_from, () => {
clearTimeout(timeout); clearTimeout(timeout);
onTransitionEndCallbacks.delete(_from); onTransitionEndCallbacks.delete(_from);

13
src/components/wrappers/video.ts

@ -15,7 +15,8 @@ import isInDOM from "../../helpers/dom/isInDOM";
import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl"; import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl";
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes"; import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
import onMediaLoad from "../../helpers/onMediaLoad"; import onMediaLoad from "../../helpers/onMediaLoad";
import throttleWithRaf from "../../helpers/schedulers/throttleWithRaf"; import { fastRaf } from "../../helpers/schedulers";
import throttle from "../../helpers/schedulers/throttle";
import sequentialDom from "../../helpers/sequentialDom"; import sequentialDom from "../../helpers/sequentialDom";
import toHHMMSS from "../../helpers/string/toHHMMSS"; import toHHMMSS from "../../helpers/string/toHHMMSS";
import { Message, PhotoSize } from "../../layer"; import { Message, PhotoSize } from "../../layer";
@ -245,7 +246,9 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
spanTime.innerText = toHHMMSS(globalVideo.duration - globalVideo.currentTime, false); spanTime.innerText = toHHMMSS(globalVideo.duration - globalVideo.currentTime, false);
}; };
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate); const throttledTimeUpdate = throttle(() => {
fastRaf(onTimeUpdate);
}, 1000, false);
const onPlay = () => { const onPlay = () => {
video.classList.add('hide'); video.classList.add('hide');
@ -437,14 +440,16 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
if(doc.type === 'video') { if(doc.type === 'video') {
const onTimeUpdate = () => { const onTimeUpdate = () => {
if(!video.videoWidth) { if(!video.duration) {
return; return;
} }
spanTime.innerText = toHHMMSS(video.duration - video.currentTime, false); spanTime.innerText = toHHMMSS(video.duration - video.currentTime, false);
}; };
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate); const throttledTimeUpdate = throttle(() => {
fastRaf(onTimeUpdate);
}, 1e3, false);
video.addEventListener('timeupdate', throttledTimeUpdate); video.addEventListener('timeupdate', throttledTimeUpdate);

2
src/helpers/canvas/getTextWidth.ts

@ -16,7 +16,7 @@ export default function getTextWidth(text: string, font: string) {
// const perf = performance.now(); // const perf = performance.now();
if(!context) { if(!context) {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
context = canvas.getContext('2d'); context = canvas.getContext('2d', {alpha: false});
context.font = font; context.font = font;
} }

7
src/helpers/dom/renderImageWithFadeIn.ts

@ -38,12 +38,11 @@ export default function renderImageWithFadeIn(
image.addEventListener('animationend', () => { image.addEventListener('animationend', () => {
sequentialDom.mutate(() => { sequentialDom.mutate(() => {
image.classList.remove('fade-in'); image.classList.remove('fade-in');
thumbImage?.remove();
if(thumbImage) {
thumbImage.remove();
}
}); });
}, {once: true}); }, {once: true});
} else {
thumbImage?.remove();
} }
}); });
}); });

2
src/lib/appManagers/appDialogsManager.ts

@ -1516,7 +1516,7 @@ export class AppDialogsManager {
}, {capture: true}); }, {capture: true});
// cancel link click // cancel link click
list.addEventListener('click', (e) => { attachClickEvent(list, (e) => {
if(e.button === 0) { if(e.button === 0) {
cancelEvent(e); cancelEvent(e);
} }

5
src/lib/appManagers/appImManager.ts

@ -89,6 +89,7 @@ import apiManagerProxy from '../mtproto/mtprotoworker';
import appRuntimeManager from './appRuntimeManager'; import appRuntimeManager from './appRuntimeManager';
import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount'; import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount';
import findUpClassName from '../../helpers/dom/findUpClassName'; import findUpClassName from '../../helpers/dom/findUpClassName';
import { CLICK_EVENT_NAME } from '../../helpers/dom/clickEvent';
export const CHAT_ANIMATION_GROUP = 'chat'; export const CHAT_ANIMATION_GROUP = 'chat';
@ -368,6 +369,10 @@ export class AppImManager extends EventListenerBase<{
const isVisible = parentElement.classList.contains(className); const isVisible = parentElement.classList.contains(className);
if(!isVisible) { if(!isVisible) {
cancelEvent(e); cancelEvent(e);
if(CLICK_EVENT_NAME !== 'click') {
window.addEventListener('click', cancelEvent, {capture: true, once: true});
}
} }
const duration = 400 / 2; const duration = 400 / 2;

18
src/lib/mediaPlayer.ts

@ -11,7 +11,6 @@ import cancelEvent from "../helpers/dom/cancelEvent";
import ListenerSetter, { Listener } from "../helpers/listenerSetter"; import ListenerSetter, { Listener } from "../helpers/listenerSetter";
import ButtonMenu from "../components/buttonMenu"; import ButtonMenu from "../components/buttonMenu";
import { ButtonMenuToggleHandler } from "../components/buttonMenuToggle"; import { ButtonMenuToggleHandler } from "../components/buttonMenuToggle";
import rootScope from "./rootScope";
import ControlsHover from "../helpers/dom/controlsHover"; import ControlsHover from "../helpers/dom/controlsHover";
import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../helpers/dom/fullScreen"; import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../helpers/dom/fullScreen";
import toHHMMSS from "../helpers/string/toHHMMSS"; import toHHMMSS from "../helpers/string/toHHMMSS";
@ -20,6 +19,7 @@ import VolumeSelector from "../components/volumeSelector";
import debounce from "../helpers/schedulers/debounce"; import debounce from "../helpers/schedulers/debounce";
import overlayCounter from "../helpers/overlayCounter"; import overlayCounter from "../helpers/overlayCounter";
import onMediaLoad from "../helpers/onMediaLoad"; import onMediaLoad from "../helpers/onMediaLoad";
import { attachClickEvent } from "../helpers/dom/clickEvent";
export default class VideoPlayer extends ControlsHover { export default class VideoPlayer extends ControlsHover {
private static PLAYBACK_RATES = [0.5, 1, 1.5, 2]; private static PLAYBACK_RATES = [0.5, 1, 1.5, 2];
@ -127,15 +127,15 @@ export default class VideoPlayer extends ControlsHover {
leftControls.insertBefore(volumeSelector.btn, timeElapsed.parentElement); leftControls.insertBefore(volumeSelector.btn, timeElapsed.parentElement);
Array.from(toggle).forEach((button) => { Array.from(toggle).forEach((button) => {
listenerSetter.add(button)('click', () => { attachClickEvent(button, () => {
this.togglePlay(); this.togglePlay();
}); }, {listenerSetter: this.listenerSetter});
}); });
if(this.pipButton) { if(this.pipButton) {
listenerSetter.add(this.pipButton)('click', () => { attachClickEvent(this.pipButton, () => {
this.video.requestPictureInPicture(); this.video.requestPictureInPicture();
}); }, {listenerSetter: this.listenerSetter});
const onPip = (pip: boolean) => { const onPip = (pip: boolean) => {
this.wrapper.style.visibility = pip ? 'hidden': ''; this.wrapper.style.visibility = pip ? 'hidden': '';
@ -170,9 +170,9 @@ export default class VideoPlayer extends ControlsHover {
} }
if(!IS_TOUCH_SUPPORTED) { if(!IS_TOUCH_SUPPORTED) {
listenerSetter.add(video)('click', () => { attachClickEvent(video, () => {
this.togglePlay(); this.togglePlay();
}); }, {listenerSetter: this.listenerSetter});
listenerSetter.add(document)('keydown', (e: KeyboardEvent) => { listenerSetter.add(document)('keydown', (e: KeyboardEvent) => {
if(overlayCounter.overlaysActive > 1 || document.pictureInPictureElement === video) { // forward popup is active, etc if(overlayCounter.overlaysActive > 1 || document.pictureInPictureElement === video) { // forward popup is active, etc
@ -216,9 +216,9 @@ export default class VideoPlayer extends ControlsHover {
} }
}); });
listenerSetter.add(fullScreenButton)('click', () => { attachClickEvent(fullScreenButton, () => {
this.toggleFullScreen(); this.toggleFullScreen();
}); }, {listenerSetter: this.listenerSetter});
addFullScreenListener(wrapper, this.onFullScreen.bind(this, fullScreenButton), listenerSetter); addFullScreenListener(wrapper, this.onFullScreen.bind(this, fullScreenButton), listenerSetter);

4
src/lib/rlottie/queryableWorker.ts

@ -47,7 +47,7 @@ export default class QueryableWorker extends EventListenerBase<{
queryMethodArguments: args queryMethodArguments: args
}); });
} else { } else {
const transfer: (ArrayBuffer | OffscreenCanvas)[] = []; const transfer: Transferable[] = [];
args.forEach((arg) => { args.forEach((arg) => {
if(arg instanceof ArrayBuffer) { if(arg instanceof ArrayBuffer) {
transfer.push(arg); transfer.push(arg);
@ -62,7 +62,7 @@ export default class QueryableWorker extends EventListenerBase<{
this.worker.postMessage({ this.worker.postMessage({
queryMethod: queryMethod, queryMethod: queryMethod,
queryMethodArguments: args queryMethodArguments: args
}, transfer as Transferable[]); }, transfer);
} }
} }
} }

3
webpack.common.js

@ -185,7 +185,8 @@ module.exports = {
// directory: path.join(__dirname, 'public') // directory: path.join(__dirname, 'public')
// }, // },
compress: true, compress: true,
http2: useLocalNotLocal ? true : (useLocal ? undefined : true), // http2: useLocalNotLocal ? true : (useLocal ? undefined : true),
http2: true,
https: useLocal ? undefined : { https: useLocal ? undefined : {
key: fs.readFileSync(__dirname + '/certs/server-key.pem', 'utf8'), key: fs.readFileSync(__dirname + '/certs/server-key.pem', 'utf8'),
cert: fs.readFileSync(__dirname + '/certs/server-cert.pem', 'utf8') cert: fs.readFileSync(__dirname + '/certs/server-cert.pem', 'utf8')

Loading…
Cancel
Save