Browse Source

Fix jumping scroll again

master
Eduard Kuzmenko 2 years ago
parent
commit
97d645f1ea
  1. 15
      git-serve-server.js
  2. 18
      src/components/animationIntersector.ts
  3. 127
      src/components/chat/bubbles.ts
  4. 2
      src/components/chat/pinnedMessage.ts
  5. 16
      src/components/wrappers.ts
  6. 2
      src/lib/appManagers/appAvatarsManager.ts
  7. 136
      src/lib/appManagers/appMessagesManager.ts
  8. 2
      src/lib/appManagers/appReactionsManager.ts
  9. 4
      src/lib/rootScope.ts

15
git-serve-server.js

@ -40,20 +40,31 @@ for(const name of Object.keys(nets)) { @@ -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) {

18
src/components/animationIntersector.ts

@ -47,14 +47,24 @@ export class AnimationIntersector { @@ -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;

127
src/components/chat/bubbles.ts

@ -208,8 +208,6 @@ export default class ChatBubbles { @@ -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 { @@ -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 { @@ -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();
for(const {message, changedResults} of arr) {
if(this.peerId !== message.peerId) {
return;
}
const bubble = this.getBubbleByMessage(message);
if(!bubble) {
return;
}
const key = message.peerId + '_' + message.mid;
const set = REACTIONS_ELEMENTS.get(key);
if(set) {
for(const element of set) {
element.update(message, changedResults);
if(!scrollSaver) {
scrollSaver = new ScrollSaver(this.scrollable, true);
scrollSaver.save();
}
} else {
rootScope.dispatchEvent('missed_reactions_element', {message, changedResults});
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;
}

2
src/components/chat/pinnedMessage.ts

@ -343,7 +343,7 @@ export default class ChatPinnedMessage { @@ -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;

16
src/components/wrappers.ts

@ -451,13 +451,23 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -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});
}
}

2
src/lib/appManagers/appAvatarsManager.ts

@ -4,6 +4,7 @@ @@ -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 { @@ -220,4 +221,5 @@ export class AppAvatarsManager {
}
const appAvatarsManager = new AppAvatarsManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appAvatarsManager = appAvatarsManager);
export default appAvatarsManager;

136
src/lib/appManagers/appMessagesManager.ts

@ -15,8 +15,8 @@ import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePr @@ -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"; @@ -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 & { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();

2
src/lib/appManagers/appReactionsManager.ts

@ -320,7 +320,7 @@ export class AppReactionsManager { @@ -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();
}

4
src/lib/rootScope.ts

@ -76,9 +76,9 @@ export type BroadcastEvents = { @@ -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[]},

Loading…
Cancel
Save