diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 9b19af97..df88dc34 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -1,8 +1,8 @@ import Recorder from '../../../public/recorder.min'; import { isTouchSupported } from "../../helpers/touchSupport"; import appChatsManager from '../../lib/appManagers/appChatsManager'; -import appDocsManager from "../../lib/appManagers/appDocsManager"; -import appImManager from "../../lib/appManagers/appImManager"; +import appDocsManager, { MyDocument } from "../../lib/appManagers/appDocsManager"; +import appImManager, { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appPeersManager from '../../lib/appManagers/appPeersManager'; import appWebPagesManager from "../../lib/appManagers/appWebPagesManager"; @@ -11,9 +11,9 @@ import apiManager from "../../lib/mtproto/mtprotoworker"; import opusDecodeController from "../../lib/opusDecodeController"; import { RichTextProcessor } from "../../lib/richtextprocessor"; import rootScope from '../../lib/rootScope'; -import { blurActiveElement, cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, isSelectionSingle, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; +import { blurActiveElement, cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; -import emoticonsDropdown from "../emoticonsDropdown"; +import emoticonsDropdown, { EmoticonsDropdown } from "../emoticonsDropdown"; import PopupCreatePoll from "../popupCreatePoll"; import PopupForward from '../popupForward'; import PopupNewMedia from '../popupNewMedia'; @@ -24,12 +24,97 @@ import { wrapReply } from "../wrappers"; import InputField from '../inputField'; import { MessageEntity } from '../../layer'; import ButtonIcon from '../buttonIcon'; +import appStickersManager from '../../lib/appManagers/appStickersManager'; +import SetTransition from '../singleTransition'; +import { SuperStickerRenderer } from '../emoticonsDropdown/tabs/stickers'; +import LazyLoadQueue from '../lazyLoadQueue'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; +export class StickersHelper { + private container: HTMLElement; + private stickersContainer: HTMLElement; + private scrollable: Scrollable; + private superStickerRenderer: SuperStickerRenderer; + private lazyLoadQueue: LazyLoadQueue; + private lastEmoticon = ''; + + constructor(private appendTo: HTMLElement) { + + } + + public checkEmoticon(emoticon: string) { + if(this.lastEmoticon == emoticon) return; + + if(this.lastEmoticon && !emoticon) { + if(this.container) { + SetTransition(this.container, 'is-visible', false, 200/* , () => { + this.stickersContainer.innerHTML = ''; + } */); + } + } + + this.lastEmoticon = emoticon; + if(this.lazyLoadQueue) { + this.lazyLoadQueue.clear(); + } + + if(!emoticon) { + return; + } + + appStickersManager.getStickersByEmoticon(emoticon) + .then(stickers => { + if(this.lastEmoticon != emoticon) { + return; + } + + if(this.init) { + this.init(); + this.init = null; + } + + this.stickersContainer.innerHTML = ''; + this.lazyLoadQueue.clear(); + if(stickers.length) { + stickers.forEach(sticker => { + this.stickersContainer.append(this.superStickerRenderer.renderSticker(sticker as MyDocument)); + }); + } + + SetTransition(this.container, 'is-visible', true, 200); + this.scrollable.scrollTop = 0; + }); + } + + private init() { + this.container = document.createElement('div'); + this.container.classList.add('stickers-helper', 'z-depth-1'); + + this.stickersContainer = document.createElement('div'); + this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers'); + this.stickersContainer.addEventListener('click', (e) => { + if(!findUpClassName(e.target, 'super-sticker')) { + return; + } + + appImManager.chatInputC.clearInput(); + EmoticonsDropdown.onMediaClick(e); + }); + + this.container.append(this.stickersContainer); + + this.scrollable = new Scrollable(this.container); + this.lazyLoadQueue = new LazyLoadQueue(); + this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP); + + this.appendTo.append(this.container); + } +} + export class MarkupTooltip { public container: HTMLElement; private wrapper: HTMLElement; @@ -323,6 +408,7 @@ export class ChatInput { private canUndoFromHTML = ''; public markupTooltip: MarkupTooltip; + public stickersHelper: StickersHelper; constructor() { if(!isTouchSupported) { @@ -382,6 +468,8 @@ export class ChatInput { this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement; this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement; + this.stickersHelper = new StickersHelper(this.replyElements.container.parentElement); + try { this.recorder = new Recorder({ //encoderBitRate: 32, @@ -789,11 +877,24 @@ export class ChatInput { } */ //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); - const value = this.messageInput.innerText; + //const value = this.messageInput.innerText; + const value = getRichValue(this.messageInput); const entities = RichTextProcessor.parseEntities(value); //console.log('messageInput entities', entities); + if(this.stickersHelper) { + let emoticon = ''; + if(entities.length && entities[0]._ == 'messageEntityEmoji') { + const entity = entities[0]; + if(entity.length == value.length && !entity.offset) { + emoticon = value; + } + } + + this.stickersHelper.checkEmoticon(emoticon); + } + const html = this.messageInput.innerHTML; if(this.canRedoFromHTML && html != this.canRedoFromHTML && !this.lockRedo) { this.canRedoFromHTML = ''; @@ -1014,6 +1115,8 @@ export class ChatInput { this.executedHistory.length = 0; this.canUndoFromHTML = ''; } + + this.onMessageInput(); } public isInputEmpty() { diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index 690c1ec2..5020546d 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -1,7 +1,7 @@ import emoticonsDropdown, { EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab } from ".."; import { readBlobAsText } from "../../../helpers/blob"; import mediaSizes from "../../../helpers/mediaSizes"; -import { StickerSet } from "../../../layer"; +import { MessagesAllStickers, StickerSet } from "../../../layer"; import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager"; import appDownloadManager from "../../../lib/appManagers/appDownloadManager"; import appStickersManager from "../../../lib/appManagers/appStickersManager"; @@ -10,12 +10,106 @@ import apiManager from "../../../lib/mtproto/mtprotoworker"; import { RichTextProcessor } from "../../../lib/richtextprocessor"; import rootScope from "../../../lib/rootScope"; import animationIntersector from "../../animationIntersector"; -import { LazyLoadQueueRepeat } from "../../lazyLoadQueue"; +import LazyLoadQueue, { LazyLoadQueueRepeat } from "../../lazyLoadQueue"; import { putPreloader, renderImageFromUrl } from "../../misc"; import Scrollable, { ScrollableX } from "../../scrollable"; import StickyIntersector from "../../stickyIntersector"; import { wrapSticker } from "../../wrappers"; +export class SuperStickerRenderer { + lazyLoadQueue: LazyLoadQueueRepeat; + animatedDivs: Set = new Set(); + + constructor(private regularLazyLoadQueue: LazyLoadQueue, private group: string) { + this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => { + if(!visible) { + this.processInvisibleDiv(target as HTMLDivElement); + } + }); + } + + renderSticker(doc: MyDocument, div?: HTMLDivElement) { + if(!div) { + div = document.createElement('div'); + div.classList.add('grid-item', 'super-sticker'); + + if(doc.sticker == 2) { + this.animatedDivs.add(div); + + this.lazyLoadQueue.observe({ + div, + load: this.processVisibleDiv + }); + } + } + + // * This will wrap only a thumb + wrapSticker({ + doc, + div, + lazyLoadQueue: this.regularLazyLoadQueue, + group: this.group, + onlyThumb: doc.sticker == 2 + }); + + return div; + } + + checkAnimationContainer = (div: HTMLElement, visible: boolean) => { + //console.error('checkAnimationContainer', div, visible); + const players = animationIntersector.getAnimations(div); + players.forEach(player => { + if(!visible) { + animationIntersector.checkAnimation(player, true, true); + } else { + animationIntersector.checkAnimation(player, false); + } + }); + }; + + processVisibleDiv = (div: HTMLElement) => { + const docID = div.dataset.docID; + const doc = appDocsManager.getDoc(docID); + + const size = mediaSizes.active.esgSticker.width; + + const promise = wrapSticker({ + doc, + div: div as HTMLDivElement, + width: size, + height: size, + lazyLoadQueue: null, + group: this.group, + onlyThumb: false, + play: true, + loop: true + }); + + promise.then(() => { + //clearTimeout(timeout); + this.checkAnimationContainer(div, this.lazyLoadQueue.intersector.isVisible(div)); + }); + + /* let timeout = window.setTimeout(() => { + console.error('processVisibleDiv timeout', div, doc); + }, 1e3); */ + + return promise; + }; + + processInvisibleDiv = (div: HTMLElement) => { + const docID = div.dataset.docID; + const doc = appDocsManager.getDoc(docID); + + //console.log('STICKER INvisible:', /* div, */docID); + + this.checkAnimationContainer(div, false); + + div.innerHTML = ''; + this.renderSticker(doc, div as HTMLDivElement); + }; +} + export default class StickersTab implements EmoticonsTab { public content: HTMLElement; private stickersDiv: HTMLElement; @@ -38,14 +132,13 @@ export default class StickersTab implements EmoticonsTab { private stickyIntersector: StickyIntersector; - private animatedDivs: Set = new Set(); - private lazyLoadQueue: LazyLoadQueueRepeat; + private superStickerRenderer: SuperStickerRenderer; categoryPush(categoryDiv: HTMLElement, categoryTitle: string, promise: Promise, prepend?: boolean) { //if((docs.length % 5) != 0) categoryDiv.classList.add('not-full'); const itemsDiv = document.createElement('div'); - itemsDiv.classList.add('category-items'); + itemsDiv.classList.add('category-items', 'super-stickers'); const titleDiv = document.createElement('div'); titleDiv.classList.add('category-title'); @@ -60,7 +153,7 @@ export default class StickersTab implements EmoticonsTab { promise.then(documents => { documents.forEach(doc => { //if(doc._ == 'documentEmpty') return; - itemsDiv.append(this.renderSticker(doc)); + itemsDiv.append(this.superStickerRenderer.renderSticker(doc)); }); if(this.queueCategoryPush.length) { @@ -80,33 +173,6 @@ export default class StickersTab implements EmoticonsTab { }); } - renderSticker(doc: MyDocument, div?: HTMLDivElement) { - if(!div) { - div = document.createElement('div'); - div.classList.add('grid-item'); - - if(doc.sticker == 2) { - this.animatedDivs.add(div); - - this.lazyLoadQueue.observe({ - div, - load: this.processVisibleDiv - }); - } - } - - // * This will wrap only a thumb - wrapSticker({ - doc, - div, - lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue, - group: EMOTICONSSTICKERGROUP, - onlyThumb: doc.sticker == 2 - }); - - return div; - } - async renderStickerSet(set: StickerSet.stickerSet, prepend = false) { const categoryDiv = document.createElement('div'); categoryDiv.classList.add('sticker-category'); @@ -169,60 +235,6 @@ export default class StickersTab implements EmoticonsTab { } } - checkAnimationContainer = (div: HTMLElement, visible: boolean) => { - //console.error('checkAnimationContainer', div, visible); - const players = animationIntersector.getAnimations(div); - players.forEach(player => { - if(!visible) { - animationIntersector.checkAnimation(player, true, true); - } else { - animationIntersector.checkAnimation(player, false); - } - }); - }; - - processVisibleDiv = (div: HTMLElement) => { - const docID = div.dataset.docID; - const doc = appDocsManager.getDoc(docID); - - const size = mediaSizes.active.esgSticker.width; - - const promise = wrapSticker({ - doc, - div: div as HTMLDivElement, - width: size, - height: size, - lazyLoadQueue: null, - group: EMOTICONSSTICKERGROUP, - onlyThumb: false, - play: true, - loop: true - }); - - promise.then(() => { - //clearTimeout(timeout); - this.checkAnimationContainer(div, this.lazyLoadQueue.intersector.isVisible(div)); - }); - - /* let timeout = window.setTimeout(() => { - console.error('processVisibleDiv timeout', div, doc); - }, 1e3); */ - - return promise; - }; - - processInvisibleDiv = (div: HTMLElement) => { - const docID = div.dataset.docID; - const doc = appDocsManager.getDoc(docID); - - //console.log('STICKER INvisible:', /* div, */docID); - - this.checkAnimationContainer(div, false); - - div.innerHTML = ''; - this.renderSticker(doc, div as HTMLDivElement); - }; - init() { this.content = document.getElementById('content-stickers'); //let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement; @@ -299,16 +311,10 @@ export default class StickersTab implements EmoticonsTab { this.categoryPush(this.recentDiv, 'Recent', Promise.resolve(this.recentStickers), true); }), - apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => { - let stickers: { - _: 'messages.allStickers', - hash: number, - sets: Array - } = res as any; - + appStickersManager.getAllStickers().then((res) => { preloader.remove(); - for(let set of stickers.sets) { + for(let set of (res as MessagesAllStickers.messagesAllStickers).sets) { this.renderStickerSet(set); } }) @@ -316,13 +322,9 @@ export default class StickersTab implements EmoticonsTab { this.mounted = true; }); - this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => { - if(!visible) { - this.processInvisibleDiv(target as HTMLDivElement); - } - }); + this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP); - emoticonsDropdown.addLazyLoadQueueRepeat(this.lazyLoadQueue, this.processInvisibleDiv); + emoticonsDropdown.addLazyLoadQueueRepeat(this.superStickerRenderer.lazyLoadQueue, this.superStickerRenderer.processInvisibleDiv); /* setInterval(() => { // @ts-ignore @@ -342,7 +344,7 @@ export default class StickersTab implements EmoticonsTab { let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`); if(!div) { - div = this.renderSticker(doc); + div = this.superStickerRenderer.renderSticker(doc); } const items = this.recentDiv.querySelector('.category-items'); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index f2c9c7e4..bca6596c 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -565,7 +565,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1; if((doc.thumbs?.length || doc.stickerCachedThumbs) && !div.firstElementChild && (!doc.downloaded || stickerType == 2 || onlyThumb)/* && doc.thumbs[0]._ != 'photoSizeEmpty' */) { - const thumb = doc.stickerCachedThumbs && doc.stickerCachedThumbs[toneIndex] || doc.thumbs[0]; + let thumb = doc.stickerCachedThumbs && doc.stickerCachedThumbs[toneIndex] || doc.thumbs[0]; //console.log('wrap sticker', thumb, div); @@ -581,23 +581,33 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o renderImageFromUrl(img, thumb.url, afterRender); } else if('bytes' in thumb) { if(thumb._ == 'photoPathSize') { - //if(!doc.w) console.error('no w', doc); - div.innerHTML = ` - - `; - } else if(toneIndex <= 0) { + if(thumb.bytes.length) { + //if(!doc.w) console.error('no w', doc); + const d = appPhotosManager.getPathFromPhotoPathSize(thumb); + /* if(d == 'Mz' || d.includes('151,48,349,33z')) { + console.error('no path', doc); + } */ + div.innerHTML = ` + + `; + } else { + thumb = doc.thumbs.find(t => (t as PhotoSize.photoStrippedSize).bytes?.length) || thumb; + } + } + + if(thumb && thumb._ != 'photoPathSize' && toneIndex <= 0) { img = new Image(); if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { - renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); + renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb as PhotoSize.photoStrippedSize, true), afterRender); } else { - webpWorkerController.convert(doc.id, thumb.bytes as Uint8Array).then(bytes => { - thumb.bytes = bytes; + webpWorkerController.convert(doc.id, (thumb as PhotoSize.photoStrippedSize).bytes as Uint8Array).then(bytes => { + (thumb as PhotoSize.photoStrippedSize).bytes = bytes; doc.pFlags.stickerThumbConverted = true; if(middleware && !middleware()) return; if(!div.childElementCount) { - renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); + renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb as PhotoSize.photoStrippedSize, true), afterRender); } }).catch(() => {}); } @@ -610,14 +620,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o const r = () => { if(div.childElementCount || (middleware && !middleware())) return; - renderImageFromUrl(img, thumb.url, afterRender); + renderImageFromUrl(img, (thumb as PhotoSize.photoStrippedSize).url, afterRender); }; - if(thumb.url) { + if((thumb as PhotoSize.photoStrippedSize).url) { r(); return Promise.resolve(); } else { - return appDocsManager.getThumbURL(doc, thumb).promise.then(r); + return appDocsManager.getThumbURL(doc, thumb as PhotoSize.photoStrippedSize).promise.then(r); } }; diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index 0cdfbca1..a9127952 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -1,4 +1,4 @@ -import { Document, InputFileLocation, InputStickerSet, MessagesRecentStickers, MessagesStickerSet, PhotoSize, StickerSet, StickerSetCovered } from '../../layer'; +import { Document, InputFileLocation, InputStickerSet, MessagesAllStickers, MessagesFeaturedStickers, MessagesFoundStickerSets, MessagesRecentStickers, MessagesStickers, MessagesStickerSet, PhotoSize, StickerPack, StickerSet, StickerSetCovered } from '../../layer'; import { Modify } from '../../types'; import apiManager from '../mtproto/mtprotoworker'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; @@ -15,18 +15,8 @@ export class AppStickersManager { private saveSetsTimeout: number; - private hashes: Partial<{ - featured: Partial<{hash: number, result: StickerSetCovered[]}>, - search: { - [query: string]: Partial<{ - hash: number, - result: StickerSetCovered[] - }> - } - }> = { - featured: {}, - search: {} - }; + private getStickerSetPromises: {[setID: string]: Promise} = {}; + private getStickersByEmoticonsPromises: {[emoticon: string]: Promise} = {}; constructor() { appStateManager.getState().then(({stickerSets}) => { @@ -74,25 +64,29 @@ export class AppStickersManager { }> = {}): Promise { if(this.stickerSets[set.id] && !params.overwrite && this.stickerSets[set.id].documents?.length) return this.stickerSets[set.id]; - const stickerSet = await apiManager.invokeApi('messages.getStickerSet', { + if(this.getStickerSetPromises[set.id]) { + return this.getStickerSetPromises[set.id]; + } + + const promise = this.getStickerSetPromises[set.id] = apiManager.invokeApi('messages.getStickerSet', { stickerset: this.getStickerSetInput(set) }); - + + const stickerSet = await promise; + delete this.getStickerSetPromises[set.id]; this.saveStickerSet(stickerSet, set.id); - return stickerSet as any; + return stickerSet; } public async getRecentStickers(): Promise> { - const res = await apiManager.invokeApi('messages.getRecentStickers') as MessagesRecentStickers.messagesRecentStickers; + const res = await apiManager.invokeApiHashable('messages.getRecentStickers') as MessagesRecentStickers.messagesRecentStickers; - if(res._ == 'messages.recentStickers') { - this.saveStickers(res.stickers); - } + this.saveStickers(res.stickers); - return res as any; + return res; } public getAnimatedEmojiSticker(emoji: string) { @@ -185,21 +179,13 @@ export class AppStickersManager { } public async getFeaturedStickers() { - const res = await apiManager.invokeApi('messages.getFeaturedStickers', { - hash: this.hashes.featured?.hash || 0 - }); + const res = await apiManager.invokeApiHashable('messages.getFeaturedStickers') as MessagesFeaturedStickers.messagesFeaturedStickers; - const hashed = this.hashes.featured ?? (this.hashes.featured = {}); - if(res._ != 'messages.featuredStickersNotModified') { - hashed.hash = res.hash; - hashed.result = res.sets; - } - - hashed.result.forEach(covered => { + res.sets.forEach(covered => { this.saveStickerSet({set: covered.set, documents: [], packs: []}, covered.set.id); }); - return hashed.result; + return res.sets; } public async toggleStickerSet(set: StickerSet.stickerSet) { @@ -231,20 +217,13 @@ export class AppStickersManager { public async searchStickerSets(query: string, excludeFeatured = true) { const flags = excludeFeatured ? 1 : 0; - const res = await apiManager.invokeApi('messages.searchStickerSets', { + const res = await apiManager.invokeApiHashable('messages.searchStickerSets', { flags, exclude_featured: excludeFeatured || undefined, - q: query, - hash: this.hashes.search[query]?.hash || 0 - }); + q: query + }) as MessagesFoundStickerSets.messagesFoundStickerSets; - const hashed = this.hashes.search[query] ?? (this.hashes.search[query] = {}); - if(res._ != 'messages.foundStickerSetsNotModified') { - hashed.hash = res.hash; - hashed.result = res.sets; - } - - hashed.result.forEach(covered => { + res.sets.forEach(covered => { this.saveStickerSet({set: covered.set, documents: [], packs: []}, covered.set.id); }); @@ -252,12 +231,60 @@ export class AppStickersManager { for(let id in this.stickerSets) { const {set} = this.stickerSets[id]; - if(set.title.toLowerCase().includes(query.toLowerCase()) && !hashed.result.find(c => c.set.id == set.id)) { + if(set.title.toLowerCase().includes(query.toLowerCase()) && !res.sets.find(c => c.set.id == set.id)) { foundSaved.push({_: 'stickerSetCovered', set, cover: null}); } } - return hashed.result.concat(foundSaved); + return res.sets.concat(foundSaved); + } + + public getAllStickers() { + return apiManager.invokeApiHashable('messages.getAllStickers'); + } + + public preloadStickerSets() { + return this.getAllStickers().then(allStickers => { + return Promise.all((allStickers as MessagesAllStickers.messagesAllStickers).sets.map(set => this.getStickerSet(set))); + }); + } + + public getStickersByEmoticon(emoticon: string) { + if(this.getStickersByEmoticonsPromises[emoticon]) return this.getStickersByEmoticonsPromises[emoticon]; + + return this.getStickersByEmoticonsPromises[emoticon] = Promise.all([ + apiManager.invokeApiHashable('messages.getStickers', { + emoticon + }), + this.preloadStickerSets(), + this.getRecentStickers() + ]).then(([messagesStickers, installedSets, recentStickers]) => { + const foundStickers = (messagesStickers as MessagesStickers.messagesStickers).stickers.map(sticker => appDocsManager.saveDoc(sticker)); + const cachedStickersAnimated: Document.document[] = [], cachedStickersStatic: Document.document[] = []; + + //console.log('getStickersByEmoticon', messagesStickers, installedSets, recentStickers); + + const iteratePacks = (packs: StickerPack.stickerPack[]) => { + for(const pack of packs) { + if(pack.emoticon.includes(emoticon)) { + for(const docID of pack.documents) { + const doc = appDocsManager.getDoc(docID); + (doc.animated ? cachedStickersAnimated : cachedStickersStatic).push(doc); + } + } + } + }; + + iteratePacks(recentStickers.packs); + + for(const set of installedSets) { + iteratePacks(set.packs); + } + + const stickers = [...new Set(cachedStickersAnimated.concat(cachedStickersStatic, foundStickers))]; + + return stickers; + }); } } diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 0ec014f5..85a18125 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -22,6 +22,15 @@ type Task = { const USEWORKERASWORKER = true; +type HashResult = { + hash: number, + result: any +}; + +type HashOptions = { + [queryJSON: string]: HashResult +}; + export class ApiManagerProxy extends CryptoWorkerMethods { public worker: Worker; public postMessage: (...args: any[]) => void; @@ -41,6 +50,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods { private log = logger('API-PROXY'); + private hashes: {[method: string]: HashOptions} = {}; + constructor() { super(); this.log('constructor'); @@ -230,6 +241,36 @@ export class ApiManagerProxy extends CryptoWorkerMethods { return this.performTaskWorker('invokeApi', method, params, o); } + public invokeApiHashable(method: T, params: Omit = {} as any, options: InvokeApiOptions = {}): Promise { + //console.log('will invokeApi:', method, params, options); + + const queryJSON = JSON.stringify(params); + let cached: HashResult; + if(this.hashes[method]) { + cached = this.hashes[method][queryJSON]; + if(cached) { + (params as any).hash = cached.hash; + } + } + + return this.performTaskWorker('invokeApi', method, params, options).then((result: any) => { + if(result._.includes('NotModified')) { + //this.log.warn('NotModified saved!', method, queryJSON); + return cached.result; + } + + if(result.hash) { + if(!this.hashes[method]) this.hashes[method] = {}; + this.hashes[method][queryJSON] = { + hash: result.hash, + result: result + }; + } + + return result; + }); + } + public setBaseDcID(dcID: number) { return this.performTaskWorker('setBaseDcID', dcID); } diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 051f9c14..9661ca96 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -768,7 +768,7 @@ $chat-helper-size: 39px; .btn-menu { padding: 8px 0; - right: -8px; + right: -11px; bottom: calc(100% + 16px); > div { @@ -1420,3 +1420,45 @@ $chat-helper-size: 39px; transition: none; } } + +.stickers-helper { + position: absolute !important; + bottom: calc(100% + 10px); + opacity: 0; + transition: opacity .2s ease-in-out; + overflow: hidden; + padding: 0 !important; + + > .scrollable { + position: relative; + max-height: 220px; + min-height: var(--esg-sticker-size); + } + + &-stickers { + display: flex; + flex-wrap: wrap; + } + + &-sticker { + position: relative; + width: var(--esg-sticker-size); + height: var(--esg-sticker-size); + margin: 5px; + + img { + width: 100%; + height: 100%; + } + } + + &:not(.is-visible) { + display: none; + } + + &.is-visible { + &:not(.backwards) { + opacity: 1; + } + } +} diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss index c9c734a9..a852c28b 100644 --- a/src/scss/partials/_emojiDropdown.scss +++ b/src/scss/partials/_emojiDropdown.scss @@ -12,19 +12,19 @@ @include respond-to(esg-top) { position: absolute !important; left: 1rem; - bottom: calc(85px); + bottom: 85px; width: 420px !important; height: 420px; max-height: 420px; box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14); z-index: 3; border-radius: 10px; - transition: all .2s ease-out; + transition: transform .2s ease-out; transform: scale(0); transform-origin: 0 100%; &.active { - transition: all .2s ease-in; + transition: transform .2s ease-in; transform: scale(1); } } @@ -225,29 +225,7 @@ grid-column-gap: 1px; justify-content: space-between; - > .grid-item { - html.no-touch &:hover { - border-radius: 12px; - background-color: var(--color-gray-hover); - } - - /* &:nth-child(5n+5) { - margin-right: 0; - } */ - - > img, > .rlottie { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - } - - > img { - animation: fade-in-opacity .2s ease forwards; - object-fit: contain; - } - } + } } diff --git a/src/scss/partials/_rightSidebar.scss b/src/scss/partials/_rightSidebar.scss index d217e7c2..fe29a4b5 100644 --- a/src/scss/partials/_rightSidebar.scss +++ b/src/scss/partials/_rightSidebar.scss @@ -613,6 +613,7 @@ &-sticker { width: 68px; height: 68px; + position: relative; //padding: 0 5px; &:hover { diff --git a/src/scss/style.scss b/src/scss/style.scss index 80f698dd..bfd82432 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -721,6 +721,38 @@ img.emoji { fill: rgba(0, 0, 0, .08); } +.super-stickers { + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px + grid-column-gap: 1px; + justify-content: space-between; +} + +.super-sticker { + html.no-touch &:hover { + border-radius: 12px; + background-color: var(--color-gray-hover); + } + + /* &:nth-child(5n+5) { + margin-right: 0; + } */ + + > img, > .rlottie { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + + > img { + animation: fade-in-opacity .2s ease forwards; + object-fit: contain; + } +} + .fade-in-transition { opacity: 1; transition: opacity .2s ease;