/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import type { AppNotificationsManager } from "../../lib/appManagers/appNotificationsManager"; import type { AppChatsManager, ChatRights } from "../../lib/appManagers/appChatsManager"; import type { AppDocsManager } from "../../lib/appManagers/appDocsManager"; import type { AppImManager } from "../../lib/appManagers/appImManager"; import type { AppInlineBotsManager } from "../../lib/appManagers/appInlineBotsManager"; import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager"; import type { AppPollsManager } from "../../lib/appManagers/appPollsManager"; import type { AppProfileManager } from "../../lib/appManagers/appProfileManager"; import type { AppStickersManager } from "../../lib/appManagers/appStickersManager"; import type { AppUsersManager } from "../../lib/appManagers/appUsersManager"; import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager"; import type { ApiManagerProxy } from "../../lib/mtproto/mtprotoworker"; import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager"; import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager"; import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager"; import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager"; import type { AppGroupCallsManager } from "../../lib/appManagers/appGroupCallsManager"; import type { AppReactionsManager } from "../../lib/appManagers/appReactionsManager"; import type stateStorage from '../../lib/stateStorage'; import EventListenerBase from "../../helpers/eventListenerBase"; import { logger, LogTypes } from "../../lib/logger"; import rootScope from "../../lib/rootScope"; import appSidebarRight from "../sidebarRight"; import ChatBubbles from "./bubbles"; import ChatContextMenu from "./contextMenu"; import ChatInput from "./input"; import ChatSelection from "./selection"; import ChatTopbar from "./topbar"; import { BOT_START_PARAM, NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import SetTransition from "../singleTransition"; import AppPrivateSearchTab from "../sidebarRight/tabs/search"; import renderImageFromUrl, { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import mediaSizes from "../../helpers/mediaSizes"; import ChatSearch from "./search"; import { IS_TOUCH_SUPPORTED } from "../../environment/touchSupport"; import getAutoDownloadSettingsByPeerId, { ChatAutoDownloadSettings } from "../../helpers/autoDownload"; import ChatBackgroundGradientRenderer from "./gradientRenderer"; import ChatBackgroundPatternRenderer from "./patternRenderer"; import { pause } from "../../helpers/schedulers/pause"; export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled'; export default class Chat extends EventListenerBase<{ setPeer: (mid: number, isTopMessage: boolean) => void }> { public container: HTMLElement; public backgroundEl: HTMLElement; public topbar: ChatTopbar; public bubbles: ChatBubbles; public input: ChatInput; public selection: ChatSelection; public contextMenu: ChatContextMenu; public search: ChatSearch; public wasAlreadyUsed: boolean; // public initPeerId = 0; public peerId: PeerId; public threadId: number; public setPeerPromise: Promise; public peerChanged: boolean; public log: ReturnType; public type: ChatType; public noForwards: boolean; public inited: boolean; public isRestricted: boolean; public autoDownload: ChatAutoDownloadSettings; public gradientRenderer: ChatBackgroundGradientRenderer; public patternRenderer: ChatBackgroundPatternRenderer; public gradientCanvas: HTMLCanvasElement; public patternCanvas: HTMLCanvasElement; public backgroundTempId: number; public setBackgroundPromise: Promise; constructor( public appImManager: AppImManager, public appChatsManager: AppChatsManager, public appDocsManager: AppDocsManager, public appInlineBotsManager: AppInlineBotsManager, public appMessagesManager: AppMessagesManager, public appPeersManager: AppPeersManager, public appPhotosManager: AppPhotosManager, public appProfileManager: AppProfileManager, public appStickersManager: AppStickersManager, public appUsersManager: AppUsersManager, public appWebPagesManager: AppWebPagesManager, public appPollsManager: AppPollsManager, public apiManager: ApiManagerProxy, public appDraftsManager: AppDraftsManager, public serverTimeManager: ServerTimeManager, public storage: typeof stateStorage, public appNotificationsManager: AppNotificationsManager, public appEmojiManager: AppEmojiManager, public appMessagesIdsManager: AppMessagesIdsManager, public appGroupCallsManager: AppGroupCallsManager, public appReactionsManager: AppReactionsManager ) { super(); this.type = 'chat'; this.container = document.createElement('div'); this.container.classList.add('chat', 'tabs-tab'); this.backgroundEl = document.createElement('div'); this.backgroundEl.classList.add('chat-background'); // * constructor end this.log = logger('CHAT', LogTypes.Log | LogTypes.Warn | LogTypes.Debug | LogTypes.Error); //this.log.error('Chat construction'); this.peerId = NULL_PEER_ID; this.container.append(this.backgroundEl); this.appImManager.chatsContainer.append(this.container); this.backgroundTempId = 0; } public setBackground(url: string, skipAnimation?: boolean): Promise { const theme = rootScope.getTheme(); let item: HTMLElement; const isColorBackground = !!theme.background.color && !theme.background.slug && !theme.background.intensity; if( isColorBackground && document.documentElement.style.cursor === 'grabbing' && this.gradientRenderer && !this.patternRenderer ) { this.gradientCanvas.dataset.colors = theme.background.color; this.gradientRenderer.init(this.gradientCanvas); return Promise.resolve(); } const tempId = ++this.backgroundTempId; const previousGradientRenderer = this.gradientRenderer; const previousPatternRenderer = this.patternRenderer; const previousGradientCanvas = this.gradientCanvas; const previousPatternCanvas = this.patternCanvas; this.gradientRenderer = this.patternRenderer = this.gradientCanvas = this.patternCanvas = undefined; const intensity = theme.background.intensity && theme.background.intensity / 100; const isDarkPattern = !!intensity && intensity < 0; let patternRenderer: ChatBackgroundPatternRenderer; let patternCanvas = item?.firstElementChild as HTMLCanvasElement; let gradientCanvas: HTMLCanvasElement; if(!item) { item = document.createElement('div'); item.classList.add('chat-background-item'); if(url) { if(intensity) { item.classList.add('is-pattern'); const rect = this.appImManager.chatsContainer.getBoundingClientRect(); patternRenderer = this.patternRenderer = ChatBackgroundPatternRenderer.getInstance({ url, width: rect.width, height: rect.height }); patternCanvas = this.patternCanvas = patternRenderer.createCanvas(); patternCanvas.classList.add('chat-background-item-canvas', 'chat-background-item-pattern-canvas'); } else if(theme.background.slug) { item.classList.add('is-image'); } } else if(theme.background.color) { item.classList.add('is-color'); } } let gradientRenderer: ChatBackgroundGradientRenderer; const color = theme.background.color; if(color) { // if(color.includes(',')) { const {canvas, gradientRenderer: _gradientRenderer} = ChatBackgroundGradientRenderer.create(color); gradientRenderer = this.gradientRenderer = _gradientRenderer; gradientCanvas = this.gradientCanvas = canvas; gradientCanvas.classList.add('chat-background-item-canvas', 'chat-background-item-color-canvas'); if(rootScope.settings.animationsEnabled) { gradientRenderer.scrollAnimate(true); } // } else { // item.style.backgroundColor = color; // item.style.backgroundImage = 'none'; // } } if(patternRenderer) { const setOpacityTo = isDarkPattern ? gradientCanvas : patternCanvas; setOpacityTo.style.setProperty('--opacity-max', '' + Math.abs(intensity)); } const promise = new Promise((resolve) => { const cb = () => { if(this.backgroundTempId !== tempId) { if(patternRenderer) { patternRenderer.cleanup(patternCanvas); } if(gradientRenderer) { gradientRenderer.cleanup(); } return; } const prev = this.backgroundEl.lastElementChild as HTMLElement; if(prev === item) { resolve(); return; } const append = [gradientCanvas, isDarkPattern ? undefined : patternCanvas].filter(Boolean); if(append.length) { item.append(...append); } this.backgroundEl.append(item); SetTransition(item, 'is-visible', true, !skipAnimation ? 200 : 0, prev ? () => { if(previousPatternRenderer) { previousPatternRenderer.cleanup(previousPatternCanvas); } if(previousGradientRenderer) { previousGradientRenderer.cleanup(); } prev.remove(); } : null, 2); resolve(); }; if(patternRenderer) { const renderPatternPromise = patternRenderer.renderToCanvas(patternCanvas); renderPatternPromise.then(() => { let promise: Promise; if(isDarkPattern) { promise = patternRenderer.exportCanvasPatternToImage(patternCanvas).then(url => { if(this.backgroundTempId !== tempId) { return; } gradientCanvas.style.webkitMaskImage = `url(${url})`; }); } else { promise = Promise.resolve(); } promise.then(cb); }); } else if(url) { renderImageFromUrl(item, url, cb); } else { cb(); } }); return this.setBackgroundPromise = Promise.race([ pause(500), promise ]); } public setType(type: ChatType) { this.type = type; if(this.type === 'scheduled') { this.getMessagesStorage = () => this.appMessagesManager.getScheduledMessagesStorage(this.peerId); //this.getMessage = (mid) => this.appMessagesManager.getMessageFromStorage(this.appMessagesManager.getScheduledMessagesStorage(this.peerId), mid); } } public init(/* peerId: PeerId */) { // this.initPeerId = peerId; this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager, this.appProfileManager, this.appUsersManager, this.appGroupCallsManager); this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager, this.appDraftsManager, this.appMessagesIdsManager, this.appChatsManager, this.appReactionsManager); this.input = new ChatInput(this, this.appMessagesManager, this.appMessagesIdsManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager, this.appUsersManager, this.appInlineBotsManager, this.appProfileManager); this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager); this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager, this.appReactionsManager); if(this.type === 'chat') { this.topbar.constructUtils(); this.topbar.constructPeerHelpers(); } else if(this.type === 'pinned') { this.topbar.constructPinnedHelpers(); } else if(this.type === 'discussion') { this.topbar.constructUtils(); this.topbar.constructDiscussionHelpers(); } this.topbar.construct(); this.input.construct(); if(this.type === 'chat') { // * гений в деле, разный порядок из-за разной последовательности действий this.bubbles.constructPeerHelpers(); this.input.constructPeerHelpers(); } else if(this.type === 'pinned') { this.bubbles.constructPinnedHelpers(); this.input.constructPinnedHelpers(); } else if(this.type === 'scheduled') { this.bubbles.constructScheduledHelpers(); this.input.constructPeerHelpers(); } else if(this.type === 'discussion') { this.bubbles.constructPeerHelpers(); this.input.constructPeerHelpers(); } if(this.type !== 'scheduled' && !IS_TOUCH_SUPPORTED) { this.bubbles.setReactionsHoverListeners(); } this.container.classList.add('type-' + this.type); this.container.append(this.topbar.container, this.bubbles.bubblesContainer, this.input.chatInput); this.bubbles.listenerSetter.add(rootScope)('dialog_migrate', ({migrateFrom, migrateTo}) => { if(this.peerId === migrateFrom) { this.setPeer(migrateTo); } }); this.bubbles.listenerSetter.add(rootScope)('dialog_drop', (e) => { if(e.peerId === this.peerId) { this.appImManager.setPeer(); } }); } public beforeDestroy() { this.bubbles.cleanup(); } private cleanupBackground() { ++this.backgroundTempId; if(this.patternRenderer) { this.patternRenderer.cleanup(this.patternCanvas); this.patternRenderer = undefined; } if(this.gradientRenderer) { this.gradientRenderer.cleanup(); this.gradientRenderer = undefined; } } public destroy() { //const perf = performance.now(); this.topbar.destroy(); this.bubbles.destroy(); this.input.destroy(); this.contextMenu && this.contextMenu.destroy(); this.cleanupBackground(); delete this.topbar; delete this.bubbles; delete this.input; delete this.selection; delete this.contextMenu; this.container.remove(); //this.log.error('Chat destroy time:', performance.now() - perf); } public cleanup(helperToo = true) { this.input.cleanup(helperToo); this.selection.cleanup(); } public setPeer(peerId: PeerId, lastMsgId?: number, startParam?: string) { if(!peerId) { this.inited = undefined; } else if(!this.inited) { if(this.init) { this.init(/* peerId */); this.init = null; } this.inited = true; } const samePeer = this.peerId === peerId; if(!samePeer) { rootScope.dispatchEvent('peer_changing', this); this.peerId = peerId || NULL_PEER_ID; } else if(this.setPeerPromise) { return; } //console.time('appImManager setPeer'); //console.time('appImManager setPeer pre promise'); ////console.time('appImManager: pre render start'); if(!peerId) { appSidebarRight.toggleSidebar(false); this.cleanup(true); this.topbar.setPeer(peerId); this.bubbles.setPeer(peerId); rootScope.dispatchEvent('peer_changed', peerId); return; } // set new if(!samePeer) { const searchTab = appSidebarRight.getTab(AppPrivateSearchTab); if(searchTab) { searchTab.close(); } this.noForwards = this.appPeersManager.noForwards(peerId); this.isRestricted = this.appPeersManager.isRestricted(peerId); this.container.classList.toggle('no-forwards', this.noForwards); appSidebarRight.sharedMediaTab.setPeer(peerId, this.threadId); this.input.clearHelper(); // костыль this.selection.cleanup(); // TODO: REFACTOR !!!!!! this.setAutoDownloadMedia(); } this.peerChanged = samePeer; if(startParam === undefined && this.isStartButtonNeeded()) { startParam = BOT_START_PARAM; } const result = this.bubbles.setPeer(peerId, lastMsgId, startParam); if(!result) { return; } const {promise} = result; //console.timeEnd('appImManager setPeer pre promise'); const setPeerPromise = this.setPeerPromise = promise.finally(() => { if(this.setPeerPromise === setPeerPromise) { this.setPeerPromise = null; } }); if(!samePeer) { appSidebarRight.sharedMediaTab.setLoadMutex(this.setPeerPromise); appSidebarRight.sharedMediaTab.loadSidebarMedia(true); } /* this.setPeerPromise.then(() => { appSidebarRight.sharedMediaTab.loadSidebarMedia(false); }); */ return result; } public setAutoDownloadMedia() { this.autoDownload = getAutoDownloadSettingsByPeerId(this.peerId); } public setMessageId(messageId?: number) { return this.setPeer(this.peerId, messageId); } public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgId: number, startParam?: string) { if(this.peerChanged) return; let peerId = this.peerId; this.peerChanged = true; this.cleanup(false); this.topbar.setPeer(peerId); this.topbar.finishPeerChange(isTarget, isJump, lastMsgId); this.bubbles.finishPeerChange(); this.input.finishPeerChange(startParam); appSidebarRight.sharedMediaTab.fillProfileElements(); this.log.setPrefix('CHAT-' + peerId + '-' + this.type); rootScope.dispatchEvent('peer_changed', peerId); this.wasAlreadyUsed = true; } public getMessagesStorage() { return this.appMessagesManager.getMessagesStorage(this.peerId); } public getMessage(mid: number) { return this.appMessagesManager.getMessageFromStorage(this.getMessagesStorage(), mid); //return this.appMessagesManager.getMessageByPeer(this.peerId, mid); } public getMidsByMid(mid: number) { return this.appMessagesManager.getMidsByMessage(this.getMessage(mid)); } public isAnyGroup() { return this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID || this.appPeersManager.isAnyGroup(this.peerId); } public initSearch(query?: string) { if(!this.peerId) return; if(mediaSizes.isMobile) { if(!this.search) { this.search = new ChatSearch(this.topbar, this, query); } else { this.search.setQuery(query); } } else { let tab = appSidebarRight.getTab(AppPrivateSearchTab); if(!tab) { tab = new AppPrivateSearchTab(appSidebarRight); } tab.open(this.peerId, this.threadId, this.bubbles.onDatePick, query); } } public canSend(action?: ChatRights) { return this.appMessagesManager.canSendToPeer(this.peerId, this.threadId, action); } public isStartButtonNeeded() { return this.appPeersManager.isBot(this.peerId) && !this.appMessagesManager.getDialogOnly(this.peerId) && !this.appMessagesManager.getHistoryStorage(this.peerId).history.length; } public getMessageSendingParams() { return { threadId: this.threadId, replyToMsgId: this.input.replyToMsgId, scheduleDate: this.input.scheduleDate, sendSilent: this.input.sendSilent, sendAsPeerId: this.input.sendAsPeerId }; } }