diff --git a/src/components/horizontalMenu.ts b/src/components/horizontalMenu.ts index 983dd018..13aecc69 100644 --- a/src/components/horizontalMenu.ts +++ b/src/components/horizontalMenu.ts @@ -2,8 +2,6 @@ import { findUpTag, whichChild } from "../helpers/dom"; import Transition from "./transition"; export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) { - let prevId = -1; - const selectTab = Transition(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd); if(tabs) { @@ -34,7 +32,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? const tabContent = content.children[id] as HTMLDivElement; if(onClick) onClick(id, tabContent); - if(target.classList.contains('active') || id == prevId) { + if(target.classList.contains('active') || id == selectTab.prevId) { return false; } @@ -42,9 +40,9 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? prev && prev.classList.remove('active'); // stripe from ZINCHUK - if(useStripe && prevId != -1) { + if(useStripe && selectTab.prevId != -1) { const indicator = target.querySelector('i')!; - const currentIndicator = target.parentElement.children[prevId].querySelector('i')!; + const currentIndicator = target.parentElement.children[selectTab.prevId].querySelector('i')!; currentIndicator.classList.remove('animate'); indicator.classList.remove('animate'); @@ -66,8 +64,6 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? target.classList.add('active'); selectTab(id); - - prevId = id; }); } diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index 5ab26055..7ca4e189 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -10,7 +10,7 @@ import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config"; import $rootScope from "../../lib/rootScope"; import { findUpClassName, findUpTag } from "../../helpers/dom"; import AppSearch, { SearchGroup } from "../appSearch"; -import AvatarElement from "../avatar"; +import "../avatar"; import { parseMenuButtonsTo } from "../misc"; import { ScrollableX } from "../scrollable"; import SearchInput from "../searchInput"; @@ -26,8 +26,8 @@ import AppIncludedChatsTab from "./tabs/includedChats"; import AppNewChannelTab from "./tabs/newChannel"; import AppNewGroupTab from "./tabs/newGroup"; import AppSettingsTab from "./tabs/settings"; - -AvatarElement; +import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import apiManagerProxy from "../../lib/mtproto/mtprotoworker"; const newChannelTab = new AppNewChannelTab(); const addMembersTab = new AppAddMembersTab(); @@ -35,7 +35,6 @@ const contactsTab = new AppContactsTab(); const newGroupTab = new AppNewGroupTab(); const settingsTab = new AppSettingsTab(); const editProfileTab = new AppEditProfileTab(); -const chatFoldersTab = new AppChatFoldersTab(); const editFolderTab = new AppEditFolderTab(); const includedChatsTab = new AppIncludedChatsTab(); const archivedTab = new AppArchivedTab(); @@ -124,7 +123,9 @@ export class AppSidebarLeft extends SidebarSlider { private recentSearchClearBtn: HTMLElement; constructor() { - super(document.getElementById('column-left') as HTMLDivElement, { + super(document.getElementById('column-left') as HTMLDivElement); + + Object.assign(this.tabs, { [AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab, [AppSidebarLeft.SLIDERITEMSIDS.newChannel]: newChannelTab, [AppSidebarLeft.SLIDERITEMSIDS.contacts]: contactsTab, @@ -132,7 +133,7 @@ export class AppSidebarLeft extends SidebarSlider { [AppSidebarLeft.SLIDERITEMSIDS.newGroup]: newGroupTab, [AppSidebarLeft.SLIDERITEMSIDS.settings]: settingsTab, [AppSidebarLeft.SLIDERITEMSIDS.editProfile]: editProfileTab, - [AppSidebarLeft.SLIDERITEMSIDS.chatFolders]: chatFoldersTab, + [AppSidebarLeft.SLIDERITEMSIDS.chatFolders]: this.chatFoldersTab = new AppChatFoldersTab(appMessagesManager, appPeersManager, this, apiManagerProxy, $rootScope), [AppSidebarLeft.SLIDERITEMSIDS.editFolder]: editFolderTab, [AppSidebarLeft.SLIDERITEMSIDS.includedChats]: includedChatsTab, }); @@ -153,7 +154,6 @@ export class AppSidebarLeft extends SidebarSlider { this.newGroupTab = newGroupTab; this.settingsTab = settingsTab; this.editProfileTab = editProfileTab; - this.chatFoldersTab = chatFoldersTab; this.editFolderTab = editFolderTab; this.includedChatsTab = includedChatsTab; diff --git a/src/components/sidebarLeft/tabs/chatFolders.ts b/src/components/sidebarLeft/tabs/chatFolders.ts index d50ce129..51060e5a 100644 --- a/src/components/sidebarLeft/tabs/chatFolders.ts +++ b/src/components/sidebarLeft/tabs/chatFolders.ts @@ -1,15 +1,16 @@ import { SliderTab } from "../../slider"; import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; -import apiManager from "../../../lib/mtproto/mtprotoworker"; -import appMessagesManager, { MyDialogFilter } from "../../../lib/appManagers/appMessagesManager"; import { RichTextProcessor } from "../../../lib/richtextprocessor"; -import appPeersManager from "../../../lib/appManagers/appPeersManager"; -import { cancelEvent } from "../../../helpers/dom"; -import appSidebarLeft from ".."; +import { cancelEvent, positionElementByIndex } from "../../../helpers/dom"; import { ripple } from "../../ripple"; import { toast } from "../../toast"; -import { DialogFilterSuggested, DialogFilter } from "../../../layer"; -import $rootScope from "../../../lib/rootScope"; +import type { ApiManagerProxy } from "../../../lib/mtproto/mtprotoworker"; +import type { AppMessagesManager } from "../../../lib/appManagers/appMessagesManager"; +import type { MyDialogFilter } from "../../../lib/storages/filters"; +import type { AppPeersManager } from "../../../lib/appManagers/appPeersManager"; +import type { AppSidebarLeft } from ".."; +import type { DialogFilterSuggested, DialogFilter } from "../../../layer"; +import type _$rootScope from "../../../lib/rootScope"; export default class AppChatFoldersTab implements SliderTab { public container: HTMLElement; @@ -21,6 +22,10 @@ export default class AppChatFoldersTab implements SliderTab { private filtersRendered: {[filterID: number]: HTMLElement} = {}; + constructor(private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appSidebarLeft: AppSidebarLeft, private apiManager: ApiManagerProxy, private $rootScope: typeof _$rootScope) { + + } + private renderFolder(dialogFilter: DialogFilterSuggested | DialogFilter | MyDialogFilter, container?: HTMLElement, div: HTMLElement = document.createElement('div')) { let filter: DialogFilter | MyDialogFilter; let description = ''; @@ -35,7 +40,7 @@ export default class AppChatFoldersTab implements SliderTab { const filterID = filter.id; if(!this.filtersRendered.hasOwnProperty(filter.id)) { div.addEventListener('click', () => { - appSidebarLeft.editFolderTab.open(appMessagesManager.filtersStorage.filters[filterID]); + this.appSidebarLeft.editFolderTab.open(this.appMessagesManager.filtersStorage.filters[filterID]); }); } @@ -60,11 +65,11 @@ export default class AppChatFoldersTab implements SliderTab { else if(pFlags.exclude_archived) description += 'Unarchived'; d.push(description); } else { - const folder = appMessagesManager.dialogsStorage.getFolder(filter.id); + const folder = this.appMessagesManager.dialogsStorage.getFolder(filter.id); let chats = 0, channels = 0, groups = 0; for(const dialog of folder) { - if(appPeersManager.isAnyGroup(dialog.peerID)) groups++; - else if(appPeersManager.isBroadcast(dialog.peerID)) channels++; + if(this.appPeersManager.isAnyGroup(dialog.peerID)) groups++; + else if(this.appPeersManager.isBroadcast(dialog.peerID)) channels++; else chats++; } @@ -83,7 +88,11 @@ export default class AppChatFoldersTab implements SliderTab { `; ripple(div); - if(container) container.append(div); + if((filter as MyDialogFilter).hasOwnProperty('orderIndex')) { + // ! header will be at 0 index + positionElementByIndex(div, div.parentElement || container, (filter as MyDialogFilter).orderIndex); + } else if(container) container.append(div); + return div; } @@ -98,7 +107,7 @@ export default class AppChatFoldersTab implements SliderTab { if(Object.keys(this.filtersRendered).length >= 10) { toast('Sorry, you can\'t create more folders.'); } else { - appSidebarLeft.editFolderTab.open(); + this.appSidebarLeft.editFolderTab.open(); } }); @@ -112,14 +121,13 @@ export default class AppChatFoldersTab implements SliderTab { this.animation = player; }); - appMessagesManager.filtersStorage.getDialogFilters().then(filters => { - for(const filterID in filters) { - const filter = filters[filterID]; + this.appMessagesManager.filtersStorage.getDialogFilters().then(filters => { + for(const filter of filters) { this.renderFolder(filter, this.foldersContainer); } }); - $rootScope.$on('filter_update', (e) => { + this.$rootScope.$on('filter_update', (e) => { const filter = e.detail; if(this.filtersRendered.hasOwnProperty(filter.id)) { this.renderFolder(filter, null, this.filtersRendered[filter.id]); @@ -130,7 +138,7 @@ export default class AppChatFoldersTab implements SliderTab { this.getSuggestedFilters(); }); - $rootScope.$on('filter_delete', (e) => { + this.$rootScope.$on('filter_delete', (e) => { const filter = e.detail; if(this.filtersRendered.hasOwnProperty(filter.id)) { /* for(const suggested of this.suggestedFilters) { @@ -145,11 +153,19 @@ export default class AppChatFoldersTab implements SliderTab { } }); + this.$rootScope.$on('filter_order', (e) => { + const order = e.detail; + order.forEach((filterID, idx) => { + const div = this.filtersRendered[filterID]; + positionElementByIndex(div, div.parentElement, idx + 1); // ! + 1 due to header + }); + }); + this.getSuggestedFilters(); } private getSuggestedFilters() { - apiManager.invokeApi('messages.getSuggestedDialogFilters').then(suggestedFilters => { + this.apiManager.invokeApi('messages.getSuggestedDialogFilters').then(suggestedFilters => { this.suggestedContainer.style.display = suggestedFilters.length ? '' : 'none'; Array.from(this.suggestedContainer.children).slice(1).forEach(el => el.remove()); @@ -171,7 +187,7 @@ export default class AppChatFoldersTab implements SliderTab { button.setAttribute('disabled', 'true'); - appMessagesManager.filtersStorage.createDialogFilter(filter.filter as any).then(bool => { + this.appMessagesManager.filtersStorage.createDialogFilter(filter.filter as any).then(bool => { if(bool) { div.remove(); } diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts index 15dc7212..c0889e3b 100644 --- a/src/components/sidebarLeft/tabs/editFolder.ts +++ b/src/components/sidebarLeft/tabs/editFolder.ts @@ -1,12 +1,13 @@ import appSidebarLeft, { AppSidebarLeft } from ".."; import { deepEqual, copy } from "../../../helpers/object"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; -import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager"; +import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters"; import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; import { parseMenuButtonsTo } from "../../misc"; import { ripple } from "../../ripple"; import { SliderTab } from "../../slider"; import { toast } from "../../toast"; +import appMessagesManager from "../../../lib/appManagers/appMessagesManager"; const MAX_FOLDER_NAME_LENGTH = 12; diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts index 5dd0c216..021743b7 100644 --- a/src/components/sidebarLeft/tabs/includedChats.ts +++ b/src/components/sidebarLeft/tabs/includedChats.ts @@ -4,7 +4,7 @@ import appSidebarLeft, { AppSidebarLeft } from ".."; import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; import appPeersManager from "../../../lib/appManagers/appPeersManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager"; -import { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager"; +import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters"; import $rootScope from "../../../lib/rootScope"; import { copy } from "../../../helpers/object"; diff --git a/src/components/slider.ts b/src/components/slider.ts index b421fc2f..4ef3e5ad 100644 --- a/src/components/slider.ts +++ b/src/components/slider.ts @@ -13,7 +13,7 @@ export default class SidebarSlider { protected _selectTab: (id: number) => void; public historyTabIDs: number[] = []; - constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab}, canHideFirst = false) { + constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, canHideFirst = false) { this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, TRANSITION_TIME); if(!canHideFirst) { this._selectTab(0); diff --git a/src/components/transition.ts b/src/components/transition.ts index b7c6b386..05d15039 100644 --- a/src/components/transition.ts +++ b/src/components/transition.ts @@ -31,12 +31,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa //const deferred: (() => void)[] = []; let transitionEndTimeout: number; let prevTabContent: HTMLElement = null; - let prevId = -1; const animationFunction = type == 'zoom-fade' ? null : (type == 'tabs' ? slideTabs : slideNavigation); - const selectTab = (id: number, animate = true) => { - if(id == prevId) return false; + function selectTab(id: number, animate = true) { + const self = selectTab; + if(id == self.prevId) return false; //console.log('selectTab id:', id); @@ -51,10 +51,10 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa tabContent.classList.add('active'); - prevId = id; + self.prevId = id; prevTabContent = tabContent; - if(onTransitionEnd) onTransitionEnd(prevId); + if(onTransitionEnd) onTransitionEnd(self.prevId); return; } @@ -64,12 +64,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa } content.classList.add('animating'); - const toRight = prevId < id; + const toRight = self.prevId < id; content.classList.toggle('backwards', !toRight); if(!tabContent) { //prevTabContent.classList.remove('active'); - } else if(prevId != -1) { + } else if(self.prevId != -1) { if(animationFunction) { animationFunction(tabContent, prevTabContent, toRight); } @@ -79,7 +79,7 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa tabContent.classList.add('active'); } - const _prevId = prevId; + const _prevId = self.prevId; if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]); if(p/* && false */) { hideTimeouts[_prevId] = window.setTimeout(() => { @@ -99,13 +99,13 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa if(onTransitionEnd) { if(transitionEndTimeout) clearTimeout(transitionEndTimeout); transitionEndTimeout = window.setTimeout(() => { - onTransitionEnd(prevId); + onTransitionEnd(self.prevId); transitionEndTimeout = 0; }, transitionTime); } } - prevId = id; + self.prevId = id; prevTabContent = tabContent; /* if(p) { @@ -115,7 +115,9 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa } else { return Promise.resolve(); } */ - }; + } + + selectTab.prevId = -1; return selectTab; }; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 871482ae..f2db12ef 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -15,7 +15,8 @@ import { RichTextProcessor } from "../richtextprocessor"; import $rootScope from "../rootScope"; import { findUpClassName, positionElementByIndex } from "../../helpers/dom"; import appImManager, { AppImManager } from "./appImManager"; -import appMessagesManager, { Dialog, MyDialogFilter as DialogFilter } from "./appMessagesManager"; +import appMessagesManager, { Dialog } from "./appMessagesManager"; +import {MyDialogFilter as DialogFilter} from "../storages/filters"; import appPeersManager from './appPeersManager'; import appStateManager from "./appStateManager"; import appUsersManager, { User } from "./appUsersManager"; @@ -35,14 +36,11 @@ type DialogDom = { }; const testScroll = false; -//const USEPINNEDDELIMITER = false; export class AppDialogsManager { public _chatList = document.getElementById('dialogs') as HTMLUListElement; public chatList = this._chatList; - - //public pinnedDelimiter: HTMLDivElement; - + public doms: {[peerID: number]: DialogDom} = {}; public lastActiveListElement: HTMLElement = null; @@ -91,12 +89,6 @@ export class AppDialogsManager { this.allUnreadCount = this.folders.menu.querySelector('.unread-count'); - /* if(USEPINNEDDELIMITER) { - this.pinnedDelimiter = document.createElement('div'); - this.pinnedDelimiter.classList.add('pinned-delimiter'); - this.pinnedDelimiter.appendChild(document.createElement('span')); - } */ - this.folders.menuScrollContainer = this.folders.menu.parentElement; this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'CL', this.chatList, 500); @@ -165,18 +157,17 @@ export class AppDialogsManager { }); $rootScope.$on('dialog_top', (e) => { - let dialog: any = e.detail; + const dialog = e.detail; this.setLastMessage(dialog); this.setDialogPosition(dialog); - //this.setPinnedDelimiter(); this.setFiltersUnreadCount(); }); $rootScope.$on('dialog_flush', (e) => { - let peerID: number = e.detail.peerID; - let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; + const peerID: number = e.detail.peerID; + const dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; if(dialog) { this.setLastMessage(dialog); this.validateForFilter(); @@ -192,7 +183,6 @@ export class AppDialogsManager { this.updateDialog(dialog); } - //this.setPinnedDelimiter(); this.validateForFilter(); this.setFiltersUnreadCount(); }); @@ -291,16 +281,23 @@ export class AppDialogsManager { } }); - /* $rootScope.$on('filter_pinned_order', (e) => { - const {order, id} = e.detail as {order: number[], id: number}; - if(this.prevTabID != id) { - return; - } + $rootScope.$on('filter_order', (e) => { + const order = e.detail; + + const containerToAppend = this.folders.menu.firstElementChild as HTMLUListElement; + order.forEach((filterID) => { + const filter = appMessagesManager.filtersStorage.filters[filterID]; + const renderedFilter = this.filtersRendered[filterID]; + + positionElementByIndex(renderedFilter.menu, containerToAppend, filter.orderIndex); + positionElementByIndex(renderedFilter.container, this.folders.container, filter.orderIndex); + }); - for(const peerID of order) { - this.updateDialog(appMessagesManager.getDialogByPeerID(peerID)[0]); + if(this.filterID) { + const tabIndex = order.indexOf(this.filterID) + 1; + selectTab.prevId = tabIndex; } - }); */ + }); const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer); this.chatsContainer.prepend(this.folders.menuScrollContainer); @@ -330,10 +327,10 @@ export class AppDialogsManager { //selectTab(0); (this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click(); appStateManager.getState().then((state) => { - const getFiltersPromise = !state.filters || Object.keys(state.filters).length ? appMessagesManager.filtersStorage.getDialogFilters() : Promise.resolve({}); + const getFiltersPromise = !state.filters || Object.keys(state.filters).length ? appMessagesManager.filtersStorage.getDialogFilters() : Promise.resolve([]); getFiltersPromise.then((filters) => { - for(const filterID in filters) { - this.addFilter(filters[filterID]); + for(const filter of filters) { + this.addFilter(filter); } }); @@ -433,7 +430,7 @@ export class AppDialogsManager { ripple(li); const containerToAppend = this.folders.menu.firstElementChild as HTMLUListElement; - positionElementByIndex(li, containerToAppend, filter.orderIndex + 1); // because 0 is All + positionElementByIndex(li, containerToAppend, filter.orderIndex); //containerToAppend.append(li); const ul = document.createElement('ul'); @@ -441,7 +438,7 @@ export class AppDialogsManager { div.append(ul); div.dataset.filterID = '' + filter.id; //this.folders.container.append(div); - positionElementByIndex(div, this.folders.container, filter.orderIndex + 1); // because 0 is All + positionElementByIndex(div, this.folders.container, filter.orderIndex); this.chatLists[filter.id] = ul; this.setListClickListener(ul, null, true); @@ -608,42 +605,6 @@ export class AppDialogsManager { } } - /* public setPinnedDelimiter() { - if(!USEPINNEDDELIMITER) return; - - let index = -1; - let dialogs = appMessagesManager.dialogsStorage.getFolder(0); - for(let dialog of dialogs) { - if(dialog.pFlags?.pinned) { - index++; - } - } - - let currentIndex = (this.pinnedDelimiter.parentElement && whichChild(this.pinnedDelimiter.parentElement)) ?? -1; - - if(index == currentIndex) return; - - let children = this.chatList.children; - - let modifying: HTMLElement[] = []; - if(currentIndex != -1 && children.length > currentIndex) { - let li = children[currentIndex] as HTMLElement; - modifying.push(li); - } - - if(index != -1 && children.length > index) { - let li = children[index] as HTMLElement; - modifying.push(li); - li.append(this.pinnedDelimiter); - } else { - this.pinnedDelimiter.remove(); - } - - modifying.forEach(elem => { - this.scroll.updateElement(elem); - }); - } */ - public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) { if(!lastMessage) { lastMessage = appMessagesManager.getMessage(dialog.top_message); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 2995f2a6..4fdf33fd 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1,4 +1,3 @@ -import { MessageRender } from "../../components/chat/messageRender"; import ProgressivePreloader from "../../components/preloader"; import { listMergeSorted } from "../../helpers/array"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; @@ -6,19 +5,21 @@ import { tsNow } from "../../helpers/date"; import { copy, defineNotNumerableProperties, deepEqual, safeReplaceObject, getObjectKeysAndSort } from "../../helpers/object"; import { randomLong } from "../../helpers/random"; import { splitStringByLength, limitSymbols } from "../../helpers/string"; -import { Dialog as MTDialog, DialogFilter, DialogPeer, DocumentAttribute, InputMessage, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, PhotoSize, SendMessageAction, Update } from "../../layer"; -import { InvokeApiOptions, Modify } from "../../types"; +import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMessage, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, PhotoSize, SendMessageAction, Update } from "../../layer"; +import { InvokeApiOptions } from "../../types"; import { langPack } from "../langPack"; import { logger, LogLevels } from "../logger"; import type { ApiFileManager } from '../mtproto/apiFileManager'; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase"; -import { default as ServerTimeManager, default as serverTimeManager } from "../mtproto/serverTimeManager"; +import serverTimeManager from "../mtproto/serverTimeManager"; import { RichTextProcessor } from "../richtextprocessor"; import $rootScope from "../rootScope"; import searchIndexManager from '../searchIndexManager'; import AppStorage from '../storage'; +import DialogsStorage from "../storages/dialogs"; +import FiltersStorage from "../storages/filters"; //import { telegramMeWebService } from "../mtproto/mtproto"; import apiUpdatesManager from "./apiUpdatesManager"; import appChatsManager from "./appChatsManager"; @@ -32,7 +33,6 @@ import appStateManager from "./appStateManager"; import appUsersManager from "./appUsersManager"; import appWebPagesManager from "./appWebPagesManager"; - //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет // TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках @@ -59,390 +59,7 @@ export type HistoryResult = { export type Dialog = MTDialog.dialog; -export class DialogsStorage { - public dialogs: {[peerID: string]: Dialog} = {}; - public byFolders: {[folderID: number]: Dialog[]} = {}; - - public allDialogsLoaded: {[folder_id: number]: boolean} = {}; - public dialogsOffsetDate: {[folder_id: number]: number} = {}; - public pinnedOrders: {[folder_id: number]: number[]} = { - 0: [], - 1: [] - }; - public dialogsNum = 0; - - public getFolder(id: number) { - if(id <= 1) { - return this.byFolders[id] ?? (this.byFolders[id] = []); - } - - const dialogs: {dialog: Dialog, index: number}[] = []; - const filter = appMessagesManager.filtersStorage.filters[id]; - - for(const peerID in this.dialogs) { - const dialog = this.dialogs[peerID]; - if(appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) { - let index: number; - - const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerID); - if(pinnedIndex !== -1) { - index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex)); - } else if(dialog.pFlags?.pinned) { - index = this.generateIndexForDialog(dialog, true); - } else { - index = dialog.index; - } - - dialogs.push({dialog, index}); - } - } - - dialogs.sort((a, b) => b.index - a.index); - return dialogs.map(d => d.dialog); - } - - public getDialog(peerID: number, folderID?: number): [Dialog, number] | [] { - const folders: Dialog[][] = []; - - if(folderID === undefined) { - const dialogs = this.byFolders; - for(const folderID in dialogs) { - folders.push(dialogs[folderID]); - } - } else { - folders.push(this.getFolder(folderID)); - } - - for(let folder of folders) { - const index = folder.findIndex(dialog => dialog.peerID == peerID); - if(index !== -1) { - return [folder[index], index]; - } - } - - return []; - } - - /* - var date = Date.now() / 1000 | 0; - var m = date * 0x10000; - - var k = (date + 1) * 0x10000; - k - m; - 65536 - */ - public generateDialogIndex(date?: number) { - if(date === undefined) { - date = tsNow(true) + serverTimeManager.serverTimeOffset; - } - - return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF); - } - - public generateIndexForDialog(dialog: Dialog, justReturn = false) { - const channelID = appPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0; - const mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID); - const message = appMessagesManager.getMessage(mid); - - let topDate = (message as Message.message).date || Date.now() / 1000; - if(channelID) { - const channel = appChatsManager.getChat(channelID); - if(!topDate || channel.date && channel.date > topDate) { - topDate = channel.date; - } - } - - const savedDraft: any = {};// DraftsManager.saveDraft(peerID, dialog.draft); // warning - if(savedDraft && savedDraft.date > topDate) { - topDate = savedDraft.date; - } - - if(dialog.pFlags.pinned && !justReturn) { - topDate = this.generateDialogPinnedDate(dialog); - //this.log('topDate', peerID, topDate); - } - - const index = this.generateDialogIndex(topDate); - if(justReturn) return index; - dialog.index = index; - } - - public generateDialogPinnedDateByIndex(pinnedIndex: number) { - return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов - } - - public generateDialogPinnedDate(dialog: Dialog) { - const order = this.pinnedOrders[dialog.folder_id]; - - const foundIndex = order.indexOf(dialog.peerID); - const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerID) - 1 : foundIndex; - - return this.generateDialogPinnedDateByIndex(pinnedIndex); - } - - public pushDialog(dialog: Dialog, offsetDate?: number) { - const dialogs = this.getFolder(dialog.folder_id); - const pos = dialogs.findIndex(d => d.peerID == dialog.peerID); - if(pos !== -1) { - dialogs.splice(pos, 1); - } - - //if(!this.dialogs[dialog.peerID]) { - this.dialogs[dialog.peerID] = dialog; - //} - - if(offsetDate && - !dialog.pFlags.pinned && - (!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { - if(pos !== -1) { - // So the dialog jumped to the last position - return false; - } - this.dialogsOffsetDate[dialog.folder_id] = offsetDate; - } - - const index = dialog.index; - const len = dialogs.length; - if(!len || index < dialogs[len - 1].index) { - dialogs.push(dialog); - } else if(index >= dialogs[0].index) { - dialogs.unshift(dialog); - } else { - for(let i = 0; i < len; i++) { - if(index > dialogs[i].index) { - dialogs.splice(i, 0, dialog); - break; - } - } - } - } - - public dropDialog(peerID: number): [Dialog, number] | [] { - const foundDialog = this.getDialog(peerID); - if(foundDialog[0]) { - this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1); - delete this.dialogs[peerID]; - } - - return foundDialog; - } -} - -export type MyDialogFilter = Modify; - -export class FiltersStorage { - public filters: {[filterID: string]: MyDialogFilter} = {}; - public orderIndex = 0; - - constructor() { - $rootScope.$on('apiUpdate', (e) => { - this.handleUpdate(e.detail); - }); - } - - public handleUpdate(update: Update) { - switch(update._) { - case 'updateDialogFilter': { - //console.log('updateDialogFilter', update); - if(update.filter) { - this.saveDialogFilter(update.filter as any); - } else if(this.filters[update.id]) { // Папка удалена - //this.getDialogFilters(true); - $rootScope.$broadcast('filter_delete', this.filters[update.id]); - delete this.filters[update.id]; - } - - break; - } - } - } - - public testDialogForFilter(dialog: Dialog, filter: MyDialogFilter) { - // exclude_peers - for(const peerID of filter.exclude_peers) { - if(peerID == dialog.peerID) { - return false; - } - } - - // include_peers - for(const peerID of filter.include_peers) { - if(peerID == dialog.peerID) { - return true; - } - } - - const pFlags = filter.pFlags; - - // exclude_archived - if(pFlags.exclude_archived && dialog.folder_id == 1) { - return false; - } - - // exclude_read - if(pFlags.exclude_read && !dialog.unread_count) { - return false; - } - - // exclude_muted - if(pFlags.exclude_muted) { - const isMuted = (dialog.notify_settings?.mute_until * 1000) > Date.now(); - if(isMuted) { - return false; - } - } - - const peerID = dialog.peerID; - if(peerID < 0) { - // broadcasts - if(pFlags.broadcasts && appPeersManager.isBroadcast(peerID)) { - return true; - } - - // groups - if(pFlags.groups && appPeersManager.isAnyGroup(peerID)) { - return true; - } - } else { - // bots - if(appPeersManager.isBot(peerID)) { - return !!pFlags.bots; - } - - // non_contacts - if(pFlags.non_contacts && !appUsersManager.contactsList.has(peerID)) { - return true; - } - - // contacts - if(pFlags.contacts && appUsersManager.contactsList.has(peerID)) { - return true; - } - } - - return false; - } - - public toggleDialogPin(peerID: number, filterID: number) { - const filter = this.filters[filterID]; - - const wasPinned = filter.pinned_peers.findAndSplice(p => p == peerID); - if(!wasPinned) { - filter.pinned_peers.unshift(peerID); - } - - return this.updateDialogFilter(filter); - } - - public createDialogFilter(filter: MyDialogFilter) { - let maxID = Math.max(1, ...Object.keys(this.filters).map(i => +i)); - filter = copy(filter); - filter.id = maxID + 1; - return this.updateDialogFilter(filter); - } - - public updateDialogFilter(filter: MyDialogFilter, remove = false) { - const flags = remove ? 0 : 1; - - return apiManager.invokeApi('messages.updateDialogFilter', { - flags, - id: filter.id, - filter: remove ? undefined : this.getOutputDialogFilter(filter) - }).then((bool: boolean) => { // возможно нужна проверка и откат, если результат не ТРУ - //console.log('updateDialogFilter bool:', bool); - - if(bool) { - /* if(!this.filters[filter.id]) { - this.saveDialogFilter(filter); - } - - $rootScope.$broadcast('filter_update', filter); */ - - this.handleUpdate({ - _: 'updateDialogFilter', - id: filter.id, - filter: remove ? undefined : filter as any - }); - } - - return bool; - }); - } - - public getOutputDialogFilter(filter: MyDialogFilter) { - const c: MyDialogFilter = copy(filter); - ['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => { - // @ts-ignore - c[key] = c[key].map((peerID: number) => appPeersManager.getInputPeerByID(peerID)); - }); - - c.include_peers.forEachReverse((peerID, idx) => { - if(c.pinned_peers.includes(peerID)) { - c.include_peers.splice(idx, 1); - } - }); - - return c as any as DialogFilter; - } - - public async getDialogFilters(overwrite = false) { - if(Object.keys(this.filters).length && !overwrite) { - return this.filters; - } - - const filters = await apiManager.invokeApi('messages.getDialogFilters'); - for(const filter of filters) { - this.saveDialogFilter(filter as any as MyDialogFilter, false); - } - - //console.log(this.filters); - return this.filters; - } - - public saveDialogFilter(filter: MyDialogFilter, update = true) { - ['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => { - // @ts-ignore - filter[key] = filter[key].map((peer: any) => appPeersManager.getPeerID(peer)); - }); - - filter.include_peers.forEachReverse((peerID, idx) => { - if(filter.pinned_peers.includes(peerID)) { - filter.include_peers.splice(idx, 1); - } - }); - - filter.include_peers = filter.pinned_peers.concat(filter.include_peers); - - if(this.filters[filter.id]) { - Object.assign(this.filters[filter.id], filter); - } else { - this.filters[filter.id] = filter; - } - - this.setOrderIndex(filter); - - if(update) { - $rootScope.$broadcast('filter_update', filter); - } - } - - public setOrderIndex(filter: MyDialogFilter) { - if(filter.hasOwnProperty('orderIndex')) { - if(filter.orderIndex > this.orderIndex) { - this.orderIndex = filter.orderIndex; - } - } else { - filter.orderIndex = this.orderIndex++; - } - } -} - -type MyMessage = Message.message | Message.messageService; +export type MyMessage = Message.message | Message.messageService; type MyInputMessagesFilter = 'inputMessagesFilterEmpty' | 'inputMessagesFilterPhotos' | 'inputMessagesFilterPhotoVideo' @@ -514,10 +131,13 @@ export class AppMessagesManager { private log = logger('MESSAGES'/* , LogLevels.error | LogLevels.debug | LogLevels.log | LogLevels.warn */); - public dialogsStorage = new DialogsStorage(); - public filtersStorage = new FiltersStorage(); + public dialogsStorage: DialogsStorage; + public filtersStorage: FiltersStorage; constructor() { + this.dialogsStorage = new DialogsStorage(this, appMessagesIDsManager, appChatsManager, appPeersManager, serverTimeManager); + this.filtersStorage = new FiltersStorage(appPeersManager, appUsersManager, /* apiManager, */ $rootScope); + $rootScope.$on('apiUpdate', (e) => { this.handleUpdate(e.detail); }); @@ -970,7 +590,7 @@ export class AppMessagesManager { const isDocument = !(file instanceof File) && !(file instanceof Blob); let caption = options.caption || ''; - const date = tsNow(true) + ServerTimeManager.serverTimeOffset; + const date = tsNow(true) + serverTimeManager.serverTimeOffset; this.log('sendFile', file, fileType); @@ -1321,7 +941,7 @@ export class AppMessagesManager { let caption = options.caption || ''; - let date = tsNow(true) + ServerTimeManager.serverTimeOffset; + let date = tsNow(true) + serverTimeManager.serverTimeOffset; if(caption) { let entities = options.entities || []; @@ -1707,7 +1327,7 @@ export class AppMessagesManager { from_id: appPeersManager.getOutputPeer(fromID), peer_id: appPeersManager.getOutputPeer(peerID), pFlags: pFlags, - date: tsNow(true) + ServerTimeManager.serverTimeOffset, + date: tsNow(true) + serverTimeManager.serverTimeOffset, message: '', media: media, random_id: randomIDS, @@ -3193,7 +2813,7 @@ export class AppMessagesManager { var offsetMessage = maxID && this.getMessage(maxID); if(offsetMessage && offsetMessage.date) { - offsetDate = offsetMessage.date + ServerTimeManager.serverTimeOffset; + offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset; offsetID = offsetMessage.id; offsetPeerID = this.getMessagePeer(offsetMessage); } @@ -3484,7 +3104,7 @@ export class AppMessagesManager { } public handleUpdate(update: Update) { - this.log.debug('AMM: handleUpdate:', update._); + this.log.debug('handleUpdate', update._); switch(update._) { case 'updateMessageID': { const randomID = update.random_id; diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts index 74f17110..7582e6b8 100644 --- a/src/lib/appManagers/appStateManager.ts +++ b/src/lib/appManagers/appStateManager.ts @@ -1,4 +1,4 @@ -import type { Dialog, DialogsStorage, FiltersStorage } from './appMessagesManager'; +import type { Dialog } from './appMessagesManager'; import type { AppStickersManager } from './appStickersManager'; import { App, MOUNT_CLASS_TO, UserAuth } from '../mtproto/mtproto_config'; import EventListenerBase from '../../helpers/eventListenerBase'; @@ -9,6 +9,8 @@ import type { AppUsersManager } from './appUsersManager'; import type { AppChatsManager } from './appChatsManager'; import type { AuthState } from '../../types'; import type { AppMessagesIDsManager } from './appMessagesIDsManager'; +import type FiltersStorage from '../storages/filters'; +import type DialogsStorage from '../storages/dialogs'; const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day const STATE_VERSION = App.version; diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index c5aa46da..ea7e37a9 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -24,6 +24,7 @@ export class AppUsersManager { public contactsIndex = searchIndexManager.createIndex(); public contactsFillPromise: Promise>; public contactsList: Set = new Set(); + private updatedContactsList = false; public getTopPeersPromise: Promise; @@ -107,10 +108,11 @@ export class AppUsersManager { appStateManager.getState().then((state) => { const contactsList = state.contactsList; - if(contactsList && Array.isArray(contactsList) && contactsList.length) { + if(contactsList && Array.isArray(contactsList)) { contactsList.forEach(userID => { this.pushContact(userID); }); + this.contactsFillPromise = Promise.resolve(this.contactsList); } @@ -119,13 +121,13 @@ export class AppUsersManager { } public fillContacts() { - if(this.contactsFillPromise) { + if(this.contactsFillPromise && this.updatedContactsList) { return this.contactsFillPromise; } - return this.contactsFillPromise = apiManager.invokeApi('contacts.getContacts', { - hash: 0 - }).then((result) => { + this.updatedContactsList = true; + + const promise = apiManager.invokeApi('contacts.getContacts').then((result) => { if(result._ == 'contacts.contacts') { this.saveApiUsers(result.users); @@ -134,8 +136,12 @@ export class AppUsersManager { }); } + this.contactsFillPromise = promise; + return this.contactsList; }); + + return this.contactsFillPromise || (this.contactsFillPromise = promise); } public async resolveUsername(username: string) { diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 7a412907..5ab6d079 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -22,7 +22,7 @@ type Task = { const USEWORKERASWORKER = true; -class ApiManagerProxy extends CryptoWorkerMethods { +export class ApiManagerProxy extends CryptoWorkerMethods { public worker: Worker; public postMessage: (...args: any[]) => void; private afterMessageIDTemp = 0; diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 127e5535..aaec3b82 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -1,7 +1,8 @@ import type { StickerSet, Update } from "../layer"; import type { MyDocument } from "./appManagers/appDocsManager"; -import type { AppMessagesManager, Dialog, MyDialogFilter } from "./appManagers/appMessagesManager"; +import type { AppMessagesManager, Dialog } from "./appManagers/appMessagesManager"; import type { Poll, PollResults } from "./appManagers/appPollsManager"; +import type { MyDialogFilter } from "./storages/filters"; import { MOUNT_CLASS_TO } from "./mtproto/mtproto_config"; type BroadcastEvents = { @@ -12,6 +13,7 @@ type BroadcastEvents = { 'filter_delete': MyDialogFilter, 'filter_update': MyDialogFilter, + 'filter_order': number[], 'dialog_draft': {peerID: number, draft: any, index: number}, 'dialog_unread': {peerID: number, count?: number}, diff --git a/src/lib/storages/dialogs.ts b/src/lib/storages/dialogs.ts new file mode 100644 index 00000000..706bd3b7 --- /dev/null +++ b/src/lib/storages/dialogs.ts @@ -0,0 +1,180 @@ +import { tsNow } from "../../helpers/date"; +import type { Message } from "../../layer"; +import type { AppChatsManager } from "../appManagers/appChatsManager"; +import type { AppMessagesIDsManager } from "../appManagers/appMessagesIDsManager"; +import type { AppMessagesManager, Dialog } from "../appManagers/appMessagesManager"; +import type { AppPeersManager } from "../appManagers/appPeersManager"; +import type { ServerTimeManager } from "../mtproto/serverTimeManager"; + +export default class DialogsStorage { + public dialogs: {[peerID: string]: Dialog} = {}; + public byFolders: {[folderID: number]: Dialog[]} = {}; + + public allDialogsLoaded: {[folder_id: number]: boolean} = {}; + public dialogsOffsetDate: {[folder_id: number]: number} = {}; + public pinnedOrders: {[folder_id: number]: number[]} = { + 0: [], + 1: [] + }; + public dialogsNum = 0; + + constructor(private appMessagesManager: AppMessagesManager, private appMessagesIDsManager: AppMessagesIDsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private serverTimeManager: ServerTimeManager) { + + } + + public getFolder(id: number) { + if(id <= 1) { + return this.byFolders[id] ?? (this.byFolders[id] = []); + } + + const dialogs: {dialog: Dialog, index: number}[] = []; + const filter = this.appMessagesManager.filtersStorage.filters[id]; + + for(const peerID in this.dialogs) { + const dialog = this.dialogs[peerID]; + if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) { + let index: number; + + const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerID); + if(pinnedIndex !== -1) { + index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex)); + } else if(dialog.pFlags?.pinned) { + index = this.generateIndexForDialog(dialog, true); + } else { + index = dialog.index; + } + + dialogs.push({dialog, index}); + } + } + + dialogs.sort((a, b) => b.index - a.index); + return dialogs.map(d => d.dialog); + } + + public getDialog(peerID: number, folderID?: number): [Dialog, number] | [] { + const folders: Dialog[][] = []; + + if(folderID === undefined) { + const dialogs = this.byFolders; + for(const folderID in dialogs) { + folders.push(dialogs[folderID]); + } + } else { + folders.push(this.getFolder(folderID)); + } + + for(let folder of folders) { + const index = folder.findIndex(dialog => dialog.peerID == peerID); + if(index !== -1) { + return [folder[index], index]; + } + } + + return []; + } + + /* + var date = Date.now() / 1000 | 0; + var m = date * 0x10000; + + var k = (date + 1) * 0x10000; + k - m; + 65536 + */ + public generateDialogIndex(date?: number) { + if(date === undefined) { + date = tsNow(true) + this.serverTimeManager.serverTimeOffset; + } + + return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF); + } + + public generateIndexForDialog(dialog: Dialog, justReturn = false) { + const channelID = this.appPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0; + const mid = this.appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID); + const message = this.appMessagesManager.getMessage(mid); + + let topDate = (message as Message.message).date || Date.now() / 1000; + if(channelID) { + const channel = this.appChatsManager.getChat(channelID); + if(!topDate || channel.date && channel.date > topDate) { + topDate = channel.date; + } + } + + const savedDraft: any = {};// DraftsManager.saveDraft(peerID, dialog.draft); // warning + if(savedDraft && savedDraft.date > topDate) { + topDate = savedDraft.date; + } + + if(dialog.pFlags.pinned && !justReturn) { + topDate = this.generateDialogPinnedDate(dialog); + //this.log('topDate', peerID, topDate); + } + + const index = this.generateDialogIndex(topDate); + if(justReturn) return index; + dialog.index = index; + } + + public generateDialogPinnedDateByIndex(pinnedIndex: number) { + return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов + } + + public generateDialogPinnedDate(dialog: Dialog) { + const order = this.pinnedOrders[dialog.folder_id]; + + const foundIndex = order.indexOf(dialog.peerID); + const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerID) - 1 : foundIndex; + + return this.generateDialogPinnedDateByIndex(pinnedIndex); + } + + public pushDialog(dialog: Dialog, offsetDate?: number) { + const dialogs = this.getFolder(dialog.folder_id); + const pos = dialogs.findIndex(d => d.peerID == dialog.peerID); + if(pos !== -1) { + dialogs.splice(pos, 1); + } + + //if(!this.dialogs[dialog.peerID]) { + this.dialogs[dialog.peerID] = dialog; + //} + + if(offsetDate && + !dialog.pFlags.pinned && + (!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { + if(pos !== -1) { + // So the dialog jumped to the last position + return false; + } + this.dialogsOffsetDate[dialog.folder_id] = offsetDate; + } + + const index = dialog.index; + const len = dialogs.length; + if(!len || index < dialogs[len - 1].index) { + dialogs.push(dialog); + } else if(index >= dialogs[0].index) { + dialogs.unshift(dialog); + } else { + for(let i = 0; i < len; i++) { + if(index > dialogs[i].index) { + dialogs.splice(i, 0, dialog); + break; + } + } + } + } + + public dropDialog(peerID: number): [Dialog, number] | [] { + const foundDialog = this.getDialog(peerID); + if(foundDialog[0]) { + this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1); + delete this.dialogs[peerID]; + } + + return foundDialog; + } +} \ No newline at end of file diff --git a/src/lib/storages/filters.ts b/src/lib/storages/filters.ts new file mode 100644 index 00000000..d14809e1 --- /dev/null +++ b/src/lib/storages/filters.ts @@ -0,0 +1,262 @@ +import { copy } from "../../helpers/object"; +import type { DialogFilter, Update } from "../../layer"; +import type { Modify } from "../../types"; +import type { AppPeersManager } from "../appManagers/appPeersManager"; +import type { AppUsersManager } from "../appManagers/appUsersManager"; +//import type { ApiManagerProxy } from "../mtproto/mtprotoworker"; +import type _$rootScope from "../rootScope"; +import type {Dialog} from '../appManagers/appMessagesManager'; +import apiManager from "../mtproto/mtprotoworker"; + +export type MyDialogFilter = Modify; + +// ! because 0 index is 'All Chats' +const START_ORDER_INDEX = 1; + +export default class FiltersStorage { + public filters: {[filterID: string]: MyDialogFilter} = {}; + public orderIndex = START_ORDER_INDEX; + + constructor(private appPeersManager: AppPeersManager, private appUsersManager: AppUsersManager, /* private apiManager: ApiManagerProxy, */ private $rootScope: typeof _$rootScope) { + $rootScope.$on('apiUpdate', (e) => { + this.handleUpdate(e.detail); + }); + } + + public handleUpdate(update: Update) { + switch(update._) { + case 'updateDialogFilter': { + //console.log('updateDialogFilter', update); + + if(update.filter) { + this.saveDialogFilter(update.filter as any); + } else if(this.filters[update.id]) { // Папка удалена + //this.getDialogFilters(true); + this.$rootScope.$broadcast('filter_delete', this.filters[update.id]); + delete this.filters[update.id]; + } + + break; + } + + case 'updateDialogFilters': { + //console.warn('updateDialogFilters', update); + + const oldFilters = copy(this.filters); + + this.getDialogFilters(true).then(filters => { + for(const _filterID in oldFilters) { + const filterID = +_filterID; + if(!filters.find(filter => filter.id == filterID)) { // * deleted + this.handleUpdate({_: 'updateDialogFilter', id: filterID}); + } + } + + this.handleUpdate({_: 'updateDialogFilterOrder', order: filters.map(filter => filter.id)}); + }); + + break; + } + + case 'updateDialogFilterOrder': { + //console.log('updateDialogFilterOrder', update); + + this.orderIndex = START_ORDER_INDEX; + update.order.forEach((filterID, idx) => { + const filter = this.filters[filterID]; + delete filter.orderIndex; + this.setOrderIndex(filter); + }); + + this.$rootScope.$broadcast('filter_order', update.order); + + break; + } + } + } + + public testDialogForFilter(dialog: Dialog, filter: MyDialogFilter) { + // exclude_peers + for(const peerID of filter.exclude_peers) { + if(peerID == dialog.peerID) { + return false; + } + } + + // include_peers + for(const peerID of filter.include_peers) { + if(peerID == dialog.peerID) { + return true; + } + } + + const pFlags = filter.pFlags; + + // exclude_archived + if(pFlags.exclude_archived && dialog.folder_id == 1) { + return false; + } + + // exclude_read + if(pFlags.exclude_read && !dialog.unread_count) { + return false; + } + + // exclude_muted + if(pFlags.exclude_muted) { + const isMuted = (dialog.notify_settings?.mute_until * 1000) > Date.now(); + if(isMuted) { + return false; + } + } + + const peerID = dialog.peerID; + if(peerID < 0) { + // broadcasts + if(pFlags.broadcasts && this.appPeersManager.isBroadcast(peerID)) { + return true; + } + + // groups + if(pFlags.groups && this.appPeersManager.isAnyGroup(peerID)) { + return true; + } + } else { + // bots + if(this.appPeersManager.isBot(peerID)) { + return !!pFlags.bots; + } + + // non_contacts + if(pFlags.non_contacts && !this.appUsersManager.contactsList.has(peerID)) { + return true; + } + + // contacts + if(pFlags.contacts && this.appUsersManager.contactsList.has(peerID)) { + return true; + } + } + + return false; + } + + public toggleDialogPin(peerID: number, filterID: number) { + const filter = this.filters[filterID]; + + const wasPinned = filter.pinned_peers.findAndSplice(p => p == peerID); + if(!wasPinned) { + filter.pinned_peers.unshift(peerID); + } + + return this.updateDialogFilter(filter); + } + + public createDialogFilter(filter: MyDialogFilter) { + let maxID = Math.max(1, ...Object.keys(this.filters).map(i => +i)); + filter = copy(filter); + filter.id = maxID + 1; + return this.updateDialogFilter(filter); + } + + public updateDialogFilter(filter: MyDialogFilter, remove = false) { + const flags = remove ? 0 : 1; + + return apiManager.invokeApi('messages.updateDialogFilter', { + flags, + id: filter.id, + filter: remove ? undefined : this.getOutputDialogFilter(filter) + }).then((bool: boolean) => { // возможно нужна проверка и откат, если результат не ТРУ + //console.log('updateDialogFilter bool:', bool); + + if(bool) { + /* if(!this.filters[filter.id]) { + this.saveDialogFilter(filter); + } + + $rootScope.$broadcast('filter_update', filter); */ + + this.handleUpdate({ + _: 'updateDialogFilter', + id: filter.id, + filter: remove ? undefined : filter as any + }); + } + + return bool; + }); + } + + public getOutputDialogFilter(filter: MyDialogFilter) { + const c: MyDialogFilter = copy(filter); + ['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => { + // @ts-ignore + c[key] = c[key].map((peerID: number) => this.appPeersManager.getInputPeerByID(peerID)); + }); + + c.include_peers.forEachReverse((peerID, idx) => { + if(c.pinned_peers.includes(peerID)) { + c.include_peers.splice(idx, 1); + } + }); + + return c as any as DialogFilter; + } + + public async getDialogFilters(overwrite = false): Promise { + const keys = Object.keys(this.filters); + if(keys.length && !overwrite) { + return keys.map(filterID => this.filters[filterID]).sort((a, b) => a.orderIndex - b.orderIndex); + } + + const filters: MyDialogFilter[] = await apiManager.invokeApi('messages.getDialogFilters') as any; + for(const filter of filters) { + this.saveDialogFilter(filter, overwrite); + } + + //console.log(this.filters); + return filters; + } + + public saveDialogFilter(filter: MyDialogFilter, update = true) { + ['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => { + // @ts-ignore + filter[key] = filter[key].map((peer: any) => this.appPeersManager.getPeerID(peer)); + }); + + filter.include_peers.forEachReverse((peerID, idx) => { + if(filter.pinned_peers.includes(peerID)) { + filter.include_peers.splice(idx, 1); + } + }); + + filter.include_peers = filter.pinned_peers.concat(filter.include_peers); + + if(this.filters[filter.id]) { + Object.assign(this.filters[filter.id], filter); + } else { + this.filters[filter.id] = filter; + } + + this.setOrderIndex(filter); + + if(update) { + this.$rootScope.$broadcast('filter_update', filter); + } + } + + public setOrderIndex(filter: MyDialogFilter) { + if(filter.hasOwnProperty('orderIndex')) { + if(filter.orderIndex >= this.orderIndex) { + this.orderIndex = filter.orderIndex + 1; + } + } else { + filter.orderIndex = this.orderIndex++; + } + } +} \ No newline at end of file