Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
18 KiB
557 lines
18 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
import emoticonsDropdown, {EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab} from '..'; |
|
import findUpClassName from '../../../helpers/dom/findUpClassName'; |
|
import mediaSizes from '../../../helpers/mediaSizes'; |
|
import {MessagesAllStickers, StickerSet} from '../../../layer'; |
|
import {MyDocument} from '../../../lib/appManagers/appDocsManager'; |
|
import {AppManagers} from '../../../lib/appManagers/managers'; |
|
import {i18n, LangPackKey} from '../../../lib/langPack'; |
|
import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText'; |
|
import rootScope from '../../../lib/rootScope'; |
|
import animationIntersector, {AnimationItemGroup} from '../../animationIntersector'; |
|
import LazyLoadQueue from '../../lazyLoadQueue'; |
|
import LazyLoadQueueRepeat from '../../lazyLoadQueueRepeat'; |
|
import {putPreloader} from '../../putPreloader'; |
|
import PopupStickers from '../../popups/stickers'; |
|
import Scrollable, {ScrollableX} from '../../scrollable'; |
|
import StickyIntersector from '../../stickyIntersector'; |
|
import {wrapSticker, wrapStickerSetThumb} from '../../wrappers'; |
|
import findAndSplice from '../../../helpers/array/findAndSplice'; |
|
import {attachClickEvent} from '../../../helpers/dom/clickEvent'; |
|
import positionElementByIndex from '../../../helpers/dom/positionElementByIndex'; |
|
import noop from '../../../helpers/noop'; |
|
import ButtonIcon from '../../buttonIcon'; |
|
import confirmationPopup from '../../confirmationPopup'; |
|
import VisibilityIntersector, {OnVisibilityChange} from '../../visibilityIntersector'; |
|
|
|
export class SuperStickerRenderer { |
|
public lazyLoadQueue: LazyLoadQueueRepeat; |
|
private animated: Set<HTMLElement> = new Set(); |
|
|
|
constructor( |
|
private regularLazyLoadQueue: LazyLoadQueue, |
|
private group: AnimationItemGroup, |
|
private managers: AppManagers, |
|
private options?: IntersectionObserverInit |
|
) { |
|
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, ({target, visible}) => { |
|
if(!visible) { |
|
this.processInvisible(target); |
|
} |
|
}, options); |
|
} |
|
|
|
public clear() { |
|
this.lazyLoadQueue.clear(); |
|
} |
|
|
|
public renderSticker(doc: MyDocument, element?: HTMLElement, loadPromises?: Promise<any>[]) { |
|
if(!element) { |
|
element = document.createElement('div'); |
|
element.classList.add('grid-item', 'super-sticker'); |
|
element.dataset.docId = '' + doc.id; |
|
|
|
if(doc.animated) { |
|
this.observeAnimated(element); |
|
} |
|
} |
|
|
|
// * This will wrap only a thumb |
|
/* !doc.animated && */wrapSticker({ |
|
doc, |
|
div: element, |
|
lazyLoadQueue: this.regularLazyLoadQueue, |
|
group: this.group, |
|
onlyThumb: doc.animated, |
|
loadPromises |
|
}); |
|
|
|
return element; |
|
} |
|
|
|
public observeAnimated(element: HTMLElement) { |
|
this.animated.add(element); |
|
this.lazyLoadQueue.observe({ |
|
div: element, |
|
load: this.processVisible |
|
}); |
|
} |
|
|
|
public unobserveAnimated(element: HTMLElement) { |
|
this.animated.delete(element); |
|
this.lazyLoadQueue.unobserve(element); |
|
} |
|
|
|
private checkAnimationContainer = (element: HTMLElement, visible: boolean) => { |
|
// console.error('checkAnimationContainer', div, visible); |
|
const players = animationIntersector.getAnimations(element); |
|
players.forEach((player) => { |
|
if(!visible) { |
|
animationIntersector.checkAnimation(player, true, true); |
|
} else { |
|
animationIntersector.checkAnimation(player, false); |
|
} |
|
}); |
|
}; |
|
|
|
private processVisible = async(element: HTMLElement) => { |
|
const docId = element.dataset.docId; |
|
const doc = await this.managers.appDocsManager.getDoc(docId); |
|
|
|
const size = mediaSizes.active.esgSticker.width; |
|
|
|
// console.log('processVisibleDiv:', div); |
|
|
|
const promise = wrapSticker({ |
|
doc, |
|
div: element, |
|
width: size, |
|
height: size, |
|
lazyLoadQueue: null, |
|
group: this.group, |
|
onlyThumb: false, |
|
play: true, |
|
loop: true, |
|
withLock: true |
|
}).then(({render}) => render); |
|
|
|
promise.then(() => { |
|
// clearTimeout(timeout); |
|
this.checkAnimationContainer(element, this.lazyLoadQueue.intersector.isVisible(element)); |
|
}); |
|
|
|
/* let timeout = window.setTimeout(() => { |
|
console.error('processVisibleDiv timeout', div, doc); |
|
}, 1e3); */ |
|
|
|
return promise; |
|
}; |
|
|
|
public processInvisible = async(element: HTMLElement) => { |
|
const docId = element.dataset.docId; |
|
const doc = await this.managers.appDocsManager.getDoc(docId); |
|
|
|
// console.log('STICKER INvisible:', /* div, */docId); |
|
|
|
this.checkAnimationContainer(element, false); |
|
|
|
element.textContent = ''; |
|
this.renderSticker(doc, element as HTMLDivElement); |
|
}; |
|
} |
|
|
|
type StickersTabCategory = { |
|
elements: { |
|
container: HTMLElement, |
|
title: HTMLElement, |
|
items: HTMLElement, |
|
menuTab: HTMLElement, |
|
menuTabPadding: HTMLElement |
|
}, |
|
set: StickerSet.stickerSet, |
|
items: { |
|
document: MyDocument, |
|
element: HTMLElement |
|
}[], |
|
pos?: number |
|
}; |
|
|
|
const RECENT_STICKERS_COUNT = 20; |
|
|
|
export default class StickersTab implements EmoticonsTab { |
|
private content: HTMLElement; |
|
|
|
private categories: {[id: string]: StickersTabCategory}; |
|
private categoriesMap: Map<HTMLElement, StickersTabCategory>; |
|
private categoriesIntersector: VisibilityIntersector; |
|
private localCategoryIndex: number; |
|
|
|
private scroll: Scrollable; |
|
private menu: HTMLElement; |
|
private mounted = false; |
|
private stickyIntersector: StickyIntersector; |
|
private superStickerRenderer: SuperStickerRenderer; |
|
|
|
constructor(private managers: AppManagers) { |
|
this.categories = {}; |
|
this.categoriesMap = new Map(); |
|
this.localCategoryIndex = 0; |
|
} |
|
|
|
private createCategory(stickerSet: StickerSet.stickerSet, _title: HTMLElement | DocumentFragment) { |
|
const container = document.createElement('div'); |
|
container.classList.add('emoji-category', 'hide'); |
|
|
|
const items = document.createElement('div'); |
|
items.classList.add('category-items', 'super-stickers'); |
|
|
|
const title = document.createElement('div'); |
|
title.classList.add('category-title'); |
|
title.append(_title); |
|
|
|
const menuTab = ButtonIcon(undefined, {noRipple: true}); |
|
menuTab.classList.add('menu-horizontal-div-item'); |
|
|
|
const menuTabPadding = document.createElement('div'); |
|
menuTabPadding.classList.add('menu-horizontal-div-item-padding'); |
|
|
|
menuTab.append(menuTabPadding); |
|
|
|
const category: StickersTabCategory = { |
|
elements: { |
|
container, |
|
title, |
|
items, |
|
menuTab, |
|
menuTabPadding |
|
}, |
|
set: stickerSet, |
|
items: [] |
|
}; |
|
|
|
container.append(title, items); |
|
|
|
this.categories[stickerSet.id] = category; |
|
this.categoriesMap.set(container, category); |
|
|
|
this.categoriesIntersector.observe(container); |
|
this.stickyIntersector.observeStickyHeaderChanges(container); |
|
|
|
return category; |
|
} |
|
|
|
private categoryAppendStickers( |
|
category: StickersTabCategory, |
|
promise: Promise<MyDocument[]> |
|
) { |
|
const {container} = category.elements; |
|
|
|
promise.then((documents) => { |
|
const isVisible = this.isCategoryVisible(category); |
|
|
|
documents.forEach((document) => { |
|
const element = this.superStickerRenderer.renderSticker(document); |
|
category.items.push({document, element}); |
|
|
|
if(isVisible) { |
|
category.elements.items.append(element); |
|
} |
|
}); |
|
|
|
this.setCategoryItemsHeight(category); |
|
container.classList.remove('hide'); |
|
}); |
|
} |
|
|
|
private isCategoryVisible(category: StickersTabCategory) { |
|
return this.categoriesIntersector.getVisible().includes(category.elements.container); |
|
} |
|
|
|
private setCategoryItemsHeight(category: StickersTabCategory) { |
|
const containerWidth = this.content.getBoundingClientRect().width - 10; |
|
const stickerSize = mediaSizes.active.esgSticker.width; |
|
|
|
const itemsPerRow = Math.floor(containerWidth / stickerSize); |
|
const rows = Math.ceil(category.items.length / itemsPerRow); |
|
const height = rows * stickerSize; |
|
|
|
category.elements.items.style.minHeight = height + 'px'; |
|
} |
|
|
|
private async renderStickerSet(set: StickerSet.stickerSet, prepend = false) { |
|
const category = this.createCategory(set, wrapEmojiText(set.title)); |
|
const {menuTab, menuTabPadding, container} = category.elements; |
|
|
|
const pos = prepend ? this.localCategoryIndex : 0xFFFF; |
|
positionElementByIndex(menuTab, this.menu, pos); |
|
|
|
const promise = this.managers.appStickersManager.getStickerSet(set); |
|
this.categoryAppendStickers( |
|
category, |
|
promise.then((stickerSet) => stickerSet.documents as MyDocument[]) |
|
); |
|
// const stickerSet = await promise; |
|
|
|
positionElementByIndex(container, this.scroll.container, pos, -1); |
|
|
|
wrapStickerSetThumb({ |
|
set, |
|
container: menuTabPadding, |
|
group: EMOTICONSSTICKERGROUP, |
|
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue, |
|
width: 32, |
|
height: 32, |
|
autoplay: false |
|
}); |
|
} |
|
|
|
public init() { |
|
this.content = document.getElementById('content-stickers'); |
|
const menuWrapper = this.content.previousElementSibling as HTMLDivElement; |
|
this.menu = menuWrapper.firstElementChild as HTMLUListElement; |
|
|
|
const menuScroll = new ScrollableX(menuWrapper); |
|
|
|
this.scroll = new Scrollable(this.content, 'STICKERS'); |
|
this.scroll.onAdditionalScroll = () => { |
|
setTyping(); |
|
}; |
|
|
|
/* 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(); |
|
} |
|
} |
|
} |
|
}); */ |
|
|
|
const onCategoryVisibility: OnVisibilityChange = ({target, visible, entry}) => { |
|
const category = this.categoriesMap.get(target); |
|
// console.log('roll the windows up', category, target, visible, entry); |
|
if(!visible) { |
|
category.elements.items.textContent = ''; |
|
} else { |
|
category.elements.items.append(...category.items.map(({element}) => element)); |
|
} |
|
}; |
|
|
|
const intersectionOptions: IntersectionObserverInit = {root: emoticonsDropdown.getElement()}; |
|
this.categoriesIntersector = new VisibilityIntersector(onCategoryVisibility, intersectionOptions); |
|
|
|
const clearCategoryItems = (category: StickersTabCategory) => { |
|
category.elements.items.textContent = ''; |
|
category.items.forEach(({element}) => this.superStickerRenderer.unobserveAnimated(element)); |
|
category.items.length = 0; |
|
}; |
|
|
|
this.scroll.container.addEventListener('click', (e) => { |
|
const target = e.target as HTMLElement; |
|
if(findUpClassName(target, 'category-title')) { |
|
const container = findUpClassName(target, 'emoji-category'); |
|
const category = this.categoriesMap.get(container); |
|
if(category.set.id === 'recent') { |
|
return; |
|
} |
|
|
|
new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}).show(); |
|
return; |
|
} |
|
|
|
EmoticonsDropdown.onMediaClick(e); |
|
}); |
|
|
|
const setTyping = (cancel = false) => { |
|
rootScope.dispatchEvent('choosing_sticker', !cancel); |
|
}; |
|
|
|
emoticonsDropdown.addEventListener('closed', () => { |
|
setTyping(true); |
|
}); |
|
|
|
emoticonsDropdown.addEventListener('opened', () => { |
|
setTyping(); |
|
}); |
|
|
|
const {stickyIntersector, setActive} = EmoticonsDropdown.menuOnClick(this.menu, this.scroll, menuScroll); |
|
this.stickyIntersector = stickyIntersector; |
|
|
|
const preloader = putPreloader(this.content, true); |
|
|
|
const createLocalCategory = (id: string, title: LangPackKey, icon?: string) => { |
|
const category = this.createCategory({id} as any, i18n(title)); |
|
category.elements.title.classList.add('disable-hover'); |
|
icon && category.elements.menuTab.classList.add('tgico-' + icon); |
|
category.elements.menuTabPadding.remove(); |
|
category.pos = this.localCategoryIndex++; |
|
this.toggleLocalCategory(category, false); |
|
return category; |
|
}; |
|
|
|
const recentCategory = createLocalCategory('recent', 'Stickers.Recent', 'recent'); |
|
recentCategory.elements.menuTab.classList.add('active'); |
|
|
|
const clearButton = ButtonIcon('close', {noRipple: true}); |
|
recentCategory.elements.title.append(clearButton); |
|
attachClickEvent(clearButton, () => { |
|
confirmationPopup({ |
|
titleLangKey: 'ClearRecentStickersAlertTitle', |
|
descriptionLangKey: 'ClearRecentStickersAlertMessage', |
|
button: { |
|
langKey: 'Clear' |
|
} |
|
}).then(() => { |
|
this.managers.appStickersManager.clearRecentStickers(); |
|
}, noop); |
|
}); |
|
|
|
const onRecentStickers = (stickers: MyDocument[]) => { |
|
const sliced = stickers.slice(0, RECENT_STICKERS_COUNT) as MyDocument[]; |
|
|
|
clearCategoryItems(recentCategory); |
|
this.toggleLocalCategory(recentCategory, !!sliced.length); |
|
this.categoryAppendStickers(recentCategory, Promise.resolve(sliced)); |
|
}; |
|
|
|
const premiumCategory = createLocalCategory('premium', 'PremiumStickersShort'); |
|
const s = document.createElement('span'); |
|
s.classList.add('tgico-star', 'color-premium'); |
|
premiumCategory.elements.menuTab.append(s); |
|
|
|
const promises = [ |
|
this.managers.appStickersManager.getRecentStickers().then((stickers) => { |
|
onRecentStickers(stickers.stickers as MyDocument[]); |
|
}), |
|
|
|
this.managers.appStickersManager.getAllStickers().then((res) => { |
|
for(const set of (res as MessagesAllStickers.messagesAllStickers).sets) { |
|
this.renderStickerSet(set); |
|
} |
|
}), |
|
|
|
this.managers.appStickersManager.getPremiumStickers().then((stickers) => { |
|
const length = stickers.length; |
|
this.toggleLocalCategory(premiumCategory, rootScope.premium && !!length); |
|
this.categoryAppendStickers(premiumCategory, Promise.resolve(stickers)); |
|
|
|
rootScope.addEventListener('premium_toggle', (isPremium) => { |
|
this.toggleLocalCategory(this.categories['premium'], isPremium && !!length); |
|
}); |
|
}) |
|
]; |
|
|
|
Promise.race(promises).finally(() => { |
|
preloader.remove(); |
|
}); |
|
|
|
Promise.all(promises).finally(() => { |
|
this.mounted = true; |
|
setTyping(); |
|
setActive(0); |
|
}); |
|
|
|
this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP, this.managers, intersectionOptions); |
|
|
|
const rendererLazyLoadQueue = this.superStickerRenderer.lazyLoadQueue; |
|
emoticonsDropdown.addLazyLoadQueueRepeat(rendererLazyLoadQueue, this.superStickerRenderer.processInvisible); |
|
|
|
// emoticonsDropdown.addEventListener('close', () => { |
|
// this.categoriesIntersector.lock(); |
|
// }); |
|
|
|
// emoticonsDropdown.addEventListener('closed', () => { |
|
// for(const [container] of this.categoriesMap) { |
|
// onCategoryVisibility(container, false); |
|
// } |
|
// }); |
|
|
|
// emoticonsDropdown.addEventListener('opened', () => { |
|
// this.categoriesIntersector.unlockAndRefresh(); |
|
// }); |
|
|
|
// setInterval(() => { |
|
// // @ts-ignore |
|
// const players = Object.values(lottieLoader.players).filter((p) => p.width >= 80); |
|
|
|
// console.log( |
|
// 'STICKERS RENDERED IN PANEL:', |
|
// players.length, |
|
// players.filter((p) => !p.paused).length, |
|
// rendererLazyLoadQueue.intersector.getVisible().length |
|
// ); |
|
// }, .25e3); |
|
|
|
rootScope.addEventListener('stickers_installed', (set) => { |
|
if(!this.categories[set.id] && this.mounted) { |
|
this.renderStickerSet(set, true); |
|
} |
|
}); |
|
|
|
rootScope.addEventListener('stickers_deleted', ({id}) => { |
|
const category = this.categories[id]; |
|
if(category && this.mounted) { |
|
category.elements.container.remove(); |
|
category.elements.menuTab.remove(); |
|
this.categoriesIntersector.unobserve(category.elements.container); |
|
clearCategoryItems(category); |
|
delete this.categories[id]; |
|
this.categoriesMap.delete(category.elements.container); |
|
} |
|
}); |
|
|
|
rootScope.addEventListener('stickers_recent', (stickers) => { |
|
if(this.mounted) { |
|
onRecentStickers(stickers); |
|
} |
|
}); |
|
|
|
const resizeCategories = () => { |
|
for(const [container, category] of this.categoriesMap) { |
|
this.setCategoryItemsHeight(category); |
|
} |
|
}; |
|
|
|
mediaSizes.addEventListener('resize', resizeCategories); |
|
|
|
emoticonsDropdown.addEventListener('opened', resizeCategories); |
|
|
|
this.init = null; |
|
} |
|
|
|
private toggleLocalCategory(category: StickersTabCategory, visible: boolean) { |
|
if(!visible) { |
|
category.elements.menuTab.remove(); |
|
category.elements.container.remove(); |
|
} else { |
|
const pos = category.pos; |
|
positionElementByIndex(category.elements.menuTab, this.menu, pos); |
|
positionElementByIndex(category.elements.container, this.scroll.container, pos); |
|
} |
|
|
|
// category.elements.container.classList.toggle('hide', !visible); |
|
} |
|
|
|
public pushRecentSticker(doc: MyDocument) { |
|
this.managers.appStickersManager.pushRecentSticker(doc.id); |
|
|
|
const category = this.categories['recent']; |
|
if(!category) { |
|
return; |
|
} |
|
|
|
const items = category.elements.items; |
|
let item = findAndSplice(category.items, (item) => item.document.id === doc.id); |
|
if(!item) { |
|
item = { |
|
element: this.superStickerRenderer.renderSticker(doc), |
|
document: doc |
|
}; |
|
} |
|
|
|
category.items.unshift(item); |
|
if(items.childElementCount) items.prepend(item.element); |
|
if(items.childElementCount > RECENT_STICKERS_COUNT) { |
|
(Array.from(items.children) as HTMLElement[]).slice(RECENT_STICKERS_COUNT).forEach((el) => el.remove()); |
|
} |
|
|
|
this.setCategoryItemsHeight(category); |
|
this.toggleLocalCategory(category, true); |
|
} |
|
|
|
onClose() { |
|
|
|
} |
|
}
|
|
|