Better chat slicing
This commit is contained in:
parent
02c91039d0
commit
b28a1931dc
@ -95,6 +95,7 @@ import getObjectKeysAndSort from "../../helpers/object/getObjectKeysAndSort";
|
||||
import forEachReverse from "../../helpers/array/forEachReverse";
|
||||
import formatNumber from "../../helpers/number/formatNumber";
|
||||
import findAndSplice from "../../helpers/array/findAndSplice";
|
||||
import getViewportSlice from "../../helpers/dom/getViewportSlice";
|
||||
|
||||
const USE_MEDIA_TAILS = false;
|
||||
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
||||
@ -154,9 +155,6 @@ export default class ChatBubbles {
|
||||
private bubbleGroups: BubbleGroups;
|
||||
|
||||
private preloader: ProgressivePreloader = null;
|
||||
|
||||
private loadedTopTimes = 0;
|
||||
private loadedBottomTimes = 0;
|
||||
|
||||
public messagesQueuePromise: Promise<void> = null;
|
||||
private messagesQueue: {message: any, bubble: HTMLElement, reverse: boolean, promises: Promise<void>[]}[] = [];
|
||||
@ -207,6 +205,7 @@ export default class ChatBubbles {
|
||||
|
||||
private hoverBubble: HTMLElement;
|
||||
private hoverReaction: HTMLElement;
|
||||
private sliceViewportDebounced: () => Promise<void>;
|
||||
|
||||
// private reactions: Map<number, ReactionsElement>;
|
||||
|
||||
@ -660,6 +659,9 @@ export default class ChatBubbles {
|
||||
}
|
||||
});
|
||||
|
||||
if(!IS_SAFARI) {
|
||||
this.sliceViewportDebounced = debounce(this.sliceViewport.bind(this), 100, false, true);
|
||||
}
|
||||
|
||||
let middleware: ReturnType<ChatBubbles['getMiddleware']>;
|
||||
useHeavyAnimationCheck(() => {
|
||||
@ -1737,7 +1739,11 @@ export default class ChatBubbles {
|
||||
}
|
||||
|
||||
if(this.chat.topbar.pinnedMessage) {
|
||||
this.chat.topbar.pinnedMessage.setCorrectIndex(this.scrollable.lastScrollDirection);
|
||||
this.chat.topbar.pinnedMessage.setCorrectIndexThrottled(this.scrollable.lastScrollDirection);
|
||||
}
|
||||
|
||||
if(this.sliceViewportDebounced) {
|
||||
this.sliceViewportDebounced();
|
||||
}
|
||||
|
||||
this.setStickyDateManually();
|
||||
@ -2233,8 +2239,6 @@ export default class ChatBubbles {
|
||||
this.viewsMids.clear();
|
||||
}
|
||||
|
||||
this.loadedTopTimes = this.loadedBottomTimes = 0;
|
||||
|
||||
this.middleware.clean();
|
||||
|
||||
this.onAnimateLadder = undefined;
|
||||
@ -3877,9 +3881,13 @@ export default class ChatBubbles {
|
||||
this.log('performHistoryResult: will render some messages:', history.length, this.isHeavyAnimationInProgress, this.messagesQueuePromise);
|
||||
} */
|
||||
|
||||
let scrollSaver: ScrollSaver;
|
||||
let scrollSaver: ScrollSaver/* , viewportSlice: ReturnType<ChatBubbles['getViewportSlice']> */;
|
||||
this.messagesQueueOnRender = () => {
|
||||
scrollSaver = new ScrollSaver(this.scrollable, reverse);
|
||||
|
||||
const viewportSlice = this.getViewportSlice();
|
||||
this.deleteViewportSlice(viewportSlice);
|
||||
|
||||
scrollSaver.save();
|
||||
};
|
||||
|
||||
@ -4370,6 +4378,38 @@ export default class ChatBubbles {
|
||||
return message;
|
||||
}
|
||||
|
||||
public getViewportSlice() {
|
||||
return getViewportSlice({
|
||||
overflowElement: this.scrollable.container,
|
||||
selector: '.bubbles-date-group .bubble:not(.is-date)',
|
||||
extraSize: Math.max(700, windowSize.height) * 2
|
||||
});
|
||||
}
|
||||
|
||||
public deleteViewportSlice(slice: ReturnType<ChatBubbles['getViewportSlice']>) {
|
||||
const {invisibleTop, invisibleBottom} = slice;
|
||||
const invisible = invisibleTop.concat(invisibleBottom);
|
||||
|
||||
if(invisibleTop.length) this.setLoaded('top', false);
|
||||
if(invisibleBottom.length) this.setLoaded('bottom', false);
|
||||
|
||||
const mids = invisible.map(({element}) => +element.dataset.mid);
|
||||
this.deleteMessagesByIds(mids, false);
|
||||
}
|
||||
|
||||
public sliceViewport() {
|
||||
if(IS_SAFARI) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const scrollSaver = new ScrollSaver(this.scrollable, true);
|
||||
// scrollSaver.save();
|
||||
const slice = this.getViewportSlice();
|
||||
// if(IS_SAFARI) slice.invisibleTop = [];
|
||||
this.deleteViewportSlice(slice);
|
||||
// scrollSaver.restore(false);
|
||||
}
|
||||
|
||||
private setLoaded(side: SliceSides, value: boolean, checkPlaceholders = true) {
|
||||
const willChange = this.scrollable.loadedAll[side] !== value;
|
||||
if(!willChange) {
|
||||
@ -4668,48 +4708,6 @@ export default class ChatBubbles {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* false && */!isFirstMessageRender && promise.then(() => {
|
||||
if(reverse) {
|
||||
this.loadedTopTimes++;
|
||||
this.loadedBottomTimes = Math.max(0, --this.loadedBottomTimes);
|
||||
} else {
|
||||
this.loadedBottomTimes++;
|
||||
this.loadedTopTimes = Math.max(0, --this.loadedTopTimes);
|
||||
}
|
||||
|
||||
let ids: number[];
|
||||
if((reverse && this.loadedTopTimes > 2) || (!reverse && this.loadedBottomTimes > 2)) {
|
||||
ids = getObjectKeysAndSort(this.bubbles);
|
||||
}
|
||||
|
||||
//let removeCount = loadCount / 2;
|
||||
const safeCount = realLoadCount * 2; // cause i've been runningrunningrunning all day
|
||||
//this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids?.length, safeCount);
|
||||
if(ids && ids.length > safeCount) {
|
||||
if(reverse) {
|
||||
//ids = ids.slice(-removeCount);
|
||||
//ids = ids.slice(removeCount * 2);
|
||||
ids = ids.slice(safeCount);
|
||||
this.setLoaded('bottom', false);
|
||||
|
||||
//this.log('getHistory: slice bottom messages:', ids.length, loadCount);
|
||||
//this.getHistoryBottomPromise = undefined; // !WARNING, это нужно для обратной загрузки истории, если запрос словил флуд
|
||||
} else {
|
||||
//ids = ids.slice(0, removeCount);
|
||||
//ids = ids.slice(0, ids.length - (removeCount * 2));
|
||||
ids = ids.slice(0, ids.length - safeCount);
|
||||
this.setLoaded('top', false);
|
||||
|
||||
//this.log('getHistory: slice up messages:', ids.length, loadCount);
|
||||
//this.getHistoryTopPromise = undefined; // !WARNING, это нужно для обратной загрузки истории, если запрос словил флуд
|
||||
}
|
||||
|
||||
//this.log('getHistory: will slice ids:', ids, reverse);
|
||||
|
||||
this.deleteMessagesByIds(ids, false);
|
||||
}
|
||||
});
|
||||
|
||||
promise.then(() => {
|
||||
// preload more
|
||||
//if(!isFirstMessageRender) {
|
||||
|
@ -21,6 +21,7 @@ import { cancelEvent } from "../../helpers/dom/cancelEvent";
|
||||
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
||||
import handleScrollSideEvent from "../../helpers/dom/handleScrollSideEvent";
|
||||
import debounce from "../../helpers/schedulers/debounce";
|
||||
import throttle from "../../helpers/schedulers/throttle";
|
||||
|
||||
class AnimatedSuper {
|
||||
static DURATION = 200;
|
||||
@ -213,47 +214,49 @@ class AnimatedCounter {
|
||||
}
|
||||
|
||||
export default class ChatPinnedMessage {
|
||||
public static LOAD_COUNT = 50;
|
||||
public static LOAD_OFFSET = 5;
|
||||
private static LOAD_COUNT = 50;
|
||||
private static LOAD_OFFSET = 5;
|
||||
|
||||
public pinnedMessageContainer: PinnedContainer;
|
||||
public pinnedMessageBorder: PinnedMessageBorder;
|
||||
private pinnedMessageBorder: PinnedMessageBorder;
|
||||
|
||||
public pinnedMaxMid = 0;
|
||||
private pinnedMaxMid = 0;
|
||||
public pinnedMid = 0;
|
||||
public pinnedIndex = -1;
|
||||
public wasPinnedIndex = 0;
|
||||
public wasPinnedMediaIndex = 0;
|
||||
private wasPinnedIndex = 0;
|
||||
private wasPinnedMediaIndex = 0;
|
||||
|
||||
public locked = false;
|
||||
public waitForScrollBottom = false;
|
||||
private waitForScrollBottom = false;
|
||||
|
||||
public count = 0;
|
||||
public mids: number[] = [];
|
||||
public offsetIndex = 0;
|
||||
private mids: number[] = [];
|
||||
private offsetIndex = 0;
|
||||
|
||||
public loading = false;
|
||||
public loadedBottom = false;
|
||||
public loadedTop = false;
|
||||
private loading = false;
|
||||
private loadedBottom = false;
|
||||
private loadedTop = false;
|
||||
|
||||
public animatedSubtitle: AnimatedSuper;
|
||||
public animatedMedia: AnimatedSuper;
|
||||
public animatedCounter: AnimatedCounter;
|
||||
private animatedSubtitle: AnimatedSuper;
|
||||
private animatedMedia: AnimatedSuper;
|
||||
private animatedCounter: AnimatedCounter;
|
||||
|
||||
public listenerSetter: ListenerSetter;
|
||||
public scrollDownListenerSetter: ListenerSetter = null;
|
||||
private listenerSetter: ListenerSetter;
|
||||
private scrollDownListenerSetter: ListenerSetter = null;
|
||||
|
||||
public hidden = false;
|
||||
|
||||
public getCurrentIndexPromise: Promise<any> = null;
|
||||
public btnOpen: HTMLButtonElement;
|
||||
private getCurrentIndexPromise: Promise<any> = null;
|
||||
private btnOpen: HTMLButtonElement;
|
||||
|
||||
public setPinnedMessage: () => void;
|
||||
private setPinnedMessage: () => void;
|
||||
|
||||
private isStatic = false;
|
||||
|
||||
private debug = false;
|
||||
|
||||
public setCorrectIndexThrottled: (lastScrollDirection?: number) => void;
|
||||
|
||||
constructor(private topbar: ChatTopbar, private chat: Chat, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager) {
|
||||
this.listenerSetter = new ListenerSetter();
|
||||
|
||||
@ -330,6 +333,7 @@ export default class ChatPinnedMessage {
|
||||
// * 200 - no lags
|
||||
// * 100 - need test
|
||||
this.setPinnedMessage = debounce(() => this._setPinnedMessage(), 100, true, true);
|
||||
this.setCorrectIndexThrottled = throttle(this.setCorrectIndex.bind(this), 100, false);
|
||||
|
||||
this.isStatic = this.chat.type === 'discussion';
|
||||
}
|
||||
|
73
src/helpers/dom/getViewportSlice.ts
Normal file
73
src/helpers/dom/getViewportSlice.ts
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import getVisibleRect from "./getVisibleRect";
|
||||
|
||||
export type ViewportSlicePart = {element: HTMLElement, rect: DOMRect, visibleRect: ReturnType<typeof getVisibleRect>}[];
|
||||
|
||||
export default function getViewportSlice({overflowElement, selector, extraSize}: {
|
||||
overflowElement: HTMLElement,
|
||||
selector: string,
|
||||
extraSize?: number
|
||||
}) {
|
||||
const perf = performance.now();
|
||||
const overflowRect = overflowElement.getBoundingClientRect();
|
||||
const elements = Array.from(overflowElement.querySelectorAll<HTMLElement>(selector));
|
||||
|
||||
const invisibleTop: ViewportSlicePart = [],
|
||||
visible: typeof invisibleTop = [],
|
||||
invisibleBottom: typeof invisibleTop = [];
|
||||
let foundVisible = false;
|
||||
for(const element of elements) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const visibleRect = getVisibleRect(element, overflowElement, false, rect, overflowRect);
|
||||
|
||||
const isVisible = !!visibleRect;
|
||||
let array: typeof invisibleTop;
|
||||
if(isVisible) {
|
||||
foundVisible = true;
|
||||
array = visible;
|
||||
} else if(foundVisible) {
|
||||
array = invisibleBottom;
|
||||
} else {
|
||||
array = invisibleTop;
|
||||
}
|
||||
|
||||
array.push({
|
||||
element,
|
||||
rect,
|
||||
visibleRect
|
||||
});
|
||||
}
|
||||
|
||||
if(extraSize && visible.length) {
|
||||
const maxTop = visible[0].rect.top;
|
||||
const minTop = maxTop - extraSize;
|
||||
const minBottom = visible[visible.length - 1].rect.bottom;
|
||||
const maxBottom = minBottom + extraSize;
|
||||
|
||||
for(let length = invisibleTop.length, i = length - 1; i >= 0; --i) {
|
||||
const element = invisibleTop[i];
|
||||
if(element.rect.top >= minTop) {
|
||||
invisibleTop.splice(i, 1);
|
||||
visible.unshift(element);
|
||||
}
|
||||
}
|
||||
|
||||
for(let i = 0, length = invisibleBottom.length; i < length; ++i) {
|
||||
const element = invisibleBottom[i];
|
||||
if(element.rect.bottom <= maxBottom) {
|
||||
invisibleBottom.splice(i--, 1);
|
||||
--length;
|
||||
visible.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('getViewportSlice time:', performance.now() - perf);
|
||||
|
||||
return {invisibleTop, visible, invisibleBottom};
|
||||
}
|
@ -4,9 +4,13 @@
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
export default function getVisibleRect(element: HTMLElement, overflowElement: HTMLElement, lookForSticky?: boolean, rect = element.getBoundingClientRect()) {
|
||||
const overflowRect = overflowElement.getBoundingClientRect();
|
||||
|
||||
export default function getVisibleRect(
|
||||
element: HTMLElement,
|
||||
overflowElement: HTMLElement,
|
||||
lookForSticky?: boolean,
|
||||
rect = element.getBoundingClientRect(),
|
||||
overflowRect = overflowElement.getBoundingClientRect()
|
||||
) {
|
||||
let {top: overflowTop, right: overflowRight, bottom: overflowBottom, left: overflowLeft} = overflowRect;
|
||||
|
||||
// * respect sticky headers
|
||||
|
@ -1135,6 +1135,9 @@ export class AppImManager {
|
||||
//if(bubble) {
|
||||
//const top = bubble.getBoundingClientRect().top;
|
||||
const chatBubbles = chat.bubbles;
|
||||
|
||||
chatBubbles.sliceViewport();
|
||||
|
||||
const top = chatBubbles.scrollable.scrollTop;
|
||||
|
||||
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
|
||||
|
@ -12,7 +12,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
|
||||
const allowedIPs = ['127.0.0.1'];
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
const useLocal = true;
|
||||
const useLocalNotLocal = true;
|
||||
const useLocalNotLocal = false;
|
||||
|
||||
if(devMode) {
|
||||
console.log('DEVMODE IS ON!');
|
||||
@ -37,7 +37,7 @@ const opts = {
|
||||
};
|
||||
|
||||
const domain = 'yourdomain.com';
|
||||
const localIp = '192.168.100.51';
|
||||
const localIp = '192.168.100.167';
|
||||
|
||||
const middleware = (req, res, next) => {
|
||||
let IP = '';
|
||||
|
Loading…
x
Reference in New Issue
Block a user