diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index df24d912..2e536b81 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -129,7 +129,7 @@ export default class Scrollable extends ScrollableBase { public onScrollMeasure: number = null; - public lastScrollTop: number = 0; + private lastScrollTop: number = 0; public loadedAll: SliceSidesContainer = {top: true, bottom: false}; @@ -155,6 +155,7 @@ export default class Scrollable extends ScrollableBase { this.checkForTriggers(); this.onScrollMeasure = 0; + this.lastScrollTop = this.scrollTop; }); }; @@ -170,13 +171,16 @@ export default class Scrollable extends ScrollableBase { const {clientHeight, scrollTop} = container; const maxScrollTop = scrollHeight - clientHeight; + // 1 - bottom, -1 - top + const direction = this.lastScrollTop == scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1); + //this.log('checkForTriggers:', scrollTop, maxScrollTop); - if(this.onScrolledTop && scrollTop <= this.onScrollOffset) { + if(this.onScrolledTop && scrollTop <= this.onScrollOffset && direction <= 0/* && direction === -1 */) { this.onScrolledTop(); } - if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset) { + if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && direction >= 0/* && direction === 1 */) { this.onScrolledBottom(); } }; @@ -215,7 +219,7 @@ export default class Scrollable extends ScrollableBase { return this.scrollTop; }; - public slice(side: SliceSides, safeCount: number/* sliceLength: number */) { + /* public slice(side: SliceSides, safeCount: number) { //const isOtherSideLoaded = this.loadedAll[side == 'top' ? 'bottom' : 'top']; //const multiplier = 2 - +isOtherSideLoaded; const multiplier = 2; @@ -239,8 +243,15 @@ export default class Scrollable extends ScrollableBase { this.loadedAll[side] = false; } + // * fix instant load of cutted side + if(side == 'top') { + this.lastScrollTop = 0; + } else { + this.lastScrollTop = this.scrollHeight + this.container.clientHeight; + } + return sliced; - } + } */ get isScrolledDown() { return this.scrollHeight - Math.round(this.scrollTop + this.container.offsetHeight) <= 1; diff --git a/src/components/sidebarLeft/tabs/archivedTab.ts b/src/components/sidebarLeft/tabs/archivedTab.ts index fcc21d6c..c83c180e 100644 --- a/src/components/sidebarLeft/tabs/archivedTab.ts +++ b/src/components/sidebarLeft/tabs/archivedTab.ts @@ -12,6 +12,7 @@ export default class AppArchivedTab implements SliderTab { init() { this.scroll = new Scrollable(this.container, 'CLA', 500); + this.scroll.container.addEventListener('scroll', appDialogsManager.onChatsRegularScroll); this.scroll.setVirtualContainer(this.chatList); this.scroll.onScrolledBottom = appDialogsManager.onChatsScroll; ///this.scroll.attachSentinels(); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 1be25aae..f5c8a267 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -9,7 +9,7 @@ import appSidebarLeft from "../../components/sidebarLeft"; import { formatDateAccordingToToday } from "../../helpers/date"; import { escapeRegExp } from "../../helpers/string"; import { isTouchSupported } from "../../helpers/touchSupport"; -import { isSafari } from "../../helpers/userAgent"; +import { isApple, isSafari } from "../../helpers/userAgent"; import { logger, LogLevels } from "../logger"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -192,6 +192,8 @@ export class AppDialogsManager { private topOffsetIndex = 0; + private sliceTimeout: number; + constructor() { this.chatsPreloader = putPreloader(null, true); @@ -204,6 +206,7 @@ export class AppDialogsManager { bottomPart.append(this.folders.container); this.scroll = this._scroll = new Scrollable(bottomPart, 'CL', 500); + this.scroll.container.addEventListener('scroll', this.onChatsRegularScroll); this.scroll.onScrolledTop = this.onChatsScrollTop; this.scroll.onScrolledBottom = this.onChatsScroll; this.scroll.setVirtualContainer(this.chatList); @@ -589,132 +592,222 @@ export class AppDialogsManager { }; } - private async loadDialogs(side: SliceSides = 'bottom') { + private loadDialogs(side: SliceSides = 'bottom') { if(testScroll) { return; } if(this.loadDialogsPromise/* || 1 == 1 */) return this.loadDialogsPromise; - - if(!this.chatList.childElementCount) { - const container = this.chatList.parentElement; - container.append(this.chatsPreloader); - } - - //return; - - const filterID = this.filterID; - let loadCount = 30/*this.chatsLoadCount */; - - const storage = appMessagesManager.dialogsStorage.getFolder(filterID); - let offsetIndex = 0; - if(side == 'top') { - const element = this.chatList.firstElementChild; - if(element) { - const peerID = +element.getAttribute('data-peerID'); - const index = storage.findIndex(dialog => dialog.peerID == peerID); - const needIndex = Math.max(0, index - loadCount); - loadCount = index - needIndex; - offsetIndex = storage[needIndex].index + 1; - } - } else { - const element = this.chatList.lastElementChild; - if(element) { - const peerID = +element.getAttribute('data-peerID'); - const dialog = storage.find(dialog => dialog.peerID == peerID); - offsetIndex = dialog.index; + const promise = new Promise(async(resolve, reject) => { + if(!this.chatList.childElementCount) { + const container = this.chatList.parentElement; + container.append(this.chatsPreloader); } - /* for(let i = storage.length - 1; i >= 0; --i) { - const dialog = storage[i]; - if(this.getDialogDom(dialog.peerID)) { + + //return; + + const filterID = this.filterID; + let loadCount = 30/*this.chatsLoadCount */; + + const storage = appMessagesManager.dialogsStorage.getFolder(filterID); + let offsetIndex = 0; + + if(side == 'top') { + const element = this.chatList.firstElementChild; + if(element) { + const peerID = +element.getAttribute('data-peerID'); + const index = storage.findIndex(dialog => dialog.peerID == peerID); + const needIndex = Math.max(0, index - loadCount); + loadCount = index - needIndex; + offsetIndex = storage[needIndex].index + 1; + } + } else { + const element = this.chatList.lastElementChild; + if(element) { + const peerID = +element.getAttribute('data-peerID'); + const dialog = storage.find(dialog => dialog.peerID == peerID); offsetIndex = dialog.index; - break; } - } */ - } - - - //let offset = storage[storage.length - 1]?.index || 0; + } + + //let offset = storage[storage.length - 1]?.index || 0; + + try { + //console.time('getDialogs time'); + + const getConversationPromise = (this.filterID > 1 ? appUsersManager.getContacts() as Promise : Promise.resolve()).then(() => { + return appMessagesManager.getConversations('', offsetIndex, loadCount, filterID); + }); + + const result = await getConversationPromise; + + if(this.filterID != filterID) { + return; + } + + //console.timeEnd('getDialogs time'); + + // * loaded all + //if(!result.dialogs.length || this.chatList.childElementCount == result.count) { + // !result.dialogs.length не подходит, так как при супердревном диалоге getConversations его не выдаст. + //if(this.chatList.childElementCount == result.count) { + if(side == 'bottom') { + if(result.isEnd) { + this.scroll.loadedAll[side] = true; + } + } else { + const storage = appMessagesManager.dialogsStorage.getFolder(filterID); + if(!result.dialogs.length || (storage.length && storage[0].index < offsetIndex)) { + this.scroll.loadedAll[side] = true; + } + } + + if(result.dialogs.length) { + const dialogs = side == 'top' ? result.dialogs.slice().reverse() : result.dialogs; + + /* let previousScrollHeightMinusTop: number; + //if(isApple || true) { + if(isApple && side == 'top') { + const {scrollTop, scrollHeight} = this.scroll; + + previousScrollHeightMinusTop = side == 'top' ? scrollHeight - scrollTop : scrollTop; + //this.scroll.scrollLocked = 1; + } */ + + dialogs.forEach((dialog) => { + this.addDialogNew({ + dialog, + append: side == 'bottom' + }); + }); + + /* if(previousScrollHeightMinusTop !== undefined) { + const newScrollTop = side == 'top' ? this.scroll.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop; + + // touchSupport for safari iOS + isTouchSupported && isApple && (this.scroll.container.style.overflow = 'hidden'); + this.scroll.scrollTop = newScrollTop; + isTouchSupported && isApple && (this.scroll.container.style.overflow = ''); + } */ + + //this.scroll.scrollLocked = 0; + + //if(side == 'bottom' || true || (testTopSlice-- > 0 && side == 'top')) { + //setTimeout(() => { + /* const sliced = this.scroll.slice(side == 'bottom' ? 'top' : 'bottom', 30); // result.dialogs.length + sliced.forEach(el => { + const peerID = +el.getAttribute('data-peerID'); + delete this.doms[peerID]; + }); */ + //}, 0); + //} + } + + if(!this.scroll.loadedAll['top']) { + const element = this.chatList.firstElementChild; + if(element) { + const peerID = +element.getAttribute('data-peerID'); + const dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; + this.topOffsetIndex = dialog.index; + } + } else { + this.topOffsetIndex = 0; + } + + this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.chatList.childElementCount); + + setTimeout(() => { + /* setTimeout(() => { + this.scroll.slice(true); + }, 100); */ + this.scroll.onScroll(); + }, 0); + } catch(err) { + this.log.error(err); + } + + this.chatsPreloader.remove(); + resolve(); + }); - try { - //console.time('getDialogs time'); + return this.loadDialogsPromise = promise.finally(() => { + this.loadDialogsPromise = undefined; + }); + } - const getConversationPromise = (this.filterID > 1 ? appUsersManager.getContacts() as Promise : Promise.resolve()).then(() => { - return appMessagesManager.getConversations('', offsetIndex, loadCount, filterID); + public onChatsRegularScroll = () => { + if(this.sliceTimeout) clearTimeout(this.sliceTimeout); + this.sliceTimeout = window.setTimeout(() => { + /* const observer = new IntersectionObserver((entries) => { + const }); - this.loadDialogsPromise = getConversationPromise; - - const result = await getConversationPromise; + Array.from(this.chatList.children).forEach(el => { + observer.observe(el); + }); */ - if(this.filterID != filterID) { - return; - } + const scrollTopWas = this.scroll.scrollTop; - //console.timeEnd('getDialogs time'); + const rect = this.scroll.container.getBoundingClientRect(); + const children = Array.from(this.scroll.splitUp.children) as HTMLElement[]; + const firstElement = document.elementFromPoint(rect.x, rect.y) as HTMLElement; + const lastElement = document.elementFromPoint(rect.x, rect.y + rect.height - 1) as HTMLElement; - // * loaded all - //if(!result.dialogs.length || this.chatList.childElementCount == result.count) { - // !result.dialogs.length не подходит, так как при супердревном диалоге getConversations его не выдаст. - //if(this.chatList.childElementCount == result.count) { - if(side == 'bottom') { - if(result.isEnd) { - this.scroll.loadedAll[side] = true; - } - } else { - const storage = appMessagesManager.dialogsStorage.getFolder(filterID); - if(!result.dialogs.length || (storage.length && storage[0].index < offsetIndex)) { - this.scroll.loadedAll[side] = true; - } + const firstElementRect = firstElement.getBoundingClientRect(); + const elementOverflow = firstElementRect.y - rect.y; + + const sliced: HTMLElement[] = []; + const firstIndex = children.indexOf(firstElement); + const lastIndex = children.indexOf(lastElement); + + const saveLength = 10; + + const sliceFromStart = isApple ? [] : children.slice(0, Math.max(0, firstIndex - saveLength)); + const sliceFromEnd = children.slice(lastIndex + saveLength); + + /* if(sliceFromStart.length != sliceFromEnd.length) { + console.log('not equal', sliceFromStart.length, sliceFromEnd.length); } - - if(result.dialogs.length) { - const dialogs = side == 'top' ? result.dialogs.slice().reverse() : result.dialogs; - dialogs.forEach((dialog) => { - this.addDialogNew({ - dialog, - append: side == 'bottom' - }); - }); - //if(side == 'bottom' || true || (testTopSlice-- > 0 && side == 'top')) { - //setTimeout(() => { - const sliced = this.scroll.slice(side == 'bottom' ? 'top' : 'bottom', 30/* result.dialogs.length */); - sliced.forEach(el => { - const peerID = +el.getAttribute('data-peerID'); - delete this.doms[peerID]; - }); - //}, 0); - //} + if(sliceFromStart.length > sliceFromEnd.length) { + const diff = sliceFromStart.length - sliceFromEnd.length; + sliceFromStart.splice(0, diff); + } else if(sliceFromEnd.length > sliceFromStart.length) { + const diff = sliceFromEnd.length - sliceFromStart.length; + sliceFromEnd.splice(sliceFromEnd.length - diff, diff); + } */ + + if(sliceFromStart.length) { + this.scroll.loadedAll['top'] = false; } - if(!this.scroll.loadedAll['top']) { - const element = this.chatList.firstElementChild; - if(element) { - const peerID = +element.getAttribute('data-peerID'); - const dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; - this.topOffsetIndex = dialog.index; - } - } else { - this.topOffsetIndex = 0; + if(sliceFromEnd.length) { + this.scroll.loadedAll['bottom'] = false; } - this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.chatList.childElementCount); + sliced.push(...sliceFromStart); + sliced.push(...sliceFromEnd); - setTimeout(() => { - /* setTimeout(() => { - this.scroll.slice(true); - }, 100); */ - this.scroll.onScroll(); - }, 0); - } catch(err) { - this.log.error(err); - } - - this.chatsPreloader.remove(); - this.loadDialogsPromise = undefined; - } + sliced.forEach(el => { + el.remove(); + const peerID = +el.getAttribute('data-peerID'); + delete this.doms[peerID]; + }); + + //this.log('[slicer] elements', firstElement, lastElement, rect, sliced, sliceFromStart.length, sliceFromEnd.length); + + //this.log('[slicer] reset scrollTop', scrollTopWas, this.scroll.scrollTop, firstElement.offsetTop, firstElementRect.y, rect.y, elementOverflow); + + this.scroll.scrollTop = firstElement.offsetTop - elementOverflow; + /* const firstElementRect = firstElement.getBoundingClientRect(); + const scrollTop = */ + + //this.scroll.scrollIntoView(firstElement, false); + + this.sliceTimeout = undefined; + }, 1e3); + }; public onChatsScrollTop = () => { this.onChatsScroll('top'); @@ -722,7 +815,7 @@ export class AppDialogsManager { public onChatsScroll = (side: SliceSides = 'bottom') => { if(this.scroll.loadedAll[side] || this.loadDialogsPromise) return; - this.log.error('onChatsScroll', side); + this.log('onChatsScroll', side); this.loadDialogs(side); }; diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts index 7d006d34..a9301e3a 100644 --- a/src/lib/appManagers/appStateManager.ts +++ b/src/lib/appManagers/appStateManager.ts @@ -91,7 +91,7 @@ export class AppStateManager extends EventListenerBase<{ if(auth) { // ! Warning ! DON'T delete this this.state.authState = {_: 'authStateSignedIn'}; - rootScope.broadcast('user_auth', auth); + rootScope.broadcast('user_auth', typeof(auth) !== 'number' ? (auth as any).id : auth); // * support old version } else if(!this.state.authState) { this.state.authState = {_: 'authStateSignIn'}; }