From 4c21835d402dbe8e71af62fdca19b0338868ac93 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Tue, 16 Jun 2020 23:48:08 +0300 Subject: [PATCH] going home --- package-lock.json | 9 + package.json | 1 + src/components/appAudio.ts | 10 +- src/components/appForward.ts | 2 +- src/components/appSelectPeers.ts | 6 +- src/components/audio.ts | 15 +- src/components/avatar.ts | 2 +- src/components/chatInput.ts | 108 +--- src/components/emoticonsDropdown.ts | 543 ++++++++++++------- src/components/emoticonsDropdown_old.ts | 526 ++++++++++++++++++ src/components/lazyLoadQueue.ts | 46 +- src/components/misc.ts | 13 +- src/components/poll.ts | 403 +++++++++++--- src/components/popup.ts | 35 +- src/components/popupCreatePoll.ts | 134 +++++ src/components/popupStickers.ts | 39 +- src/components/preloader.ts | 5 + src/components/scrollable_new.ts | 63 ++- src/components/slider.ts | 6 +- src/components/wrappers.ts | 114 ++-- src/index.hbs | 29 +- src/lib/MP4Source.js | 272 ++++++++++ src/lib/MP4Sourcee.ts | 323 +++++++++++ src/lib/MP4Sourceee.ts | 279 ++++++++++ src/lib/appManagers/appDialogsManager.ts | 8 +- src/lib/appManagers/appDocsManager.ts | 141 ++++- src/lib/appManagers/appImManager.ts | 178 ++++-- src/lib/appManagers/appMediaViewer.ts | 148 ++++- src/lib/appManagers/appMessagesManager.ts | 237 +++++++- src/lib/appManagers/appPhotosManager.ts | 2 +- src/lib/appManagers/appPollsManager.ts | 104 +++- src/lib/appManagers/appSidebarLeft.ts | 5 +- src/lib/appManagers/appSidebarRight.ts | 223 ++++++-- src/lib/appManagers/appStickersManager.ts | 8 +- src/lib/appManagers/appWebpManager.ts | 5 +- src/lib/cacheStorage.ts | 5 +- src/lib/filemanager.ts | 8 +- src/lib/lottieLoader.ts | 15 +- src/lib/mediaPlayer.ts | 30 +- src/lib/mtproto/apiFileManager.ts | 174 +++--- src/lib/mtproto/mtproto.worker.js | 2 +- src/lib/mtproto/mtprotoworker.ts | 5 +- src/lib/mtproto/referenceDatabase.ts | 10 + src/lib/opusDecodeController.ts | 67 ++- src/lib/polyfill.ts | 2 +- src/pages/pagesManager.ts | 5 +- src/scss/partials/_chat.scss | 55 +- src/scss/partials/_chatBubble.scss | 232 +++++++- src/scss/partials/_chatlist.scss | 10 + src/scss/partials/_ckin.scss | 10 +- src/scss/partials/_fonts.scss | 2 +- src/scss/partials/_ico.scss | 1 + src/scss/partials/_mediaViewer.scss | 7 +- src/scss/partials/_rightSidebar.scss | 96 +++- src/scss/partials/popups/_createPoll.scss | 33 ++ src/scss/partials/popups/_datePicker.scss | 9 - src/scss/partials/popups/_editAvatar.scss | 3 - src/scss/partials/popups/_mediaAttacher.scss | 5 +- src/scss/partials/popups/_popup.scss | 14 + src/scss/partials/popups/_stickers.scss | 15 - src/scss/style.scss | 2 + src/types.d.ts | 5 +- 62 files changed, 4050 insertions(+), 814 deletions(-) create mode 100644 src/components/emoticonsDropdown_old.ts create mode 100644 src/components/popupCreatePoll.ts create mode 100644 src/lib/MP4Source.js create mode 100644 src/lib/MP4Sourcee.ts create mode 100644 src/lib/MP4Sourceee.ts create mode 100644 src/lib/mtproto/referenceDatabase.ts create mode 100644 src/scss/partials/popups/_createPoll.scss diff --git a/package-lock.json b/package-lock.json index 74656c00..c730b384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9512,6 +9512,15 @@ } } }, + "mp4box": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/mp4box/-/mp4box-0.3.20.tgz", + "integrity": "sha512-9I1wOBql0c9BsIPDGHY97dcH5kT7hG0Tx6SAaJvXf+A6Z0zBfGy7L1vEfjMKgjXSjtdXWL7gO+8a5euikaFTEA==", + "dev": true, + "requires": { + "npm": "^6.9.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/package.json b/package.json index 0d331a9e..0cfdfaeb 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "lottie-web": "^5.6.10", "media-query-plugin": "^1.3.1", "mini-css-extract-plugin": "^0.9.0", + "mp4box": "^0.3.20", "node-sass": "^4.14.1", "npm": "^6.14.5", "on-build-webpack": "^0.1.0", diff --git a/src/components/appAudio.ts b/src/components/appAudio.ts index 0602c42d..221ed240 100644 --- a/src/components/appAudio.ts +++ b/src/components/appAudio.ts @@ -52,6 +52,14 @@ class AppAudio { appDocsManager.downloadDoc(doc.id).then(() => { this.container.append(audio); source.src = doc.url; + }, () => { + if(this.nextMid == mid) { + this.loadSiblingsAudio(doc.type as 'voice' | 'audio', mid).then(() => { + if(this.nextMid && this.audios[this.nextMid]) { + this.audios[this.nextMid].play(); + } + }) + } }); return this.audios[mid] = audio; @@ -74,7 +82,7 @@ class AppAudio { const message = appMessagesManager.getMessage(mid); this.prevMid = this.nextMid = 0; - appMessagesManager.getSearch(message.peerID, '', { + return appMessagesManager.getSearch(message.peerID, '', { _: type == 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterVoice' }, mid, 3, 0, 2).then(value => { if(this.playingAudio != audio) { diff --git a/src/components/appForward.ts b/src/components/appForward.ts index 83362517..d3e49c2f 100644 --- a/src/components/appForward.ts +++ b/src/components/appForward.ts @@ -74,7 +74,7 @@ class AppForward { this.sendBtn.classList.remove('is-visible'); } }, 'dialogs', () => { - console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount); + //console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount); this.sidebarWasActive = appSidebarRight.sidebarEl.classList.contains('active'); appSidebarRight.toggleSidebar(true); }); diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index 20e41e0e..df83335a 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -98,7 +98,7 @@ export class AppSelectPeers { this.list.innerHTML = ''; this.query = value; - console.log('selectPeers input:', this.query); + //console.log('selectPeers input:', this.query); this.getMoreResults(); } }); @@ -135,7 +135,7 @@ export class AppSelectPeers { const newOffsetIndex = dialogs[dialogs.length - 1].index || 0; dialogs = dialogs.filter(d => d.peerID != this.myID); - if(!this.offsetIndex) { + if(!this.offsetIndex && !this.query) { dialogs.unshift({ peerID: this.myID, pFlags: {} @@ -175,7 +175,7 @@ export class AppSelectPeers { } private renderResults(peerIDs: number[]) { - console.log('will renderResults:', peerIDs); + //console.log('will renderResults:', peerIDs); peerIDs.forEach(peerID => { const {dom} = appDialogsManager.addDialog(peerID, this.scrollable, false, false); dom.containerEl.insertAdjacentHTML('afterbegin', '
'); diff --git a/src/components/audio.ts b/src/components/audio.ts index 8c27c62b..7479807c 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -118,12 +118,10 @@ function wrapVoiceMessage(doc: MTDocument, audioEl: AudioElement) { rects.slice(0, lastIndex + 1).forEach(node => node.classList.add('active')); } - audioEl.addAudioListener('playing', () => { - //rects.forEach(node => node.classList.remove('active')); - + let start = () => { clearInterval(interval); interval = setInterval(() => { - if(lastIndex > svg.childElementCount || isNaN(audio.duration)) { + if(lastIndex > svg.childElementCount || isNaN(audio.duration) || audio.paused) { clearInterval(interval); return; } @@ -137,6 +135,15 @@ function wrapVoiceMessage(doc: MTDocument, audioEl: AudioElement) { //console.log('lastIndex:', lastIndex, audio.currentTime); //}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */); }, 20); + }; + + if(!audio.paused) { + start(); + } + + audioEl.addAudioListener('playing', () => { + //rects.forEach(node => node.classList.remove('active')); + start(); }); audioEl.addAudioListener('pause', () => { diff --git a/src/components/avatar.ts b/src/components/avatar.ts index 1c34732c..1cbb31d5 100644 --- a/src/components/avatar.ts +++ b/src/components/avatar.ts @@ -45,7 +45,7 @@ export default class AvatarElement extends HTMLElement { } else if(name == 'peer-title') { this.peerTitle = newValue; } else if(name == 'dialog') { - this.isDialog = !!newValue; + this.isDialog = !!+newValue; } } diff --git a/src/components/chatInput.ts b/src/components/chatInput.ts index 6a830c54..9f2ea70c 100644 --- a/src/components/chatInput.ts +++ b/src/components/chatInput.ts @@ -1,5 +1,4 @@ import Scrollable from "./scrollable_new"; -import LazyLoadQueue from "./lazyLoadQueue"; import { RichTextProcessor } from "../lib/richtextprocessor"; //import apiManager from "../lib/mtproto/apiManager"; import apiManager from "../lib/mtproto/mtprotoworker"; @@ -8,14 +7,14 @@ import appImManager from "../lib/appManagers/appImManager"; import { getRichValue, calcImageInBox } from "../lib/utils"; import { wrapDocument, wrapReply } from "./wrappers"; import appMessagesManager from "../lib/appManagers/appMessagesManager"; -import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from "./emoticonsDropdown"; import { Layouter, RectPart } from "./groupedLayout"; import Recorder from '../../public/recorder.min'; //import Recorder from '../opus-recorder/dist/recorder.min'; import opusDecodeController from "../lib/opusDecodeController"; import { touchSupport } from "../lib/config"; -import animationIntersector from "./animationIntersector"; import appDocsManager from "../lib/appManagers/appDocsManager"; +import emoticonsDropdown from "./emoticonsDropdown"; +import PopupCreatePoll from "./popupCreatePoll"; export class ChatInput { public pageEl = document.getElementById('page-chats') as HTMLDivElement; @@ -25,10 +24,6 @@ export class ChatInput { public inputScroll = new Scrollable(this.inputMessageContainer); public btnSend = document.getElementById('btn-send') as HTMLButtonElement; public btnCancelRecord = this.btnSend.parentElement.previousElementSibling as HTMLButtonElement; - public emoticonsDropdown: HTMLDivElement = null; - public emoticonsTimeout: number = 0; - public toggleEmoticons: HTMLButtonElement; - public emoticonsLazyLoadQueue: LazyLoadQueue = null; public lastUrl = ''; public lastTimeType = 0; @@ -74,8 +69,6 @@ export class ChatInput { private scrollDiff = 0; constructor() { - this.toggleEmoticons = this.pageEl.querySelector('.toggle-emoticons') as HTMLButtonElement; - this.attachMenu.container = document.getElementById('attach-file') as HTMLButtonElement; this.attachMenu.media = this.attachMenu.container.querySelector('.menu-media') as HTMLDivElement; this.attachMenu.document = this.attachMenu.container.querySelector('.menu-document') as HTMLDivElement; @@ -109,7 +102,7 @@ export class ChatInput { } this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => { - if(e.key == 'Enter') { + if(e.key == 'Enter' && !touchSupport) { /* if(e.ctrlKey || e.metaKey) { this.messageInput.innerHTML += '
'; placeCaretAtEnd(this.message) @@ -127,7 +120,7 @@ export class ChatInput { if(touchSupport) { this.messageInput.addEventListener('touchend', (e) => { this.saveScroll(); - toggleEmoticons(false); + emoticonsDropdown.toggle(false); }); window.addEventListener('resize', () => { @@ -423,6 +416,10 @@ export class ChatInput { this.fileInput.click(); }); + this.attachMenu.poll.addEventListener('click', () => { + new PopupCreatePoll().show(); + }); + document.addEventListener('paste', (event) => { if(!appImManager.peerID || this.attachMediaPopUp.container.classList.contains('active')) { return; @@ -632,95 +629,6 @@ export class ChatInput { }; } - let emoticonsDisplayTimeout = 0; - const toggleEmoticons = async(enable?: boolean) => { - if(!this.emoticonsDropdown) return; - - if(touchSupport) { - const willBeActive = (!!this.emoticonsDropdown.style.display && enable === undefined) || enable; - this.toggleEmoticons.classList.toggle('flip-icon', willBeActive); - if(willBeActive) { - this.saveScroll(); - // @ts-ignore - document.activeElement.blur(); - await new Promise((resolve) => { - setTimeout(resolve, 100); - }); - } - } else { - this.toggleEmoticons.classList.toggle('active', enable); - } - - if((this.emoticonsDropdown.style.display && enable === undefined) || enable) { - this.emoticonsDropdown.style.display = ''; - void this.emoticonsDropdown.offsetLeft; // reflow - this.emoticonsDropdown.classList.add('active'); - this.emoticonsLazyLoadQueue.unlock(); - clearTimeout(emoticonsDisplayTimeout); - - /* if(touchSupport) { - this.restoreScroll(); - } */ - } else { - this.emoticonsDropdown.classList.remove('active'); - animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); - this.emoticonsLazyLoadQueue.lock(); - - clearTimeout(emoticonsDisplayTimeout); - emoticonsDisplayTimeout = setTimeout(() => { - this.emoticonsDropdown.style.display = 'none'; - }, touchSupport ? 0 : 200); - - /* if(touchSupport) { - this.restoreScroll(); - } */ - } - - animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); - }; - - if(touchSupport) { - this.toggleEmoticons.addEventListener('click', () => { - if(!this.emoticonsDropdown) { - let res = initEmoticonsDropdown(this.pageEl, appImManager, - appMessagesManager, this.messageInput, this.toggleEmoticons, this.btnSend); - - this.emoticonsDropdown = res.dropdown; - this.emoticonsLazyLoadQueue = res.lazyLoadQueue; - - toggleEmoticons(true); - } else { - toggleEmoticons(); - } - }); - } else { - this.toggleEmoticons.onmouseover = (e) => { - clearTimeout(this.emoticonsTimeout); - //this.emoticonsTimeout = setTimeout(() => { - if(!this.emoticonsDropdown) { - let res = initEmoticonsDropdown(this.pageEl, appImManager, - appMessagesManager, this.messageInput, this.toggleEmoticons, this.btnSend); - - this.emoticonsDropdown = res.dropdown; - this.emoticonsLazyLoadQueue = res.lazyLoadQueue; - - this.toggleEmoticons.onmouseout = this.emoticonsDropdown.onmouseout = (e) => { - clearTimeout(this.emoticonsTimeout); - this.emoticonsTimeout = setTimeout(() => { - toggleEmoticons(); - }, 200); - }; - - this.emoticonsDropdown.onmouseover = (e) => { - clearTimeout(this.emoticonsTimeout); - }; - } - - toggleEmoticons(true); - //}, 0/* 200 */); - }; - } - this.replyElements.cancelBtn.addEventListener('click', () => { this.replyElements.container.classList.remove('active'); this.replyToMsgID = 0; diff --git a/src/components/emoticonsDropdown.ts b/src/components/emoticonsDropdown.ts index 571a5f91..dc486a10 100644 --- a/src/components/emoticonsDropdown.ts +++ b/src/components/emoticonsDropdown.ts @@ -1,6 +1,5 @@ -import { AppImManager } from "../lib/appManagers/appImManager"; -import { AppMessagesManager } from "../lib/appManagers/appMessagesManager"; -import { horizontalMenu, renderImageFromUrl } from "./misc"; +import appImManager from "../lib/appManagers/appImManager"; +import { horizontalMenu, renderImageFromUrl, putPreloader } from "./misc"; import lottieLoader from "../lib/lottieLoader"; //import Scrollable from "./scrollable"; import Scrollable from "./scrollable_new"; @@ -9,125 +8,41 @@ 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 CryptoWorker from '../lib/crypto/cryptoworker'; import LazyLoadQueue from "./lazyLoadQueue"; import { wrapSticker } from "./wrappers"; import appDocsManager from "../lib/appManagers/appDocsManager"; import ProgressivePreloader from "./preloader"; -import Config from "../lib/config"; +import Config, { touchSupport } from "../lib/config"; import { MTDocument } from "../types"; import animationIntersector from "./animationIntersector"; import appSidebarRight from "../lib/appManagers/appSidebarRight"; export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; -const initEmoticonsDropdown = (pageEl: HTMLDivElement, - appImManager: AppImManager, appMessagesManager: AppMessagesManager, - messageInput: HTMLDivElement, toggleEl: HTMLButtonElement, btnSend: HTMLButtonElement) => { - let dropdown = pageEl.querySelector('.emoji-dropdown') as HTMLDivElement; - - dropdown.style.display = ''; - void dropdown.offsetLeft; // reflow - dropdown.classList.add('active'); // need - - let lazyLoadQueue = new LazyLoadQueue(5); - - const searchButton = dropdown.querySelector('.emoji-tabs-search'); - searchButton.addEventListener('click', () => { - appSidebarRight.stickersTab.init(); - }); - - let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement; - let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement; - let tabID = -1; - horizontalMenu(tabs, container, (id) => { - animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); - - tabID = id; - }, () => { - if(tabID == 1 && stickersInit) { - stickersInit(); - } else if(tabID == 2 && gifsInit) { - gifsInit(); - } - - animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); - }); +interface EmoticonsTab { + init: () => void, + onCloseAfterTimeout?: () => void +} - (tabs.firstElementChild.children[1] as HTMLLIElement).click(); // set emoji tab - - let emoticonsMenuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => { - menu.addEventListener('click', function(e) { - let target = e.target as HTMLLIElement; - target = findUpTag(target, 'LI'); +class EmojiTab implements EmoticonsTab { + public content: HTMLElement; - let index = whichChild(target); - let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable + init() { + this.content = document.getElementById('content-emoji') as HTMLDivElement; - /* 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); - }); - }); */ - }); - }; - - let emoticonsContentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLDivElement, menuScroll?: Scrollable) => { - let y = 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; - }; - - { const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"]; - let divs: { + const divs: { [category: string]: HTMLDivElement } = {}; - let sorted: { + const sorted: { [category: string]: string[] } = {}; - for(let emoji in Config.Emoji) { - let details = Config.Emoji[emoji]; - let i = '' + details; - let category = categories[+i[0] - 1]; + 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] = []; @@ -142,27 +57,27 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, delete sorted["Skin Tones"]; //console.time('emojiParse'); - for(let category in sorted) { - let div = document.createElement('div'); + for(const category in sorted) { + const div = document.createElement('div'); div.classList.add('emoji-category'); - let titleDiv = document.createElement('div'); + const titleDiv = document.createElement('div'); titleDiv.classList.add('category-title'); titleDiv.innerText = category; - let itemsDiv = document.createElement('div'); + const itemsDiv = document.createElement('div'); itemsDiv.classList.add('category-items'); div.append(titleDiv, itemsDiv); - let emojis = sorted[category]; + const emojis = sorted[category]; emojis.forEach(emoji => { - //let emoji = details.unified; - //let emoji = (details.unified as string).split('-') + //const emoji = details.unified; + //const emoji = (details.unified as string).split('-') //.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); - let spanEmoji = document.createElement('span'); - let kek = RichTextProcessor.wrapRichText(emoji); + const spanEmoji = document.createElement('span'); + const kek = RichTextProcessor.wrapRichText(emoji); if(!kek.includes('emoji')) { console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji)); @@ -182,84 +97,77 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, } //console.timeEnd('emojiParse'); - let contentEmojiDiv = document.getElementById('content-emoji') as HTMLDivElement; - let heights: number[] = [0]; + const heights: number[] = [0]; let prevCategoryIndex = 1; - let menu = contentEmojiDiv.previousElementSibling.firstElementChild as HTMLUListElement; - let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 'EMOJI', null); + const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement; + const emojiScroll = new Scrollable(this.content, 'y', 'EMOJI', null); emojiScroll.container.addEventListener('scroll', (e) => { - prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); + prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); }); //emojiScroll.setVirtualContainer(emojiScroll.container); - categories.map(category => { - let div = divs[category]; + const preloader = putPreloader(this.content, true); - if(!div) { - console.error('no div by category:', category); - } + setTimeout(() => { + preloader.remove(); - emojiScroll.append(div); - return div; - }).forEach(div => { - //console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight); - heights.push((heights[heights.length - 1] || 0) + div.scrollHeight); - }); + 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); + heights.push((heights[heights.length - 1] || 0) + div.scrollHeight); + }); + }, 200); - contentEmojiDiv.addEventListener('click', function(e) { - let target = e.target as any; - //if(target.tagName != 'SPAN') return; + this.content.addEventListener('click', this.onContentClick); + EmoticonsDropdown.menuOnClick(menu, heights, emojiScroll); + this.init = null; + } - if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) { - target = target.firstElementChild; - } else if(target.tagName == 'DIV') return; + onContentClick = (e: MouseEvent) => { + let target = e.target as any; + //if(target.tagName != 'SPAN') return; - //console.log('contentEmoji div', target); + if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) { + target = target.firstElementChild; + } else if(target.tagName == 'DIV') return; - /* if(!target.classList.contains('emoji')) { - target = target.parentElement as HTMLSpanElement; + //console.log('contentEmoji div', target); - if(!target.classList.contains('emoji')) { - return; - } - } */ + appImManager.chatInputC.messageInput.innerHTML += target.outerHTML; - //messageInput.innerHTML += target.innerHTML; - messageInput.innerHTML += target.outerHTML; + const event = new Event('input', {bubbles: true, cancelable: true}); + appImManager.chatInputC.messageInput.dispatchEvent(event); + }; - btnSend.classList.add('tgico-send'); - btnSend.classList.remove('tgico-microphone2'); - }); + onClose() { - emoticonsMenuOnClick(menu, heights, emojiScroll); } +} - let onMediaClick = (e: MouseEvent) => { - let target = e.target as HTMLDivElement; - target = findUpTag(target, 'DIV'); - - let fileID = target.dataset.docID; - if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { - dropdown.classList.remove('active'); - toggleEl.classList.remove('active'); - } else { - console.warn('got no doc by id:', fileID); - } - }; +class StickersTab implements EmoticonsTab { + public content: HTMLElement; - let stickersInit = () => { - let contentStickersDiv = document.getElementById('content-stickers') as HTMLDivElement; + init() { + this.content = document.getElementById('content-stickers'); //let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement; - let menuWrapper = contentStickersDiv.previousElementSibling as HTMLDivElement; + let menuWrapper = this.content.previousElementSibling as HTMLDivElement; let menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement; let menuScroll = new Scrollable(menuWrapper, 'x'); let stickersDiv = document.createElement('div'); stickersDiv.classList.add('stickers-categories'); - contentStickersDiv.append(stickersDiv); + this.content.append(stickersDiv); /* stickersDiv.addEventListener('mouseover', (e) => { let target = e.target as HTMLElement; @@ -278,7 +186,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, } }); */ - stickersDiv.addEventListener('click', onMediaClick); + stickersDiv.addEventListener('click', EmoticonsDropdown.onMediaClick); let heights: number[] = []; @@ -300,7 +208,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, wrapSticker({ doc, div, - lazyLoadQueue, + lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue, group: EMOTICONSSTICKERGROUP, onlyThumb: true }); @@ -349,17 +257,17 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, }; let prevCategoryIndex = 0; - let stickersScroll = new Scrollable(contentStickersDiv, 'y', 'STICKERS', undefined, undefined, 2); + let stickersScroll = new Scrollable(this.content, 'y', 'STICKERS', undefined, undefined, 2); stickersScroll.container.addEventListener('scroll', (e) => { animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); - prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll); + prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll); }); stickersScroll.setVirtualContainer(stickersDiv); - emoticonsMenuOnClick(menu, heights, stickersScroll, menuScroll); + EmoticonsDropdown.menuOnClick(menu, heights, stickersScroll, menuScroll); - stickersInit = null; + const preloader = putPreloader(this.content, true); Promise.all([ appStickersManager.getRecentStickers().then(stickers => { @@ -368,6 +276,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, //stickersScroll.prepend(categoryDiv); + preloader.remove(); categoryPush(categoryDiv, 'Recent', stickers.stickers, true); }), @@ -378,6 +287,8 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, sets: Array } = res as any; + preloader.remove(); + for(let set of stickers.sets) { let categoryDiv = document.createElement('div'); categoryDiv.classList.add('sticker-category'); @@ -434,19 +345,31 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, } }) ]); - }; - let gifsInit = () => { - let contentDiv = document.getElementById('content-gifs') as HTMLDivElement; - let masonry = contentDiv.firstElementChild as HTMLDivElement; + this.init = null; + } + + onClose() { - masonry.addEventListener('click', onMediaClick); + } +} - let scroll = new Scrollable(contentDiv, 'y', 'GIFS', null); +class GifsTab implements EmoticonsTab { + public content: HTMLElement; - let width = 400; - let maxSingleWidth = width - 100; - let height = 100; + init() { + this.content = document.getElementById('content-gifs'); + const masonry = this.content.firstElementChild as HTMLDivElement; + + masonry.addEventListener('click', EmoticonsDropdown.onMediaClick); + + const scroll = new Scrollable(this.content, 'y', 'GIFS', null); + + const preloader = putPreloader(this.content, true); + + const width = 400; + const maxSingleWidth = width - 100; + const height = 100; apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => { let res = _res as { @@ -454,9 +377,9 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, gifs: MTDocument[], hash: number }; - console.log('getSavedGifs res:', res); + //console.log('getSavedGifs res:', res); - let line: MTDocument[] = []; + //let line: MTDocument[] = []; let wastedWidth = 0; @@ -464,6 +387,8 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, res.gifs[idx] = appDocsManager.saveDoc(gif); }); + preloader.remove(); + for(let i = 0, length = res.gifs.length; i < length;) { let gif = res.gifs[i]; @@ -489,7 +414,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, line.push(gif); */ ++i; - console.log('gif:', gif, w, h); + //console.log('gif:', gif, w, h); let div = document.createElement('div'); div.style.width = w + 'px'; @@ -499,7 +424,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, masonry.append(div); let preloader = new ProgressivePreloader(div); - lazyLoadQueue.push({ + EmoticonsDropdown.lazyLoadQueue.push({ div, load: () => { let promise = appDocsManager.downloadDoc(gif); @@ -517,10 +442,258 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, } }); - gifsInit = undefined; + this.init = null; + } + + onClose() { + + } +} + +class EmoticonsDropdown { + public static lazyLoadQueue = new LazyLoadQueue(); + private element: HTMLElement; + + private emojiTab: EmojiTab; + private stickersTab: StickersTab; + private 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) => { + 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 != 1); + 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', () => { + appSidebarRight.stickersTab.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.unlock(); + clearTimeout(this.displayTimeout); + + /* if(touchSupport) { + this.restoreScroll(); + } */ + } else { + this.element.classList.remove('active'); + animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); + EmoticonsDropdown.lazyLoadQueue.lock(); + + clearTimeout(this.displayTimeout); + this.displayTimeout = setTimeout(() => { + this.element.style.display = 'none'; + }, 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 HTMLLIElement; + target = findUpTag(target, 'LI'); + + let index = whichChild(target); + let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable + + /* 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); + }); + }); */ + }); }; - return {dropdown, lazyLoadQueue}; -}; + public static contentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLDivElement, menuScroll?: Scrollable) => { + let y = 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; + }; -export default initEmoticonsDropdown; + public static onMediaClick = (e: MouseEvent) => { + let target = e.target as HTMLDivElement; + target = findUpTag(target, 'DIV'); + + let fileID = target.dataset.docID; + if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { + /* dropdown.classList.remove('active'); + toggleEl.classList.remove('active'); */ + } 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; diff --git a/src/components/emoticonsDropdown_old.ts b/src/components/emoticonsDropdown_old.ts new file mode 100644 index 00000000..50bdd20d --- /dev/null +++ b/src/components/emoticonsDropdown_old.ts @@ -0,0 +1,526 @@ +import { AppImManager } from "../lib/appManagers/appImManager"; +import { AppMessagesManager } from "../lib/appManagers/appMessagesManager"; +import { horizontalMenu, renderImageFromUrl } from "./misc"; +import lottieLoader from "../lib/lottieLoader"; +//import Scrollable from "./scrollable"; +import Scrollable from "./scrollable_new"; +import { findUpTag, whichChild, calcImageInBox } 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 CryptoWorker from '../lib/crypto/cryptoworker'; +import LazyLoadQueue from "./lazyLoadQueue"; +import { wrapSticker } from "./wrappers"; +import appDocsManager from "../lib/appManagers/appDocsManager"; +import ProgressivePreloader from "./preloader"; +import Config from "../lib/config"; +import { MTDocument } from "../types"; +import animationIntersector from "./animationIntersector"; +import appSidebarRight from "../lib/appManagers/appSidebarRight"; + +export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; + +const initEmoticonsDropdown = (pageEl: HTMLDivElement, + appImManager: AppImManager, appMessagesManager: AppMessagesManager, + messageInput: HTMLDivElement, toggleEl: HTMLButtonElement, btnSend: HTMLButtonElement) => { + let dropdown = pageEl.querySelector('.emoji-dropdown') as HTMLDivElement; + + dropdown.style.display = ''; + void dropdown.offsetLeft; // reflow + dropdown.classList.add('active'); // need + + let lazyLoadQueue = new LazyLoadQueue(); + + const searchButton = dropdown.querySelector('.emoji-tabs-search'); + searchButton.addEventListener('click', () => { + appSidebarRight.stickersTab.init(); + }); + + let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement; + let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement; + let tabID = -1; + horizontalMenu(tabs, container, (id) => { + animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); + + tabID = id; + }, () => { + if(tabID == 1 && stickersInit) { + stickersInit(); + } else if(tabID == 2 && gifsInit) { + gifsInit(); + } + + animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); + }); + + (tabs.firstElementChild.children[1] as HTMLLIElement).click(); // set emoji tab + + let emoticonsMenuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => { + menu.addEventListener('click', function(e) { + let target = e.target as HTMLLIElement; + target = findUpTag(target, 'LI'); + + let index = whichChild(target); + let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable + + /* 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); + }); + }); */ + }); + }; + + let emoticonsContentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLDivElement, menuScroll?: Scrollable) => { + let y = 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; + }; + + { + const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"]; + let divs: { + [category: string]: HTMLDivElement + } = {}; + + let sorted: { + [category: string]: string[] + } = {}; + + for(let emoji in Config.Emoji) { + let details = Config.Emoji[emoji]; + let i = '' + details; + let 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(let category in sorted) { + let div = document.createElement('div'); + div.classList.add('emoji-category'); + + let titleDiv = document.createElement('div'); + titleDiv.classList.add('category-title'); + titleDiv.innerText = category; + + let itemsDiv = document.createElement('div'); + itemsDiv.classList.add('category-items'); + + div.append(titleDiv, itemsDiv); + + let emojis = sorted[category]; + emojis.forEach(emoji => { + //let emoji = details.unified; + //let emoji = (details.unified as string).split('-') + //.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); + + let spanEmoji = document.createElement('span'); + let kek = RichTextProcessor.wrapRichText(emoji); + + if(!kek.includes('emoji')) { + console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji)); + return; + } + + //console.log(kek); + + spanEmoji.innerHTML = kek; + + //spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement; + //spanEmoji.setAttribute('emoji', emoji); + itemsDiv.appendChild(spanEmoji); + }); + + divs[category] = div; + } + //console.timeEnd('emojiParse'); + + let contentEmojiDiv = document.getElementById('content-emoji') as HTMLDivElement; + let heights: number[] = [0]; + + let prevCategoryIndex = 1; + let menu = contentEmojiDiv.previousElementSibling.firstElementChild as HTMLUListElement; + let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 'EMOJI', null); + emojiScroll.container.addEventListener('scroll', (e) => { + prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); + }); + //emojiScroll.setVirtualContainer(emojiScroll.container); + + categories.map(category => { + let 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); + heights.push((heights[heights.length - 1] || 0) + div.scrollHeight); + }); + + contentEmojiDiv.addEventListener('click', function(e) { + let target = e.target as any; + //if(target.tagName != 'SPAN') return; + + if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) { + target = target.firstElementChild; + } else if(target.tagName == 'DIV') return; + + //console.log('contentEmoji div', target); + + /* if(!target.classList.contains('emoji')) { + target = target.parentElement as HTMLSpanElement; + + if(!target.classList.contains('emoji')) { + return; + } + } */ + + //messageInput.innerHTML += target.innerHTML; + messageInput.innerHTML += target.outerHTML; + + btnSend.classList.add('tgico-send'); + btnSend.classList.remove('tgico-microphone2'); + }); + + emoticonsMenuOnClick(menu, heights, emojiScroll); + } + + let onMediaClick = (e: MouseEvent) => { + let target = e.target as HTMLDivElement; + target = findUpTag(target, 'DIV'); + + let fileID = target.dataset.docID; + if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { + dropdown.classList.remove('active'); + toggleEl.classList.remove('active'); + } else { + console.warn('got no doc by id:', fileID); + } + }; + + let stickersInit = () => { + let contentStickersDiv = document.getElementById('content-stickers') as HTMLDivElement; + //let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement; + + let menuWrapper = contentStickersDiv.previousElementSibling as HTMLDivElement; + let menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement; + + let menuScroll = new Scrollable(menuWrapper, 'x'); + + let stickersDiv = document.createElement('div'); + stickersDiv.classList.add('stickers-categories'); + contentStickersDiv.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(); + } + } + } + }); */ + + stickersDiv.addEventListener('click', onMediaClick); + + let heights: number[] = []; + + let heightRAF = 0; + let categoryPush = (categoryDiv: HTMLDivElement, 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 => { + let div = document.createElement('div'); + wrapSticker({ + doc, + div, + lazyLoadQueue, + group: EMOTICONSSTICKERGROUP, + onlyThumb: true + }); + + itemsDiv.append(div); + }); + + if(prepend) stickersScroll.prepend(categoryDiv); + else stickersScroll.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; + } */ + + if(heightRAF) window.cancelAnimationFrame(heightRAF); + heightRAF = window.requestAnimationFrame(() => { + heightRAF = 0; + + let paddingTop = parseInt(window.getComputedStyle(stickersScroll.container).getPropertyValue('padding-top')) || 0; + + heights.length = 0; + /* let concated = stickersScroll.hiddenElements.up.concat(stickersScroll.visibleElements, stickersScroll.hiddenElements.down); + concated.forEach((el, i) => { + heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0); + }); */ + let concated = Array.from(stickersScroll.splitUp.children) as HTMLElement[]; + concated.forEach((el, i) => { + heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0); + }); + + //console.log('stickers concated', concated, heights); + }); + + /* Array.from(stickersDiv.children).forEach((div, i) => { + heights[i] = (heights[i - 1] || 0) + div.scrollHeight; + }); */ + + //stickersScroll.onScroll(); + + //return heights.push(prevHeight + scrollHeight) - 1; + }; + + let prevCategoryIndex = 0; + let stickersScroll = new Scrollable(contentStickersDiv, 'y', 'STICKERS', undefined, undefined, 2); + stickersScroll.container.addEventListener('scroll', (e) => { + animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP); + + prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll); + }); + stickersScroll.setVirtualContainer(stickersDiv); + + emoticonsMenuOnClick(menu, heights, stickersScroll, menuScroll); + + stickersInit = null; + + Promise.all([ + appStickersManager.getRecentStickers().then(stickers => { + let categoryDiv = document.createElement('div'); + categoryDiv.classList.add('sticker-category'); + + //stickersScroll.prepend(categoryDiv); + + categoryPush(categoryDiv, 'Recent', stickers.stickers, true); + }), + + apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => { + let stickers: { + _: 'messages.allStickers', + hash: number, + sets: Array + } = res as any; + + for(let set of stickers.sets) { + let categoryDiv = document.createElement('div'); + categoryDiv.classList.add('sticker-category'); + + let li = document.createElement('li'); + li.classList.add('btn-icon'); + + menu.append(li); + + //stickersScroll.append(categoryDiv); + + let stickerSet = await appStickersManager.getStickerSet(set); + + //console.log('got stickerSet', stickerSet, li); + + if(stickerSet.set.thumb) { + appStickersManager.getStickerSetThumb(stickerSet.set).then((blob) => { + //console.log('setting thumb', stickerSet, blob); + if(stickerSet.set.pFlags.animated) { // means animated + const reader = new FileReader(); + + reader.addEventListener('loadend', async(e) => { + // @ts-ignore + const text = e.srcElement.result; + let json = await apiManager.gzipUncompress(text, true); + + let animation = await lottieLoader.loadAnimationWorker({ + container: li, + loop: true, + autoplay: false, + animationData: JSON.parse(json), + width: 40, + height: 40 + }, EMOTICONSSTICKERGROUP); + }); + + reader.readAsArrayBuffer(blob); + } else { + let image = new Image(); + 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 + } + + categoryPush(categoryDiv, stickerSet.set.title, stickerSet.documents, false); + } + }) + ]); + }; + + let gifsInit = () => { + let contentDiv = document.getElementById('content-gifs') as HTMLDivElement; + let masonry = contentDiv.firstElementChild as HTMLDivElement; + + masonry.addEventListener('click', onMediaClick); + + let scroll = new Scrollable(contentDiv, 'y', 'GIFS', null); + + let width = 400; + let maxSingleWidth = width - 100; + let height = 100; + + 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[] = []; + + let wastedWidth = 0; + + res.gifs.forEach((gif, idx) => { + res.gifs[idx] = appDocsManager.saveDoc(gif); + }); + + for(let i = 0, length = res.gifs.length; i < length;) { + let gif = res.gifs[i]; + + let gifWidth = gif.w; + let gifHeight = gif.h; + if(gifHeight < height) { + gifWidth = height / gifHeight * gifWidth; + gifHeight = height; + } + + let willUseWidth = Math.min(maxSingleWidth, width - wastedWidth, gifWidth); + let {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height); + + /* wastedWidth += w; + + if(wastedWidth == width || h < height) { + wastedWidth = 0; + console.log('completed line', i, line); + line = []; + continue; + } + + line.push(gif); */ + ++i; + + console.log('gif:', gif, w, h); + + let div = document.createElement('div'); + div.style.width = w + 'px'; + //div.style.height = h + 'px'; + div.dataset.docID = gif.id; + + masonry.append(div); + + let preloader = new ProgressivePreloader(div); + lazyLoadQueue.push({ + div, + load: () => { + let promise = appDocsManager.downloadDoc(gif); + preloader.attach(div, true, promise); + + promise.then(blob => { + preloader.detach(); + + div.innerHTML = ``; + }); + + return promise; + } + }); + } + }); + + gifsInit = undefined; + }; + + return {dropdown, lazyLoadQueue}; +}; + +export default initEmoticonsDropdown; diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index 89ea4c7f..9c51d8a9 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -8,8 +8,7 @@ type LazyLoadElement = { export default class LazyLoadQueue { private lazyLoadMedia: Array = []; - private loadingMedia = 0; - private tempID = 0; + private inProcess: Array = []; private lockPromise: Promise = null; private unlockResolve: () => void = null; @@ -25,28 +24,32 @@ export default class LazyLoadQueue { this.observer = new IntersectionObserver(entries => { if(this.lockPromise) return; - for(const entry of entries) { - if(entry.isIntersecting) { - const target = entry.target as HTMLElement; + const intersecting = entries.filter(entry => entry.isIntersecting); + intersecting.forEachReverse(entry => { + const target = entry.target as HTMLElement; - this.log('isIntersecting', target); + this.log('isIntersecting', target); - // need for set element first if scrolled - const item = this.lazyLoadMedia.findAndSplice(i => i.div == target); - if(item) { - item.wasSeen = true; - this.lazyLoadMedia.unshift(item); - this.processQueue(item); - } + // need for set element first if scrolled + const item = this.lazyLoadMedia.findAndSplice(i => i.div == target); + if(item) { + item.wasSeen = true; + this.lazyLoadMedia.unshift(item); + //this.processQueue(item); } + }); + + if(intersecting.length) { + this.processQueue(); } }); } public clear() { - this.tempID--; this.lazyLoadMedia.length = 0; - this.loadingMedia = 0; + for(let item of this.inProcess) { + this.lazyLoadMedia.push(item); + } if(this.observer) { this.observer.disconnect(); @@ -54,7 +57,7 @@ export default class LazyLoadQueue { } public length() { - return this.lazyLoadMedia.length + this.loadingMedia; + return this.lazyLoadMedia.length + this.inProcess.length; } public lock() { @@ -72,7 +75,7 @@ export default class LazyLoadQueue { } public async processQueue(item?: LazyLoadElement) { - if(this.parallelLimit > 0 && this.loadingMedia >= this.parallelLimit) return; + if(this.parallelLimit > 0 && this.inProcess.length >= this.parallelLimit) return; if(item) { this.lazyLoadMedia.findAndSplice(i => i == item); @@ -81,9 +84,7 @@ export default class LazyLoadQueue { } if(item) { - this.loadingMedia++; - - let tempID = this.tempID; + this.inProcess.push(item); this.log('will load media', this.lockPromise, item); @@ -95,15 +96,14 @@ export default class LazyLoadQueue { this.log('waited lock:', performance.now() - perf); } + //await new Promise((resolve) => setTimeout(resolve, 2e3)); //await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve))); await item.load(); } catch(err) { this.log.error('loadMediaQueue error:', err, item); } - if(tempID == this.tempID) { - this.loadingMedia--; - } + this.inProcess.findAndSplice(i => i == item); this.log('loaded media', item); diff --git a/src/components/misc.ts b/src/components/misc.ts index 1f2f10bd..824340e3 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -18,7 +18,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise { let elapsedTime = Date.now() - startTime; @@ -328,8 +328,11 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? if(activeStripe) { const tabsRect = tabs.getBoundingClientRect(); - const textRect = target.firstElementChild.getBoundingClientRect(); - activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; + const targetRect = target.getBoundingClientRect(); + const width = 50; + activeStripe.style.cssText = `width: ${width}px; transform: translateX(${targetRect.left - tabsRect.left + ((targetRect.width - width) / 2)}px);`; + /* const textRect = target.firstElementChild.getBoundingClientRect(); + activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; */ //activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`; //console.log('tabs click:', tabsRect, textRect); } @@ -362,9 +365,9 @@ export function formatPhoneNumber(str: string) { } }); - if(country.pattern) { + /* if(country.pattern) { str = str.slice(0, country.pattern.length); - } + } */ } return {formatted: str, country}; diff --git a/src/components/poll.ts b/src/components/poll.ts index c2a1bc33..7e461310 100644 --- a/src/components/poll.ts +++ b/src/components/poll.ts @@ -1,7 +1,11 @@ import appPollsManager, { PollResults, Poll } from "../lib/appManagers/appPollsManager"; import { RichTextProcessor } from "../lib/richtextprocessor"; -import { findUpClassName, $rootScope } from "../lib/utils"; -import { mediaSizes } from "../lib/config"; +import { findUpClassName, $rootScope, cancelEvent } from "../lib/utils"; +import { mediaSizes, touchSupport } from "../lib/config"; +import { ripple } from "./misc"; +import appSidebarRight from "../lib/appManagers/appSidebarRight"; +import appImManager from "../lib/appManagers/appImManager"; +import serverTimeManager from "../lib/mtproto/serverTimeManager"; let lineTotalLength = 0; const tailLength = 9; @@ -9,13 +13,13 @@ const times = 10; const fullTime = 340; const oneTime = fullTime / times; -let roundPercents = (percents: number[]) => { +export const roundPercents = (percents: number[]) => { //console.log('roundPercents before percents:', percents); - let sum = percents.reduce((acc, p) => acc + Math.round(p), 0); + const sum = percents.reduce((acc, p) => acc + Math.round(p), 0); if(sum > 100) { - let diff = sum - 100; - let length = percents.length; + const diff = sum - 100; + const length = percents.length; for(let i = 0; i < diff; ++i) { let minIndex = -1, minRemainder = 1; for(let k = 0; k < length; ++k) { @@ -27,14 +31,15 @@ let roundPercents = (percents: number[]) => { } if(minIndex == -1) { - throw new Error('lol chto'); + //throw new Error('lol chto'); + return; } percents[minIndex] -= minRemainder; } } else if(sum < 100) { - let diff = 100 - sum; - let length = percents.length; + const diff = 100 - sum; + const length = percents.length; for(let i = 0; i < diff; ++i) { let minIndex = -1, maxRemainder = 0; for(let k = 0; k < length; ++k) { @@ -46,7 +51,8 @@ let roundPercents = (percents: number[]) => { } if(minIndex == -1) { - throw new Error('lol chto'); + //throw new Error('lol chto'); + return; } percents[minIndex] += 1 - maxRemainder; @@ -58,32 +64,103 @@ let roundPercents = (percents: number[]) => { const connectedPolls: {id: string, element: PollElement}[] = []; $rootScope.$on('poll_update', (e: CustomEvent) => { - let {poll, results} = e.detail as {poll: Poll, results: PollResults}; + const {poll, results} = e.detail as {poll: Poll, results: PollResults}; - for(let connected of connectedPolls) { + //console.log('poll_update', poll, results); + for(const connected of connectedPolls) { if(connected.id == poll.id) { - let pollElement = connected.element; - pollElement.performResults(results, poll.chosenIndex); + const pollElement = connected.element; + pollElement.isClosed = !!poll.pFlags.closed; + pollElement.performResults(results, poll.chosenIndexes); } } }); +$rootScope.$on('peer_changed', () => { + if(prevQuizHint) { + hideQuizHint(prevQuizHint, prevQuizHintOnHide, prevQuizHintTimeout); + } +}); + +const hideQuizHint = (element: HTMLElement, onHide: () => void, timeout: number) => { + element.classList.remove('active'); + + clearTimeout(timeout); + setTimeout(() => { + onHide(); + element.remove(); + + if(prevQuizHint == element && prevQuizHintOnHide == onHide && prevQuizHintTimeout == timeout) { + prevQuizHint = prevQuizHintOnHide = null; + prevQuizHintTimeout = 0; + } + }, 200); +}; + +let prevQuizHint: HTMLElement, prevQuizHintOnHide: () => void, prevQuizHintTimeout: number; +const setQuizHint = (solution: string, solution_entities: any[], onHide: () => void) => { + if(prevQuizHint) { + hideQuizHint(prevQuizHint, prevQuizHintOnHide, prevQuizHintTimeout); + } + + const element = document.createElement('div'); + element.classList.add('quiz-hint'); + + const container = document.createElement('div'); + container.classList.add('container', 'tgico'); + + const textEl = document.createElement('div'); + textEl.classList.add('text'); + + container.append(textEl); + element.append(container); + + textEl.innerHTML = RichTextProcessor.wrapRichText(solution, {entities: solution_entities}); + appImManager.bubblesContainer.append(element); + + void element.offsetLeft; // reflow + element.classList.add('active'); + + prevQuizHint = element; + prevQuizHintOnHide = onHide; + prevQuizHintTimeout = setTimeout(() => { + hideQuizHint(element, onHide, prevQuizHintTimeout); + }, touchSupport ? 5000 : 7000); +}; + export default class PollElement extends HTMLElement { private svgLines: SVGSVGElement[]; private numberDivs: HTMLDivElement[]; - private selectedSpan: HTMLSpanElement; private answerDivs: HTMLDivElement[]; + private descDiv: HTMLElement; + private typeDiv: HTMLElement; + private avatarsDiv: HTMLElement; + private viewResults: HTMLElement; private votersCountDiv: HTMLDivElement; private maxOffset = -46.5; private maxLength: number; private maxLengths: number[]; + public isClosed = false; private isQuiz = false; private isRetracted = false; - private chosenIndex = -1; + private isPublic = false; + private isMultiple = false; + private chosenIndexes: number[] = []; private percents: number[]; + private pollID: string; + private mid: number; + + private quizInterval: number; + private quizTimer: SVGSVGElement; + + private sendVoteBtn: HTMLElement; + private chosingIndexes: number[] = []; + + private sendVotePromise: Promise; + constructor() { super(); // элемент создан @@ -98,28 +175,32 @@ export default class PollElement extends HTMLElement { console.log('line total length:', lineTotalLength); } - let pollID = this.getAttribute('poll-id'); - let {poll, results} = appPollsManager.getPoll(pollID); + this.pollID = this.getAttribute('poll-id'); + this.mid = +this.getAttribute('message-id'); + const {poll, results} = appPollsManager.getPoll(this.pollID); - connectedPolls.push({id: pollID, element: this}); + connectedPolls.push({id: this.pollID, element: this}); console.log('pollElement poll:', poll, results); let desc = ''; if(poll.pFlags) { - if(poll.pFlags.closed) { + this.isPublic = !!poll.pFlags.public_voters; + this.isQuiz = !!poll.pFlags.quiz; + this.isClosed = !!poll.pFlags.closed; + this.isMultiple = !!poll.pFlags.multiple_choice; + + if(this.isClosed) { desc = 'Final results'; + this.classList.add('is-closed'); } else { - if(poll.pFlags.quiz) { - this.isQuiz = true; - } - let type = this.isQuiz ? 'Quiz' : 'Poll'; - desc = (poll.pFlags.public_voters ? 'Public' : 'Anonymous') + ' ' + type; + desc = (this.isPublic ? '' : 'Anonymous ') + type; } } - let votes = poll.answers.map((answer, idx) => { + const multipleSelect = this.isMultiple ? '' : ''; + const votes = poll.answers.map((answer, idx) => { return `
@@ -127,34 +208,127 @@ export default class PollElement extends HTMLElement { + ${multipleSelect}
${RichTextProcessor.wrapEmojiText(answer.text)}
+
`; }).join(''); this.innerHTML = `
${poll.rQuestion}
-
${desc}
+
+
${desc}
+
+
${votes} -
+ `; + this.descDiv = this.firstElementChild.nextElementSibling as HTMLElement; + this.typeDiv = this.descDiv.firstElementChild as HTMLElement; + this.avatarsDiv = this.descDiv.lastElementChild as HTMLElement; + + if(this.isQuiz) { + this.classList.add('is-quiz'); + + if(poll.close_period && poll.close_date) { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + //svg.setAttributeNS(null, 'viewBox', '0 0 15 15'); + svg.classList.add('poll-quiz-timer'); + + this.quizTimer = svg; + + const strokeWidth = 2; + const radius = (15 / 2) - (strokeWidth * 2); + const circumference = 2 * Math.PI * radius; + + const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + circle.classList.add('poll-quiz-timer-circle'); + circle.setAttributeNS(null, 'cx', '15'); + circle.setAttributeNS(null, 'cy', '15'); + circle.setAttributeNS(null, 'r', '' + radius); + + svg.append(circle); + + this.descDiv.append(svg); + + const period = poll.close_period * 1000; + const closeTime = (poll.close_date - serverTimeManager.serverTimeOffset) * 1000; + this.quizInterval = setInterval(() => { + const time = Date.now(); + + const totalLength = circle.getTotalLength(); + const percents = (closeTime - time) / period; + circle.style.strokeDasharray = '' + (percents * totalLength) + ', ' + circumference; + + if(time >= closeTime) { + clearInterval(this.quizInterval); + this.quizInterval = 0; + + // нужно запросить апдейт чтобы опрос обновился + appPollsManager.getResults(this.mid); + } + }, 1e3); + } + } + this.answerDivs = Array.from(this.querySelectorAll('.poll-answer')) as HTMLDivElement[]; - this.votersCountDiv = this.querySelector('.poll-votes-count') as HTMLDivElement; this.svgLines = Array.from(this.querySelectorAll('.poll-line')) as SVGSVGElement[]; this.numberDivs = Array.from(this.querySelectorAll('.poll-answer-percents')) as HTMLDivElement[]; - let width = this.getBoundingClientRect().width; + const footerDiv = this.lastElementChild; + this.viewResults = footerDiv.firstElementChild as HTMLElement; + this.votersCountDiv = footerDiv.lastElementChild as HTMLDivElement; + + this.viewResults.addEventListener('click', (e) => { + cancelEvent(e); + appSidebarRight.pollResultsTab.init(this.pollID, this.mid); + }); + ripple(this.viewResults); + + if(this.isMultiple) { + this.sendVoteBtn = document.createElement('div'); + this.sendVoteBtn.classList.add('poll-footer-button', 'poll-send-vote'); + this.sendVoteBtn.innerText = 'Vote'; + ripple(this.sendVoteBtn); + + if(!poll.chosenIndexes.length) { + this.votersCountDiv.classList.add('hide'); + } + + this.sendVoteBtn.addEventListener('click', () => { + /* const indexes = this.answerDivs.filter(el => el.classList.contains('is-chosing')).map(el => +el.dataset.index); + if(indexes.length) { + + } */ + if(this.chosingIndexes.length) { + this.sendVotes(this.chosingIndexes).then(() => { + this.chosingIndexes.length = 0; + this.answerDivs.forEach(el => { + el.classList.remove('is-chosing'); + }); + }); + } + }); + + footerDiv.append(this.sendVoteBtn); + } + + const width = this.getBoundingClientRect().width; this.maxLength = width + tailLength + this.maxOffset + -13.7; // 13 - position left - if(poll.chosenIndex !== -1) { - this.performResults(results, poll.chosenIndex); - } else { + if(poll.chosenIndexes.length || this.isClosed) { + this.performResults(results, poll.chosenIndexes); + } else if(!this.isClosed) { this.setVotersCount(results); this.addEventListener('click', this.clickHandler); } @@ -168,11 +342,16 @@ export default class PollElement extends HTMLElement { } static get observedAttributes(): string[] { - return [/* массив имён атрибутов для отслеживания их изменений */]; + return ['poll-id', 'message-id'/* массив имён атрибутов для отслеживания их изменений */]; } attributeChangedCallback(name: string, oldValue: string, newValue: string) { // вызывается при изменении одного из перечисленных выше атрибутов + if(name == 'poll-id') { + this.pollID = newValue; + } else if(name == 'message-id') { + this.mid = +newValue; + } } adoptedCallback() { @@ -180,14 +359,45 @@ export default class PollElement extends HTMLElement { // (происходит в document.adoptNode, используется очень редко) } + initQuizHint(results: PollResults) { + if(results.solution && results.solution_entities) { + const toggleHint = document.createElement('div'); + toggleHint.classList.add('tgico-tip', 'poll-hint'); + this.descDiv.append(toggleHint); + + //let active = false; + toggleHint.addEventListener('click', (e) => { + cancelEvent(e); + + //active = true; + toggleHint.classList.add('active'); + setQuizHint(results.solution, results.solution_entities, () => { + //active = false; + toggleHint.classList.remove('active'); + }); + }); + } + } + clickHandler(e: MouseEvent) { - let target = findUpClassName(e.target, 'poll-answer') as HTMLElement; + const target = findUpClassName(e.target, 'poll-answer') as HTMLElement; if(!target) { return; } - let answerIndex = +target.dataset.index; - this.sendVote(answerIndex); + const answerIndex = +target.dataset.index; + if(this.isMultiple) { + target.classList.toggle('is-chosing'); + + const foundIndex = this.chosingIndexes.indexOf(answerIndex); + if(foundIndex !== -1) { + this.chosingIndexes.splice(foundIndex, 1); + } else { + this.chosingIndexes.push(answerIndex); + } + } else { + this.sendVotes([answerIndex]); + } /* target.classList.add('is-voting'); setTimeout(() => { // simulate @@ -196,23 +406,57 @@ export default class PollElement extends HTMLElement { }, 1000); */ } - sendVote(index: number) { - let target = this.answerDivs[index]; - target.classList.add('is-voting'); - let mid = +this.getAttribute('message-id'); + sendVotes(indexes: number[]) { + if(this.sendVotePromise) return this.sendVotePromise; + + const targets = this.answerDivs.filter((_, idx) => indexes.includes(idx)); + targets.forEach(target => { + target.classList.add('is-voting'); + }); this.classList.add('disable-hover'); - appPollsManager.sendVote(mid, [index]).then(() => { - target.classList.remove('is-voting'); + return this.sendVotePromise = appPollsManager.sendVote(this.mid, indexes).then(() => { + targets.forEach(target => { + target.classList.remove('is-voting'); + }); + this.classList.remove('disable-hover'); + }).finally(() => { + this.sendVotePromise = null; }); } - performResults(results: PollResults, chosenIndex: number) { - if(this.chosenIndex != chosenIndex) { // if we voted - this.isRetracted = this.chosenIndex != -1 && chosenIndex == -1; - this.chosenIndex = chosenIndex; - + performResults(results: PollResults, chosenIndexes: number[]) { + if(this.isQuiz && (results.results?.length || this.isClosed)) { + this.answerDivs.forEach((el, idx) => { + el.classList.toggle('is-correct', !!results.results[idx].pFlags.correct); + }); + + if(this.initQuizHint) { + this.initQuizHint(results); + delete this.initQuizHint; + } + + if(this.quizInterval) { + clearInterval(this.quizInterval); + this.quizInterval = 0; + } + + if(this.quizTimer?.parentElement) { + this.quizTimer.remove(); + } + } + + if(this.isClosed) { + this.classList.add('is-closed'); + this.typeDiv.innerText = 'Final results'; + } + + // set chosen + if(this.chosenIndexes.length != chosenIndexes.length || this.isClosed) { // if we voted + this.isRetracted = this.chosenIndexes.length && !chosenIndexes.length; + this.chosenIndexes = chosenIndexes.slice(); + if(this.isRetracted) { this.addEventListener('click', this.clickHandler); } else { @@ -221,29 +465,56 @@ export default class PollElement extends HTMLElement { } // is need update - if(this.chosenIndex != -1 || this.isRetracted) { - const percents = results.results.map(v => v.voters / results.total_voters * 100); - this.setResults(this.isRetracted ? this.percents : percents, chosenIndex); + if(this.chosenIndexes.length || this.isRetracted || this.isClosed) { + const percents = results.results.map(v => results.total_voters ? v.voters / results.total_voters * 100 : 0); + this.setResults(this.isRetracted ? this.percents : percents, this.chosenIndexes); this.percents = percents; this.isRetracted = false; } this.setVotersCount(results); - } - setResults(percents: number[], chosenIndex: number) { - this.svgLines.forEach(svg => svg.style.display = ''); + if(this.isPublic) { + if(!this.isMultiple) { + this.viewResults.classList.toggle('hide', !results.total_voters || !this.chosenIndexes.length); + this.votersCountDiv.classList.toggle('hide', !!this.chosenIndexes.length); + } + + let html = ''; + /** + * MACOS, ANDROID - без реверса + * WINDOWS DESKTOP - реверс + * все приложения накладывают аватарку первую на вторую, а в макете зато вторая на первую, ЛОЛ! + */ + results.recent_voters/* .slice().reverse() */.forEach((userID, idx) => { + const style = idx == 0 ? '' : `style="transform: translateX(-${idx * 5}px);"`; + html += ``; + }); + this.avatarsDiv.innerHTML = html; + } - if(chosenIndex !== -1) { - let answerDiv = this.answerDivs[chosenIndex]; - if(!this.selectedSpan) { - this.selectedSpan = document.createElement('span'); - this.selectedSpan.classList.add('poll-answer-selected', 'tgico-check'); + if(this.isMultiple) { + this.sendVoteBtn.classList.toggle('hide', !!this.chosenIndexes.length); + if(!this.chosenIndexes.length) { + this.votersCountDiv.classList.add('hide'); + this.viewResults.classList.add('hide'); + } else if(this.isPublic) { + this.viewResults.classList.toggle('hide', !results.total_voters || !this.chosenIndexes.length); + this.votersCountDiv.classList.toggle('hide', !!this.chosenIndexes.length); + } else { + this.votersCountDiv.classList.toggle('hide', !this.chosenIndexes.length); } - answerDiv.append(this.selectedSpan); } + } + + setResults(percents: number[], chosenIndexes: number[]) { + this.svgLines.forEach(svg => svg.style.display = ''); + + this.answerDivs.forEach((el, idx) => { + el.classList.toggle('is-chosen', chosenIndexes.includes(idx)); + }); - let maxValue = Math.max(...percents); + const maxValue = Math.max(...percents); this.maxLengths = percents.map(p => p / maxValue * this.maxLength); // line @@ -265,7 +536,7 @@ export default class PollElement extends HTMLElement { for(let i = (times - 1), k = 0; i >= 0; --i, ++k) { setTimeout(() => { percents.forEach((percents, idx) => { - let value = Math.round(percents / times * i); + const value = Math.round(percents / times * i); this.numberDivs[idx].innerText = value + '%'; }); }, oneTime * k); @@ -274,7 +545,7 @@ export default class PollElement extends HTMLElement { for(let i = 0; i < times; ++i) { setTimeout(() => { percents.forEach((percents, idx) => { - let value = Math.round(percents / times * (i + 1)); + const value = Math.round(percents / times * (i + 1)); this.numberDivs[idx].innerText = value + '%'; }); }, oneTime * i); @@ -294,14 +565,14 @@ export default class PollElement extends HTMLElement { } setVotersCount(results: PollResults) { - let votersCount = results.total_voters || 0; - let votersOrAnswers = this.isQuiz ? (votersCount > 1 || !votersCount ? 'answers' : 'answer') : (votersCount > 1 || !votersCount ? 'votes' : 'vote'); + const votersCount = results.total_voters || 0; + const votersOrAnswers = this.isQuiz ? (votersCount > 1 || !votersCount ? 'answers' : 'answer') : (votersCount > 1 || !votersCount ? 'votes' : 'vote'); this.votersCountDiv.innerText = `${results.total_voters ? results.total_voters + ' ' + votersOrAnswers : 'No ' + votersOrAnswers}`; } setLineProgress(index: number, percents: number) { - let svg = this.svgLines[index]; + const svg = this.svgLines[index]; if(percents == -1) { svg.style.strokeDasharray = ''; diff --git a/src/components/popup.ts b/src/components/popup.ts index ee5ce780..0245165d 100644 --- a/src/components/popup.ts +++ b/src/components/popup.ts @@ -6,8 +6,14 @@ export class PopupElement { protected container = document.createElement('div'); protected header = document.createElement('div'); protected title = document.createElement('div'); + protected closeBtn: HTMLElement; + protected confirmBtn: HTMLElement; + protected body: HTMLElement; - constructor(className: string, buttons?: Array) { + protected onClose: () => void; + protected onCloseAfterTimeout: () => void; + + constructor(className: string, buttons?: Array, options: Partial<{closable: boolean, withConfirm: string, body: boolean}> = {}) { this.element.classList.add('popup'); this.element.className = 'popup' + (className ? ' ' + className : ''); this.container.classList.add('popup-container', 'z-depth-1'); @@ -16,7 +22,32 @@ export class PopupElement { this.title.classList.add('popup-title'); this.header.append(this.title); + + if(options.closable) { + this.closeBtn = document.createElement('span'); + this.closeBtn.classList.add('btn-icon', 'popup-close', 'tgico-close'); + ripple(this.closeBtn); + this.header.prepend(this.closeBtn); + + this.closeBtn.addEventListener('click', () => { + this.destroy(); + }, {once: true}); + } + + if(options.withConfirm) { + this.confirmBtn = document.createElement('button'); + this.confirmBtn.classList.add('btn-primary'); + this.confirmBtn.innerText = options.withConfirm; + this.header.append(this.confirmBtn); + ripple(this.confirmBtn); + } + this.container.append(this.header); + if(options.body) { + this.body = document.createElement('div'); + this.body.classList.add('popup-body'); + this.container.append(this.body); + } if(buttons && buttons.length) { const buttonsDiv = document.createElement('div'); @@ -56,9 +87,11 @@ export class PopupElement { } public destroy() { + this.onClose && this.onClose(); this.element.classList.remove('active'); setTimeout(() => { this.element.remove(); + this.onCloseAfterTimeout && this.onCloseAfterTimeout(); }, 1000); } } diff --git a/src/components/popupCreatePoll.ts b/src/components/popupCreatePoll.ts new file mode 100644 index 00000000..1799af11 --- /dev/null +++ b/src/components/popupCreatePoll.ts @@ -0,0 +1,134 @@ +import { PopupElement } from "./popup"; +import Scrollable from "./scrollable_new"; +import appMessagesManager from "../lib/appManagers/appMessagesManager"; +import { $rootScope } from "../lib/utils"; +import { Poll } from "../lib/appManagers/appPollsManager"; +import { nextRandomInt, bigint } from "../lib/bin_utils"; +import { toast } from "./misc"; + +const InputField = (placeholder: string, label: string, name: string) => { + const div = document.createElement('div'); + div.classList.add('input-field'); + + div.innerHTML = ` + + + `; + + return div; +}; + +export default class PopupCreatePoll extends PopupElement { + private questionInput: HTMLInputElement; + private questions: HTMLElement; + private scrollable: Scrollable; + private tempID = 0; + + constructor() { + super('popup-create-poll popup-new-media', null, {closable: true, withConfirm: 'CREATE', body: true}); + + this.title.innerText = 'New Poll'; + + const questionField = InputField('Ask a question', 'Ask a question', 'question'); + this.questionInput = questionField.firstElementChild as HTMLInputElement; + + this.header.append(questionField); + + const d = document.createElement('div'); + d.innerText = 'Options'; + + this.questions = document.createElement('div'); + this.questions.classList.add('poll-create-questions'); + + this.body.append(d, this.questions); + + this.confirmBtn.addEventListener('click', this.onSubmitClick); + + this.scrollable = new Scrollable(this.body, 'y', undefined); + this.appendMoreField(); + } + + onSubmitClick = (e: MouseEvent) => { + const question = this.questionInput.value; + + if(!question.trim()) { + toast('Please enter a question'); + return; + } + + const answers = Array.from(this.questions.children).map((el, idx) => { + const input = (el.firstElementChild as HTMLInputElement); + return input.value; + }).filter(v => !!v.trim()); + + if(answers.length < 2) { + toast('Please enter at least two options'); + return; + } + + this.closeBtn.click(); + this.confirmBtn.removeEventListener('click', this.onSubmitClick); + + //const randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]; + //const randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(); + + const poll: Partial = {}; + poll._ = 'poll'; + //poll.id = randomIDS; + poll.flags = 0; + poll.question = question; + + poll.answers = answers.map((value, idx) => { + return { + _: 'pollAnswer', + text: value, + option: new Uint8Array([idx]) + }; + }); + + appMessagesManager.sendOther($rootScope.selectedPeerID, { + _: 'inputMediaPoll', + flags: 0, + poll + }); + }; + + onInput = (e: Event) => { + const target = e.target as HTMLInputElement; + + if(target.value.length) { + target.parentElement.classList.add('is-filled'); + } + + const isLast = !target.parentElement.nextElementSibling; + if(isLast && target.value.length && this.questions.childElementCount < 10) { + this.appendMoreField(); + } + }; + + onDeleteClick = (e: MouseEvent) => { + const target = e.target as HTMLSpanElement; + target.parentElement.remove(); + + Array.from(this.questions.children).forEach((el, idx) => { + const label = el.firstElementChild.nextElementSibling as HTMLLabelElement; + label.innerText = 'Option ' + (idx + 1); + }); + }; + + private appendMoreField() { + const idx = this.questions.childElementCount + 1; + const questionField = InputField('Add an Option', 'Option ' + idx, 'question-' + this.tempID++); + (questionField.firstElementChild as HTMLInputElement).addEventListener('input', this.onInput); + + const deleteBtn = document.createElement('span'); + deleteBtn.classList.add('btn-icon', 'tgico-close'); + questionField.append(deleteBtn); + + deleteBtn.addEventListener('click', this.onDeleteClick, {once: true}); + + this.questions.append(questionField); + + this.scrollable.scrollTo(this.scrollable.scrollHeight, true, true); + } +} \ No newline at end of file diff --git a/src/components/popupStickers.ts b/src/components/popupStickers.ts index a1ead0a1..4cb60027 100644 --- a/src/components/popupStickers.ts +++ b/src/components/popupStickers.ts @@ -4,17 +4,15 @@ import { RichTextProcessor } from "../lib/richtextprocessor"; import Scrollable from "./scrollable_new"; import { wrapSticker } from "./wrappers"; import LazyLoadQueue from "./lazyLoadQueue"; -import { putPreloader, ripple } from "./misc"; +import { putPreloader } from "./misc"; import animationIntersector from "./animationIntersector"; import { findUpClassName } from "../lib/utils"; -import appDocsManager from "../lib/appManagers/appDocsManager"; import appImManager from "../lib/appManagers/appImManager"; export default class PopupStickers extends PopupElement { private stickersFooter: HTMLElement; private stickersDiv: HTMLElement; private h6: HTMLElement; - private closeBtn: HTMLElement; private set: MTStickerSet; @@ -23,30 +21,23 @@ export default class PopupStickers extends PopupElement { id: string, access_hash: string }) { - super('popup-stickers'); + super('popup-stickers', null, {closable: true, body: true}); this.h6 = document.createElement('h6'); this.h6.innerText = 'Loading...'; - const popupBody = document.createElement('div'); - popupBody.classList.add('popup-body'); + this.header.append(this.h6); - this.closeBtn = document.createElement('span'); - this.closeBtn.classList.add('btn-icon', 'popup-close', 'tgico-close'); - this.header.append(this.closeBtn, this.h6); - - this.closeBtn.addEventListener('click', () => { - this.destroy(); + this.onClose = () => { animationIntersector.checkAnimations(false); this.stickersFooter.removeEventListener('click', this.onFooterClick); this.stickersDiv.removeEventListener('click', this.onStickersClick); this.element.removeEventListener('click', onOverlayClick); + }; - setTimeout(() => { - animationIntersector.checkAnimations(undefined, 'STICKERS-POPUP'); - }, 1001); - }, {once: true}); - ripple(this.closeBtn); + this.onCloseAfterTimeout = () => { + animationIntersector.checkAnimations(undefined, 'STICKERS-POPUP'); + }; const onOverlayClick = (e: MouseEvent) => { if(!findUpClassName(e.target, 'popup-container')) { @@ -71,12 +62,9 @@ export default class PopupStickers extends PopupElement { this.stickersFooter.innerText = 'Loading...'; - popupBody.append(div); - this.container.append(popupBody); - - const scrollable = new Scrollable(popupBody, 'y', undefined); - - popupBody.append(this.stickersFooter); + this.body.append(div); + const scrollable = new Scrollable(this.body, 'y', undefined); + this.body.append(this.stickersFooter); // const editButton = document.createElement('button'); // editButton.classList.add('btn-primary'); @@ -111,11 +99,6 @@ export default class PopupStickers extends PopupElement { private loadStickerSet() { return appStickersManager.getStickerSet(this.stickerSetInput).then(set => { //console.log('PopupStickers loadStickerSet got set:', set); - //JOPA SGORELA SUKA, kak zdes mojet bit false esli u menya etot nabor dobavlen i otobrajaetsya v telege ??? - //koroche sut v tom, chto esli ti dobavil nabor a potom ctrl + f5 , i najimaesh na popup stikera v chate, to ono dumaet chto nabora net, potomu chto installed_date kakogoto huya false, i ego pravda tam net. - //razberis brat.. a tak vrode vse rabotaet namana. ya gavnokoder i gavnoverstker - //testiroval na stikere v dialoge viti (zeleniy dinozavr) http://i.piccy.info/i9/71cbc718bedb6d8a33da9bff775d8316/1591669743/204119/1382638/Snymok_ekrana_2020_06_09_v_05_28_25.jpg - //console.log('hasOwnProperty got set installed_date ????', set.set.hasOwnProperty('installed_date')); this.set = set.set; diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 8c2d1d62..083a6252 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -58,9 +58,14 @@ export default class ProgressivePreloader { this.promise = promise = null; } }; + //promise.catch(onEnd); promise.finally(onEnd); promise.notify = (details: {done: number, total: number}) => { + /* if(details.done >= details.total) { + onEnd(); + } */ + if(tempID != this.tempID) return; //console.log('preloader download', promise, details); diff --git a/src/components/scrollable_new.ts b/src/components/scrollable_new.ts index b1207c62..34c56acb 100644 --- a/src/components/scrollable_new.ts +++ b/src/components/scrollable_new.ts @@ -1,5 +1,6 @@ import { logger, LogLevels } from "../lib/polyfill"; import smoothscroll from '../lib/smoothscroll'; +import { touchSupport, isSafari } from "../lib/config"; //import { isInDOM } from "../lib/utils"; (window as any).__forceSmoothScrollPolyfill__ = true; smoothscroll.polyfill(); @@ -103,22 +104,22 @@ export default class Scrollable { this.visible = new Set(); this.observer = new IntersectionObserver(entries => { - let filtered = entries.filter(entry => entry.isIntersecting); + const filtered = entries.filter(entry => entry.isIntersecting); //return; //this.log('entries:', entries); entries.forEach(entry => { - let target = entry.target as HTMLElement; + const target = entry.target as HTMLElement; if(entry.isIntersecting) { this.setVisible(target); this.log.debug('intersection entry:', entry, this.lastTopID, this.lastBottomID); } else { - let id = +target.dataset.virtual; - let isTop = entry.boundingClientRect.top < 0; + const id = +target.dataset.virtual; + const isTop = entry.boundingClientRect.top < 0; if(isTop) { this.lastTopID = id + 1; @@ -159,10 +160,10 @@ export default class Scrollable { this.log.debug('entries:', entries, filtered, this.lastScrollDirection, this.lastTopID, this.lastBottomID); - let minVisibleID = this.lastTopID - this.splitCount; - let maxVisibleID = this.lastBottomID + this.splitCount; - for(let target of this.visible) { - let id = +target.dataset.virtual; + const minVisibleID = this.lastTopID - this.splitCount; + const maxVisibleID = this.lastBottomID + this.splitCount; + for(const target of this.visible) { + const id = +target.dataset.virtual; if(id < minVisibleID || id > maxVisibleID) { this.setHidden(target); } @@ -178,9 +179,9 @@ export default class Scrollable { if(axis == 'x') { this.container.classList.add('scrollable-x'); - let scrollHorizontally = (e: any) => { + const scrollHorizontally = (e: any) => { e = window.event || e; - var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); + const delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); this.container.scrollLeft -= (delta * 20); e.preventDefault(); }; @@ -217,6 +218,27 @@ export default class Scrollable { this.overflowContainer = window.innerWidth <= 720 && false ? document.documentElement : this.container; + if(touchSupport && isSafari) { + let allowUp: boolean, allowDown: boolean, slideBeginY: number; + this.container.addEventListener('touchstart', (event) => { + allowUp = this.container.scrollTop > 0; + allowDown = (this.container.scrollTop < this.container.scrollHeight - this.container.clientHeight); + // @ts-ignore + slideBeginY = event.pageY; + }); + + this.container.addEventListener('touchmove', (event: any) => { + var up = (event.pageY > slideBeginY); + var down = (event.pageY < slideBeginY); + slideBeginY = event.pageY; + if((up && allowUp) || (down && allowDown)) { + event.stopPropagation(); + } else { + event.preventDefault(); + } + }); + } + /* scrollables.set(this.container, this); scrollsIntersector.observe(this.container); */ } @@ -323,7 +345,7 @@ export default class Scrollable { this.onScrollMeasure = 0; if(!this.splitUp) return; - let scrollTop = this.overflowContainer.scrollTop; + const scrollTop = this.overflowContainer.scrollTop; if(this.lastScrollTop != scrollTop) { this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1; this.lastScrollTop = scrollTop; @@ -336,8 +358,8 @@ export default class Scrollable { public checkForTriggers(container: HTMLElement) { if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return; - let scrollTop = container.scrollTop; - let maxScrollTop = container.scrollHeight - container.clientHeight; + const scrollTop = container.scrollTop; + const maxScrollTop = container.scrollHeight - container.clientHeight; //this.log('checkForTriggers:', scrollTop, maxScrollTop); @@ -359,7 +381,7 @@ export default class Scrollable { public updateElement(element: HTMLElement) { element.style.minHeight = ''; window.requestAnimationFrame(() => { - let height = element.scrollHeight; + const height = element.scrollHeight; window.requestAnimationFrame(() => { element.style.minHeight = height + 'px'; @@ -368,12 +390,13 @@ export default class Scrollable { } public prepareElement(element: HTMLElement, append = true) { + //return; element.dataset.virtual = '' + (append ? this.virtualTempIDBottom++ : this.virtualTempIDTop--); this.log.debug('prepareElement: prepared'); window.requestAnimationFrame(() => { - let {scrollHeight/* , scrollWidth */} = element; + const {scrollHeight/* , scrollWidth */} = element; this.log.debug('prepareElement: first rAF'); @@ -413,7 +436,7 @@ export default class Scrollable { public scrollIntoView(element: HTMLElement, smooth = true) { if(element.parentElement && !this.scrollLocked) { - let isFirstUnread = element.classList.contains('is-first-unread'); + const isFirstUnread = element.classList.contains('is-first-unread'); let offsetTop = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top; offsetTop = this.container.scrollTop + offsetTop; @@ -423,10 +446,10 @@ export default class Scrollable { return; } - let clientHeight = this.container.clientHeight; - let height = element.scrollHeight; + const clientHeight = this.container.clientHeight; + const height = element.scrollHeight; - let d = (clientHeight - height) / 2; + const d = (clientHeight - height) / 2; offsetTop -= d; this.scrollTo(offsetTop, smooth); @@ -436,7 +459,7 @@ export default class Scrollable { public scrollTo(top: number, smooth = true, important = false) { if(this.scrollLocked && !important) return; - let scrollTop = this.scrollTop; + const scrollTop = this.scrollTop; if(scrollTop == Math.floor(top)) { return; } diff --git a/src/components/slider.ts b/src/components/slider.ts index e464798f..293f0b80 100644 --- a/src/components/slider.ts +++ b/src/components/slider.ts @@ -14,7 +14,7 @@ export default class SidebarSlider { this._selectTab(0); let onCloseBtnClick = () => { - console.log('sidebar-close-button click:', this.historyTabIDs); + //console.log('sidebar-close-button click:', this.historyTabIDs); let closingID = this.historyTabIDs.pop(); // pop current this.onCloseTab(closingID); this._selectTab(this.historyTabIDs[this.historyTabIDs.length - 1] || 0); @@ -25,6 +25,10 @@ export default class SidebarSlider { } public selectTab(id: number) { + if(this.historyTabIDs[this.historyTabIDs.length - 1] == id) { + return; + } + this.historyTabIDs.push(id); this._selectTab(id); } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index fe1a2445..70368cdf 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -18,6 +18,7 @@ import { mediaSizes } from '../lib/config'; import { MTDocument, MTPhotoSize } from '../types'; import animationIntersector from './animationIntersector'; import AudioElement from './audio'; +import MP4Source from '../lib/MP4Sourcee'; export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: { doc: MTDocument, @@ -30,6 +31,23 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai middleware: () => boolean, lazyLoadQueue: LazyLoadQueue }) { + let span: HTMLSpanElement, spanPlay: HTMLSpanElement; + if(doc.type != 'round') { + span = document.createElement('span'); + span.classList.add('video-time'); + container.append(span); + + if(doc.type != 'gif') { + span.innerText = (doc.duration + '').toHHMMSS(false); + + spanPlay = document.createElement('span'); + spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); + container.append(spanPlay); + } else { + span.innerText = 'GIF'; + } + } + if(doc.type == 'video') { return wrapPhoto(doc, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware); } @@ -55,19 +73,16 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai } } - let video = document.createElement('video'); - let source = document.createElement('source'); - video.append(source); - - if(doc.type == 'gif') { - video.addEventListener('loadeddata', () => { - video.pause(); - animationIntersector.addAnimation(video, 'chat'); - }, {once: true}); + if(img) { + img.classList.add('thumbnail'); } + + const video = document.createElement('video'); + const source = document.createElement('source'); + video.append(source); if(withTail) { - let foreignObject = img.parentElement; + const foreignObject = img.parentElement; video.width = +foreignObject.getAttributeNS(null, 'width'); video.height = +foreignObject.getAttributeNS(null, 'height'); foreignObject.append(video); @@ -75,31 +90,26 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai container.append(video); } - let span: HTMLSpanElement, spanPlay: HTMLSpanElement; - if(doc.type != 'round') { - span = document.createElement('span'); - span.classList.add('video-time'); - container.append(span); - - if(doc.type != 'gif') { - span.innerText = (doc.duration + '').toHHMMSS(false); - - spanPlay = document.createElement('span'); - spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); - container.append(spanPlay); - } else { - span.innerText = 'GIF'; - } - } - let loadVideo = async() => { + let url: string; if(message.media.preloader) { // means upload (message.media.preloader as ProgressivePreloader).attach(container, undefined, undefined, false); } else if(!doc.downloaded) { - let preloader = new ProgressivePreloader(container, true); - let promise = appDocsManager.downloadDoc(doc); - preloader.attach(container, true, promise, false); - await promise; + const promise = appDocsManager.downloadVideo(doc.id); + + //if(!doc.supportsStreaming) { + const preloader = new ProgressivePreloader(container, true); + preloader.attach(container, true, promise, false); + //} + + const mp4Source: MP4Source = await (promise as Promise); + if(mp4Source instanceof MP4Source) { + url = mp4Source.getURL(); + } + } + + if(!url) { + url = doc.url; } if(middleware && !middleware()) { @@ -107,17 +117,30 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai } //console.log('loaded doc:', doc, doc.url, container); + + if(doc.type == 'gif'/* || true */) { + video.addEventListener('canplay', () => { + if(img && img.parentElement) { + img.remove(); + } + + /* if(!video.paused) { + video.pause(); + } */ + animationIntersector.addAnimation(video, 'chat'); + }, {once: true}); + } - renderImageFromUrl(source, doc.url); + renderImageFromUrl(source, url); source.type = doc.mime_type; video.append(source); video.setAttribute('playsinline', ''); - if(img && img.parentElement) { - img.remove(); - } + /* if(!container.parentElement) { + container.append(video); + } */ - if(doc.type == 'gif') { + if(doc.type == 'gif'/* || true */) { video.muted = true; video.loop = true; //video.play(); @@ -130,7 +153,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai } }; - if(doc.size >= 20e6 && !doc.downloaded) { + /* if(doc.size >= 20e6 && !doc.downloaded) { let downloadDiv = document.createElement('div'); downloadDiv.classList.add('download'); @@ -146,9 +169,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai container.prepend(downloadDiv); return; - } + } */ - return doc.downloaded ? loadVideo() : lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */}); + //return; + return doc.downloaded/* && false */ ? loadVideo() : lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */}); } export const formatDate = (timestamp: number, monthShort = false, withYear = true) => { @@ -402,7 +426,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o if(thumb.bytes) { let img = new Image(); - if(appWebpManager.isSupported() || doc.stickerThumbConverted) { + if((appWebpManager.isSupported() || doc.stickerThumbConverted)/* && false */) { renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)); div.append(img); @@ -415,7 +439,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o if(!div.childElementCount) { renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)).then(() => { - div.append(img); + if(!div.childElementCount) { + div.append(img); + } }); } }); @@ -427,12 +453,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o } else if(!onlyThumb && stickerType == 2 && withThumb && toneIndex <= 0) { let img = new Image(); let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => { - if(!img.parentElement || (middleware && !middleware())) return; + if(div.childElementCount || (middleware && !middleware())) return; let promise = renderImageFromUrl(img, url); if(!downloaded) { promise.then(() => { - div.append(img); + if(!div.childElementCount) { + div.append(img); + } }); } }); diff --git a/src/index.hbs b/src/index.hbs index e15e04d3..57dad505 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -176,7 +176,7 @@ -