Fix jumping scroll again

This commit is contained in:
Eduard Kuzmenko 2022-03-28 18:16:12 +03:00
parent da05e05e50
commit 746514e87d
9 changed files with 225 additions and 99 deletions

View File

@ -40,20 +40,31 @@ for(const name of Object.keys(nets)) {
}
const useHttp = false;
const server = useHttp ? http : https;
const transport = useHttp ? http : https;
let options = {};
if(!useHttp) {
options.key = fs.readFileSync(__dirname + '/certs/server-key.pem');
options.cert = fs.readFileSync(__dirname + '/certs/server-cert.pem');
}
console.log(results);
const port = 3000;
const protocol = useHttp ? 'http' : 'https';
console.log('Listening port:', port);
function createServer(host) {
server.createServer(options, app).listen(port, host, () => {
const server = transport.createServer(options, app);
server.listen(port, host, () => {
console.log('Host:', `${protocol}://${host || 'localhost'}:${port}/`);
});
server.on('error', (e) => {
// @ts-ignore
if(e.code === 'EADDRINUSE') {
console.log('Address in use:', host);
server.close();
}
});
}
for(const name in results) {

View File

@ -47,14 +47,24 @@ export class AnimationIntersector {
if(entry.isIntersecting) {
this.visible.add(player);
this.checkAnimation(player, false);
/* if(animation instanceof HTMLVideoElement && animation.dataset.src) {
animation.src = animation.dataset.src;
animation.load();
} */
} else {
this.visible.delete(player);
this.checkAnimation(player, true);
if(player.animation instanceof RLottiePlayer/* && player.animation.cachingDelta === 2 */) {
const animation = player.animation;
if(animation instanceof RLottiePlayer/* && animation.cachingDelta === 2 */) {
//console.warn('will clear cache', player);
player.animation.clearCache();
}
animation.clearCache();
}/* else if(animation instanceof HTMLVideoElement && animation.src) {
animation.dataset.src = animation.src;
animation.src = '';
animation.load();
} */
}
break;

View File

@ -208,8 +208,6 @@ export default class ChatBubbles {
private hoverBubble: HTMLElement;
private hoverReaction: HTMLElement;
private onUpdateScrollSaver: ScrollSaver;
// private reactions: Map<number, ReactionsElement>;
constructor(
@ -431,8 +429,10 @@ export default class ChatBubbles {
const updatePosition = this.chat.type === 'scheduled';
this.saveOnUpdateScroll();
const scrollSaver = new ScrollSaver(this.scrollable, true);
scrollSaver.save();
this.safeRenderMessage(mounted.message, true, false, mounted.bubble, updatePosition);
scrollSaver.restore();
if(updatePosition) {
(this.messagesQueuePromise || Promise.resolve()).then(() => {
@ -461,43 +461,41 @@ export default class ChatBubbles {
this.appendReactionsElementToBubble(bubble, message, changedResults);
});
this.listenerSetter.add(rootScope)('message_reactions', ({message, changedResults}) => {
if(this.peerId !== message.peerId) {
return;
}
this.listenerSetter.add(rootScope)('messages_reactions', (arr) => {
let scrollSaver: ScrollSaver;
const bubble = this.getBubbleByMessage(message);
if(!bubble) {
return;
}
this.saveOnUpdateScroll();
const key = message.peerId + '_' + message.mid;
const set = REACTIONS_ELEMENTS.get(key);
if(set) {
for(const element of set) {
element.update(message, changedResults);
for(const {message, changedResults} of arr) {
if(this.peerId !== message.peerId) {
return;
}
} else {
rootScope.dispatchEvent('missed_reactions_element', {message, changedResults});
const bubble = this.getBubbleByMessage(message);
if(!bubble) {
return;
}
if(!scrollSaver) {
scrollSaver = new ScrollSaver(this.scrollable, true);
scrollSaver.save();
}
const key = message.peerId + '_' + message.mid;
const set = REACTIONS_ELEMENTS.get(key);
if(set) {
for(const element of set) {
element.update(message, changedResults);
}
} else {
rootScope.dispatchEvent('missed_reactions_element', {message, changedResults});
}
}
if(scrollSaver) {
scrollSaver.restore();
}
});
}
/* this.listenerSetter.add(rootScope)('message_reactions', ({peerId, mid}) => {
if(this.peerId !== peerId) {
return;
}
const reactionsElement = this.reactions.get(mid);
if(!reactionsElement) {
return;
}
}); */
this.listenerSetter.add(rootScope)('album_edit', ({peerId, groupId, deletedMids}) => {
//fastRaf(() => { // ! can't use delayed smth here, need original bubble to be edited
if(peerId !== this.peerId) return;
@ -767,24 +765,35 @@ export default class ChatBubbles {
}
});
this.listenerSetter.add(rootScope)('message_views', ({peerId, views, mid}) => {
if(this.peerId !== peerId) return;
this.listenerSetter.add(rootScope)('messages_views', (arr) => {
fastRaf(() => {
const bubble = this.bubbles[mid];
if(!bubble) return;
let scrollSaver: ScrollSaver;
for(const {peerId, views, mid} of arr) {
if(this.peerId !== peerId) return;
const postViewsElements = Array.from(bubble.querySelectorAll('.post-views')) as HTMLElement[];
if(postViewsElements.length) {
const str = formatNumber(views, 1);
let different = false;
postViewsElements.forEach(postViews => {
if(different || postViews.innerHTML !== str) {
this.saveOnUpdateScroll();
different = true;
postViews.innerHTML = str;
}
});
const bubble = this.bubbles[mid];
if(!bubble) return;
const postViewsElements = Array.from(bubble.querySelectorAll('.post-views')) as HTMLElement[];
if(postViewsElements.length) {
const str = formatNumber(views, 1);
let different = false;
postViewsElements.forEach(postViews => {
if(different || postViews.innerHTML !== str) {
if(!scrollSaver) {
scrollSaver = new ScrollSaver(this.scrollable, true);
scrollSaver.save();
}
different = true;
postViews.innerHTML = str;
}
});
}
}
if(scrollSaver) {
scrollSaver.restore();
}
});
});
@ -928,18 +937,6 @@ export default class ChatBubbles {
}
}
private saveOnUpdateScroll() {
if(!this.onUpdateScrollSaver) {
this.onUpdateScrollSaver = new ScrollSaver(this.scrollable, true);
setTimeout(() => {
this.onUpdateScrollSaver.restore();
this.onUpdateScrollSaver = undefined;
}, 0);
this.onUpdateScrollSaver.save();
}
}
private onBubblesMouseMove = (e: MouseEvent) => {
const content = findUpClassName(e.target, 'bubble-content');
if(content && !this.chat.selection.isSelecting) {
@ -1048,6 +1045,8 @@ export default class ChatBubbles {
};
public setStickyDateManually() {
// return;
const timestamps = Object.keys(this.dateMessages).map(k => +k).sort((a, b) => b - a);
let lastVisible: HTMLElement;
@ -1716,7 +1715,7 @@ export default class ChatBubbles {
//lottieLoader.checkAnimations(false, 'chat');
const distanceToEnd = this.scrollable.getDistanceToEnd();
if(!IS_TOUCH_SUPPORTED && this.scrollable.lastScrollDirection !== 0 && distanceToEnd > 0) {
if(/* !IS_TOUCH_SUPPORTED && */this.scrollable.lastScrollDirection !== 0 && distanceToEnd > 0) {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
@ -1767,7 +1766,7 @@ export default class ChatBubbles {
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
//this.scrollable.attachSentinels(undefined, 300);
if(IS_TOUCH_SUPPORTED) {
if(IS_TOUCH_SUPPORTED && false) {
this.scrollable.container.addEventListener('touchmove', () => {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
@ -3782,7 +3781,7 @@ export default class ChatBubbles {
}
private appendReactionsElementToBubble(bubble: HTMLElement, message: Message.message, changedResults?: ReactionCount[]) {
if(this.peerId.isUser()) {
if(this.peerId.isUser()/* || true */) {
return;
}

View File

@ -343,7 +343,7 @@ export default class ChatPinnedMessage {
public setCorrectIndex(lastScrollDirection?: number) {
if(this.isStatic) return;
//return;
// return;
if(this.locked || this.hidden/* || this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise */) {
return;

View File

@ -451,13 +451,23 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
});
if(doc.type === 'video') {
video.addEventListener('timeupdate', () => {
const onTimeUpdate = () => {
if(!video.videoWidth) {
return;
}
spanTime.innerText = toHHMMSS(video.duration - video.currentTime, false);
});
};
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate);
video.addEventListener('timeupdate', throttledTimeUpdate);
if(spanPlay) {
video.addEventListener('timeupdate', () => {
spanPlay.remove();
sequentialDom.mutateElement(spanPlay, () => {
spanPlay.remove();
});
}, {once: true});
}
}

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { MOUNT_CLASS_TO } from "../../config/debug";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import replaceContent from "../../helpers/dom/replaceContent";
import sequentialDom from "../../helpers/sequentialDom";
@ -220,4 +221,5 @@ export class AppAvatarsManager {
}
const appAvatarsManager = new AppAvatarsManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appAvatarsManager = appAvatarsManager);
export default appAvatarsManager;

View File

@ -15,8 +15,8 @@ import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePr
import { formatDateAccordingToTodayNew, formatTime, tsNow } from "../../helpers/date";
import { createPosterForVideo } from "../../helpers/files";
import { randomLong } from "../../helpers/random";
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo, Updates, ReplyMarkup, InputPeer, InputPhoto, InputDocument, InputGeoPoint, WebPage, GeoPoint, ReportReason, MessagesGetDialogs, InputChannel, InputDialogPeer, ReactionCount, MessagePeerReaction, MessagesSearchCounter, Peer } from "../../layer";
import { InvokeApiOptions } from "../../types";
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo, Updates, ReplyMarkup, InputPeer, InputPhoto, InputDocument, InputGeoPoint, WebPage, GeoPoint, ReportReason, MessagesGetDialogs, InputChannel, InputDialogPeer, ReactionCount, MessagePeerReaction, MessagesSearchCounter, Peer, MessageReactions } from "../../layer";
import { ArgumentTypes, InvokeApiOptions } from "../../types";
import I18n, { FormatterArguments, i18n, join, langPack, LangPackKey, UNSUPPORTED_LANG_PACK_KEY, _i18n } from "../langPack";
import { logger, LogTypes } from "../logger";
import type { ApiFileManager } from '../mtproto/apiFileManager';
@ -70,6 +70,7 @@ import deepEqual from "../../helpers/object/deepEqual";
import escapeRegExp from "../../helpers/string/escapeRegExp";
import limitSymbols from "../../helpers/string/limitSymbols";
import splitStringByLength from "../../helpers/string/splitStringByLength";
import debounce from "../../helpers/schedulers/debounce";
//console.trace('include');
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
@ -128,6 +129,13 @@ type PendingAfterMsg = Partial<InvokeApiOptions & {
messageId: string
}>;
type MapValueType<A> = A extends Map<any, infer V> ? V : never;
export type BatchUpdates = {
'messages_reactions': AppMessagesManager['batchUpdateReactions'],
'messages_views': AppMessagesManager['batchUpdateViews']
};
export class AppMessagesManager {
private messagesStorageByPeerId: {[peerId: string]: MessagesStorage};
public groupedMessagesStorage: {[groupId: string]: MessagesStorage}; // will be used for albums
@ -214,6 +222,14 @@ export class AppMessagesManager {
private unreadMentions: {[peerId: PeerId]: SlicedArray} = {};
private goToNextMentionPromises: {[peerId: PeerId]: Promise<any>} = {};
private batchUpdates: {
[k in keyof BatchUpdates]?: {
callback: BatchUpdates[k],
batch: ArgumentTypes<BatchUpdates[k]>[0]
}
} = {};
private batchUpdatesDebounced: () => Promise<void>;
constructor() {
this.clear();
@ -345,6 +361,20 @@ export class AppMessagesManager {
this.maxSeenId = state.maxSeenMsgId;
}
});
this.batchUpdatesDebounced = debounce(() => {
for(const event in this.batchUpdates) {
const details = this.batchUpdates[event as keyof BatchUpdates];
delete this.batchUpdates[event as keyof BatchUpdates];
// @ts-ignore
const result = details.callback(details.batch);
if(result && (!(result instanceof Array) || result.length)) {
// @ts-ignore
rootScope.dispatchEvent(event as keyof BatchUpdates, result);
}
}
}, 33, false, true);
}
public clear() {
@ -4700,26 +4730,10 @@ export class AppMessagesManager {
}
}
const results = reactions?.results ?? [];
const previousResults = message.reactions?.results ?? [];
const changedResults = results.filter(reactionCount => {
const previousReactionCount = previousResults.find(_reactionCount => _reactionCount.reaction === reactionCount.reaction);
return (
message.pFlags.out && (
!previousReactionCount ||
reactionCount.count > previousReactionCount.count
)
) || (
reactionCount.pFlags.chosen && (
!previousReactionCount ||
!previousReactionCount.pFlags.chosen
)
);
});
message.reactions = reactions;
rootScope.dispatchEvent('message_reactions', {message, changedResults});
const key = message.peerId + '_' + message.mid;
this.pushBatchUpdate('messages_reactions', this.batchUpdateReactions, key, () => copy(message.reactions));
if(!update.local) {
this.setDialogToStateIfMessageIsTop(message);
@ -5094,7 +5108,7 @@ export class AppMessagesManager {
const message: Message.message = this.getMessageByPeer(peerId, mid);
if(!message.deleted && message.views !== undefined && message.views < views) {
message.views = views;
rootScope.dispatchEvent('message_views', {peerId, mid, views});
this.pushBatchUpdate('messages_views', this.batchUpdateViews, message.peerId + '_' + message.mid);
this.setDialogToStateIfMessageIsTop(message);
}
};
@ -6283,6 +6297,86 @@ export class AppMessagesManager {
public canForward(message: Message.message | Message.messageService) {
return !(message as Message.message).pFlags.noforwards && !appPeersManager.noForwards(message.peerId);
}
private pushBatchUpdate<E extends keyof BatchUpdates, C extends BatchUpdates[E]>(
event: E,
callback: C,
key: string,
getElementCallback?: () => MapValueType<ArgumentTypes<C>[0]>
) {
let details = this.batchUpdates[event];
if(!details) {
// @ts-ignore
details = this.batchUpdates[event] = {
callback,
batch: new Map()
};
}
if(!details.batch.has(key)) {
// @ts-ignore
details.batch.set(key, getElementCallback ? getElementCallback() : undefined);
this.batchUpdatesDebounced();
}
}
private getMessagesFromMap<T extends Map<any, any>>(map: T) {
const newMap: Map<Message.message, MapValueType<T>> = new Map();
for(const [key, value] of map) {
const [peerIdStr, mid] = key.split('_');
const message: Message.message | Message.messageEmpty = this.getMessageByPeer(peerIdStr.toPeerId(), +mid);
if(message._ === 'messageEmpty') {
continue;
}
newMap.set(message, value);
}
return newMap;
}
private batchUpdateViews = (batch: Map<string, undefined>) => {
const toDispatch: {peerId: PeerId, mid: number, views: number}[] = [];
const map = this.getMessagesFromMap(batch);
for(const [message] of map) {
toDispatch.push({
peerId: message.peerId,
mid: message.mid,
views: message.views
})
}
return toDispatch;
};
private batchUpdateReactions = (batch: Map<string, MessageReactions>) => {
const toDispatch: {message: Message.message, changedResults: ReactionCount.reactionCount[]}[] = [];
const map = this.getMessagesFromMap(batch);
for(const [message, reactions] of map) {
const results = reactions?.results ?? [];
const previousResults = message.reactions?.results ?? [];
const changedResults = results.filter(reactionCount => {
const previousReactionCount = previousResults.find(_reactionCount => _reactionCount.reaction === reactionCount.reaction);
return (
message.pFlags.out && (
!previousReactionCount ||
reactionCount.count > previousReactionCount.count
)
) || (
reactionCount.pFlags.chosen && (
!previousReactionCount ||
!previousReactionCount.pFlags.chosen
)
);
});
toDispatch.push({message, changedResults});
}
return toDispatch;
};
}
const appMessagesManager = new AppMessagesManager();

View File

@ -320,7 +320,7 @@ export class AppReactionsManager {
if(onlyLocal) {
message.reactions = reactions;
rootScope.dispatchEvent('message_reactions', {message, changedResults: []});
rootScope.dispatchEvent('messages_reactions', [{message, changedResults: []}]);
return Promise.resolve();
}

View File

@ -76,9 +76,9 @@ export type BroadcastEvents = {
//'history_request': void,
'message_edit': {storage: MessagesStorage, peerId: PeerId, mid: number},
'message_views': {peerId: PeerId, mid: number, views: number},
'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number, message: MyMessage},
'message_reactions': {message: Message.message, changedResults: ReactionCount[]},
'messages_views': {peerId: PeerId, mid: number, views: number}[],
'messages_reactions': {message: Message.message, changedResults: ReactionCount[]}[],
'messages_pending': void,
'messages_read': void,
'messages_downloaded': {peerId: PeerId, mids: number[]},