Fallback to regular worker due to instability
More fixes WebP stickers fix
This commit is contained in:
parent
e8073991a0
commit
5d0f91c5f1
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
14
public/sw.js
14
public/sw.js
File diff suppressed because one or more lines are too long
BIN
public/sw.js.gz
BIN
public/sw.js.gz
Binary file not shown.
|
@ -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;
|
|
@ -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;
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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…
Reference in New Issue