From be8976e0d98408ef52add48c171ee861bf06d5ae Mon Sep 17 00:00:00 2001 From: morethanwords Date: Wed, 2 Sep 2020 01:54:11 +0300 Subject: [PATCH] Animated sticker panel Fix lazyLoadQueue for panel --- src/components/animationIntersector.ts | 11 +- src/components/emoticonsDropdown/index.ts | 35 ++++-- .../emoticonsDropdown/tabs/stickers.ts | 116 +++++++++++++++++- src/components/lazyLoadQueue.ts | 2 +- src/components/wrappers.ts | 2 +- 5 files changed, 148 insertions(+), 18 deletions(-) diff --git a/src/components/animationIntersector.ts b/src/components/animationIntersector.ts index 8ef1192e..6e897163 100644 --- a/src/components/animationIntersector.ts +++ b/src/components/animationIntersector.ts @@ -50,16 +50,17 @@ export class AnimationIntersector { }); } - public getAnimation(element: HTMLElement) { - for(let group in this.byGroups) { - for(let player of this.byGroups[group]) { + public getAnimations(element: HTMLElement) { + const found: AnimationItem[] = []; + for(const group in this.byGroups) { + for(const player of this.byGroups[group]) { if(player.el == element) { - return player; + found.push(player); } } } - return null; + return found; } public addAnimation(animation: RLottiePlayer | HTMLVideoElement, group = '') { diff --git a/src/components/emoticonsDropdown/index.ts b/src/components/emoticonsDropdown/index.ts index ed5f1ffa..b9d3f9eb 100644 --- a/src/components/emoticonsDropdown/index.ts +++ b/src/components/emoticonsDropdown/index.ts @@ -40,6 +40,18 @@ export class EmoticonsDropdown { public toggleEl: HTMLElement; private displayTimeout: number; + public events: { + onClose: Array<() => void>, + onCloseAfter: Array<() => void>, + onOpen: Array<() => void>, + onOpenAfter: Array<() => void> + } = { + onClose: [], + onCloseAfter: [], + onOpen: [], + onOpenAfter: [] + }; + constructor() { this.element = document.getElementById('emoji-dropdown') as HTMLDivElement; @@ -174,26 +186,30 @@ export class EmoticonsDropdown { } if((this.element.style.display && enable === undefined) || enable) { - this.element.style.display = ''; - void this.element.offsetLeft; // reflow - this.element.classList.add('active'); + this.events.onOpen.forEach(cb => cb()); EmoticonsDropdown.lazyLoadQueue.lockIntersection(); //EmoticonsDropdown.lazyLoadQueue.unlock(); animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); + this.element.style.display = ''; + void this.element.offsetLeft; // reflow + this.element.classList.add('active'); + clearTimeout(this.displayTimeout); this.displayTimeout = setTimeout(() => { animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); EmoticonsDropdown.lazyLoadQueue.unlockIntersection(); + + this.events.onOpenAfter.forEach(cb => cb()); }, touchSupport ? 0 : 200); /* if(touchSupport) { this.restoreScroll(); } */ } else { - this.element.classList.remove('active'); - + this.events.onClose.forEach(cb => cb()); + EmoticonsDropdown.lazyLoadQueue.lockIntersection(); //EmoticonsDropdown.lazyLoadQueue.lock(); @@ -201,14 +217,17 @@ export class EmoticonsDropdown { animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); + this.element.classList.remove('active'); + clearTimeout(this.displayTimeout); this.displayTimeout = setTimeout(() => { this.element.style.display = 'none'; // теперь можно убрать visible, чтобы они не включились после фокуса animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); - EmoticonsDropdown.lazyLoadQueue.unlockIntersection(); + + this.events.onCloseAfter.forEach(cb => cb()); }, touchSupport ? 0 : 200); /* if(touchSupport) { @@ -291,7 +310,9 @@ export class EmoticonsDropdown { if(!target) return; - let fileID = target.dataset.docID; + const fileID = target.dataset.docID; + if(!fileID) return; + if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { /* dropdown.classList.remove('active'); toggleEl.classList.remove('active'); */ diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index 7edbf640..e2aeeec1 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -1,4 +1,4 @@ -import { EmoticonsTab, EMOTICONSSTICKERGROUP, EmoticonsDropdown } from ".."; +import emoticonsDropdown, { EmoticonsTab, EMOTICONSSTICKERGROUP, EmoticonsDropdown } from ".."; import { MTDocument } from "../../../types"; import Scrollable from "../../scrollable_new"; import { wrapSticker } from "../../wrappers"; @@ -11,6 +11,8 @@ import { RichTextProcessor } from "../../../lib/richtextprocessor"; import { $rootScope } from "../../../lib/utils"; import apiManager from "../../../lib/mtproto/mtprotoworker"; import StickyIntersector from "../../stickyIntersector"; +import appDocsManager from "../../../lib/appManagers/appDocsManager"; +import animationIntersector from "../../animationIntersector"; export default class StickersTab implements EmoticonsTab { public content: HTMLElement; @@ -33,6 +35,9 @@ export default class StickersTab implements EmoticonsTab { private stickyIntersector: StickyIntersector; + private animatedDivs: Set = new Set(); + private animatedIntersector: IntersectionObserver; + categoryPush(categoryDiv: HTMLElement, categoryTitle: string, promise: Promise, prepend?: boolean) { //if((docs.length % 5) != 0) categoryDiv.classList.add('not-full'); @@ -71,8 +76,16 @@ export default class StickersTab implements EmoticonsTab { }); } - renderSticker(doc: MTDocument) { - const div = document.createElement('div'); + renderSticker(doc: MTDocument, div?: HTMLDivElement) { + if(!div) { + div = document.createElement('div'); + + if(doc.sticker == 2) { + this.animatedDivs.add(div); + this.animatedIntersector.observe(div); + } + } + wrapSticker({ doc, div, @@ -242,6 +255,101 @@ export default class StickersTab implements EmoticonsTab { this.mounted = true; }); + const checkAnimationDiv = (div: HTMLDivElement) => { + const players = animationIntersector.getAnimations(div); + players.forEach(player => { + if(!visible.has(div)) { + animationIntersector.checkAnimation(player, true, true); + } else { + animationIntersector.checkAnimation(player, false); + } + }); + }; + + const processInvisibleDiv = (div: HTMLDivElement) => { + visible.delete(div); + //console.log('STICKER INvisible:', target, docID); + + const docID = div.dataset.docID; + const doc = appDocsManager.getDoc(docID); + + checkAnimationDiv(div); + + div.innerHTML = ''; + this.renderSticker(doc, div); + }; + + let locked = false; + const visible: Set = new Set(); + this.animatedIntersector = new IntersectionObserver((entries) => { + if(locked) { + return; + } + + entries.forEach(entry => { + const {target, isIntersecting} = entry; + + const div = target as HTMLDivElement; + const docID = div.dataset.docID; + const doc = appDocsManager.getDoc(docID); + if(isIntersecting) { + //console.log('STICKER visible:', target, docID); + if(visible.has(div)) { + return; + } + + visible.add(div); + + wrapSticker({ + doc, + div, + width: 80, + height: 80, + lazyLoadQueue: null, + group: EMOTICONSSTICKERGROUP, + onlyThumb: false, + play: true, + loop: true + }).then(() => { + checkAnimationDiv(div); + }); + } else { + processInvisibleDiv(div); + } + }); + + //animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP, false); + }); + + emoticonsDropdown.events.onClose.push(() => { + locked = true; + }); + + emoticonsDropdown.events.onCloseAfter.push(() => { + const divs = [...visible]; + + for(const div of divs) { + processInvisibleDiv(div); + } + }); + + emoticonsDropdown.events.onOpenAfter.push(() => { + locked = false; + + // refresh + this.animatedIntersector.disconnect(); + const divs = [...this.animatedDivs]; + for(const div of divs) { + this.animatedIntersector.observe(div); + } + }); + + /* setInterval(() => { + // @ts-ignore + console.log('STICKERS RENDERED IN PANEL:', Object.values(lottieLoader.players).filter(p => p.width == 80).length); + }, .25e3); */ + + this.init = null; } @@ -255,7 +363,7 @@ export default class StickersTab implements EmoticonsTab { div = this.renderSticker(doc); } - const items = this.recentDiv.lastElementChild; + const items = this.recentDiv.querySelector('.category-items'); items.prepend(div); if(items.childElementCount > 20) { diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index 8f1bfa39..9be029d5 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -24,7 +24,7 @@ export default class LazyLoadQueue { if(noObserver) return; this.observer = new IntersectionObserver(entries => { - if(this.lockPromise) return; + if(this.lockPromise || this.intersectionLocked) return; const intersecting = entries.filter(entry => entry.isIntersecting); intersecting.forEachReverse(entry => { diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index b1e34714..af761539 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -578,7 +578,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o //appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => { //fetch(doc.url).then(res => res.json()).then(async(json) => { - appDocsManager.downloadDocNew(doc.id) + await appDocsManager.downloadDocNew(doc.id) .then(readBlobAsText) .then(JSON.parse) .then(async(json) => {