morethanwords
4 years ago
18 changed files with 798 additions and 603 deletions
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
import { MTDocument } from "../types"; |
||||
import { calcImageInBox, findUpClassName } from "../lib/utils"; |
||||
import appDocsManager from "../lib/appManagers/appDocsManager"; |
||||
import { wrapVideo } from "./wrappers"; |
||||
import { renderImageFromUrl } from "./misc"; |
||||
import LazyLoadQueue from "./lazyLoadQueue"; |
||||
|
||||
const width = 400; |
||||
const maxSingleWidth = width - 100; |
||||
const height = 100; |
||||
|
||||
export default class GifsMasonry { |
||||
constructor(private element: HTMLElement) { |
||||
|
||||
} |
||||
|
||||
public add(doc: MTDocument, group: string, lazyLoadQueue?: LazyLoadQueue) { |
||||
let gifWidth = doc.w; |
||||
let gifHeight = doc.h; |
||||
if(gifHeight < height) { |
||||
gifWidth = height / gifHeight * gifWidth; |
||||
gifHeight = height; |
||||
} |
||||
|
||||
let willUseWidth = Math.min(maxSingleWidth, width, 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); */ |
||||
|
||||
//console.log('gif:', gif, w, h);
|
||||
|
||||
let div = document.createElement('div'); |
||||
div.classList.add('gif', 'fade-in-transition'); |
||||
div.style.width = w + 'px'; |
||||
div.style.opacity = '0'; |
||||
//div.style.height = h + 'px';
|
||||
div.dataset.docID = doc.id; |
||||
|
||||
this.element.append(div); |
||||
|
||||
//let preloader = new ProgressivePreloader(div);
|
||||
|
||||
const posterURL = appDocsManager.getThumbURL(doc, false); |
||||
let img: HTMLImageElement; |
||||
if(posterURL) { |
||||
img = new Image(); |
||||
img.src = posterURL; |
||||
} |
||||
|
||||
let mouseOut = false; |
||||
const onMouseOver = (/* e: MouseEvent */) => { |
||||
//console.log('onMouseOver', doc.id);
|
||||
//cancelEvent(e);
|
||||
mouseOut = false; |
||||
|
||||
wrapVideo({ |
||||
doc, |
||||
container: div, |
||||
lazyLoadQueue, |
||||
//lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
|
||||
group, |
||||
noInfo: true, |
||||
}); |
||||
|
||||
const video = div.querySelector('video'); |
||||
video.addEventListener('canplay', () => { |
||||
div.style.opacity = ''; |
||||
if(!mouseOut) { |
||||
img && img.classList.add('hide'); |
||||
} else { |
||||
img && img.classList.remove('hide'); |
||||
if(div.lastElementChild != img) { |
||||
div.lastElementChild.remove(); |
||||
} |
||||
} |
||||
}, {once: true}); |
||||
}; |
||||
|
||||
const afterRender = () => { |
||||
if(img) { |
||||
div.append(img); |
||||
div.style.opacity = ''; |
||||
} |
||||
|
||||
if(lazyLoadQueue) { |
||||
onMouseOver(); |
||||
} else { |
||||
div.addEventListener('mouseover', onMouseOver, {once: true}); |
||||
div.addEventListener('mouseout', (e) => { |
||||
const toElement = (e as any).toElement as Element; |
||||
//console.log('onMouseOut', doc.id, e);
|
||||
if(findUpClassName(toElement, 'gif') == div) { |
||||
return; |
||||
} |
||||
|
||||
//cancelEvent(e);
|
||||
|
||||
mouseOut = true; |
||||
|
||||
const cb = () => { |
||||
if(div.lastElementChild != img) { |
||||
div.lastElementChild.remove(); |
||||
} |
||||
|
||||
div.addEventListener('mouseover', onMouseOver, {once: true}); |
||||
}; |
||||
|
||||
img && img.classList.remove('hide'); |
||||
/* window.requestAnimationFrame(() => { |
||||
window.requestAnimationFrame(); |
||||
}); */ |
||||
if(img) window.requestAnimationFrame(() => window.requestAnimationFrame(cb)); |
||||
else cb(); |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
(posterURL ? renderImageFromUrl(img, posterURL, afterRender) : afterRender()); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
import { SliderTab } from "../slider"; |
||||
import SearchInput from "../searchInput"; |
||||
import Scrollable from "../scrollable_new"; |
||||
import LazyLoadQueue from "../lazyLoadQueue"; |
||||
import animationIntersector from "../animationIntersector"; |
||||
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight"; |
||||
import appUsersManager, { User } from "../../lib/appManagers/appUsersManager"; |
||||
import appInlineBotsManager, { AppInlineBotsManager } from "../../lib/appManagers/AppInlineBotsManager"; |
||||
import GifsMasonry from "../gifsMasonry"; |
||||
|
||||
const ANIMATIONGROUP = 'GIFS-SEARCH'; |
||||
|
||||
export default class AppGifsTab implements SliderTab { |
||||
private container = document.getElementById('search-gifs-container') as HTMLDivElement; |
||||
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement; |
||||
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement; |
||||
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
|
||||
private searchInput: SearchInput; |
||||
private gifsDiv = this.contentDiv.firstElementChild as HTMLDivElement; |
||||
private scrollable: Scrollable; |
||||
private lazyLoadQueue: LazyLoadQueue; |
||||
|
||||
private nextOffset = ''; |
||||
|
||||
private gifBotPeerID: number; |
||||
private masonry: GifsMasonry; |
||||
|
||||
private searchPromise: ReturnType<AppInlineBotsManager['getInlineResults']>; |
||||
|
||||
constructor() { |
||||
this.scrollable = new Scrollable(this.contentDiv, 'y', ANIMATIONGROUP, undefined, undefined, 2); |
||||
this.scrollable.setVirtualContainer(this.gifsDiv); |
||||
|
||||
this.masonry = new GifsMasonry(this.gifsDiv); |
||||
|
||||
this.lazyLoadQueue = new LazyLoadQueue(); |
||||
|
||||
this.searchInput = new SearchInput('Search GIFs', (value) => { |
||||
this.reset(); |
||||
this.search(value); |
||||
}); |
||||
|
||||
this.scrollable.onScrolledBottom = () => { |
||||
this.search(this.searchInput.value, false); |
||||
}; |
||||
|
||||
this.backBtn.parentElement.append(this.searchInput.container); |
||||
} |
||||
|
||||
public onCloseAfterTimeout() { |
||||
this.reset(); |
||||
this.gifsDiv.innerHTML = ''; |
||||
this.searchInput.value = ''; |
||||
animationIntersector.checkAnimations(undefined, ANIMATIONGROUP); |
||||
} |
||||
|
||||
private reset() { |
||||
this.searchPromise = null; |
||||
this.nextOffset = ''; |
||||
this.lazyLoadQueue.clear(); |
||||
} |
||||
|
||||
public init() { |
||||
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.gifs); |
||||
|
||||
appSidebarRight.toggleSidebar(true).then(() => { |
||||
//this.renderFeatured();
|
||||
this.search('', true); |
||||
this.reset(); |
||||
}); |
||||
} |
||||
|
||||
public async search(query: string, newSearch = true) { |
||||
if(this.searchPromise) return; |
||||
|
||||
if(!this.gifBotPeerID) { |
||||
this.gifBotPeerID = (await appUsersManager.resolveUsername('gif')).id; |
||||
} |
||||
|
||||
try { |
||||
this.searchPromise = appInlineBotsManager.getInlineResults(0, this.gifBotPeerID, query, this.nextOffset); |
||||
const { results, next_offset } = await this.searchPromise; |
||||
|
||||
if(this.searchInput.value != query) { |
||||
return; |
||||
} |
||||
|
||||
this.searchPromise = null; |
||||
this.nextOffset = next_offset; |
||||
if(newSearch) { |
||||
this.gifsDiv.innerHTML = ''; |
||||
} |
||||
|
||||
results.forEach((result) => { |
||||
if(result._ === 'botInlineMediaResult' && result.document) { |
||||
this.masonry.add(result.document, ANIMATIONGROUP, this.lazyLoadQueue); |
||||
} |
||||
}); |
||||
|
||||
this.scrollable.onScroll(); |
||||
} catch (err) { |
||||
throw new Error(JSON.stringify(err)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
import { SliderTab } from "../slider"; |
||||
import Scrollable from "../scrollable_new"; |
||||
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight"; |
||||
import appPollsManager from "../../lib/appManagers/appPollsManager"; |
||||
import { roundPercents } from "../poll"; |
||||
import { RichTextProcessor } from "../../lib/richtextprocessor"; |
||||
import appDialogsManager from "../../lib/appManagers/appDialogsManager"; |
||||
import { ripple } from "../ripple"; |
||||
|
||||
export default class AppPollResultsTab implements SliderTab { |
||||
private container = document.getElementById('poll-results-container') as HTMLDivElement; |
||||
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement; |
||||
private resultsDiv = this.contentDiv.firstElementChild as HTMLDivElement; |
||||
private scrollable: Scrollable; |
||||
|
||||
private pollID: string; |
||||
private mid: number; |
||||
|
||||
constructor() { |
||||
this.scrollable = new Scrollable(this.contentDiv, 'y', 'POLL-RESULTS', undefined, undefined, 2); |
||||
} |
||||
|
||||
public cleanup() { |
||||
this.resultsDiv.innerHTML = ''; |
||||
this.pollID = ''; |
||||
this.mid = 0; |
||||
} |
||||
|
||||
public onCloseAfterTimeout() { |
||||
this.cleanup(); |
||||
} |
||||
|
||||
public init(pollID: string, mid: number) { |
||||
if(this.pollID == pollID && this.mid == mid) return; |
||||
|
||||
this.cleanup(); |
||||
|
||||
this.pollID = pollID; |
||||
this.mid = mid; |
||||
|
||||
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.pollResults); |
||||
|
||||
const poll = appPollsManager.getPoll(pollID); |
||||
|
||||
const title = document.createElement('h3'); |
||||
title.innerHTML = poll.poll.rQuestion; |
||||
|
||||
const percents = poll.results.results.map(v => v.voters / poll.results.total_voters * 100); |
||||
roundPercents(percents); |
||||
|
||||
const fragment = document.createDocumentFragment(); |
||||
poll.results.results.forEach((result, idx) => { |
||||
if(!result.voters) return; |
||||
|
||||
const hr = document.createElement('hr'); |
||||
|
||||
const answer = poll.poll.answers[idx]; |
||||
|
||||
// Head
|
||||
const answerEl = document.createElement('div'); |
||||
answerEl.classList.add('poll-results-answer'); |
||||
|
||||
const answerTitle = document.createElement('div'); |
||||
answerTitle.innerHTML = RichTextProcessor.wrapEmojiText(answer.text); |
||||
|
||||
const answerPercents = document.createElement('div'); |
||||
answerPercents.innerText = Math.round(percents[idx]) + '%'; |
||||
|
||||
answerEl.append(answerTitle, answerPercents); |
||||
|
||||
// Humans
|
||||
const list = document.createElement('ul'); |
||||
list.classList.add('poll-results-voters'); |
||||
|
||||
appDialogsManager.setListClickListener(list); |
||||
|
||||
list.style.minHeight = Math.min(result.voters, 4) * 50 + 'px'; |
||||
|
||||
fragment.append(hr, answerEl, list); |
||||
|
||||
let offset: string, limit = 4, loading = false, left = result.voters - 4; |
||||
const load = () => { |
||||
if(loading) return; |
||||
loading = true; |
||||
|
||||
appPollsManager.getVotes(mid, answer.option, offset, limit).then(votesList => { |
||||
votesList.votes.forEach(vote => { |
||||
const {dom} = appDialogsManager.addDialog(vote.user_id, list, false, false, undefined, false); |
||||
dom.lastMessageSpan.parentElement.remove(); |
||||
}); |
||||
|
||||
if(offset) { |
||||
left -= votesList.votes.length; |
||||
(showMore.lastElementChild as HTMLElement).innerText = `Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}`; |
||||
} |
||||
|
||||
offset = votesList.next_offset; |
||||
limit = 20; |
||||
|
||||
if(!left || !votesList.votes.length) { |
||||
showMore.remove(); |
||||
} |
||||
}).finally(() => { |
||||
loading = false; |
||||
}); |
||||
}; |
||||
|
||||
load(); |
||||
|
||||
if(left <= 0) return; |
||||
|
||||
const showMore = document.createElement('div'); |
||||
showMore.classList.add('poll-results-more', 'show-more'); |
||||
showMore.addEventListener('click', load); |
||||
|
||||
showMore.innerHTML = `<div class="tgico-down"></div><div>Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}</div>`; |
||||
ripple(showMore); |
||||
|
||||
fragment.append(showMore); |
||||
}); |
||||
|
||||
this.resultsDiv.append(title, fragment); |
||||
|
||||
appSidebarRight.toggleSidebar(true).then(() => { |
||||
/* appPollsManager.getVotes(mid).then(votes => { |
||||
console.log('gOt VotEs', votes); |
||||
}); */ |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,235 @@
@@ -0,0 +1,235 @@
|
||||
import { SliderTab } from "../slider"; |
||||
import SearchInput from "../searchInput"; |
||||
import Scrollable from "../scrollable_new"; |
||||
import LazyLoadQueue from "../lazyLoadQueue"; |
||||
import { findUpClassName } from "../../lib/utils"; |
||||
import appImManager from "../../lib/appManagers/appImManager"; |
||||
import appStickersManager, { MTStickerSet, MTStickerSetCovered, MTStickerSetMultiCovered } from "../../lib/appManagers/appStickersManager"; |
||||
import PopupStickers from "../popupStickers"; |
||||
import animationIntersector from "../animationIntersector"; |
||||
import { RichTextProcessor } from "../../lib/richtextprocessor"; |
||||
import { wrapSticker } from "../wrappers"; |
||||
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight"; |
||||
|
||||
export default class AppStickersTab implements SliderTab { |
||||
private container = document.getElementById('stickers-container') as HTMLDivElement; |
||||
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement; |
||||
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement; |
||||
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
|
||||
private searchInput: SearchInput; |
||||
private setsDiv = this.contentDiv.firstElementChild as HTMLDivElement; |
||||
private scrollable: Scrollable; |
||||
private lazyLoadQueue: LazyLoadQueue; |
||||
|
||||
constructor() { |
||||
this.scrollable = new Scrollable(this.contentDiv, 'y', 'STICKERS-SEARCH', undefined, undefined, 2); |
||||
this.scrollable.setVirtualContainer(this.setsDiv); |
||||
|
||||
this.lazyLoadQueue = new LazyLoadQueue(); |
||||
|
||||
this.searchInput = new SearchInput('Search Stickers', (value) => { |
||||
this.search(value); |
||||
}); |
||||
|
||||
this.backBtn.parentElement.append(this.searchInput.container); |
||||
|
||||
this.setsDiv.addEventListener('click', (e) => { |
||||
const sticker = findUpClassName(e.target, 'sticker-set-sticker'); |
||||
if(sticker) { |
||||
const docID = sticker.dataset.docID; |
||||
appImManager.chatInputC.sendMessageWithDocument(docID); |
||||
return; |
||||
} |
||||
|
||||
const target = findUpClassName(e.target, 'sticker-set'); |
||||
if(!target) return; |
||||
|
||||
|
||||
const id = target.dataset.stickerSet as string; |
||||
const access_hash = target.dataset.stickerSet as string; |
||||
|
||||
const button = findUpClassName(e.target, 'sticker-set-button') as HTMLElement; |
||||
if(button) { |
||||
e.preventDefault(); |
||||
e.cancelBubble = true; |
||||
|
||||
button.setAttribute('disabled', 'true'); |
||||
|
||||
appStickersManager.getStickerSet({id, access_hash}).then(full => { |
||||
appStickersManager.toggleStickerSet(full.set).then(changed => { |
||||
if(changed) { |
||||
button.innerText = full.set.installed_date ? 'Added' : 'Add'; |
||||
button.classList.toggle('gray', !!full.set.installed_date); |
||||
} |
||||
}).finally(() => { |
||||
//button.style.width = set.installed_date ? '68px' : '52px';
|
||||
button.removeAttribute('disabled'); |
||||
}); |
||||
}); |
||||
} else { |
||||
appStickersManager.getStickerSet({id, access_hash}).then(full => { |
||||
new PopupStickers(full.set).show(); |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public onCloseAfterTimeout() { |
||||
this.setsDiv.innerHTML = ''; |
||||
this.searchInput.value = ''; |
||||
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH'); |
||||
} |
||||
|
||||
public renderSet(set: MTStickerSet) { |
||||
//console.log('renderSet:', set);
|
||||
const div = document.createElement('div'); |
||||
div.classList.add('sticker-set'); |
||||
|
||||
const header = document.createElement('div'); |
||||
header.classList.add('sticker-set-header'); |
||||
|
||||
const details = document.createElement('div'); |
||||
details.classList.add('sticker-set-details'); |
||||
details.innerHTML = ` |
||||
<div class="sticker-set-name">${RichTextProcessor.wrapEmojiText(set.title)}</div> |
||||
<div class="sticker-set-count">${set.count} stickers</div> |
||||
`;
|
||||
|
||||
const button = document.createElement('button'); |
||||
button.classList.add('btn-primary', 'sticker-set-button'); |
||||
button.innerText = set.installed_date ? 'Added' : 'Add'; |
||||
// button.style.width = set.installed_date ? '68px' : '52px';
|
||||
|
||||
if(set.installed_date) { |
||||
button.classList.add('gray'); |
||||
} |
||||
|
||||
//ripple(button);
|
||||
|
||||
header.append(details, button); |
||||
|
||||
const stickersDiv = document.createElement('div'); |
||||
stickersDiv.classList.add('sticker-set-stickers'); |
||||
|
||||
const count = Math.min(5, set.count); |
||||
for(let i = 0; i < count; ++i) { |
||||
const stickerDiv = document.createElement('div'); |
||||
stickerDiv.classList.add('sticker-set-sticker'); |
||||
|
||||
stickersDiv.append(stickerDiv); |
||||
} |
||||
|
||||
appStickersManager.getStickerSet(set).then(set => { |
||||
//console.log('renderSet got set:', set);
|
||||
|
||||
for(let i = 0; i < count; ++i) { |
||||
const div = stickersDiv.children[i] as HTMLDivElement; |
||||
wrapSticker({ |
||||
doc: set.documents[i], |
||||
div, |
||||
lazyLoadQueue: this.lazyLoadQueue, |
||||
group: 'STICKERS-SEARCH', |
||||
/* play: false, |
||||
loop: false, */ |
||||
play: true, |
||||
loop: true, |
||||
width: 68, |
||||
height: 68 |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
/* const onMouseOver = () => { |
||||
const animations: AnimationItem['animation'][] = []; |
||||
for(let i = 0; i < count; ++i) { |
||||
const stickerDiv = stickersDiv.children[i] as HTMLElement; |
||||
const animationItem = animationIntersector.getAnimation(stickerDiv); |
||||
if(!animationItem) continue; |
||||
|
||||
const animation = animationItem.animation; |
||||
|
||||
animations.push(animation); |
||||
animation.loop = true; |
||||
animation.play(); |
||||
} |
||||
|
||||
div.addEventListener('mouseout', () => { |
||||
animations.forEach(animation => { |
||||
animation.loop = false; |
||||
}); |
||||
|
||||
div.addEventListener('mouseover', onMouseOver, {once: true}); |
||||
}, {once: true}); |
||||
}; |
||||
|
||||
div.addEventListener('mouseover', onMouseOver, {once: true}); */ |
||||
|
||||
div.dataset.stickerSet = set.id; |
||||
div.dataset.access_hash = set.access_hash; |
||||
div.dataset.title = set.title; |
||||
|
||||
div.append(header, stickersDiv); |
||||
|
||||
this.scrollable.append(div); |
||||
} |
||||
|
||||
public init() { |
||||
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.stickers); |
||||
|
||||
appSidebarRight.toggleSidebar(true).then(() => { |
||||
this.renderFeatured(); |
||||
}); |
||||
} |
||||
|
||||
public renderFeatured() { |
||||
return appStickersManager.getFeaturedStickers().then(coveredSets => { |
||||
if(this.searchInput.value) { |
||||
return; |
||||
} |
||||
|
||||
coveredSets = this.filterRendered('', coveredSets); |
||||
coveredSets.forEach(set => { |
||||
this.renderSet(set.set); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
private filterRendered(query: string, coveredSets: (MTStickerSetCovered | MTStickerSetMultiCovered)[]) { |
||||
coveredSets = coveredSets.slice(); |
||||
|
||||
const children = Array.from(this.setsDiv.children) as HTMLElement[]; |
||||
children.forEachReverse(el => { |
||||
const id = el.dataset.stickerSet; |
||||
const index = coveredSets.findIndex(covered => covered.set.id == id); |
||||
|
||||
if(index !== -1) { |
||||
coveredSets.splice(index, 1); |
||||
} else if(!query || !el.dataset.title.toLowerCase().includes(query.toLowerCase())) { |
||||
el.remove(); |
||||
} |
||||
}); |
||||
|
||||
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH'); |
||||
|
||||
return coveredSets; |
||||
} |
||||
|
||||
public search(query: string) { |
||||
if(!query) { |
||||
return this.renderFeatured(); |
||||
} |
||||
|
||||
return appStickersManager.searchStickerSets(query, false).then(coveredSets => { |
||||
if(this.searchInput.value != query) { |
||||
return; |
||||
} |
||||
|
||||
//console.log('search result:', coveredSets);
|
||||
|
||||
coveredSets = this.filterRendered(query, coveredSets); |
||||
coveredSets.forEach(set => { |
||||
this.renderSet(set.set); |
||||
}); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
.gifs-masonry { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
|
||||
> .gif { |
||||
flex: 1 0 auto; |
||||
max-width: 100%; |
||||
height: 100px; |
||||
margin: 2.5px; |
||||
cursor: pointer; |
||||
//background: #000; |
||||
position: relative; |
||||
|
||||
video, img { |
||||
object-fit: cover; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
img { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue