Fallback to regular worker due to instability
More fixes WebP stickers fix
This commit is contained in:
parent
f85fa8965b
commit
c7bd824873
@ -1,6 +1,5 @@
|
||||
import Scrollable from "./scrollable_new";
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
//import apiManager from "../lib/mtproto/apiManager";
|
||||
import apiManager from "../lib/mtproto/mtprotoworker";
|
||||
import appWebPagesManager from "../lib/appManagers/appWebPagesManager";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
|
@ -1,853 +0,0 @@
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import { renderImageFromUrl, putPreloader } from "./misc";
|
||||
import lottieLoader from "../lib/lottieLoader";
|
||||
//import Scrollable from "./scrollable";
|
||||
import Scrollable from "./scrollable_new";
|
||||
import { findUpTag, whichChild, calcImageInBox, emojiUnicode, $rootScope, cancelEvent, findUpClassName } from "../lib/utils";
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
|
||||
//import apiManager from '../lib/mtproto/apiManager';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
import LazyLoadQueue from "./lazyLoadQueue";
|
||||
import { wrapSticker, wrapVideo } from "./wrappers";
|
||||
import appDocsManager from "../lib/appManagers/appDocsManager";
|
||||
import ProgressivePreloader from "./preloader";
|
||||
import Config, { touchSupport } from "../lib/config";
|
||||
import { MTDocument } from "../types";
|
||||
import animationIntersector from "./animationIntersector";
|
||||
import appSidebarRight from "../lib/appManagers/appSidebarRight";
|
||||
import appStateManager from "../lib/appManagers/appStateManager";
|
||||
import { horizontalMenu } from "./horizontalMenu";
|
||||
import GifsMasonry from "./gifsMasonry";
|
||||
|
||||
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
|
||||
|
||||
interface EmoticonsTab {
|
||||
init: () => void,
|
||||
onCloseAfterTimeout?: () => void
|
||||
}
|
||||
|
||||
class EmojiTab implements EmoticonsTab {
|
||||
public content: HTMLElement;
|
||||
|
||||
private recent: string[] = [];
|
||||
private recentItemsDiv: HTMLElement;
|
||||
|
||||
private heights: number[] = [];
|
||||
private scroll: Scrollable;
|
||||
|
||||
init() {
|
||||
this.content = document.getElementById('content-emoji') as HTMLDivElement;
|
||||
|
||||
const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"];
|
||||
const divs: {
|
||||
[category: string]: HTMLDivElement
|
||||
} = {};
|
||||
|
||||
const sorted: {
|
||||
[category: string]: string[]
|
||||
} = {
|
||||
'Recent': []
|
||||
};
|
||||
|
||||
for(const emoji in Config.Emoji) {
|
||||
const details = Config.Emoji[emoji];
|
||||
const i = '' + details;
|
||||
const category = categories[+i[0] - 1];
|
||||
if(!category) continue; // maybe it's skin tones
|
||||
|
||||
if(!sorted[category]) sorted[category] = [];
|
||||
sorted[category][+i.slice(1) || 0] = emoji;
|
||||
}
|
||||
|
||||
//console.log('emoticons sorted:', sorted);
|
||||
|
||||
//Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
|
||||
|
||||
categories.pop();
|
||||
delete sorted["Skin Tones"];
|
||||
|
||||
//console.time('emojiParse');
|
||||
for(const category in sorted) {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('emoji-category');
|
||||
|
||||
const titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('category-title');
|
||||
titleDiv.innerText = category;
|
||||
|
||||
const itemsDiv = document.createElement('div');
|
||||
itemsDiv.classList.add('category-items');
|
||||
|
||||
div.append(titleDiv, itemsDiv);
|
||||
|
||||
const emojis = sorted[category];
|
||||
emojis.forEach(emoji => {
|
||||
/* if(emojiUnicode(emoji) == '1f481-200d-2642') {
|
||||
console.log('append emoji', emoji, emojiUnicode(emoji));
|
||||
} */
|
||||
|
||||
this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv);
|
||||
|
||||
/* if(category == 'Smileys & Emotion') {
|
||||
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
|
||||
} */
|
||||
});
|
||||
|
||||
divs[category] = div;
|
||||
}
|
||||
//console.timeEnd('emojiParse');
|
||||
|
||||
let prevCategoryIndex = 0;
|
||||
const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement;
|
||||
const emojiScroll = this.scroll = new Scrollable(this.content, 'y', 'EMOJI', null);
|
||||
emojiScroll.container.addEventListener('scroll', (e) => {
|
||||
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, this.heights, prevCategoryIndex, emojiScroll.container);
|
||||
});
|
||||
//emojiScroll.setVirtualContainer(emojiScroll.container);
|
||||
|
||||
const preloader = putPreloader(this.content, true);
|
||||
|
||||
Promise.all([
|
||||
new Promise((resolve) => setTimeout(resolve, 200)),
|
||||
|
||||
appStateManager.getState().then(state => {
|
||||
if(Array.isArray(state.recentEmoji)) {
|
||||
this.recent = state.recentEmoji;
|
||||
}
|
||||
})
|
||||
]).then(() => {
|
||||
preloader.remove();
|
||||
|
||||
this.recentItemsDiv = divs['Recent'].querySelector('.category-items');
|
||||
for(const emoji of this.recent) {
|
||||
this.appendEmoji(emoji, this.recentItemsDiv);
|
||||
}
|
||||
|
||||
categories.unshift('Recent');
|
||||
categories.map(category => {
|
||||
const div = divs[category];
|
||||
|
||||
if(!div) {
|
||||
console.error('no div by category:', category);
|
||||
}
|
||||
|
||||
emojiScroll.append(div);
|
||||
return div;
|
||||
}).forEach(div => {
|
||||
//console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight);
|
||||
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight);
|
||||
});
|
||||
});
|
||||
|
||||
this.content.addEventListener('click', this.onContentClick);
|
||||
EmoticonsDropdown.menuOnClick(menu, this.heights, emojiScroll);
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
private appendEmoji(emoji: string, container: HTMLElement, prepend = false) {
|
||||
//const emoji = details.unified;
|
||||
//const emoji = (details.unified as string).split('-')
|
||||
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
||||
|
||||
const spanEmoji = document.createElement('span');
|
||||
const kek = RichTextProcessor.wrapEmojiText(emoji);
|
||||
|
||||
/* if(!kek.includes('emoji')) {
|
||||
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
|
||||
return;
|
||||
} */
|
||||
|
||||
//console.log(kek);
|
||||
|
||||
spanEmoji.innerHTML = kek;
|
||||
|
||||
if(spanEmoji.firstElementChild) {
|
||||
(spanEmoji.firstElementChild as HTMLImageElement).setAttribute('loading', 'lazy');
|
||||
}
|
||||
|
||||
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
||||
//spanEmoji.setAttribute('emoji', emoji);
|
||||
if(prepend) container.prepend(spanEmoji);
|
||||
else container.appendChild(spanEmoji);
|
||||
}
|
||||
|
||||
private getEmojiFromElement(element: HTMLElement) {
|
||||
if(element.tagName == 'SPAN' && !element.classList.contains('emoji')) {
|
||||
element = element.firstElementChild as HTMLElement;
|
||||
}
|
||||
|
||||
return element.getAttribute('alt') || element.innerText;
|
||||
}
|
||||
|
||||
onContentClick = (e: MouseEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
//if(target.tagName != 'SPAN') return;
|
||||
|
||||
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) {
|
||||
target = target.firstElementChild as HTMLElement;
|
||||
} else if(target.tagName == 'DIV') return;
|
||||
|
||||
//console.log('contentEmoji div', target);
|
||||
|
||||
appImManager.chatInputC.messageInput.innerHTML += target.outerHTML;
|
||||
|
||||
// Recent
|
||||
const emoji = this.getEmojiFromElement(target);
|
||||
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
|
||||
const _emoji = this.getEmojiFromElement(el);
|
||||
if(emoji == _emoji) {
|
||||
el.remove();
|
||||
}
|
||||
});
|
||||
const scrollHeight = this.recentItemsDiv.scrollHeight;
|
||||
this.appendEmoji(emoji, this.recentItemsDiv, true);
|
||||
|
||||
// нужно поставить новые размеры для скролла
|
||||
if(this.recentItemsDiv.scrollHeight != scrollHeight) {
|
||||
this.heights.length = 0;
|
||||
(Array.from(this.scroll.container.children) as HTMLElement[]).forEach(div => {
|
||||
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight);
|
||||
});
|
||||
}
|
||||
|
||||
this.recent.findAndSplice(e => e == emoji);
|
||||
this.recent.unshift(emoji);
|
||||
if(this.recent.length > 36) {
|
||||
this.recent.length = 36;
|
||||
}
|
||||
|
||||
appStateManager.pushToState('recentEmoji', this.recent);
|
||||
|
||||
// Append to input
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chatInputC.messageInput.dispatchEvent(event);
|
||||
};
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class StickersTab implements EmoticonsTab {
|
||||
public content: HTMLElement;
|
||||
|
||||
private stickerSets: {[id: string]: {
|
||||
stickers: HTMLElement,
|
||||
tab: HTMLElement
|
||||
}} = {};
|
||||
|
||||
private recentDiv: HTMLElement;
|
||||
private recentStickers: MTDocument[] = [];
|
||||
|
||||
private heights: number[] = [];
|
||||
private heightRAF = 0;
|
||||
private scroll: Scrollable;
|
||||
|
||||
private menu: HTMLUListElement;
|
||||
|
||||
private mounted = false;
|
||||
|
||||
categoryPush(categoryDiv: HTMLElement, categoryTitle: string, docs: MTDocument[], prepend?: boolean) {
|
||||
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
|
||||
|
||||
let itemsDiv = document.createElement('div');
|
||||
itemsDiv.classList.add('category-items');
|
||||
|
||||
let titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('category-title');
|
||||
titleDiv.innerText = categoryTitle;
|
||||
|
||||
categoryDiv.append(titleDiv, itemsDiv);
|
||||
|
||||
docs.forEach(doc => {
|
||||
itemsDiv.append(this.renderSticker(doc));
|
||||
});
|
||||
|
||||
if(prepend) {
|
||||
if(this.recentDiv.parentElement) {
|
||||
this.scroll.prepend(categoryDiv);
|
||||
this.scroll.prepend(this.recentDiv);
|
||||
} else {
|
||||
this.scroll.prepend(categoryDiv);
|
||||
}
|
||||
} else this.scroll.append(categoryDiv);
|
||||
|
||||
/* let scrollHeight = categoryDiv.scrollHeight;
|
||||
let prevHeight = heights[heights.length - 1] || 0;
|
||||
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
|
||||
if(prepend && heights.length) {// all stickers loaded faster than recent
|
||||
heights.forEach((h, i) => heights[i] += scrollHeight);
|
||||
|
||||
return heights.unshift(scrollHeight) - 1;
|
||||
} */
|
||||
|
||||
this.setNewHeights();
|
||||
|
||||
/* Array.from(stickersDiv.children).forEach((div, i) => {
|
||||
heights[i] = (heights[i - 1] || 0) + div.scrollHeight;
|
||||
}); */
|
||||
|
||||
//this.scroll.onScroll();
|
||||
|
||||
//return heights.push(prevHeight + scrollHeight) - 1;
|
||||
}
|
||||
|
||||
setNewHeights() {
|
||||
if(this.heightRAF) return;
|
||||
//if(this.heightRAF) window.cancelAnimationFrame(this.heightRAF);
|
||||
this.heightRAF = window.requestAnimationFrame(() => {
|
||||
this.heightRAF = 0;
|
||||
|
||||
const heights = this.heights;
|
||||
|
||||
let paddingTop = parseInt(window.getComputedStyle(this.scroll.container).getPropertyValue('padding-top')) || 0;
|
||||
|
||||
heights.length = 0;
|
||||
/* let concated = this.scroll.hiddenElements.up.concat(this.scroll.visibleElements, this.scroll.hiddenElements.down);
|
||||
concated.forEach((el, i) => {
|
||||
heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0);
|
||||
}); */
|
||||
let concated = Array.from(this.scroll.splitUp.children) as HTMLElement[];
|
||||
concated.forEach((el, i) => {
|
||||
heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0);
|
||||
});
|
||||
|
||||
this.scroll.reorder();
|
||||
|
||||
//console.log('stickers concated', concated, heights);
|
||||
});
|
||||
}
|
||||
|
||||
renderSticker(doc: MTDocument) {
|
||||
let div = document.createElement('div');
|
||||
wrapSticker({
|
||||
doc,
|
||||
div,
|
||||
/* width: 80,
|
||||
height: 80,
|
||||
play: false,
|
||||
loop: false, */
|
||||
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
|
||||
group: EMOTICONSSTICKERGROUP,
|
||||
onlyThumb: doc.sticker == 2
|
||||
});
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
async renderStickerSet(set: MTStickerSet, prepend = false) {
|
||||
let categoryDiv = document.createElement('div');
|
||||
categoryDiv.classList.add('sticker-category');
|
||||
|
||||
let li = document.createElement('li');
|
||||
li.classList.add('btn-icon');
|
||||
|
||||
this.stickerSets[set.id] = {
|
||||
stickers: categoryDiv,
|
||||
tab: li
|
||||
};
|
||||
|
||||
if(prepend) {
|
||||
this.menu.insertBefore(li, this.menu.firstElementChild.nextSibling);
|
||||
} else {
|
||||
this.menu.append(li);
|
||||
}
|
||||
|
||||
//stickersScroll.append(categoryDiv);
|
||||
|
||||
let stickerSet = await appStickersManager.getStickerSet(set);
|
||||
|
||||
//console.log('got stickerSet', stickerSet, li);
|
||||
|
||||
if(stickerSet.set.thumb) {
|
||||
const thumbURL = appStickersManager.getStickerSetThumbURL(stickerSet.set);
|
||||
|
||||
if(stickerSet.set.pFlags.animated) {
|
||||
fetch(thumbURL)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
lottieLoader.loadAnimationWorker({
|
||||
container: li,
|
||||
loop: true,
|
||||
autoplay: false,
|
||||
animationData: json,
|
||||
width: 32,
|
||||
height: 32
|
||||
}, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
} else {
|
||||
const image = new Image();
|
||||
renderImageFromUrl(image, thumbURL, () => {
|
||||
li.append(image);
|
||||
});
|
||||
}
|
||||
} else { // as thumb will be used first sticker
|
||||
wrapSticker({
|
||||
doc: stickerSet.documents[0],
|
||||
div: li as any,
|
||||
group: EMOTICONSSTICKERGROUP
|
||||
}); // kostil
|
||||
}
|
||||
|
||||
this.categoryPush(categoryDiv, stickerSet.set.title, stickerSet.documents, prepend);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.content = document.getElementById('content-stickers');
|
||||
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
|
||||
|
||||
this.recentDiv = document.createElement('div');
|
||||
this.recentDiv.classList.add('sticker-category');
|
||||
|
||||
let menuWrapper = this.content.previousElementSibling as HTMLDivElement;
|
||||
this.menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement;
|
||||
|
||||
let menuScroll = new Scrollable(menuWrapper, 'x');
|
||||
|
||||
let stickersDiv = document.createElement('div');
|
||||
stickersDiv.classList.add('stickers-categories');
|
||||
this.content.append(stickersDiv);
|
||||
|
||||
/* stickersDiv.addEventListener('mouseover', (e) => {
|
||||
let target = e.target as HTMLElement;
|
||||
|
||||
if(target.tagName == 'CANVAS') { // turn on sticker
|
||||
let animation = lottieLoader.getAnimation(target.parentElement, EMOTICONSSTICKERGROUP);
|
||||
|
||||
if(animation) {
|
||||
// @ts-ignore
|
||||
if(animation.currentFrame == animation.totalFrames - 1) {
|
||||
animation.goToAndPlay(0, true);
|
||||
} else {
|
||||
animation.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}); */
|
||||
|
||||
$rootScope.$on('stickers_installed', (e: CustomEvent) => {
|
||||
const set: MTStickerSet = e.detail;
|
||||
|
||||
if(!this.stickerSets[set.id] && this.mounted) {
|
||||
this.renderStickerSet(set, true);
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$on('stickers_deleted', (e: CustomEvent) => {
|
||||
const set: MTStickerSet = e.detail;
|
||||
|
||||
if(this.stickerSets[set.id] && this.mounted) {
|
||||
const elements = this.stickerSets[set.id];
|
||||
elements.stickers.remove();
|
||||
elements.tab.remove();
|
||||
this.setNewHeights();
|
||||
delete this.stickerSets[set.id];
|
||||
}
|
||||
});
|
||||
|
||||
stickersDiv.addEventListener('click', EmoticonsDropdown.onMediaClick);
|
||||
|
||||
let prevCategoryIndex = 0;
|
||||
this.scroll = new Scrollable(this.content, 'y', 'STICKERS', undefined, undefined, 2);
|
||||
this.scroll.container.addEventListener('scroll', (e) => {
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
|
||||
if(this.heights[1] == 0) {
|
||||
this.setNewHeights();
|
||||
}
|
||||
|
||||
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(this.menu, this.heights, prevCategoryIndex, this.scroll.container, menuScroll);
|
||||
});
|
||||
this.scroll.setVirtualContainer(stickersDiv);
|
||||
|
||||
this.menu.addEventListener('click', () => {
|
||||
if(this.heights[1] == 0) {
|
||||
this.setNewHeights();
|
||||
}
|
||||
});
|
||||
|
||||
EmoticonsDropdown.menuOnClick(this.menu, this.heights, this.scroll, menuScroll);
|
||||
|
||||
const preloader = putPreloader(this.content, true);
|
||||
|
||||
Promise.all([
|
||||
appStickersManager.getRecentStickers().then(stickers => {
|
||||
this.recentStickers = stickers.stickers.slice(0, 20);
|
||||
|
||||
//stickersScroll.prepend(categoryDiv);
|
||||
|
||||
this.stickerSets['recent'] = {
|
||||
stickers: this.recentDiv,
|
||||
tab: this.menu.firstElementChild as HTMLElement
|
||||
};
|
||||
|
||||
preloader.remove();
|
||||
this.categoryPush(this.recentDiv, 'Recent', this.recentStickers, true);
|
||||
}),
|
||||
|
||||
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => {
|
||||
let stickers: {
|
||||
_: 'messages.allStickers',
|
||||
hash: number,
|
||||
sets: Array<MTStickerSet>
|
||||
} = res as any;
|
||||
|
||||
preloader.remove();
|
||||
|
||||
for(let set of stickers.sets) {
|
||||
this.renderStickerSet(set);
|
||||
}
|
||||
})
|
||||
]).finally(() => {
|
||||
this.mounted = true;
|
||||
});
|
||||
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
pushRecentSticker(doc: MTDocument) {
|
||||
if(!this.recentDiv.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`);
|
||||
if(!div) {
|
||||
div = this.renderSticker(doc);
|
||||
}
|
||||
|
||||
const items = this.recentDiv.lastElementChild;
|
||||
items.prepend(div);
|
||||
|
||||
if(items.childElementCount > 20) {
|
||||
(Array.from(items.children) as HTMLElement[]).slice(20).forEach(el => el.remove());
|
||||
}
|
||||
|
||||
this.setNewHeights();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class GifsTab implements EmoticonsTab {
|
||||
public content: HTMLElement;
|
||||
|
||||
init() {
|
||||
this.content = document.getElementById('content-gifs');
|
||||
const gifsContainer = this.content.firstElementChild as HTMLDivElement;
|
||||
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick);
|
||||
|
||||
const masonry = new GifsMasonry(gifsContainer);
|
||||
const scroll = new Scrollable(this.content, 'y', 'GIFS', null);
|
||||
const preloader = putPreloader(this.content, true);
|
||||
|
||||
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => {
|
||||
let res = _res as {
|
||||
_: 'messages.savedGifs',
|
||||
gifs: MTDocument[],
|
||||
hash: number
|
||||
};
|
||||
//console.log('getSavedGifs res:', res);
|
||||
|
||||
//let line: MTDocument[] = [];
|
||||
|
||||
preloader.remove();
|
||||
res.gifs.forEach((doc, idx) => {
|
||||
res.gifs[idx] = appDocsManager.saveDoc(doc);
|
||||
masonry.add(res.gifs[idx], EMOTICONSSTICKERGROUP, EmoticonsDropdown.lazyLoadQueue);
|
||||
});
|
||||
});
|
||||
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class EmoticonsDropdown {
|
||||
public static lazyLoadQueue = new LazyLoadQueue();
|
||||
private element: HTMLElement;
|
||||
|
||||
public emojiTab: EmojiTab;
|
||||
public stickersTab: StickersTab;
|
||||
public gifsTab: GifsTab;
|
||||
|
||||
private container: HTMLElement;
|
||||
private tabsEl: HTMLElement;
|
||||
private tabID = -1;
|
||||
|
||||
private tabs: {[id: number]: EmoticonsTab};
|
||||
|
||||
public searchButton: HTMLElement;
|
||||
public deleteBtn: HTMLElement;
|
||||
|
||||
public toggleEl: HTMLElement;
|
||||
private displayTimeout: number;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement;
|
||||
|
||||
let firstTime = true;
|
||||
this.toggleEl = document.getElementById('toggle-emoticons');
|
||||
if(touchSupport) {
|
||||
this.toggleEl.addEventListener('click', () => {
|
||||
if(firstTime) {
|
||||
firstTime = false;
|
||||
this.toggle(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.toggleEl.onmouseover = (e) => {
|
||||
clearTimeout(this.displayTimeout);
|
||||
//this.displayTimeout = setTimeout(() => {
|
||||
if(firstTime) {
|
||||
this.toggleEl.onmouseout = this.element.onmouseout = (e) => {
|
||||
const toElement = (e as any).toElement as Element;
|
||||
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
this.toggle();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
this.element.onmouseover = (e) => {
|
||||
clearTimeout(this.displayTimeout);
|
||||
};
|
||||
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
this.toggle(true);
|
||||
//}, 0/* 200 */);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.emojiTab = new EmojiTab();
|
||||
this.stickersTab = new StickersTab();
|
||||
this.gifsTab = new GifsTab();
|
||||
|
||||
this.tabs = {
|
||||
0: this.emojiTab,
|
||||
1: this.stickersTab,
|
||||
2: this.gifsTab
|
||||
};
|
||||
|
||||
this.container = this.element.querySelector('.emoji-container .tabs-container') as HTMLDivElement;
|
||||
this.tabsEl = this.element.querySelector('.emoji-tabs') as HTMLUListElement;
|
||||
horizontalMenu(this.tabsEl, this.container, (id) => {
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
this.tabID = id;
|
||||
this.searchButton.classList.toggle('hide', this.tabID == 0);
|
||||
this.deleteBtn.classList.toggle('hide', this.tabID != 0);
|
||||
}, () => {
|
||||
const tab = this.tabs[this.tabID];
|
||||
if(tab.init) {
|
||||
tab.init();
|
||||
}
|
||||
|
||||
tab.onCloseAfterTimeout && tab.onCloseAfterTimeout();
|
||||
animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
|
||||
this.searchButton = this.element.querySelector('.emoji-tabs-search');
|
||||
this.searchButton.addEventListener('click', () => {
|
||||
if(this.tabID == 1) {
|
||||
appSidebarRight.stickersTab.init();
|
||||
} else {
|
||||
appSidebarRight.gifsTab.init();
|
||||
}
|
||||
});
|
||||
|
||||
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete');
|
||||
this.deleteBtn.addEventListener('click', () => {
|
||||
const input = appImManager.chatInputC.messageInput;
|
||||
if((input.lastChild as any)?.tagName) {
|
||||
input.lastElementChild.remove();
|
||||
} else if(input.lastChild) {
|
||||
if(!input.lastChild.textContent.length) {
|
||||
input.lastChild.remove();
|
||||
} else {
|
||||
input.lastChild.textContent = input.lastChild.textContent.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chatInputC.messageInput.dispatchEvent(event);
|
||||
//appSidebarRight.stickersTab.init();
|
||||
});
|
||||
|
||||
(this.tabsEl.firstElementChild.children[1] as HTMLLIElement).click(); // set emoji tab
|
||||
this.tabs[0].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
|
||||
}
|
||||
|
||||
public toggle = async(enable?: boolean) => {
|
||||
//if(!this.element) return;
|
||||
const willBeActive = (!!this.element.style.display && enable === undefined) || enable;
|
||||
if(this.init) {
|
||||
if(willBeActive) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(touchSupport) {
|
||||
this.toggleEl.classList.toggle('flip-icon', willBeActive);
|
||||
if(willBeActive) {
|
||||
appImManager.chatInputC.saveScroll();
|
||||
// @ts-ignore
|
||||
document.activeElement.blur();
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.toggleEl.classList.toggle('active', enable);
|
||||
}
|
||||
|
||||
if((this.element.style.display && enable === undefined) || enable) {
|
||||
this.element.style.display = '';
|
||||
void this.element.offsetLeft; // reflow
|
||||
this.element.classList.add('active');
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lockIntersection();
|
||||
//EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
EmoticonsDropdown.lazyLoadQueue.unlockIntersection();
|
||||
}, touchSupport ? 0 : 200);
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
} else {
|
||||
this.element.classList.remove('active');
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lockIntersection();
|
||||
//EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
|
||||
// нужно залочить группу и выключить стикеры
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
this.element.style.display = 'none';
|
||||
|
||||
// теперь можно убрать visible, чтобы они не включились после фокуса
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.unlockIntersection();
|
||||
}, touchSupport ? 0 : 200);
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
}
|
||||
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
};
|
||||
|
||||
public static menuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => {
|
||||
menu.addEventListener('click', function(e) {
|
||||
let target = e.target as HTMLElement;
|
||||
target = findUpTag(target, 'LI');
|
||||
|
||||
if(!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = whichChild(target);
|
||||
let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable
|
||||
|
||||
//console.log('emoticonsMenuOnClick', index, heights, target);
|
||||
|
||||
/* if(menuScroll) {
|
||||
menuScroll.container.scrollLeft = target.scrollWidth * index;
|
||||
}
|
||||
console.log('emoticonsMenuOnClick', menu.getBoundingClientRect(), target.getBoundingClientRect());
|
||||
*/
|
||||
/* scroll.onAddedBottom = () => { // привет, костыль, давно не виделись!
|
||||
scroll.container.scrollTop = y;
|
||||
scroll.onAddedBottom = () => {};
|
||||
}; */
|
||||
scroll.container.scrollTop = y;
|
||||
|
||||
/* setTimeout(() => {
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
}, 100); */
|
||||
|
||||
/* window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
}); */
|
||||
});
|
||||
};
|
||||
|
||||
public static contentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLElement, menuScroll?: Scrollable) => {
|
||||
let y = Math.round(scroll.scrollTop);
|
||||
|
||||
//console.log(heights, y);
|
||||
|
||||
for(let i = 0; i < heights.length; ++i) {
|
||||
let height = heights[i];
|
||||
if(y < height) {
|
||||
menu.children[prevCategoryIndex].classList.remove('active');
|
||||
prevCategoryIndex = i/* + 1 */;
|
||||
menu.children[prevCategoryIndex].classList.add('active');
|
||||
|
||||
if(menuScroll) {
|
||||
if(i < heights.length - 4) {
|
||||
menuScroll.container.scrollLeft = (i - 3) * 47;
|
||||
} else {
|
||||
menuScroll.container.scrollLeft = i * 47;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return prevCategoryIndex;
|
||||
};
|
||||
|
||||
public static onMediaClick = (e: MouseEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
target = findUpTag(target, 'DIV');
|
||||
|
||||
if(!target) return;
|
||||
|
||||
let fileID = target.dataset.docID;
|
||||
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
|
||||
/* dropdown.classList.remove('active');
|
||||
toggleEl.classList.remove('active'); */
|
||||
emoticonsDropdown.toggle(false);
|
||||
} else {
|
||||
console.warn('got no doc by id:', fileID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const emoticonsDropdown = new EmoticonsDropdown();
|
||||
// @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).emoticonsDropdown = emoticonsDropdown;
|
||||
}
|
||||
export default emoticonsDropdown;
|
302
src/components/emoticonsDropdown/index.ts
Normal file
302
src/components/emoticonsDropdown/index.ts
Normal file
@ -0,0 +1,302 @@
|
||||
import LazyLoadQueue from "../lazyLoadQueue";
|
||||
import GifsTab from "./tabs/gifs";
|
||||
import { touchSupport } from "../../lib/config";
|
||||
import { findUpClassName, findUpTag, whichChild } from "../../lib/utils";
|
||||
import { horizontalMenu } from "../horizontalMenu";
|
||||
import animationIntersector from "../animationIntersector";
|
||||
import appSidebarRight from "../../lib/appManagers/appSidebarRight";
|
||||
import appImManager from "../../lib/appManagers/appImManager";
|
||||
import Scrollable from "../scrollable_new";
|
||||
import EmojiTab from "./tabs/emoji";
|
||||
import StickersTab from "./tabs/stickers";
|
||||
|
||||
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
|
||||
|
||||
export interface EmoticonsTab {
|
||||
init: () => void,
|
||||
onCloseAfterTimeout?: () => void
|
||||
}
|
||||
|
||||
export class EmoticonsDropdown {
|
||||
public static lazyLoadQueue = new LazyLoadQueue();
|
||||
private element: HTMLElement;
|
||||
|
||||
public emojiTab: EmojiTab;
|
||||
public stickersTab: StickersTab;
|
||||
public gifsTab: GifsTab;
|
||||
|
||||
private container: HTMLElement;
|
||||
private tabsEl: HTMLElement;
|
||||
private tabID = -1;
|
||||
|
||||
private tabs: {[id: number]: EmoticonsTab};
|
||||
|
||||
public searchButton: HTMLElement;
|
||||
public deleteBtn: HTMLElement;
|
||||
|
||||
public toggleEl: HTMLElement;
|
||||
private displayTimeout: number;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement;
|
||||
|
||||
let firstTime = true;
|
||||
this.toggleEl = document.getElementById('toggle-emoticons');
|
||||
if(touchSupport) {
|
||||
this.toggleEl.addEventListener('click', () => {
|
||||
if(firstTime) {
|
||||
firstTime = false;
|
||||
this.toggle(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.toggleEl.onmouseover = (e) => {
|
||||
clearTimeout(this.displayTimeout);
|
||||
//this.displayTimeout = setTimeout(() => {
|
||||
if(firstTime) {
|
||||
this.toggleEl.onmouseout = this.element.onmouseout = (e) => {
|
||||
const toElement = (e as any).toElement as Element;
|
||||
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
this.toggle();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
this.element.onmouseover = (e) => {
|
||||
clearTimeout(this.displayTimeout);
|
||||
};
|
||||
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
this.toggle(true);
|
||||
//}, 0/* 200 */);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.emojiTab = new EmojiTab();
|
||||
this.stickersTab = new StickersTab();
|
||||
this.gifsTab = new GifsTab();
|
||||
|
||||
this.tabs = {
|
||||
0: this.emojiTab,
|
||||
1: this.stickersTab,
|
||||
2: this.gifsTab
|
||||
};
|
||||
|
||||
this.container = this.element.querySelector('.emoji-container .tabs-container') as HTMLDivElement;
|
||||
this.tabsEl = this.element.querySelector('.emoji-tabs') as HTMLUListElement;
|
||||
horizontalMenu(this.tabsEl, this.container, (id) => {
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
this.tabID = id;
|
||||
this.searchButton.classList.toggle('hide', this.tabID == 0);
|
||||
this.deleteBtn.classList.toggle('hide', this.tabID != 0);
|
||||
}, () => {
|
||||
const tab = this.tabs[this.tabID];
|
||||
if(tab.init) {
|
||||
tab.init();
|
||||
}
|
||||
|
||||
tab.onCloseAfterTimeout && tab.onCloseAfterTimeout();
|
||||
animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
|
||||
this.searchButton = this.element.querySelector('.emoji-tabs-search');
|
||||
this.searchButton.addEventListener('click', () => {
|
||||
if(this.tabID == 1) {
|
||||
appSidebarRight.stickersTab.init();
|
||||
} else {
|
||||
appSidebarRight.gifsTab.init();
|
||||
}
|
||||
});
|
||||
|
||||
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete');
|
||||
this.deleteBtn.addEventListener('click', () => {
|
||||
const input = appImManager.chatInputC.messageInput;
|
||||
if((input.lastChild as any)?.tagName) {
|
||||
input.lastElementChild.remove();
|
||||
} else if(input.lastChild) {
|
||||
if(!input.lastChild.textContent.length) {
|
||||
input.lastChild.remove();
|
||||
} else {
|
||||
input.lastChild.textContent = input.lastChild.textContent.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chatInputC.messageInput.dispatchEvent(event);
|
||||
//appSidebarRight.stickersTab.init();
|
||||
});
|
||||
|
||||
(this.tabsEl.firstElementChild.children[1] as HTMLLIElement).click(); // set emoji tab
|
||||
this.tabs[0].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
|
||||
}
|
||||
|
||||
public toggle = async(enable?: boolean) => {
|
||||
//if(!this.element) return;
|
||||
const willBeActive = (!!this.element.style.display && enable === undefined) || enable;
|
||||
if(this.init) {
|
||||
if(willBeActive) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(touchSupport) {
|
||||
this.toggleEl.classList.toggle('flip-icon', willBeActive);
|
||||
if(willBeActive) {
|
||||
appImManager.chatInputC.saveScroll();
|
||||
// @ts-ignore
|
||||
document.activeElement.blur();
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.toggleEl.classList.toggle('active', enable);
|
||||
}
|
||||
|
||||
if((this.element.style.display && enable === undefined) || enable) {
|
||||
this.element.style.display = '';
|
||||
void this.element.offsetLeft; // reflow
|
||||
this.element.classList.add('active');
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lockIntersection();
|
||||
//EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
EmoticonsDropdown.lazyLoadQueue.unlockIntersection();
|
||||
}, touchSupport ? 0 : 200);
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
} else {
|
||||
this.element.classList.remove('active');
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lockIntersection();
|
||||
//EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
|
||||
// нужно залочить группу и выключить стикеры
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
this.element.style.display = 'none';
|
||||
|
||||
// теперь можно убрать visible, чтобы они не включились после фокуса
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.unlockIntersection();
|
||||
}, touchSupport ? 0 : 200);
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
}
|
||||
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
};
|
||||
|
||||
public static menuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => {
|
||||
menu.addEventListener('click', function(e) {
|
||||
let target = e.target as HTMLElement;
|
||||
target = findUpTag(target, 'LI');
|
||||
|
||||
if(!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = whichChild(target);
|
||||
let y = heights[index - 1/* 2 */] || 0; // 10 == padding .scrollable
|
||||
|
||||
//console.log('emoticonsMenuOnClick', index, heights, target);
|
||||
|
||||
/* if(menuScroll) {
|
||||
menuScroll.container.scrollLeft = target.scrollWidth * index;
|
||||
}
|
||||
console.log('emoticonsMenuOnClick', menu.getBoundingClientRect(), target.getBoundingClientRect());
|
||||
*/
|
||||
/* scroll.onAddedBottom = () => { // привет, костыль, давно не виделись!
|
||||
scroll.container.scrollTop = y;
|
||||
scroll.onAddedBottom = () => {};
|
||||
}; */
|
||||
scroll.container.scrollTop = y;
|
||||
|
||||
/* setTimeout(() => {
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
}, 100); */
|
||||
|
||||
/* window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
}); */
|
||||
});
|
||||
};
|
||||
|
||||
public static contentOnScroll = (menu: HTMLUListElement, heights: number[], prevCategoryIndex: number, scroll: HTMLElement, menuScroll?: Scrollable) => {
|
||||
let y = Math.round(scroll.scrollTop);
|
||||
|
||||
//console.log(heights, y);
|
||||
|
||||
for(let i = 0; i < heights.length; ++i) {
|
||||
let height = heights[i];
|
||||
if(y < height) {
|
||||
menu.children[prevCategoryIndex].classList.remove('active');
|
||||
prevCategoryIndex = i/* + 1 */;
|
||||
menu.children[prevCategoryIndex].classList.add('active');
|
||||
|
||||
if(menuScroll) {
|
||||
if(i < heights.length - 4) {
|
||||
menuScroll.container.scrollLeft = (i - 3) * 47;
|
||||
} else {
|
||||
menuScroll.container.scrollLeft = i * 47;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return prevCategoryIndex;
|
||||
};
|
||||
|
||||
public static onMediaClick = (e: MouseEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
target = findUpTag(target, 'DIV');
|
||||
|
||||
if(!target) return;
|
||||
|
||||
let fileID = target.dataset.docID;
|
||||
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
|
||||
/* dropdown.classList.remove('active');
|
||||
toggleEl.classList.remove('active'); */
|
||||
emoticonsDropdown.toggle(false);
|
||||
} else {
|
||||
console.warn('got no doc by id:', fileID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const emoticonsDropdown = new EmoticonsDropdown();
|
||||
// @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).emoticonsDropdown = emoticonsDropdown;
|
||||
}
|
||||
export default emoticonsDropdown;
|
209
src/components/emoticonsDropdown/tabs/emoji.ts
Normal file
209
src/components/emoticonsDropdown/tabs/emoji.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { EmoticonsTab, EmoticonsDropdown } from "..";
|
||||
import Scrollable from "../../scrollable_new";
|
||||
import Config from "../../../lib/config";
|
||||
import { putPreloader } from "../../misc";
|
||||
import appStateManager from "../../../lib/appManagers/appStateManager";
|
||||
import { RichTextProcessor } from "../../../lib/richtextprocessor";
|
||||
import appImManager from "../../../lib/appManagers/appImManager";
|
||||
|
||||
export default class EmojiTab implements EmoticonsTab {
|
||||
public content: HTMLElement;
|
||||
|
||||
private recent: string[] = [];
|
||||
private recentItemsDiv: HTMLElement;
|
||||
|
||||
private heights: number[] = [];
|
||||
private scroll: Scrollable;
|
||||
|
||||
init() {
|
||||
this.content = document.getElementById('content-emoji') as HTMLDivElement;
|
||||
|
||||
const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"];
|
||||
const divs: {
|
||||
[category: string]: HTMLDivElement
|
||||
} = {};
|
||||
|
||||
const sorted: {
|
||||
[category: string]: string[]
|
||||
} = {
|
||||
'Recent': []
|
||||
};
|
||||
|
||||
for(const emoji in Config.Emoji) {
|
||||
const details = Config.Emoji[emoji];
|
||||
const i = '' + details;
|
||||
const category = categories[+i[0] - 1];
|
||||
if(!category) continue; // maybe it's skin tones
|
||||
|
||||
if(!sorted[category]) sorted[category] = [];
|
||||
sorted[category][+i.slice(1) || 0] = emoji;
|
||||
}
|
||||
|
||||
//console.log('emoticons sorted:', sorted);
|
||||
|
||||
//Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
|
||||
|
||||
categories.pop();
|
||||
delete sorted["Skin Tones"];
|
||||
|
||||
//console.time('emojiParse');
|
||||
for(const category in sorted) {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('emoji-category');
|
||||
|
||||
const titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('category-title');
|
||||
titleDiv.innerText = category;
|
||||
|
||||
const itemsDiv = document.createElement('div');
|
||||
itemsDiv.classList.add('category-items');
|
||||
|
||||
div.append(titleDiv, itemsDiv);
|
||||
|
||||
const emojis = sorted[category];
|
||||
emojis.forEach(emoji => {
|
||||
/* if(emojiUnicode(emoji) == '1f481-200d-2642') {
|
||||
console.log('append emoji', emoji, emojiUnicode(emoji));
|
||||
} */
|
||||
|
||||
this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv);
|
||||
|
||||
/* if(category == 'Smileys & Emotion') {
|
||||
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
|
||||
} */
|
||||
});
|
||||
|
||||
divs[category] = div;
|
||||
}
|
||||
//console.timeEnd('emojiParse');
|
||||
|
||||
let prevCategoryIndex = 0;
|
||||
const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement;
|
||||
const emojiScroll = this.scroll = new Scrollable(this.content, 'y', 'EMOJI', null);
|
||||
emojiScroll.container.addEventListener('scroll', (e) => {
|
||||
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, this.heights, prevCategoryIndex, emojiScroll.container);
|
||||
});
|
||||
//emojiScroll.setVirtualContainer(emojiScroll.container);
|
||||
|
||||
const preloader = putPreloader(this.content, true);
|
||||
|
||||
Promise.all([
|
||||
new Promise((resolve) => setTimeout(resolve, 200)),
|
||||
|
||||
appStateManager.getState().then(state => {
|
||||
if(Array.isArray(state.recentEmoji)) {
|
||||
this.recent = state.recentEmoji;
|
||||
}
|
||||
})
|
||||
]).then(() => {
|
||||
preloader.remove();
|
||||
|
||||
this.recentItemsDiv = divs['Recent'].querySelector('.category-items');
|
||||
for(const emoji of this.recent) {
|
||||
this.appendEmoji(emoji, this.recentItemsDiv);
|
||||
}
|
||||
|
||||
categories.unshift('Recent');
|
||||
categories.map(category => {
|
||||
const div = divs[category];
|
||||
|
||||
if(!div) {
|
||||
console.error('no div by category:', category);
|
||||
}
|
||||
|
||||
emojiScroll.append(div);
|
||||
return div;
|
||||
}).forEach(div => {
|
||||
//console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight);
|
||||
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight);
|
||||
});
|
||||
});
|
||||
|
||||
this.content.addEventListener('click', this.onContentClick);
|
||||
EmoticonsDropdown.menuOnClick(menu, this.heights, emojiScroll);
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
private appendEmoji(emoji: string, container: HTMLElement, prepend = false) {
|
||||
//const emoji = details.unified;
|
||||
//const emoji = (details.unified as string).split('-')
|
||||
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
||||
|
||||
const spanEmoji = document.createElement('span');
|
||||
const kek = RichTextProcessor.wrapEmojiText(emoji);
|
||||
|
||||
/* if(!kek.includes('emoji')) {
|
||||
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
|
||||
return;
|
||||
} */
|
||||
|
||||
//console.log(kek);
|
||||
|
||||
spanEmoji.innerHTML = kek;
|
||||
|
||||
if(spanEmoji.firstElementChild) {
|
||||
(spanEmoji.firstElementChild as HTMLImageElement).setAttribute('loading', 'lazy');
|
||||
}
|
||||
|
||||
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
||||
//spanEmoji.setAttribute('emoji', emoji);
|
||||
if(prepend) container.prepend(spanEmoji);
|
||||
else container.appendChild(spanEmoji);
|
||||
}
|
||||
|
||||
private getEmojiFromElement(element: HTMLElement) {
|
||||
if(element.tagName == 'SPAN' && !element.classList.contains('emoji')) {
|
||||
element = element.firstElementChild as HTMLElement;
|
||||
}
|
||||
|
||||
return element.getAttribute('alt') || element.innerText;
|
||||
}
|
||||
|
||||
onContentClick = (e: MouseEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
//if(target.tagName != 'SPAN') return;
|
||||
|
||||
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) {
|
||||
target = target.firstElementChild as HTMLElement;
|
||||
} else if(target.tagName == 'DIV') return;
|
||||
|
||||
//console.log('contentEmoji div', target);
|
||||
|
||||
appImManager.chatInputC.messageInput.innerHTML += target.outerHTML;
|
||||
|
||||
// Recent
|
||||
const emoji = this.getEmojiFromElement(target);
|
||||
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
|
||||
const _emoji = this.getEmojiFromElement(el);
|
||||
if(emoji == _emoji) {
|
||||
el.remove();
|
||||
}
|
||||
});
|
||||
const scrollHeight = this.recentItemsDiv.scrollHeight;
|
||||
this.appendEmoji(emoji, this.recentItemsDiv, true);
|
||||
|
||||
// нужно поставить новые размеры для скролла
|
||||
if(this.recentItemsDiv.scrollHeight != scrollHeight) {
|
||||
this.heights.length = 0;
|
||||
(Array.from(this.scroll.container.children) as HTMLElement[]).forEach(div => {
|
||||
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight);
|
||||
});
|
||||
}
|
||||
|
||||
this.recent.findAndSplice(e => e == emoji);
|
||||
this.recent.unshift(emoji);
|
||||
if(this.recent.length > 36) {
|
||||
this.recent.length = 36;
|
||||
}
|
||||
|
||||
appStateManager.pushToState('recentEmoji', this.recent);
|
||||
|
||||
// Append to input
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chatInputC.messageInput.dispatchEvent(event);
|
||||
};
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
}
|
44
src/components/emoticonsDropdown/tabs/gifs.ts
Normal file
44
src/components/emoticonsDropdown/tabs/gifs.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { EmoticonsDropdown, EmoticonsTab, EMOTICONSSTICKERGROUP } from "..";
|
||||
import GifsMasonry from "../../gifsMasonry";
|
||||
import Scrollable from "../../scrollable_new";
|
||||
import { putPreloader } from "../../misc";
|
||||
import apiManager from "../../../lib/mtproto/mtprotoworker";
|
||||
import { MTDocument } from "../../../types";
|
||||
import appDocsManager from "../../../lib/appManagers/appDocsManager";
|
||||
|
||||
export default class GifsTab implements EmoticonsTab {
|
||||
public content: HTMLElement;
|
||||
|
||||
init() {
|
||||
this.content = document.getElementById('content-gifs');
|
||||
const gifsContainer = this.content.firstElementChild as HTMLDivElement;
|
||||
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick);
|
||||
|
||||
const masonry = new GifsMasonry(gifsContainer);
|
||||
const scroll = new Scrollable(this.content, 'y', 'GIFS', null);
|
||||
const preloader = putPreloader(this.content, true);
|
||||
|
||||
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => {
|
||||
let res = _res as {
|
||||
_: 'messages.savedGifs',
|
||||
gifs: MTDocument[],
|
||||
hash: number
|
||||
};
|
||||
//console.log('getSavedGifs res:', res);
|
||||
|
||||
//let line: MTDocument[] = [];
|
||||
|
||||
preloader.remove();
|
||||
res.gifs.forEach((doc, idx) => {
|
||||
res.gifs[idx] = appDocsManager.saveDoc(doc);
|
||||
masonry.add(res.gifs[idx], EMOTICONSSTICKERGROUP, EmoticonsDropdown.lazyLoadQueue);
|
||||
});
|
||||
});
|
||||
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
}
|
318
src/components/emoticonsDropdown/tabs/stickers.ts
Normal file
318
src/components/emoticonsDropdown/tabs/stickers.ts
Normal file
@ -0,0 +1,318 @@
|
||||
import { EmoticonsTab, EMOTICONSSTICKERGROUP, EmoticonsDropdown } from "..";
|
||||
import { MTDocument } from "../../../types";
|
||||
import Scrollable from "../../scrollable_new";
|
||||
import { wrapSticker } from "../../wrappers";
|
||||
import appStickersManager, { MTStickerSet } from "../../../lib/appManagers/appStickersManager";
|
||||
import appDownloadManager from "../../../lib/appManagers/appDownloadManager";
|
||||
import { readBlobAsText } from "../../../helpers/blob";
|
||||
import lottieLoader from "../../../lib/lottieLoader";
|
||||
import { renderImageFromUrl, putPreloader } from "../../misc";
|
||||
import { RichTextProcessor } from "../../../lib/richtextprocessor";
|
||||
import { $rootScope } from "../../../lib/utils";
|
||||
import apiManager from "../../../lib/mtproto/mtprotoworker";
|
||||
|
||||
export default class StickersTab implements EmoticonsTab {
|
||||
public content: HTMLElement;
|
||||
|
||||
private stickerSets: {[id: string]: {
|
||||
stickers: HTMLElement,
|
||||
tab: HTMLElement
|
||||
}} = {};
|
||||
|
||||
private recentDiv: HTMLElement;
|
||||
private recentStickers: MTDocument[] = [];
|
||||
|
||||
private heights: number[] = [];
|
||||
private heightRAF = 0;
|
||||
private scroll: Scrollable;
|
||||
|
||||
private menu: HTMLUListElement;
|
||||
|
||||
private mounted = false;
|
||||
|
||||
categoryPush(categoryDiv: HTMLElement, categoryTitle: string, docs: MTDocument[], prepend?: boolean) {
|
||||
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
|
||||
|
||||
const itemsDiv = document.createElement('div');
|
||||
itemsDiv.classList.add('category-items');
|
||||
|
||||
const titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('category-title');
|
||||
titleDiv.innerHTML = categoryTitle;
|
||||
|
||||
categoryDiv.append(titleDiv, itemsDiv);
|
||||
|
||||
docs.forEach(doc => {
|
||||
itemsDiv.append(this.renderSticker(doc));
|
||||
});
|
||||
|
||||
if(prepend) {
|
||||
if(this.recentDiv.parentElement) {
|
||||
this.scroll.prepend(categoryDiv);
|
||||
this.scroll.prepend(this.recentDiv);
|
||||
} else {
|
||||
this.scroll.prepend(categoryDiv);
|
||||
}
|
||||
} else this.scroll.append(categoryDiv);
|
||||
|
||||
/* let scrollHeight = categoryDiv.scrollHeight;
|
||||
let prevHeight = heights[heights.length - 1] || 0;
|
||||
//console.log('scrollHeight', scrollHeight, categoryDiv, stickersDiv.childElementCount);
|
||||
if(prepend && heights.length) {// all stickers loaded faster than recent
|
||||
heights.forEach((h, i) => heights[i] += scrollHeight);
|
||||
|
||||
return heights.unshift(scrollHeight) - 1;
|
||||
} */
|
||||
|
||||
this.setNewHeights();
|
||||
|
||||
/* Array.from(stickersDiv.children).forEach((div, i) => {
|
||||
heights[i] = (heights[i - 1] || 0) + div.scrollHeight;
|
||||
}); */
|
||||
|
||||
//this.scroll.onScroll();
|
||||
|
||||
//return heights.push(prevHeight + scrollHeight) - 1;
|
||||
}
|
||||
|
||||
setNewHeights() {
|
||||
if(this.heightRAF) return;
|
||||
//if(this.heightRAF) window.cancelAnimationFrame(this.heightRAF);
|
||||
this.heightRAF = window.requestAnimationFrame(() => {
|
||||
this.heightRAF = 0;
|
||||
|
||||
const heights = this.heights;
|
||||
|
||||
let paddingTop = parseInt(window.getComputedStyle(this.scroll.container).getPropertyValue('padding-top')) || 0;
|
||||
|
||||
heights.length = 0;
|
||||
/* let concated = this.scroll.hiddenElements.up.concat(this.scroll.visibleElements, this.scroll.hiddenElements.down);
|
||||
concated.forEach((el, i) => {
|
||||
heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0);
|
||||
}); */
|
||||
let concated = Array.from(this.scroll.splitUp.children) as HTMLElement[];
|
||||
concated.forEach((el, i) => {
|
||||
heights[i] = (heights[i - 1] || 0) + el.scrollHeight + (i == 0 ? paddingTop : 0);
|
||||
});
|
||||
|
||||
this.scroll.reorder();
|
||||
|
||||
//console.log('stickers concated', concated, heights);
|
||||
});
|
||||
}
|
||||
|
||||
renderSticker(doc: MTDocument) {
|
||||
const div = document.createElement('div');
|
||||
wrapSticker({
|
||||
doc,
|
||||
div,
|
||||
/* width: 80,
|
||||
height: 80,
|
||||
play: false,
|
||||
loop: false, */
|
||||
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
|
||||
group: EMOTICONSSTICKERGROUP,
|
||||
onlyThumb: doc.sticker == 2
|
||||
});
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
async renderStickerSet(set: MTStickerSet, prepend = false) {
|
||||
const categoryDiv = document.createElement('div');
|
||||
categoryDiv.classList.add('sticker-category');
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.classList.add('btn-icon');
|
||||
|
||||
this.stickerSets[set.id] = {
|
||||
stickers: categoryDiv,
|
||||
tab: li
|
||||
};
|
||||
|
||||
if(prepend) {
|
||||
this.menu.insertBefore(li, this.menu.firstElementChild.nextSibling);
|
||||
} else {
|
||||
this.menu.append(li);
|
||||
}
|
||||
|
||||
//stickersScroll.append(categoryDiv);
|
||||
|
||||
const stickerSet = await appStickersManager.getStickerSet(set);
|
||||
|
||||
//console.log('got stickerSet', stickerSet, li);
|
||||
|
||||
if(stickerSet.set.thumb) {
|
||||
const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set);
|
||||
const promise = appDownloadManager.download(downloadOptions);
|
||||
|
||||
if(stickerSet.set.pFlags.animated) {
|
||||
promise
|
||||
.then(readBlobAsText)
|
||||
.then(JSON.parse)
|
||||
.then(json => {
|
||||
lottieLoader.loadAnimationWorker({
|
||||
container: li,
|
||||
loop: true,
|
||||
autoplay: false,
|
||||
animationData: json,
|
||||
width: 32,
|
||||
height: 32
|
||||
}, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
} else {
|
||||
const image = new Image();
|
||||
promise.then(blob => {
|
||||
renderImageFromUrl(image, URL.createObjectURL(blob), () => {
|
||||
li.append(image);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else { // as thumb will be used first sticker
|
||||
wrapSticker({
|
||||
doc: stickerSet.documents[0],
|
||||
div: li as any,
|
||||
group: EMOTICONSSTICKERGROUP
|
||||
}); // kostil
|
||||
}
|
||||
|
||||
this.categoryPush(categoryDiv, RichTextProcessor.wrapEmojiText(stickerSet.set.title), stickerSet.documents, prepend);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.content = document.getElementById('content-stickers');
|
||||
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
|
||||
|
||||
this.recentDiv = document.createElement('div');
|
||||
this.recentDiv.classList.add('sticker-category');
|
||||
|
||||
let menuWrapper = this.content.previousElementSibling as HTMLDivElement;
|
||||
this.menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement;
|
||||
|
||||
let menuScroll = new Scrollable(menuWrapper, 'x');
|
||||
|
||||
let stickersDiv = document.createElement('div');
|
||||
stickersDiv.classList.add('stickers-categories');
|
||||
this.content.append(stickersDiv);
|
||||
|
||||
/* stickersDiv.addEventListener('mouseover', (e) => {
|
||||
let target = e.target as HTMLElement;
|
||||
|
||||
if(target.tagName == 'CANVAS') { // turn on sticker
|
||||
let animation = lottieLoader.getAnimation(target.parentElement, EMOTICONSSTICKERGROUP);
|
||||
|
||||
if(animation) {
|
||||
// @ts-ignore
|
||||
if(animation.currentFrame == animation.totalFrames - 1) {
|
||||
animation.goToAndPlay(0, true);
|
||||
} else {
|
||||
animation.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}); */
|
||||
|
||||
$rootScope.$on('stickers_installed', (e: CustomEvent) => {
|
||||
const set: MTStickerSet = e.detail;
|
||||
|
||||
if(!this.stickerSets[set.id] && this.mounted) {
|
||||
this.renderStickerSet(set, true);
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$on('stickers_deleted', (e: CustomEvent) => {
|
||||
const set: MTStickerSet = e.detail;
|
||||
|
||||
if(this.stickerSets[set.id] && this.mounted) {
|
||||
const elements = this.stickerSets[set.id];
|
||||
elements.stickers.remove();
|
||||
elements.tab.remove();
|
||||
this.setNewHeights();
|
||||
delete this.stickerSets[set.id];
|
||||
}
|
||||
});
|
||||
|
||||
stickersDiv.addEventListener('click', EmoticonsDropdown.onMediaClick);
|
||||
|
||||
let prevCategoryIndex = 0;
|
||||
this.scroll = new Scrollable(this.content, 'y', 'STICKERS', undefined, undefined, 2);
|
||||
this.scroll.container.addEventListener('scroll', (e) => {
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
|
||||
if(this.heights[1] == 0) {
|
||||
this.setNewHeights();
|
||||
}
|
||||
|
||||
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(this.menu, this.heights, prevCategoryIndex, this.scroll.container, menuScroll);
|
||||
});
|
||||
this.scroll.setVirtualContainer(stickersDiv);
|
||||
|
||||
this.menu.addEventListener('click', () => {
|
||||
if(this.heights[1] == 0) {
|
||||
this.setNewHeights();
|
||||
}
|
||||
});
|
||||
|
||||
EmoticonsDropdown.menuOnClick(this.menu, this.heights, this.scroll, menuScroll);
|
||||
|
||||
const preloader = putPreloader(this.content, true);
|
||||
|
||||
Promise.all([
|
||||
appStickersManager.getRecentStickers().then(stickers => {
|
||||
this.recentStickers = stickers.stickers.slice(0, 20);
|
||||
|
||||
//stickersScroll.prepend(categoryDiv);
|
||||
|
||||
this.stickerSets['recent'] = {
|
||||
stickers: this.recentDiv,
|
||||
tab: this.menu.firstElementChild as HTMLElement
|
||||
};
|
||||
|
||||
preloader.remove();
|
||||
this.categoryPush(this.recentDiv, 'Recent', this.recentStickers, true);
|
||||
}),
|
||||
|
||||
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => {
|
||||
let stickers: {
|
||||
_: 'messages.allStickers',
|
||||
hash: number,
|
||||
sets: Array<MTStickerSet>
|
||||
} = res as any;
|
||||
|
||||
preloader.remove();
|
||||
|
||||
for(let set of stickers.sets) {
|
||||
this.renderStickerSet(set);
|
||||
}
|
||||
})
|
||||
]).finally(() => {
|
||||
this.mounted = true;
|
||||
});
|
||||
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
pushRecentSticker(doc: MTDocument) {
|
||||
if(!this.recentDiv.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`);
|
||||
if(!div) {
|
||||
div = this.renderSticker(doc);
|
||||
}
|
||||
|
||||
const items = this.recentDiv.lastElementChild;
|
||||
items.prepend(div);
|
||||
|
||||
if(items.childElementCount > 20) {
|
||||
(Array.from(items.children) as HTMLElement[]).slice(20).forEach(el => el.remove());
|
||||
}
|
||||
|
||||
this.setNewHeights();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
}
|
@ -49,11 +49,18 @@ export default class GifsMasonry {
|
||||
|
||||
//let preloader = new ProgressivePreloader(div);
|
||||
|
||||
const posterURL = appDocsManager.getThumbURL(doc, false);
|
||||
const gotThumb = appDocsManager.getThumb(doc, false);
|
||||
|
||||
const willBeAPoster = !!gotThumb;
|
||||
let img: HTMLImageElement;
|
||||
if(posterURL) {
|
||||
if(willBeAPoster) {
|
||||
img = new Image();
|
||||
img.src = posterURL;
|
||||
|
||||
if(!gotThumb.thumb.url) {
|
||||
gotThumb.promise.then(() => {
|
||||
img.src = gotThumb.thumb.url;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mouseOut = false;
|
||||
@ -124,6 +131,6 @@ export default class GifsMasonry {
|
||||
}
|
||||
};
|
||||
|
||||
(posterURL ? renderImageFromUrl(img, posterURL, afterRender) : afterRender());
|
||||
(gotThumb?.thumb?.url ? renderImageFromUrl(img, gotThumb.thumb.url, afterRender) : afterRender());
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import ProgressivePreloader from './preloader';
|
||||
import LazyLoadQueue from './lazyLoadQueue';
|
||||
import VideoPlayer from '../lib/mediaPlayer';
|
||||
import { RichTextProcessor } from '../lib/richtextprocessor';
|
||||
import { renderImageFromUrl, loadedURLs } from './misc';
|
||||
import { renderImageFromUrl } from './misc';
|
||||
import appMessagesManager from '../lib/appManagers/appMessagesManager';
|
||||
import { Layouter, RectPart } from './groupedLayout';
|
||||
import PollElement from './poll';
|
||||
@ -14,8 +14,9 @@ import { mediaSizes, isSafari } from '../lib/config';
|
||||
import { MTDocument, MTPhotoSize } from '../types';
|
||||
import animationIntersector from './animationIntersector';
|
||||
import AudioElement from './audio';
|
||||
import appDownloadManager, { Download, Progress, DownloadBlob } from '../lib/appManagers/appDownloadManager';
|
||||
import { webpWorkerController } from '../lib/webp/webpWorkerController';
|
||||
import { DownloadBlob } from '../lib/appManagers/appDownloadManager';
|
||||
import webpWorkerController from '../lib/webp/webpWorkerController';
|
||||
import { readBlobAsText } from '../helpers/blob';
|
||||
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: {
|
||||
doc: MTDocument,
|
||||
@ -92,9 +93,11 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
}
|
||||
|
||||
if(!img?.parentElement) {
|
||||
const posterURL = appDocsManager.getThumbURL(doc, false);
|
||||
if(posterURL) {
|
||||
video.poster = posterURL;
|
||||
const gotThumb = appDocsManager.getThumb(doc, false);
|
||||
if(gotThumb) {
|
||||
gotThumb.promise.then(() => {
|
||||
video.poster = gotThumb.thumb.url;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,16 +382,7 @@ export function wrapPhoto(photo: MTPhoto | MTDocument, message: any, container:
|
||||
if(preloader) {
|
||||
preloader.attach(container, true, promise);
|
||||
}
|
||||
|
||||
/* const url = appPhotosManager.getPhotoURL(photoID, size);
|
||||
return renderImageFromUrl(image || container, url).then(() => {
|
||||
photo.downloaded = true;
|
||||
}); */
|
||||
|
||||
/* if(preloader) {
|
||||
preloader.attach(container, true, promise);
|
||||
} */
|
||||
|
||||
|
||||
return promise.then(() => {
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
@ -413,7 +407,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
withThumb?: boolean,
|
||||
loop?: boolean
|
||||
}) {
|
||||
let stickerType = doc.sticker;
|
||||
const stickerType = doc.sticker;
|
||||
|
||||
if(!width) {
|
||||
width = !emoji ? 200 : undefined;
|
||||
@ -439,8 +433,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
|
||||
const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1;
|
||||
|
||||
if(doc.thumbs && !div.firstElementChild && (!doc.downloaded || stickerType == 2)) {
|
||||
let thumb = doc.thumbs[0];
|
||||
if(doc.thumbs?.length && !div.firstElementChild && (!doc.downloaded || stickerType == 2 || onlyThumb) && toneIndex <= 0) {
|
||||
const thumb = doc.thumbs[0];
|
||||
|
||||
//console.log('wrap sticker', thumb, div);
|
||||
|
||||
@ -454,56 +448,50 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
if(thumb.bytes || thumb.url) {
|
||||
img = new Image();
|
||||
|
||||
if((!isSafari || doc.stickerThumbConverted)/* && false */) {
|
||||
if((!isSafari || doc.stickerThumbConverted || thumb.url)/* && false */) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender);
|
||||
} else {
|
||||
webpWorkerController.convert(doc.id, thumb.bytes).then(bytes => {
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
thumb.bytes = bytes;
|
||||
doc.stickerThumbConverted = true;
|
||||
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
if(!div.childElementCount) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender);
|
||||
}
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
if(onlyThumb) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} else if(!onlyThumb && stickerType == 2 && withThumb && toneIndex <= 0) {
|
||||
} else if(stickerType == 2 && (withThumb || onlyThumb)) {
|
||||
img = new Image();
|
||||
|
||||
|
||||
const load = () => {
|
||||
if(div.childElementCount || (middleware && !middleware())) return;
|
||||
renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb), afterRender);
|
||||
|
||||
const r = () => {
|
||||
if(div.childElementCount || (middleware && !middleware())) return;
|
||||
renderImageFromUrl(img, thumb.url, afterRender);
|
||||
};
|
||||
|
||||
if(thumb.url) {
|
||||
r();
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return appDocsManager.getThumbURL(doc, thumb).promise.then(r);
|
||||
}
|
||||
};
|
||||
|
||||
/* let downloaded = appDocsManager.hasDownloadedThumb(doc.id, thumb.type);
|
||||
if(downloaded) {
|
||||
div.append(img);
|
||||
} */
|
||||
|
||||
//lazyLoadQueue && !downloaded ? lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}) : load();
|
||||
load();
|
||||
if(lazyLoadQueue && onlyThumb) {
|
||||
lazyLoadQueue.push({div, load});
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(onlyThumb && doc.thumbs) { // for sticker panel
|
||||
let thumb = doc.thumbs[0];
|
||||
|
||||
let load = () => {
|
||||
let img = new Image();
|
||||
renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb), () => {
|
||||
if(middleware && !middleware()) return;
|
||||
div.append(img);
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load();
|
||||
if(onlyThumb) { // for sticker panel
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let downloaded = doc.downloaded;
|
||||
@ -519,13 +507,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
|
||||
//appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => {
|
||||
//fetch(doc.url).then(res => res.json()).then(async(json) => {
|
||||
appDownloadManager.download(doc.url, appDocsManager.getInputFileName(doc), 'json').then(async(json) => {
|
||||
appDocsManager.downloadDocNew(doc.id)
|
||||
.then(readBlobAsText)
|
||||
.then(JSON.parse)
|
||||
.then(async(json) => {
|
||||
//console.timeEnd('download sticker' + doc.id);
|
||||
//console.log('loaded sticker:', doc, div);
|
||||
//console.log('loaded sticker:', doc, div, blob);
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
//await new Promise((resolve) => setTimeout(resolve, 5e3));
|
||||
|
||||
let animation = await LottieLoader.loadAnimationWorker/* loadAnimation */({
|
||||
container: div,
|
||||
loop: loop && !emoji,
|
||||
@ -534,7 +523,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
width,
|
||||
height
|
||||
}, group, toneIndex);
|
||||
|
||||
|
||||
animation.addListener('firstFrame', () => {
|
||||
if(div.firstElementChild && div.firstElementChild.tagName == 'IMG') {
|
||||
div.firstElementChild.remove();
|
||||
@ -542,16 +531,17 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
animation.canvas.classList.add('fade-in');
|
||||
}
|
||||
}, true);
|
||||
|
||||
|
||||
if(emoji) {
|
||||
div.addEventListener('click', () => {
|
||||
let animation = LottieLoader.getAnimation(div);
|
||||
|
||||
|
||||
if(animation.paused) {
|
||||
animation.restart();
|
||||
}
|
||||
});
|
||||
}
|
||||
//await new Promise((resolve) => setTimeout(resolve, 5e3));
|
||||
});
|
||||
|
||||
//console.timeEnd('render sticker' + doc.id);
|
||||
@ -571,13 +561,22 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
});
|
||||
}
|
||||
|
||||
renderImageFromUrl(img, doc.url, () => {
|
||||
if(div.firstElementChild && div.firstElementChild != img) {
|
||||
div.firstElementChild.remove();
|
||||
}
|
||||
const r = () => {
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
div.append(img);
|
||||
});
|
||||
renderImageFromUrl(img, doc.url, () => {
|
||||
if(div.firstElementChild && div.firstElementChild != img) {
|
||||
div.firstElementChild.remove();
|
||||
}
|
||||
|
||||
div.append(img);
|
||||
});
|
||||
};
|
||||
|
||||
if(doc.url) r();
|
||||
else {
|
||||
appDocsManager.downloadDocNew(doc).then(r);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
10
src/helpers/blob.ts
Normal file
10
src/helpers/blob.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const readBlobAsText = (blob: Blob) => {
|
||||
return new Promise<string>(resolve => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('loadend', async(e) => {
|
||||
// @ts-ignore
|
||||
resolve(e.srcElement.result);
|
||||
});
|
||||
reader.readAsText(blob);
|
||||
});
|
||||
};
|
29
src/helpers/context.ts
Normal file
29
src/helpers/context.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
||||
export const isServiceWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
|
||||
export const isWorker = isWebWorker || isServiceWorker;
|
||||
|
||||
// в SW может быть сразу две переменных TRUE, поэтому проверяю по последней
|
||||
|
||||
const notifyServiceWorker = (...args: any[]) => {
|
||||
(self as any as ServiceWorkerGlobalScope)
|
||||
.clients
|
||||
.matchAll({ includeUncontrolled: false, type: 'window' })
|
||||
.then((listeners) => {
|
||||
if(!listeners.length) {
|
||||
//console.trace('no listeners?', self, listeners);
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
listeners[0].postMessage(...args);
|
||||
});
|
||||
};
|
||||
|
||||
const notifyWorker = (...args: any[]) => {
|
||||
// @ts-ignore
|
||||
(self as any as DedicatedWorkerGlobalScope).postMessage(...args);
|
||||
};
|
||||
|
||||
const empty = () => {};
|
||||
|
||||
export const notifySomeone = isServiceWorker ? notifyServiceWorker : (isWebWorker ? notifyWorker : empty);
|
@ -1,23 +1,21 @@
|
||||
import {RichTextProcessor} from '../richtextprocessor';
|
||||
import { CancellablePromise, deferredPromise } from '../polyfill';
|
||||
import { isObject, getFileURL, FileURLType } from '../utils';
|
||||
import opusDecodeController from '../opusDecodeController';
|
||||
import { MTDocument, inputDocumentFileLocation, MTPhotoSize } from '../../types';
|
||||
import { getFileNameByLocation } from '../bin_utils';
|
||||
import appDownloadManager, { Download, ResponseMethod, DownloadBlob } from './appDownloadManager';
|
||||
import appDownloadManager, { DownloadBlob } from './appDownloadManager';
|
||||
import appPhotosManager from './appPhotosManager';
|
||||
|
||||
class AppDocsManager {
|
||||
private docs: {[docID: string]: MTDocument} = {};
|
||||
private downloadPromises: {[docID: string]: CancellablePromise<Blob>} = {};
|
||||
|
||||
public saveDoc(apiDoc: MTDocument, context?: any) {
|
||||
//console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
|
||||
if(this.docs[apiDoc.id]) {
|
||||
const d = this.docs[apiDoc.id];
|
||||
|
||||
if(apiDoc.thumbs) {
|
||||
if(!d.thumbs) d.thumbs = apiDoc.thumbs;
|
||||
public saveDoc(doc: MTDocument, context?: any) {
|
||||
//console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
|
||||
if(this.docs[doc.id]) {
|
||||
const d = this.docs[doc.id];
|
||||
|
||||
if(doc.thumbs) {
|
||||
if(!d.thumbs) d.thumbs = doc.thumbs;
|
||||
/* else if(apiDoc.thumbs[0].bytes && !d.thumbs[0].bytes) {
|
||||
d.thumbs.unshift(apiDoc.thumbs[0]);
|
||||
} else if(d.thumbs[0].url) { // fix for converted thumb in safari
|
||||
@ -25,7 +23,7 @@ class AppDocsManager {
|
||||
} */
|
||||
}
|
||||
|
||||
d.file_reference = apiDoc.file_reference;
|
||||
d.file_reference = doc.file_reference;
|
||||
return d;
|
||||
|
||||
//return Object.assign(d, apiDoc, context);
|
||||
@ -33,22 +31,22 @@ class AppDocsManager {
|
||||
}
|
||||
|
||||
if(context) {
|
||||
Object.assign(apiDoc, context);
|
||||
Object.assign(doc, context);
|
||||
}
|
||||
|
||||
this.docs[apiDoc.id] = apiDoc;
|
||||
this.docs[doc.id] = doc;
|
||||
|
||||
apiDoc.attributes.forEach((attribute: any) => {
|
||||
doc.attributes.forEach((attribute: any) => {
|
||||
switch(attribute._) {
|
||||
case 'documentAttributeFilename':
|
||||
apiDoc.file_name = RichTextProcessor.wrapPlainText(attribute.file_name);
|
||||
doc.file_name = RichTextProcessor.wrapPlainText(attribute.file_name);
|
||||
break;
|
||||
|
||||
case 'documentAttributeAudio':
|
||||
apiDoc.duration = attribute.duration;
|
||||
apiDoc.audioTitle = attribute.title;
|
||||
apiDoc.audioPerformer = attribute.performer;
|
||||
apiDoc.type = attribute.pFlags.voice && apiDoc.mime_type == "audio/ogg" ? 'voice' : 'audio';
|
||||
doc.duration = attribute.duration;
|
||||
doc.audioTitle = attribute.title;
|
||||
doc.audioPerformer = attribute.performer;
|
||||
doc.type = attribute.pFlags.voice && doc.mime_type == "audio/ogg" ? 'voice' : 'audio';
|
||||
|
||||
/* if(apiDoc.type == 'audio') {
|
||||
apiDoc.supportsStreaming = true;
|
||||
@ -56,97 +54,98 @@ class AppDocsManager {
|
||||
break;
|
||||
|
||||
case 'documentAttributeVideo':
|
||||
apiDoc.duration = attribute.duration;
|
||||
apiDoc.w = attribute.w;
|
||||
apiDoc.h = attribute.h;
|
||||
doc.duration = attribute.duration;
|
||||
doc.w = attribute.w;
|
||||
doc.h = attribute.h;
|
||||
//apiDoc.supportsStreaming = attribute.pFlags?.supports_streaming/* && apiDoc.size > 524288 */;
|
||||
if(/* apiDoc.thumbs && */attribute.pFlags.round_message) {
|
||||
apiDoc.type = 'round';
|
||||
doc.type = 'round';
|
||||
} else /* if(apiDoc.thumbs) */ {
|
||||
apiDoc.type = 'video';
|
||||
doc.type = 'video';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'documentAttributeSticker':
|
||||
if(attribute.alt !== undefined) {
|
||||
apiDoc.stickerEmojiRaw = attribute.alt;
|
||||
apiDoc.stickerEmoji = RichTextProcessor.wrapRichText(apiDoc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true});
|
||||
doc.stickerEmojiRaw = attribute.alt;
|
||||
doc.stickerEmoji = RichTextProcessor.wrapRichText(doc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true});
|
||||
}
|
||||
|
||||
if(attribute.stickerset) {
|
||||
if(attribute.stickerset._ == 'inputStickerSetEmpty') {
|
||||
delete attribute.stickerset;
|
||||
} else if(attribute.stickerset._ == 'inputStickerSetID') {
|
||||
apiDoc.stickerSetInput = attribute.stickerset;
|
||||
doc.stickerSetInput = attribute.stickerset;
|
||||
}
|
||||
}
|
||||
|
||||
if(/* apiDoc.thumbs && */apiDoc.mime_type == 'image/webp') {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.sticker = 1;
|
||||
if(/* apiDoc.thumbs && */doc.mime_type == 'image/webp') {
|
||||
doc.type = 'sticker';
|
||||
doc.sticker = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'documentAttributeImageSize':
|
||||
apiDoc.w = attribute.w;
|
||||
apiDoc.h = attribute.h;
|
||||
doc.w = attribute.w;
|
||||
doc.h = attribute.h;
|
||||
break;
|
||||
|
||||
case 'documentAttributeAnimated':
|
||||
if((apiDoc.mime_type == 'image/gif' || apiDoc.mime_type == 'video/mp4')/* && apiDoc.thumbs */) {
|
||||
apiDoc.type = 'gif';
|
||||
if((doc.mime_type == 'image/gif' || doc.mime_type == 'video/mp4')/* && apiDoc.thumbs */) {
|
||||
doc.type = 'gif';
|
||||
}
|
||||
|
||||
apiDoc.animated = true;
|
||||
doc.animated = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if(!apiDoc.mime_type) {
|
||||
switch(apiDoc.type) {
|
||||
if(!doc.mime_type) {
|
||||
switch(doc.type) {
|
||||
case 'gif':
|
||||
case 'video':
|
||||
case 'round':
|
||||
apiDoc.mime_type = 'video/mp4';
|
||||
doc.mime_type = 'video/mp4';
|
||||
break;
|
||||
case 'sticker':
|
||||
apiDoc.mime_type = 'image/webp';
|
||||
doc.mime_type = 'image/webp';
|
||||
break;
|
||||
case 'audio':
|
||||
apiDoc.mime_type = 'audio/mpeg';
|
||||
doc.mime_type = 'audio/mpeg';
|
||||
break;
|
||||
case 'voice':
|
||||
apiDoc.mime_type = 'audio/ogg';
|
||||
doc.mime_type = 'audio/ogg';
|
||||
break;
|
||||
default:
|
||||
apiDoc.mime_type = 'application/octet-stream';
|
||||
doc.mime_type = 'application/octet-stream';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if((apiDoc.type == 'gif' && apiDoc.size > 8e6) || apiDoc.type == 'audio' || apiDoc.type == 'video') {
|
||||
apiDoc.supportsStreaming = true;
|
||||
if((doc.type == 'gif' && doc.size > 8e6) || doc.type == 'audio' || doc.type == 'video') {
|
||||
doc.supportsStreaming = true;
|
||||
doc.url = this.getFileURL(doc);
|
||||
}
|
||||
|
||||
if(!apiDoc.file_name) {
|
||||
apiDoc.file_name = '';
|
||||
if(!doc.file_name) {
|
||||
doc.file_name = '';
|
||||
}
|
||||
|
||||
if(apiDoc.mime_type == 'application/x-tgsticker' && apiDoc.file_name == "AnimatedSticker.tgs") {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.animated = true;
|
||||
apiDoc.sticker = 2;
|
||||
if(doc.mime_type == 'application/x-tgsticker' && doc.file_name == "AnimatedSticker.tgs") {
|
||||
doc.type = 'sticker';
|
||||
doc.animated = true;
|
||||
doc.sticker = 2;
|
||||
}
|
||||
|
||||
if(apiDoc._ == 'documentEmpty') {
|
||||
apiDoc.size = 0;
|
||||
if(doc._ == 'documentEmpty') {
|
||||
doc.size = 0;
|
||||
}
|
||||
|
||||
if(!apiDoc.url) {
|
||||
apiDoc.url = this.getFileURL(apiDoc);
|
||||
}
|
||||
/* if(!doc.url) {
|
||||
doc.url = this.getFileURL(doc);
|
||||
} */
|
||||
|
||||
return apiDoc;
|
||||
return doc;
|
||||
}
|
||||
|
||||
public getDoc(docID: string | MTDocument): MTDocument {
|
||||
@ -177,9 +176,26 @@ class AppDocsManager {
|
||||
};
|
||||
}
|
||||
|
||||
public getFileURL(doc: MTDocument, download = false, thumb?: MTPhotoSize) {
|
||||
public getFileDownloadOptions(doc: MTDocument, thumb?: MTPhotoSize) {
|
||||
const inputFileLocation = this.getInput(doc, thumb?.type);
|
||||
|
||||
let mimeType: string;
|
||||
if(thumb) {
|
||||
mimeType = doc.sticker ? 'image/webp' : 'image/jpeg'/* doc.mime_type */;
|
||||
} else {
|
||||
mimeType = doc.mime_type || 'application/octet-stream';
|
||||
}
|
||||
|
||||
return {
|
||||
dcID: doc.dc_id,
|
||||
location: inputFileLocation,
|
||||
size: thumb ? thumb.size : doc.size,
|
||||
mimeType: mimeType,
|
||||
fileName: doc.file_name
|
||||
};
|
||||
}
|
||||
|
||||
public getFileURL(doc: MTDocument, download = false, thumb?: MTPhotoSize) {
|
||||
let type: FileURLType;
|
||||
if(download) {
|
||||
type = 'download';
|
||||
@ -191,23 +207,25 @@ class AppDocsManager {
|
||||
type = 'document';
|
||||
}
|
||||
|
||||
let mimeType: string;
|
||||
if(thumb) {
|
||||
mimeType = doc.sticker ? 'image/webp' : 'image/jpeg'/* doc.mime_type */;
|
||||
} else {
|
||||
mimeType = doc.mime_type || 'application/octet-stream';
|
||||
}
|
||||
|
||||
return getFileURL(type, {
|
||||
dcID: doc.dc_id,
|
||||
location: inputFileLocation,
|
||||
size: thumb ? thumb.size : doc.size,
|
||||
mimeType: mimeType,
|
||||
fileName: doc.file_name
|
||||
});
|
||||
return getFileURL(type, this.getFileDownloadOptions(doc, thumb));
|
||||
}
|
||||
|
||||
public getThumbURL(doc: MTDocument, useBytes = true) {
|
||||
public getThumbURL(doc: MTDocument, thumb: MTPhotoSize) {
|
||||
let promise: Promise<any> = Promise.resolve();
|
||||
|
||||
if(!thumb.url) {
|
||||
if(thumb.bytes) {
|
||||
thumb.url = appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker);
|
||||
} else {
|
||||
//return this.getFileURL(doc, false, thumb);
|
||||
promise = this.downloadDocNew(doc, thumb);
|
||||
}
|
||||
}
|
||||
|
||||
return {thumb, promise};
|
||||
}
|
||||
|
||||
public getThumb(doc: MTDocument, useBytes = true) {
|
||||
if(doc.thumbs?.length) {
|
||||
let thumb: MTPhotoSize;
|
||||
if(!useBytes) {
|
||||
@ -218,43 +236,43 @@ class AppDocsManager {
|
||||
thumb = doc.thumbs[0];
|
||||
}
|
||||
|
||||
if(thumb.bytes) {
|
||||
return appPhotosManager.getPreviewURLFromBytes(doc.thumbs[0].bytes, !!doc.sticker);
|
||||
} else {
|
||||
return this.getFileURL(doc, false, thumb);
|
||||
}
|
||||
return this.getThumbURL(doc, thumb);
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
public getInputFileName(doc: MTDocument, thumbSize?: string) {
|
||||
return getFileNameByLocation(this.getInput(doc, thumbSize), {fileName: doc.file_name});
|
||||
}
|
||||
|
||||
public downloadDocNew(docID: string | MTDocument/* , method: ResponseMethod = 'blob' */): DownloadBlob {
|
||||
public downloadDocNew(docID: string | MTDocument, thumb?: MTPhotoSize): DownloadBlob {
|
||||
const doc = this.getDoc(docID);
|
||||
|
||||
if(doc._ == 'documentEmpty') {
|
||||
throw new Error('Document empty!');
|
||||
}
|
||||
|
||||
const fileName = this.getInputFileName(doc);
|
||||
const fileName = this.getInputFileName(doc, thumb?.type);
|
||||
|
||||
let download: DownloadBlob = appDownloadManager.getDownload(fileName);
|
||||
if(download) {
|
||||
return download;
|
||||
}
|
||||
|
||||
download = appDownloadManager.download(doc.url, fileName/* , method */);
|
||||
const downloadOptions = this.getFileDownloadOptions(doc, thumb);
|
||||
download = appDownloadManager.download(downloadOptions);
|
||||
|
||||
const originalPromise = download;
|
||||
originalPromise.then((blob) => {
|
||||
doc.downloaded = true;
|
||||
|
||||
if(!doc.supportsStreaming) {
|
||||
if(thumb) {
|
||||
thumb.url = URL.createObjectURL(blob);
|
||||
return;
|
||||
} else if(!doc.supportsStreaming) {
|
||||
doc.url = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
doc.downloaded = true;
|
||||
});
|
||||
|
||||
if(doc.type == 'voice' && !opusDecodeController.isPlaySupported()) {
|
||||
@ -278,8 +296,6 @@ class AppDocsManager {
|
||||
});
|
||||
|
||||
return blob;
|
||||
//return originalPromise;
|
||||
//return new Response(blob);
|
||||
});
|
||||
}
|
||||
|
||||
@ -287,10 +303,8 @@ class AppDocsManager {
|
||||
}
|
||||
|
||||
public saveDocFile(doc: MTDocument) {
|
||||
const url = this.getFileURL(doc, true);
|
||||
const fileName = this.getInputFileName(doc);
|
||||
|
||||
return appDownloadManager.downloadToDisc(fileName, url, doc.file_name);
|
||||
const options = this.getFileDownloadOptions(doc);
|
||||
return appDownloadManager.downloadToDisc(options, doc.file_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { $rootScope } from "../utils";
|
||||
import apiManager from "../mtproto/mtprotoworker";
|
||||
import { deferredPromise, CancellablePromise } from "../polyfill";
|
||||
import type { DownloadOptions } from "../mtproto/apiFileManager";
|
||||
import { getFileNameByLocation } from "../bin_utils";
|
||||
|
||||
export type ResponseMethodBlob = 'blob';
|
||||
export type ResponseMethodJson = 'json';
|
||||
@ -38,40 +40,24 @@ export class AppDownloadManager {
|
||||
});
|
||||
}
|
||||
|
||||
public download(url: string, fileName: string, responseMethod?: ResponseMethodBlob): DownloadBlob;
|
||||
public download(url: string, fileName: string, responseMethod?: ResponseMethodJson): DownloadJson;
|
||||
public download(url: string, fileName: string, responseMethod: ResponseMethod = 'blob'): DownloadBlob {
|
||||
public download(options: DownloadOptions, responseMethod?: ResponseMethodBlob): DownloadBlob;
|
||||
public download(options: DownloadOptions, responseMethod?: ResponseMethodJson): DownloadJson;
|
||||
public download(options: DownloadOptions, responseMethod: ResponseMethod = 'blob'): DownloadBlob {
|
||||
const fileName = getFileNameByLocation(options.location, {fileName: options.fileName});
|
||||
|
||||
if(this.downloads.hasOwnProperty(fileName)) return this.downloads[fileName];
|
||||
|
||||
const deferred = deferredPromise<Blob>();
|
||||
|
||||
const controller = new AbortController();
|
||||
const promise = fetch(url, {signal: controller.signal})
|
||||
.then(res => res[responseMethod]())
|
||||
.then(res => deferred.resolve(res))
|
||||
.catch(err => { // Только потому что event.request.signal не работает в SW, либо я кривой?
|
||||
if(err.name === 'AbortError') {
|
||||
//console.log('Fetch aborted');
|
||||
apiManager.cancelDownload(fileName);
|
||||
delete this.downloads[fileName];
|
||||
delete this.progress[fileName];
|
||||
delete this.progressCallbacks[fileName];
|
||||
} else {
|
||||
//console.error('Uh oh, an error!', err);
|
||||
}
|
||||
|
||||
deferred.reject(err);
|
||||
throw err;
|
||||
apiManager.downloadFile(options)
|
||||
.then(deferred.resolve, deferred.reject)
|
||||
.finally(() => {
|
||||
delete this.progressCallbacks[fileName];
|
||||
});
|
||||
|
||||
//console.log('Will download file:', fileName, url);
|
||||
|
||||
promise.finally(() => {
|
||||
delete this.progressCallbacks[fileName];
|
||||
});
|
||||
|
||||
deferred.cancel = () => {
|
||||
controller.abort();
|
||||
deferred.cancel = () => {};
|
||||
};
|
||||
|
||||
@ -129,8 +115,8 @@ export class AppDownloadManager {
|
||||
return this.download(fileName, url);
|
||||
} */
|
||||
|
||||
public downloadToDisc(fileName: string, url: string, discFileName: string) {
|
||||
const download = this.download(url, fileName);
|
||||
public downloadToDisc(options: DownloadOptions, discFileName: string) {
|
||||
const download = this.download(options);
|
||||
download/* .promise */.then(blob => {
|
||||
const objectURL = URL.createObjectURL(blob);
|
||||
this.createDownloadAnchor(objectURL, discFileName, () => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { calcImageInBox, isObject, getFileURL } from "../utils";
|
||||
import { calcImageInBox, isObject } from "../utils";
|
||||
import { bytesFromHex, getFileNameByLocation } from "../bin_utils";
|
||||
import { MTPhotoSize, inputPhotoFileLocation, inputDocumentFileLocation, FileLocation, MTDocument } from "../../types";
|
||||
import appDownloadManager, { Download } from "./appDownloadManager";
|
||||
import { deferredPromise, CancellablePromise } from "../polyfill";
|
||||
import appDownloadManager from "./appDownloadManager";
|
||||
import { CancellablePromise } from "../polyfill";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
|
||||
export type MTPhoto = {
|
||||
@ -203,8 +203,8 @@ export class AppPhotosManager {
|
||||
|
||||
return photoSize;
|
||||
}
|
||||
|
||||
public getPhotoURL(photo: MTPhoto | MTDocument, photoSize: MTPhotoSize) {
|
||||
|
||||
public getPhotoDownloadOptions(photo: MTPhoto | MTDocument, photoSize: MTPhotoSize) {
|
||||
const isDocument = photo._ == 'document';
|
||||
|
||||
if(!photoSize || photoSize._ == 'photoSizeEmpty') {
|
||||
@ -222,8 +222,14 @@ export class AppPhotosManager {
|
||||
thumb_size: photoSize.type
|
||||
} : photoSize.location;
|
||||
|
||||
return {url: getFileURL('photo', {dcID: photo.dc_id, location, size: isPhoto ? photoSize.size : undefined}), location};
|
||||
return {dcID: photo.dc_id, location, size: isPhoto ? photoSize.size : undefined};
|
||||
}
|
||||
|
||||
/* public getPhotoURL(photo: MTPhoto | MTDocument, photoSize: MTPhotoSize) {
|
||||
const downloadOptions = this.getPhotoDownloadOptions(photo, photoSize);
|
||||
|
||||
return {url: getFileURL('photo', downloadOptions), location: downloadOptions.location};
|
||||
} */
|
||||
|
||||
public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): CancellablePromise<Blob> {
|
||||
const photo = this.getPhoto(photoID);
|
||||
@ -240,15 +246,15 @@ export class AppPhotosManager {
|
||||
return Promise.resolve() as any;
|
||||
}
|
||||
|
||||
const {url, location} = this.getPhotoURL(photo, photoSize);
|
||||
const fileName = getFileNameByLocation(location);
|
||||
const downloadOptions = this.getPhotoDownloadOptions(photo, photoSize);
|
||||
const fileName = getFileNameByLocation(downloadOptions.location);
|
||||
|
||||
let download = appDownloadManager.getDownload(fileName);
|
||||
if(download) {
|
||||
return download;
|
||||
}
|
||||
|
||||
download = appDownloadManager.download(url, fileName);
|
||||
download = appDownloadManager.download(downloadOptions);
|
||||
download.then(blob => {
|
||||
if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) {
|
||||
cacheContext.downloaded = blob.size;
|
||||
@ -261,7 +267,6 @@ export class AppPhotosManager {
|
||||
});
|
||||
|
||||
return download;
|
||||
//return fetch(url).then(res => res.blob());
|
||||
}
|
||||
|
||||
public getCacheContext(photo: any) {
|
||||
@ -302,10 +307,12 @@ export class AppPhotosManager {
|
||||
thumb_size: fullPhotoSize.type
|
||||
};
|
||||
|
||||
const url = getFileURL('download', {dcID: photo.dc_id, location, size: fullPhotoSize.size, fileName: 'photo' + photo.id + '.jpg'});
|
||||
const fileName = getFileNameByLocation(location);
|
||||
|
||||
appDownloadManager.downloadToDisc(fileName, url, 'photo' + photo.id + '.jpg');
|
||||
appDownloadManager.downloadToDisc({
|
||||
dcID: photo.dc_id,
|
||||
location,
|
||||
size: fullPhotoSize.size,
|
||||
fileName: 'photo' + photo.id + '.jpg'
|
||||
}, 'photo' + photo.id + '.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import AppStorage from '../storage';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import appDocsManager from './appDocsManager';
|
||||
import { MTDocument, inputStickerSetThumb } from '../../types';
|
||||
import { $rootScope, getFileURL } from '../utils';
|
||||
import { $rootScope } from '../utils';
|
||||
|
||||
export type MTStickerSet = {
|
||||
_: 'stickerSet',
|
||||
@ -217,7 +217,7 @@ class AppStickersManager {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public getStickerSetThumbURL(stickerSet: MTStickerSet) {
|
||||
public getStickerSetThumbDownloadOptions(stickerSet: MTStickerSet) {
|
||||
const thumb = stickerSet.thumb;
|
||||
const dcID = stickerSet.thumb_dc_id;
|
||||
|
||||
@ -230,11 +230,27 @@ class AppStickersManager {
|
||||
local_id: thumb.location.local_id
|
||||
};
|
||||
|
||||
const url = getFileURL('document', {dcID, location: input, size: thumb.size, mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'});
|
||||
return {dcID, location: input, size: thumb.size, mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'};
|
||||
}
|
||||
|
||||
/* public getStickerSetThumbURL(stickerSet: MTStickerSet) {
|
||||
const thumb = stickerSet.thumb;
|
||||
const dcID = stickerSet.thumb_dc_id;
|
||||
|
||||
const isAnimated = stickerSet.pFlags?.animated;
|
||||
|
||||
const input: inputStickerSetThumb = {
|
||||
_: 'inputStickerSetThumb',
|
||||
stickerset: this.getStickerSetInput(stickerSet),
|
||||
volume_id: thumb.location.volume_id,
|
||||
local_id: thumb.location.local_id
|
||||
};
|
||||
|
||||
const url = getFileURL('document', this.getStickerSetThumbDownloadOptions(stickerSet));
|
||||
return url;
|
||||
|
||||
//return promise;
|
||||
}
|
||||
} */
|
||||
|
||||
public getStickerSetInput(set: {id: string, access_hash: string}) {
|
||||
return set.id == 'emoji' ? {
|
||||
|
@ -8,6 +8,7 @@ import { logger, LogLevels } from "../logger";
|
||||
import { InputFileLocation, FileLocation, UploadFile } from "../../types";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
import cryptoWorker from "../crypto/cryptoworker";
|
||||
import { notifySomeone } from "../../helpers/context";
|
||||
|
||||
type Delayed = {
|
||||
offset: number,
|
||||
@ -18,7 +19,7 @@ type Delayed = {
|
||||
export type DownloadOptions = {
|
||||
dcID: number,
|
||||
location: InputFileLocation | FileLocation,
|
||||
size: number,
|
||||
size?: number,
|
||||
fileName?: string,
|
||||
mimeType?: string,
|
||||
limitPart?: number,
|
||||
@ -156,17 +157,8 @@ export class ApiFileManager {
|
||||
convertWebp = (bytes: Uint8Array, fileName: string) => {
|
||||
const convertPromise = deferredPromise<Uint8Array>();
|
||||
|
||||
(self as any as ServiceWorkerGlobalScope)
|
||||
.clients
|
||||
.matchAll({includeUncontrolled: false, type: 'window'})
|
||||
.then((listeners) => {
|
||||
if(!listeners.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners[0].postMessage({type: 'convertWebp', payload: {fileName, bytes}});
|
||||
});
|
||||
|
||||
const task = {type: 'convertWebp', payload: {fileName, bytes}};
|
||||
notifySomeone(task);
|
||||
return this.webpConvertPromises[fileName] = convertPromise;
|
||||
};
|
||||
|
||||
|
@ -1,148 +1,38 @@
|
||||
// just to include
|
||||
import {secureRandom} from '../polyfill';
|
||||
secureRandom;
|
||||
|
||||
import apiManager from "./apiManager";
|
||||
import AppStorage from '../storage';
|
||||
import cryptoWorker from "../crypto/cryptoworker";
|
||||
import networkerFactory from "./networkerFactory";
|
||||
import apiFileManager, { DownloadOptions } from './apiFileManager';
|
||||
import { getFileNameByLocation } from '../bin_utils';
|
||||
import { logger, LogLevels } from '../logger';
|
||||
import { isSafari } from '../../helpers/userAgent';
|
||||
import { logger, LogLevels } from '../logger';
|
||||
import type { DownloadOptions } from './apiFileManager';
|
||||
import type { InputFileLocation, FileLocation, UploadFile, WorkerTaskTemplate } from '../../types';
|
||||
import { deferredPromise, CancellablePromise } from '../polyfill';
|
||||
import { notifySomeone } from '../../helpers/context';
|
||||
|
||||
const log = logger('SW', LogLevels.error);
|
||||
|
||||
const ctx = self as any as ServiceWorkerGlobalScope;
|
||||
|
||||
//console.error('INCLUDE !!!', new Error().stack);
|
||||
const deferredPromises: {[taskID: number]: CancellablePromise<any>} = {};
|
||||
|
||||
/* function isObject(object: any) {
|
||||
return typeof(object) === 'object' && object !== null;
|
||||
} */
|
||||
ctx.addEventListener('message', (e) => {
|
||||
const task = e.data as ServiceWorkerTaskResponse;
|
||||
const promise = deferredPromises[task.id];
|
||||
|
||||
/* function fillTransfer(transfer: any, obj: any) {
|
||||
if(!obj) return;
|
||||
|
||||
if(obj instanceof ArrayBuffer) {
|
||||
transfer.add(obj);
|
||||
} else if(obj.buffer && obj.buffer instanceof ArrayBuffer) {
|
||||
transfer.add(obj.buffer);
|
||||
} else if(isObject(obj)) {
|
||||
for(var i in obj) {
|
||||
fillTransfer(transfer, obj[i]);
|
||||
}
|
||||
} else if(Array.isArray(obj)) {
|
||||
obj.forEach(value => {
|
||||
fillTransfer(transfer, value);
|
||||
});
|
||||
if(task.payload) {
|
||||
promise.resolve(task.payload);
|
||||
} else {
|
||||
promise.reject();
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Respond to request
|
||||
*/
|
||||
function respond(client: Client | ServiceWorker | MessagePort, ...args: any[]) {
|
||||
// отключил для всего потому что не успел пофиксить transfer detached
|
||||
//if(isSafari(self)/* || true */) {
|
||||
// @ts-ignore
|
||||
client.postMessage(...args);
|
||||
/* } else {
|
||||
var transfer = new Set();
|
||||
fillTransfer(transfer, arguments);
|
||||
|
||||
//console.log('reply', transfer, [...transfer]);
|
||||
ctx.postMessage(...arguments, [...transfer]);
|
||||
//console.log('reply', transfer, [...transfer]);
|
||||
} */
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast Notification
|
||||
*/
|
||||
function notify(...args: any[]) {
|
||||
ctx.clients.matchAll({includeUncontrolled: false, type: 'window'}).then((listeners) => {
|
||||
if(!listeners.length) {
|
||||
//console.trace('no listeners?', self, listeners);
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.forEach(listener => {
|
||||
// @ts-ignore
|
||||
listener.postMessage(...args);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
networkerFactory.setUpdatesProcessor((obj, bool) => {
|
||||
notify({update: {obj, bool}});
|
||||
delete deferredPromises[task.id];
|
||||
});
|
||||
|
||||
const onMessage = async(e: ExtendableMessageEvent) => {
|
||||
try {
|
||||
const taskID = e.data.taskID;
|
||||
let taskID = 0;
|
||||
|
||||
log.debug('got message:', taskID, e, e.data);
|
||||
|
||||
if(e.data.useLs) {
|
||||
AppStorage.finishTask(e.data.taskID, e.data.args);
|
||||
return;
|
||||
} else if(e.data.type == 'convertWebp') {
|
||||
const {fileName, bytes} = e.data.payload;
|
||||
const deferred = apiFileManager.webpConvertPromises[fileName];
|
||||
if(deferred) {
|
||||
deferred.resolve(bytes);
|
||||
delete apiFileManager.webpConvertPromises[fileName];
|
||||
}
|
||||
}
|
||||
|
||||
switch(e.data.task) {
|
||||
case 'computeSRP':
|
||||
case 'gzipUncompress':
|
||||
// @ts-ignore
|
||||
return cryptoWorker[e.data.task].apply(cryptoWorker, e.data.args).then(result => {
|
||||
respond(e.source, {taskID: taskID, result: result});
|
||||
});
|
||||
|
||||
case 'cancelDownload':
|
||||
case 'downloadFile': {
|
||||
/* // @ts-ignore
|
||||
return apiFileManager.downloadFile(...e.data.args); */
|
||||
|
||||
try {
|
||||
// @ts-ignore
|
||||
let result = apiFileManager[e.data.task].apply(apiFileManager, e.data.args);
|
||||
|
||||
if(result instanceof Promise) {
|
||||
result = await result;
|
||||
}
|
||||
|
||||
respond(e.source, {taskID: taskID, result: result});
|
||||
} catch(err) {
|
||||
respond(e.source, {taskID: taskID, error: err});
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
try {
|
||||
// @ts-ignore
|
||||
let result = apiManager[e.data.task].apply(apiManager, e.data.args);
|
||||
|
||||
if(result instanceof Promise) {
|
||||
result = await result;
|
||||
}
|
||||
|
||||
respond(e.source, {taskID: taskID, result: result});
|
||||
} catch(err) {
|
||||
respond(e.source, {taskID: taskID, error: err});
|
||||
}
|
||||
|
||||
//throw new Error('Unknown task: ' + e.data.task);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
export interface ServiceWorkerTask extends WorkerTaskTemplate {
|
||||
type: 'requestFilePart',
|
||||
payload: [number, InputFileLocation | FileLocation, number, number]
|
||||
};
|
||||
|
||||
}
|
||||
export interface ServiceWorkerTaskResponse extends WorkerTaskTemplate {
|
||||
type: 'requestFilePart',
|
||||
payload: UploadFile
|
||||
};
|
||||
|
||||
const onFetch = (event: FetchEvent): void => {
|
||||
@ -152,70 +42,6 @@ const onFetch = (event: FetchEvent): void => {
|
||||
log.debug('[fetch]:', event);
|
||||
|
||||
switch(scope) {
|
||||
case 'download':
|
||||
case 'thumb':
|
||||
case 'document':
|
||||
case 'photo': {
|
||||
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
|
||||
|
||||
const rangeHeader = event.request.headers.get('Range');
|
||||
if(rangeHeader && info.mimeType && info.size) { // maybe safari
|
||||
const range = parseRange(event.request.headers.get('Range'));
|
||||
const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
|
||||
if(possibleResponse) {
|
||||
return event.respondWith(possibleResponse);
|
||||
}
|
||||
}
|
||||
|
||||
const fileName = getFileNameByLocation(info.location, {fileName: info.fileName});
|
||||
|
||||
/* event.request.signal.addEventListener('abort', (e) => {
|
||||
console.log('[SW] user aborted request:', fileName);
|
||||
cancellablePromise.cancel();
|
||||
});
|
||||
|
||||
event.request.signal.onabort = (e) => {
|
||||
console.log('[SW] user aborted request:', fileName);
|
||||
cancellablePromise.cancel();
|
||||
};
|
||||
|
||||
if(fileName == '5452060085729624717') {
|
||||
setInterval(() => {
|
||||
console.log('[SW] request status:', fileName, event.request.signal.aborted);
|
||||
}, 1000);
|
||||
} */
|
||||
|
||||
const cancellablePromise = apiFileManager.downloadFile(info);
|
||||
cancellablePromise.notify = (progress: {done: number, total: number, offset: number}) => {
|
||||
notify({progress: {fileName, ...progress}});
|
||||
};
|
||||
|
||||
log.debug('[fetch] file:', /* info, */fileName);
|
||||
|
||||
event.respondWith(Promise.race([
|
||||
timeout(45 * 1000),
|
||||
new Promise<Response>((resolve) => { // пробую это чтобы проверить, не сдохнет ли воркер
|
||||
cancellablePromise.then(b => {
|
||||
const responseInit: ResponseInit = {};
|
||||
|
||||
if(rangeHeader) {
|
||||
responseInit.headers = {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Range': `bytes 0-${info.size - 1}/${info.size || '*'}`,
|
||||
'Content-Length': `${info.size}`,
|
||||
}
|
||||
}
|
||||
|
||||
resolve(new Response(b, responseInit));
|
||||
}).catch(err => {
|
||||
|
||||
});
|
||||
})
|
||||
]));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'stream': {
|
||||
const range = parseRange(event.request.headers.get('Range'));
|
||||
const [offset, end] = range;
|
||||
@ -227,6 +53,7 @@ const onFetch = (event: FetchEvent): void => {
|
||||
|
||||
event.respondWith(Promise.race([
|
||||
timeout(45 * 1000),
|
||||
|
||||
new Promise<Response>((resolve, reject) => {
|
||||
// safari workaround
|
||||
const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
|
||||
@ -237,11 +64,19 @@ const onFetch = (event: FetchEvent): void => {
|
||||
const limit = end && end < STREAM_CHUNK_UPPER_LIMIT ? alignLimit(end - offset + 1) : STREAM_CHUNK_UPPER_LIMIT;
|
||||
const alignedOffset = alignOffset(offset, limit);
|
||||
|
||||
//log.debug('[stream] requestFilePart:', info.dcID, info.location, alignedOffset, limit);
|
||||
|
||||
apiFileManager.requestFilePart(info.dcID, info.location, alignedOffset, limit).then(result => {
|
||||
log.debug('[stream] requestFilePart:', info.dcID, info.location, alignedOffset, limit);
|
||||
|
||||
const task: ServiceWorkerTask = {
|
||||
type: 'requestFilePart',
|
||||
id: taskID++,
|
||||
payload: [info.dcID, info.location, alignedOffset, limit]
|
||||
};
|
||||
|
||||
|
||||
const deferred = deferredPromises[task.id] = deferredPromise<UploadFile>();
|
||||
deferred.then(result => {
|
||||
let ab = result.bytes;
|
||||
|
||||
|
||||
//log.debug('[stream] requestFilePart result:', result);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
@ -267,127 +102,12 @@ const onFetch = (event: FetchEvent): void => {
|
||||
}));
|
||||
//}, 2.5e3);
|
||||
}).catch(err => {});
|
||||
|
||||
notifySomeone(task);
|
||||
})
|
||||
]));
|
||||
break;
|
||||
}
|
||||
|
||||
/* case 'download': {
|
||||
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
|
||||
|
||||
const promise = new Promise<Response>((resolve) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Disposition': `attachment; filename="${info.fileName}"`,
|
||||
};
|
||||
|
||||
if(info.size) headers['Content-Length'] = info.size.toString();
|
||||
if(info.mimeType) headers['Content-Type'] = info.mimeType;
|
||||
|
||||
log('[download] file:', info);
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller: ReadableStreamDefaultController) {
|
||||
const limitPart = DOWNLOAD_CHUNK_LIMIT;
|
||||
|
||||
apiFileManager.downloadFile({
|
||||
...info,
|
||||
limitPart,
|
||||
processPart: (bytes, offset) => {
|
||||
log('[download] file processPart:', bytes, offset);
|
||||
|
||||
controller.enqueue(new Uint8Array(bytes));
|
||||
|
||||
const isFinal = offset + limitPart >= info.size;
|
||||
if(isFinal) {
|
||||
controller.close();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}).catch(err => {
|
||||
log.error('[download] error:', err);
|
||||
controller.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
log.error('[download] file canceled:', info);
|
||||
}
|
||||
});
|
||||
|
||||
resolve(new Response(stream, {headers}));
|
||||
});
|
||||
|
||||
event.respondWith(promise);
|
||||
|
||||
break;
|
||||
} */
|
||||
|
||||
case 'upload': {
|
||||
if(event.request.method == 'POST') {
|
||||
event.respondWith(event.request.blob().then(blob => {
|
||||
return apiFileManager.uploadFile(blob).then(v => new Response(JSON.stringify(v), {headers: {'Content-Type': 'application/json'}}));
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* default: {
|
||||
|
||||
break;
|
||||
}
|
||||
case 'documents':
|
||||
case 'photos':
|
||||
case 'profiles':
|
||||
// direct download
|
||||
if (event.request.method === 'POST') {
|
||||
event.respondWith(// download(url, 'unknown file.txt', getFilePartRequest));
|
||||
event.request.text()
|
||||
.then((text) => {
|
||||
const [, filename] = text.split('=');
|
||||
return download(url, filename ? filename.toString() : 'unknown file', getFilePartRequest);
|
||||
}),
|
||||
);
|
||||
|
||||
// inline
|
||||
} else {
|
||||
event.respondWith(
|
||||
ctx.cache.match(url).then((cached) => {
|
||||
if (cached) return cached;
|
||||
|
||||
return Promise.race([
|
||||
timeout(45 * 1000), // safari fix
|
||||
new Promise<Response>((resolve) => {
|
||||
fetchRequest(url, resolve, getFilePartRequest, ctx.cache, fileProgress);
|
||||
}),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stream': {
|
||||
const [offset, end] = parseRange(event.request.headers.get('Range') || '');
|
||||
|
||||
log('stream', url, offset, end);
|
||||
|
||||
event.respondWith(new Promise((resolve) => {
|
||||
fetchStreamRequest(url, offset, end, resolve, getFilePartRequest);
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'stripped':
|
||||
case 'cached': {
|
||||
const bytes = getThumb(url) || null;
|
||||
event.respondWith(new Response(bytes, { headers: { 'Content-Type': 'image/jpg' } }));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (url && url.endsWith('.tgs')) event.respondWith(fetchTGS(url));
|
||||
else event.respondWith(fetch(event.request.url)); */
|
||||
}
|
||||
} catch(err) {
|
||||
event.respondWith(new Response('', {
|
||||
@ -398,7 +118,6 @@ const onFetch = (event: FetchEvent): void => {
|
||||
};
|
||||
|
||||
const onChangeState = () => {
|
||||
ctx.onmessage = onMessage;
|
||||
ctx.onfetch = onFetch;
|
||||
};
|
||||
|
||||
@ -496,6 +215,5 @@ function alignLimit(limit: number) {
|
||||
|
||||
// @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(ctx as any).onMessage = onMessage;
|
||||
(ctx as any).onFetch = onFetch;
|
||||
}
|
||||
|
146
src/lib/mtproto/mtproto.worker.ts
Normal file
146
src/lib/mtproto/mtproto.worker.ts
Normal file
@ -0,0 +1,146 @@
|
||||
// just to include
|
||||
import {secureRandom} from '../polyfill';
|
||||
secureRandom;
|
||||
|
||||
import apiManager from "./apiManager";
|
||||
import AppStorage from '../storage';
|
||||
import cryptoWorker from "../crypto/cryptoworker";
|
||||
import networkerFactory from "./networkerFactory";
|
||||
import apiFileManager from './apiFileManager';
|
||||
import { logger, LogLevels } from '../logger';
|
||||
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service';
|
||||
|
||||
const log = logger('DW', LogLevels.error);
|
||||
|
||||
const ctx = self as any as DedicatedWorkerGlobalScope;
|
||||
|
||||
//console.error('INCLUDE !!!', new Error().stack);
|
||||
|
||||
/* function isObject(object: any) {
|
||||
return typeof(object) === 'object' && object !== null;
|
||||
} */
|
||||
|
||||
/* function fillTransfer(transfer: any, obj: any) {
|
||||
if(!obj) return;
|
||||
|
||||
if(obj instanceof ArrayBuffer) {
|
||||
transfer.add(obj);
|
||||
} else if(obj.buffer && obj.buffer instanceof ArrayBuffer) {
|
||||
transfer.add(obj.buffer);
|
||||
} else if(isObject(obj)) {
|
||||
for(var i in obj) {
|
||||
fillTransfer(transfer, obj[i]);
|
||||
}
|
||||
} else if(Array.isArray(obj)) {
|
||||
obj.forEach(value => {
|
||||
fillTransfer(transfer, value);
|
||||
});
|
||||
}
|
||||
} */
|
||||
|
||||
function respond(...args: any[]) {
|
||||
// отключил для всего потому что не успел пофиксить transfer detached
|
||||
//if(isSafari(self)/* || true */) {
|
||||
// @ts-ignore
|
||||
ctx.postMessage(...args);
|
||||
/* } else {
|
||||
var transfer = new Set();
|
||||
fillTransfer(transfer, arguments);
|
||||
|
||||
//console.log('reply', transfer, [...transfer]);
|
||||
ctx.postMessage(...arguments, [...transfer]);
|
||||
//console.log('reply', transfer, [...transfer]);
|
||||
} */
|
||||
}
|
||||
|
||||
networkerFactory.setUpdatesProcessor((obj, bool) => {
|
||||
respond({update: {obj, bool}});
|
||||
});
|
||||
|
||||
ctx.addEventListener('message', async(e) => {
|
||||
try {
|
||||
const task = e.data;
|
||||
const taskID = task.taskID;
|
||||
|
||||
log.debug('got message:', taskID, task);
|
||||
|
||||
//debugger;
|
||||
|
||||
if(task.useLs) {
|
||||
AppStorage.finishTask(task.taskID, task.args);
|
||||
return;
|
||||
} else if(task.type == 'convertWebp') {
|
||||
const {fileName, bytes} = task.payload;
|
||||
const deferred = apiFileManager.webpConvertPromises[fileName];
|
||||
if(deferred) {
|
||||
deferred.resolve(bytes);
|
||||
delete apiFileManager.webpConvertPromises[fileName];
|
||||
}
|
||||
|
||||
return;
|
||||
} else if((task as ServiceWorkerTask).type == 'requestFilePart') {
|
||||
const task = e.data as ServiceWorkerTask;
|
||||
const responseTask: ServiceWorkerTaskResponse = {
|
||||
type: task.type,
|
||||
id: task.id,
|
||||
payload: null
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await apiFileManager.requestFilePart(...task.payload);
|
||||
responseTask.payload = res;
|
||||
} catch(err) {
|
||||
|
||||
}
|
||||
|
||||
respond(responseTask);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(task.task) {
|
||||
case 'computeSRP':
|
||||
case 'gzipUncompress':
|
||||
// @ts-ignore
|
||||
return cryptoWorker[task.task].apply(cryptoWorker, task.args).then(result => {
|
||||
respond({taskID: taskID, result: result});
|
||||
});
|
||||
|
||||
case 'cancelDownload':
|
||||
case 'downloadFile': {
|
||||
try {
|
||||
// @ts-ignore
|
||||
let result = apiFileManager[task.task].apply(apiFileManager, task.args);
|
||||
|
||||
if(result instanceof Promise) {
|
||||
result = await result;
|
||||
}
|
||||
|
||||
respond({taskID: taskID, result: result});
|
||||
} catch(err) {
|
||||
respond({taskID: taskID, error: err});
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
try {
|
||||
// @ts-ignore
|
||||
let result = apiManager[task.task].apply(apiManager, task.args);
|
||||
|
||||
if(result instanceof Promise) {
|
||||
result = await result;
|
||||
}
|
||||
|
||||
respond({taskID: taskID, result: result});
|
||||
} catch(err) {
|
||||
respond({taskID: taskID, error: err});
|
||||
}
|
||||
|
||||
//throw new Error('Unknown task: ' + task.task);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
ctx.postMessage('ready');
|
@ -1,9 +1,11 @@
|
||||
import {isObject, $rootScope} from '../utils';
|
||||
import AppStorage from '../storage';
|
||||
import CryptoWorkerMethods from '../crypto/crypto_methods';
|
||||
//import runtime from 'serviceworker-webpack-plugin/lib/runtime';
|
||||
import { logger } from '../logger';
|
||||
import { webpWorkerController } from '../webp/webpWorkerController';
|
||||
import webpWorkerController from '../webp/webpWorkerController';
|
||||
import MTProtoWorker from 'worker-loader!./mtproto.worker';
|
||||
import type { DownloadOptions } from './apiFileManager';
|
||||
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service';
|
||||
|
||||
type Task = {
|
||||
taskID: number,
|
||||
@ -11,7 +13,12 @@ type Task = {
|
||||
args: any[]
|
||||
};
|
||||
|
||||
const USEWORKERASWORKER = true;
|
||||
|
||||
class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
public worker: Worker;
|
||||
public postMessage: (...args: any[]) => void;
|
||||
|
||||
private taskID = 0;
|
||||
private awaiting: {
|
||||
[id: number]: {
|
||||
@ -30,10 +37,11 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
super();
|
||||
this.log('constructor');
|
||||
|
||||
/**
|
||||
* Service worker
|
||||
*/
|
||||
//(runtime.register({ scope: './' }) as Promise<ServiceWorkerRegistration>).then(registration => {
|
||||
this.registerServiceWorker();
|
||||
this.registerWorker();
|
||||
}
|
||||
|
||||
private registerServiceWorker() {
|
||||
navigator.serviceWorker.register('./sw.js', {scope: './'}).then(registration => {
|
||||
|
||||
}, (err) => {
|
||||
@ -44,6 +52,10 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
this.log('set SW');
|
||||
this.releasePending();
|
||||
|
||||
if(!USEWORKERASWORKER) {
|
||||
this.postMessage = navigator.serviceWorker.controller.postMessage.bind(navigator.serviceWorker.controller);
|
||||
}
|
||||
|
||||
//registration.update();
|
||||
});
|
||||
|
||||
@ -60,26 +72,12 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
* Message resolver
|
||||
*/
|
||||
navigator.serviceWorker.addEventListener('message', (e) => {
|
||||
if(!isObject(e.data)) {
|
||||
const task: ServiceWorkerTask = e.data;
|
||||
if(!isObject(task)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(e.data.useLs) {
|
||||
// @ts-ignore
|
||||
AppStorage[e.data.task](...e.data.args).then(res => {
|
||||
navigator.serviceWorker.controller.postMessage({useLs: true, taskID: e.data.taskID, args: res});
|
||||
});
|
||||
} else if(e.data.update) {
|
||||
if(this.updatesProcessor) {
|
||||
this.updatesProcessor(e.data.update.obj, e.data.update.bool);
|
||||
}
|
||||
} else if(e.data.progress) {
|
||||
$rootScope.$broadcast('download_progress', e.data.progress);
|
||||
} else if(e.data.type == 'convertWebp') {
|
||||
webpWorkerController.postMessage(e.data);
|
||||
} else {
|
||||
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
|
||||
}
|
||||
this.postMessage(task);
|
||||
});
|
||||
|
||||
navigator.serviceWorker.addEventListener('messageerror', (e) => {
|
||||
@ -87,6 +85,49 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
});
|
||||
}
|
||||
|
||||
private registerWorker() {
|
||||
const worker = new MTProtoWorker();
|
||||
worker.addEventListener('message', (e) => {
|
||||
if(!this.worker) {
|
||||
this.worker = worker;
|
||||
this.log('set webWorker');
|
||||
|
||||
if(USEWORKERASWORKER) {
|
||||
this.postMessage = this.worker.postMessage.bind(this.worker);
|
||||
}
|
||||
|
||||
this.releasePending();
|
||||
}
|
||||
|
||||
//this.log('got message from worker:', e.data);
|
||||
|
||||
const task = e.data;
|
||||
|
||||
if(!isObject(task)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(task.useLs) {
|
||||
// @ts-ignore
|
||||
AppStorage[task.task](...task.args).then(res => {
|
||||
this.postMessage({useLs: true, taskID: task.taskID, args: res});
|
||||
});
|
||||
} else if(task.update) {
|
||||
if(this.updatesProcessor) {
|
||||
this.updatesProcessor(task.update.obj, task.update.bool);
|
||||
}
|
||||
} else if(task.progress) {
|
||||
$rootScope.$broadcast('download_progress', task.progress);
|
||||
} else if(task.type == 'convertWebp') {
|
||||
webpWorkerController.postMessage(task);
|
||||
} else if((task as ServiceWorkerTaskResponse).type == 'requestFilePart') {
|
||||
navigator.serviceWorker.controller.postMessage(task);
|
||||
} else {
|
||||
this.finalizeTask(task.taskID, task.result, task.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private finalizeTask(taskID: number, result: any, error: any) {
|
||||
const deferred = this.awaiting[taskID];
|
||||
if(deferred !== undefined) {
|
||||
@ -116,10 +157,10 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
}
|
||||
|
||||
private releasePending() {
|
||||
if(navigator.serviceWorker.controller) {
|
||||
if(this.postMessage) {
|
||||
this.log.debug('releasing tasks, length:', this.pending.length);
|
||||
this.pending.forEach(pending => {
|
||||
navigator.serviceWorker.controller.postMessage(pending);
|
||||
this.postMessage(pending);
|
||||
});
|
||||
|
||||
this.log.debug('released tasks');
|
||||
@ -174,6 +215,10 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
public cancelDownload(fileName: string) {
|
||||
return this.performTaskWorker('cancelDownload', fileName);
|
||||
}
|
||||
|
||||
public downloadFile(options: DownloadOptions) {
|
||||
return this.performTaskWorker('downloadFile', options);
|
||||
}
|
||||
}
|
||||
|
||||
const apiManagerProxy = new ApiManagerProxy();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Modes } from './mtproto/mtproto_config';
|
||||
import { notifySomeone, isWorker } from '../helpers/context';
|
||||
|
||||
class ConfigStorage {
|
||||
public keyPrefix = '';
|
||||
@ -137,7 +138,6 @@ class ConfigStorage {
|
||||
}
|
||||
|
||||
class AppStorage {
|
||||
private isWorker: boolean;
|
||||
private taskID = 0;
|
||||
private tasks: {[taskID: number]: (result: any) => void} = {};
|
||||
//private log = (...args: any[]) => console.log('[SW LS]', ...args);
|
||||
@ -150,11 +150,7 @@ class AppStorage {
|
||||
this.setPrefix('t_');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
//this.isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
||||
this.isWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
|
||||
|
||||
if(!this.isWorker) {
|
||||
if(!isWorker) {
|
||||
this.configStorage = new ConfigStorage();
|
||||
}
|
||||
}
|
||||
@ -185,26 +181,13 @@ class AppStorage {
|
||||
|
||||
private proxy<T>(methodName: 'set' | 'get' | 'remove' | 'clear', ..._args: any[]) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
if(this.isWorker) {
|
||||
if(isWorker) {
|
||||
const taskID = this.taskID++;
|
||||
|
||||
this.tasks[taskID] = resolve;
|
||||
const task = {useLs: true, task: methodName, taskID, args: _args};
|
||||
|
||||
(self as any as ServiceWorkerGlobalScope)
|
||||
.clients
|
||||
.matchAll({ includeUncontrolled: false, type: 'window' })
|
||||
.then((listeners) => {
|
||||
if(!listeners.length) {
|
||||
//console.trace('no listeners?', self, listeners);
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('will proxy', {useLs: true, task: methodName, taskID, args: _args});
|
||||
listeners[0].postMessage({useLs: true, task: methodName, taskID, args: _args});
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
//self.postMessage({useLs: true, task: methodName, taskID: this.taskID, args: _args});
|
||||
notifySomeone(task);
|
||||
} else {
|
||||
let args = Array.prototype.slice.call(_args);
|
||||
args.push((result: T) => {
|
||||
|
@ -5,7 +5,7 @@
|
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import { InputFileLocation, FileLocation } from "../types";
|
||||
import type { DownloadOptions } from "./mtproto/apiFileManager";
|
||||
|
||||
var _logTimer = Date.now();
|
||||
export function dT () {
|
||||
@ -539,13 +539,7 @@ export function getEmojiToneIndex(input: string) {
|
||||
}
|
||||
|
||||
export type FileURLType = 'photo' | 'thumb' | 'document' | 'stream' | 'download';
|
||||
export function getFileURL(type: FileURLType, options: {
|
||||
dcID: number,
|
||||
location: InputFileLocation | FileLocation,
|
||||
size?: number,
|
||||
mimeType?: string,
|
||||
fileName?: string
|
||||
}) {
|
||||
export function getFileURL(type: FileURLType, options: DownloadOptions) {
|
||||
//console.log('getFileURL', location);
|
||||
//const perf = performance.now();
|
||||
const encoded = encodeURIComponent(JSON.stringify(options));
|
||||
|
@ -3,30 +3,37 @@ import type { WebpConvertTask } from './webpWorkerController';
|
||||
|
||||
const ctx = self as any as DedicatedWorkerGlobalScope;
|
||||
const tasks: WebpConvertTask[] = [];
|
||||
let isProcessing = false;
|
||||
//let isProcessing = false;
|
||||
|
||||
function finishTask() {
|
||||
isProcessing = false;
|
||||
//isProcessing = false;
|
||||
processTasks();
|
||||
}
|
||||
|
||||
function processTasks() {
|
||||
if(isProcessing) return;
|
||||
//if(isProcessing) return;
|
||||
|
||||
const task = tasks.shift();
|
||||
if(!task) return;
|
||||
|
||||
isProcessing = true;
|
||||
//isProcessing = true;
|
||||
|
||||
switch(task.type) {
|
||||
case 'convertWebp': {
|
||||
const {fileName, bytes} = task.payload;
|
||||
|
||||
let convertedBytes: Uint8Array;
|
||||
try {
|
||||
convertedBytes = webp2png(bytes).bytes;
|
||||
} catch(err) {
|
||||
console.error('Convert webp2png error:', err, 'payload:', task.payload);
|
||||
}
|
||||
|
||||
ctx.postMessage({
|
||||
type: 'convertWebp',
|
||||
payload: {
|
||||
fileName,
|
||||
bytes: webp2png(bytes).bytes
|
||||
bytes: convertedBytes
|
||||
}
|
||||
});
|
||||
|
||||
@ -42,6 +49,12 @@ function processTasks() {
|
||||
|
||||
function scheduleTask(task: WebpConvertTask) {
|
||||
tasks.push(task);
|
||||
/* if(task.payload.fileName.indexOf('main-') === 0) {
|
||||
tasks.push(task);
|
||||
} else {
|
||||
tasks.unshift(task);
|
||||
} */
|
||||
|
||||
processTasks();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import WebpWorker from 'worker-loader!./webp.worker';
|
||||
import { CancellablePromise, deferredPromise } from '../polyfill';
|
||||
import apiManagerProxy from '../mtproto/mtprotoworker';
|
||||
|
||||
export type WebpConvertTask = {
|
||||
type: 'convertWebp',
|
||||
@ -21,11 +22,11 @@ export class WebpWorkerController {
|
||||
if(payload.fileName.indexOf('main-') === 0) {
|
||||
const promise = this.convertPromises[payload.fileName];
|
||||
if(promise) {
|
||||
promise.resolve(payload.bytes);
|
||||
payload.bytes ? promise.resolve(payload.bytes) : promise.reject();
|
||||
delete this.convertPromises[payload.fileName];
|
||||
}
|
||||
} else {
|
||||
navigator.serviceWorker.controller.postMessage(e.data);
|
||||
apiManagerProxy.postMessage(e.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -40,18 +41,23 @@ export class WebpWorkerController {
|
||||
}
|
||||
|
||||
convert(fileName: string, bytes: Uint8Array) {
|
||||
fileName = 'main-' + fileName;
|
||||
|
||||
if(this.convertPromises.hasOwnProperty(fileName)) {
|
||||
return this.convertPromises[fileName];
|
||||
}
|
||||
|
||||
const convertPromise = deferredPromise<Uint8Array>();
|
||||
|
||||
fileName = 'main-' + fileName;
|
||||
|
||||
this.postMessage({type: 'convertWebp', payload: {fileName, bytes}});
|
||||
|
||||
return this.convertPromises[fileName] = convertPromise;
|
||||
}
|
||||
}
|
||||
|
||||
export const webpWorkerController = new WebpWorkerController();
|
||||
const webpWorkerController = new WebpWorkerController();
|
||||
// @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).webpWorkerController = webpWorkerController;
|
||||
}
|
||||
export default webpWorkerController;
|
@ -6,7 +6,6 @@ import Config from '../lib/config';
|
||||
import { findUpTag } from "../lib/utils";
|
||||
import pageAuthCode from "./pageAuthCode";
|
||||
import pageSignQR from './pageSignQR';
|
||||
//import apiManager from "../lib/mtproto/apiManager";
|
||||
import apiManager from "../lib/mtproto/mtprotoworker";
|
||||
import Page from "./page";
|
||||
import { App, Modes } from "../lib/mtproto/mtproto_config";
|
||||
|
@ -1211,6 +1211,10 @@ $bubble-margin: .25rem;
|
||||
background-color: #0089ff;
|
||||
}
|
||||
|
||||
&__loaded {
|
||||
background-color: #cacaca;
|
||||
}
|
||||
|
||||
input::-webkit-slider-thumb {
|
||||
background: #63a2e3;
|
||||
border: none;
|
||||
@ -1355,7 +1359,8 @@ $bubble-margin: .25rem;
|
||||
}
|
||||
|
||||
&.is-edited .time {
|
||||
width: 85px;
|
||||
/* width: 85px; */
|
||||
width: 90px !important;
|
||||
}
|
||||
|
||||
.document-ico:after {
|
||||
@ -1444,6 +1449,12 @@ $bubble-margin: .25rem;
|
||||
&.is-sending poll-element {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.media-progress {
|
||||
&__loaded {
|
||||
background-color: #90e18d !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reply-markup {
|
||||
|
@ -191,7 +191,7 @@
|
||||
}
|
||||
|
||||
.player-volume {
|
||||
margin: -3px 12px 0 16px;
|
||||
margin: -3px 2px 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
@ -481,6 +481,10 @@
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
&__loaded {
|
||||
background-color: #cacaca;
|
||||
}
|
||||
|
||||
&__seek {
|
||||
height: 2px;
|
||||
//background-color: #e6ecf0;
|
||||
|
8
src/types.d.ts
vendored
8
src/types.d.ts
vendored
@ -144,4 +144,10 @@ export type inputStickerSetThumb = {
|
||||
local_id: number
|
||||
};
|
||||
|
||||
export type InputFileLocation = inputFileLocation | inputDocumentFileLocation | inputPhotoFileLocation | inputPeerPhotoFileLocation | inputStickerSetThumb;
|
||||
export type InputFileLocation = inputFileLocation | inputDocumentFileLocation | inputPhotoFileLocation | inputPeerPhotoFileLocation | inputStickerSetThumb;
|
||||
|
||||
export type WorkerTaskTemplate = {
|
||||
type: string,
|
||||
id: number,
|
||||
payload: any
|
||||
};
|
@ -64,9 +64,7 @@ module.exports = merge(common, {
|
||||
files.forEach(file => {
|
||||
//console.log('to unlink 1:', file);
|
||||
|
||||
if(file.includes('mitm.')
|
||||
|| file.includes('sw.js')
|
||||
|| file.includes('.xml')
|
||||
if(file.includes('.xml')
|
||||
|| file.includes('.webmanifest')
|
||||
|| file.includes('.wasm')
|
||||
|| file.includes('rlottie')
|
||||
|
Loading…
x
Reference in New Issue
Block a user