morethanwords
4 years ago
28 changed files with 1457 additions and 1455 deletions
@ -1,853 +0,0 @@ |
|||||||
import appImManager from "../lib/appManagers/appImManager"; |
|
||||||
import { renderImageFromUrl, putPreloader } from "./misc"; |
|
||||||
import lottieLoader from "../lib/lottieLoader"; |
|
||||||
//import Scrollable from "./scrollable";
|
|
||||||
import Scrollable from "./scrollable_new"; |
|
||||||
import { findUpTag, whichChild, calcImageInBox, emojiUnicode, $rootScope, cancelEvent, findUpClassName } from "../lib/utils"; |
|
||||||
import { RichTextProcessor } from "../lib/richtextprocessor"; |
|
||||||
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager"; |
|
||||||
//import apiManager from '../lib/mtproto/apiManager';
|
|
||||||
import apiManager from '../lib/mtproto/mtprotoworker'; |
|
||||||
import LazyLoadQueue from "./lazyLoadQueue"; |
|
||||||
import { wrapSticker, wrapVideo } from "./wrappers"; |
|
||||||
import appDocsManager from "../lib/appManagers/appDocsManager"; |
|
||||||
import ProgressivePreloader from "./preloader"; |
|
||||||
import Config, { touchSupport } from "../lib/config"; |
|
||||||
import { MTDocument } from "../types"; |
|
||||||
import animationIntersector from "./animationIntersector"; |
|
||||||
import appSidebarRight from "../lib/appManagers/appSidebarRight"; |
|
||||||
import appStateManager from "../lib/appManagers/appStateManager"; |
|
||||||
import { horizontalMenu } from "./horizontalMenu"; |
|
||||||
import GifsMasonry from "./gifsMasonry"; |
|
||||||
|
|
||||||
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; |
|
||||||
|
|
||||||
interface EmoticonsTab { |
|
||||||
init: () => void, |
|
||||||
onCloseAfterTimeout?: () => void |
|
||||||
} |
|
||||||
|
|
||||||
class EmojiTab implements EmoticonsTab { |
|
||||||
public content: HTMLElement; |
|
||||||
|
|
||||||
private recent: string[] = []; |
|
||||||
private recentItemsDiv: HTMLElement; |
|
||||||
|
|
||||||
private heights: number[] = []; |
|
||||||
private scroll: Scrollable; |
|
||||||
|
|
||||||
init() { |
|
||||||
this.content = document.getElementById('content-emoji') as HTMLDivElement; |
|
||||||
|
|
||||||
const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"]; |
|
||||||
const divs: { |
|
||||||
[category: string]: HTMLDivElement |
|
||||||
} = {}; |
|
||||||
|
|
||||||
const sorted: { |
|
||||||
[category: string]: string[] |
|
||||||
} = { |
|
||||||
'Recent': [] |
|
||||||
}; |
|
||||||
|
|
||||||
for(const emoji in Config.Emoji) { |
|
||||||
const details = Config.Emoji[emoji]; |
|
||||||
const i = '' + details; |
|
||||||
const category = categories[+i[0] - 1]; |
|
||||||
if(!category) continue; // maybe it's skin tones
|
|
||||||
|
|
||||||
if(!sorted[category]) sorted[category] = []; |
|
||||||
sorted[category][+i.slice(1) || 0] = emoji; |
|
||||||
} |
|
||||||
|
|
||||||
//console.log('emoticons sorted:', sorted);
|
|
||||||
|
|
||||||
//Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
|
|
||||||
|
|
||||||
categories.pop(); |
|
||||||
delete sorted["Skin Tones"]; |
|
||||||
|
|
||||||
//console.time('emojiParse');
|
|
||||||
for(const category in sorted) { |
|
||||||
const div = document.createElement('div'); |
|
||||||
div.classList.add('emoji-category'); |
|
||||||
|
|
||||||
const titleDiv = document.createElement('div'); |
|
||||||
titleDiv.classList.add('category-title'); |
|
||||||
titleDiv.innerText = category; |
|
||||||
|
|
||||||
const itemsDiv = document.createElement('div'); |
|
||||||
itemsDiv.classList.add('category-items'); |
|
||||||
|
|
||||||
div.append(titleDiv, itemsDiv); |
|
||||||
|
|
||||||
const emojis = sorted[category]; |
|
||||||
emojis.forEach(emoji => { |
|
||||||
/* if(emojiUnicode(emoji) == '1f481-200d-2642') { |
|
||||||
console.log('append emoji', emoji, emojiUnicode(emoji)); |
|
||||||
} */ |
|
||||||
|
|
||||||
this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv); |
|
||||||
|
|
||||||
/* if(category == 'Smileys & Emotion') { |
|
||||||
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji)); |
|
||||||
} */ |
|
||||||
}); |
|
||||||
|
|
||||||
divs[category] = div; |
|
||||||
} |
|
||||||
//console.timeEnd('emojiParse');
|
|
||||||
|
|
||||||
let prevCategoryIndex = 0; |
|
||||||
const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement; |
|
||||||
const emojiScroll = this.scroll = new Scrollable(this.content, 'y', 'EMOJI', null); |
|
||||||
emojiScroll.container.addEventListener('scroll', (e) => { |
|
||||||
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, this.heights, prevCategoryIndex, emojiScroll.container); |
|
||||||
}); |
|
||||||
//emojiScroll.setVirtualContainer(emojiScroll.container);
|
|
||||||
|
|
||||||
const preloader = putPreloader(this.content, true); |
|
||||||
|
|
||||||
Promise.all([ |
|
||||||
new Promise((resolve) => setTimeout(resolve, 200)), |
|
||||||
|
|
||||||
appStateManager.getState().then(state => { |
|
||||||
if(Array.isArray(state.recentEmoji)) { |
|
||||||
this.recent = state.recentEmoji; |
|
||||||
} |
|
||||||
}) |
|
||||||
]).then(() => { |
|
||||||
preloader.remove(); |
|
||||||
|
|
||||||
this.recentItemsDiv = divs['Recent'].querySelector('.category-items'); |
|
||||||
for(const emoji of this.recent) { |
|
||||||
this.appendEmoji(emoji, this.recentItemsDiv); |
|
||||||
} |
|
||||||
|
|
||||||
categories.unshift('Recent'); |
|
||||||
categories.map(category => { |
|
||||||
const div = divs[category]; |
|
||||||
|
|
||||||
if(!div) { |
|
||||||
console.error('no div by category:', category); |
|
||||||
} |
|
||||||
|
|
||||||
emojiScroll.append(div); |
|
||||||
return div; |
|
||||||
}).forEach(div => { |
|
||||||
//console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight);
|
|
||||||
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
this.content.addEventListener('click', this.onContentClick); |
|
||||||
EmoticonsDropdown.menuOnClick(menu, this.heights, emojiScroll); |
|
||||||
this.init = null; |
|
||||||
} |
|
||||||
|
|
||||||
private appendEmoji(emoji: string, container: HTMLElement, prepend = false) { |
|
||||||
//const emoji = details.unified;
|
|
||||||
//const emoji = (details.unified as string).split('-')
|
|
||||||
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
|
||||||
|
|
||||||
const spanEmoji = document.createElement('span'); |
|
||||||
const kek = RichTextProcessor.wrapEmojiText(emoji); |
|
||||||
|
|
||||||
/* if(!kek.includes('emoji')) { |
|
||||||
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji)); |
|
||||||
return; |
|
||||||
} */ |
|
||||||
|
|
||||||
//console.log(kek);
|
|
||||||
|
|
||||||
spanEmoji.innerHTML = kek; |
|
||||||
|
|
||||||
if(spanEmoji.firstElementChild) { |
|
||||||
(spanEmoji.firstElementChild as HTMLImageElement).setAttribute('loading', 'lazy'); |
|
||||||
} |
|
||||||
|
|
||||||
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
|
||||||
//spanEmoji.setAttribute('emoji', emoji);
|
|
||||||
if(prepend) container.prepend(spanEmoji); |
|
||||||
else container.appendChild(spanEmoji); |
|
||||||
} |
|
||||||
|
|
||||||
private getEmojiFromElement(element: HTMLElement) { |
|
||||||
if(element.tagName == 'SPAN' && !element.classList.contains('emoji')) { |
|
||||||
element = element.firstElementChild as HTMLElement; |
|
||||||
} |
|
||||||
|
|
||||||
return element.getAttribute('alt') || element.innerText; |
|
||||||
} |
|
||||||
|
|
||||||
onContentClick = (e: MouseEvent) => { |
|
||||||
let target = e.target as HTMLElement; |
|
||||||
//if(target.tagName != 'SPAN') return;
|
|
||||||
|
|
||||||
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) { |
|
||||||
target = target.firstElementChild as HTMLElement; |
|
||||||
} else if(target.tagName == 'DIV') return; |
|
||||||
|
|
||||||
//console.log('contentEmoji div', target);
|
|
||||||
|
|
||||||
appImManager.chatInputC.messageInput.innerHTML += target.outerHTML; |
|
||||||
|
|
||||||
// Recent
|
|
||||||
const emoji = this.getEmojiFromElement(target); |
|
||||||
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => { |
|
||||||
const _emoji = this.getEmojiFromElement(el); |
|
||||||
if(emoji == _emoji) { |
|
||||||
el.remove(); |
|
||||||
} |
|
||||||
}); |
|
||||||
const scrollHeight = this.recentItemsDiv.scrollHeight; |
|
||||||
this.appendEmoji(emoji, this.recentItemsDiv, true); |
|
||||||
|
|
||||||
// нужно поставить новые размеры для скролла
|
|
||||||
if(this.recentItemsDiv.scrollHeight != scrollHeight) { |
|
||||||
this.heights.length = 0; |
|
||||||
(Array.from(this.scroll.container.children) as HTMLElement[]).forEach(div => { |
|
||||||
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
this.recent.findAndSplice(e => e == emoji); |
|
||||||
this.recent.unshift(emoji); |
|
||||||
if(this.recent.length > 36) { |
|
||||||
this.recent.length = 36; |
|
||||||
} |
|
||||||
|
|
||||||
appStateManager.pushToState('recentEmoji', this.recent); |
|
||||||
|
|
||||||
// Append to input
|
|
||||||
const event = new Event('input', {bubbles: true, cancelable: true}); |
|
||||||
appImManager.chatInputC.messageInput.dispatchEvent(event); |
|
||||||
}; |
|
||||||
|
|
||||||
onClose() { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class StickersTab implements EmoticonsTab { |
|
||||||
public content: HTMLElement; |
|
||||||
|
|
||||||
private stickerSets: {[id: string]: { |
|
||||||
stickers: HTMLElement, |
|
||||||
tab: HTMLElement |
|
||||||
}} = {}; |
|
||||||
|
|
||||||
private recentDiv: HTMLElement; |
|
||||||
private recentStickers: MTDocument[] = []; |
|
||||||
|
|
||||||
private heights: number[] = []; |
|
||||||
private heightRAF = 0; |
|
||||||
private scroll: Scrollable; |
|
||||||
|
|
||||||
private menu: HTMLUListElement; |
|
||||||
|
|
||||||
private mounted = false; |
|
||||||
|
|
||||||
categoryPush(categoryDiv: HTMLElement, categoryTitle: string, docs: MTDocument[], prepend?: boolean) { |
|
||||||
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
|
|
||||||
|
|
||||||
let itemsDiv = document.createElement('div'); |
|
||||||
itemsDiv.classList.add('category-items'); |
|
||||||
|
|
||||||
let titleDiv = document.createElement('div'); |
|
||||||
titleDiv.classList.add('category-title'); |
|
||||||
titleDiv.innerText = categoryTitle; |
|
||||||
|
|
||||||
categoryDiv.append(titleDiv, itemsDiv); |
|
||||||
|
|
||||||
docs.forEach(doc => { |
|
||||||
itemsDiv.append(this.renderSticker(doc)); |
|
||||||
}); |
|
||||||
|
|
||||||
if(prepend) { |
|
||||||
if(this.recentDiv.parentElement) { |
|
||||||
this.scroll.prepend(categoryDiv); |
|
||||||
this.scroll.prepend(this.recentDiv); |
|
||||||
} else { |
|
||||||
this.scroll.prepend(categoryDiv); |
|
||||||
} |
|
||||||
} else this.scroll.append(categoryDiv); |
|
||||||
|
|
||||||
/* let scrollHeight = categoryDiv.scrollHeight; |
|
||||||
let prevHeight = heights[heights.length - 1] || 0; |
|
||||||
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
|
|
||||||
if(prepend && heights.length) {// all stickers loaded faster than recent
|
|
||||||
heights.forEach((h, i) => heights[i] += scrollHeight); |
|
||||||
|
|
||||||
return heights.unshift(scrollHeight) - 1; |
|
||||||
} */ |
|
||||||
|
|
||||||
this.setNewHeights(); |
|
||||||
|
|
||||||
/* Array.from(stickersDiv.children).forEach((div, i) => { |
|
||||||
heights[i] = (heights[i - 1] || 0) + div.scrollHeight; |
|
||||||
}); */ |
|
||||||
|
|
||||||
//this.scroll.onScroll();
|
|
||||||
|
|
||||||
//return heights.push(prevHeight + scrollHeight) - 1;
|
|
||||||
} |
|
||||||
|
|
||||||
setNewHeights() { |
|
||||||
if(this.heightRAF) return; |
|
||||||
//if(this.heightRAF) window.cancelAnimationFrame(this.heightRAF);
|
|
||||||
this.heightRAF = window.requestAnimationFrame(() => { |
|
||||||
this.heightRAF = 0; |
|
||||||
|
|
||||||
const heights = this.heights; |
|
||||||
|
|
||||||
let paddingTop = parseInt(window.getComputedStyle(this.scroll.container).getPropertyValue('padding-top')) || 0; |
|
||||||
|
|
||||||
heights.length = 0; |
|
||||||
/* let concated = this.scroll.hiddenElements.up.concat(this.scroll.visibleElements, this.scroll.hiddenElements.down); |
|
||||||
concated.forEach((el, i) => { |
|
||||||
heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0); |
|
||||||
}); */ |
|
||||||
let concated = Array.from(this.scroll.splitUp.children) as HTMLElement[]; |
|
||||||
concated.forEach((el, i) => { |
|
||||||
heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0); |
|
||||||
}); |
|
||||||
|
|
||||||
this.scroll.reorder(); |
|
||||||
|
|
||||||
//console.log('stickers concated', concated, heights);
|
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
renderSticker(doc: MTDocument) { |
|
||||||
let div = document.createElement('div'); |
|
||||||
wrapSticker({ |
|
||||||
doc, |
|
||||||
div, |
|
||||||
/* width: 80, |
|
||||||
height: 80, |
|
||||||
play: false, |
|
||||||
loop: false, */ |
|
||||||
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue, |
|
||||||
group: EMOTICONSSTICKERGROUP, |
|
||||||
onlyThumb: doc.sticker == 2 |
|
||||||
}); |
|
||||||
|
|
||||||
return div; |
|
||||||
} |
|
||||||
|
|
||||||
async renderStickerSet(set: MTStickerSet, prepend = false) { |
|
||||||
let categoryDiv = document.createElement('div'); |
|
||||||
categoryDiv.classList.add('sticker-category'); |
|
||||||
|
|
||||||
let li = document.createElement('li'); |
|
||||||
li.classList.add('btn-icon'); |
|
||||||
|
|
||||||
this.stickerSets[set.id] = { |
|
||||||
stickers: categoryDiv, |
|
||||||
tab: li |
|
||||||
}; |
|
||||||
|
|
||||||
if(prepend) { |
|
||||||
this.menu.insertBefore(li, this.menu.firstElementChild.nextSibling); |
|
||||||
} else { |
|
||||||
this.menu.append(li); |
|
||||||
} |
|
||||||
|
|
||||||
//stickersScroll.append(categoryDiv);
|
|
||||||
|
|
||||||
let stickerSet = await appStickersManager.getStickerSet(set); |
|
||||||
|
|
||||||
//console.log('got stickerSet', stickerSet, li);
|
|
||||||
|
|
||||||
if(stickerSet.set.thumb) { |
|
||||||
const thumbURL = appStickersManager.getStickerSetThumbURL(stickerSet.set); |
|
||||||
|
|
||||||
if(stickerSet.set.pFlags.animated) { |
|
||||||
fetch(thumbURL) |
|
||||||
.then(res => res.json()) |
|
||||||
.then(json => { |
|
||||||
lottieLoader.loadAnimationWorker({ |
|
||||||
container: li, |
|
||||||
loop: true, |
|
||||||
autoplay: false, |
|
||||||
animationData: json, |
|
||||||
width: 32, |
|
||||||
height: 32 |
|
||||||
}, EMOTICONSSTICKERGROUP); |
|
||||||
}); |
|
||||||
} else { |
|
||||||
const image = new Image(); |
|
||||||
renderImageFromUrl(image, thumbURL, () => { |
|
||||||
li.append(image); |
|
||||||
}); |
|
||||||
} |
|
||||||
} else { // as thumb will be used first sticker
|
|
||||||
wrapSticker({ |
|
||||||
doc: stickerSet.documents[0], |
|
||||||
div: li as any, |
|
||||||
group: EMOTICONSSTICKERGROUP |
|
||||||
}); // kostil
|
|
||||||
} |
|
||||||
|
|
||||||
this.categoryPush(categoryDiv, stickerSet.set.title, stickerSet.documents, prepend); |
|
||||||
} |
|
||||||
|
|
||||||
init() { |
|
||||||
this.content = document.getElementById('content-stickers'); |
|
||||||
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
|
|
||||||
|
|
||||||
this.recentDiv = document.createElement('div'); |
|
||||||
this.recentDiv.classList.add('sticker-category'); |
|
||||||
|
|
||||||
let menuWrapper = this.content.previousElementSibling as HTMLDivElement; |
|
||||||
this.menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement; |
|
||||||
|
|
||||||
let menuScroll = new Scrollable(menuWrapper, 'x'); |
|
||||||
|
|
||||||
let stickersDiv = document.createElement('div'); |
|
||||||
stickersDiv.classList.add('stickers-categories'); |
|
||||||
this.content.append(stickersDiv); |
|
||||||
|
|
||||||
/* stickersDiv.addEventListener('mouseover', (e) => { |
|
||||||
let target = e.target as HTMLElement; |
|
||||||
|
|
||||||
if(target.tagName == 'CANVAS') { // turn on sticker
|
|
||||||
let animation = lottieLoader.getAnimation(target.parentElement, EMOTICONSSTICKERGROUP); |
|
||||||
|
|
||||||
if(animation) { |
|
||||||
// @ts-ignore
|
|
||||||
if(animation.currentFrame == animation.totalFrames - 1) { |
|
||||||
animation.goToAndPlay(0, true); |
|
||||||
} else { |
|
||||||
animation.play(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}); */ |
|
||||||
|
|
||||||
$rootScope.$on('stickers_installed', (e: CustomEvent) => { |
|
||||||
const set: MTStickerSet = e.detail; |
|
||||||
|
|
||||||
if(!this.stickerSets[set.id] && this.mounted) { |
|
||||||
this.renderStickerSet(set, true); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
$rootScope.$on('stickers_deleted', (e: CustomEvent) => { |
|
||||||
const set: MTStickerSet = e.detail; |
|
||||||
|
|
||||||
if(this.stickerSets[set.id] && this.mounted) { |
|
||||||
const elements = this.stickerSets[set.id]; |
|
||||||
elements.stickers.remove(); |
|
||||||
elements.tab.remove(); |
|
||||||
this.setNewHeights(); |
|
||||||
delete this.stickerSets[set.id]; |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
stickersDiv.addEventListener('click', EmoticonsDropdown.onMediaClick); |
|
||||||
|
|
||||||
let prevCategoryIndex = 0; |
|
||||||
this.scroll = new Scrollable(this.content, 'y', 'STICKERS', undefined, undefined, 2); |
|
||||||
this.scroll.container.addEventListener('scroll', (e) => { |
|
||||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
|
||||||
|
|
||||||
if(this.heights[1] == 0) { |
|
||||||
this.setNewHeights(); |
|
||||||
} |
|
||||||
|
|
||||||
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(this.menu, this.heights, prevCategoryIndex, this.scroll.container, menuScroll); |
|
||||||
}); |
|
||||||
this.scroll.setVirtualContainer(stickersDiv); |
|
||||||
|
|
||||||
this.menu.addEventListener('click', () => { |
|
||||||
if(this.heights[1] == 0) { |
|
||||||
this.setNewHeights(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
EmoticonsDropdown.menuOnClick(this.menu, this.heights, this.scroll, menuScroll); |
|
||||||
|
|
||||||
const preloader = putPreloader(this.content, true); |
|
||||||
|
|
||||||
Promise.all([ |
|
||||||
appStickersManager.getRecentStickers().then(stickers => { |
|
||||||
this.recentStickers = stickers.stickers.slice(0, 20); |
|
||||||
|
|
||||||
//stickersScroll.prepend(categoryDiv);
|
|
||||||
|
|
||||||
this.stickerSets['recent'] = { |
|
||||||
stickers: this.recentDiv, |
|
||||||
tab: this.menu.firstElementChild as HTMLElement |
|
||||||
}; |
|
||||||
|
|
||||||
preloader.remove(); |
|
||||||
this.categoryPush(this.recentDiv, 'Recent', this.recentStickers, true); |
|
||||||
}), |
|
||||||
|
|
||||||
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => { |
|
||||||
let stickers: { |
|
||||||
_: 'messages.allStickers', |
|
||||||
hash: number, |
|
||||||
sets: Array<MTStickerSet> |
|
||||||
} = res as any; |
|
||||||
|
|
||||||
preloader.remove(); |
|
||||||
|
|
||||||
for(let set of stickers.sets) { |
|
||||||
this.renderStickerSet(set); |
|
||||||
} |
|
||||||
}) |
|
||||||
]).finally(() => { |
|
||||||
this.mounted = true; |
|
||||||
}); |
|
||||||
|
|
||||||
this.init = null; |
|
||||||
} |
|
||||||
|
|
||||||
pushRecentSticker(doc: MTDocument) { |
|
||||||
if(!this.recentDiv.parentElement) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`); |
|
||||||
if(!div) { |
|
||||||
div = this.renderSticker(doc); |
|
||||||
} |
|
||||||
|
|
||||||
const items = this.recentDiv.lastElementChild; |
|
||||||
items.prepend(div); |
|
||||||
|
|
||||||
if(items.childElementCount > 20) { |
|
||||||
(Array.from(items.children) as HTMLElement[]).slice(20).forEach(el => el.remove()); |
|
||||||
} |
|
||||||
|
|
||||||
this.setNewHeights(); |
|
||||||
} |
|
||||||
|
|
||||||
onClose() { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class GifsTab implements EmoticonsTab { |
|
||||||
public content: HTMLElement; |
|
||||||
|
|
||||||
init() { |
|
||||||
this.content = document.getElementById('content-gifs'); |
|
||||||
const gifsContainer = this.content.firstElementChild as HTMLDivElement; |
|
||||||
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick); |
|
||||||
|
|
||||||
const masonry = new GifsMasonry(gifsContainer); |
|
||||||
const scroll = new Scrollable(this.content, 'y', 'GIFS', null); |
|
||||||
const preloader = putPreloader(this.content, true); |
|
||||||
|
|
||||||
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => { |
|
||||||
let res = _res as { |
|
||||||
_: 'messages.savedGifs', |
|
||||||
gifs: MTDocument[], |
|
||||||
hash: number |
|
||||||
}; |
|
||||||
//console.log('getSavedGifs res:', res);
|
|
||||||
|
|
||||||
//let line: MTDocument[] = [];
|
|
||||||
|
|
||||||
preloader.remove(); |
|
||||||
res.gifs.forEach((doc, idx) => { |
|
||||||
res.gifs[idx] = appDocsManager.saveDoc(doc); |
|
||||||
masonry.add(res.gifs[idx], EMOTICONSSTICKERGROUP, EmoticonsDropdown.lazyLoadQueue); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
this.init = null; |
|
||||||
} |
|
||||||
|
|
||||||
onClose() { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class EmoticonsDropdown { |
|
||||||
public static lazyLoadQueue = new LazyLoadQueue(); |
|
||||||
private element: HTMLElement; |
|
||||||
|
|
||||||
public emojiTab: EmojiTab; |
|
||||||
public stickersTab: StickersTab; |
|
||||||
public gifsTab: GifsTab; |
|
||||||
|
|
||||||
private container: HTMLElement; |
|
||||||
private tabsEl: HTMLElement; |
|
||||||
private tabID = -1; |
|
||||||
|
|
||||||
private tabs: {[id: number]: EmoticonsTab}; |
|
||||||
|
|
||||||
public searchButton: HTMLElement; |
|
||||||
public deleteBtn: HTMLElement; |
|
||||||
|
|
||||||
public toggleEl: HTMLElement; |
|
||||||
private displayTimeout: number; |
|
||||||
|
|
||||||
constructor() { |
|
||||||
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement; |
|
||||||
|
|
||||||
let firstTime = true; |
|
||||||
this.toggleEl = document.getElementById('toggle-emoticons'); |
|
||||||
if(touchSupport) { |
|
||||||
this.toggleEl.addEventListener('click', () => { |
|
||||||
if(firstTime) { |
|
||||||
firstTime = false; |
|
||||||
this.toggle(true); |
|
||||||
} else { |
|
||||||
this.toggle(); |
|
||||||
} |
|
||||||
}); |
|
||||||
} else { |
|
||||||
this.toggleEl.onmouseover = (e) => { |
|
||||||
clearTimeout(this.displayTimeout); |
|
||||||
//this.displayTimeout = setTimeout(() => {
|
|
||||||
if(firstTime) { |
|
||||||
this.toggleEl.onmouseout = this.element.onmouseout = (e) => { |
|
||||||
const toElement = (e as any).toElement as Element; |
|
||||||
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
clearTimeout(this.displayTimeout); |
|
||||||
this.displayTimeout = setTimeout(() => { |
|
||||||
this.toggle(); |
|
||||||
}, 200); |
|
||||||
}; |
|
||||||
|
|
||||||
this.element.onmouseover = (e) => { |
|
||||||
clearTimeout(this.displayTimeout); |
|
||||||
}; |
|
||||||
|
|
||||||
firstTime = false; |
|
||||||
} |
|
||||||
|
|
||||||
this.toggle(true); |
|
||||||
//}, 0/* 200 */);
|
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private init() { |
|
||||||
this.emojiTab = new EmojiTab(); |
|
||||||
this.stickersTab = new StickersTab(); |
|
||||||
this.gifsTab = new GifsTab(); |
|
||||||
|
|
||||||
this.tabs = { |
|
||||||
0: this.emojiTab, |
|
||||||
1: this.stickersTab, |
|
||||||
2: this.gifsTab |
|
||||||
}; |
|
||||||
|
|
||||||
this.container = this.element.querySelector('.emoji-container .tabs-container') as HTMLDivElement; |
|
||||||
this.tabsEl = this.element.querySelector('.emoji-tabs') as HTMLUListElement; |
|
||||||
horizontalMenu(this.tabsEl, this.container, (id) => { |
|
||||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); |
|
||||||
|
|
||||||
this.tabID = id; |
|
||||||
this.searchButton.classList.toggle('hide', this.tabID == 0); |
|
||||||
this.deleteBtn.classList.toggle('hide', this.tabID != 0); |
|
||||||
}, () => { |
|
||||||
const tab = this.tabs[this.tabID]; |
|
||||||
if(tab.init) { |
|
||||||
tab.init(); |
|
||||||
} |
|
||||||
|
|
||||||
tab.onCloseAfterTimeout && tab.onCloseAfterTimeout(); |
|
||||||
animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); |
|
||||||
}); |
|
||||||
|
|
||||||
this.searchButton = this.element.querySelector('.emoji-tabs-search'); |
|
||||||
this.searchButton.addEventListener('click', () => { |
|
||||||
if(this.tabID == 1) { |
|
||||||
appSidebarRight.stickersTab.init(); |
|
||||||
} else { |
|
||||||
appSidebarRight.gifsTab.init(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete'); |
|
||||||
this.deleteBtn.addEventListener('click', () => { |
|
||||||
const input = appImManager.chatInputC.messageInput; |
|
||||||
if((input.lastChild as any)?.tagName) { |
|
||||||
input.lastElementChild.remove(); |
|
||||||
} else if(input.lastChild) { |
|
||||||
if(!input.lastChild.textContent.length) { |
|
||||||
input.lastChild.remove(); |
|
||||||
} else { |
|
||||||
input.lastChild.textContent = input.lastChild.textContent.slice(0, -1); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const event = new Event('input', {bubbles: true, cancelable: true}); |
|
||||||
appImManager.chatInputC.messageInput.dispatchEvent(event); |
|
||||||
//appSidebarRight.stickersTab.init();
|
|
||||||
}); |
|
||||||
|
|
||||||
(this.tabsEl.firstElementChild.children[1] as HTMLLIElement).click(); // set emoji tab
|
|
||||||
this.tabs[0].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
|
|
||||||
} |
|
||||||
|
|
||||||
public toggle = async(enable?: boolean) => { |
|
||||||
//if(!this.element) return;
|
|
||||||
const willBeActive = (!!this.element.style.display && enable === undefined) || enable; |
|
||||||
if(this.init) { |
|
||||||
if(willBeActive) { |
|
||||||
this.init(); |
|
||||||
this.init = null; |
|
||||||
} else { |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if(touchSupport) { |
|
||||||
this.toggleEl.classList.toggle('flip-icon', willBeActive); |
|
||||||
if(willBeActive) { |
|
||||||
appImManager.chatInputC.saveScroll(); |
|
||||||
// @ts-ignore
|
|
||||||
document.activeElement.blur(); |
|
||||||
await new Promise((resolve) => { |
|
||||||
setTimeout(resolve, 100); |
|
||||||
}); |
|
||||||
} |
|
||||||
} else { |
|
||||||
this.toggleEl.classList.toggle('active', enable); |
|
||||||
} |
|
||||||
|
|
||||||
if((this.element.style.display && enable === undefined) || enable) { |
|
||||||
this.element.style.display = ''; |
|
||||||
void this.element.offsetLeft; // reflow
|
|
||||||
this.element.classList.add('active'); |
|
||||||
|
|
||||||
EmoticonsDropdown.lazyLoadQueue.lockIntersection(); |
|
||||||
//EmoticonsDropdown.lazyLoadQueue.unlock();
|
|
||||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); |
|
||||||
|
|
||||||
clearTimeout(this.displayTimeout); |
|
||||||
this.displayTimeout = setTimeout(() => { |
|
||||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); |
|
||||||
EmoticonsDropdown.lazyLoadQueue.unlockIntersection(); |
|
||||||
}, touchSupport ? 0 : 200); |
|
||||||
|
|
||||||
/* if(touchSupport) { |
|
||||||
this.restoreScroll(); |
|
||||||
} */ |
|
||||||
} else { |
|
||||||
this.element.classList.remove('active'); |
|
||||||
|
|
||||||
EmoticonsDropdown.lazyLoadQueue.lockIntersection(); |
|
||||||
//EmoticonsDropdown.lazyLoadQueue.lock();
|
|
||||||
|
|
||||||
// нужно залочить группу и выключить стикеры
|
|
||||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); |
|
||||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); |
|
||||||
|
|
||||||
clearTimeout(this.displayTimeout); |
|
||||||
this.displayTimeout = setTimeout(() => { |
|
||||||
this.element.style.display = 'none'; |
|
||||||
|
|
||||||
// теперь можно убрать visible, чтобы они не включились после фокуса
|
|
||||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); |
|
||||||
|
|
||||||
EmoticonsDropdown.lazyLoadQueue.unlockIntersection(); |
|
||||||
}, touchSupport ? 0 : 200); |
|
||||||
|
|
||||||
/* if(touchSupport) { |
|
||||||
this.restoreScroll(); |
|
||||||
} */ |
|
||||||
} |
|
||||||
|
|
||||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
|
||||||
}; |
|
||||||
|
|
||||||
public static menuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => { |
|
||||||
menu.addEventListener('click', function(e) { |
|
||||||
let target = e.target as HTMLElement; |
|
||||||
target = findUpTag(target, 'LI'); |
|
||||||
|
|
||||||
if(!target) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
let index = whichChild(target); |
|
||||||
let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable
|
|
||||||
|
|
||||||
//console.log('emoticonsMenuOnClick', index, heights, target);
|
|
||||||
|
|
||||||
/* if(menuScroll) { |
|
||||||
menuScroll.container.scrollLeft = target.scrollWidth * index; |
|
||||||
} |
|
||||||
console.log('emoticonsMenuOnClick', menu.getBoundingClientRect(), target.getBoundingClientRect()); |
|
||||||
*/ |
|
||||||
/* scroll.onAddedBottom = () => { // привет, костыль, давно не виделись! |
|
||||||
scroll.container.scrollTop = y; |
|
||||||
scroll.onAddedBottom = () => {}; |
|
||||||
}; */ |
|
||||||
scroll.container.scrollTop = y; |
|
||||||
|
|
||||||
/* setTimeout(() => { |
|
||||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); |
|
||||||
}, 100); */ |
|
||||||
|
|
||||||
/* window.requestAnimationFrame(() => { |
|
||||||
window.requestAnimationFrame(() => { |
|
||||||
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); |
|
||||||
}); |
|
||||||
}); */ |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
public static contentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLElement, menuScroll?: Scrollable) => { |
|
||||||
let y = Math.round(scroll.scrollTop); |
|
||||||
|
|
||||||
//console.log(heights, y);
|
|
||||||
|
|
||||||
for(let i = 0; i < heights.length; ++i) { |
|
||||||
let height = heights[i]; |
|
||||||
if(y < height) { |
|
||||||
menu.children[prevCategoryIndex].classList.remove('active'); |
|
||||||
prevCategoryIndex = i/* + 1 */; |
|
||||||
menu.children[prevCategoryIndex].classList.add('active'); |
|
||||||
|
|
||||||
if(menuScroll) { |
|
||||||
if(i < heights.length - 4) { |
|
||||||
menuScroll.container.scrollLeft = (i - 3) * 47; |
|
||||||
} else { |
|
||||||
menuScroll.container.scrollLeft = i * 47; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return prevCategoryIndex; |
|
||||||
}; |
|
||||||
|
|
||||||
public static onMediaClick = (e: MouseEvent) => { |
|
||||||
let target = e.target as HTMLElement; |
|
||||||
target = findUpTag(target, 'DIV'); |
|
||||||
|
|
||||||
if(!target) return; |
|
||||||
|
|
||||||
let fileID = target.dataset.docID; |
|
||||||
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { |
|
||||||
/* dropdown.classList.remove('active'); |
|
||||||
toggleEl.classList.remove('active'); */ |
|
||||||
emoticonsDropdown.toggle(false); |
|
||||||
} else { |
|
||||||
console.warn('got no doc by id:', fileID); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
const emoticonsDropdown = new EmoticonsDropdown(); |
|
||||||
// @ts-ignore
|
|
||||||
if(process.env.NODE_ENV != 'production') { |
|
||||||
(window as any).emoticonsDropdown = emoticonsDropdown; |
|
||||||
} |
|
||||||
export default emoticonsDropdown; |
|
@ -0,0 +1,302 @@ |
|||||||
|
import LazyLoadQueue from "../lazyLoadQueue"; |
||||||
|
import GifsTab from "./tabs/gifs"; |
||||||
|
import { touchSupport } from "../../lib/config"; |
||||||
|
import { findUpClassName, findUpTag, whichChild } from "../../lib/utils"; |
||||||
|
import { horizontalMenu } from "../horizontalMenu"; |
||||||
|
import animationIntersector from "../animationIntersector"; |
||||||
|
import appSidebarRight from "../../lib/appManagers/appSidebarRight"; |
||||||
|
import appImManager from "../../lib/appManagers/appImManager"; |
||||||
|
import Scrollable from "../scrollable_new"; |
||||||
|
import EmojiTab from "./tabs/emoji"; |
||||||
|
import StickersTab from "./tabs/stickers"; |
||||||
|
|
||||||
|
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; |
||||||
|
|
||||||
|
export interface EmoticonsTab { |
||||||
|
init: () => void, |
||||||
|
onCloseAfterTimeout?: () => void |
||||||
|
} |
||||||
|
|
||||||
|
export class EmoticonsDropdown { |
||||||
|
public static lazyLoadQueue = new LazyLoadQueue(); |
||||||
|
private element: HTMLElement; |
||||||
|
|
||||||
|
public emojiTab: EmojiTab; |
||||||
|
public stickersTab: StickersTab; |
||||||
|
public gifsTab: GifsTab; |
||||||
|
|
||||||
|
private container: HTMLElement; |
||||||
|
private tabsEl: HTMLElement; |
||||||
|
private tabID = -1; |
||||||
|
|
||||||
|
private tabs: {[id: number]: EmoticonsTab}; |
||||||
|
|
||||||
|
public searchButton: HTMLElement; |
||||||
|
public deleteBtn: HTMLElement; |
||||||
|
|
||||||
|
public toggleEl: HTMLElement; |
||||||
|
private displayTimeout: number; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement; |
||||||
|
|
||||||
|
let firstTime = true; |
||||||
|
this.toggleEl = document.getElementById('toggle-emoticons'); |
||||||
|
if(touchSupport) { |
||||||
|
this.toggleEl.addEventListener('click', () => { |
||||||
|
if(firstTime) { |
||||||
|
firstTime = false; |
||||||
|
this.toggle(true); |
||||||
|
} else { |
||||||
|
this.toggle(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} else { |
||||||
|
this.toggleEl.onmouseover = (e) => { |
||||||
|
clearTimeout(this.displayTimeout); |
||||||
|
//this.displayTimeout = setTimeout(() => {
|
||||||
|
if(firstTime) { |
||||||
|
this.toggleEl.onmouseout = this.element.onmouseout = (e) => { |
||||||
|
const toElement = (e as any).toElement as Element; |
||||||
|
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
clearTimeout(this.displayTimeout); |
||||||
|
this.displayTimeout = setTimeout(() => { |
||||||
|
this.toggle(); |
||||||
|
}, 200); |
||||||
|
}; |
||||||
|
|
||||||
|
this.element.onmouseover = (e) => { |
||||||
|
clearTimeout(this.displayTimeout); |
||||||
|
}; |
||||||
|
|
||||||
|
firstTime = false; |
||||||
|
} |
||||||
|
|
||||||
|
this.toggle(true); |
||||||
|
//}, 0/* 200 */);
|
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private init() { |
||||||
|
this.emojiTab = new EmojiTab(); |
||||||
|
this.stickersTab = new StickersTab(); |
||||||
|
this.gifsTab = new GifsTab(); |
||||||
|
|
||||||
|
this.tabs = { |
||||||
|
0: this.emojiTab, |
||||||
|
1: this.stickersTab, |
||||||
|
2: this.gifsTab |
||||||
|
}; |
||||||
|
|
||||||
|
this.container = this.element.querySelector('.emoji-container .tabs-container') as HTMLDivElement; |
||||||
|
this.tabsEl = this.element.querySelector('.emoji-tabs') as HTMLUListElement; |
||||||
|
horizontalMenu(this.tabsEl, this.container, (id) => { |
||||||
|
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); |
||||||
|
|
||||||
|
this.tabID = id; |
||||||
|
this.searchButton.classList.toggle('hide', this.tabID == 0); |
||||||
|
this.deleteBtn.classList.toggle('hide', this.tabID != 0); |
||||||
|
}, () => { |
||||||
|
const tab = this.tabs[this.tabID]; |
||||||
|
if(tab.init) { |
||||||
|
tab.init(); |
||||||
|
} |
||||||
|
|
||||||
|
tab.onCloseAfterTimeout && tab.onCloseAfterTimeout(); |
||||||
|
animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); |
||||||
|
}); |
||||||
|
|
||||||
|
this.searchButton = this.element.querySelector('.emoji-tabs-search'); |
||||||
|
this.searchButton.addEventListener('click', () => { |
||||||
|
if(this.tabID == 1) { |
||||||
|
appSidebarRight.stickersTab.init(); |
||||||
|
} else { |
||||||
|
appSidebarRight.gifsTab.init(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete'); |
||||||
|
this.deleteBtn.addEventListener('click', () => { |
||||||
|
const input = appImManager.chatInputC.messageInput; |
||||||
|
if((input.lastChild as any)?.tagName) { |
||||||
|
input.lastElementChild.remove(); |
||||||
|
} else if(input.lastChild) { |
||||||
|
if(!input.lastChild.textContent.length) { |
||||||
|
input.lastChild.remove(); |
||||||
|
} else { |
||||||
|
input.lastChild.textContent = input.lastChild.textContent.slice(0, -1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const event = new Event('input', {bubbles: true, cancelable: true}); |
||||||
|
appImManager.chatInputC.messageInput.dispatchEvent(event); |
||||||
|
//appSidebarRight.stickersTab.init();
|
||||||
|
}); |
||||||
|
|
||||||
|
(this.tabsEl.firstElementChild.children[1] as HTMLLIElement).click(); // set emoji tab
|
||||||
|
this.tabs[0].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
|
||||||
|
} |
||||||
|
|
||||||
|
public toggle = async(enable?: boolean) => { |
||||||
|
//if(!this.element) return;
|
||||||
|
const willBeActive = (!!this.element.style.display && enable === undefined) || enable; |
||||||
|
if(this.init) { |
||||||
|
if(willBeActive) { |
||||||
|
this.init(); |
||||||
|
this.init = null; |
||||||
|
} else { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(touchSupport) { |
||||||
|
this.toggleEl.classList.toggle('flip-icon', willBeActive); |
||||||
|
if(willBeActive) { |
||||||
|
appImManager.chatInputC.saveScroll(); |
||||||
|
// @ts-ignore
|
||||||
|
document.activeElement.blur(); |
||||||
|
await new Promise((resolve) => { |
||||||
|
setTimeout(resolve, 100); |
||||||
|
}); |
||||||
|
} |
||||||
|
} else { |
||||||
|
this.toggleEl.classList.toggle('active', enable); |
||||||
|
} |
||||||
|
|
||||||
|
if((this.element.style.display && enable === undefined) || enable) { |
||||||
|
this.element.style.display = ''; |
||||||
|
void this.element.offsetLeft; // reflow
|
||||||
|
this.element.classList.add('active'); |
||||||
|
|
||||||
|
EmoticonsDropdown.lazyLoadQueue.lockIntersection(); |
||||||
|
//EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||||
|
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); |
||||||
|
|
||||||
|
clearTimeout(this.displayTimeout); |
||||||
|
this.displayTimeout = setTimeout(() => { |
||||||
|
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); |
||||||
|
EmoticonsDropdown.lazyLoadQueue.unlockIntersection(); |
||||||
|
}, touchSupport ? 0 : 200); |
||||||
|
|
||||||
|
/* if(touchSupport) { |
||||||
|
this.restoreScroll(); |
||||||
|
} */ |
||||||
|
} else { |
||||||
|
this.element.classList.remove('active'); |
||||||
|
|
||||||
|
EmoticonsDropdown.lazyLoadQueue.lockIntersection(); |
||||||
|
//EmoticonsDropdown.lazyLoadQueue.lock();
|
||||||
|
|
||||||
|
// нужно залочить группу и выключить стикеры
|
||||||
|
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); |
||||||
|
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); |
||||||
|
|
||||||
|
clearTimeout(this.displayTimeout); |
||||||
|
this.displayTimeout = setTimeout(() => { |
||||||
|
this.element.style.display = 'none'; |
||||||
|
|
||||||
|
// теперь можно убрать visible, чтобы они не включились после фокуса
|
||||||
|
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); |
||||||
|
|
||||||
|
EmoticonsDropdown.lazyLoadQueue.unlockIntersection(); |
||||||
|
}, touchSupport ? 0 : 200); |
||||||
|
|
||||||
|
/* if(touchSupport) { |
||||||
|
this.restoreScroll(); |
||||||
|
} */ |
||||||
|
} |
||||||
|
|
||||||
|
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||||
|
}; |
||||||
|
|
||||||
|
public static menuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => { |
||||||
|
menu.addEventListener('click', function(e) { |
||||||
|
let target = e.target as HTMLElement; |
||||||
|
target = findUpTag(target, 'LI'); |
||||||
|
|
||||||
|
if(!target) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let index = whichChild(target); |
||||||
|
let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable
|
||||||
|
|
||||||
|
//console.log('emoticonsMenuOnClick', index, heights, target);
|
||||||
|
|
||||||
|
/* if(menuScroll) { |
||||||
|
menuScroll.container.scrollLeft = target.scrollWidth * index; |
||||||
|
} |
||||||
|
console.log('emoticonsMenuOnClick', menu.getBoundingClientRect(), target.getBoundingClientRect()); |
||||||
|
*/ |
||||||
|
/* scroll.onAddedBottom = () => { // привет, костыль, давно не виделись! |
||||||
|
scroll.container.scrollTop = y; |
||||||
|
scroll.onAddedBottom = () => {}; |
||||||
|
}; */ |
||||||
|
scroll.container.scrollTop = y; |
||||||
|
|
||||||
|
/* setTimeout(() => { |
||||||
|
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); |
||||||
|
}, 100); */ |
||||||
|
|
||||||
|
/* window.requestAnimationFrame(() => { |
||||||
|
window.requestAnimationFrame(() => { |
||||||
|
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); |
||||||
|
}); |
||||||
|
}); */ |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
public static contentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLElement, menuScroll?: Scrollable) => { |
||||||
|
let y = Math.round(scroll.scrollTop); |
||||||
|
|
||||||
|
//console.log(heights, y);
|
||||||
|
|
||||||
|
for(let i = 0; i < heights.length; ++i) { |
||||||
|
let height = heights[i]; |
||||||
|
if(y < height) { |
||||||
|
menu.children[prevCategoryIndex].classList.remove('active'); |
||||||
|
prevCategoryIndex = i/* + 1 */; |
||||||
|
menu.children[prevCategoryIndex].classList.add('active'); |
||||||
|
|
||||||
|
if(menuScroll) { |
||||||
|
if(i < heights.length - 4) { |
||||||
|
menuScroll.container.scrollLeft = (i - 3) * 47; |
||||||
|
} else { |
||||||
|
menuScroll.container.scrollLeft = i * 47; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return prevCategoryIndex; |
||||||
|
}; |
||||||
|
|
||||||
|
public static onMediaClick = (e: MouseEvent) => { |
||||||
|
let target = e.target as HTMLElement; |
||||||
|
target = findUpTag(target, 'DIV'); |
||||||
|
|
||||||
|
if(!target) return; |
||||||
|
|
||||||
|
let fileID = target.dataset.docID; |
||||||
|
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { |
||||||
|
/* dropdown.classList.remove('active'); |
||||||
|
toggleEl.classList.remove('active'); */ |
||||||
|
emoticonsDropdown.toggle(false); |
||||||
|
} else { |
||||||
|
console.warn('got no doc by id:', fileID); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
const emoticonsDropdown = new EmoticonsDropdown(); |
||||||
|
// @ts-ignore
|
||||||
|
if(process.env.NODE_ENV != 'production') { |
||||||
|
(window as any).emoticonsDropdown = emoticonsDropdown; |
||||||
|
} |
||||||
|
export default emoticonsDropdown; |
@ -0,0 +1,209 @@ |
|||||||
|
import { EmoticonsTab, EmoticonsDropdown } from ".."; |
||||||
|
import Scrollable from "../../scrollable_new"; |
||||||
|
import Config from "../../../lib/config"; |
||||||
|
import { putPreloader } from "../../misc"; |
||||||
|
import appStateManager from "../../../lib/appManagers/appStateManager"; |
||||||
|
import { RichTextProcessor } from "../../../lib/richtextprocessor"; |
||||||
|
import appImManager from "../../../lib/appManagers/appImManager"; |
||||||
|
|
||||||
|
export default class EmojiTab implements EmoticonsTab { |
||||||
|
public content: HTMLElement; |
||||||
|
|
||||||
|
private recent: string[] = []; |
||||||
|
private recentItemsDiv: HTMLElement; |
||||||
|
|
||||||
|
private heights: number[] = []; |
||||||
|
private scroll: Scrollable; |
||||||
|
|
||||||
|
init() { |
||||||
|
this.content = document.getElementById('content-emoji') as HTMLDivElement; |
||||||
|
|
||||||
|
const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"]; |
||||||
|
const divs: { |
||||||
|
[category: string]: HTMLDivElement |
||||||
|
} = {}; |
||||||
|
|
||||||
|
const sorted: { |
||||||
|
[category: string]: string[] |
||||||
|
} = { |
||||||
|
'Recent': [] |
||||||
|
}; |
||||||
|
|
||||||
|
for(const emoji in Config.Emoji) { |
||||||
|
const details = Config.Emoji[emoji]; |
||||||
|
const i = '' + details; |
||||||
|
const category = categories[+i[0] - 1]; |
||||||
|
if(!category) continue; // maybe it's skin tones
|
||||||
|
|
||||||
|
if(!sorted[category]) sorted[category] = []; |
||||||
|
sorted[category][+i.slice(1) || 0] = emoji; |
||||||
|
} |
||||||
|
|
||||||
|
//console.log('emoticons sorted:', sorted);
|
||||||
|
|
||||||
|
//Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
|
||||||
|
|
||||||
|
categories.pop(); |
||||||
|
delete sorted["Skin Tones"]; |
||||||
|
|
||||||
|
//console.time('emojiParse');
|
||||||
|
for(const category in sorted) { |
||||||
|
const div = document.createElement('div'); |
||||||
|
div.classList.add('emoji-category'); |
||||||
|
|
||||||
|
const titleDiv = document.createElement('div'); |
||||||
|
titleDiv.classList.add('category-title'); |
||||||
|
titleDiv.innerText = category; |
||||||
|
|
||||||
|
const itemsDiv = document.createElement('div'); |
||||||
|
itemsDiv.classList.add('category-items'); |
||||||
|
|
||||||
|
div.append(titleDiv, itemsDiv); |
||||||
|
|
||||||
|
const emojis = sorted[category]; |
||||||
|
emojis.forEach(emoji => { |
||||||
|
/* if(emojiUnicode(emoji) == '1f481-200d-2642') { |
||||||
|
console.log('append emoji', emoji, emojiUnicode(emoji)); |
||||||
|
} */ |
||||||
|
|
||||||
|
this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv); |
||||||
|
|
||||||
|
/* if(category == 'Smileys & Emotion') { |
||||||
|
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji)); |
||||||
|
} */ |
||||||
|
}); |
||||||
|
|
||||||
|
divs[category] = div; |
||||||
|
} |
||||||
|
//console.timeEnd('emojiParse');
|
||||||
|
|
||||||
|
let prevCategoryIndex = 0; |
||||||
|
const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement; |
||||||
|
const emojiScroll = this.scroll = new Scrollable(this.content, 'y', 'EMOJI', null); |
||||||
|
emojiScroll.container.addEventListener('scroll', (e) => { |
||||||
|
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, this.heights, prevCategoryIndex, emojiScroll.container); |
||||||
|
}); |
||||||
|
//emojiScroll.setVirtualContainer(emojiScroll.container);
|
||||||
|
|
||||||
|
const preloader = putPreloader(this.content, true); |
||||||
|
|
||||||
|
Promise.all([ |
||||||
|
new Promise((resolve) => setTimeout(resolve, 200)), |
||||||
|
|
||||||
|
appStateManager.getState().then(state => { |
||||||
|
if(Array.isArray(state.recentEmoji)) { |
||||||
|
this.recent = state.recentEmoji; |
||||||
|
} |
||||||
|
}) |
||||||
|
]).then(() => { |
||||||
|
preloader.remove(); |
||||||
|
|
||||||
|
this.recentItemsDiv = divs['Recent'].querySelector('.category-items'); |
||||||
|
for(const emoji of this.recent) { |
||||||
|
this.appendEmoji(emoji, this.recentItemsDiv); |
||||||
|
} |
||||||
|
|
||||||
|
categories.unshift('Recent'); |
||||||
|
categories.map(category => { |
||||||
|
const div = divs[category]; |
||||||
|
|
||||||
|
if(!div) { |
||||||
|
console.error('no div by category:', category); |
||||||
|
} |
||||||
|
|
||||||
|
emojiScroll.append(div); |
||||||
|
return div; |
||||||
|
}).forEach(div => { |
||||||
|
//console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight);
|
||||||
|
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
this.content.addEventListener('click', this.onContentClick); |
||||||
|
EmoticonsDropdown.menuOnClick(menu, this.heights, emojiScroll); |
||||||
|
this.init = null; |
||||||
|
} |
||||||
|
|
||||||
|
private appendEmoji(emoji: string, container: HTMLElement, prepend = false) { |
||||||
|
//const emoji = details.unified;
|
||||||
|
//const emoji = (details.unified as string).split('-')
|
||||||
|
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
||||||
|
|
||||||
|
const spanEmoji = document.createElement('span'); |
||||||
|
const kek = RichTextProcessor.wrapEmojiText(emoji); |
||||||
|
|
||||||
|
/* if(!kek.includes('emoji')) { |
||||||
|
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji)); |
||||||
|
return; |
||||||
|
} */ |
||||||
|
|
||||||
|
//console.log(kek);
|
||||||
|
|
||||||
|
spanEmoji.innerHTML = kek; |
||||||
|
|
||||||
|
if(spanEmoji.firstElementChild) { |
||||||
|
(spanEmoji.firstElementChild as HTMLImageElement).setAttribute('loading', 'lazy'); |
||||||
|
} |
||||||
|
|
||||||
|
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
||||||
|
//spanEmoji.setAttribute('emoji', emoji);
|
||||||
|
if(prepend) container.prepend(spanEmoji); |
||||||
|
else container.appendChild(spanEmoji); |
||||||
|
} |
||||||
|
|
||||||
|
private getEmojiFromElement(element: HTMLElement) { |
||||||
|
if(element.tagName == 'SPAN' && !element.classList.contains('emoji')) { |
||||||
|
element = element.firstElementChild as HTMLElement; |
||||||
|
} |
||||||
|
|
||||||
|
return element.getAttribute('alt') || element.innerText; |
||||||
|
} |
||||||
|
|
||||||
|
onContentClick = (e: MouseEvent) => { |
||||||
|
let target = e.target as HTMLElement; |
||||||
|
//if(target.tagName != 'SPAN') return;
|
||||||
|
|
||||||
|
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) { |
||||||
|
target = target.firstElementChild as HTMLElement; |
||||||
|
} else if(target.tagName == 'DIV') return; |
||||||
|
|
||||||
|
//console.log('contentEmoji div', target);
|
||||||
|
|
||||||
|
appImManager.chatInputC.messageInput.innerHTML += target.outerHTML; |
||||||
|
|
||||||
|
// Recent
|
||||||
|
const emoji = this.getEmojiFromElement(target); |
||||||
|
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => { |
||||||
|
const _emoji = this.getEmojiFromElement(el); |
||||||
|
if(emoji == _emoji) { |
||||||
|
el.remove(); |
||||||
|
} |
||||||
|
}); |
||||||
|
const scrollHeight = this.recentItemsDiv.scrollHeight; |
||||||
|
this.appendEmoji(emoji, this.recentItemsDiv, true); |
||||||
|
|
||||||
|
// нужно поставить новые размеры для скролла
|
||||||
|
if(this.recentItemsDiv.scrollHeight != scrollHeight) { |
||||||
|
this.heights.length = 0; |
||||||
|
(Array.from(this.scroll.container.children) as HTMLElement[]).forEach(div => { |
||||||
|
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
this.recent.findAndSplice(e => e == emoji); |
||||||
|
this.recent.unshift(emoji); |
||||||
|
if(this.recent.length > 36) { |
||||||
|
this.recent.length = 36; |
||||||
|
} |
||||||
|
|
||||||
|
appStateManager.pushToState('recentEmoji', this.recent); |
||||||
|
|
||||||
|
// Append to input
|
||||||
|
const event = new Event('input', {bubbles: true, cancelable: true}); |
||||||
|
appImManager.chatInputC.messageInput.dispatchEvent(event); |
||||||
|
}; |
||||||
|
|
||||||
|
onClose() { |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import { EmoticonsDropdown, EmoticonsTab, EMOTICONSSTICKERGROUP } from ".."; |
||||||
|
import GifsMasonry from "../../gifsMasonry"; |
||||||
|
import Scrollable from "../../scrollable_new"; |
||||||
|
import { putPreloader } from "../../misc"; |
||||||
|
import apiManager from "../../../lib/mtproto/mtprotoworker"; |
||||||
|
import { MTDocument } from "../../../types"; |
||||||
|
import appDocsManager from "../../../lib/appManagers/appDocsManager"; |
||||||
|
|
||||||
|
export default class GifsTab implements EmoticonsTab { |
||||||
|
public content: HTMLElement; |
||||||
|
|
||||||
|
init() { |
||||||
|
this.content = document.getElementById('content-gifs'); |
||||||
|
const gifsContainer = this.content.firstElementChild as HTMLDivElement; |
||||||
|
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick); |
||||||
|
|
||||||
|
const masonry = new GifsMasonry(gifsContainer); |
||||||
|
const scroll = new Scrollable(this.content, 'y', 'GIFS', null); |
||||||
|
const preloader = putPreloader(this.content, true); |
||||||
|
|
||||||
|
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => { |
||||||
|
let res = _res as { |
||||||
|
_: 'messages.savedGifs', |
||||||
|
gifs: MTDocument[], |
||||||
|
hash: number |
||||||
|
}; |
||||||
|
//console.log('getSavedGifs res:', res);
|
||||||
|
|
||||||
|
//let line: MTDocument[] = [];
|
||||||
|
|
||||||
|
preloader.remove(); |
||||||
|
res.gifs.forEach((doc, idx) => { |
||||||
|
res.gifs[idx] = appDocsManager.saveDoc(doc); |
||||||
|
masonry.add(res.gifs[idx], EMOTICONSSTICKERGROUP, EmoticonsDropdown.lazyLoadQueue); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
this.init = null; |
||||||
|
} |
||||||
|
|
||||||
|
onClose() { |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,318 @@ |
|||||||
|
import { EmoticonsTab, EMOTICONSSTICKERGROUP, EmoticonsDropdown } from ".."; |
||||||
|
import { MTDocument } from "../../../types"; |
||||||
|
import Scrollable from "../../scrollable_new"; |
||||||
|
import { wrapSticker } from "../../wrappers"; |
||||||
|
import appStickersManager, { MTStickerSet } from "../../../lib/appManagers/appStickersManager"; |
||||||
|
import appDownloadManager from "../../../lib/appManagers/appDownloadManager"; |
||||||
|
import { readBlobAsText } from "../../../helpers/blob"; |
||||||
|
import lottieLoader from "../../../lib/lottieLoader"; |
||||||
|
import { renderImageFromUrl, putPreloader } from "../../misc"; |
||||||
|
import { RichTextProcessor } from "../../../lib/richtextprocessor"; |
||||||
|
import { $rootScope } from "../../../lib/utils"; |
||||||
|
import apiManager from "../../../lib/mtproto/mtprotoworker"; |
||||||
|
|
||||||
|
export default class StickersTab implements EmoticonsTab { |
||||||
|
public content: HTMLElement; |
||||||
|
|
||||||
|
private stickerSets: {[id: string]: { |
||||||
|
stickers: HTMLElement, |
||||||
|
tab: HTMLElement |
||||||
|
}} = {}; |
||||||
|
|
||||||
|
private recentDiv: HTMLElement; |
||||||
|
private recentStickers: MTDocument[] = []; |
||||||
|
|
||||||
|
private heights: number[] = []; |
||||||
|
private heightRAF = 0; |
||||||
|
private scroll: Scrollable; |
||||||
|
|
||||||
|
private menu: HTMLUListElement; |
||||||
|
|
||||||
|
private mounted = false; |
||||||
|
|
||||||
|
categoryPush(categoryDiv: HTMLElement, categoryTitle: string, docs: MTDocument[], prepend?: boolean) { |
||||||
|
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
|
||||||
|
|
||||||
|
const itemsDiv = document.createElement('div'); |
||||||
|
itemsDiv.classList.add('category-items'); |
||||||
|
|
||||||
|
const titleDiv = document.createElement('div'); |
||||||
|
titleDiv.classList.add('category-title'); |
||||||
|
titleDiv.innerHTML = categoryTitle; |
||||||
|
|
||||||
|
categoryDiv.append(titleDiv, itemsDiv); |
||||||
|
|
||||||
|
docs.forEach(doc => { |
||||||
|
itemsDiv.append(this.renderSticker(doc)); |
||||||
|
}); |
||||||
|
|
||||||
|
if(prepend) { |
||||||
|
if(this.recentDiv.parentElement) { |
||||||
|
this.scroll.prepend(categoryDiv); |
||||||
|
this.scroll.prepend(this.recentDiv); |
||||||
|
} else { |
||||||
|
this.scroll.prepend(categoryDiv); |
||||||
|
} |
||||||
|
} else this.scroll.append(categoryDiv); |
||||||
|
|
||||||
|
/* let scrollHeight = categoryDiv.scrollHeight; |
||||||
|
let prevHeight = heights[heights.length - 1] || 0; |
||||||
|
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
|
||||||
|
if(prepend && heights.length) {// all stickers loaded faster than recent
|
||||||
|
heights.forEach((h, i) => heights[i] += scrollHeight); |
||||||
|
|
||||||
|
return heights.unshift(scrollHeight) - 1; |
||||||
|
} */ |
||||||
|
|
||||||
|
this.setNewHeights(); |
||||||
|
|
||||||
|
/* Array.from(stickersDiv.children).forEach((div, i) => { |
||||||
|
heights[i] = (heights[i - 1] || 0) + div.scrollHeight; |
||||||
|
}); */ |
||||||
|
|
||||||
|
//this.scroll.onScroll();
|
||||||
|
|
||||||
|
//return heights.push(prevHeight + scrollHeight) - 1;
|
||||||
|
} |
||||||
|
|
||||||
|
setNewHeights() { |
||||||
|
if(this.heightRAF) return; |
||||||
|
//if(this.heightRAF) window.cancelAnimationFrame(this.heightRAF);
|
||||||
|
this.heightRAF = window.requestAnimationFrame(() => { |
||||||
|
this.heightRAF = 0; |
||||||
|
|
||||||
|
const heights = this.heights; |
||||||
|
|
||||||
|
let paddingTop = parseInt(window.getComputedStyle(this.scroll.container).getPropertyValue('padding-top')) || 0; |
||||||
|
|
||||||
|
heights.length = 0; |
||||||
|
/* let concated = this.scroll.hiddenElements.up.concat(this.scroll.visibleElements, this.scroll.hiddenElements.down); |
||||||
|
concated.forEach((el, i) => { |
||||||
|
heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0); |
||||||
|
}); */ |
||||||
|
let concated = Array.from(this.scroll.splitUp.children) as HTMLElement[]; |
||||||
|
concated.forEach((el, i) => { |
||||||
|
heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0); |
||||||
|
}); |
||||||
|
|
||||||
|
this.scroll.reorder(); |
||||||
|
|
||||||
|
//console.log('stickers concated', concated, heights);
|
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
renderSticker(doc: MTDocument) { |
||||||
|
const div = document.createElement('div'); |
||||||
|
wrapSticker({ |
||||||
|
doc, |
||||||
|
div, |
||||||
|
/* width: 80, |
||||||
|
height: 80, |
||||||
|
play: false, |
||||||
|
loop: false, */ |
||||||
|
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue, |
||||||
|
group: EMOTICONSSTICKERGROUP, |
||||||
|
onlyThumb: doc.sticker == 2 |
||||||
|
}); |
||||||
|
|
||||||
|
return div; |
||||||
|
} |
||||||
|
|
||||||
|
async renderStickerSet(set: MTStickerSet, prepend = false) { |
||||||
|
const categoryDiv = document.createElement('div'); |
||||||
|
categoryDiv.classList.add('sticker-category'); |
||||||
|
|
||||||
|
const li = document.createElement('li'); |
||||||
|
li.classList.add('btn-icon'); |
||||||
|
|
||||||
|
this.stickerSets[set.id] = { |
||||||
|
stickers: categoryDiv, |
||||||
|
tab: li |
||||||
|
}; |
||||||
|
|
||||||
|
if(prepend) { |
||||||
|
this.menu.insertBefore(li, this.menu.firstElementChild.nextSibling); |
||||||
|
} else { |
||||||
|
this.menu.append(li); |
||||||
|
} |
||||||
|
|
||||||
|
//stickersScroll.append(categoryDiv);
|
||||||
|
|
||||||
|
const stickerSet = await appStickersManager.getStickerSet(set); |
||||||
|
|
||||||
|
//console.log('got stickerSet', stickerSet, li);
|
||||||
|
|
||||||
|
if(stickerSet.set.thumb) { |
||||||
|
const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set); |
||||||
|
const promise = appDownloadManager.download(downloadOptions); |
||||||
|
|
||||||
|
if(stickerSet.set.pFlags.animated) { |
||||||
|
promise |
||||||
|
.then(readBlobAsText) |
||||||
|
.then(JSON.parse) |
||||||
|
.then(json => { |
||||||
|
lottieLoader.loadAnimationWorker({ |
||||||
|
container: li, |
||||||
|
loop: true, |
||||||
|
autoplay: false, |
||||||
|
animationData: json, |
||||||
|
width: 32, |
||||||
|
height: 32 |
||||||
|
}, EMOTICONSSTICKERGROUP); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
const image = new Image(); |
||||||
|
promise.then(blob => { |
||||||
|
renderImageFromUrl(image, URL.createObjectURL(blob), () => { |
||||||
|
li.append(image); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
} else { // as thumb will be used first sticker
|
||||||
|
wrapSticker({ |
||||||
|
doc: stickerSet.documents[0], |
||||||
|
div: li as any, |
||||||
|
group: EMOTICONSSTICKERGROUP |
||||||
|
}); // kostil
|
||||||
|
} |
||||||
|
|
||||||
|
this.categoryPush(categoryDiv, RichTextProcessor.wrapEmojiText(stickerSet.set.title), stickerSet.documents, prepend); |
||||||
|
} |
||||||
|
|
||||||
|
init() { |
||||||
|
this.content = document.getElementById('content-stickers'); |
||||||
|
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
|
||||||
|
|
||||||
|
this.recentDiv = document.createElement('div'); |
||||||
|
this.recentDiv.classList.add('sticker-category'); |
||||||
|
|
||||||
|
let menuWrapper = this.content.previousElementSibling as HTMLDivElement; |
||||||
|
this.menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement; |
||||||
|
|
||||||
|
let menuScroll = new Scrollable(menuWrapper, 'x'); |
||||||
|
|
||||||
|
let stickersDiv = document.createElement('div'); |
||||||
|
stickersDiv.classList.add('stickers-categories'); |
||||||
|
this.content.append(stickersDiv); |
||||||
|
|
||||||
|
/* stickersDiv.addEventListener('mouseover', (e) => { |
||||||
|
let target = e.target as HTMLElement; |
||||||
|
|
||||||
|
if(target.tagName == 'CANVAS') { // turn on sticker
|
||||||
|
let animation = lottieLoader.getAnimation(target.parentElement, EMOTICONSSTICKERGROUP); |
||||||
|
|
||||||
|
if(animation) { |
||||||
|
// @ts-ignore
|
||||||
|
if(animation.currentFrame == animation.totalFrames - 1) { |
||||||
|
animation.goToAndPlay(0, true); |
||||||
|
} else { |
||||||
|
animation.play(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); */ |
||||||
|
|
||||||
|
$rootScope.$on('stickers_installed', (e: CustomEvent) => { |
||||||
|
const set: MTStickerSet = e.detail; |
||||||
|
|
||||||
|
if(!this.stickerSets[set.id] && this.mounted) { |
||||||
|
this.renderStickerSet(set, true); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$rootScope.$on('stickers_deleted', (e: CustomEvent) => { |
||||||
|
const set: MTStickerSet = e.detail; |
||||||
|
|
||||||
|
if(this.stickerSets[set.id] && this.mounted) { |
||||||
|
const elements = this.stickerSets[set.id]; |
||||||
|
elements.stickers.remove(); |
||||||
|
elements.tab.remove(); |
||||||
|
this.setNewHeights(); |
||||||
|
delete this.stickerSets[set.id]; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
stickersDiv.addEventListener('click', EmoticonsDropdown.onMediaClick); |
||||||
|
|
||||||
|
let prevCategoryIndex = 0; |
||||||
|
this.scroll = new Scrollable(this.content, 'y', 'STICKERS', undefined, undefined, 2); |
||||||
|
this.scroll.container.addEventListener('scroll', (e) => { |
||||||
|
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||||
|
|
||||||
|
if(this.heights[1] == 0) { |
||||||
|
this.setNewHeights(); |
||||||
|
} |
||||||
|
|
||||||
|
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(this.menu, this.heights, prevCategoryIndex, this.scroll.container, menuScroll); |
||||||
|
}); |
||||||
|
this.scroll.setVirtualContainer(stickersDiv); |
||||||
|
|
||||||
|
this.menu.addEventListener('click', () => { |
||||||
|
if(this.heights[1] == 0) { |
||||||
|
this.setNewHeights(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
EmoticonsDropdown.menuOnClick(this.menu, this.heights, this.scroll, menuScroll); |
||||||
|
|
||||||
|
const preloader = putPreloader(this.content, true); |
||||||
|
|
||||||
|
Promise.all([ |
||||||
|
appStickersManager.getRecentStickers().then(stickers => { |
||||||
|
this.recentStickers = stickers.stickers.slice(0, 20); |
||||||
|
|
||||||
|
//stickersScroll.prepend(categoryDiv);
|
||||||
|
|
||||||
|
this.stickerSets['recent'] = { |
||||||
|
stickers: this.recentDiv, |
||||||
|
tab: this.menu.firstElementChild as HTMLElement |
||||||
|
}; |
||||||
|
|
||||||
|
preloader.remove(); |
||||||
|
this.categoryPush(this.recentDiv, 'Recent', this.recentStickers, true); |
||||||
|
}), |
||||||
|
|
||||||
|
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => { |
||||||
|
let stickers: { |
||||||
|
_: 'messages.allStickers', |
||||||
|
hash: number, |
||||||
|
sets: Array<MTStickerSet> |
||||||
|
} = res as any; |
||||||
|
|
||||||
|
preloader.remove(); |
||||||
|
|
||||||
|
for(let set of stickers.sets) { |
||||||
|
this.renderStickerSet(set); |
||||||
|
} |
||||||
|
}) |
||||||
|
]).finally(() => { |
||||||
|
this.mounted = true; |
||||||
|
}); |
||||||
|
|
||||||
|
this.init = null; |
||||||
|
} |
||||||
|
|
||||||
|
pushRecentSticker(doc: MTDocument) { |
||||||
|
if(!this.recentDiv.parentElement) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`); |
||||||
|
if(!div) { |
||||||
|
div = this.renderSticker(doc); |
||||||
|
} |
||||||
|
|
||||||
|
const items = this.recentDiv.lastElementChild; |
||||||
|
items.prepend(div); |
||||||
|
|
||||||
|
if(items.childElementCount > 20) { |
||||||
|
(Array.from(items.children) as HTMLElement[]).slice(20).forEach(el => el.remove()); |
||||||
|
} |
||||||
|
|
||||||
|
this.setNewHeights(); |
||||||
|
} |
||||||
|
|
||||||
|
onClose() { |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
export const readBlobAsText = (blob: Blob) => { |
||||||
|
return new Promise<string>(resolve => { |
||||||
|
const reader = new FileReader(); |
||||||
|
reader.addEventListener('loadend', async(e) => { |
||||||
|
// @ts-ignore
|
||||||
|
resolve(e.srcElement.result); |
||||||
|
}); |
||||||
|
reader.readAsText(blob); |
||||||
|
}); |
||||||
|
}; |
@ -0,0 +1,29 @@ |
|||||||
|
export const isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; |
||||||
|
export const isServiceWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope; |
||||||
|
export const isWorker = isWebWorker || isServiceWorker; |
||||||
|
|
||||||
|
// в SW может быть сразу две переменных TRUE, поэтому проверяю по последней
|
||||||
|
|
||||||
|
const notifyServiceWorker = (...args: any[]) => { |
||||||
|
(self as any as ServiceWorkerGlobalScope) |
||||||
|
.clients |
||||||
|
.matchAll({ includeUncontrolled: false, type: 'window' }) |
||||||
|
.then((listeners) => { |
||||||
|
if(!listeners.length) { |
||||||
|
//console.trace('no listeners?', self, listeners);
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
listeners[0].postMessage(...args); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const notifyWorker = (...args: any[]) => { |
||||||
|
// @ts-ignore
|
||||||
|
(self as any as DedicatedWorkerGlobalScope).postMessage(...args); |
||||||
|
}; |
||||||
|
|
||||||
|
const empty = () => {}; |
||||||
|
|
||||||
|
export const notifySomeone = isServiceWorker ? notifyServiceWorker : (isWebWorker ? notifyWorker : empty); |
@ -0,0 +1,146 @@ |
|||||||
|
// just to include
|
||||||
|
import {secureRandom} from '../polyfill'; |
||||||
|
secureRandom; |
||||||
|
|
||||||
|
import apiManager from "./apiManager"; |
||||||
|
import AppStorage from '../storage'; |
||||||
|
import cryptoWorker from "../crypto/cryptoworker"; |
||||||
|
import networkerFactory from "./networkerFactory"; |
||||||
|
import apiFileManager from './apiFileManager'; |
||||||
|
import { logger, LogLevels } from '../logger'; |
||||||
|
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service'; |
||||||
|
|
||||||
|
const log = logger('DW', LogLevels.error); |
||||||
|
|
||||||
|
const ctx = self as any as DedicatedWorkerGlobalScope; |
||||||
|
|
||||||
|
//console.error('INCLUDE !!!', new Error().stack);
|
||||||
|
|
||||||
|
/* function isObject(object: any) { |
||||||
|
return typeof(object) === 'object' && object !== null; |
||||||
|
} */ |
||||||
|
|
||||||
|
/* function fillTransfer(transfer: any, obj: any) { |
||||||
|
if(!obj) return; |
||||||
|
|
||||||
|
if(obj instanceof ArrayBuffer) { |
||||||
|
transfer.add(obj); |
||||||
|
} else if(obj.buffer && obj.buffer instanceof ArrayBuffer) { |
||||||
|
transfer.add(obj.buffer); |
||||||
|
} else if(isObject(obj)) { |
||||||
|
for(var i in obj) { |
||||||
|
fillTransfer(transfer, obj[i]); |
||||||
|
} |
||||||
|
} else if(Array.isArray(obj)) { |
||||||
|
obj.forEach(value => { |
||||||
|
fillTransfer(transfer, value); |
||||||
|
}); |
||||||
|
} |
||||||
|
} */ |
||||||
|
|
||||||
|
function respond(...args: any[]) { |
||||||
|
// отключил для всего потому что не успел пофиксить transfer detached
|
||||||
|
//if(isSafari(self)/* || true */) {
|
||||||
|
// @ts-ignore
|
||||||
|
ctx.postMessage(...args); |
||||||
|
/* } else { |
||||||
|
var transfer = new Set(); |
||||||
|
fillTransfer(transfer, arguments); |
||||||
|
|
||||||
|
//console.log('reply', transfer, [...transfer]);
|
||||||
|
ctx.postMessage(...arguments, [...transfer]); |
||||||
|
//console.log('reply', transfer, [...transfer]);
|
||||||
|
} */ |
||||||
|
} |
||||||
|
|
||||||
|
networkerFactory.setUpdatesProcessor((obj, bool) => { |
||||||
|
respond({update: {obj, bool}}); |
||||||
|
}); |
||||||
|
|
||||||
|
ctx.addEventListener('message', async(e) => { |
||||||
|
try { |
||||||
|
const task = e.data; |
||||||
|
const taskID = task.taskID; |
||||||
|
|
||||||
|
log.debug('got message:', taskID, task); |
||||||
|
|
||||||
|
//debugger;
|
||||||
|
|
||||||
|
if(task.useLs) { |
||||||
|
AppStorage.finishTask(task.taskID, task.args); |
||||||
|
return; |
||||||
|
} else if(task.type == 'convertWebp') { |
||||||
|
const {fileName, bytes} = task.payload; |
||||||
|
const deferred = apiFileManager.webpConvertPromises[fileName]; |
||||||
|
if(deferred) { |
||||||
|
deferred.resolve(bytes); |
||||||
|
delete apiFileManager.webpConvertPromises[fileName]; |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
} else if((task as ServiceWorkerTask).type == 'requestFilePart') { |
||||||
|
const task = e.data as ServiceWorkerTask; |
||||||
|
const responseTask: ServiceWorkerTaskResponse = { |
||||||
|
type: task.type, |
||||||
|
id: task.id, |
||||||
|
payload: null |
||||||
|
}; |
||||||
|
|
||||||
|
try { |
||||||
|
const res = await apiFileManager.requestFilePart(...task.payload); |
||||||
|
responseTask.payload = res; |
||||||
|
} catch(err) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
respond(responseTask); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
switch(task.task) { |
||||||
|
case 'computeSRP': |
||||||
|
case 'gzipUncompress': |
||||||
|
// @ts-ignore
|
||||||
|
return cryptoWorker[task.task].apply(cryptoWorker, task.args).then(result => { |
||||||
|
respond({taskID: taskID, result: result}); |
||||||
|
}); |
||||||
|
|
||||||
|
case 'cancelDownload': |
||||||
|
case 'downloadFile': { |
||||||
|
try { |
||||||
|
// @ts-ignore
|
||||||
|
let result = apiFileManager[task.task].apply(apiFileManager, task.args); |
||||||
|
|
||||||
|
if(result instanceof Promise) { |
||||||
|
result = await result; |
||||||
|
} |
||||||
|
|
||||||
|
respond({taskID: taskID, result: result}); |
||||||
|
} catch(err) { |
||||||
|
respond({taskID: taskID, error: err}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
default: { |
||||||
|
try { |
||||||
|
// @ts-ignore
|
||||||
|
let result = apiManager[task.task].apply(apiManager, task.args); |
||||||
|
|
||||||
|
if(result instanceof Promise) { |
||||||
|
result = await result; |
||||||
|
} |
||||||
|
|
||||||
|
respond({taskID: taskID, result: result}); |
||||||
|
} catch(err) { |
||||||
|
respond({taskID: taskID, error: err}); |
||||||
|
} |
||||||
|
|
||||||
|
//throw new Error('Unknown task: ' + task.task);
|
||||||
|
} |
||||||
|
} |
||||||
|
} catch(err) { |
||||||
|
|
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
ctx.postMessage('ready'); |
Loading…
Reference in new issue