tweb/src/lib/appManagers/appImManager.ts
Eduard Kuzmenko 4d7638af5a Notifications almost finished
Maybe fix updates
Removed jsbn dependency
2021-03-09 02:15:44 +04:00

778 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//import apiManager from '../mtproto/apiManager';
import animationIntersector from '../../components/animationIntersector';
import appSidebarLeft from "../../components/sidebarLeft";
import appSidebarRight, { AppSidebarRight, RIGHT_COLUMN_ACTIVE_CLASSNAME } from '../../components/sidebarRight';
import mediaSizes, { ScreenSize } from '../../helpers/mediaSizes';
import { logger, LogLevels } from "../logger";
import apiManager from '../mtproto/mtprotoworker';
import rootScope from '../rootScope';
import apiUpdatesManager from './apiUpdatesManager';
import appUsersManager from "./appUsersManager";
import Chat, { ChatType } from '../../components/chat/chat';
import appChatsManager from './appChatsManager';
import appDocsManager from './appDocsManager';
import appInlineBotsManager from './appInlineBotsManager';
import appMessagesManager from './appMessagesManager';
import appPeersManager from './appPeersManager';
import appPhotosManager from './appPhotosManager';
import appProfileManager from './appProfileManager';
import appStickersManager from './appStickersManager';
import appWebPagesManager from './appWebPagesManager';
import { blurActiveElement, cancelEvent, disableTransition, getFilesFromEvent, placeCaretAtEnd, whichChild } from '../../helpers/dom';
import PopupNewMedia from '../../components/popups/newMedia';
import { numberThousandSplitter } from '../../helpers/number';
import MarkupTooltip from '../../components/chat/markupTooltip';
import { isTouchSupported } from '../../helpers/touchSupport';
import appPollsManager from './appPollsManager';
import SetTransition from '../../components/singleTransition';
import ChatDragAndDrop from '../../components/chat/dragAndDrop';
import { debounce, pause, doubleRaf } from '../../helpers/schedulers';
import lottieLoader from '../lottieLoader';
import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import appDraftsManager from './appDraftsManager';
import serverTimeManager from '../mtproto/serverTimeManager';
import sessionStorage from '../sessionStorage';
import appDownloadManager from './appDownloadManager';
import appStateManager, { AppStateManager } from './appStateManager';
import { MOUNT_CLASS_TO } from '../../config/debug';
import appNavigationController from '../../components/appNavigationController';
//console.log('appImManager included33!');
appSidebarLeft; // just to include
const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown';
export const CHAT_ANIMATION_GROUP = 'chat';
const FOCUS_EVENT_NAME = isTouchSupported ? 'touchstart' : 'mousemove';
export class AppImManager {
public columnEl = document.getElementById('column-center') as HTMLDivElement;
public chatsContainer: HTMLElement;
public offline = false;
public updateStatusInterval = 0;
public log: ReturnType<typeof logger>;
public setPeerPromise: Promise<void> = null;
public tabId = -1;
private chats: Chat[] = [];
private prevTab: HTMLElement;
private chatsSelectTabDebounced: () => void;
public markupTooltip: MarkupTooltip;
get myId() {
return rootScope.myId;
}
get chat(): Chat {
return this.chats[this.chats.length - 1];
}
constructor() {
apiUpdatesManager.attach();
this.log = logger('IM', LogLevels.log | LogLevels.warn | LogLevels.debug | LogLevels.error);
this.selectTab(0);
window.addEventListener('blur', () => {
animationIntersector.checkAnimations(true);
this.offline = rootScope.idle.isIDLE = true;
this.updateStatus();
clearInterval(this.updateStatusInterval);
rootScope.broadcast('idle', true);
window.addEventListener('focus', () => {
this.offline = rootScope.idle.isIDLE = false;
this.updateStatus();
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
// в обратном порядке
animationIntersector.checkAnimations(false);
rootScope.broadcast('idle', false);
}, {once: true});
});
// * Prevent setting online after reloading page
window.addEventListener(FOCUS_EVENT_NAME, () => {
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
this.updateStatus();
rootScope.broadcast('idle', false);
}, {once: true, passive: true});
this.chatsContainer = document.createElement('div');
this.chatsContainer.classList.add('chats-container', 'tabs-container');
this.chatsContainer.dataset.animation = 'navigation';
this.columnEl.append(this.chatsContainer);
this.createNewChat();
this.chatsSelectTab(this.chat.container);
window.addEventListener('hashchange', (e) => {
const hash = location.hash;
const splitted = hash.split('?');
if(!splitted[1]) {
return;
}
const params: any = {};
splitted[1].split('&').forEach(item => {
params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]);
});
this.log('hashchange', hash, splitted[0], params);
switch(splitted[0]) {
case '#/im': {
const p = params.p;
let postId = params.post !== undefined ? appMessagesManager.generateMessageId(+params.post) : undefined;
switch(p[0]) {
case '@': {
appUsersManager.resolveUsername(p).then(peer => {
const isUser = peer._ === 'user';
const peerId = isUser ? peer.id : -peer.id;
this.setInnerPeer(peerId, postId);
});
break;
}
default: { // peerId
this.setInnerPeer(postId ? -+p : +p, postId);
break;
}
}
}
}
location.hash = '';
});
this.setSettings();
rootScope.on('settings_updated', () => this.setSettings());
useHeavyAnimationCheck(() => {
animationIntersector.setOnlyOnePlayableGroup('lock');
animationIntersector.checkAnimations(true);
}, () => {
animationIntersector.setOnlyOnePlayableGroup('');
animationIntersector.checkAnimations(false);
});
const isDefaultBackground = rootScope.settings.background.blur === AppStateManager.STATE_INIT.settings.background.blur &&
rootScope.settings.background.slug === AppStateManager.STATE_INIT.settings.background.slug;
if(!isDefaultBackground) {
appDownloadManager.cacheStorage.getFile('backgrounds/' + rootScope.settings.background.slug).then(blob => {
this.setBackground(URL.createObjectURL(blob), false);
}, () => { // * if NO_ENTRY_FOUND
this.setBackground('');
appStateManager.setByKey('settings.background', AppStateManager.STATE_INIT.settings.background); // * reset background
});
} else {
this.setBackground('');
}
// * fix simultaneous opened both sidebars, can happen when floating sidebar is opened with left sidebar
mediaSizes.addListener('changeScreen', (from, to) => {
if(document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)
&& document.body.classList.contains(RIGHT_COLUMN_ACTIVE_CLASSNAME)) {
appSidebarRight.toggleSidebar(false);
}
});
/* rootScope.on('peer_changing', (chat) => {
this.saveChatPosition(chat);
});
sessionStorage.get('chatPositions').then((c) => {
sessionStorage.setToCache('chatPositions', c || {});
}); */
}
public setBackground(url: string, broadcastEvent = true): Promise<void> {
const promises = this.chats.map(chat => chat.setBackground(url));
return promises[promises.length - 1].then(() => {
if(broadcastEvent) {
rootScope.broadcast('background_change');
}
});
}
/* public saveChatPosition(chat: Chat) {
const bubble = chat.bubbles.getBubbleByPoint('top');
if(bubble) {
const top = bubble.getBoundingClientRect().top;
this.log('saving position by bubble:', bubble, top);
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
const chatPositions = sessionStorage.getFromCache('chatPositions');
chatPositions[key] = {
mid: +bubble.dataset.mid,
top
};
sessionStorage.set({chatPositions});
}
}
public getChatSavedPosition(chat: Chat) {
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
return sessionStorage.getFromCache('chatPositions')[key];
} */
private setSettings() {
document.documentElement.style.setProperty('--messages-text-size', rootScope.settings.messagesTextSize + 'px');
if(rootScope.settings.background.highlightningColor) {
document.documentElement.style.setProperty('--message-highlightning-color', rootScope.settings.background.highlightningColor);
} else {
document.documentElement.style.removeProperty('--message-highlightning-color');
}
document.body.classList.toggle('animation-level-0', !rootScope.settings.animationsEnabled);
document.body.classList.toggle('animation-level-1', false);
document.body.classList.toggle('animation-level-2', rootScope.settings.animationsEnabled);
this.chatsSelectTabDebounced = debounce(() => {
const topbar = this.chat.topbar;
if(topbar.pinnedMessage) { // * буду молиться богам, чтобы это ничего не сломало, но это исправляет получение пиннеда после анимации
topbar.pinnedMessage.setCorrectIndex(0);
}
apiManager.setQueueId(this.chat.bubbles.lazyLoadQueue.queueId);
}, rootScope.settings.animationsEnabled ? 250 : 0, false, true);
lottieLoader.setLoop(rootScope.settings.stickers.loop);
animationIntersector.checkAnimations(false);
}
// * не могу использовать тут TransitionSlider, так как мне нужен отрисованный блок рядом
// * (или под текущим чатом) чтобы правильно отрендерить чат (напр. scrollTop)
private chatsSelectTab(tab: HTMLElement, animate?: boolean) {
if(this.prevTab === tab) {
return;
}
if(animate === false && this.prevTab) { // * will be used for Safari iOS history swipe
disableTransition([tab, this.prevTab].filter(Boolean));
}
if(this.prevTab) {
this.prevTab.classList.remove('active');
this.chatsSelectTabDebounced();
// ! нужно переделать на animation, так как при лаге анимация будет длиться не 250мс
if(rootScope.settings.animationsEnabled && animate !== false) {
dispatchHeavyAnimationEvent(pause(250 + 150), 250 + 150);
}
const prevIdx = whichChild(this.prevTab);
const idx = whichChild(tab);
if(idx > prevIdx) {
appNavigationController.pushItem({
type: 'chat',
onPop: (canAnimate) => {
this.setPeer(0, undefined, canAnimate);
blurActiveElement();
}
});
}
}
tab.classList.add('active');
this.prevTab = tab;
}
private init() {
document.addEventListener('paste', this.onDocumentPaste, true);
const onKeyDown = (e: KeyboardEvent) => {
if(rootScope.overlayIsActive) return;
const target = e.target as HTMLElement;
//if(target.tagName === 'INPUT') return;
//this.log('onkeydown', e, document.activeElement);
const chat = this.chat;
if(e.key === 'Escape') {
let cancel = true;
if(this.markupTooltip?.container?.classList.contains('is-visible')) {
this.markupTooltip.hide();
} else if(chat.selection.isSelecting) {
chat.selection.cancelSelection();
} else if(chat.container.classList.contains('is-helper-active')) {
chat.input.replyElements.cancelBtn.click();
} else if(chat.peerId !== 0) { // hide current dialog
this.setPeer(0);
} else {
cancel = false;
}
// * cancel event for safari, because if application is in fullscreen, browser will try to exit fullscreen
if(cancel) {
cancelEvent(e);
}
} else if(e.key === 'Meta' || e.key === 'Control') {
return;
} else if(e.code === "KeyC" && (e.ctrlKey || e.metaKey) && target.tagName !== 'INPUT') {
return;
} else if(e.code === 'ArrowUp') {
if(!chat.input.editMsgId && chat.input.isInputEmpty()) {
const history = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
if(history.history.length) {
let goodMid: number;
for(const mid of history.history.slice) {
const message = chat.getMessage(mid);
const good = this.myId === chat.peerId ? message.fromId === this.myId : message.pFlags.out;
if(good) {
if(appMessagesManager.canEditMessage(chat.getMessage(mid), 'text')) {
goodMid = mid;
break;
}
// * this check will allow editing only last message
//break;
}
}
if(goodMid) {
chat.input.initMessageEditing(goodMid);
cancelEvent(e); // * prevent from scrolling
}
}
}
}
if(chat.input.messageInput && e.target !== chat.input.messageInput && target.tagName !== 'INPUT' && !target.hasAttribute('contenteditable') && !isTouchSupported) {
chat.input.messageInput.focus();
placeCaretAtEnd(chat.input.messageInput);
}
};
document.body.addEventListener('keydown', onKeyDown);
rootScope.addEventListener('history_multiappend', (e) => {
const msgIdsByPeer = e;
for(const peerId in msgIdsByPeer) {
appSidebarRight.sharedMediaTab.renderNewMessages(+peerId, msgIdsByPeer[peerId]);
}
});
rootScope.addEventListener('history_delete', (e) => {
const {peerId, msgs} = e;
const mids = Object.keys(msgs).map(s => +s);
appSidebarRight.sharedMediaTab.deleteDeletedMessages(peerId, mids);
});
// Calls when message successfully sent and we have an id
rootScope.addEventListener('message_sent', (e) => {
const {storage, tempId, mid} = e;
const message = appMessagesManager.getMessageFromStorage(storage, mid);
appSidebarRight.sharedMediaTab.renderNewMessages(message.peerId, [mid]);
});
if(!isTouchSupported) {
this.attachDragAndDropListeners();
}
//if(!isTouchSupported) {
this.markupTooltip = new MarkupTooltip(this);
this.markupTooltip.handleSelection();
//}
}
private attachDragAndDropListeners() {
const drops: ChatDragAndDrop[] = [];
let mounted = false;
const toggle = async(e: DragEvent, mount: boolean) => {
if(mount === mounted) return;
const _types = e.dataTransfer.types;
// @ts-ignore
const isFiles = _types.contains ? _types.contains('Files') : _types.indexOf('Files') >= 0;
if(!isFiles || !this.canDrag()) { // * skip dragging text case
counter = 0;
return;
}
if(mount && !drops.length) {
const types: string[] = await getFilesFromEvent(e, true)
const force = isFiles && !types.length; // * can't get file items not from 'drop' on Safari
const foundMedia = types.filter(t => ['image', 'video'].includes(t.split('/')[0])).length;
const foundDocuments = types.length - foundMedia;
this.log('drag files', types);
if(types.length || force) {
drops.push(new ChatDragAndDrop(dropsContainer, {
icon: 'dragfiles',
header: 'Drop files here to send them',
subtitle: 'without compression',
onDrop: (e: DragEvent) => {
toggle(e, false);
appImManager.log('drop', e);
appImManager.onDocumentPaste(e, 'document');
}
}));
}
if((foundMedia && !foundDocuments) || force) {
drops.push(new ChatDragAndDrop(dropsContainer, {
icon: 'dragmedia',
header: 'Drop files here to send them',
subtitle: 'in a quick way',
onDrop: (e: DragEvent) => {
toggle(e, false);
appImManager.log('drop', e);
appImManager.onDocumentPaste(e, 'media');
}
}));
}
this.chat.container.append(dropsContainer);
}
//if(!mount) return;
SetTransition(dropsContainer, 'is-visible', mount, 200, () => {
if(!mount) {
drops.forEach(drop => {
drop.destroy();
});
drops.length = 0;
}
});
if(mount) {
drops.forEach(drop => {
drop.setPath();
});
} else {
counter = 0;
}
document.body.classList.toggle('is-dragging', mount);
mounted = mount;
};
/* document.body.addEventListener('dragover', (e) => {
cancelEvent(e);
}); */
let counter = 0;
document.body.addEventListener('dragenter', (e) => {
counter++;
});
document.body.addEventListener('dragover', (e) => {
//this.log('dragover', e/* , e.dataTransfer.types[0] */);
toggle(e, true);
cancelEvent(e);
});
document.body.addEventListener('dragleave', (e) => {
//this.log('dragleave', e, counter);
//if((e.pageX <= 0 || e.pageX >= appPhotosManager.windowW) || (e.pageY <= 0 || e.pageY >= appPhotosManager.windowH)) {
counter--;
if(counter === 0) {
//if(!findUpClassName(e.target, 'drops-container')) {
toggle(e, false);
}
});
const dropsContainer = document.createElement('div');
dropsContainer.classList.add('drops-container');
}
private canDrag() {
const peerId = this.chat?.peerId;
return !(!peerId || rootScope.overlayIsActive || (peerId < 0 && !appChatsManager.hasRights(peerId, 'send', 'send_media')));
}
private onDocumentPaste = (e: ClipboardEvent | DragEvent, attachType?: 'media' | 'document') => {
if(!this.canDrag()) return;
//console.log('document paste');
//console.log('item', event.clipboardData.getData());
if(e instanceof DragEvent) {
const _types = e.dataTransfer.types;
// @ts-ignore
const isFiles = _types.contains ? _types.contains('Files') : _types.indexOf('Files') >= 0;
if(isFiles) {
cancelEvent(e);
}
}
getFilesFromEvent(e).then((files: File[]) => {
if(files.length) {
if(attachType === 'media' && files.find(file => !['image', 'video'].includes(file.type.split('/')[0]))) {
attachType = 'document';
}
const chatInput = this.chat.input;
chatInput.willAttachType = attachType || (files[0].type.indexOf('image/') === 0 ? 'media' : "document");
new PopupNewMedia(this.chat, files, chatInput.willAttachType);
}
});
};
public selectTab(id: number, animate?: boolean) {
if(animate === false) { // * will be used for Safari iOS history swipe
disableTransition([appSidebarLeft.sidebarEl, this.columnEl, appSidebarRight.sidebarEl]);
}
document.body.classList.toggle(LEFT_COLUMN_ACTIVE_CLASSNAME, id === 0);
const prevTabId = this.tabId;
this.log('selectTab', id, prevTabId);
let animationPromise: Promise<any> = doubleRaf();
if(prevTabId !== -1 && prevTabId !== id && rootScope.settings.animationsEnabled && animate !== false) {
const transitionTime = (mediaSizes.isMobile ? 250 : 200) + 100; // * cause transition time could be > 250ms
animationPromise = pause(transitionTime);
dispatchHeavyAnimationEvent(animationPromise, transitionTime);
this.columnEl.classList.add('disable-hover');
animationPromise.finally(() => {
this.columnEl.classList.remove('disable-hover');
});
}
this.tabId = id;
if(mediaSizes.isMobile && prevTabId === 2 && id < 2) {
document.body.classList.remove(RIGHT_COLUMN_ACTIVE_CLASSNAME);
}
if(prevTabId !== -1 && id > prevTabId && id < 2) {
appNavigationController.pushItem({
type: 'im',
onPop: (canAnimate) => {
//this.selectTab(prevTabId, !isSafari);
this.setPeer(0, undefined, canAnimate);
}
});
}
rootScope.broadcast('im_tab_change', id);
//this._selectTab(id, mediaSizes.isMobile);
//document.body.classList.toggle(RIGHT_COLUMN_ACTIVE_CLASSNAME, id === 2);
return animationPromise;
}
public updateStatus() {
if(!this.myId) return Promise.resolve();
appUsersManager.setUserStatus(this.myId, this.offline);
return apiManager.invokeApi('account.updateStatus', {offline: this.offline});
}
private createNewChat() {
const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager, appDraftsManager, serverTimeManager, sessionStorage);
if(this.chats.length) {
chat.backgroundEl.append(this.chat.backgroundEl.lastElementChild.cloneNode(true));
}
this.chats.push(chat);
}
private spliceChats(fromIndex: number, justReturn = true, animate?: boolean) {
if(fromIndex >= this.chats.length) return;
if(this.chats.length > 1 && justReturn) {
rootScope.broadcast('peer_changing', this.chat);
}
const spliced = this.chats.splice(fromIndex, this.chats.length - fromIndex);
// * fix middle chat z-index on animation
if(spliced.length > 1) {
spliced.slice(0, -1).forEach(chat => {
chat.container.remove();
});
}
this.chatsSelectTab(this.chat.container, animate);
if(justReturn) {
rootScope.broadcast('peer_changed', this.chat.peerId);
if(appSidebarRight.historyTabIds[appSidebarRight.historyTabIds.length - 1] === AppSidebarRight.SLIDERITEMSIDS.search) {
appSidebarRight.closeTab(AppSidebarRight.SLIDERITEMSIDS.search);
}
appSidebarRight.sharedMediaTab.setPeer(this.chat.peerId, this.chat.threadId);
appSidebarRight.sharedMediaTab.loadSidebarMedia(true);
appSidebarRight.sharedMediaTab.fillProfileElements();
/* setTimeout(() => {
appSidebarRight.sharedMediaTab.loadSidebarMedia(false);
}); */
}
setTimeout(() => {
//chat.setPeer(0);
spliced.forEach(chat => {
chat.destroy();
});
}, 250 + 100);
}
public setPeer(peerId: number, lastMsgId?: number, animate?: boolean): boolean {
if(this.init) {
this.init();
this.init = null;
}
const chat = this.chat;
const chatIndex = this.chats.indexOf(chat);
if(!peerId) {
if(chatIndex > 0) {
this.spliceChats(chatIndex, undefined, animate);
return;
} else if(mediaSizes.activeScreen === ScreenSize.medium) { // * floating sidebar case
this.selectTab(+!this.tabId, animate);
return;
}
} else if(chatIndex > 0 && chat.peerId && chat.peerId !== peerId) {
this.spliceChats(1, false, animate);
return this.setPeer(peerId, lastMsgId);
}
// * don't reset peer if returning
if(peerId === chat.peerId && mediaSizes.activeScreen <= ScreenSize.medium && document.body.classList.contains(LEFT_COLUMN_ACTIVE_CLASSNAME)) {
this.selectTab(1, animate);
return false;
}
if(peerId || mediaSizes.activeScreen !== ScreenSize.mobile) {
const result = chat.setPeer(peerId, lastMsgId);
// * wait for cached render
const promise = result?.cached ? result.promise : Promise.resolve();
if(peerId) {
promise.then(() => {
//window.requestAnimationFrame(() => {
setTimeout(() => { // * setTimeout is better here
setTimeout(() => {
this.chatsSelectTab(this.chat.container);
}, 0);
this.selectTab(1, animate);
}, 0);
});
}
}
if(!peerId) {
this.selectTab(0, animate);
return false;
}
}
public setInnerPeer(peerId: number, lastMsgId?: number, type: ChatType = 'chat', threadId?: number) {
// * prevent opening already opened peer
const existingIndex = this.chats.findIndex(chat => chat.peerId === peerId && chat.type === type);
if(existingIndex !== -1) {
this.spliceChats(existingIndex + 1);
return this.setPeer(peerId, lastMsgId);
}
const chat = this.chat;
if(!chat.init) { // * use first not inited chat
this.createNewChat();
}
if(type) {
this.chat.setType(type);
if(threadId) {
this.chat.threadId = threadId;
}
}
//this.chatsSelectTab(this.chat.container);
return this.setPeer(peerId, lastMsgId);
}
public openScheduled(peerId: number) {
this.setInnerPeer(peerId, undefined, 'scheduled');
}
public async getPeerStatus(peerId: number) {
let subtitle = '';
if(!peerId) return subtitle;
if(peerId < 0) { // not human
const chat = appPeersManager.getPeer(peerId);
const chatInfo = await appProfileManager.getChatFull(chat.id) as any;
this.chat.log('chatInfo res:', chatInfo);
const participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants && chatInfo.participants.participants.length) || 1;
//if(participants_count) {
subtitle = appChatsManager.getChatMembersString(-peerId);
if(participants_count < 2) return subtitle;
const onlines = await appChatsManager.getOnlines(chat.id);
if(onlines > 1) {
subtitle += ', ' + numberThousandSplitter(onlines, ' ') + ' online';
}
return subtitle;
//}
} else { // user
const user = appUsersManager.getUser(peerId);
if(rootScope.myId === peerId) {
return '';
} else if(user) {
subtitle = appUsersManager.getUserStatusString(user.id);
if(!appUsersManager.isBot(peerId)) {
const typings = appChatsManager.typingsInPeer[peerId];
if(typings && typings.length) {
return '<span class="online">typing...</span>';
} else if(subtitle === 'online') {
return `<span class="online">${subtitle}</span>`;
} else {
return subtitle;
}
} else {
return subtitle;
}
}
}
}
}
const appImManager = new AppImManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appImManager = appImManager);
export default appImManager;