diff --git a/src/components/animationIntersector.ts b/src/components/animationIntersector.ts index 1a33fd27..f2c8ad58 100644 --- a/src/components/animationIntersector.ts +++ b/src/components/animationIntersector.ts @@ -142,7 +142,7 @@ export class AnimationIntersector { animation.autoplay && (!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group) ) { - console.warn('play animation:', animation); + //console.warn('play animation:', animation); animation.play(); } } diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 34b842c4..adbb871e 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -40,8 +40,9 @@ import AudioElement from "../audio"; import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer"; import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import { FocusDirection } from "../../helpers/fastSmoothScroll"; -import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck"; +import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent } from "../../hooks/useHeavyAnimationCheck"; import { fastRaf } from "../../helpers/schedulers"; +import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise"; const IGNORE_ACTIONS = ['messageActionHistoryClear']; @@ -295,30 +296,35 @@ export default class ChatBubbles { this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => { const {peerId, mids} = e.detail; - - (mids as number[]).forEach(mid => { - /* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve(); - promise.then(() => { - - }); */ - this.needUpdate.forEachReverse((obj, idx) => { - if(obj.replyMid === mid, obj.replyToPeerId === peerId) { - const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0]; - - //this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]); - const bubble = this.bubbles[mid]; - if(!bubble) return; - - const message = this.chat.getMessage(mid); - - const repliedMessage = this.appMessagesManager.getMessageByPeer(obj.replyToPeerId, replyMid); - if(repliedMessage.deleted) { // ! чтобы не пыталось бесконечно загрузить удалённое сообщение - delete message.reply_to_mid; // ! WARNING! + + const middleware = this.getMiddleware(); + getHeavyAnimationPromise().then(() => { + if(!middleware()) return; + + (mids as number[]).forEach(mid => { + /* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve(); + promise.then(() => { + + }); */ + this.needUpdate.forEachReverse((obj, idx) => { + if(obj.replyMid === mid, obj.replyToPeerId === peerId) { + const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0]; + + //this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]); + const bubble = this.bubbles[mid]; + if(!bubble) return; + + const message = this.chat.getMessage(mid); + + const repliedMessage = this.appMessagesManager.getMessageByPeer(obj.replyToPeerId, replyMid); + if(repliedMessage.deleted) { // ! чтобы не пыталось бесконечно загрузить удалённое сообщение + delete message.reply_to_mid; // ! WARNING! + } + + this.renderMessage(message, true, false, bubble, false); + //this.renderMessage(message, true, true, bubble, false); } - - this.renderMessage(message, true, false, bubble, false); - //this.renderMessage(message, true, true, bubble, false); - } + }); }); }); }); @@ -1421,7 +1427,7 @@ export default class ChatBubbles { } public setMessagesQueuePromise() { - if(this.messagesQueuePromise) return; + if(this.messagesQueuePromise || !this.messagesQueue.length) return; this.messagesQueuePromise = new Promise((resolve, reject) => { setTimeout(() => { @@ -1525,7 +1531,7 @@ export default class ChatBubbles { const peerId = this.peerId; // * can't use 'message.pFlags.out' here because this check will be used to define side of message (left-right) - const our = message.fromId == rootScope.myId || (message.pFlags.out && this.appPeersManager.isMegagroup(this.peerId)); + const our = message.fromId === rootScope.myId || (message.pFlags.out && this.appPeersManager.isMegagroup(this.peerId)); const messageDiv = document.createElement('div'); messageDiv.classList.add('message'); @@ -1553,7 +1559,7 @@ export default class ChatBubbles { } } } else { - const save = ['is-highlighted']; + const save = ['is-highlighted', 'zoom-fade']; const wasClassNames = bubble.className.split(' '); const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c))); bubble.className = classNames.join(' '); @@ -1561,9 +1567,11 @@ export default class ChatBubbles { bubbleContainer = bubble.lastElementChild as HTMLDivElement; bubbleContainer.innerHTML = ''; //bubbleContainer.style.marginBottom = ''; + const animationDelay = bubbleContainer.style.animationDelay; bubbleContainer.style.cssText = ''; + bubbleContainer.style.animationDelay = animationDelay; - if(bubble == this.firstUnreadBubble) { + if(bubble === this.firstUnreadBubble) { bubble.classList.add('is-first-unread'); } @@ -2514,7 +2522,9 @@ export default class ChatBubbles { ////console.timeEnd('render history total'); - return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); + return getHeavyAnimationPromise().then(() => { + return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); + }); }, (err) => { this.log.error('getHistory error:', err); return false; @@ -2533,7 +2543,9 @@ export default class ChatBubbles { cached = true; this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad); processResult(result); - promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); + promise = getHeavyAnimationPromise().then(() => { + return this.performHistoryResult((result as HistoryResult).history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); + }); //return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); } @@ -2544,21 +2556,33 @@ export default class ChatBubbles { waitPromise.then(() => { if(rootScope.settings.animationsEnabled) { const mids = getObjectKeysAndSort(this.bubbles, 'desc').filter(mid => !additionMsgIds.includes(mid)); + const animationPromise = deferredPromise(); + + let lastMsDelay = 0; mids.forEach((mid, idx) => { const bubble = this.bubbles[mid]; + lastMsDelay = ((idx || 0.1) * 10); //if(idx || isSafari) { // ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL ! - bubble.style.animationDelay = ((idx || 0.1) * 10) + 'ms'; + bubble.style.animationDelay = lastMsDelay + 'ms'; //} bubble.classList.add('zoom-fade'); bubble.addEventListener('animationend', () => { bubble.style.animationDelay = ''; bubble.classList.remove('zoom-fade'); + + if(idx === (mids.length - 1)) { + animationPromise.resolve(); + } }, {once: true}); //this.log('supa', bubble); }); + + if(mids.length) { + dispatchHeavyAnimationEvent(animationPromise, lastMsDelay); + } } setTimeout(() => { diff --git a/src/helpers/animation.ts b/src/helpers/animation.ts index adf9db82..5485e77a 100644 --- a/src/helpers/animation.ts +++ b/src/helpers/animation.ts @@ -19,6 +19,7 @@ export function cancelAnimationByKey(key: AnimationInstanceKey) { const instance = getAnimationInstance(key); if(instance) { instance.isCancelled = true; + instance.deferred.resolve(); instances.delete(key); } } @@ -31,7 +32,9 @@ export function animateSingle(tick: Function, key: AnimationInstanceKey, instanc } fastRaf(() => { - if(instance.isCancelled) return; + if(instance.isCancelled) { + return; + } if(tick()) { animateSingle(tick, key, instance); diff --git a/src/helpers/fastSmoothScroll.ts b/src/helpers/fastSmoothScroll.ts index b090724c..f1479420 100644 --- a/src/helpers/fastSmoothScroll.ts +++ b/src/helpers/fastSmoothScroll.ts @@ -95,7 +95,7 @@ function scrollWithJs( switch(position) { case 'start': - path = (elementPosition - margin) - scrollPosition; + path = elementPosition - margin; break; case 'end': //path = (elementTop + elementHeight + margin) - containerHeight; diff --git a/src/hooks/useHeavyAnimationCheck.ts b/src/hooks/useHeavyAnimationCheck.ts index 98aa8ba0..b3f7d9fd 100644 --- a/src/hooks/useHeavyAnimationCheck.ts +++ b/src/hooks/useHeavyAnimationCheck.ts @@ -4,31 +4,43 @@ import { AnyToVoidFunction } from '../types'; import ListenerSetter from '../helpers/listenerSetter'; import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise'; +import { pause } from '../helpers/schedulers'; const ANIMATION_START_EVENT = 'event-heavy-animation-start'; const ANIMATION_END_EVENT = 'event-heavy-animation-end'; let isAnimating = false; let heavyAnimationPromise: CancellablePromise = Promise.resolve(); -let lastAnimationPromise: Promise; +let promisesInQueue = 0; -export const dispatchHeavyAnimationEvent = (promise: Promise) => { +export const dispatchHeavyAnimationEvent = (promise: Promise, timeout?: number) => { if(!isAnimating) { heavyAnimationPromise = deferredPromise(); + document.dispatchEvent(new Event(ANIMATION_START_EVENT)); + isAnimating = true; + console.log('dispatchHeavyAnimationEvent: start'); } + + ++promisesInQueue; + console.log('dispatchHeavyAnimationEvent: attach promise, length:', promisesInQueue); - document.dispatchEvent(new Event(ANIMATION_START_EVENT)); - isAnimating = true; - lastAnimationPromise = promise; + const promises = [ + timeout !== undefined ? pause(timeout) : undefined, + promise.finally(() => {}) + ].filter(Boolean); - promise.then(() => { - if(lastAnimationPromise !== promise) { - return; - } + const perf = performance.now(); + Promise.race(promises).then(() => { + --promisesInQueue; + console.log('dispatchHeavyAnimationEvent: promise end, length:', promisesInQueue, performance.now() - perf); + if(!promisesInQueue) { + isAnimating = false; + promisesInQueue = 0; + document.dispatchEvent(new Event(ANIMATION_END_EVENT)); + heavyAnimationPromise.resolve(); - isAnimating = false; - document.dispatchEvent(new Event(ANIMATION_END_EVENT)); - heavyAnimationPromise.resolve(); + console.log('dispatchHeavyAnimationEvent: end'); + } }); return heavyAnimationPromise; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index ccdf1812..0f5b553c 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -126,7 +126,7 @@ $bubble-margin: .25rem; &.is-highlighted, &.is-selected { &:after { - top: #{2rem + $bubble-margin} !important; + top: calc(#{$bubble-margin / 2} + 30px); } } } @@ -370,7 +370,7 @@ $bubble-margin: .25rem; &.is-group-last { margin-bottom: #{$bubble-margin * 2}; - &:before, &:after { + &:after { bottom: -#{$bubble-margin}; } @@ -386,7 +386,7 @@ $bubble-margin: .25rem; } &.is-group-first { - &:before, &:after { + &:after { top: -#{$bubble-margin}; } }