tweb/src/components/chat/chat.ts

408 lines
14 KiB
TypeScript
Raw Normal View History

2021-04-08 15:52:31 +02:00
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
2021-03-11 04:06:44 +01:00
import type { AppNotificationsManager } from "../../lib/appManagers/appNotificationsManager";
import type { AppChatsManager } 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";
2021-01-09 15:08:26 +01:00
import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager";
import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager";
2021-01-09 15:08:26 +01:00
import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager";
2021-08-03 03:44:13 +02:00
import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager";
2021-06-22 04:28:37 +02:00
import type { State } from "../../lib/appManagers/appStateManager";
import type stateStorage from '../../lib/stateStorage';
import EventListenerBase from "../../helpers/eventListenerBase";
2021-04-27 17:45:53 +02:00
import { logger, LogTypes } from "../../lib/logger";
import rootScope from "../../lib/rootScope";
2021-04-04 17:39:17 +02:00
import appSidebarRight from "../sidebarRight";
import ChatBubbles from "./bubbles";
import ChatContextMenu from "./contextMenu";
import ChatInput from "./input";
import ChatSelection from "./selection";
import ChatTopbar from "./topbar";
2021-10-21 15:16:43 +02:00
import { NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import SetTransition from "../singleTransition";
import { fastRaf } from "../../helpers/schedulers";
2021-03-12 17:49:09 +01:00
import AppPrivateSearchTab from "../sidebarRight/tabs/search";
2021-04-04 17:39:17 +02:00
import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl";
import mediaSizes from "../../helpers/mediaSizes";
import ChatSearch from "./search";
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;
2021-01-09 16:06:47 +01:00
public wasAlreadyUsed = false;
// public initPeerId = 0;
2021-10-21 15:16:43 +02:00
public peerId: PeerId;
2020-12-20 04:54:35 +01:00
public threadId: number;
public setPeerPromise: Promise<void>;
public peerChanged: boolean;
public log: ReturnType<typeof logger>;
public type: ChatType = 'chat';
2021-03-13 11:12:24 +01:00
public noAutoDownloadMedia: boolean;
public inited = false;
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,
2021-08-03 03:44:13 +02:00
public appEmojiManager: AppEmojiManager,
public appMessagesIdsManager: AppMessagesIdsManager
) {
super();
this.container = document.createElement('div');
2021-08-20 15:55:23 +02:00
this.container.classList.add('chat', 'tabs-tab');
this.backgroundEl = document.createElement('div');
this.backgroundEl.classList.add('chat-background');
// * constructor end
2021-04-27 17:45:53 +02:00
this.log = logger('CHAT', LogTypes.Log | LogTypes.Warn | LogTypes.Debug | LogTypes.Error);
2020-12-20 04:54:35 +01:00
//this.log.error('Chat construction');
this.container.append(this.backgroundEl);
this.appImManager.chatsContainer.append(this.container);
}
public setBackground(url: string): Promise<void> {
const theme = rootScope.getTheme();
2021-04-22 18:58:31 +02:00
let item: HTMLElement;
if(theme.background.type === 'color' && document.documentElement.style.cursor === 'grabbing') {
const _item = this.backgroundEl.lastElementChild as HTMLElement;
if(_item && _item.dataset.type === theme.background.type) {
item = _item;
}
}
if(!item) {
item = document.createElement('div');
item.classList.add('chat-background-item');
item.dataset.type = theme.background.type;
}
2021-04-19 17:30:51 +02:00
if(theme.background.type === 'color') {
item.style.backgroundColor = theme.background.color;
item.style.backgroundImage = 'none';
}
return new Promise<void>((resolve) => {
const cb = () => {
2021-04-22 18:58:31 +02:00
const prev = this.backgroundEl.lastElementChild as HTMLElement;
if(prev === item) {
resolve();
return;
}
this.backgroundEl.append(item);
// * одного недостаточно, при обновлении страницы все равно фон появляется неплавно
// ! с requestAnimationFrame лучше, но все равно иногда моргает, так что использую два фаста.
fastRaf(() => {
fastRaf(() => {
SetTransition(item, 'is-visible', true, 200, prev ? () => {
prev.remove();
} : null);
});
});
resolve();
};
if(url) {
renderImageFromUrl(item, url, cb);
} else {
cb();
}
});
}
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);
}
}
2021-10-21 15:16:43 +02:00
public init(/* peerId: PeerId */) {
// this.initPeerId = peerId;
2021-01-09 15:08:26 +01:00
2021-09-15 12:47:27 +02:00
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager, this.appProfileManager, this.appUsersManager);
2021-08-03 03:44:13 +02:00
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager, this.appDraftsManager, this.appMessagesIdsManager);
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.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
2021-08-03 03:44:13 +02:00
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager);
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();
2020-12-20 04:54:35 +01:00
} else if(this.type === 'discussion') {
this.bubbles.constructPeerHelpers();
this.input.constructPeerHelpers();
}
this.container.classList.add('type-' + this.type);
this.container.append(this.topbar.container, this.bubbles.bubblesContainer, this.input.chatInput);
2021-03-16 17:50:25 +01:00
this.bubbles.listenerSetter.add(rootScope)('dialog_migrate', ({migrateFrom, migrateTo}) => {
2021-03-16 17:50:25 +01:00
if(this.peerId === migrateFrom) {
this.setPeer(migrateTo);
}
});
this.bubbles.listenerSetter.add(rootScope)('dialog_drop', (e) => {
2021-03-16 17:50:25 +01:00
if(e.peerId === this.peerId) {
2021-10-21 15:16:43 +02:00
this.appImManager.setPeer(NULL_PEER_ID);
2021-03-16 17:50:25 +01:00
}
});
}
public beforeDestroy() {
this.bubbles.cleanup();
}
public destroy() {
2020-12-20 04:54:35 +01:00
//const perf = performance.now();
this.topbar.destroy();
this.bubbles.destroy();
this.input.destroy();
delete this.topbar;
delete this.bubbles;
delete this.input;
delete this.selection;
delete this.contextMenu;
this.container.remove();
2020-12-20 04:54:35 +01:00
//this.log.error('Chat destroy time:', performance.now() - perf);
}
2021-01-09 16:06:47 +01:00
public cleanup(helperToo = true) {
this.input.cleanup(helperToo);
this.selection.cleanup();
}
2021-10-21 15:16:43 +02:00
public setPeer(peerId: PeerId, lastMsgId?: number) {
if(!peerId) {
this.inited = false;
} else if(!this.inited) {
if(this.init) {
this.init(/* peerId */);
this.init = null;
}
this.inited = true;
}
2021-01-09 15:08:26 +01:00
const samePeer = this.peerId === peerId;
if(!samePeer) {
2021-06-11 14:52:53 +02:00
rootScope.dispatchEvent('peer_changing', this);
2021-01-09 15:08:26 +01:00
this.peerId = peerId;
} else if(this.setPeerPromise) {
return;
2021-01-09 15:08:26 +01:00
}
//console.time('appImManager setPeer');
//console.time('appImManager setPeer pre promise');
////console.time('appImManager: pre render start');
2021-01-09 15:08:26 +01:00
if(!peerId) {
appSidebarRight.toggleSidebar(false);
2021-01-09 16:06:47 +01:00
this.cleanup(true);
this.topbar.setPeer(peerId);
this.bubbles.setPeer(peerId);
2021-06-11 14:52:53 +02:00
rootScope.dispatchEvent('peer_changed', peerId);
return;
}
// set new
if(!samePeer) {
2021-03-12 17:49:09 +01:00
const searchTab = appSidebarRight.getTab(AppPrivateSearchTab);
if(searchTab) {
searchTab.close();
}
2020-12-23 03:18:24 +01:00
appSidebarRight.sharedMediaTab.setPeer(peerId, this.threadId);
2021-01-09 16:06:47 +01:00
this.input.clearHelper(); // костыль
this.selection.cleanup(); // TODO: REFACTOR !!!!!!
2021-03-13 11:12:24 +01:00
this.setAutoDownloadMedia();
}
2021-01-09 15:08:26 +01:00
this.peerChanged = samePeer;
const result = this.bubbles.setPeer(peerId, lastMsgId);
if(!result) {
return;
}
const {promise} = result;
//console.timeEnd('appImManager setPeer pre promise');
2021-01-09 15:08:26 +01:00
const setPeerPromise = this.setPeerPromise = promise.finally(() => {
if(this.setPeerPromise === setPeerPromise) {
this.setPeerPromise = null;
}
});
if(!samePeer) {
appSidebarRight.sharedMediaTab.setLoadMutex(this.setPeerPromise);
appSidebarRight.sharedMediaTab.loadSidebarMedia(true);
}
2020-12-20 04:54:35 +01:00
/* this.setPeerPromise.then(() => {
appSidebarRight.sharedMediaTab.loadSidebarMedia(false);
2020-12-20 04:54:35 +01:00
}); */
return result;
}
2021-03-13 11:12:24 +01:00
public setAutoDownloadMedia() {
2021-10-21 15:16:43 +02:00
const peerId = this.peerId;
if(!peerId) {
return;
}
2021-03-13 11:12:24 +01:00
let type: keyof State['settings']['autoDownload'];
2021-10-21 15:16:43 +02:00
if(!peerId.isUser()) {
if(peerId.isBroadcast()) {
2021-03-13 11:12:24 +01:00
type = 'channels';
} else {
type = 'groups';
}
} else {
2021-10-21 15:16:43 +02:00
if(peerId.isContact()) {
2021-03-13 11:12:24 +01:00
type = 'contacts';
} else {
type = 'private';
}
}
this.noAutoDownloadMedia = !rootScope.settings.autoDownload[type];
}
2021-01-09 15:08:26 +01:00
public setMessageId(messageId?: number) {
return this.setPeer(this.peerId, messageId);
}
public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgId: number) {
if(this.peerChanged) return;
let peerId = this.peerId;
this.peerChanged = true;
2021-01-09 16:06:47 +01:00
this.cleanup(false);
2021-01-09 15:08:26 +01:00
this.topbar.setPeer(peerId);
this.topbar.finishPeerChange(isTarget, isJump, lastMsgId);
this.bubbles.finishPeerChange();
this.input.finishPeerChange();
appSidebarRight.sharedMediaTab.fillProfileElements();
this.log.setPrefix('CHAT-' + peerId + '-' + this.type);
2021-06-11 14:52:53 +02:00
rootScope.dispatchEvent('peer_changed', peerId);
2021-01-09 16:06:47 +01:00
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) {
new ChatSearch(this.topbar, this, query);
} else {
let tab = appSidebarRight.getTab(AppPrivateSearchTab);
if(!tab) {
tab = new AppPrivateSearchTab(appSidebarRight);
}
tab.open(this.peerId, this.threadId, this.bubbles.onDatePick, query);
}
}
}