sticker thumbs & safari open chat fix & online blink fix & login added idle monkey & webp safari save to indexeddb & lazyload queue reorder & minor fixes
This commit is contained in:
parent
7642105c1b
commit
22ceba971d
@ -56,7 +56,6 @@ class AppForward {
|
||||
this.cleanup();
|
||||
this.msgIDs = ids;
|
||||
|
||||
appSidebarRight.toggleSidebar(true);
|
||||
this.container.classList.add('active');
|
||||
this.sendBtn.innerHTML = '';
|
||||
this.sendBtn.classList.add('tgico-send');
|
||||
@ -68,6 +67,9 @@ class AppForward {
|
||||
} else {
|
||||
this.sendBtn.classList.remove('is-visible');
|
||||
}
|
||||
}, 'dialogs', () => {
|
||||
console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount);
|
||||
appSidebarRight.toggleSidebar(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class AppSelectPeers {
|
||||
private query = '';
|
||||
private cachedContacts: number[];
|
||||
|
||||
constructor(private appendTo: HTMLDivElement, private onChange?: (length: number) => void, private peerType: 'contacts' | 'dialogs' = 'dialogs') {
|
||||
constructor(private appendTo: HTMLDivElement, private onChange?: (length: number) => void, private peerType: 'contacts' | 'dialogs' = 'dialogs', onFirstRender?: () => void) {
|
||||
this.container.classList.add('selector');
|
||||
|
||||
let topContainer = document.createElement('div');
|
||||
@ -107,12 +107,18 @@ export class AppSelectPeers {
|
||||
this.container.append(topContainer, delimiter, this.chatsContainer);
|
||||
appendTo.append(this.container);
|
||||
|
||||
this.getMoreResults();
|
||||
let getResultsPromise = this.getMoreResults() as Promise<any>;
|
||||
if(onFirstRender) {
|
||||
getResultsPromise.then(() => {
|
||||
onFirstRender();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getMoreDialogs() {
|
||||
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
|
||||
appMessagesManager.getConversations(this.offsetIndex, 50, 0).then(value => {
|
||||
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
|
||||
return appMessagesManager.getConversations(this.offsetIndex, pageCount, 0).then(value => {
|
||||
let dialogs = value.dialogs;
|
||||
|
||||
let newOffsetIndex = dialogs[value.dialogs.length - 1].index || 0;
|
||||
@ -150,9 +156,9 @@ export class AppSelectPeers {
|
||||
|
||||
private getMoreResults() {
|
||||
if(this.peerType == 'dialogs') {
|
||||
this.getMoreDialogs();
|
||||
return this.getMoreDialogs();
|
||||
} else {
|
||||
this.getMoreContacts();
|
||||
return this.getMoreContacts();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import lottieLoader from "../lib/lottieLoader";
|
||||
import { Layouter, RectPart } from "./groupedLayout";
|
||||
|
||||
export class ChatInput {
|
||||
public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
|
||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
public messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */;
|
||||
public fileInput = document.getElementById('input-file') as HTMLInputElement;
|
||||
public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement;
|
||||
@ -463,6 +463,7 @@ export class ChatInput {
|
||||
this.emoticonsDropdown.classList.remove('active');
|
||||
this.toggleEmoticons.classList.remove('active');
|
||||
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
this.emoticonsLazyLoadQueue.lock();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@ -471,6 +472,7 @@ export class ChatInput {
|
||||
};
|
||||
} else {
|
||||
this.emoticonsDropdown.classList.add('active');
|
||||
this.emoticonsLazyLoadQueue.unlock();
|
||||
}
|
||||
|
||||
this.toggleEmoticons.classList.add('active');
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { AppImManager } from "../lib/appManagers/appImManager";
|
||||
import { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
|
||||
import { horizontalMenu } from "./misc";
|
||||
import { horizontalMenu, renderImageFromUrl } from "./misc";
|
||||
import lottieLoader from "../lib/lottieLoader";
|
||||
//import Scrollable from "./scrollable";
|
||||
import Scrollable from "./scrollable_new";
|
||||
@ -12,7 +12,6 @@ import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
//import CryptoWorker from '../lib/crypto/cryptoworker';
|
||||
import LazyLoadQueue from "./lazyLoadQueue";
|
||||
import { MTDocument, wrapSticker } from "./wrappers";
|
||||
import appWebpManager from "../lib/appManagers/appWebpManager";
|
||||
import appDocsManager from "../lib/appManagers/appDocsManager";
|
||||
import ProgressivePreloader from "./preloader";
|
||||
import Config from "../lib/config";
|
||||
@ -29,15 +28,18 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
|
||||
|
||||
let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement;
|
||||
let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement;
|
||||
let tabID = -1;
|
||||
horizontalMenu(tabs, container, (id) => {
|
||||
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
if(id == 1 && stickersInit) {
|
||||
tabID = id;
|
||||
}, () => {
|
||||
if(tabID == 1 && stickersInit) {
|
||||
stickersInit();
|
||||
} else if(id == 2 && gifsInit) {
|
||||
} else if(tabID == 2 && gifsInit) {
|
||||
gifsInit();
|
||||
}
|
||||
}, () => {
|
||||
|
||||
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
|
||||
@ -347,15 +349,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
|
||||
stickersInit = null;
|
||||
|
||||
Promise.all([
|
||||
apiManager.invokeApi('messages.getRecentStickers', {flags: 0, hash: 0}).then((res) => {
|
||||
let stickers: {
|
||||
_: string,
|
||||
hash: number,
|
||||
packs: any[],
|
||||
stickers: MTDocument[],
|
||||
dates: number[]
|
||||
} = res as any;
|
||||
|
||||
appStickersManager.getRecentStickers().then(stickers => {
|
||||
let categoryDiv = document.createElement('div');
|
||||
categoryDiv.classList.add('sticker-category');
|
||||
|
||||
@ -408,8 +402,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
|
||||
reader.readAsArrayBuffer(blob);
|
||||
} else {
|
||||
let image = new Image();
|
||||
//image.src = URL.createObjectURL(blob);
|
||||
appWebpManager.polyfillImage(image, blob);
|
||||
renderImageFromUrl(image, URL.createObjectURL(blob));
|
||||
|
||||
li.append(image);
|
||||
}
|
||||
|
@ -19,16 +19,18 @@ export default class LazyLoadQueue {
|
||||
|
||||
constructor(private parallelLimit = 5) {
|
||||
this.observer = new IntersectionObserver(entries => {
|
||||
if(this.lockPromise) return;
|
||||
|
||||
for(let entry of entries) {
|
||||
if(entry.isIntersecting) {
|
||||
let target = entry.target as HTMLElement;
|
||||
|
||||
for(let item of this.lazyLoadMedia) {
|
||||
if(item.div == target) {
|
||||
item.wasSeen = true;
|
||||
this.processQueue(item);
|
||||
break;
|
||||
}
|
||||
// need for set element first if scrolled
|
||||
let item = this.lazyLoadMedia.findAndSplice(i => i.div == target);
|
||||
if(item) {
|
||||
item.wasSeen = true;
|
||||
this.lazyLoadMedia.unshift(item);
|
||||
this.processQueue(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,26 +121,29 @@ let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceEl
|
||||
else elem.style.backgroundImage = 'url(' + url + ')';
|
||||
};
|
||||
|
||||
export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string) {
|
||||
export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string): Promise<boolean> {
|
||||
if(loadedURLs[url]) {
|
||||
set(elem, url);
|
||||
return true;
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if(elem instanceof HTMLSourceElement) {
|
||||
elem.src = url;
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
let loader = new Image();
|
||||
loader.src = url;
|
||||
//let perf = performance.now();
|
||||
loader.addEventListener('load', () => {
|
||||
set(elem, url);
|
||||
loadedURLs[url] = true;
|
||||
//console.log('onload:', url, performance.now() - perf);
|
||||
return new Promise((resolve, reject) => {
|
||||
let loader = new Image();
|
||||
loader.src = url;
|
||||
//let perf = performance.now();
|
||||
loader.addEventListener('load', () => {
|
||||
set(elem, url);
|
||||
loadedURLs[url] = true;
|
||||
//console.log('onload:', url, performance.now() - perf);
|
||||
resolve(false);
|
||||
});
|
||||
loader.addEventListener('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function putPreloader(elem: Element, returnDiv = false) {
|
||||
|
@ -56,6 +56,9 @@ export default class Scrollable {
|
||||
private lastBottomID = 0;
|
||||
private lastScrollDirection = 0; // true = bottom
|
||||
|
||||
private onScrolledTopFired = false;
|
||||
private onScrolledBottomFired = false;
|
||||
|
||||
public scrollLocked = 0;
|
||||
|
||||
private setVisible(element: HTMLElement) {
|
||||
@ -184,37 +187,56 @@ export default class Scrollable {
|
||||
//this.onScroll();
|
||||
}
|
||||
|
||||
public attachSentinels(container = this.container, offset = this.onScrollOffset) {
|
||||
if(!this.sentinelsObserver) {
|
||||
this.topSentinel = document.createElement('div');
|
||||
this.topSentinel.classList.add('scrollable-sentinel');
|
||||
this.topSentinel.style.top = offset + 'px';
|
||||
this.bottomSentinel = document.createElement('div');
|
||||
this.bottomSentinel.classList.add('scrollable-sentinel');
|
||||
this.bottomSentinel.style.bottom = offset + 'px';
|
||||
|
||||
this.container.append(this.topSentinel, this.bottomSentinel);
|
||||
|
||||
this.sentinelsObserver = new IntersectionObserver(entries => {
|
||||
for(let entry of entries) {
|
||||
if(entry.isIntersecting) {
|
||||
let top = entry.target == this.topSentinel;
|
||||
if(top) {
|
||||
this.onScrolledTop && this.onScrolledTop();
|
||||
} else {
|
||||
this.onScrolledBottom && this.onScrolledBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.sentinelsObserver.observe(this.topSentinel);
|
||||
this.sentinelsObserver.observe(this.bottomSentinel);
|
||||
}
|
||||
// public attachSentinels(container = this.container, offset = this.onScrollOffset) {
|
||||
// if(!this.sentinelsObserver) {
|
||||
// this.topSentinel = document.createElement('div');
|
||||
// this.topSentinel.classList.add('scrollable-sentinel');
|
||||
// this.topSentinel.style.top = offset + 'px';
|
||||
// this.bottomSentinel = document.createElement('div');
|
||||
// this.bottomSentinel.classList.add('scrollable-sentinel');
|
||||
// this.bottomSentinel.style.bottom = offset + 'px';
|
||||
|
||||
container.prepend(this.topSentinel);
|
||||
container.append(this.bottomSentinel);
|
||||
}
|
||||
// this.container.append(this.topSentinel, this.bottomSentinel);
|
||||
|
||||
// //let fire: () => void;
|
||||
|
||||
// this.sentinelsObserver = new IntersectionObserver(entries => {
|
||||
// for(let entry of entries) {
|
||||
// let top = entry.target == this.topSentinel;
|
||||
// if(top) {
|
||||
// this.onScrolledTopFired = entry.isIntersecting;
|
||||
// } else {
|
||||
// this.onScrolledBottomFired = entry.isIntersecting;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /* this.debug && */this.log('Set onScrolledFires:', this.onScrolledTopFired, this.onScrolledBottomFired);
|
||||
|
||||
// /* if((this.onScrolledTopFired || this.onScrolledBottomFired) && !fire) {
|
||||
// fire = () => window.requestAnimationFrame(() => {
|
||||
// if(!this.scrollLocked) {
|
||||
// if(this.onScrolledTopFired && this.onScrolledTop) this.onScrolledTop();
|
||||
// if(this.onScrolledBottomFired && this.onScrolledBottom) this.onScrolledBottom();
|
||||
// }
|
||||
|
||||
// if(!this.onScrolledTopFired && !this.onScrolledBottomFired) {
|
||||
// fire = undefined;
|
||||
// } else {
|
||||
// fire();
|
||||
// }
|
||||
// });
|
||||
|
||||
// fire();
|
||||
// } */
|
||||
// });
|
||||
|
||||
// this.sentinelsObserver.observe(this.topSentinel);
|
||||
// this.sentinelsObserver.observe(this.bottomSentinel);
|
||||
// }
|
||||
|
||||
// container.prepend(this.topSentinel);
|
||||
// container.append(this.bottomSentinel);
|
||||
// }
|
||||
|
||||
public setVirtualContainer(el?: HTMLElement) {
|
||||
this.splitUp = el;
|
||||
@ -231,7 +253,7 @@ export default class Scrollable {
|
||||
/* if(this.debug) {
|
||||
this.log('onScroll call', this.onScrollMeasure);
|
||||
} */
|
||||
|
||||
|
||||
let appendTo = this.splitUp || this.appendTo;
|
||||
|
||||
clearTimeout(this.disableHoverTimeout);
|
||||
@ -246,8 +268,13 @@ export default class Scrollable {
|
||||
this.lastScrollDirection = 0;
|
||||
}, 100);
|
||||
|
||||
if(!this.splitUp || this.onScrollMeasure) return;
|
||||
if(this.onScrollMeasure) return;
|
||||
this.onScrollMeasure = window.requestAnimationFrame(() => {
|
||||
this.checkForTriggers();
|
||||
|
||||
this.onScrollMeasure = 0;
|
||||
if(!this.splitUp) return;
|
||||
|
||||
let scrollTop = this.container.scrollTop;
|
||||
if(this.lastScrollTop != scrollTop) {
|
||||
this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1;
|
||||
@ -255,10 +282,24 @@ export default class Scrollable {
|
||||
} else {
|
||||
this.lastScrollDirection = 0;
|
||||
}
|
||||
this.onScrollMeasure = 0;
|
||||
});
|
||||
}
|
||||
|
||||
public checkForTriggers() {
|
||||
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
|
||||
|
||||
let scrollTop = this.container.scrollTop;
|
||||
let maxScrollTop = this.container.scrollHeight - this.container.clientHeight;
|
||||
|
||||
if(this.onScrolledTop && scrollTop <= this.onScrollOffset) {
|
||||
this.onScrolledTop();
|
||||
}
|
||||
|
||||
if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset) {
|
||||
this.onScrolledBottom();
|
||||
}
|
||||
}
|
||||
|
||||
public reorder() {
|
||||
(Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => {
|
||||
el.dataset.virtual = '' + idx;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import appPhotosManager, { MTPhoto } from '../lib/appManagers/appPhotosManager';
|
||||
import appPhotosManager from '../lib/appManagers/appPhotosManager';
|
||||
//import CryptoWorker from '../lib/crypto/cryptoworker';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
import LottieLoader from '../lib/lottieLoader';
|
||||
@ -8,7 +8,6 @@ import { formatBytes } from "../lib/utils";
|
||||
import ProgressivePreloader from './preloader';
|
||||
import LazyLoadQueue from './lazyLoadQueue';
|
||||
import apiFileManager from '../lib/mtproto/apiFileManager';
|
||||
import appWebpManager from '../lib/appManagers/appWebpManager';
|
||||
import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer';
|
||||
import { RichTextProcessor } from '../lib/richtextprocessor';
|
||||
import { CancellablePromise } from '../lib/polyfill';
|
||||
@ -16,6 +15,7 @@ import { renderImageFromUrl } from './misc';
|
||||
import appMessagesManager from '../lib/appManagers/appMessagesManager';
|
||||
import { Layouter, RectPart } from './groupedLayout';
|
||||
import PollElement from './poll';
|
||||
import appWebpManager from '../lib/appManagers/appWebpManager';
|
||||
|
||||
export type MTDocument = {
|
||||
_: 'document' | 'documentEmpty',
|
||||
@ -40,15 +40,15 @@ export type MTDocument = {
|
||||
duration?: number,
|
||||
downloaded?: boolean,
|
||||
url?: string,
|
||||
version?: any,
|
||||
|
||||
audioTitle?: string,
|
||||
audioPerformer?: string,
|
||||
|
||||
sticker?: boolean,
|
||||
sticker?: number,
|
||||
stickerEmoji?: string,
|
||||
stickerEmojiRaw?: string,
|
||||
stickerSetInput?: any,
|
||||
stickerThumbConverted?: true,
|
||||
|
||||
animated?: boolean
|
||||
};
|
||||
@ -74,48 +74,39 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
middleware: () => boolean,
|
||||
lazyLoadQueue: LazyLoadQueue
|
||||
}) {
|
||||
let img: HTMLImageElement | SVGImageElement;
|
||||
|
||||
let img: HTMLImageElement;
|
||||
if(withTail) {
|
||||
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut);
|
||||
} else if(!boxWidth && !boxHeight) { // album
|
||||
let sizes = doc.thumbs;
|
||||
if(!doc.downloaded && sizes && sizes[0].bytes) {
|
||||
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
|
||||
}
|
||||
|
||||
img = container.firstElementChild as HTMLImageElement || new Image();
|
||||
|
||||
if(!container.contains(img)) {
|
||||
container.append(img);
|
||||
}
|
||||
} else {
|
||||
if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) {
|
||||
let size = appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight);
|
||||
if(!boxWidth && !boxHeight) { // album
|
||||
let sizes = doc.thumbs;
|
||||
if(!doc.downloaded && sizes && sizes[0].bytes) {
|
||||
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
|
||||
}
|
||||
} else {
|
||||
if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) {
|
||||
appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
img = container.firstElementChild as HTMLImageElement || new Image();
|
||||
|
||||
if(!container.contains(img)) {
|
||||
container.append(img);
|
||||
|
||||
img = container.lastElementChild as HTMLImageElement;
|
||||
if(!img || img.tagName != 'IMG') {
|
||||
container.append(img = new Image());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let video = document.createElement('video');
|
||||
if(withTail) {
|
||||
let foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject');
|
||||
let width = img.getAttributeNS(null, 'width');
|
||||
let height = img.getAttributeNS(null, 'height');
|
||||
foreignObject.setAttributeNS(null, 'width', width);
|
||||
foreignObject.setAttributeNS(null, 'height', height);
|
||||
video.width = +width;
|
||||
video.height = +height;
|
||||
foreignObject.append(video);
|
||||
img.parentElement.append(foreignObject);
|
||||
}
|
||||
|
||||
let source = document.createElement('source');
|
||||
video.append(source);
|
||||
|
||||
if(withTail) {
|
||||
let foreignObject = img.parentElement;
|
||||
video.width = +foreignObject.getAttributeNS(null, 'width');
|
||||
video.height = +foreignObject.getAttributeNS(null, 'height');
|
||||
foreignObject.append(video);
|
||||
} else {
|
||||
container.append(video);
|
||||
}
|
||||
|
||||
let span: HTMLSpanElement;
|
||||
if(doc.type != 'round') {
|
||||
@ -154,12 +145,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
source.type = doc.mime_type;
|
||||
video.append(source);
|
||||
|
||||
if(!withTail) {
|
||||
if(img && container.contains(img)) {
|
||||
container.removeChild(img);
|
||||
}
|
||||
|
||||
container.append(video);
|
||||
if(img && img.parentElement) {
|
||||
img.remove();
|
||||
}
|
||||
|
||||
if(doc.type == 'gif') {
|
||||
@ -593,14 +580,16 @@ export function wrapVoiceMessage(doc: MTDocument, withTime = false): HTMLDivElem
|
||||
function wrapMediaWithTail(photo: any, message: {mid: number, message: string}, container: HTMLDivElement, boxWidth: number, boxHeight: number, isOut: boolean) {
|
||||
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.classList.add('bubble__media-container', isOut ? 'is-out' : 'is-in');
|
||||
|
||||
let foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject');
|
||||
|
||||
appPhotosManager.setAttachmentSize(photo._ == 'document' ? photo : photo.id, foreignObject, boxWidth, boxHeight);
|
||||
|
||||
let width = +foreignObject.getAttributeNS(null, 'width');
|
||||
let height = +foreignObject.getAttributeNS(null, 'height');
|
||||
|
||||
let image = document.createElementNS("http://www.w3.org/2000/svg", "image");
|
||||
svg.append(image);
|
||||
|
||||
let size = appPhotosManager.setAttachmentSize(photo._ == 'document' ? photo : photo.id, svg, boxWidth, boxHeight);
|
||||
|
||||
let width = +svg.getAttributeNS(null, 'width');
|
||||
let height = +svg.getAttributeNS(null, 'height');
|
||||
svg.setAttributeNS(null, 'width', '' + width);
|
||||
svg.setAttributeNS(null, 'height', '' + height);
|
||||
|
||||
let clipID = 'clip' + message.mid;
|
||||
svg.dataset.clipID = clipID;
|
||||
@ -626,36 +615,38 @@ function wrapMediaWithTail(photo: any, message: {mid: number, message: string},
|
||||
|
||||
defs.innerHTML = `<clipPath id="${clipID}">${clipPathHTML}</clipPath>`;
|
||||
|
||||
svg.prepend(defs);
|
||||
container.appendChild(svg);
|
||||
container.style.width = parseInt(container.style.width) - 9 + 'px';
|
||||
|
||||
return image;
|
||||
svg.append(defs, foreignObject);
|
||||
container.append(svg);
|
||||
|
||||
let img = foreignObject.firstElementChild as HTMLImageElement;
|
||||
if(!img) {
|
||||
foreignObject.append(img = new Image());
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
export function wrapPhoto(photoID: string, message: any, container: HTMLDivElement, boxWidth = 380, boxHeight = 380, withTail = true, isOut = false, lazyLoadQueue: LazyLoadQueue, middleware: () => boolean, size: MTPhotoSize = null) {
|
||||
let photo = appPhotosManager.getPhoto(photoID);
|
||||
|
||||
let image: SVGImageElement | HTMLImageElement;
|
||||
let image: HTMLImageElement;
|
||||
if(withTail) {
|
||||
image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut);
|
||||
} else if(size) { // album
|
||||
let sizes = photo.sizes;
|
||||
if(!photo.downloaded && sizes && sizes[0].bytes) {
|
||||
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
|
||||
} else {
|
||||
if(size) { // album
|
||||
let sizes = photo.sizes;
|
||||
if(!photo.downloaded && sizes && sizes[0].bytes) {
|
||||
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
|
||||
}
|
||||
} else if(boxWidth && boxHeight) { // means webpage's preview
|
||||
size = appPhotosManager.setAttachmentSize(photoID, container, boxWidth, boxHeight, false);
|
||||
}
|
||||
|
||||
image = container.firstElementChild as HTMLImageElement || new Image();
|
||||
|
||||
if(!container.contains(image)) {
|
||||
container.appendChild(image);
|
||||
}
|
||||
} else if(boxWidth && boxHeight) { // means webpage's preview
|
||||
size = appPhotosManager.setAttachmentSize(photoID, container, boxWidth, boxHeight, false);
|
||||
|
||||
image = container.firstElementChild as HTMLImageElement || new Image();
|
||||
|
||||
if(!container.contains(image)) {
|
||||
container.appendChild(image);
|
||||
image = container.lastElementChild as HTMLImageElement;
|
||||
if(!image || image.tagName != 'IMG') {
|
||||
container.append(image = new Image());
|
||||
}
|
||||
}
|
||||
|
||||
@ -694,7 +685,7 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme
|
||||
}
|
||||
|
||||
export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: () => boolean, lazyLoadQueue?: LazyLoadQueue, group?: string, canvas?: boolean, play = false, onlyThumb = false) {
|
||||
let stickerType = doc.mime_type == "application/x-tgsticker" ? 2 : (doc.mime_type == "image/webp" ? 1 : 0);
|
||||
let stickerType = doc.sticker;
|
||||
|
||||
if(stickerType == 2 && !LottieLoader.loaded) {
|
||||
LottieLoader.loadLottie();
|
||||
@ -702,8 +693,10 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
|
||||
|
||||
if(!stickerType) {
|
||||
console.error('wrong doc for wrapSticker!', doc);
|
||||
return Promise.resolve();
|
||||
throw new Error('wrong doc for wrapSticker!');
|
||||
}
|
||||
|
||||
div.dataset.docID = doc.id;
|
||||
|
||||
//console.log('wrap sticker', doc, div, onlyThumb);
|
||||
|
||||
@ -713,32 +706,61 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
|
||||
//console.log('wrap sticker', thumb, div);
|
||||
|
||||
if(thumb.bytes) {
|
||||
apiFileManager.saveSmallFile(thumb.location, thumb.bytes);
|
||||
let img = new Image();
|
||||
|
||||
if(appWebpManager.isSupported() || doc.stickerThumbConverted) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true));
|
||||
|
||||
div.append(img);
|
||||
} else {
|
||||
appWebpManager.convertToPng(thumb.bytes).then(bytes => {
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
thumb.bytes = bytes;
|
||||
doc.stickerThumbConverted = true;
|
||||
|
||||
if(!div.childElementCount) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)).then(() => {
|
||||
div.append(img);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
appPhotosManager.setAttachmentPreview(thumb.bytes, div, true);
|
||||
if(onlyThumb) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} else if(!onlyThumb && stickerType == 2) {
|
||||
let img = new Image();
|
||||
let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => {
|
||||
if(!img.parentElement || (middleware && !middleware())) return;
|
||||
let promise = renderImageFromUrl(img, url);
|
||||
|
||||
if(!downloaded) {
|
||||
promise.then(() => {
|
||||
div.append(img);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if(onlyThumb) return Promise.resolve();
|
||||
let downloaded = appDocsManager.hasDownloadedThumb(doc.id, thumb.type);
|
||||
if(downloaded) {
|
||||
div.append(img);
|
||||
}
|
||||
|
||||
lazyLoadQueue && !downloaded ? lazyLoadQueue.push({div, load}) : load();
|
||||
}
|
||||
}
|
||||
|
||||
if(onlyThumb && doc.thumbs) {
|
||||
|
||||
if(onlyThumb && doc.thumbs) { // for sticker panel
|
||||
let thumb = doc.thumbs[0];
|
||||
|
||||
let load = () => apiFileManager.downloadSmallFile({
|
||||
_: 'inputDocumentFileLocation',
|
||||
access_hash: doc.access_hash,
|
||||
file_reference: doc.file_reference,
|
||||
thumb_size: thumb.type,
|
||||
id: doc.id
|
||||
}, {dcID: doc.dc_id}).then(blob => {
|
||||
let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => {
|
||||
let img = new Image();
|
||||
|
||||
appWebpManager.polyfillImage(img, blob);
|
||||
|
||||
div.append(img);
|
||||
|
||||
div.dataset.docID = doc.id;
|
||||
appStickersManager.saveSticker(doc);
|
||||
renderImageFromUrl(img, url).then(() => {
|
||||
if(middleware && !middleware()) return;
|
||||
div.append(img);
|
||||
});
|
||||
});
|
||||
|
||||
return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load();
|
||||
@ -748,10 +770,8 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
|
||||
let load = () => appDocsManager.downloadDoc(doc.id).then(blob => {
|
||||
//console.log('loaded sticker:', blob, div);
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
/* if(div.firstElementChild) {
|
||||
div.firstElementChild.remove();
|
||||
} */
|
||||
|
||||
//return;
|
||||
|
||||
if(stickerType == 2) {
|
||||
const reader = new FileReader();
|
||||
@ -802,15 +822,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
|
||||
}, {once: true});
|
||||
}
|
||||
});
|
||||
} /* else {
|
||||
let canvas = div.firstElementChild as HTMLCanvasElement;
|
||||
if(!canvas.width && !canvas.height) {
|
||||
console.log('Need lottie resize');
|
||||
|
||||
// @ts-ignore
|
||||
animation.resize();
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
if(play) {
|
||||
animation.play();
|
||||
@ -831,23 +843,14 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
|
||||
});
|
||||
}
|
||||
|
||||
if(!doc.url) {
|
||||
appWebpManager.polyfillImage(img, blob).then((url) => {
|
||||
doc.url = url;
|
||||
|
||||
if(div.firstElementChild && div.firstElementChild != img) {
|
||||
div.firstElementChild.remove();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
img.src = doc.url;
|
||||
}
|
||||
renderImageFromUrl(img, doc.url).then(() => {
|
||||
if(div.firstElementChild && div.firstElementChild != img) {
|
||||
div.firstElementChild.remove();
|
||||
}
|
||||
|
||||
div.append(img);
|
||||
div.append(img);
|
||||
});
|
||||
}
|
||||
|
||||
div.dataset.docID = doc.id;
|
||||
appStickersManager.saveSticker(doc);
|
||||
});
|
||||
|
||||
return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}), Promise.resolve()) : load();
|
||||
|
@ -3,6 +3,7 @@ import { RichTextProcessor } from "../richtextprocessor";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import apiUpdatesManager from "./apiUpdatesManager";
|
||||
import appProfileManager from "./appProfileManager";
|
||||
|
||||
type Channel = {
|
||||
_: 'channel',
|
||||
@ -42,6 +43,8 @@ export class AppChatsManager {
|
||||
public megagroups: any = {};
|
||||
public cachedPhotoLocations: any = {};
|
||||
|
||||
public megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}} = {};
|
||||
|
||||
constructor() {
|
||||
$rootScope.$on('apiUpdate', (e: CustomEvent) => {
|
||||
// console.log('on apiUpdate', update)
|
||||
@ -130,7 +133,7 @@ export class AppChatsManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
var chat = this.getChat(id);
|
||||
let chat = this.getChat(id);
|
||||
if(chat._ == 'chatForbidden' ||
|
||||
chat._ == 'channelForbidden' ||
|
||||
chat.pFlags.kicked ||
|
||||
@ -191,7 +194,7 @@ export class AppChatsManager {
|
||||
}
|
||||
|
||||
public isChannel(id: number) {
|
||||
var chat = this.chats[id];
|
||||
let chat = this.chats[id];
|
||||
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') || this.channelAccess[id]) {
|
||||
return true;
|
||||
}
|
||||
@ -203,7 +206,7 @@ export class AppChatsManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
var chat = this.chats[id];
|
||||
let chat = this.chats[id];
|
||||
if(chat && chat._ == 'channel' && chat.pFlags.megagroup) {
|
||||
return true;
|
||||
}
|
||||
@ -246,12 +249,12 @@ export class AppChatsManager {
|
||||
}
|
||||
|
||||
public hasChat(id: number, allowMin?: any) {
|
||||
var chat = this.chats[id]
|
||||
let chat = this.chats[id]
|
||||
return isObject(chat) && (allowMin || !chat.pFlags.min);
|
||||
}
|
||||
|
||||
public getChatPhoto(id: number) {
|
||||
var chat = this.getChat(id);
|
||||
let chat = this.getChat(id);
|
||||
|
||||
if(this.cachedPhotoLocations[id] === undefined) {
|
||||
this.cachedPhotoLocations[id] = chat && chat.photo ? chat.photo : {empty: true};
|
||||
@ -261,7 +264,7 @@ export class AppChatsManager {
|
||||
}
|
||||
|
||||
public getChatString(id: number) {
|
||||
var chat = this.getChat(id);
|
||||
let chat = this.getChat(id);
|
||||
if(this.isChannel(id)) {
|
||||
return (this.isMegagroup(id) ? 's' : 'c') + id + '_' + chat.access_hash;
|
||||
}
|
||||
@ -277,8 +280,8 @@ export class AppChatsManager {
|
||||
}
|
||||
|
||||
public wrapForFull(id: number, fullChat: any) {
|
||||
var chatFull = copy(fullChat);
|
||||
var chat = this.getChat(id);
|
||||
let chatFull = copy(fullChat);
|
||||
let chat = this.getChat(id);
|
||||
|
||||
if(!chatFull.participants_count) {
|
||||
chatFull.participants_count = chat.participants_count;
|
||||
@ -300,10 +303,10 @@ export class AppChatsManager {
|
||||
}
|
||||
|
||||
public wrapParticipants(id: number, participants: any[]) {
|
||||
var chat = this.getChat(id);
|
||||
var myID = appUsersManager.getSelf().id;
|
||||
let chat = this.getChat(id);
|
||||
let myID = appUsersManager.getSelf().id;
|
||||
if(this.isChannel(id)) {
|
||||
var isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator;
|
||||
let isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator;
|
||||
participants.forEach((participant) => {
|
||||
participant.canLeave = myID == participant.user_id;
|
||||
participant.canKick = isAdmin && participant._ == 'channelParticipant';
|
||||
@ -312,7 +315,7 @@ export class AppChatsManager {
|
||||
participant.user = appUsersManager.getUser(participant.user_id);
|
||||
});
|
||||
} else {
|
||||
var isAdmin = chat.pFlags.creator || chat.pFlags.admins_enabled && chat.pFlags.admin;
|
||||
let isAdmin = chat.pFlags.creator || chat.pFlags.admins_enabled && chat.pFlags.admin;
|
||||
participants.forEach((participant) => {
|
||||
participant.canLeave = myID == participant.user_id;
|
||||
participant.canKick = !participant.canLeave && (
|
||||
@ -388,6 +391,44 @@ export class AppChatsManager {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async getOnlines(id: number): Promise<number> {
|
||||
if(this.isMegagroup(id)) {
|
||||
let timestamp = Date.now() / 1000 | 0;
|
||||
let cached = this.megagroupOnlines[id] ?? (this.megagroupOnlines[id] = {timestamp: 0, onlines: 1});
|
||||
if((timestamp - cached.timestamp) < 60) {
|
||||
return cached.onlines;
|
||||
}
|
||||
|
||||
let res = await apiManager.invokeApi('messages.getOnlines', {
|
||||
peer: this.getChannelInputPeer(id)
|
||||
});
|
||||
|
||||
let onlines = res.onlines ?? 1;
|
||||
cached.timestamp = timestamp;
|
||||
cached.onlines = onlines;
|
||||
|
||||
return onlines;
|
||||
} else if(this.isBroadcast(id)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let chatInfo = appProfileManager.getChatFull(id);
|
||||
if(chatInfo._ == 'chatFull' && chatInfo.participants && chatInfo.participants.participants) {
|
||||
let participants = chatInfo.participants.participants;
|
||||
|
||||
return participants.reduce((acc: number, participant: any) => {
|
||||
let user = appUsersManager.getUser(participant.user_id);
|
||||
if(user && user.status && user.status._ == 'userStatusOnline') {
|
||||
return acc + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, 0);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppChatsManager();
|
||||
|
@ -409,12 +409,12 @@ export class AppDialogsManager {
|
||||
this.scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500);
|
||||
this.scroll.setVirtualContainer(this.chatList);
|
||||
this.scroll.onScrolledBottom = this.onChatsScroll.bind(this);
|
||||
this.scroll.attachSentinels();
|
||||
//this.scroll.attachSentinels();
|
||||
|
||||
this.scrollArchived = new Scrollable(this.chatsArchivedContainer, 'y', 'CLA', this.chatListArchived, 500);
|
||||
this.scrollArchived.setVirtualContainer(this.chatListArchived);
|
||||
this.scrollArchived.onScrolledBottom = this.onChatsArchivedScroll.bind(this);
|
||||
this.scroll.attachSentinels();
|
||||
///this.scroll.attachSentinels();
|
||||
|
||||
this.setListClickListener(this.chatList);
|
||||
this.setListClickListener(this.chatListArchived);
|
||||
@ -828,7 +828,7 @@ export class AppDialogsManager {
|
||||
dom.lastTimeSpan.innerHTML = timeStr;
|
||||
} else dom.lastTimeSpan.innerHTML = '';
|
||||
|
||||
if(this.doms[peerID] || this.domsArchived[peerID]) {
|
||||
if((this.doms[peerID] || this.domsArchived[peerID]) == dom) {
|
||||
this.setUnreadMessages(dialog);
|
||||
} else { // means search
|
||||
dom.listEl.dataset.mid = lastMessage.mid;
|
||||
|
@ -7,8 +7,9 @@ import { isObject } from '../utils';
|
||||
|
||||
class AppDocsManager {
|
||||
private docs: {[docID: string]: MTDocument} = {};
|
||||
private thumbs: {[docIDAndSize: string]: Promise<string>} = {};
|
||||
|
||||
public saveDoc(apiDoc: MTDocument/* any */, context?: any) {
|
||||
public saveDoc(apiDoc: MTDocument, context?: any) {
|
||||
//console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
|
||||
if(this.docs[apiDoc.id]) {
|
||||
let d = this.docs[apiDoc.id];
|
||||
@ -68,8 +69,6 @@ class AppDocsManager {
|
||||
break;
|
||||
|
||||
case 'documentAttributeSticker':
|
||||
apiDoc.sticker = true;
|
||||
|
||||
if(attribute.alt !== undefined) {
|
||||
apiDoc.stickerEmojiRaw = attribute.alt;
|
||||
apiDoc.stickerEmoji = RichTextProcessor.wrapRichText(apiDoc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true});
|
||||
@ -83,11 +82,13 @@ class AppDocsManager {
|
||||
}
|
||||
}
|
||||
|
||||
if(apiDoc.thumbs && apiDoc.mime_type == 'image/webp') {
|
||||
if(/* apiDoc.thumbs && */apiDoc.mime_type == 'image/webp') {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.sticker = 1;
|
||||
} else if(apiDoc.mime_type == 'application/x-tgsticker') {
|
||||
apiDoc.type = 'sticker';
|
||||
apiDoc.animated = true;
|
||||
apiDoc.sticker = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -145,7 +146,7 @@ class AppDocsManager {
|
||||
return isObject(docID) ? docID : this.docs[docID];
|
||||
}
|
||||
|
||||
public getInputByID(docID: any) {
|
||||
public getMediaInputByID(docID: any) {
|
||||
let doc = this.getDoc(docID);
|
||||
return {
|
||||
_: 'inputMediaDocument',
|
||||
@ -159,6 +160,18 @@ class AppDocsManager {
|
||||
ttl_seconds: 0
|
||||
};
|
||||
}
|
||||
|
||||
public getInputByID(docID: any, thumbSize?: string) {
|
||||
let doc = this.getDoc(docID);
|
||||
|
||||
return {
|
||||
_: 'inputDocumentFileLocation',
|
||||
id: doc.id,
|
||||
access_hash: doc.access_hash,
|
||||
file_reference: doc.file_reference,
|
||||
thumb_size: thumbSize
|
||||
};
|
||||
}
|
||||
|
||||
public getFileName(doc: MTDocument) {
|
||||
if(doc.file_name) {
|
||||
@ -173,42 +186,10 @@ class AppDocsManager {
|
||||
return 't_' + (doc.type || 'file') + doc.id + fileExt;
|
||||
}
|
||||
|
||||
public updateDocDownloaded(docID: string) {
|
||||
var doc = this.docs[docID];
|
||||
var inputFileLocation = {
|
||||
_: 'inputDocumentFileLocation',
|
||||
id: docID,
|
||||
access_hash: doc.access_hash,
|
||||
version: doc.version,
|
||||
file_name: this.getFileName(doc)
|
||||
};
|
||||
|
||||
if(doc.downloaded === undefined) {
|
||||
apiFileManager.getDownloadedFile(inputFileLocation, doc.size).then(() => {
|
||||
doc.downloaded = true;
|
||||
}, () => {
|
||||
doc.downloaded = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public downloadDoc(docID: string | MTDocument, toFileEntry?: any): CancellablePromise<Blob> {
|
||||
let doc: MTDocument;
|
||||
if(typeof(docID) === 'string') {
|
||||
doc = this.docs[docID];
|
||||
} else {
|
||||
doc = docID;
|
||||
}
|
||||
public downloadDoc(docID: any, toFileEntry?: any): CancellablePromise<Blob> {
|
||||
let doc = this.getDoc(docID);
|
||||
|
||||
var inputFileLocation = {
|
||||
_: 'inputDocumentFileLocation',
|
||||
id: doc.id,
|
||||
access_hash: doc.access_hash,
|
||||
file_reference: doc.file_reference,
|
||||
thumb_size: '',
|
||||
version: doc.version,
|
||||
file_name: this.getFileName(doc)
|
||||
};
|
||||
let inputFileLocation = this.getInputByID(doc);
|
||||
|
||||
if(doc._ == 'documentEmpty') {
|
||||
return Promise.reject();
|
||||
@ -217,26 +198,27 @@ class AppDocsManager {
|
||||
if(doc.downloaded && !toFileEntry) {
|
||||
if(doc.url) return Promise.resolve(null);
|
||||
|
||||
var cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
|
||||
let cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
|
||||
if(cachedBlob) {
|
||||
return Promise.resolve(cachedBlob);
|
||||
}
|
||||
}
|
||||
|
||||
//historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size};
|
||||
|
||||
|
||||
// нет смысла делать объект с выполняющимися промисами, нижняя строка и так вернёт загружающийся
|
||||
var downloadPromise: CancellablePromise<Blob> = apiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
|
||||
let downloadPromise: CancellablePromise<Blob> = apiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
|
||||
mimeType: doc.mime_type || 'application/octet-stream',
|
||||
toFileEntry: toFileEntry
|
||||
toFileEntry: toFileEntry,
|
||||
stickerType: doc.sticker
|
||||
});
|
||||
|
||||
|
||||
downloadPromise.then((blob) => {
|
||||
if(blob) {
|
||||
doc.downloaded = true;
|
||||
|
||||
if(/* !doc.animated || */doc.type && doc.type != 'sticker') {
|
||||
doc.url = FileManager.getFileCorrectUrl(blob, doc.mime_type);
|
||||
if(doc.type && doc.sticker != 2) {
|
||||
doc.url = URL.createObjectURL(blob);
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +248,36 @@ class AppDocsManager {
|
||||
|
||||
return downloadPromise;
|
||||
}
|
||||
|
||||
public downloadDocThumb(docID: any, thumbSize: string) {
|
||||
let doc = this.getDoc(docID);
|
||||
|
||||
let key = doc.id + '-' + thumbSize;
|
||||
if(this.thumbs[key]) {
|
||||
return this.thumbs[key];
|
||||
}
|
||||
|
||||
let input = this.getInputByID(doc, thumbSize);
|
||||
|
||||
if(doc._ == 'documentEmpty') {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
let mimeType = doc.sticker ? 'image/webp' : doc.mime_type;
|
||||
let promise = apiFileManager.downloadSmallFile(input, {
|
||||
dcID: doc.dc_id,
|
||||
stickerType: doc.sticker ? 1 : undefined,
|
||||
mimeType: mimeType
|
||||
});
|
||||
|
||||
return this.thumbs[key] = promise.then((blob) => {
|
||||
return URL.createObjectURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
public hasDownloadedThumb(docID: string, thumbSize: string) {
|
||||
return !!this.thumbs[docID + '-' + thumbSize];
|
||||
}
|
||||
|
||||
public async saveDocFile(docID: string) {
|
||||
var doc = this.docs[docID];
|
||||
|
@ -2,7 +2,7 @@
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack, whichChild } from "../utils";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import appMessagesManager, { Dialog } from "./appMessagesManager";
|
||||
import appMessagesManager from "./appMessagesManager";
|
||||
import appPeersManager from "./appPeersManager";
|
||||
import appProfileManager from "./appProfileManager";
|
||||
import appDialogsManager from "./appDialogsManager";
|
||||
@ -38,7 +38,7 @@ appSidebarLeft; // just to include
|
||||
let testScroll = false;
|
||||
|
||||
export class AppImManager {
|
||||
public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
|
||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
|
||||
public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement;
|
||||
public avatarEl = document.getElementById('im-avatar') as AvatarElement;
|
||||
@ -754,34 +754,22 @@ export class AppImManager {
|
||||
this.log('loadMoreHistory', top);
|
||||
if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return;
|
||||
|
||||
let history = Object.keys(this.bubbles).map(id => +id).sort((a, b) => a - b);
|
||||
// warning, если иды только отрицательные то вниз не попадёт (хотя мб и так не попадёт)
|
||||
let history = Object.keys(this.bubbles).map(id => +id).filter(id => id > 0).sort((a, b) => a - b);
|
||||
if(!history.length) return;
|
||||
|
||||
/* let history = appMessagesManager.historiesStorage[this.peerID].history;
|
||||
let length = history.length; */
|
||||
|
||||
// filter negative ids
|
||||
let lastBadIndex = -1;
|
||||
for(let i = 0; i < history.length; ++i) {
|
||||
if(history[i] <= 0) lastBadIndex = i;
|
||||
else break;
|
||||
}
|
||||
if(lastBadIndex != -1) {
|
||||
history = history.slice(lastBadIndex + 1);
|
||||
}
|
||||
|
||||
if(top && !this.scrolledAll) {
|
||||
this.log('Will load more (up) history by id:', history[0], 'maxID:', history[history.length - 1], history);
|
||||
/* if(history.length == 75) {
|
||||
this.log('load more', this.scrollable.scrollHeight, this.scrollable.scrollTop, this.scrollable);
|
||||
return;
|
||||
} */
|
||||
/* false && */this.getHistory(history[0], true);
|
||||
}
|
||||
|
||||
if(this.scrolledAllDown) return;
|
||||
|
||||
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
|
||||
/* if(!dialog) {
|
||||
this.log.warn('no dialog for load history');
|
||||
return;
|
||||
} */
|
||||
|
||||
// if scroll down after search
|
||||
if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)) {
|
||||
@ -822,14 +810,14 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
public setScroll() {
|
||||
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner);
|
||||
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, 300);
|
||||
this.scroll = this.scrollable.container;
|
||||
|
||||
this.bubblesContainer.append(this.goDownBtn);
|
||||
|
||||
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
|
||||
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
|
||||
this.scrollable.attachSentinels(undefined, 300);
|
||||
//this.scrollable.attachSentinels(undefined, 300);
|
||||
|
||||
this.scroll.addEventListener('scroll', this.onScroll.bind(this));
|
||||
this.scroll.parentElement.classList.add('scrolled-down');
|
||||
@ -850,30 +838,7 @@ export class AppImManager {
|
||||
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = '';
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
appPeersManager.isMegagroup(this.peerID) ? apiManager.invokeApi('messages.getOnlines', {
|
||||
peer: appPeersManager.getInputPeerByID(this.peerID)
|
||||
}) as Promise<any> : Promise.resolve(),
|
||||
// will redirect if wrong
|
||||
appProfileManager.getChatFull(chat.id)
|
||||
]).then(results => {
|
||||
let [chatOnlines, chatInfo] = results;
|
||||
|
||||
let onlines = chatOnlines ? chatOnlines.onlines : 1;
|
||||
|
||||
if(chatInfo._ == 'chatFull' && chatInfo.participants && chatInfo.participants.participants) {
|
||||
let participants = chatInfo.participants.participants;
|
||||
|
||||
onlines = participants.reduce((acc: number, participant: any) => {
|
||||
let user = appUsersManager.getUser(participant.user_id);
|
||||
if(user && user.status && user.status._ == 'userStatusOnline') {
|
||||
return acc + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
appProfileManager.getChatFull(chat.id).then((chatInfo: any) => {
|
||||
this.log('chatInfo res:', chatInfo);
|
||||
|
||||
if(chatInfo.pinned_msg_id) { // request pinned message
|
||||
@ -884,12 +849,17 @@ export class AppImManager {
|
||||
let participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants && chatInfo.participants.participants.length);
|
||||
if(participants_count) {
|
||||
let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
|
||||
|
||||
if(onlines > 1) {
|
||||
subtitle += ', ' + numberWithCommas(onlines) + ' online';
|
||||
}
|
||||
|
||||
|
||||
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle;
|
||||
|
||||
if(participants_count < 2) return;
|
||||
appChatsManager.getOnlines(chat.id).then(onlines => {
|
||||
if(onlines > 1) {
|
||||
subtitle += ', ' + numberWithCommas(onlines) + ' online';
|
||||
}
|
||||
|
||||
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if(!appUsersManager.isBot(this.peerID)) { // user
|
||||
@ -1071,7 +1041,7 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
this.scrollable.container.append(this.chatInner);
|
||||
this.scrollable.attachSentinels();
|
||||
//this.scrollable.attachSentinels();
|
||||
//this.scrollable.container.insertBefore(this.chatInner, this.scrollable.container.lastElementChild);
|
||||
|
||||
this.lazyLoadQueue.unlock();
|
||||
@ -1328,34 +1298,40 @@ export class AppImManager {
|
||||
|
||||
public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean) {
|
||||
let promises: Promise<any>[] = [];
|
||||
(Array.from(bubble.querySelectorAll('img, image, video')) as HTMLImageElement[]).forEach(el => {
|
||||
if(el.tagName == 'VIDEO') {
|
||||
(Array.from(bubble.querySelectorAll('img, video')) as HTMLImageElement[]).forEach(el => {
|
||||
if(el instanceof HTMLVideoElement) {
|
||||
let source = el.firstElementChild as HTMLSourceElement;
|
||||
if(!source || !source.src) {
|
||||
this.log.warn('no source', el, source, 'src', source.src);
|
||||
return;
|
||||
}
|
||||
} else if(el.complete || (!el.src && !el.getAttribute('href'))) return;
|
||||
} else if(el.readyState >= 4) return;
|
||||
} else if(el.complete || !el.src) return;
|
||||
|
||||
let src = el.src || el.getAttributeNS(null, 'href');
|
||||
let src = el.src;
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
if(el.tagName == 'VIDEO') {
|
||||
el.addEventListener('loadeddata', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
let r: () => boolean;
|
||||
let onLoad = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
|
||||
if(el instanceof HTMLVideoElement) {
|
||||
el.addEventListener('loadeddata', onLoad);
|
||||
r = () => el.readyState >= 4;
|
||||
} else {
|
||||
el.addEventListener('load', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
el.addEventListener('load', onLoad);
|
||||
r = () => el.complete;
|
||||
}
|
||||
|
||||
// for safari
|
||||
let c = () => r() ? onLoad() : window.requestAnimationFrame(c);
|
||||
window.requestAnimationFrame(c);
|
||||
|
||||
let timeout = setTimeout(() => {
|
||||
console.log('did not called', el, el.parentElement, el.complete, src);
|
||||
reject();
|
||||
}, 5000);
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
@ -2092,7 +2068,7 @@ export class AppImManager {
|
||||
avatarElem.setAttribute('peer-title', message.fwd_from.from_name);
|
||||
}
|
||||
|
||||
avatarElem.setAttribute('peer', '' + ((message.fwd_from ? message.fwdFromID : message.fromID) || 0));
|
||||
avatarElem.setAttribute('peer', '' + ((message.fwd_from && this.peerID == this.myID ? message.fwdFromID : message.fromID) || 0));
|
||||
|
||||
this.log('exec loadDialogPhoto', message);
|
||||
|
||||
@ -2151,43 +2127,28 @@ export class AppImManager {
|
||||
|
||||
console.time('appImManager render history');
|
||||
|
||||
let firstLoad = !!this.setPeerPromise && false;
|
||||
|
||||
let peerID = this.peerID;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
let method = (reverse ? history.shift : history.pop).bind(history);
|
||||
|
||||
let r = () => {
|
||||
if(this.peerID != peerID) {
|
||||
return reject('peer changed');
|
||||
}
|
||||
|
||||
let msgID = method();
|
||||
if(!msgID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = appMessagesManager.getMessage(msgID);
|
||||
this.renderMessage(message, reverse, true);
|
||||
|
||||
firstLoad ? window.requestAnimationFrame(r) : r();
|
||||
};
|
||||
|
||||
firstLoad ? window.requestAnimationFrame(r) : r();
|
||||
|
||||
let realLength = this.scrollable.length;
|
||||
let previousScrollHeightMinusTop: number;
|
||||
if(realLength > 0 && reverse) {
|
||||
this.messagesQueueOnRender = () => {
|
||||
let scrollTop = realLength ? this.scrollable.scrollTop : 0;
|
||||
previousScrollHeightMinusTop = realLength ? this.scrollable.scrollHeight - scrollTop : 0;
|
||||
let scrollTop = this.scrollable.scrollTop;
|
||||
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
|
||||
|
||||
this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop);
|
||||
this.messagesQueueOnRender = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
while(history.length) {
|
||||
let message = appMessagesManager.getMessage(method());
|
||||
this.renderMessage(message, reverse, true);
|
||||
}
|
||||
|
||||
(this.messagesQueuePromise || Promise.resolve()).then(() => {
|
||||
if(realLength > 0 && reverse && previousScrollHeightMinusTop !== undefined) {
|
||||
if(previousScrollHeightMinusTop !== undefined) {
|
||||
this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, previousScrollHeightMinusTop);
|
||||
this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop;
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ export class AppMediaViewer {
|
||||
|
||||
private reverse = false; // reverse means next = higher msgid
|
||||
private needLoadMore = true;
|
||||
|
||||
private pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
|
||||
constructor() {
|
||||
this.log = logger('AMV');
|
||||
@ -568,7 +570,7 @@ export class AppMediaViewer {
|
||||
return promise;
|
||||
}
|
||||
|
||||
public updateMediaSource(target: HTMLElement, url: string, tagName: 'source' | 'image') {
|
||||
public updateMediaSource(target: HTMLElement, url: string, tagName: 'source' | 'img') {
|
||||
//if(target instanceof SVGSVGElement) {
|
||||
let el = target.querySelector(tagName) as HTMLElement;
|
||||
renderImageFromUrl(el, url);
|
||||
@ -665,17 +667,20 @@ export class AppMediaViewer {
|
||||
|
||||
////////this.log('wasActive:', wasActive);
|
||||
|
||||
if(useContainerAsTarget) {
|
||||
target = target.querySelector('img, video') || target;
|
||||
}
|
||||
|
||||
let mover = this.content.mover;
|
||||
|
||||
let maxWidth = appPhotosManager.windowW - 16;
|
||||
//let maxWidth = appPhotosManager.windowW - 16;
|
||||
let maxWidth = this.pageEl.scrollWidth - 16;
|
||||
let maxHeight = appPhotosManager.windowH - 100;
|
||||
if(isVideo) {
|
||||
let size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight);
|
||||
appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight);
|
||||
|
||||
////////this.log('will wrap video', media, size);
|
||||
|
||||
if(useContainerAsTarget) target = target.querySelector('img, video') || target;
|
||||
|
||||
let afterTimeout = this.setMoverToTarget(target, false, fromRight);
|
||||
//return; // set and don't move
|
||||
//if(wasActive) return;
|
||||
@ -745,8 +750,6 @@ export class AppMediaViewer {
|
||||
} else {
|
||||
let size = appPhotosManager.setAttachmentSize(media.id, container, maxWidth, maxHeight);
|
||||
|
||||
if(useContainerAsTarget) target = target.querySelector('img, video') || target;
|
||||
|
||||
let afterTimeout = this.setMoverToTarget(target, false, fromRight);
|
||||
//return; // set and don't move
|
||||
//if(wasActive) return;
|
||||
@ -766,8 +769,8 @@ export class AppMediaViewer {
|
||||
|
||||
let url = media.url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(target, url, 'image');
|
||||
this.updateMediaSource(mover, url, 'image');
|
||||
this.updateMediaSource(target, url, 'img');
|
||||
this.updateMediaSource(mover, url, 'img');
|
||||
} else {
|
||||
let aspecter = mover.firstElementChild;
|
||||
let image = aspecter.firstElementChild as HTMLImageElement;
|
||||
|
@ -224,7 +224,7 @@ export class AppMessagesManager {
|
||||
}).catch(resolve);
|
||||
});
|
||||
|
||||
//setInterval(() => this.saveState(), 10000);
|
||||
setInterval(() => this.saveState(), 10000);
|
||||
}
|
||||
|
||||
public saveState() {
|
||||
@ -1166,7 +1166,7 @@ export class AppMessagesManager {
|
||||
} else {
|
||||
let doc = messageMedia.document;
|
||||
appDocsManager.saveDoc(doc);
|
||||
inputMedia = appDocsManager.getInputByID(doc.id);
|
||||
inputMedia = appDocsManager.getMediaInputByID(doc.id);
|
||||
}
|
||||
|
||||
inputs.push({
|
||||
@ -3002,7 +3002,11 @@ export class AppMessagesManager {
|
||||
if(messageID > maxID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
message = this.messagesStorage[messageID];
|
||||
if(!message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(message.pFlags.out != isOut) {
|
||||
continue;
|
||||
|
@ -176,8 +176,8 @@ export class AppPhotosManager {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public setAttachmentPreview(bytes: Uint8Array, element: HTMLElement | SVGSVGElement, isSticker = false, background = false) {
|
||||
|
||||
public getPreviewURLFromBytes(bytes: Uint8Array | number[], isSticker = false) {
|
||||
let arr: Uint8Array;
|
||||
if(!isSticker) {
|
||||
arr = AppPhotosManager.jf.concat(bytes.slice(3), AppPhotosManager.Df);
|
||||
@ -187,9 +187,7 @@ export class AppPhotosManager {
|
||||
arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
||||
}
|
||||
|
||||
//console.log('setAttachmentPreview', bytes, arr, div, isSticker);
|
||||
|
||||
let blob = new Blob([arr], {type: "image/jpeg"});
|
||||
//console.log('getPreviewURLFromBytes', bytes, arr, div, isSticker);
|
||||
|
||||
/* let reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
@ -197,8 +195,19 @@ export class AppPhotosManager {
|
||||
};
|
||||
reader.readAsDataURL(blob); */
|
||||
|
||||
let blob = new Blob([arr], {type: "image/jpeg"});
|
||||
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
public getPreviewURLFromThumb(thumb: any, isSticker = false) {
|
||||
return thumb.url ?? (thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker));
|
||||
}
|
||||
|
||||
public setAttachmentPreview(bytes: Uint8Array | number[], element: HTMLElement | SVGForeignObjectElement, isSticker = false, background = false) {
|
||||
let url = this.getPreviewURLFromBytes(bytes, isSticker);
|
||||
|
||||
if(background) {
|
||||
let url = URL.createObjectURL(blob);
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.addEventListener('load', () => {
|
||||
@ -206,30 +215,23 @@ export class AppPhotosManager {
|
||||
});
|
||||
|
||||
return element;
|
||||
//element.style.backgroundImage = 'url(' + url + ')';
|
||||
} else {
|
||||
if(element instanceof SVGSVGElement) {
|
||||
let image = element.firstElementChild as SVGImageElement || document.createElementNS("http://www.w3.org/2000/svg", "image");
|
||||
image.setAttributeNS(null, 'href', URL.createObjectURL(blob));
|
||||
element.append(image);
|
||||
|
||||
return image;
|
||||
} else if(element instanceof HTMLImageElement) {
|
||||
element.src = URL.createObjectURL(blob);
|
||||
if(element instanceof HTMLImageElement) {
|
||||
element.src = url;
|
||||
return element;
|
||||
} else {
|
||||
let img = new Image();
|
||||
img.style.width = '100%';
|
||||
img.style.height = '100%';
|
||||
/* img.style.width = '100%';
|
||||
img.style.height = '100%'; */
|
||||
|
||||
img.src = URL.createObjectURL(blob);
|
||||
img.src = url;
|
||||
element.append(img);
|
||||
return img;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setAttachmentSize(photoID: any, element: HTMLElement | SVGSVGElement, boxWidth = 380, boxHeight = 380, isSticker = false) {
|
||||
public setAttachmentSize(photoID: any, element: HTMLElement | SVGForeignObjectElement, boxWidth = 380, boxHeight = 380, isSticker = false) {
|
||||
let photo: /* MTDocument | MTPhoto */any = null;
|
||||
|
||||
if(typeof(photoID) === 'string') {
|
||||
@ -243,7 +245,7 @@ export class AppPhotosManager {
|
||||
//console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div);
|
||||
|
||||
let sizes = photo.sizes || photo.thumbs;
|
||||
if((!photo.downloaded || (isSticker && photo.animated)) && sizes && sizes[0].bytes) {
|
||||
if(!photo.downloaded && !isSticker && sizes && sizes[0].bytes) {
|
||||
this.setAttachmentPreview(sizes[0].bytes, element, isSticker);
|
||||
}
|
||||
|
||||
@ -258,17 +260,11 @@ export class AppPhotosManager {
|
||||
}
|
||||
|
||||
let {w, h} = calcImageInBox(width, height, boxWidth, boxHeight);
|
||||
if(element instanceof SVGSVGElement) {
|
||||
if(element instanceof SVGForeignObjectElement) {
|
||||
element.setAttributeNS(null, 'width', '' + w);
|
||||
element.setAttributeNS(null, 'height', '' + h);
|
||||
|
||||
//console.log('set dimensions to svg element:', element, w, h);
|
||||
|
||||
if(element.firstElementChild) {
|
||||
let imageSvg = element.firstElementChild as SVGImageElement;
|
||||
imageSvg.setAttributeNS(null, 'width', '' + w);
|
||||
imageSvg.setAttributeNS(null, 'height', '' + h);
|
||||
}
|
||||
} else {
|
||||
element.style.width = w + 'px';
|
||||
element.style.height = h + 'px';
|
||||
|
@ -111,14 +111,14 @@ class AppSidebarRight {
|
||||
let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement;
|
||||
this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement;
|
||||
|
||||
this.scroll = new Scrollable(this.profileContainer, 'y', 'SR');
|
||||
this.scroll = new Scrollable(this.profileContainer, 'y', 'SR', undefined, 400);
|
||||
this.scroll.onScrolledBottom = () => {
|
||||
if(this.sharedMediaSelected && this.sharedMediaSelected.childElementCount/* && false */) {
|
||||
this.log('onScrolledBottom will load media');
|
||||
this.loadSidebarMedia(true);
|
||||
}
|
||||
};
|
||||
this.scroll.attachSentinels(undefined, 400);
|
||||
//this.scroll.attachSentinels(undefined, 400);
|
||||
|
||||
horizontalMenu(this.profileTabs, container, (id, tabContent) => {
|
||||
if(this.prevTabID == id) return;
|
||||
|
@ -51,6 +51,8 @@ class AppStickersManager {
|
||||
private stickerSets: {
|
||||
[stickerSetID: string]: MTStickerSetFull
|
||||
} = {};
|
||||
|
||||
private saveSetsTimeout: number;
|
||||
|
||||
constructor() {
|
||||
AppStorage.get<{
|
||||
@ -59,17 +61,15 @@ class AppStickersManager {
|
||||
if(sets) {
|
||||
for(let id in sets) {
|
||||
let set = sets[id];
|
||||
set.documents.forEach(doc => {
|
||||
this.saveSticker(doc);
|
||||
});
|
||||
this.saveStickers(set.documents);
|
||||
}
|
||||
|
||||
this.stickerSets = sets;
|
||||
}
|
||||
|
||||
if(!this.stickerSets['emoji']) {
|
||||
//if(!this.stickerSets['emoji']) {
|
||||
this.getStickerSet({id: 'emoji', access_hash: ''});
|
||||
}
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
@ -81,6 +81,12 @@ class AppStickersManager {
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
public saveStickers(docs: MTDocument[]) {
|
||||
docs.forEach((doc, idx) => {
|
||||
docs[idx] = this.saveSticker(doc);
|
||||
});
|
||||
}
|
||||
|
||||
public getSticker(fileID: string) {
|
||||
return this.documents[fileID];
|
||||
@ -115,6 +121,20 @@ class AppStickersManager {
|
||||
return stickerSet;
|
||||
}
|
||||
|
||||
public async getRecentStickers() {
|
||||
let res: {
|
||||
_: string,
|
||||
hash: number,
|
||||
packs: any[],
|
||||
stickers: MTDocument[],
|
||||
dates: number[]
|
||||
} = await apiManager.invokeApi('messages.getRecentStickers', {flags: 0, hash: 0});
|
||||
|
||||
this.saveStickers(res.stickers);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public getAnimatedEmojiSticker(emoji: string) {
|
||||
let stickerSet = this.stickerSets.emoji;
|
||||
|
||||
@ -122,7 +142,7 @@ class AppStickersManager {
|
||||
return stickerSet.documents.find(doc => doc.stickerEmojiRaw == emoji);
|
||||
}
|
||||
|
||||
public async saveStickerSet(res: {
|
||||
public saveStickerSet(res: {
|
||||
_: "messages.stickerSet",
|
||||
set: MTStickerSet,
|
||||
packs: any[],
|
||||
@ -136,12 +156,18 @@ class AppStickersManager {
|
||||
documents: res.documents
|
||||
};
|
||||
|
||||
res.documents.forEach(this.saveSticker.bind(this));
|
||||
this.saveStickers(res.documents);
|
||||
|
||||
//console.log('stickers wrote', this.stickerSets);
|
||||
await AppStorage.set({
|
||||
stickerSets: this.stickerSets
|
||||
});
|
||||
if(this.saveSetsTimeout) return;
|
||||
this.saveSetsTimeout = setTimeout(() => {
|
||||
AppStorage.set({
|
||||
stickerSets: this.stickerSets
|
||||
});
|
||||
|
||||
this.saveSetsTimeout = 0;
|
||||
}, 0);
|
||||
|
||||
|
||||
/* AppStorage.get('stickerSets').then((sets: any) => {
|
||||
this.stickerSets = sets;
|
||||
@ -153,7 +179,9 @@ class AppStickersManager {
|
||||
let thumb = stickerSet.thumb;
|
||||
let dcID = stickerSet.thumb_dc_id;
|
||||
|
||||
let promise = apiFileManager.downloadSmallFile({
|
||||
let isAnimated = stickerSet.pFlags?.animated;
|
||||
|
||||
let promise = apiFileManager.downloadFile(dcID, {
|
||||
_: 'inputStickerSetThumb',
|
||||
stickerset: {
|
||||
_: 'inputStickerSetID',
|
||||
@ -162,7 +190,10 @@ class AppStickersManager {
|
||||
},
|
||||
volume_id: thumb.location.volume_id,
|
||||
local_id: thumb.location.local_id
|
||||
}, {dcID: dcID});
|
||||
}, thumb.size, {
|
||||
stickerType: isAnimated ? 2 : 1,
|
||||
mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
@ -1,31 +1,21 @@
|
||||
// @ts-ignore
|
||||
//import createWorker from 'offscreen-canvas/create-worker';
|
||||
|
||||
class AppWebpManager {
|
||||
public webpMachine: any = null;
|
||||
public loaded: Promise<void>;
|
||||
public busyPromise: Promise<string>;
|
||||
public queue: {bytes: Uint8Array, img: HTMLImageElement, callback: (url: string) => void}[] = [];
|
||||
//public worker: any;
|
||||
public webpSupport: Promise<boolean> = null;
|
||||
private webpMachine: any = null;
|
||||
private loaded: Promise<void>;
|
||||
private busyPromise: Promise<Uint8Array | void>;
|
||||
private queue: {bytes: Uint8Array, callback: (res: Uint8Array) => void}[] = [];
|
||||
|
||||
private testPromise: Promise<boolean> = null;
|
||||
public webpSupport = false;
|
||||
|
||||
constructor() {
|
||||
//let canvas = document.createElement('canvas');
|
||||
//console.log('got message from worker:', canvas.toDataURL());
|
||||
/* this.worker = createWorker(canvas, '/webp.bundle.js', (e: any) => {
|
||||
// Messages from the worker
|
||||
console.log('got message from worker:', e, canvas.toDataURL());
|
||||
}); */
|
||||
|
||||
this.webpSupported().then(res => {
|
||||
});
|
||||
this.testWebpSupport();
|
||||
}
|
||||
|
||||
public loadWebpHero() {
|
||||
private loadWebpHero() {
|
||||
if(this.loaded) return this.loaded;
|
||||
|
||||
this.loaded = new Promise(async(resolve, reject) => {
|
||||
let res = await this.webpSupported();
|
||||
let res = await this.testWebpSupport();
|
||||
|
||||
if(!res) {
|
||||
(window as any).webpLoaded = () => {
|
||||
@ -46,17 +36,16 @@ class AppWebpManager {
|
||||
});
|
||||
}
|
||||
|
||||
convert(bytes: Uint8Array): Promise<string> {
|
||||
private convert(bytes: Uint8Array): AppWebpManager['busyPromise'] {
|
||||
return this.webpMachine.decode(bytes);
|
||||
//return this.worker.post({message: 'webpBytes', bytes});
|
||||
}
|
||||
|
||||
async processQueue() {
|
||||
private async processQueue() {
|
||||
if(this.busyPromise) return;
|
||||
|
||||
this.busyPromise = Promise.resolve('');
|
||||
this.busyPromise = Promise.resolve();
|
||||
|
||||
let {img, bytes, callback} = this.queue.pop();
|
||||
let {bytes, callback} = this.queue.pop();
|
||||
|
||||
if(!this.loaded) {
|
||||
this.loadWebpHero();
|
||||
@ -65,13 +54,11 @@ class AppWebpManager {
|
||||
await this.loaded;
|
||||
|
||||
this.busyPromise = this.convert(bytes);
|
||||
let url = await this.busyPromise;
|
||||
let imgTemp = new Image();
|
||||
imgTemp.src = url;
|
||||
imgTemp.onload = () => {
|
||||
img.src = imgTemp.src;
|
||||
};
|
||||
callback(url);
|
||||
let res = await this.busyPromise;
|
||||
|
||||
console.log('converted webp', res);
|
||||
|
||||
callback(res as Uint8Array);
|
||||
|
||||
this.busyPromise = null;
|
||||
|
||||
@ -80,42 +67,33 @@ class AppWebpManager {
|
||||
}
|
||||
}
|
||||
|
||||
webpSupported() {
|
||||
if(this.webpSupport) return this.webpSupport;
|
||||
public testWebpSupport() {
|
||||
if(this.testPromise) return this.testPromise;
|
||||
|
||||
return this.webpSupport = new Promise((resolve, reject) => {
|
||||
var webP = new Image();
|
||||
return this.testPromise = new Promise((resolve, reject) => {
|
||||
let webP = new Image();
|
||||
webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' +
|
||||
'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';
|
||||
webP.onload = webP.onerror = () => {
|
||||
resolve(webP.height === 2);
|
||||
resolve(this.webpSupport = webP.height === 2/* && false */);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async polyfillImage(img: HTMLImageElement, blob: Blob) {
|
||||
/* console.log('polyfillImage', this);
|
||||
return this.webpMachine.polyfillImage(image); */
|
||||
public isSupported() {
|
||||
return this.webpSupport;
|
||||
}
|
||||
|
||||
//if(await this.webpMachine.webpSupport) {
|
||||
if(await this.webpSupport) {
|
||||
let url = URL.createObjectURL(blob);
|
||||
img.src = url;
|
||||
return url;
|
||||
}
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('loadend', (e) => {
|
||||
// @ts-ignore
|
||||
let bytes = new Uint8Array(e.srcElement.result);
|
||||
|
||||
this.queue.push({bytes, img, callback: resolve});
|
||||
this.processQueue();
|
||||
});
|
||||
reader.readAsArrayBuffer(blob);
|
||||
public convertToPng(bytes: Uint8Array) {
|
||||
console.warn('convertToPng!');
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
// @ts-ignore
|
||||
this.queue.push({bytes, callback: resolve});
|
||||
this.processQueue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppWebpManager();
|
||||
const appWebpManager = new AppWebpManager();
|
||||
(window as any).appWebpManager = appWebpManager;
|
||||
export default appWebpManager;
|
||||
|
@ -342,24 +342,6 @@ export function longFromInts(high: number, low: number) {
|
||||
return bigint(high).shiftLeft(32).add(bigint(low)).toString(10);
|
||||
}
|
||||
|
||||
export function intToUint(val: number | string) {
|
||||
if(typeof(val) === 'string') val = parseInt(val);
|
||||
|
||||
/* if(val < 0) {
|
||||
val = val + 4294967296;
|
||||
} */
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
export function uintToInt(val: number) {
|
||||
/* if(val > 2147483647) {
|
||||
val = val - 4294967296;
|
||||
} */
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, full = false, prepend = false) {
|
||||
let len = bytes.byteLength || bytes.length;
|
||||
let needPadding = blockSize - (len % blockSize);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {blobSafeMimeType, blobConstruct, bytesToBase64} from './bin_utils';
|
||||
import {blobConstruct} from './bin_utils';
|
||||
|
||||
/* import 'web-streams-polyfill/ponyfill';
|
||||
// @ts-ignore
|
||||
@ -139,14 +139,6 @@ class FileManager {
|
||||
|
||||
return fakeFileWriter;
|
||||
}
|
||||
|
||||
public getFileCorrectUrl(fileData: Blob | number[], mimeType: string): string {
|
||||
var safeMimeType = blobSafeMimeType(mimeType);
|
||||
if(fileData instanceof Blob) {
|
||||
return URL.createObjectURL(fileData);
|
||||
}
|
||||
return 'data:' + safeMimeType + ';base64,' + bytesToBase64(fileData);
|
||||
}
|
||||
|
||||
public download(blob: Blob, mimeType: string, fileName: string) {
|
||||
if(window.navigator && navigator.msSaveBlob !== undefined) {
|
||||
@ -180,7 +172,7 @@ class FileManager {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.getFileCorrectUrl(blob, mimeType);
|
||||
let url = URL.createObjectURL(blob);
|
||||
var anchor = document.createElementNS('http://www.w3.org/1999/xhtml', 'a') as HTMLAnchorElement;
|
||||
anchor.href = url as string;
|
||||
anchor.download = fileName;
|
||||
|
@ -51,6 +51,8 @@ class IdbFileStorage {
|
||||
request.onsuccess = (event) => {
|
||||
finished = true;
|
||||
var db = request.result;
|
||||
|
||||
console.log('Opened IndexedDB');
|
||||
|
||||
db.onerror = (error) => {
|
||||
this.storageIsAvailable = false;
|
||||
|
@ -96,7 +96,7 @@ class LottieLoader {
|
||||
params.renderer = 'svg';
|
||||
//}
|
||||
|
||||
params.rendererSettings = {
|
||||
let rendererSettings = {
|
||||
//context: context, // the canvas context
|
||||
//preserveAspectRatio: 'xMinYMin slice', // Supports the same options as the svg element's preserveAspectRatio property
|
||||
clearCanvas: true,
|
||||
@ -104,6 +104,12 @@ class LottieLoader {
|
||||
hideOnTransparent: true, //Boolean, only svg renderer, hides elements when opacity reaches 0 (defaults to true),
|
||||
};
|
||||
|
||||
if(params.rendererSettings) {
|
||||
params.rendererSettings = Object.assign(params.rendererSettings, rendererSettings);
|
||||
} else {
|
||||
params.rendererSettings = rendererSettings;
|
||||
}
|
||||
|
||||
if(!this.lottie) {
|
||||
if(!this.loaded) this.loadLottie();
|
||||
await this.loaded;
|
||||
|
@ -5,6 +5,7 @@ import FileManager from "../filemanager";
|
||||
//import apiManager from "./apiManager";
|
||||
import apiManager from "./mtprotoworker";
|
||||
import { logger, deferredPromise, CancellablePromise } from "../polyfill";
|
||||
import appWebpManager from "../appManagers/appWebpManager";
|
||||
|
||||
export class ApiFileManager {
|
||||
public cachedSavePromises: {
|
||||
@ -14,7 +15,7 @@ export class ApiFileManager {
|
||||
[fileName: string]: any
|
||||
} = {};
|
||||
public cachedDownloads: {
|
||||
[fileName: string]: any
|
||||
[fileName: string]: Blob
|
||||
} = {};
|
||||
|
||||
/* public indexedKeys: Set<string> = new Set();
|
||||
@ -84,25 +85,38 @@ export class ApiFileManager {
|
||||
});
|
||||
}
|
||||
|
||||
public getFileName(location: any) {
|
||||
public getFileName(location: any, options?: Partial<{
|
||||
stickerType: number
|
||||
}>) {
|
||||
switch(location._) {
|
||||
case 'inputDocumentFileLocation':
|
||||
var fileName = (location.file_name as string || '').split('.');
|
||||
var ext: string = fileName[fileName.length - 1] || '';
|
||||
case 'inputDocumentFileLocation': {
|
||||
let fileName = (location.file_name as string || '').split('.');
|
||||
let ext = fileName[fileName.length - 1] || '';
|
||||
|
||||
var versionPart = location.version ? ('v' + location.version) : '';
|
||||
return (fileName[0] ? fileName[0] + '_' : '') + location.id + versionPart + (ext ? '.' + ext : ext);
|
||||
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
ext += '.png'
|
||||
}
|
||||
|
||||
default:
|
||||
let thumbPart = location.thumb_size ? '_' + location.thumb_size : '';
|
||||
return (fileName[0] ? fileName[0] + '_' : '') + location.id + thumbPart + (ext ? '.' + ext : ext);
|
||||
}
|
||||
|
||||
default: {
|
||||
if(!location.volume_id && !location.file_reference) {
|
||||
this.log.trace('Empty location', location);
|
||||
}
|
||||
|
||||
let ext = 'jpg';
|
||||
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
ext += '.png'
|
||||
}
|
||||
|
||||
if(location.volume_id) {
|
||||
return location.volume_id + '_' + location.local_id + '.' + ext;
|
||||
} else {
|
||||
return location.id + '_' + location.access_hash + '.' + ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,10 +159,11 @@ export class ApiFileManager {
|
||||
return this.cachedSavePromises[fileName];
|
||||
}
|
||||
|
||||
public downloadSmallFile(location: any, options: {
|
||||
mimeType?: string,
|
||||
dcID?: number,
|
||||
} = {}): Promise<Blob> {
|
||||
public downloadSmallFile(location: any, options: Partial<{
|
||||
mimeType: string,
|
||||
dcID: number,
|
||||
stickerType: number
|
||||
}> = {}): Promise<Blob> {
|
||||
if(!FileManager.isAvailable()) {
|
||||
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
|
||||
}
|
||||
@ -159,10 +174,16 @@ export class ApiFileManager {
|
||||
|
||||
//this.log('downloadSmallFile', location, options);
|
||||
|
||||
let processSticker = false;
|
||||
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
processSticker = true;
|
||||
options.mimeType = 'image/png';
|
||||
}
|
||||
|
||||
let dcID = options.dcID || location.dc_id;
|
||||
let mimeType = options.mimeType || 'image/jpeg';
|
||||
var fileName = this.getFileName(location);
|
||||
var cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
|
||||
let fileName = this.getFileName(location, options);
|
||||
let cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
|
||||
|
||||
//this.log('downloadSmallFile!', location, options, fileName, cachedPromise);
|
||||
|
||||
@ -170,13 +191,14 @@ export class ApiFileManager {
|
||||
return cachedPromise;
|
||||
}
|
||||
|
||||
var fileStorage = this.getFileStorage();
|
||||
let fileStorage = this.getFileStorage();
|
||||
|
||||
return this.cachedDownloadPromises[fileName] = fileStorage.getFile(fileName).then((blob: any) => {
|
||||
return this.cachedDownloadPromises[fileName] = fileStorage.getFile(fileName).then((blob) => {
|
||||
//throw '';
|
||||
return this.cachedDownloads[fileName] = blob;
|
||||
}, () => {
|
||||
var downloadPromise = this.downloadRequest(dcID, () => {
|
||||
var inputLocation = location;
|
||||
}).catch(() => {
|
||||
let downloadPromise = this.downloadRequest(dcID, () => {
|
||||
let inputLocation = location;
|
||||
if(!inputLocation._ || inputLocation._ == 'fileLocation') {
|
||||
inputLocation = Object.assign({}, location, {_: 'inputFileLocation'});
|
||||
}
|
||||
@ -196,18 +218,17 @@ export class ApiFileManager {
|
||||
});
|
||||
}, dcID);
|
||||
|
||||
var processDownloaded = (bytes: Uint8Array) => {
|
||||
let processDownloaded = (bytes: Uint8Array) => {
|
||||
//this.log('processDownloaded', location, bytes);
|
||||
|
||||
return Promise.resolve(bytes);
|
||||
/* if(!location.sticker || WebpManager.isWebpSupported()) {
|
||||
return qSync.when(bytes);
|
||||
if(processSticker) {
|
||||
return appWebpManager.convertToPng(bytes);
|
||||
}
|
||||
|
||||
return WebpManager.getPngBlobFromWebp(bytes); */
|
||||
return Promise.resolve(bytes);
|
||||
};
|
||||
|
||||
return fileStorage.getFileWriter(fileName, mimeType).then((fileWriter: any) => {
|
||||
return fileStorage.getFileWriter(fileName, mimeType).then(fileWriter => {
|
||||
return downloadPromise.then((result: any) => {
|
||||
return processDownloaded(result.bytes).then((proccessedResult) => {
|
||||
return FileManager.write(fileWriter, proccessedResult).then(() => {
|
||||
@ -236,12 +257,12 @@ export class ApiFileManager {
|
||||
});
|
||||
} */
|
||||
|
||||
public downloadFile(dcID: number, location: any, size: number, options: {
|
||||
mimeType?: string,
|
||||
dcID?: number,
|
||||
toFileEntry?: any,
|
||||
limitPart?: number
|
||||
} = {}): CancellablePromise<Blob> {
|
||||
public downloadFile(dcID: number, location: any, size: number, options: Partial<{
|
||||
mimeType: string,
|
||||
toFileEntry: any,
|
||||
limitPart: number,
|
||||
stickerType: number
|
||||
}> = {}): CancellablePromise<Blob> {
|
||||
if(!FileManager.isAvailable()) {
|
||||
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
|
||||
}
|
||||
@ -250,11 +271,21 @@ export class ApiFileManager {
|
||||
this.getIndexedKeys();
|
||||
} */
|
||||
|
||||
let processSticker = false;
|
||||
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
if(options.toFileEntry || size > 524288) {
|
||||
delete options.stickerType;
|
||||
} else {
|
||||
processSticker = true;
|
||||
options.mimeType = 'image/png';
|
||||
}
|
||||
}
|
||||
|
||||
// this.log('Dload file', dcID, location, size)
|
||||
var fileName = this.getFileName(location);
|
||||
var toFileEntry = options.toFileEntry || null;
|
||||
var cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
|
||||
var fileStorage = this.getFileStorage();
|
||||
let fileName = this.getFileName(location, options);
|
||||
let toFileEntry = options.toFileEntry || null;
|
||||
let cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
|
||||
let fileStorage = this.getFileStorage();
|
||||
|
||||
//this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments);
|
||||
|
||||
@ -272,7 +303,7 @@ export class ApiFileManager {
|
||||
if(blob.size < size) {
|
||||
this.log('downloadFile need to deleteFile, wrong size:', blob.size, size);
|
||||
|
||||
return this.deleteFile(location).then(() => {
|
||||
return this.deleteFile(fileName).then(() => {
|
||||
return this.downloadFile(dcID, location, size, options);
|
||||
}).catch(() => {
|
||||
return this.downloadFile(dcID, location, size, options);
|
||||
@ -303,10 +334,11 @@ export class ApiFileManager {
|
||||
|
||||
fileStorage.getFile(fileName, size).then(async(blob: Blob) => {
|
||||
//this.log('is that i wanted');
|
||||
//throw '';
|
||||
|
||||
if(blob.size < size) {
|
||||
this.log('downloadFile need to deleteFile 2, wrong size:', blob.size, size);
|
||||
await this.deleteFile(location);
|
||||
await this.deleteFile(fileName);
|
||||
throw false;
|
||||
}
|
||||
|
||||
@ -322,13 +354,12 @@ export class ApiFileManager {
|
||||
//var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
|
||||
var fileWriterPromise = toFileEntry ? Promise.resolve(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
|
||||
|
||||
var processDownloaded = (bytes: any) => {
|
||||
return Promise.resolve(bytes);
|
||||
/* if(!processSticker) {
|
||||
return Promise.resolve(bytes);
|
||||
var processDownloaded = (bytes: Uint8Array) => {
|
||||
if(processSticker) {
|
||||
return appWebpManager.convertToPng(bytes);
|
||||
}
|
||||
|
||||
return WebpManager.getPngBlobFromWebp(bytes); */
|
||||
return Promise.resolve(bytes);
|
||||
};
|
||||
|
||||
fileWriterPromise.then((fileWriter: any) => {
|
||||
@ -391,7 +422,7 @@ export class ApiFileManager {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return processDownloaded(result.bytes).then((processedResult: Uint8Array) => {
|
||||
return processDownloaded(result.bytes).then((processedResult) => {
|
||||
return FileManager.write(fileWriter, processedResult).then(() => {
|
||||
writeFileDeferred.resolve();
|
||||
}, errorHandler).then(() => {
|
||||
@ -438,12 +469,13 @@ export class ApiFileManager {
|
||||
return deferred;
|
||||
}
|
||||
|
||||
public deleteFile(fileName: any) {
|
||||
fileName = typeof(fileName) == 'string' ? fileName : this.getFileName(fileName);
|
||||
public deleteFile(fileName: string) {
|
||||
this.log('will delete file:', fileName);
|
||||
|
||||
delete this.cachedDownloadPromises[fileName];
|
||||
delete this.cachedDownloads[fileName];
|
||||
delete this.cachedSavePromises[fileName];
|
||||
|
||||
return this.getFileStorage().deleteFile(fileName);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {isObject} from '../bin_utils';
|
||||
import {convertToUint8Array,
|
||||
bufferConcat, nextRandomInt, bytesToHex, longToBytes,
|
||||
bytesCmp, uintToInt, bigStringInt} from '../bin_utils';
|
||||
bytesCmp, bigStringInt} from '../bin_utils';
|
||||
import {TLDeserialization, TLSerialization} from './tl_utils';
|
||||
import CryptoWorker from '../crypto/cryptoworker';
|
||||
import AppStorage from '../storage';
|
||||
@ -296,8 +296,7 @@ class MTPNetworker {
|
||||
var isClean = this.cleanupSent();
|
||||
//this.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean, this);
|
||||
if((this.longPollPending && Date.now() < this.longPollPending) ||
|
||||
this.offline ||
|
||||
NetworkerFactory.akStopped) {
|
||||
this.offline) {
|
||||
//this.log('No lp this time');
|
||||
return false;
|
||||
}
|
||||
@ -502,7 +501,7 @@ class MTPNetworker {
|
||||
|
||||
public performScheduledRequest() {
|
||||
// this.log('scheduled', this.dcID, this.iii)
|
||||
if(this.offline || NetworkerFactory.akStopped) {
|
||||
if(this.offline) {
|
||||
this.log('Cancel scheduled');
|
||||
return false;
|
||||
}
|
||||
@ -1027,7 +1026,7 @@ class MTPNetworker {
|
||||
|
||||
public processError(rawError: {error_message: string, error_code: number}) {
|
||||
var matches = (rawError.error_message || '').match(/^([A-Z_0-9]+\b)(: (.+))?/) || [];
|
||||
rawError.error_code = uintToInt(rawError.error_code);
|
||||
rawError.error_code = rawError.error_code;
|
||||
|
||||
return {
|
||||
code: !rawError.error_code || rawError.error_code <= 0 ? 500 : rawError.error_code,
|
||||
|
@ -2,24 +2,6 @@ import { MTPNetworker } from "./networker";
|
||||
|
||||
export class NetworkerFactory {
|
||||
public updatesProcessor: (obj: any, bool: boolean) => void = null;
|
||||
//public offlineInited = false;
|
||||
public akStopped = false;
|
||||
|
||||
/* public startAll() {
|
||||
if(this.akStopped) {
|
||||
this.akStopped = false;
|
||||
|
||||
if(this.updatesProcessor) {
|
||||
this.updatesProcessor({
|
||||
_: 'new_session_created'
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public stopAll() {
|
||||
this.akStopped = true;
|
||||
} */
|
||||
|
||||
public setUpdatesProcessor(callback: (obj: any, bool: boolean) => void) {
|
||||
this.updatesProcessor = callback;
|
||||
|
@ -5,7 +5,7 @@
|
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import {bigint, intToUint, bigStringInt, bytesToHex, uintToInt, isObject} from '../bin_utils';
|
||||
import {bigint, bigStringInt, bytesToHex, isObject} from '../bin_utils';
|
||||
import Schema from './schema';
|
||||
|
||||
/// #if MTPROTO_WORKER
|
||||
@ -131,8 +131,8 @@ class TLSerialization {
|
||||
}
|
||||
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
|
||||
|
||||
this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]');
|
||||
this.writeInt(intToUint(divRem[0].intValue()), (field || '') + ':long[high]');
|
||||
this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]');
|
||||
this.writeInt(divRem[0].intValue(), (field || '') + ':long[high]');
|
||||
}
|
||||
|
||||
public storeDouble(f: any, field?: string) {
|
||||
@ -250,7 +250,7 @@ class TLSerialization {
|
||||
throw new Error('No method ' + methodName + ' found');
|
||||
}
|
||||
|
||||
this.storeInt(intToUint(methodData.id), methodName + '[id]');
|
||||
this.storeInt(methodData.id, methodName + '[id]');
|
||||
|
||||
var param, type;
|
||||
var i, condType;
|
||||
@ -347,7 +347,7 @@ class TLSerialization {
|
||||
}
|
||||
|
||||
if(!isBare) {
|
||||
this.writeInt(intToUint(constructorData.id), field + '[' + predicate + '][id]');
|
||||
this.writeInt(constructorData.id, field + '[' + predicate + '][id]');
|
||||
}
|
||||
|
||||
var param, type: string;
|
||||
@ -601,7 +601,7 @@ class TLDeserialization {
|
||||
if(type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') {
|
||||
if(type.charAt(0) == 'V') {
|
||||
var constructor = this.readInt(field + '[id]');
|
||||
var constructorCmp = uintToInt(constructor);
|
||||
var constructorCmp = constructor;
|
||||
|
||||
if(constructorCmp == gzipPacked) { // Gzip packed
|
||||
var compressed = this.fetchBytes(field + '[packed_string]');
|
||||
@ -657,7 +657,7 @@ class TLDeserialization {
|
||||
}
|
||||
} else {
|
||||
var constructor = this.readInt(field + '[id]');
|
||||
var constructorCmp = uintToInt(constructor);
|
||||
var constructorCmp = constructor;
|
||||
|
||||
if(constructorCmp == gzipPacked) { // Gzip packed
|
||||
var compressed = this.fetchBytes(field + '[packed_string]');
|
||||
|
@ -289,8 +289,10 @@ export function getSelectedText() {
|
||||
|
||||
export const $rootScope = {
|
||||
$broadcast: (name/* : string */, detail/*? : any */) => {
|
||||
console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail);
|
||||
//console.trace();
|
||||
if(name != 'user_update') {
|
||||
console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail);
|
||||
}
|
||||
|
||||
let myCustomEvent = new CustomEvent(name, {detail});
|
||||
document.dispatchEvent(myCustomEvent);
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {Webp} from "webp-hero/libwebp/dist/webp.js"
|
||||
import {detectWebpSupport} from "webp-hero/dist/detect-webp-support.js"
|
||||
|
||||
const relax = () => new Promise(resolve => requestAnimationFrame(resolve))
|
||||
const relax = () => new Promise(resolve => requestAnimationFrame(resolve));
|
||||
|
||||
export class WebpMachineError extends Error {}
|
||||
|
||||
@ -11,34 +10,39 @@ export class WebpMachineError extends Error {}
|
||||
* - can only decode images one-at-a-time (otherwise will throw busy error)
|
||||
*/
|
||||
export class WebpMachine {
|
||||
private readonly webp: Webp
|
||||
private readonly webpSupport: Promise<boolean>
|
||||
private busy = false
|
||||
private cache: {[key: string]: string} = {}
|
||||
private readonly webp: Webp;
|
||||
private busy = false;
|
||||
|
||||
constructor({
|
||||
webp = new Webp(),
|
||||
webpSupport = detectWebpSupport()
|
||||
} = {}) {
|
||||
this.webp = webp;
|
||||
constructor() {
|
||||
this.webp = new Webp();
|
||||
this.webp.Module.doNotCaptureKeyboard = true;
|
||||
this.webpSupport = webpSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode raw webp data into a png data url
|
||||
*/
|
||||
async decode(webpData: Uint8Array): Promise<string> {
|
||||
decode(webpData: Uint8Array): Promise<Uint8Array> {
|
||||
if(this.busy) throw new WebpMachineError("cannot decode when already busy");
|
||||
this.busy = true;
|
||||
|
||||
try {
|
||||
await relax();
|
||||
const canvas = document.createElement("canvas");
|
||||
this.webp.setCanvas(canvas);
|
||||
this.webp.webpToSdl(webpData, webpData.length);
|
||||
this.busy = false;
|
||||
return canvas.toDataURL();
|
||||
return relax().then(() => {
|
||||
const canvas = document.createElement("canvas");
|
||||
this.webp.setCanvas(canvas);
|
||||
this.webp.webpToSdl(webpData, webpData.length);
|
||||
this.busy = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob(blob => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
resolve(new Uint8Array(event.target.result as ArrayBuffer));
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}, 'image/png', 1);
|
||||
});
|
||||
});
|
||||
} catch(error) {
|
||||
this.busy = false;
|
||||
error.name = WebpMachineError.name;
|
||||
|
@ -28,7 +28,12 @@ let sentTypeElement: HTMLParagraphElement = null;
|
||||
|
||||
let onFirstMount = (): Promise<any> => {
|
||||
let needFrame = 0, lastLength = 0;
|
||||
let animation: /* AnimationItem */any = undefined;
|
||||
|
||||
let animation: /* AnimationItem */any;
|
||||
let idleAnimation: any;
|
||||
|
||||
let mTrackingSvg: SVGSVGElement;
|
||||
let mIdleSvg: SVGSVGElement;
|
||||
|
||||
const CODELENGTH = authCode.type.length;
|
||||
|
||||
@ -113,6 +118,13 @@ let onFirstMount = (): Promise<any> => {
|
||||
});
|
||||
}
|
||||
|
||||
let cleanup = () => {
|
||||
setTimeout(() => {
|
||||
if(animation) animation.destroy();
|
||||
if(idleAnimation) idleAnimation.destroy();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
let submitCode = (code: string) => {
|
||||
codeInput.setAttribute('disabled', 'true');
|
||||
|
||||
@ -135,7 +147,7 @@ let onFirstMount = (): Promise<any> => {
|
||||
});
|
||||
|
||||
pageIm.mount();
|
||||
if(animation) animation.destroy();
|
||||
cleanup();
|
||||
break;
|
||||
case 'auth.authorizationSignUpRequired':
|
||||
console.log('Registration needed!');
|
||||
@ -145,7 +157,7 @@ let onFirstMount = (): Promise<any> => {
|
||||
'phone_code_hash': authCode.phone_code_hash
|
||||
});
|
||||
|
||||
if(animation) animation.destroy();
|
||||
cleanup();
|
||||
break;
|
||||
default:
|
||||
codeInput.innerText = response._;
|
||||
@ -158,7 +170,7 @@ let onFirstMount = (): Promise<any> => {
|
||||
case 'SESSION_PASSWORD_NEEDED':
|
||||
console.warn('pageAuthCode: SESSION_PASSWORD_NEEDED');
|
||||
err.handled = true;
|
||||
if(animation) animation.destroy();
|
||||
cleanup();
|
||||
pagePassword.mount();
|
||||
break;
|
||||
case 'PHONE_CODE_EMPTY':
|
||||
@ -196,8 +208,14 @@ let onFirstMount = (): Promise<any> => {
|
||||
if(!animation) return;
|
||||
|
||||
let frame: number;
|
||||
if(length) frame = Math.round((length > max ? max : length) * (165 / max) + 11.33);
|
||||
else frame = 0;
|
||||
if(length) {
|
||||
frame = Math.round((length > max ? max : length) * (165 / max) + 11.33);
|
||||
|
||||
mIdleSvg.style.display = 'none';
|
||||
mTrackingSvg.style.display = '';
|
||||
} else {
|
||||
frame = 0;
|
||||
}
|
||||
//animation.playSegments([1, 2]);
|
||||
|
||||
let direction = needFrame > frame ? -1 : 1;
|
||||
@ -217,33 +235,68 @@ let onFirstMount = (): Promise<any> => {
|
||||
//animation.goToAndStop(length / max * );
|
||||
});
|
||||
|
||||
let imageDiv = page.pageEl.querySelector('.auth-image');
|
||||
return Promise.all([
|
||||
LottieLoader.loadLottie(),
|
||||
|
||||
fetch('assets/img/TwoFactorSetupMonkeyIdle.tgs')
|
||||
.then(res => res.arrayBuffer())
|
||||
.then(data => apiManager.gzipUncompress<string>(data, true))
|
||||
.then(str => LottieLoader.loadAnimation({
|
||||
container: imageDiv,
|
||||
renderer: 'svg',
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
animationData: JSON.parse(str),
|
||||
rendererSettings: {
|
||||
className: 'monkey-idle'
|
||||
}
|
||||
}))
|
||||
.then(_animation => {
|
||||
idleAnimation = _animation;
|
||||
|
||||
mIdleSvg = imageDiv.querySelector('.monkey-idle');
|
||||
}),
|
||||
|
||||
fetch('assets/img/TwoFactorSetupMonkeyTracking.tgs')
|
||||
.then(res => res.arrayBuffer())
|
||||
.then(data => apiManager.gzipUncompress<string>(data, true))
|
||||
.then(str => LottieLoader.loadAnimation({
|
||||
container: page.pageEl.querySelector('.auth-image'),
|
||||
container: imageDiv,
|
||||
renderer: 'svg',
|
||||
loop: false,
|
||||
autoplay: false,
|
||||
animationData: JSON.parse(str)
|
||||
animationData: JSON.parse(str),
|
||||
rendererSettings: {
|
||||
className: 'monkey-tracking'
|
||||
}
|
||||
}))
|
||||
.then(_animation => {
|
||||
animation = _animation;
|
||||
animation.setSpeed(1);
|
||||
//console.log(animation.getDuration(), animation.getDuration(true));
|
||||
|
||||
mTrackingSvg = imageDiv.querySelector('.monkey-tracking');
|
||||
if(!codeInput.value.length) {
|
||||
mTrackingSvg.style.display = 'none';
|
||||
}
|
||||
|
||||
animation.addEventListener('enterFrame', (e: any) => {
|
||||
//console.log('enterFrame', e, needFrame);
|
||||
let currentFrame = Math.round(e.currentTime);
|
||||
|
||||
if((e.direction == 1 && currentFrame >= needFrame) ||
|
||||
(e.direction == -1 && currentFrame <= needFrame)) {
|
||||
animation.setSpeed(1);
|
||||
animation.pause();
|
||||
}
|
||||
animation.setSpeed(1);
|
||||
animation.pause();
|
||||
}
|
||||
|
||||
if(currentFrame == 0 && needFrame == 0 && mIdleSvg) {
|
||||
mTrackingSvg.style.display = 'none';
|
||||
mIdleSvg.style.display = '';
|
||||
idleAnimation.stop();
|
||||
idleAnimation.play();
|
||||
}
|
||||
});
|
||||
})
|
||||
]);
|
||||
|
@ -206,6 +206,8 @@ let onFirstMount = () => {
|
||||
putPreloader(this);
|
||||
//this.innerHTML = 'PLEASE WAIT...';
|
||||
|
||||
return;
|
||||
|
||||
let phone_number = telEl.value;
|
||||
apiManager.invokeApi('auth.sendCode', {
|
||||
//flags: 0,
|
||||
|
@ -205,6 +205,7 @@ $time-background: rgba(0, 0, 0, .35);
|
||||
#attach-file {
|
||||
&.menu-open {
|
||||
color: $color-blue;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.btn-menu {
|
||||
|
@ -355,8 +355,6 @@
|
||||
position: relative;
|
||||
|
||||
img, video {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
@ -394,13 +392,13 @@
|
||||
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
img:not(.emoji), video {
|
||||
/* object-fit: contain; */
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
img:not(.emoji), video {
|
||||
/* object-fit: contain; */
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.is-album {
|
||||
|
@ -68,7 +68,7 @@
|
||||
.category-items {
|
||||
display: grid;
|
||||
grid-column-gap: 2.44px;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-template-columns: repeat(9, 1fr);
|
||||
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.25rem;
|
||||
@ -121,10 +121,9 @@
|
||||
|
||||
.category-items {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-column-gap: 1px;
|
||||
|
||||
> div {
|
||||
width: 80px;
|
||||
|
@ -1,20 +1,13 @@
|
||||
#column-right {
|
||||
width: 0%;
|
||||
/* grid-column: 3; */
|
||||
position: relative;
|
||||
transition: .2s ease-in-out;
|
||||
|
||||
.profile-container {
|
||||
> .scrollable {
|
||||
min-width: 25vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sidebar-content {
|
||||
min-width: 25vw;
|
||||
|
||||
@media (min-width: $large-screen) {
|
||||
> .scrollable {
|
||||
min-width: calc(#{$large-screen} / 4 - 1px);
|
||||
}
|
||||
min-width: calc(#{$large-screen} / 4 - 1px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +44,7 @@
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
/* height: 100%; */
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@ -172,7 +165,7 @@
|
||||
&-content {
|
||||
//min-height: 100%;
|
||||
min-height: calc(100% - 49px);
|
||||
position: absolute; // FIX THE SAFARI!
|
||||
//position: absolute; // FIX THE SAFARI!
|
||||
//position: relative;
|
||||
/* width: 500%;
|
||||
margin-left: -100%;
|
||||
|
@ -34,8 +34,8 @@ div.scrollable::-webkit-scrollbar-thumb {
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* display: flex;
|
||||
flex-direction: column; */
|
||||
|
||||
&.scrollable-x {
|
||||
overflow-x: auto;
|
||||
@ -50,7 +50,7 @@ div.scrollable::-webkit-scrollbar-thumb {
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
&-sentinel {
|
||||
/* &-sentinel {
|
||||
position: relative;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
@ -58,7 +58,7 @@ div.scrollable::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
width: 1px;
|
||||
min-width: 1px;
|
||||
}
|
||||
} */
|
||||
|
||||
/* &.scrollable-x ~ .scrollbar-thumb {
|
||||
top: auto;
|
||||
|
@ -15,7 +15,6 @@
|
||||
#{$parent}-photo {
|
||||
max-height: 320px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 8px;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
|
@ -418,18 +418,11 @@ avatar-element {
|
||||
|
||||
@keyframes ripple-effect {
|
||||
0% {
|
||||
//transform: translate(-50%, -50%) scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
/* 50% {
|
||||
opacity: 1;
|
||||
} */
|
||||
|
||||
to {
|
||||
transform: scale(2);
|
||||
//transform: translate(-50%, -50%) scale(2);
|
||||
//opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1062,30 +1055,6 @@ input:focus, button:focus {
|
||||
}
|
||||
}
|
||||
|
||||
/* button {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
&:before {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
content: " ";
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background: #000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: .08;
|
||||
}
|
||||
} */
|
||||
|
||||
.btn-primary {
|
||||
background: $color-blue;
|
||||
color: #fff;
|
||||
@ -1106,9 +1075,8 @@ input:focus, button:focus {
|
||||
|
||||
svg, use {
|
||||
height: calc(100% - 20px);
|
||||
right: 12.5px;
|
||||
right: 15px;
|
||||
left: auto;
|
||||
margin: 4px 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user