diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index b5fcc4f9..1fbcec0d 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -112,6 +112,7 @@ import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount import PopupPayment from '../popups/payment'; import isInDOM from '../../helpers/dom/isInDOM'; import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb'; +import attachStickerViewerListeners from '../stickerViewer'; const USE_MEDIA_TAILS = false; const IGNORE_ACTIONS: Set = new Set([ @@ -667,6 +668,7 @@ export default class ChatBubbles { }); }); + attachStickerViewerListeners({listenTo: this.scrollable.container, listenerSetter: this.listenerSetter}); attachClickEvent(this.scrollable.container, this.onBubblesClick, {listenerSetter: this.listenerSetter}); // this.listenerSetter.add(this.bubblesContainer)('click', this.onBubblesClick/* , {capture: true, passive: false} */); @@ -902,7 +904,7 @@ export default class ChatBubbles { }); this.listenerSetter.add(rootScope)('dialogs_multiupdate', (dialogs) => { - if(dialogs[this.peerId]) { + if(dialogs.has(this.peerId)) { this.chat.input.setUnreadCount(); } }); diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 537c15fa..3ad79bc6 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -735,7 +735,7 @@ export default class ChatInput { }); this.listenerSetter.add(rootScope)('dialogs_multiupdate', (dialogs) => { - if(dialogs[this.chat.peerId]) { + if(dialogs.has(this.chat.peerId)) { if(this.startParam === BOT_START_PARAM) { this.setStartParam(); } else { // updateNewMessage comes earlier than dialog appers diff --git a/src/components/chat/reactionsMenu.ts b/src/components/chat/reactionsMenu.ts index 32e572ff..e4891d64 100644 --- a/src/components/chat/reactionsMenu.ts +++ b/src/components/chat/reactionsMenu.ts @@ -105,6 +105,7 @@ export class ChatReactionsMenu { callbackify(result, (reactions) => { if(!middleware() || !reactions.length) return; reactions.forEach((reaction) => { + if(reaction.pFlags.premium && !rootScope.premium) return; this.renderReaction(reaction); }); diff --git a/src/components/chat/stickersHelper.ts b/src/components/chat/stickersHelper.ts index c5539600..fc3bddb1 100644 --- a/src/components/chat/stickersHelper.ts +++ b/src/components/chat/stickersHelper.ts @@ -4,6 +4,7 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import ListenerSetter from '../../helpers/listenerSetter'; import mediaSizes from '../../helpers/mediaSizes'; import preloadAnimatedEmojiSticker from '../../helpers/preloadAnimatedEmojiSticker'; import {MyDocument} from '../../lib/appManagers/appDocsManager'; @@ -14,6 +15,7 @@ import {EmoticonsDropdown} from '../emoticonsDropdown'; import {SuperStickerRenderer} from '../emoticonsDropdown/tabs/stickers'; import LazyLoadQueue from '../lazyLoadQueue'; import Scrollable from '../scrollable'; +import attachStickerViewerListeners from '../stickerViewer'; import AutocompleteHelper from './autocompleteHelper'; import AutocompleteHelperController from './autocompleteHelperController'; @@ -22,6 +24,7 @@ export default class StickersHelper extends AutocompleteHelper { private superStickerRenderer: SuperStickerRenderer; private lazyLoadQueue: LazyLoadQueue; private onChangeScreen: () => void; + private listenerSetter: ListenerSetter; constructor( appendTo: HTMLElement, @@ -52,6 +55,9 @@ export default class StickersHelper extends AutocompleteHelper { if(this.onChangeScreen) { mediaSizes.removeEventListener('changeScreen', this.onChangeScreen); this.onChangeScreen = undefined; + + this.listenerSetter.removeAll(); + this.listenerSetter = undefined; } rootScope.dispatchEvent('choosing_sticker', false); @@ -105,6 +111,9 @@ export default class StickersHelper extends AutocompleteHelper { this.list.style.width = width + 'px'; }; mediaSizes.addEventListener('changeScreen', this.onChangeScreen); + + this.listenerSetter = new ListenerSetter(); + attachStickerViewerListeners({listenTo: this.container, listenerSetter: this.listenerSetter}); } this.onChangeScreen(); diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index 08363d58..8a03bcc2 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -32,6 +32,8 @@ import createStickersContextMenu from '../../../helpers/dom/createStickersContex import findUpAsChild from '../../../helpers/dom/findUpAsChild'; import forEachReverse from '../../../helpers/array/forEachReverse'; import {MTAppConfig} from '../../../lib/mtproto/appConfig'; +import attachStickerViewerListeners from '../../stickerViewer'; +import ListenerSetter from '../../../helpers/listenerSetter'; export class SuperStickerRenderer { public lazyLoadQueue: LazyLoadQueueRepeat; @@ -567,6 +569,8 @@ export default class StickersTab implements EmoticonsTab { emoticonsDropdown.addEventListener('opened', resizeCategories); + attachStickerViewerListeners({listenTo: this.content, listenerSetter: new ListenerSetter()}); + createStickersContextMenu({ listenTo: this.content, verifyRecent: (target) => !!findUpAsChild(target, this.categories['recent'].elements.items), diff --git a/src/components/popups/stickers.ts b/src/components/popups/stickers.ts index d9e1da3b..00b87d60 100644 --- a/src/components/popups/stickers.ts +++ b/src/components/popups/stickers.ts @@ -21,6 +21,7 @@ import {toastNew} from '../toast'; import setInnerHTML from '../../helpers/dom/setInnerHTML'; import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText'; import createStickersContextMenu from '../../helpers/dom/createStickersContextMenu'; +import attachStickerViewerListeners from '../stickerViewer'; const ANIMATION_GROUP: AnimationItemGroup = 'STICKERS-POPUP'; @@ -64,6 +65,8 @@ export default class PopupStickers extends PopupElement { isStickerPack: true }); + attachStickerViewerListeners({listenTo: this.stickersDiv, listenerSetter: this.listenerSetter}); + this.loadStickerSet(); } diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 867fa3a8..1f3c7d1e 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -229,15 +229,16 @@ export default class ProgressivePreloader { this.attachPromise(promise); } + let useRafs = 0; if(this.detached || this.preloader.parentElement !== elem) { - const useRafs = isInDOM(this.preloader) ? 1 : 2; + useRafs = isInDOM(this.preloader) ? 1 : 2; if(this.preloader.parentElement !== elem) { elem[this.attachMethod](this.preloader); } - - SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME, undefined, useRafs); } + SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME, undefined, useRafs); + if(this.cancelable && reset) { this.setProgress(0); } @@ -253,7 +254,7 @@ export default class ProgressivePreloader { // return; - if(this.preloader && this.preloader.parentElement) { + if(this.preloader?.parentElement) { /* setTimeout(() => */// fastRaf(() => { /* if(!this.detached) return; this.detached = true; */ diff --git a/src/components/sidebarRight/tabs/stickers.ts b/src/components/sidebarRight/tabs/stickers.ts index 798bacc3..05144a43 100644 --- a/src/components/sidebarRight/tabs/stickers.ts +++ b/src/components/sidebarRight/tabs/stickers.ts @@ -19,6 +19,7 @@ import {attachClickEvent} from '../../../helpers/dom/clickEvent'; import forEachReverse from '../../../helpers/array/forEachReverse'; import setInnerHTML from '../../../helpers/dom/setInnerHTML'; import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText'; +import attachStickerViewerListeners from '../../stickerViewer'; export default class AppStickersTab extends SliderSuperTab { private inputSearch: InputSearch; @@ -41,6 +42,8 @@ export default class AppStickersTab extends SliderSuperTab { this.setsDiv.classList.add('sticker-sets'); this.scrollable.append(this.setsDiv); + attachStickerViewerListeners({listenTo: this.setsDiv, listenerSetter: this.listenerSetter}); + attachClickEvent(this.setsDiv, (e) => { const sticker = findUpClassName(e.target, 'sticker-set-sticker'); if(sticker) { diff --git a/src/components/stickerViewer.ts b/src/components/stickerViewer.ts new file mode 100644 index 00000000..16ae7fb1 --- /dev/null +++ b/src/components/stickerViewer.ts @@ -0,0 +1,303 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import IS_TOUCH_SUPPORTED from '../environment/touchSupport'; +import cancelEvent from '../helpers/dom/cancelEvent'; +import {simulateClickEvent, attachClickEvent} from '../helpers/dom/clickEvent'; +import findUpClassName from '../helpers/dom/findUpClassName'; +import getVisibleRect from '../helpers/dom/getVisibleRect'; +import ListenerSetter from '../helpers/listenerSetter'; +import {makeMediaSize} from '../helpers/mediaSize'; +import {getMiddleware} from '../helpers/middleware'; +import {doubleRaf} from '../helpers/schedulers'; +import pause from '../helpers/schedulers/pause'; +import windowSize from '../helpers/windowSize'; +import {MyDocument} from '../lib/appManagers/appDocsManager'; +import getStickerEffectThumb from '../lib/appManagers/utils/stickers/getStickerEffectThumb'; +import wrapEmojiText from '../lib/richTextProcessor/wrapEmojiText'; +import lottieLoader from '../lib/rlottie/lottieLoader'; +import RLottiePlayer from '../lib/rlottie/rlottiePlayer'; +import rootScope from '../lib/rootScope'; +import animationIntersector, {AnimationItemGroup} from './animationIntersector'; +import SetTransition from './singleTransition'; +import {wrapSticker} from './wrappers'; +import {STICKER_EFFECT_MULTIPLIER} from './wrappers/sticker'; + +let hasViewer = false; +export default function attachStickerViewerListeners({listenTo, listenerSetter}: { + listenerSetter: ListenerSetter, + listenTo: HTMLElement +}) { + if(IS_TOUCH_SUPPORTED) { + return; + } + + const managers = rootScope.managers; + const findClassName = 'media-sticker-wrapper'; + + listenerSetter.add(listenTo)('mousedown', (e) => { + if(hasViewer || e.buttons > 1 || e.button !== 0) return; + let mediaContainer = findUpClassName(e.target, findClassName); + if(!mediaContainer) { + return; + } + + // const img: HTMLImageElement = mediaContainer.querySelector('img.media-sticker'); + + const docId = mediaContainer.dataset.docId; + if(!docId) { + return; + } + + const className = 'sticker-viewer'; + const group: AnimationItemGroup = 'STICKER-VIEWER'; + const openDuration = 200; + const switchDuration = 200; + const previousGroup = animationIntersector.getOnlyOnePlayableGroup(); + const _middleware = getMiddleware(); + let container: HTMLElement, previousTransformer: HTMLElement; + + const doThatSticker = async({mediaContainer, doc, middleware, lockGroups, isSwitching}: { + mediaContainer: HTMLElement, + doc: MyDocument, + middleware: () => boolean, + lockGroups?: boolean, + isSwitching?: boolean + }) => { + const effectThumb = getStickerEffectThumb(doc); + const mediaRect: DOMRect = mediaContainer.getBoundingClientRect(); + const s = makeMediaSize(doc.w, doc.h); + const size = effectThumb ? 280 : 360; + const boxSize = makeMediaSize(size, size); + const fitted = mediaRect.width === mediaRect.height ? boxSize : s.aspectFitted(boxSize); + + const bubble = findUpClassName(mediaContainer, 'bubble'); + const isOut = bubble ? bubble.classList.contains('is-out') : true; + + const transformer = document.createElement('div'); + transformer.classList.add(className + '-transformer'); + + const stickerContainer = document.createElement('div'); + stickerContainer.classList.add(className + '-sticker'); + /* transformer.style.width = */stickerContainer.style.width = fitted.width + 'px'; + /* transformer.style.height = */stickerContainer.style.height = fitted.height + 'px'; + + const stickerEmoji = document.createElement('div'); + stickerEmoji.classList.add(className + '-emoji'); + stickerEmoji.append(wrapEmojiText(doc.stickerEmojiRaw)); + + if(effectThumb) { + const margin = (size * STICKER_EFFECT_MULTIPLIER - size) / 3 * (isOut ? 1 : -1); + transformer.classList.add('has-effect'); + // const property = `--margin-${isOut ? 'right' : 'left'}`; + // stickerContainer.style.setProperty(property, `${margin * 2}px`); + transformer.style.setProperty('--translateX', `${margin}px`); + stickerEmoji.style.setProperty('--translateX', `${-margin}px`); + } + + const overflowElement = findUpClassName(mediaContainer, 'scrollable'); + const visibleRect = getVisibleRect(mediaContainer, overflowElement, true, mediaRect); + if(visibleRect.overflow.vertical || visibleRect.overflow.horizontal) { + stickerContainer.classList.add('is-overflow'); + } + + // if(img) { + // const ratio = img.naturalWidth / img.naturalHeight; + // if((mediaRect.width / mediaRect.height).toFixed(1) !== ratio.toFixed(1)) { + + // mediaRect = mediaRect.toJSON(); + // } + // } + + const rect = mediaContainer.getBoundingClientRect(); + const scaleX = rect.width / fitted.width; + const scaleY = rect.height / fitted.height; + const transformX = rect.left - (windowSize.width - rect.width) / 2; + const transformY = rect.top - (windowSize.height - rect.height) / 2; + transformer.style.transform = `translate(${transformX}px, ${transformY}px) scale(${scaleX}, ${scaleY})`; + if(isSwitching) transformer.classList.add('is-switching'); + transformer.append(stickerContainer, stickerEmoji); + container.append(transformer); + + const player = await wrapSticker({ + doc, + div: stickerContainer, + group, + width: fitted.width, + height: fitted.height, + play: false, + loop: true, + middleware, + managers, + needFadeIn: false, + isOut, + withThumb: false, + relativeEffect: true, + loopEffect: true + }).then(({render}) => render); + if(!middleware()) return; + + if(!container.parentElement) { + document.body.append(container); + } + + const firstFramePromise = player instanceof RLottiePlayer ? + new Promise((resolve) => player.addEventListener('firstFrame', resolve, {once: true})) : + Promise.resolve(); + await Promise.all([firstFramePromise, doubleRaf()]); + await pause(0); // ! need it because firstFrame will be called just from the loop + if(!middleware()) return; + + if(lockGroups) { + animationIntersector.setOnlyOnePlayableGroup(group); + animationIntersector.checkAnimations(true); + } + + if(player instanceof RLottiePlayer) { + const prevPlayer = lottieLoader.getAnimation(mediaContainer); + player.curFrame = prevPlayer.curFrame; + player.play(); + await new Promise((resolve) => { + let i = 0; + const c = () => { + if(++i === 2) { + resolve(); + player.removeEventListener('enterFrame', c); + } + }; + + player.addEventListener('enterFrame', c); + }); + player.pause(); + } else if(player instanceof HTMLVideoElement) { + player.currentTime = (mediaContainer.querySelector('video') as HTMLVideoElement).currentTime; + } + + return { + ready: () => { + if(player instanceof RLottiePlayer || player instanceof HTMLVideoElement) { + player.play(); + } + + if(effectThumb) { + simulateClickEvent(stickerContainer); + } + }, + transformer + }; + }; + + const timeout = window.setTimeout(async() => { + document.removeEventListener('mousemove', onMousePreMove); + + container = document.createElement('div'); + container.classList.add(className); + hasViewer = true; + + const middleware = _middleware.get(); + const doc = await managers.appDocsManager.getDoc(docId); + if(!middleware()) return; + + let result: Awaited>; + try { + result = await doThatSticker({ + doc, + mediaContainer, + middleware, + lockGroups: true + }); + if(!result) return; + } catch(err) { + return; + } + + const {ready, transformer} = result; + + previousTransformer = transformer; + + SetTransition(container, 'is-visible', true, openDuration, () => { + if(!middleware()) return; + ready(); + }); + + document.addEventListener('mousemove', onMouseMove); + }, 125); + + const onMouseMove = async(e: MouseEvent) => { + const newMediaContainer = findUpClassName(e.target, 'media-sticker-wrapper'); + if(!newMediaContainer || mediaContainer === newMediaContainer) { + return; + } + + const docId = newMediaContainer.dataset.docId; + if(!docId) { + return; + } + + mediaContainer = newMediaContainer; + _middleware.clean(); + const middleware = _middleware.get(); + + const doc = await managers.appDocsManager.getDoc(docId); + if(!middleware()) return; + + let r: Awaited>; + try { + r = await doThatSticker({ + doc, + mediaContainer, + middleware, + isSwitching: true + }); + if(!r) return; + } catch(err) { + return; + } + + const {ready, transformer} = r; + + const _previousTransformer = previousTransformer; + SetTransition(_previousTransformer, 'is-switching', true, switchDuration, () => { + _previousTransformer.remove(); + }); + + previousTransformer = transformer; + + SetTransition(transformer, 'is-switching', false, switchDuration, () => { + if(!middleware()) return; + ready(); + }); + }; + + const onMousePreMove = (e: MouseEvent) => { + if(!findUpClassName(e.target, findClassName)) { + onMouseUp(); + } + }; + + const onMouseUp = () => { + clearTimeout(timeout); + _middleware.clean(); + + if(container) { + SetTransition(container, 'is-visible', false, openDuration, () => { + container.remove(); + animationIntersector.setOnlyOnePlayableGroup(previousGroup); + animationIntersector.checkAnimations(false); + hasViewer = false; + }); + + attachClickEvent(document.body, cancelEvent, {capture: true, once: true}); + } + + document.removeEventListener('mousemove', onMousePreMove); + document.removeEventListener('mousemove', onMouseMove); + }; + + document.addEventListener('mousemove', onMousePreMove); + document.addEventListener('mouseup', onMouseUp, {once: true}); + }); +} diff --git a/src/components/wrappers/document.ts b/src/components/wrappers/document.ts index 3b5e60b7..ee09936e 100644 --- a/src/components/wrappers/document.ts +++ b/src/components/wrappers/document.ts @@ -199,12 +199,20 @@ export default async function wrapDocument({message, withTime, fontWeight, voice let downloadDiv: HTMLElement, preloader: ProgressivePreloader = null; const onLoad = () => { + docDiv.classList.remove('downloading'); + + if(/* !hasThumb || */(doc.size > MAX_FILE_SAVE_SIZE && !uploadFileName)) { + preloader.setManual(); + preloader.attach(downloadDiv); + preloader.preloader.classList.add('manual'); + preloader.setDownloadFunction(load); + return; + } + if(doc.size <= MAX_FILE_SAVE_SIZE) { docDiv.classList.add('downloaded'); } - docDiv.classList.remove('downloading'); - if(downloadDiv) { if(downloadDiv !== icoDiv) { const _downloadDiv = downloadDiv; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 815986cc..ec7731b9 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -518,8 +518,7 @@ export class AppDialogsManager { }); rootScope.addEventListener('dialogs_multiupdate', (dialogs) => { - for(const peerId in dialogs) { - const dialog = dialogs[peerId]; + for(const [peerId, dialog] of dialogs) { this.updateDialog(dialog); if(this.processContact) { diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 1848f960..7742e082 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -415,267 +415,6 @@ export class AppImManager extends EventListenerBase<{ }, useRafs); }; - let hasViewer = false; - !IS_TOUCH_SUPPORTED && document.addEventListener('mousedown', (e) => { - if(hasViewer || e.buttons > 1 || e.button !== 0) return; - let mediaContainer = findUpClassName(e.target, 'media-sticker-wrapper'); - if(!mediaContainer) { - return; - } - - // const img: HTMLImageElement = mediaContainer.querySelector('img.media-sticker'); - - const docId = mediaContainer.dataset.docId; - if(!docId) { - return; - } - - const className = 'sticker-viewer'; - const group: AnimationItemGroup = 'STICKER-VIEWER'; - const openDuration = 200; - const switchDuration = 200; - const previousGroup = animationIntersector.getOnlyOnePlayableGroup(); - const _middleware = getMiddleware(); - let container: HTMLElement, previousTransformer: HTMLElement; - - const doThatSticker = async({mediaContainer, doc, middleware, lockGroups, isSwitching}: { - mediaContainer: HTMLElement, - doc: MyDocument, - middleware: () => boolean, - lockGroups?: boolean, - isSwitching?: boolean - }) => { - const effectThumb = getStickerEffectThumb(doc); - const mediaRect: DOMRect = mediaContainer.getBoundingClientRect(); - const s = makeMediaSize(doc.w, doc.h); - const size = effectThumb ? 280 : 360; - const boxSize = makeMediaSize(size, size); - const fitted = mediaRect.width === mediaRect.height ? boxSize : s.aspectFitted(boxSize); - - const bubble = findUpClassName(mediaContainer, 'bubble'); - const isOut = bubble ? bubble.classList.contains('is-out') : true; - - const transformer = document.createElement('div'); - transformer.classList.add(className + '-transformer'); - - const stickerContainer = document.createElement('div'); - stickerContainer.classList.add(className + '-sticker'); - /* transformer.style.width = */stickerContainer.style.width = fitted.width + 'px'; - /* transformer.style.height = */stickerContainer.style.height = fitted.height + 'px'; - - const stickerEmoji = document.createElement('div'); - stickerEmoji.classList.add(className + '-emoji'); - stickerEmoji.append(wrapEmojiText(doc.stickerEmojiRaw)); - - if(effectThumb) { - const margin = (size * STICKER_EFFECT_MULTIPLIER - size) / 3 * (isOut ? 1 : -1); - transformer.classList.add('has-effect'); - // const property = `--margin-${isOut ? 'right' : 'left'}`; - // stickerContainer.style.setProperty(property, `${margin * 2}px`); - transformer.style.setProperty('--translateX', `${margin}px`); - stickerEmoji.style.setProperty('--translateX', `${-margin}px`); - } - - const overflowElement = findUpClassName(mediaContainer, 'scrollable'); - const visibleRect = getVisibleRect(mediaContainer, overflowElement, true, mediaRect); - if(visibleRect.overflow.vertical || visibleRect.overflow.horizontal) { - stickerContainer.classList.add('is-overflow'); - } - - // if(img) { - // const ratio = img.naturalWidth / img.naturalHeight; - // if((mediaRect.width / mediaRect.height).toFixed(1) !== ratio.toFixed(1)) { - - // mediaRect = mediaRect.toJSON(); - // } - // } - - const rect = mediaContainer.getBoundingClientRect(); - const scaleX = rect.width / fitted.width; - const scaleY = rect.height / fitted.height; - const transformX = rect.left - (windowSize.width - rect.width) / 2; - const transformY = rect.top - (windowSize.height - rect.height) / 2; - transformer.style.transform = `translate(${transformX}px, ${transformY}px) scale(${scaleX}, ${scaleY})`; - if(isSwitching) transformer.classList.add('is-switching'); - transformer.append(stickerContainer, stickerEmoji); - container.append(transformer); - - const player = await wrapSticker({ - doc, - div: stickerContainer, - group, - width: fitted.width, - height: fitted.height, - play: false, - loop: true, - middleware, - managers: this.managers, - needFadeIn: false, - isOut, - withThumb: false, - relativeEffect: true, - loopEffect: true - }).then(({render}) => render); - if(!middleware()) return; - - if(!container.parentElement) { - document.body.append(container); - } - - const firstFramePromise = player instanceof RLottiePlayer ? - new Promise((resolve) => player.addEventListener('firstFrame', resolve, {once: true})) : - Promise.resolve(); - await Promise.all([firstFramePromise, doubleRaf()]); - await pause(0); // ! need it because firstFrame will be called just from the loop - if(!middleware()) return; - - if(lockGroups) { - animationIntersector.setOnlyOnePlayableGroup(group); - animationIntersector.checkAnimations(true); - } - - if(player instanceof RLottiePlayer) { - const prevPlayer = lottieLoader.getAnimation(mediaContainer); - player.curFrame = prevPlayer.curFrame; - player.play(); - await new Promise((resolve) => { - let i = 0; - const c = () => { - if(++i === 2) { - resolve(); - player.removeEventListener('enterFrame', c); - } - }; - - player.addEventListener('enterFrame', c); - }); - player.pause(); - } else if(player instanceof HTMLVideoElement) { - player.currentTime = (mediaContainer.querySelector('video') as HTMLVideoElement).currentTime; - } - - return { - ready: () => { - if(player instanceof RLottiePlayer || player instanceof HTMLVideoElement) { - player.play(); - } - - if(effectThumb) { - simulateClickEvent(stickerContainer); - } - }, - transformer - }; - }; - - const timeout = window.setTimeout(async() => { - document.removeEventListener('mousemove', onMousePreMove); - - container = document.createElement('div'); - container.classList.add(className); - hasViewer = true; - - const middleware = _middleware.get(); - const doc = await this.managers.appDocsManager.getDoc(docId); - if(!middleware()) return; - - let result: Awaited>; - try { - result = await doThatSticker({ - doc, - mediaContainer, - middleware, - lockGroups: true - }); - if(!result) return; - } catch(err) { - return; - } - - const {ready, transformer} = result; - - previousTransformer = transformer; - - SetTransition(container, 'is-visible', true, openDuration, () => { - if(!middleware()) return; - ready(); - }); - - document.addEventListener('mousemove', onMouseMove); - }, 100); - - const onMouseMove = async(e: MouseEvent) => { - const newMediaContainer = findUpClassName(e.target, 'media-sticker-wrapper'); - if(!newMediaContainer || mediaContainer === newMediaContainer) { - return; - } - - const docId = newMediaContainer.dataset.docId; - if(!docId) { - return; - } - - mediaContainer = newMediaContainer; - _middleware.clean(); - const middleware = _middleware.get(); - - const doc = await this.managers.appDocsManager.getDoc(docId); - if(!middleware()) return; - - let r: Awaited>; - try { - r = await doThatSticker({ - doc, - mediaContainer, - middleware, - isSwitching: true - }); - if(!r) return; - } catch(err) { - return; - } - - const {ready, transformer} = r; - - const _previousTransformer = previousTransformer; - SetTransition(_previousTransformer, 'is-switching', true, switchDuration, () => { - _previousTransformer.remove(); - }); - - previousTransformer = transformer; - - SetTransition(transformer, 'is-switching', false, switchDuration, () => { - if(!middleware()) return; - ready(); - }); - }; - - const onMousePreMove = () => { - clearTimeout(timeout); - }; - - const onMouseUp = () => { - clearTimeout(timeout); - _middleware.clean(); - - if(container) { - SetTransition(container, 'is-visible', false, openDuration, () => { - container.remove(); - animationIntersector.setOnlyOnePlayableGroup(previousGroup); - animationIntersector.checkAnimations(false); - hasViewer = false; - }); - - attachClickEvent(document.body, cancelEvent, {capture: true, once: true}); - } - - document.removeEventListener('mousemove', onMouseMove); - }; - - document.addEventListener('mousemove', onMousePreMove, {once: true}); - document.addEventListener('mouseup', onMouseUp, {once: true}); - }); - rootScope.addEventListener('sticker_updated', ({type, faved}) => { if(type === 'faved') { toastNew({ diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 65ae9290..40534f5e 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -196,7 +196,7 @@ export class AppMessagesManager extends AppManager { public migratedToFrom: {[peerId: PeerId]: PeerId} = {}; private newDialogsHandlePromise: Promise; - private newDialogsToHandle: {[peerId: PeerId]: Dialog} = {}; + private newDialogsToHandle: Map = new Map(); public newUpdatesAfterReloadToHandle: {[peerId: PeerId]: Set} = {}; private notificationsHandlePromise = 0; @@ -1907,7 +1907,7 @@ export class AppMessagesManager extends AppManager { let maxSeenIdIncremented = offsetDate ? true : false; let hasPrepend = false; - const noIdsDialogs: {[peerId: PeerId]: Dialog} = {}; + const noIdsDialogs: Map = new Map(); const setFolderId: REAL_FOLDER_ID = folderId === GLOBAL_FOLDER_ID ? FOLDER_ID_ALL : folderId; const saveGlobalOffset = folderId === GLOBAL_FOLDER_ID; forEachReverse((dialogsResult.dialogs as Dialog[]), (dialog) => { @@ -1941,16 +1941,15 @@ export class AppMessagesManager extends AppManager { // ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет // ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога if(!getServerMessageId(dialog.read_inbox_max_id) && !getServerMessageId(dialog.read_outbox_max_id)) { - noIdsDialogs[dialog.peerId] = dialog; + noIdsDialogs.set(dialog.peerId, dialog); this.log.error('noIdsDialogs', dialog, params); } }); - const keys = Object.keys(noIdsDialogs); - if(keys.length) { + if(noIdsDialogs.size) { // setTimeout(() => { // test bad situation - const peerIds = keys.map((key) => key.toPeerId()); + const peerIds = [...noIdsDialogs.keys()]; const promises = peerIds.map((peerId) => this.reloadConversation(peerId)); Promise.all(promises).then(() => { this.rootScope.dispatchEvent('dialogs_multiupdate', noIdsDialogs); @@ -1988,7 +1987,7 @@ export class AppMessagesManager extends AppManager { if(hasPrepend) { this.scheduleHandleNewDialogs(); } else { - this.rootScope.dispatchEvent('dialogs_multiupdate', {}); + this.rootScope.dispatchEvent('dialogs_multiupdate', new Map()); } const dialogs = (dialogsResult as MessagesDialogs.messagesDialogsSlice).dialogs; @@ -2402,31 +2401,7 @@ export class AppMessagesManager extends AppManager { } return this.doFlushHistory(this.appPeersManager.getInputPeerById(peerId), justClear, revoke).then(() => { - [ - this.historiesStorage, - this.threadsStorage, - this.searchesStorage, - this.pinnedMessages, - this.pendingAfterMsgs, - this.pendingTopMsgs - ].forEach((s) => { - delete s[peerId]; - }); - - const m = this.needSingleMessages.get(peerId); - if(m) { - m.clear(); - } - - [ - this.messagesStorageByPeerId, - this.scheduledMessagesStorage - ].forEach((s) => { - const ss = s[peerId]; - if(ss) { - ss.clear(); - } - }); + this.flushStoragesByPeerId(peerId); if(justClear) { this.rootScope.dispatchEvent('dialog_flush', {peerId, dialog: this.getDialogOnly(peerId)}); @@ -2445,6 +2420,38 @@ export class AppMessagesManager extends AppManager { }); } + private flushStoragesByPeerId(peerId: PeerId) { + [ + this.historiesStorage, + this.threadsStorage, + this.searchesStorage, + this.pinnedMessages, + this.pendingAfterMsgs, + this.pendingTopMsgs + ].forEach((s) => { + delete s[peerId]; + }); + + const needSingleMessages = this.needSingleMessages.get(peerId); + if(needSingleMessages) { + for(const [mid, promise] of needSingleMessages) { + promise.resolve(this.generateEmptyMessage(mid)); + } + + needSingleMessages.clear(); + } + + [ + this.messagesStorageByPeerId, + this.scheduledMessagesStorage + ].forEach((s) => { + const ss = s[peerId]; + if(ss) { + ss.clear(); + } + }); + } + public hidePinnedMessages(peerId: PeerId) { return Promise.all([ this.appStateManager.getState(), @@ -3558,12 +3565,11 @@ export class AppMessagesManager extends AppManager { private handleNewDialogs = () => { let newMaxSeenId = 0; - const obj = this.newDialogsToHandle; - for(const peerId in obj) { - const dialog = obj[peerId]; + const map = this.newDialogsToHandle; + for(const [peerId, dialog] of map) { if(!dialog) { this.reloadConversation(peerId.toPeerId()); - delete obj[peerId]; + map.delete(peerId); } else { this.dialogsStorage.pushDialog(dialog); if(!this.appPeersManager.isChannel(peerId.toPeerId())) { @@ -3578,13 +3584,13 @@ export class AppMessagesManager extends AppManager { this.incrementMaxSeenId(newMaxSeenId); } - this.rootScope.dispatchEvent('dialogs_multiupdate', obj); - this.newDialogsToHandle = {}; + this.rootScope.dispatchEvent('dialogs_multiupdate', map); + this.newDialogsToHandle.clear(); }; public scheduleHandleNewDialogs(peerId?: PeerId, dialog?: Dialog) { if(peerId !== undefined) { - this.newDialogsToHandle[peerId] = dialog; + this.newDialogsToHandle.set(peerId, dialog); } if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise; @@ -4263,7 +4269,7 @@ export class AppMessagesManager extends AppManager { releaseUnreadCount(); this.dialogsStorage.setDialogToState(dialog); - this.rootScope.dispatchEvent('dialogs_multiupdate', {[peerId]: dialog}); + this.rootScope.dispatchEvent('dialogs_multiupdate', new Map([[peerId, dialog]])); } }; @@ -4321,10 +4327,8 @@ export class AppMessagesManager extends AppManager { }); if(isTopMessage || (message as Message.message).grouped_id) { - const updatedDialogs: {[peerId: PeerId]: Dialog} = {}; - updatedDialogs[peerId] = dialog; this.dialogsStorage.setDialogToState(dialog); - this.rootScope.dispatchEvent('dialogs_multiupdate', updatedDialogs); + this.rootScope.dispatchEvent('dialogs_multiupdate', new Map([[peerId, dialog]])); } } }; @@ -4599,9 +4603,7 @@ export class AppMessagesManager extends AppManager { private onUpdateChannelReload = (update: Update.updateChannelReload) => { const peerId = update.channel_id.toPeerId(true); - this.dialogsStorage.dropDialog(peerId); - - delete this.historiesStorage[peerId]; + this.flushStoragesByPeerId(peerId); this.reloadConversation(peerId).then(() => { this.rootScope.dispatchEvent('history_reload', peerId); }); @@ -5651,9 +5653,7 @@ export class AppMessagesManager extends AppManager { }); if(this.isMessageIsTopMessage(message)) { - this.rootScope.dispatchEvent('dialogs_multiupdate', { - [peerId]: this.getDialogOnly(peerId) - }); + this.rootScope.dispatchEvent('dialogs_multiupdate', new Map([[peerId, this.getDialogOnly(peerId)]])); } } diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index 13aaf84c..a89891dd 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -18,6 +18,7 @@ import fixEmoji from '../richTextProcessor/fixEmoji'; import ctx from '../../environment/ctx'; import {getEnvironment} from '../../environment/utils'; import getDocumentInput from './utils/docs/getDocumentInput'; +import getStickerEffectThumb from './utils/stickers/getStickerEffectThumb'; const CACHE_TIME = 3600e3; @@ -673,7 +674,8 @@ export class AppStickersManager extends AppManager { const stickers = [...new Set(cachedStickersAnimated.concat(cachedStickersStatic, foundStickers))]/* .filter((doc) => !doc.animated) */; forEachReverse(stickers, (sticker, idx, arr) => { - if(sticker.sticker === 3 && !getEnvironment().IS_WEBM_SUPPORTED) { + if((sticker.sticker === 3 && !getEnvironment().IS_WEBM_SUPPORTED) || + (!this.rootScope.premium && getStickerEffectThumb(sticker))) { arr.splice(idx, 1); } }); diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 859f44fa..65005c31 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -58,7 +58,7 @@ export type BroadcastEvents = { // 'dialog_top': Dialog, 'dialog_notify_settings': Dialog, // 'dialog_order': {dialog: Dialog, pos: number}, - 'dialogs_multiupdate': {[peerId: PeerId]: Dialog}, + 'dialogs_multiupdate': Map, 'history_append': {storageKey: MessagesStorageKey, message: Message.message}, 'history_update': {storageKey: MessagesStorageKey, message: MyMessage, sequential?: boolean}, diff --git a/src/lib/serviceWorker/download.ts b/src/lib/serviceWorker/download.ts index f7d8fba2..2a364a51 100644 --- a/src/lib/serviceWorker/download.ts +++ b/src/lib/serviceWorker/download.ts @@ -26,6 +26,8 @@ const downloadMap: Map = new Map(); const DOWNLOAD_ERROR = makeError('UNKNOWN'); const DOWNLOAD_TEST = false; +(self as any).downloadMap = downloadMap; + type A = Parameters['addMultipleEventsListeners']>[0]; const events: A = { diff --git a/src/lib/serviceWorker/index.service.ts b/src/lib/serviceWorker/index.service.ts index 3602b636..0d195d10 100644 --- a/src/lib/serviceWorker/index.service.ts +++ b/src/lib/serviceWorker/index.service.ts @@ -20,7 +20,7 @@ import {getWindowClients} from '../../helpers/context'; import {MessageSendPort} from '../mtproto/superMessagePort'; import handleDownload from './download'; -export const log = logger('SW', LogTypes.Error | LogTypes.Debug | LogTypes.Log | LogTypes.Warn); +export const log = logger('SW', LogTypes.Error | LogTypes.Debug | LogTypes.Log | LogTypes.Warn, true); const ctx = self as any as ServiceWorkerGlobalScope; // #if !MTPROTO_SW diff --git a/src/lib/serviceWorker/stream.ts b/src/lib/serviceWorker/stream.ts index 63706cbf..6434718c 100644 --- a/src/lib/serviceWorker/stream.ts +++ b/src/lib/serviceWorker/stream.ts @@ -18,7 +18,7 @@ const deferredPromises: Map { return cacheStorage.timeoutOperation((cache) => { diff --git a/src/lib/storages/dialogs.ts b/src/lib/storages/dialogs.ts index 07af9b23..d052ab1e 100644 --- a/src/lib/storages/dialogs.ts +++ b/src/lib/storages/dialogs.ts @@ -361,16 +361,18 @@ export default class DialogsStorage extends AppManager { private setDialogIndexInFilter(dialog: Dialog, indexKey: ReturnType, filter: MyDialogFilter) { let index: number; - if(REAL_FOLDERS.has(filter.id)) { - index = getDialogIndex(dialog, indexKey); - } else if(this.filtersStorage.testDialogForFilter(dialog, filter)) { + const isRealFolder = REAL_FOLDERS.has(filter.id); + /* if(isRealFolder) { + // index = getDialogIndex(dialog, indexKey); + index = this.generateIndexForDialog(dialog, true); + } else */if(this.filtersStorage.testDialogForFilter(dialog, filter)) { const pinnedIndex = filter.pinnedPeerIds.indexOf(dialog.peerId); if(pinnedIndex !== -1) { index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinnedPeerIds.length - 1 - pinnedIndex), true); - } else if(dialog.pFlags?.pinned) { - index = this.generateIndexForDialog(dialog, true); + } else if(dialog.pFlags?.pinned || isRealFolder) { + index = this.generateIndexForDialog(dialog, true, undefined, !isRealFolder); } else { - index = getDialogIndex(dialog); + index = getDialogIndex(dialog) ?? this.generateIndexForDialog(dialog, true); } } @@ -541,13 +543,13 @@ export default class DialogsStorage extends AppManager { } } - public generateIndexForDialog(dialog: Dialog, justReturn = false, message?: MyMessage) { - // if(!justReturn) { - // return; - // } + public generateIndexForDialog(dialog: Dialog, justReturn?: boolean, message?: MyMessage, noPinnedOrderUpdate?: boolean) { + if(!justReturn) { + return; + } let topDate = 0, isPinned: boolean; - if(dialog.pFlags.pinned && !justReturn) { + if(dialog.pFlags.pinned && !noPinnedOrderUpdate) { topDate = this.generateDialogPinnedDate(dialog); isPinned = true; } else { @@ -784,7 +786,7 @@ export default class DialogsStorage extends AppManager { // this.appMessagesManager.log('applyConversation', dialogsResult); - const updatedDialogs: {[peerId: PeerId]: Dialog} = {}; + const updatedDialogs: Map = new Map(); (dialogsResult.dialogs as Dialog[]).forEach((dialog) => { const peerId = this.appPeersManager.getPeerId(dialog.peer); let topMessage = dialog.top_message; @@ -805,7 +807,7 @@ export default class DialogsStorage extends AppManager { if(topMessage || dialog.draft?._ === 'draftMessage') { this.saveDialog(dialog); - updatedDialogs[peerId] = dialog; + updatedDialogs.set(peerId, dialog); } else { this.dropDialogWithEvent(peerId); } @@ -823,7 +825,7 @@ export default class DialogsStorage extends AppManager { } }); - if(Object.keys(updatedDialogs).length) { + if(updatedDialogs.size) { this.rootScope.dispatchEvent('dialogs_multiupdate', updatedDialogs); } } @@ -1158,6 +1160,8 @@ export default class DialogsStorage extends AppManager { const handleOrder = (order: PeerId[]) => { this.resetPinnedOrder(folderId); + this.pinnedOrders[folderId].push(...order); + this.savePinnedOrders(); order.reverse(); // index must be higher order.forEach((peerId) => { newPinned[peerId] = true;