/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import { isTouchSupported } from "../../helpers/touchSupport"; import appChatsManager from "../../lib/appManagers/appChatsManager"; import appImManager from "../../lib/appManagers/appImManager"; import rootScope from "../../lib/rootScope"; import { blurActiveElement, whichChild } from "../../helpers/dom"; import animationIntersector from "../animationIntersector"; import { horizontalMenu } from "../horizontalMenu"; import LazyLoadQueue, { LazyLoadQueueIntersector } from "../lazyLoadQueue"; import Scrollable, { ScrollableX } from "../scrollable"; import appSidebarRight from "../sidebarRight"; import StickyIntersector from "../stickyIntersector"; import EmojiTab from "./tabs/emoji"; import GifsTab from "./tabs/gifs"; import StickersTab from "./tabs/stickers"; import { pause } from "../../helpers/schedulers"; import { MOUNT_CLASS_TO } from "../../config/debug"; import AppGifsTab from "../sidebarRight/tabs/gifs"; import AppStickersTab from "../sidebarRight/tabs/stickers"; import findUpClassName from "../../helpers/dom/findUpClassName"; import findUpTag from "../../helpers/dom/findUpTag"; export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; export interface EmoticonsTab { init: () => void, onCloseAfterTimeout?: () => void } const test = false; 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; private displayTimeout: number; public events: { onClose: Array<() => void>, onCloseAfter: Array<() => void>, onOpen: Array<() => void>, onOpenAfter: Array<() => void> } = { onClose: [], onCloseAfter: [], onOpen: [], onOpenAfter: [] }; private selectTab: ReturnType; private forceClose = false; constructor() { this.element = document.getElementById('emoji-dropdown') as HTMLDivElement; } public attachButtonListener(button: HTMLElement) { let firstTime = true; if(isTouchSupported) { button.addEventListener('click', () => { if(firstTime) { firstTime = false; this.toggle(true); } else { this.toggle(); } }); } else { button.onmouseover = (e) => { //console.log('onmouseover button'); clearTimeout(this.displayTimeout); //this.displayTimeout = setTimeout(() => { if(firstTime) { button.onmouseout = this.onMouseOut; firstTime = false; } this.toggle(true); //}, 0/* 200 */); }; } } private onMouseOut = (e: MouseEvent) => { if(test) return; if(!this.element.classList.contains('active')) return; const toElement = (e as any).toElement as Element; if(toElement && findUpClassName(toElement, 'emoji-dropdown')) { return; } clearTimeout(this.displayTimeout); this.displayTimeout = window.setTimeout(() => { this.toggle(false); }, 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; this.selectTab = horizontalMenu(this.tabsEl, this.container, this.onSelectTabClick, () => { 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) { if(!appSidebarRight.isTabExists(AppStickersTab)) { new AppStickersTab(appSidebarRight).open(); } } else { if(!appSidebarRight.isTabExists(AppGifsTab)) { new AppGifsTab(appSidebarRight).open(); } } }); this.deleteBtn = this.element.querySelector('.emoji-tabs-delete'); this.deleteBtn.addEventListener('click', () => { const input = appImManager.chat.input.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.chat.input.messageInput.dispatchEvent(event); //appSidebarRight.stickersTab.init(); }); (this.tabsEl.children[1] as HTMLLIElement).click(); // set emoji tab if(this.tabs[0].init) { this.tabs[0].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка } rootScope.on('peer_changed', this.checkRights); this.checkRights(); if(!isTouchSupported) { this.element.onmouseout = this.onMouseOut; this.element.onmouseover = (e) => { if(this.forceClose) { return; } //console.log('onmouseover element'); clearTimeout(this.displayTimeout); }; } } private onSelectTabClick = (id: number) => { if(this.tabId === id) { return; } animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); this.tabId = id; this.searchButton.classList.toggle('hide', this.tabId === 0); this.deleteBtn.classList.toggle('hide', this.tabId !== 0); }; public checkRights = () => { const peerId = appImManager.chat.peerId; const children = this.tabsEl.children; const tabsElements = Array.from(children) as HTMLElement[]; const canSendStickers = peerId > 0 || appChatsManager.hasRights(peerId, 'send_stickers'); tabsElements[2].toggleAttribute('disabled', !canSendStickers); const canSendGifs = peerId > 0 || appChatsManager.hasRights(peerId, 'send_gifs'); tabsElements[3].toggleAttribute('disabled', !canSendGifs); const active = this.tabsEl.querySelector('.active'); if(active && whichChild(active) !== 1 && (!canSendStickers || !canSendGifs)) { this.selectTab(0, false); } }; 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(isTouchSupported) { if(willBeActive) { //appImManager.chat.input.saveScroll(); if(blurActiveElement()) { await pause(100); } } } if(this.element.parentElement !== appImManager.chat.input.chatInput) { appImManager.chat.input.chatInput.append(this.element); } if((this.element.style.display && enable === undefined) || enable) { this.events.onOpen.forEach(cb => cb()); EmoticonsDropdown.lazyLoadQueue.lock(); //EmoticonsDropdown.lazyLoadQueue.unlock(); animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); this.element.style.display = ''; void this.element.offsetLeft; // reflow this.element.classList.add('active'); clearTimeout(this.displayTimeout); this.displayTimeout = window.setTimeout(() => { animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); EmoticonsDropdown.lazyLoadQueue.unlock(); EmoticonsDropdown.lazyLoadQueue.refresh(); this.forceClose = false; this.container.classList.remove('disable-hover'); this.events.onOpenAfter.forEach(cb => cb()); }, isTouchSupported ? 0 : 200); // ! can't use together with resizeObserver /* if(isTouchSupported) { const height = this.element.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10; console.log('[ESG]: toggle: enable height', height); appImManager.chat.bubbles.scrollable.scrollTop += height; } */ /* if(touchSupport) { this.restoreScroll(); } */ } else { this.events.onClose.forEach(cb => cb()); EmoticonsDropdown.lazyLoadQueue.lock(); //EmoticonsDropdown.lazyLoadQueue.lock(); // нужно залочить группу и выключить стикеры animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP); animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); this.element.classList.remove('active'); clearTimeout(this.displayTimeout); this.displayTimeout = window.setTimeout(() => { this.element.style.display = 'none'; // теперь можно убрать visible, чтобы они не включились после фокуса animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP); EmoticonsDropdown.lazyLoadQueue.unlock(); EmoticonsDropdown.lazyLoadQueue.refresh(); this.forceClose = false; this.container.classList.remove('disable-hover'); this.events.onCloseAfter.forEach(cb => cb()); }, isTouchSupported ? 0 : 200); /* if(isTouchSupported) { const scrollHeight = this.container.scrollHeight; if(scrollHeight) { const height = this.container.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10; appImManager.chat.bubbles.scrollable.scrollTop -= height; } } */ /* if(touchSupport) { this.restoreScroll(); } */ } //animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); }; public static menuOnClick = (menu: HTMLElement, scroll: Scrollable, menuScroll?: ScrollableX) => { let prevId = 0; let jumpedTo = -1; const setActive = (id: number) => { if(id === prevId) { return false; } menu.children[prevId].classList.remove('active'); menu.children[id].classList.add('active'); prevId = id; return true; }; const stickyIntersector = new StickyIntersector(scroll.container, (stuck, target) => { //console.log('sticky scrollTOp', stuck, target, scroll.container.scrollTop); if(Math.abs(jumpedTo - scroll.container.scrollTop) <= 1) { return; } else { jumpedTo = -1; } const which = whichChild(target); if(!stuck && which) { // * due to stickyIntersector return; } setActive(which); if(menuScroll) { if(which < menu.childElementCount - 4) { menuScroll.container.scrollLeft = (which - 3) * 47; } else { menuScroll.container.scrollLeft = which * 47; } } }); menu.addEventListener('click', (e) => { let target = e.target as HTMLElement; target = findUpClassName(target, 'menu-horizontal-div-item'); if(!target) { return; } const which = whichChild(target); /* if(menuScroll) { menuScroll.scrollIntoView(target, false, 0); } */ if(!setActive(which)) { return; } const element = (scroll.splitUp || scroll.container).children[which] as HTMLElement; const offsetTop = element.offsetTop + 1; // * due to stickyIntersector scroll.container.scrollTop = jumpedTo = offsetTop; //console.log('set scrollTop:', offsetTop); }); return stickyIntersector; }; public static onMediaClick = (e: MouseEvent, clearDraft = false) => { let target = e.target as HTMLElement; target = findUpTag(target, 'DIV'); if(!target) return; const fileId = target.dataset.docId; if(!fileId) return; if(appImManager.chat.input.sendMessageWithDocument(fileId, undefined, clearDraft)) { /* dropdown.classList.remove('active'); toggleEl.classList.remove('active'); */ emoticonsDropdown.forceClose = true; emoticonsDropdown.container.classList.add('disable-hover'); emoticonsDropdown.toggle(false); } else { console.warn('got no doc by id:', fileId); } }; public addLazyLoadQueueRepeat(lazyLoadQueue: LazyLoadQueueIntersector, processInvisibleDiv: (div: HTMLElement) => void) { this.events.onClose.push(() => { lazyLoadQueue.lock(); }); this.events.onCloseAfter.push(() => { const divs = lazyLoadQueue.intersector.getVisible(); for(const div of divs) { processInvisibleDiv(div); } lazyLoadQueue.intersector.clearVisible(); }); this.events.onOpenAfter.push(() => { lazyLoadQueue.unlockAndRefresh(); }); } } const emoticonsDropdown = new EmoticonsDropdown(); MOUNT_CLASS_TO.emoticonsDropdown = emoticonsDropdown; export default emoticonsDropdown;