Browse Source

sticker thumbs & safari open chat fix & online blink fix & login added idle monkey & webp safari save to indexeddb & lazyload queue reorder & minor fixes

master
morethanwords 4 years ago
parent
commit
22ceba971d
  1. 4
      src/components/appForward.ts
  2. 16
      src/components/appSelectPeers.ts
  3. 4
      src/components/chatInput.ts
  4. 25
      src/components/emoticonsDropdown.ts
  5. 14
      src/components/lazyLoadQueue.ts
  6. 25
      src/components/misc.ts
  7. 109
      src/components/scrollable_new.ts
  8. 237
      src/components/wrappers.ts
  9. 65
      src/lib/appManagers/appChatsManager.ts
  10. 6
      src/lib/appManagers/appDialogsManager.ts
  11. 106
      src/lib/appManagers/appDocsManager.ts
  12. 145
      src/lib/appManagers/appImManager.ts
  13. 21
      src/lib/appManagers/appMediaViewer.ts
  14. 8
      src/lib/appManagers/appMessagesManager.ts
  15. 50
      src/lib/appManagers/appPhotosManager.ts
  16. 4
      src/lib/appManagers/appSidebarRight.ts
  17. 55
      src/lib/appManagers/appStickersManager.ts
  18. 94
      src/lib/appManagers/appWebpManager.ts
  19. 18
      src/lib/bin_utils.ts
  20. 12
      src/lib/filemanager.ts
  21. 2
      src/lib/idb.ts
  22. 8
      src/lib/lottieLoader.ts
  23. 122
      src/lib/mtproto/apiFileManager.ts
  24. 9
      src/lib/mtproto/networker.ts
  25. 18
      src/lib/mtproto/networkerFactory.ts
  26. 14
      src/lib/mtproto/tl_utils.ts
  27. 6
      src/lib/utils.js
  28. 44
      src/lib/webp.ts
  29. 75
      src/pages/pageAuthCode.ts
  30. 2
      src/pages/pageSignIn.ts
  31. 1
      src/scss/partials/_chat.scss
  32. 16
      src/scss/partials/_chatBubble.scss
  33. 9
      src/scss/partials/_emojiDropdown.scss
  34. 17
      src/scss/partials/_rightSIdebar.scss
  35. 8
      src/scss/partials/_scrollable.scss
  36. 1
      src/scss/partials/popups/_mediaAttacher.scss
  37. 34
      src/scss/style.scss

4
src/components/appForward.ts

@ -56,7 +56,6 @@ class AppForward {
this.cleanup(); this.cleanup();
this.msgIDs = ids; this.msgIDs = ids;
appSidebarRight.toggleSidebar(true);
this.container.classList.add('active'); this.container.classList.add('active');
this.sendBtn.innerHTML = ''; this.sendBtn.innerHTML = '';
this.sendBtn.classList.add('tgico-send'); this.sendBtn.classList.add('tgico-send');
@ -68,6 +67,9 @@ class AppForward {
} else { } else {
this.sendBtn.classList.remove('is-visible'); this.sendBtn.classList.remove('is-visible');
} }
}, 'dialogs', () => {
console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount);
appSidebarRight.toggleSidebar(true);
}); });
} }
} }

16
src/components/appSelectPeers.ts

@ -29,7 +29,7 @@ export class AppSelectPeers {
private query = ''; private query = '';
private cachedContacts: number[]; 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'); this.container.classList.add('selector');
let topContainer = document.createElement('div'); let topContainer = document.createElement('div');
@ -107,12 +107,18 @@ export class AppSelectPeers {
this.container.append(topContainer, delimiter, this.chatsContainer); this.container.append(topContainer, delimiter, this.chatsContainer);
appendTo.append(this.container); appendTo.append(this.container);
this.getMoreResults(); let getResultsPromise = this.getMoreResults() as Promise<any>;
if(onFirstRender) {
getResultsPromise.then(() => {
onFirstRender();
});
}
} }
private getMoreDialogs() { 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 dialogs = value.dialogs;
let newOffsetIndex = dialogs[value.dialogs.length - 1].index || 0; let newOffsetIndex = dialogs[value.dialogs.length - 1].index || 0;
@ -150,9 +156,9 @@ export class AppSelectPeers {
private getMoreResults() { private getMoreResults() {
if(this.peerType == 'dialogs') { if(this.peerType == 'dialogs') {
this.getMoreDialogs(); return this.getMoreDialogs();
} else { } else {
this.getMoreContacts(); return this.getMoreContacts();
} }
} }

4
src/components/chatInput.ts

@ -13,7 +13,7 @@ import lottieLoader from "../lib/lottieLoader";
import { Layouter, RectPart } from "./groupedLayout"; import { Layouter, RectPart } from "./groupedLayout";
export class ChatInput { 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 messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */;
public fileInput = document.getElementById('input-file') as HTMLInputElement; public fileInput = document.getElementById('input-file') as HTMLInputElement;
public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement; public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement;
@ -463,6 +463,7 @@ export class ChatInput {
this.emoticonsDropdown.classList.remove('active'); this.emoticonsDropdown.classList.remove('active');
this.toggleEmoticons.classList.remove('active'); this.toggleEmoticons.classList.remove('active');
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
this.emoticonsLazyLoadQueue.lock();
}, 200); }, 200);
}; };
@ -471,6 +472,7 @@ export class ChatInput {
}; };
} else { } else {
this.emoticonsDropdown.classList.add('active'); this.emoticonsDropdown.classList.add('active');
this.emoticonsLazyLoadQueue.unlock();
} }
this.toggleEmoticons.classList.add('active'); this.toggleEmoticons.classList.add('active');

25
src/components/emoticonsDropdown.ts

@ -1,6 +1,6 @@
import { AppImManager } from "../lib/appManagers/appImManager"; import { AppImManager } from "../lib/appManagers/appImManager";
import { AppMessagesManager } from "../lib/appManagers/appMessagesManager"; import { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
import { horizontalMenu } from "./misc"; import { horizontalMenu, renderImageFromUrl } from "./misc";
import lottieLoader from "../lib/lottieLoader"; import lottieLoader from "../lib/lottieLoader";
//import Scrollable from "./scrollable"; //import Scrollable from "./scrollable";
import Scrollable from "./scrollable_new"; import Scrollable from "./scrollable_new";
@ -12,7 +12,6 @@ import apiManager from '../lib/mtproto/mtprotoworker';
//import CryptoWorker from '../lib/crypto/cryptoworker'; //import CryptoWorker from '../lib/crypto/cryptoworker';
import LazyLoadQueue from "./lazyLoadQueue"; import LazyLoadQueue from "./lazyLoadQueue";
import { MTDocument, wrapSticker } from "./wrappers"; import { MTDocument, wrapSticker } from "./wrappers";
import appWebpManager from "../lib/appManagers/appWebpManager";
import appDocsManager from "../lib/appManagers/appDocsManager"; import appDocsManager from "../lib/appManagers/appDocsManager";
import ProgressivePreloader from "./preloader"; import ProgressivePreloader from "./preloader";
import Config from "../lib/config"; import Config from "../lib/config";
@ -29,15 +28,18 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement; let container = pageEl.querySelector('.emoji-container .tabs-container') as HTMLDivElement;
let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement; let tabs = pageEl.querySelector('.emoji-dropdown .emoji-tabs') as HTMLUListElement;
let tabID = -1;
horizontalMenu(tabs, container, (id) => { horizontalMenu(tabs, container, (id) => {
lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(true, EMOTICONSSTICKERGROUP);
if(id == 1 && stickersInit) { tabID = id;
}, () => {
if(tabID == 1 && stickersInit) {
stickersInit(); stickersInit();
} else if(id == 2 && gifsInit) { } else if(tabID == 2 && gifsInit) {
gifsInit(); gifsInit();
} }
}, () => {
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
}); });
@ -347,15 +349,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
stickersInit = null; stickersInit = null;
Promise.all([ Promise.all([
apiManager.invokeApi('messages.getRecentStickers', {flags: 0, hash: 0}).then((res) => { appStickersManager.getRecentStickers().then(stickers => {
let stickers: {
_: string,
hash: number,
packs: any[],
stickers: MTDocument[],
dates: number[]
} = res as any;
let categoryDiv = document.createElement('div'); let categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category'); categoryDiv.classList.add('sticker-category');
@ -408,8 +402,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
reader.readAsArrayBuffer(blob); reader.readAsArrayBuffer(blob);
} else { } else {
let image = new Image(); let image = new Image();
//image.src = URL.createObjectURL(blob); renderImageFromUrl(image, URL.createObjectURL(blob));
appWebpManager.polyfillImage(image, blob);
li.append(image); li.append(image);
} }

14
src/components/lazyLoadQueue.ts

@ -19,16 +19,18 @@ export default class LazyLoadQueue {
constructor(private parallelLimit = 5) { constructor(private parallelLimit = 5) {
this.observer = new IntersectionObserver(entries => { this.observer = new IntersectionObserver(entries => {
if(this.lockPromise) return;
for(let entry of entries) { for(let entry of entries) {
if(entry.isIntersecting) { if(entry.isIntersecting) {
let target = entry.target as HTMLElement; let target = entry.target as HTMLElement;
for(let item of this.lazyLoadMedia) { // need for set element first if scrolled
if(item.div == target) { let item = this.lazyLoadMedia.findAndSplice(i => i.div == target);
item.wasSeen = true; if(item) {
this.processQueue(item); item.wasSeen = true;
break; this.lazyLoadMedia.unshift(item);
} this.processQueue(item);
} }
} }
} }

25
src/components/misc.ts

@ -121,26 +121,29 @@ let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceEl
else elem.style.backgroundImage = 'url(' + url + ')'; 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]) { if(loadedURLs[url]) {
set(elem, url); set(elem, url);
return true; return Promise.resolve(true);
} }
if(elem instanceof HTMLSourceElement) { if(elem instanceof HTMLSourceElement) {
elem.src = url; elem.src = url;
return Promise.resolve(false);
} else { } else {
let loader = new Image(); return new Promise((resolve, reject) => {
loader.src = url; let loader = new Image();
//let perf = performance.now(); loader.src = url;
loader.addEventListener('load', () => { //let perf = performance.now();
set(elem, url); loader.addEventListener('load', () => {
loadedURLs[url] = true; set(elem, url);
//console.log('onload:', url, performance.now() - perf); 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) { export function putPreloader(elem: Element, returnDiv = false) {

109
src/components/scrollable_new.ts

@ -56,6 +56,9 @@ export default class Scrollable {
private lastBottomID = 0; private lastBottomID = 0;
private lastScrollDirection = 0; // true = bottom private lastScrollDirection = 0; // true = bottom
private onScrolledTopFired = false;
private onScrolledBottomFired = false;
public scrollLocked = 0; public scrollLocked = 0;
private setVisible(element: HTMLElement) { private setVisible(element: HTMLElement) {
@ -184,37 +187,56 @@ export default class Scrollable {
//this.onScroll(); //this.onScroll();
} }
public attachSentinels(container = this.container, offset = this.onScrollOffset) { // public attachSentinels(container = this.container, offset = this.onScrollOffset) {
if(!this.sentinelsObserver) { // if(!this.sentinelsObserver) {
this.topSentinel = document.createElement('div'); // this.topSentinel = document.createElement('div');
this.topSentinel.classList.add('scrollable-sentinel'); // this.topSentinel.classList.add('scrollable-sentinel');
this.topSentinel.style.top = offset + 'px'; // this.topSentinel.style.top = offset + 'px';
this.bottomSentinel = document.createElement('div'); // this.bottomSentinel = document.createElement('div');
this.bottomSentinel.classList.add('scrollable-sentinel'); // this.bottomSentinel.classList.add('scrollable-sentinel');
this.bottomSentinel.style.bottom = offset + 'px'; // this.bottomSentinel.style.bottom = offset + 'px';
this.container.append(this.topSentinel, this.bottomSentinel); // this.container.append(this.topSentinel, this.bottomSentinel);
this.sentinelsObserver = new IntersectionObserver(entries => { // //let fire: () => void;
for(let entry of entries) {
if(entry.isIntersecting) { // this.sentinelsObserver = new IntersectionObserver(entries => {
let top = entry.target == this.topSentinel; // for(let entry of entries) {
if(top) { // let top = entry.target == this.topSentinel;
this.onScrolledTop && this.onScrolledTop(); // if(top) {
} else { // this.onScrolledTopFired = entry.isIntersecting;
this.onScrolledBottom && this.onScrolledBottom(); // } else {
} // this.onScrolledBottomFired = entry.isIntersecting;
} // }
} // }
});
// /* this.debug && */this.log('Set onScrolledFires:', this.onScrolledTopFired, this.onScrolledBottomFired);
this.sentinelsObserver.observe(this.topSentinel);
this.sentinelsObserver.observe(this.bottomSentinel); // /* if((this.onScrolledTopFired || this.onScrolledBottomFired) && !fire) {
} // fire = () => window.requestAnimationFrame(() => {
// if(!this.scrollLocked) {
container.prepend(this.topSentinel); // if(this.onScrolledTopFired && this.onScrolledTop) this.onScrolledTop();
container.append(this.bottomSentinel); // 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) { public setVirtualContainer(el?: HTMLElement) {
this.splitUp = el; this.splitUp = el;
@ -231,7 +253,7 @@ export default class Scrollable {
/* if(this.debug) { /* if(this.debug) {
this.log('onScroll call', this.onScrollMeasure); this.log('onScroll call', this.onScrollMeasure);
} */ } */
let appendTo = this.splitUp || this.appendTo; let appendTo = this.splitUp || this.appendTo;
clearTimeout(this.disableHoverTimeout); clearTimeout(this.disableHoverTimeout);
@ -246,8 +268,13 @@ export default class Scrollable {
this.lastScrollDirection = 0; this.lastScrollDirection = 0;
}, 100); }, 100);
if(!this.splitUp || this.onScrollMeasure) return; if(this.onScrollMeasure) return;
this.onScrollMeasure = window.requestAnimationFrame(() => { this.onScrollMeasure = window.requestAnimationFrame(() => {
this.checkForTriggers();
this.onScrollMeasure = 0;
if(!this.splitUp) return;
let scrollTop = this.container.scrollTop; let scrollTop = this.container.scrollTop;
if(this.lastScrollTop != scrollTop) { if(this.lastScrollTop != scrollTop) {
this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1; this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1;
@ -255,10 +282,24 @@ export default class Scrollable {
} else { } else {
this.lastScrollDirection = 0; 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() { public reorder() {
(Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => { (Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => {
el.dataset.virtual = '' + idx; el.dataset.virtual = '' + idx;

237
src/components/wrappers.ts

@ -1,4 +1,4 @@
import appPhotosManager, { MTPhoto } from '../lib/appManagers/appPhotosManager'; import appPhotosManager from '../lib/appManagers/appPhotosManager';
//import CryptoWorker from '../lib/crypto/cryptoworker'; //import CryptoWorker from '../lib/crypto/cryptoworker';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import LottieLoader from '../lib/lottieLoader'; import LottieLoader from '../lib/lottieLoader';
@ -8,7 +8,6 @@ import { formatBytes } from "../lib/utils";
import ProgressivePreloader from './preloader'; import ProgressivePreloader from './preloader';
import LazyLoadQueue from './lazyLoadQueue'; import LazyLoadQueue from './lazyLoadQueue';
import apiFileManager from '../lib/mtproto/apiFileManager'; import apiFileManager from '../lib/mtproto/apiFileManager';
import appWebpManager from '../lib/appManagers/appWebpManager';
import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer'; import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer';
import { RichTextProcessor } from '../lib/richtextprocessor'; import { RichTextProcessor } from '../lib/richtextprocessor';
import { CancellablePromise } from '../lib/polyfill'; import { CancellablePromise } from '../lib/polyfill';
@ -16,6 +15,7 @@ import { renderImageFromUrl } from './misc';
import appMessagesManager from '../lib/appManagers/appMessagesManager'; import appMessagesManager from '../lib/appManagers/appMessagesManager';
import { Layouter, RectPart } from './groupedLayout'; import { Layouter, RectPart } from './groupedLayout';
import PollElement from './poll'; import PollElement from './poll';
import appWebpManager from '../lib/appManagers/appWebpManager';
export type MTDocument = { export type MTDocument = {
_: 'document' | 'documentEmpty', _: 'document' | 'documentEmpty',
@ -40,15 +40,15 @@ export type MTDocument = {
duration?: number, duration?: number,
downloaded?: boolean, downloaded?: boolean,
url?: string, url?: string,
version?: any,
audioTitle?: string, audioTitle?: string,
audioPerformer?: string, audioPerformer?: string,
sticker?: boolean, sticker?: number,
stickerEmoji?: string, stickerEmoji?: string,
stickerEmojiRaw?: string, stickerEmojiRaw?: string,
stickerSetInput?: any, stickerSetInput?: any,
stickerThumbConverted?: true,
animated?: boolean animated?: boolean
}; };
@ -74,49 +74,40 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
middleware: () => boolean, middleware: () => boolean,
lazyLoadQueue: LazyLoadQueue lazyLoadQueue: LazyLoadQueue
}) { }) {
let img: HTMLImageElement | SVGImageElement; let img: HTMLImageElement;
if(withTail) { if(withTail) {
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut); 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 { } else {
if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) { if(!boxWidth && !boxHeight) { // album
let size = appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight); 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(); img = container.lastElementChild as HTMLImageElement;
if(!img || img.tagName != 'IMG') {
if(!container.contains(img)) { container.append(img = new Image());
container.append(img);
} }
} }
let video = document.createElement('video'); let video = document.createElement('video');
let source = document.createElement('source');
video.append(source);
if(withTail) { if(withTail) {
let foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject'); let foreignObject = img.parentElement;
let width = img.getAttributeNS(null, 'width'); video.width = +foreignObject.getAttributeNS(null, 'width');
let height = img.getAttributeNS(null, 'height'); video.height = +foreignObject.getAttributeNS(null, 'height');
foreignObject.setAttributeNS(null, 'width', width);
foreignObject.setAttributeNS(null, 'height', height);
video.width = +width;
video.height = +height;
foreignObject.append(video); foreignObject.append(video);
img.parentElement.append(foreignObject); } else {
container.append(video);
} }
let source = document.createElement('source');
video.append(source);
let span: HTMLSpanElement; let span: HTMLSpanElement;
if(doc.type != 'round') { if(doc.type != 'round') {
span = document.createElement('span'); span = document.createElement('span');
@ -154,12 +145,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
source.type = doc.mime_type; source.type = doc.mime_type;
video.append(source); video.append(source);
if(!withTail) { if(img && img.parentElement) {
if(img && container.contains(img)) { img.remove();
container.removeChild(img);
}
container.append(video);
} }
if(doc.type == 'gif') { 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) { 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"); let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.classList.add('bubble__media-container', isOut ? 'is-out' : 'is-in'); svg.classList.add('bubble__media-container', isOut ? 'is-out' : 'is-in');
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 foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject');
let width = +svg.getAttributeNS(null, 'width'); appPhotosManager.setAttachmentSize(photo._ == 'document' ? photo : photo.id, foreignObject, boxWidth, boxHeight);
let height = +svg.getAttributeNS(null, 'height');
let width = +foreignObject.getAttributeNS(null, 'width');
let height = +foreignObject.getAttributeNS(null, 'height');
svg.setAttributeNS(null, 'width', '' + width);
svg.setAttributeNS(null, 'height', '' + height);
let clipID = 'clip' + message.mid; let clipID = 'clip' + message.mid;
svg.dataset.clipID = clipID; svg.dataset.clipID = clipID;
@ -626,36 +615,38 @@ function wrapMediaWithTail(photo: any, message: {mid: number, message: string},
defs.innerHTML = `<clipPath id="${clipID}">${clipPathHTML}</clipPath>`; defs.innerHTML = `<clipPath id="${clipID}">${clipPathHTML}</clipPath>`;
svg.prepend(defs); container.style.width = parseInt(container.style.width) - 9 + 'px';
container.appendChild(svg);
svg.append(defs, foreignObject);
container.append(svg);
let img = foreignObject.firstElementChild as HTMLImageElement;
if(!img) {
foreignObject.append(img = new Image());
}
return 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) { 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 photo = appPhotosManager.getPhoto(photoID);
let image: SVGImageElement | HTMLImageElement; let image: HTMLImageElement;
if(withTail) { if(withTail) {
image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut); image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut);
} else if(size) { // album } else {
let sizes = photo.sizes; if(size) { // album
if(!photo.downloaded && sizes && sizes[0].bytes) { let sizes = photo.sizes;
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false); 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(); image = container.lastElementChild as HTMLImageElement;
if(!image || image.tagName != 'IMG') {
if(!container.contains(image)) { container.append(image = new 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);
} }
} }
@ -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) { 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) { if(stickerType == 2 && !LottieLoader.loaded) {
LottieLoader.loadLottie(); LottieLoader.loadLottie();
@ -702,8 +693,10 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
if(!stickerType) { if(!stickerType) {
console.error('wrong doc for wrapSticker!', doc); 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); //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); //console.log('wrap sticker', thumb, div);
if(thumb.bytes) { 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 thumb = doc.thumbs[0];
let load = () => apiFileManager.downloadSmallFile({ let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => {
_: '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 img = new Image(); let img = new Image();
renderImageFromUrl(img, url).then(() => {
appWebpManager.polyfillImage(img, blob); if(middleware && !middleware()) return;
div.append(img);
div.append(img); });
div.dataset.docID = doc.id;
appStickersManager.saveSticker(doc);
}); });
return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); 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 => { let load = () => appDocsManager.downloadDoc(doc.id).then(blob => {
//console.log('loaded sticker:', blob, div); //console.log('loaded sticker:', blob, div);
if(middleware && !middleware()) return; if(middleware && !middleware()) return;
/* if(div.firstElementChild) { //return;
div.firstElementChild.remove();
} */
if(stickerType == 2) { if(stickerType == 2) {
const reader = new FileReader(); const reader = new FileReader();
@ -802,15 +822,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
}, {once: true}); }, {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) { if(play) {
animation.play(); animation.play();
@ -831,23 +843,14 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
}); });
} }
if(!doc.url) { renderImageFromUrl(img, doc.url).then(() => {
appWebpManager.polyfillImage(img, blob).then((url) => { if(div.firstElementChild && div.firstElementChild != img) {
doc.url = url; div.firstElementChild.remove();
}
if(div.firstElementChild && div.firstElementChild != img) {
div.firstElementChild.remove();
}
});
} else {
img.src = doc.url;
}
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(); return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}), Promise.resolve()) : load();

65
src/lib/appManagers/appChatsManager.ts

@ -3,6 +3,7 @@ import { RichTextProcessor } from "../richtextprocessor";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import apiUpdatesManager from "./apiUpdatesManager"; import apiUpdatesManager from "./apiUpdatesManager";
import appProfileManager from "./appProfileManager";
type Channel = { type Channel = {
_: 'channel', _: 'channel',
@ -42,6 +43,8 @@ export class AppChatsManager {
public megagroups: any = {}; public megagroups: any = {};
public cachedPhotoLocations: any = {}; public cachedPhotoLocations: any = {};
public megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}} = {};
constructor() { constructor() {
$rootScope.$on('apiUpdate', (e: CustomEvent) => { $rootScope.$on('apiUpdate', (e: CustomEvent) => {
// console.log('on apiUpdate', update) // console.log('on apiUpdate', update)
@ -130,7 +133,7 @@ export class AppChatsManager {
return false; return false;
} }
var chat = this.getChat(id); let chat = this.getChat(id);
if(chat._ == 'chatForbidden' || if(chat._ == 'chatForbidden' ||
chat._ == 'channelForbidden' || chat._ == 'channelForbidden' ||
chat.pFlags.kicked || chat.pFlags.kicked ||
@ -191,7 +194,7 @@ export class AppChatsManager {
} }
public isChannel(id: number) { public isChannel(id: number) {
var chat = this.chats[id]; let chat = this.chats[id];
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') || this.channelAccess[id]) { if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') || this.channelAccess[id]) {
return true; return true;
} }
@ -203,7 +206,7 @@ export class AppChatsManager {
return true; return true;
} }
var chat = this.chats[id]; let chat = this.chats[id];
if(chat && chat._ == 'channel' && chat.pFlags.megagroup) { if(chat && chat._ == 'channel' && chat.pFlags.megagroup) {
return true; return true;
} }
@ -246,12 +249,12 @@ export class AppChatsManager {
} }
public hasChat(id: number, allowMin?: any) { public hasChat(id: number, allowMin?: any) {
var chat = this.chats[id] let chat = this.chats[id]
return isObject(chat) && (allowMin || !chat.pFlags.min); return isObject(chat) && (allowMin || !chat.pFlags.min);
} }
public getChatPhoto(id: number) { public getChatPhoto(id: number) {
var chat = this.getChat(id); let chat = this.getChat(id);
if(this.cachedPhotoLocations[id] === undefined) { if(this.cachedPhotoLocations[id] === undefined) {
this.cachedPhotoLocations[id] = chat && chat.photo ? chat.photo : {empty: true}; this.cachedPhotoLocations[id] = chat && chat.photo ? chat.photo : {empty: true};
@ -261,7 +264,7 @@ export class AppChatsManager {
} }
public getChatString(id: number) { public getChatString(id: number) {
var chat = this.getChat(id); let chat = this.getChat(id);
if(this.isChannel(id)) { if(this.isChannel(id)) {
return (this.isMegagroup(id) ? 's' : 'c') + id + '_' + chat.access_hash; return (this.isMegagroup(id) ? 's' : 'c') + id + '_' + chat.access_hash;
} }
@ -277,8 +280,8 @@ export class AppChatsManager {
} }
public wrapForFull(id: number, fullChat: any) { public wrapForFull(id: number, fullChat: any) {
var chatFull = copy(fullChat); let chatFull = copy(fullChat);
var chat = this.getChat(id); let chat = this.getChat(id);
if(!chatFull.participants_count) { if(!chatFull.participants_count) {
chatFull.participants_count = chat.participants_count; chatFull.participants_count = chat.participants_count;
@ -300,10 +303,10 @@ export class AppChatsManager {
} }
public wrapParticipants(id: number, participants: any[]) { public wrapParticipants(id: number, participants: any[]) {
var chat = this.getChat(id); let chat = this.getChat(id);
var myID = appUsersManager.getSelf().id; let myID = appUsersManager.getSelf().id;
if(this.isChannel(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) => { participants.forEach((participant) => {
participant.canLeave = myID == participant.user_id; participant.canLeave = myID == participant.user_id;
participant.canKick = isAdmin && participant._ == 'channelParticipant'; participant.canKick = isAdmin && participant._ == 'channelParticipant';
@ -312,7 +315,7 @@ export class AppChatsManager {
participant.user = appUsersManager.getUser(participant.user_id); participant.user = appUsersManager.getUser(participant.user_id);
}); });
} else { } 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) => { participants.forEach((participant) => {
participant.canLeave = myID == participant.user_id; participant.canLeave = myID == participant.user_id;
participant.canKick = !participant.canLeave && ( 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(); export default new AppChatsManager();

6
src/lib/appManagers/appDialogsManager.ts

@ -409,12 +409,12 @@ export class AppDialogsManager {
this.scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500); this.scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500);
this.scroll.setVirtualContainer(this.chatList); this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = this.onChatsScroll.bind(this); 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 = new Scrollable(this.chatsArchivedContainer, 'y', 'CLA', this.chatListArchived, 500);
this.scrollArchived.setVirtualContainer(this.chatListArchived); this.scrollArchived.setVirtualContainer(this.chatListArchived);
this.scrollArchived.onScrolledBottom = this.onChatsArchivedScroll.bind(this); this.scrollArchived.onScrolledBottom = this.onChatsArchivedScroll.bind(this);
this.scroll.attachSentinels(); ///this.scroll.attachSentinels();
this.setListClickListener(this.chatList); this.setListClickListener(this.chatList);
this.setListClickListener(this.chatListArchived); this.setListClickListener(this.chatListArchived);
@ -828,7 +828,7 @@ export class AppDialogsManager {
dom.lastTimeSpan.innerHTML = timeStr; dom.lastTimeSpan.innerHTML = timeStr;
} else dom.lastTimeSpan.innerHTML = ''; } else dom.lastTimeSpan.innerHTML = '';
if(this.doms[peerID] || this.domsArchived[peerID]) { if((this.doms[peerID] || this.domsArchived[peerID]) == dom) {
this.setUnreadMessages(dialog); this.setUnreadMessages(dialog);
} else { // means search } else { // means search
dom.listEl.dataset.mid = lastMessage.mid; dom.listEl.dataset.mid = lastMessage.mid;

106
src/lib/appManagers/appDocsManager.ts

@ -7,8 +7,9 @@ import { isObject } from '../utils';
class AppDocsManager { class AppDocsManager {
private docs: {[docID: string]: MTDocument} = {}; 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]); //console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
if(this.docs[apiDoc.id]) { if(this.docs[apiDoc.id]) {
let d = this.docs[apiDoc.id]; let d = this.docs[apiDoc.id];
@ -68,8 +69,6 @@ class AppDocsManager {
break; break;
case 'documentAttributeSticker': case 'documentAttributeSticker':
apiDoc.sticker = true;
if(attribute.alt !== undefined) { if(attribute.alt !== undefined) {
apiDoc.stickerEmojiRaw = attribute.alt; apiDoc.stickerEmojiRaw = attribute.alt;
apiDoc.stickerEmoji = RichTextProcessor.wrapRichText(apiDoc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true}); 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.type = 'sticker';
apiDoc.sticker = 1;
} else if(apiDoc.mime_type == 'application/x-tgsticker') { } else if(apiDoc.mime_type == 'application/x-tgsticker') {
apiDoc.type = 'sticker'; apiDoc.type = 'sticker';
apiDoc.animated = true; apiDoc.animated = true;
apiDoc.sticker = 2;
} }
break; break;
@ -145,7 +146,7 @@ class AppDocsManager {
return isObject(docID) ? docID : this.docs[docID]; return isObject(docID) ? docID : this.docs[docID];
} }
public getInputByID(docID: any) { public getMediaInputByID(docID: any) {
let doc = this.getDoc(docID); let doc = this.getDoc(docID);
return { return {
_: 'inputMediaDocument', _: 'inputMediaDocument',
@ -159,6 +160,18 @@ class AppDocsManager {
ttl_seconds: 0 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) { public getFileName(doc: MTDocument) {
if(doc.file_name) { if(doc.file_name) {
@ -173,42 +186,10 @@ class AppDocsManager {
return 't_' + (doc.type || 'file') + doc.id + fileExt; return 't_' + (doc.type || 'file') + doc.id + fileExt;
} }
public updateDocDownloaded(docID: string) { public downloadDoc(docID: any, toFileEntry?: any): CancellablePromise<Blob> {
var doc = this.docs[docID]; let doc = this.getDoc(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;
}
var inputFileLocation = { let inputFileLocation = this.getInputByID(doc);
_: 'inputDocumentFileLocation',
id: doc.id,
access_hash: doc.access_hash,
file_reference: doc.file_reference,
thumb_size: '',
version: doc.version,
file_name: this.getFileName(doc)
};
if(doc._ == 'documentEmpty') { if(doc._ == 'documentEmpty') {
return Promise.reject(); return Promise.reject();
@ -217,26 +198,27 @@ class AppDocsManager {
if(doc.downloaded && !toFileEntry) { if(doc.downloaded && !toFileEntry) {
if(doc.url) return Promise.resolve(null); if(doc.url) return Promise.resolve(null);
var cachedBlob = apiFileManager.getCachedFile(inputFileLocation); let cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
if(cachedBlob) { if(cachedBlob) {
return Promise.resolve(cachedBlob); return Promise.resolve(cachedBlob);
} }
} }
//historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size}; //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', mimeType: doc.mime_type || 'application/octet-stream',
toFileEntry: toFileEntry toFileEntry: toFileEntry,
stickerType: doc.sticker
}); });
downloadPromise.then((blob) => { downloadPromise.then((blob) => {
if(blob) { if(blob) {
doc.downloaded = true; doc.downloaded = true;
if(/* !doc.animated || */doc.type && doc.type != 'sticker') { if(doc.type && doc.sticker != 2) {
doc.url = FileManager.getFileCorrectUrl(blob, doc.mime_type); doc.url = URL.createObjectURL(blob);
} }
} }
@ -266,6 +248,36 @@ class AppDocsManager {
return downloadPromise; 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) { public async saveDocFile(docID: string) {
var doc = this.docs[docID]; var doc = this.docs[docID];

145
src/lib/appManagers/appImManager.ts

@ -2,7 +2,7 @@
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack, whichChild } from "../utils"; import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack, whichChild } from "../utils";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appMessagesManager, { Dialog } from "./appMessagesManager"; import appMessagesManager from "./appMessagesManager";
import appPeersManager from "./appPeersManager"; import appPeersManager from "./appPeersManager";
import appProfileManager from "./appProfileManager"; import appProfileManager from "./appProfileManager";
import appDialogsManager from "./appDialogsManager"; import appDialogsManager from "./appDialogsManager";
@ -38,7 +38,7 @@ appSidebarLeft; // just to include
let testScroll = false; let testScroll = false;
export class AppImManager { 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 btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement; public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement;
public avatarEl = document.getElementById('im-avatar') as AvatarElement; public avatarEl = document.getElementById('im-avatar') as AvatarElement;
@ -754,34 +754,22 @@ export class AppImManager {
this.log('loadMoreHistory', top); this.log('loadMoreHistory', top);
if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return; 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; 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) { if(top && !this.scrolledAll) {
this.log('Will load more (up) history by id:', history[0], 'maxID:', history[history.length - 1], history); 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); /* false && */this.getHistory(history[0], true);
} }
if(this.scrolledAllDown) return; if(this.scrolledAllDown) return;
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
/* if(!dialog) {
this.log.warn('no dialog for load history');
return;
} */
// if scroll down after search // if scroll down after search
if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)) { if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)) {
@ -822,14 +810,14 @@ export class AppImManager {
} }
public setScroll() { 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.scroll = this.scrollable.container;
this.bubblesContainer.append(this.goDownBtn); this.bubblesContainer.append(this.goDownBtn);
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true); this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false); 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.addEventListener('scroll', this.onScroll.bind(this));
this.scroll.parentElement.classList.add('scrolled-down'); this.scroll.parentElement.classList.add('scrolled-down');
@ -850,30 +838,7 @@ export class AppImManager {
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = ''; this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = '';
} }
Promise.all([ appProfileManager.getChatFull(chat.id).then((chatInfo: any) => {
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);
}
this.log('chatInfo res:', chatInfo); this.log('chatInfo res:', chatInfo);
if(chatInfo.pinned_msg_id) { // request pinned message 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); let participants_count = chatInfo.participants_count || (chatInfo.participants && chatInfo.participants.participants && chatInfo.participants.participants.length);
if(participants_count) { if(participants_count) {
let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
if(onlines > 1) {
subtitle += ', ' + numberWithCommas(onlines) + ' online';
}
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle; 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 } else if(!appUsersManager.isBot(this.peerID)) { // user
@ -1071,7 +1041,7 @@ export class AppImManager {
} }
this.scrollable.container.append(this.chatInner); this.scrollable.container.append(this.chatInner);
this.scrollable.attachSentinels(); //this.scrollable.attachSentinels();
//this.scrollable.container.insertBefore(this.chatInner, this.scrollable.container.lastElementChild); //this.scrollable.container.insertBefore(this.chatInner, this.scrollable.container.lastElementChild);
this.lazyLoadQueue.unlock(); this.lazyLoadQueue.unlock();
@ -1328,34 +1298,40 @@ export class AppImManager {
public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean) { public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean) {
let promises: Promise<any>[] = []; let promises: Promise<any>[] = [];
(Array.from(bubble.querySelectorAll('img, image, video')) as HTMLImageElement[]).forEach(el => { (Array.from(bubble.querySelectorAll('img, video')) as HTMLImageElement[]).forEach(el => {
if(el.tagName == 'VIDEO') { if(el instanceof HTMLVideoElement) {
let source = el.firstElementChild as HTMLSourceElement; let source = el.firstElementChild as HTMLSourceElement;
if(!source || !source.src) { if(!source || !source.src) {
this.log.warn('no source', el, source, 'src', source.src); this.log.warn('no source', el, source, 'src', source.src);
return; return;
} } else if(el.readyState >= 4) return;
} else if(el.complete || (!el.src && !el.getAttribute('href'))) 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) => { let promise = new Promise((resolve, reject) => {
if(el.tagName == 'VIDEO') { let r: () => boolean;
el.addEventListener('loadeddata', () => { let onLoad = () => {
clearTimeout(timeout); clearTimeout(timeout);
resolve(); resolve();
}); };
if(el instanceof HTMLVideoElement) {
el.addEventListener('loadeddata', onLoad);
r = () => el.readyState >= 4;
} else { } else {
el.addEventListener('load', () => { el.addEventListener('load', onLoad);
clearTimeout(timeout); r = () => el.complete;
resolve();
});
} }
// for safari
let c = () => r() ? onLoad() : window.requestAnimationFrame(c);
window.requestAnimationFrame(c);
let timeout = setTimeout(() => { let timeout = setTimeout(() => {
console.log('did not called', el, el.parentElement, el.complete, src); console.log('did not called', el, el.parentElement, el.complete, src);
reject(); reject();
}, 5000); }, 1500);
}); });
promises.push(promise); promises.push(promise);
@ -2092,7 +2068,7 @@ export class AppImManager {
avatarElem.setAttribute('peer-title', message.fwd_from.from_name); 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); this.log('exec loadDialogPhoto', message);
@ -2151,43 +2127,28 @@ export class AppImManager {
console.time('appImManager render history'); console.time('appImManager render history');
let firstLoad = !!this.setPeerPromise && false;
let peerID = this.peerID;
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
let method = (reverse ? history.shift : history.pop).bind(history); 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 realLength = this.scrollable.length;
let previousScrollHeightMinusTop: number; let previousScrollHeightMinusTop: number;
if(realLength > 0 && reverse) { if(realLength > 0 && reverse) {
this.messagesQueueOnRender = () => { this.messagesQueueOnRender = () => {
let scrollTop = realLength ? this.scrollable.scrollTop : 0; let scrollTop = this.scrollable.scrollTop;
previousScrollHeightMinusTop = realLength ? this.scrollable.scrollHeight - scrollTop : 0; previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop); 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(() => { (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.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, previousScrollHeightMinusTop);
this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop;
} }

21
src/lib/appManagers/appMediaViewer.ts

@ -57,6 +57,8 @@ export class AppMediaViewer {
private reverse = false; // reverse means next = higher msgid private reverse = false; // reverse means next = higher msgid
private needLoadMore = true; private needLoadMore = true;
private pageEl = document.getElementById('page-chats') as HTMLDivElement;
constructor() { constructor() {
this.log = logger('AMV'); this.log = logger('AMV');
@ -568,7 +570,7 @@ export class AppMediaViewer {
return promise; return promise;
} }
public updateMediaSource(target: HTMLElement, url: string, tagName: 'source' | 'image') { public updateMediaSource(target: HTMLElement, url: string, tagName: 'source' | 'img') {
//if(target instanceof SVGSVGElement) { //if(target instanceof SVGSVGElement) {
let el = target.querySelector(tagName) as HTMLElement; let el = target.querySelector(tagName) as HTMLElement;
renderImageFromUrl(el, url); renderImageFromUrl(el, url);
@ -665,17 +667,20 @@ export class AppMediaViewer {
////////this.log('wasActive:', wasActive); ////////this.log('wasActive:', wasActive);
if(useContainerAsTarget) {
target = target.querySelector('img, video') || target;
}
let mover = this.content.mover; 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; let maxHeight = appPhotosManager.windowH - 100;
if(isVideo) { if(isVideo) {
let size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight); appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight);
////////this.log('will wrap video', media, size); ////////this.log('will wrap video', media, size);
if(useContainerAsTarget) target = target.querySelector('img, video') || target;
let afterTimeout = this.setMoverToTarget(target, false, fromRight); let afterTimeout = this.setMoverToTarget(target, false, fromRight);
//return; // set and don't move //return; // set and don't move
//if(wasActive) return; //if(wasActive) return;
@ -745,8 +750,6 @@ export class AppMediaViewer {
} else { } else {
let size = appPhotosManager.setAttachmentSize(media.id, container, maxWidth, maxHeight); let size = appPhotosManager.setAttachmentSize(media.id, container, maxWidth, maxHeight);
if(useContainerAsTarget) target = target.querySelector('img, video') || target;
let afterTimeout = this.setMoverToTarget(target, false, fromRight); let afterTimeout = this.setMoverToTarget(target, false, fromRight);
//return; // set and don't move //return; // set and don't move
//if(wasActive) return; //if(wasActive) return;
@ -766,8 +769,8 @@ export class AppMediaViewer {
let url = media.url; let url = media.url;
if(target instanceof SVGSVGElement) { if(target instanceof SVGSVGElement) {
this.updateMediaSource(target, url, 'image'); this.updateMediaSource(target, url, 'img');
this.updateMediaSource(mover, url, 'image'); this.updateMediaSource(mover, url, 'img');
} else { } else {
let aspecter = mover.firstElementChild; let aspecter = mover.firstElementChild;
let image = aspecter.firstElementChild as HTMLImageElement; let image = aspecter.firstElementChild as HTMLImageElement;

8
src/lib/appManagers/appMessagesManager.ts

@ -224,7 +224,7 @@ export class AppMessagesManager {
}).catch(resolve); }).catch(resolve);
}); });
//setInterval(() => this.saveState(), 10000); setInterval(() => this.saveState(), 10000);
} }
public saveState() { public saveState() {
@ -1166,7 +1166,7 @@ export class AppMessagesManager {
} else { } else {
let doc = messageMedia.document; let doc = messageMedia.document;
appDocsManager.saveDoc(doc); appDocsManager.saveDoc(doc);
inputMedia = appDocsManager.getInputByID(doc.id); inputMedia = appDocsManager.getMediaInputByID(doc.id);
} }
inputs.push({ inputs.push({
@ -3002,7 +3002,11 @@ export class AppMessagesManager {
if(messageID > maxID) { if(messageID > maxID) {
continue; continue;
} }
message = this.messagesStorage[messageID]; message = this.messagesStorage[messageID];
if(!message) {
continue;
}
if(message.pFlags.out != isOut) { if(message.pFlags.out != isOut) {
continue; continue;

50
src/lib/appManagers/appPhotosManager.ts

@ -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; let arr: Uint8Array;
if(!isSticker) { if(!isSticker) {
arr = AppPhotosManager.jf.concat(bytes.slice(3), AppPhotosManager.Df); arr = AppPhotosManager.jf.concat(bytes.slice(3), AppPhotosManager.Df);
@ -187,9 +187,7 @@ export class AppPhotosManager {
arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
} }
//console.log('setAttachmentPreview', bytes, arr, div, isSticker); //console.log('getPreviewURLFromBytes', bytes, arr, div, isSticker);
let blob = new Blob([arr], {type: "image/jpeg"});
/* let reader = new FileReader(); /* let reader = new FileReader();
reader.onloadend = () => { reader.onloadend = () => {
@ -197,8 +195,19 @@ export class AppPhotosManager {
}; };
reader.readAsDataURL(blob); */ 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) { if(background) {
let url = URL.createObjectURL(blob);
let img = new Image(); let img = new Image();
img.src = url; img.src = url;
img.addEventListener('load', () => { img.addEventListener('load', () => {
@ -206,30 +215,23 @@ export class AppPhotosManager {
}); });
return element; return element;
//element.style.backgroundImage = 'url(' + url + ')';
} else { } else {
if(element instanceof SVGSVGElement) { if(element instanceof HTMLImageElement) {
let image = element.firstElementChild as SVGImageElement || document.createElementNS("http://www.w3.org/2000/svg", "image"); element.src = url;
image.setAttributeNS(null, 'href', URL.createObjectURL(blob));
element.append(image);
return image;
} else if(element instanceof HTMLImageElement) {
element.src = URL.createObjectURL(blob);
return element; return element;
} else { } else {
let img = new Image(); let img = new Image();
img.style.width = '100%'; /* img.style.width = '100%';
img.style.height = '100%'; img.style.height = '100%'; */
img.src = URL.createObjectURL(blob); img.src = url;
element.append(img); element.append(img);
return 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; let photo: /* MTDocument | MTPhoto */any = null;
if(typeof(photoID) === 'string') { if(typeof(photoID) === 'string') {
@ -243,7 +245,7 @@ export class AppPhotosManager {
//console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div); //console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div);
let sizes = photo.sizes || photo.thumbs; 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); this.setAttachmentPreview(sizes[0].bytes, element, isSticker);
} }
@ -258,17 +260,11 @@ export class AppPhotosManager {
} }
let {w, h} = calcImageInBox(width, height, boxWidth, boxHeight); let {w, h} = calcImageInBox(width, height, boxWidth, boxHeight);
if(element instanceof SVGSVGElement) { if(element instanceof SVGForeignObjectElement) {
element.setAttributeNS(null, 'width', '' + w); element.setAttributeNS(null, 'width', '' + w);
element.setAttributeNS(null, 'height', '' + h); element.setAttributeNS(null, 'height', '' + h);
//console.log('set dimensions to svg element:', element, w, 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 { } else {
element.style.width = w + 'px'; element.style.width = w + 'px';
element.style.height = h + 'px'; element.style.height = h + 'px';

4
src/lib/appManagers/appSidebarRight.ts

@ -111,14 +111,14 @@ class AppSidebarRight {
let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement; let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement;
this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement; 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 = () => { this.scroll.onScrolledBottom = () => {
if(this.sharedMediaSelected && this.sharedMediaSelected.childElementCount/* && false */) { if(this.sharedMediaSelected && this.sharedMediaSelected.childElementCount/* && false */) {
this.log('onScrolledBottom will load media'); this.log('onScrolledBottom will load media');
this.loadSidebarMedia(true); this.loadSidebarMedia(true);
} }
}; };
this.scroll.attachSentinels(undefined, 400); //this.scroll.attachSentinels(undefined, 400);
horizontalMenu(this.profileTabs, container, (id, tabContent) => { horizontalMenu(this.profileTabs, container, (id, tabContent) => {
if(this.prevTabID == id) return; if(this.prevTabID == id) return;

55
src/lib/appManagers/appStickersManager.ts

@ -51,6 +51,8 @@ class AppStickersManager {
private stickerSets: { private stickerSets: {
[stickerSetID: string]: MTStickerSetFull [stickerSetID: string]: MTStickerSetFull
} = {}; } = {};
private saveSetsTimeout: number;
constructor() { constructor() {
AppStorage.get<{ AppStorage.get<{
@ -59,17 +61,15 @@ class AppStickersManager {
if(sets) { if(sets) {
for(let id in sets) { for(let id in sets) {
let set = sets[id]; let set = sets[id];
set.documents.forEach(doc => { this.saveStickers(set.documents);
this.saveSticker(doc);
});
} }
this.stickerSets = sets; this.stickerSets = sets;
} }
if(!this.stickerSets['emoji']) { //if(!this.stickerSets['emoji']) {
this.getStickerSet({id: 'emoji', access_hash: ''}); this.getStickerSet({id: 'emoji', access_hash: ''});
} //}
}); });
} }
@ -81,6 +81,12 @@ class AppStickersManager {
return doc; return doc;
} }
public saveStickers(docs: MTDocument[]) {
docs.forEach((doc, idx) => {
docs[idx] = this.saveSticker(doc);
});
}
public getSticker(fileID: string) { public getSticker(fileID: string) {
return this.documents[fileID]; return this.documents[fileID];
@ -115,6 +121,20 @@ class AppStickersManager {
return stickerSet; 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) { public getAnimatedEmojiSticker(emoji: string) {
let stickerSet = this.stickerSets.emoji; let stickerSet = this.stickerSets.emoji;
@ -122,7 +142,7 @@ class AppStickersManager {
return stickerSet.documents.find(doc => doc.stickerEmojiRaw == emoji); return stickerSet.documents.find(doc => doc.stickerEmojiRaw == emoji);
} }
public async saveStickerSet(res: { public saveStickerSet(res: {
_: "messages.stickerSet", _: "messages.stickerSet",
set: MTStickerSet, set: MTStickerSet,
packs: any[], packs: any[],
@ -136,12 +156,18 @@ class AppStickersManager {
documents: res.documents documents: res.documents
}; };
res.documents.forEach(this.saveSticker.bind(this)); this.saveStickers(res.documents);
//console.log('stickers wrote', this.stickerSets); //console.log('stickers wrote', this.stickerSets);
await AppStorage.set({ if(this.saveSetsTimeout) return;
stickerSets: this.stickerSets this.saveSetsTimeout = setTimeout(() => {
}); AppStorage.set({
stickerSets: this.stickerSets
});
this.saveSetsTimeout = 0;
}, 0);
/* AppStorage.get('stickerSets').then((sets: any) => { /* AppStorage.get('stickerSets').then((sets: any) => {
this.stickerSets = sets; this.stickerSets = sets;
@ -153,7 +179,9 @@ class AppStickersManager {
let thumb = stickerSet.thumb; let thumb = stickerSet.thumb;
let dcID = stickerSet.thumb_dc_id; let dcID = stickerSet.thumb_dc_id;
let promise = apiFileManager.downloadSmallFile({ let isAnimated = stickerSet.pFlags?.animated;
let promise = apiFileManager.downloadFile(dcID, {
_: 'inputStickerSetThumb', _: 'inputStickerSetThumb',
stickerset: { stickerset: {
_: 'inputStickerSetID', _: 'inputStickerSetID',
@ -162,7 +190,10 @@ class AppStickersManager {
}, },
volume_id: thumb.location.volume_id, volume_id: thumb.location.volume_id,
local_id: thumb.location.local_id local_id: thumb.location.local_id
}, {dcID: dcID}); }, thumb.size, {
stickerType: isAnimated ? 2 : 1,
mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'
});
return promise; return promise;
} }

94
src/lib/appManagers/appWebpManager.ts

@ -1,31 +1,21 @@
// @ts-ignore
//import createWorker from 'offscreen-canvas/create-worker';
class AppWebpManager { class AppWebpManager {
public webpMachine: any = null; private webpMachine: any = null;
public loaded: Promise<void>; private loaded: Promise<void>;
public busyPromise: Promise<string>; private busyPromise: Promise<Uint8Array | void>;
public queue: {bytes: Uint8Array, img: HTMLImageElement, callback: (url: string) => void}[] = []; private queue: {bytes: Uint8Array, callback: (res: Uint8Array) => void}[] = [];
//public worker: any;
public webpSupport: Promise<boolean> = null; private testPromise: Promise<boolean> = null;
public webpSupport = false;
constructor() { constructor() {
//let canvas = document.createElement('canvas'); this.testWebpSupport();
//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 => {
});
} }
public loadWebpHero() { private loadWebpHero() {
if(this.loaded) return this.loaded; if(this.loaded) return this.loaded;
this.loaded = new Promise(async(resolve, reject) => { this.loaded = new Promise(async(resolve, reject) => {
let res = await this.webpSupported(); let res = await this.testWebpSupport();
if(!res) { if(!res) {
(window as any).webpLoaded = () => { (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.webpMachine.decode(bytes);
//return this.worker.post({message: 'webpBytes', bytes});
} }
async processQueue() { private async processQueue() {
if(this.busyPromise) return; 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) { if(!this.loaded) {
this.loadWebpHero(); this.loadWebpHero();
@ -65,13 +54,11 @@ class AppWebpManager {
await this.loaded; await this.loaded;
this.busyPromise = this.convert(bytes); this.busyPromise = this.convert(bytes);
let url = await this.busyPromise; let res = await this.busyPromise;
let imgTemp = new Image();
imgTemp.src = url; console.log('converted webp', res);
imgTemp.onload = () => {
img.src = imgTemp.src; callback(res as Uint8Array);
};
callback(url);
this.busyPromise = null; this.busyPromise = null;
@ -80,42 +67,33 @@ class AppWebpManager {
} }
} }
webpSupported() { public testWebpSupport() {
if(this.webpSupport) return this.webpSupport; if(this.testPromise) return this.testPromise;
return this.webpSupport = new Promise((resolve, reject) => { return this.testPromise = new Promise((resolve, reject) => {
var webP = new Image(); let webP = new Image();
webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' + webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' +
'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA'; 'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';
webP.onload = webP.onerror = () => { webP.onload = webP.onerror = () => {
resolve(webP.height === 2); resolve(this.webpSupport = webP.height === 2/* && false */);
}; };
}); });
} }
async polyfillImage(img: HTMLImageElement, blob: Blob) { public isSupported() {
/* console.log('polyfillImage', this); return this.webpSupport;
return this.webpMachine.polyfillImage(image); */ }
//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) => { public convertToPng(bytes: Uint8Array) {
const reader = new FileReader(); console.warn('convertToPng!');
reader.addEventListener('loadend', (e) => { return new Promise<Uint8Array>((resolve, reject) => {
// @ts-ignore // @ts-ignore
let bytes = new Uint8Array(e.srcElement.result); this.queue.push({bytes, callback: resolve});
this.processQueue();
this.queue.push({bytes, img, callback: resolve});
this.processQueue();
});
reader.readAsArrayBuffer(blob);
}); });
} }
} }
export default new AppWebpManager(); const appWebpManager = new AppWebpManager();
(window as any).appWebpManager = appWebpManager;
export default appWebpManager;

18
src/lib/bin_utils.ts

@ -342,24 +342,6 @@ export function longFromInts(high: number, low: number) {
return bigint(high).shiftLeft(32).add(bigint(low)).toString(10); 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) { export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, full = false, prepend = false) {
let len = bytes.byteLength || bytes.length; let len = bytes.byteLength || bytes.length;
let needPadding = blockSize - (len % blockSize); let needPadding = blockSize - (len % blockSize);

12
src/lib/filemanager.ts

@ -1,4 +1,4 @@
import {blobSafeMimeType, blobConstruct, bytesToBase64} from './bin_utils'; import {blobConstruct} from './bin_utils';
/* import 'web-streams-polyfill/ponyfill'; /* import 'web-streams-polyfill/ponyfill';
// @ts-ignore // @ts-ignore
@ -139,14 +139,6 @@ class FileManager {
return fakeFileWriter; 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) { public download(blob: Blob, mimeType: string, fileName: string) {
if(window.navigator && navigator.msSaveBlob !== undefined) { if(window.navigator && navigator.msSaveBlob !== undefined) {
@ -180,7 +172,7 @@ class FileManager {
return; 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; var anchor = document.createElementNS('http://www.w3.org/1999/xhtml', 'a') as HTMLAnchorElement;
anchor.href = url as string; anchor.href = url as string;
anchor.download = fileName; anchor.download = fileName;

2
src/lib/idb.ts

@ -51,6 +51,8 @@ class IdbFileStorage {
request.onsuccess = (event) => { request.onsuccess = (event) => {
finished = true; finished = true;
var db = request.result; var db = request.result;
console.log('Opened IndexedDB');
db.onerror = (error) => { db.onerror = (error) => {
this.storageIsAvailable = false; this.storageIsAvailable = false;

8
src/lib/lottieLoader.ts

@ -96,7 +96,7 @@ class LottieLoader {
params.renderer = 'svg'; params.renderer = 'svg';
//} //}
params.rendererSettings = { let rendererSettings = {
//context: context, // the canvas context //context: context, // the canvas context
//preserveAspectRatio: 'xMinYMin slice', // Supports the same options as the svg element's preserveAspectRatio property //preserveAspectRatio: 'xMinYMin slice', // Supports the same options as the svg element's preserveAspectRatio property
clearCanvas: true, clearCanvas: true,
@ -104,6 +104,12 @@ class LottieLoader {
hideOnTransparent: true, //Boolean, only svg renderer, hides elements when opacity reaches 0 (defaults to true), 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.lottie) {
if(!this.loaded) this.loadLottie(); if(!this.loaded) this.loadLottie();
await this.loaded; await this.loaded;

122
src/lib/mtproto/apiFileManager.ts

@ -5,6 +5,7 @@ import FileManager from "../filemanager";
//import apiManager from "./apiManager"; //import apiManager from "./apiManager";
import apiManager from "./mtprotoworker"; import apiManager from "./mtprotoworker";
import { logger, deferredPromise, CancellablePromise } from "../polyfill"; import { logger, deferredPromise, CancellablePromise } from "../polyfill";
import appWebpManager from "../appManagers/appWebpManager";
export class ApiFileManager { export class ApiFileManager {
public cachedSavePromises: { public cachedSavePromises: {
@ -14,7 +15,7 @@ export class ApiFileManager {
[fileName: string]: any [fileName: string]: any
} = {}; } = {};
public cachedDownloads: { public cachedDownloads: {
[fileName: string]: any [fileName: string]: Blob
} = {}; } = {};
/* public indexedKeys: Set<string> = new Set(); /* 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._) { switch(location._) {
case 'inputDocumentFileLocation': case 'inputDocumentFileLocation': {
var fileName = (location.file_name as string || '').split('.'); let fileName = (location.file_name as string || '').split('.');
var ext: string = fileName[fileName.length - 1] || ''; let ext = fileName[fileName.length - 1] || '';
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
ext += '.png'
}
var versionPart = location.version ? ('v' + location.version) : ''; let thumbPart = location.thumb_size ? '_' + location.thumb_size : '';
return (fileName[0] ? fileName[0] + '_' : '') + location.id + versionPart + (ext ? '.' + ext : ext); return (fileName[0] ? fileName[0] + '_' : '') + location.id + thumbPart + (ext ? '.' + ext : ext);
}
default: default: {
if(!location.volume_id && !location.file_reference) { if(!location.volume_id && !location.file_reference) {
this.log.trace('Empty location', location); this.log.trace('Empty location', location);
} }
let ext = 'jpg';
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
ext += '.png'
}
if(location.volume_id) { if(location.volume_id) {
return location.volume_id + '_' + location.local_id + '.' + ext; return location.volume_id + '_' + location.local_id + '.' + ext;
} else { } else {
return location.id + '_' + location.access_hash + '.' + ext; return location.id + '_' + location.access_hash + '.' + ext;
} }
}
} }
} }
@ -145,10 +159,11 @@ export class ApiFileManager {
return this.cachedSavePromises[fileName]; return this.cachedSavePromises[fileName];
} }
public downloadSmallFile(location: any, options: { public downloadSmallFile(location: any, options: Partial<{
mimeType?: string, mimeType: string,
dcID?: number, dcID: number,
} = {}): Promise<Blob> { stickerType: number
}> = {}): Promise<Blob> {
if(!FileManager.isAvailable()) { if(!FileManager.isAvailable()) {
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'}); return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
} }
@ -159,10 +174,16 @@ export class ApiFileManager {
//this.log('downloadSmallFile', location, options); //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 dcID = options.dcID || location.dc_id;
let mimeType = options.mimeType || 'image/jpeg'; let mimeType = options.mimeType || 'image/jpeg';
var fileName = this.getFileName(location); let fileName = this.getFileName(location, options);
var cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName]; let cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
//this.log('downloadSmallFile!', location, options, fileName, cachedPromise); //this.log('downloadSmallFile!', location, options, fileName, cachedPromise);
@ -170,13 +191,14 @@ export class ApiFileManager {
return cachedPromise; 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; return this.cachedDownloads[fileName] = blob;
}, () => { }).catch(() => {
var downloadPromise = this.downloadRequest(dcID, () => { let downloadPromise = this.downloadRequest(dcID, () => {
var inputLocation = location; let inputLocation = location;
if(!inputLocation._ || inputLocation._ == 'fileLocation') { if(!inputLocation._ || inputLocation._ == 'fileLocation') {
inputLocation = Object.assign({}, location, {_: 'inputFileLocation'}); inputLocation = Object.assign({}, location, {_: 'inputFileLocation'});
} }
@ -196,18 +218,17 @@ export class ApiFileManager {
}); });
}, dcID); }, dcID);
var processDownloaded = (bytes: Uint8Array) => { let processDownloaded = (bytes: Uint8Array) => {
//this.log('processDownloaded', location, bytes); //this.log('processDownloaded', location, bytes);
return Promise.resolve(bytes); if(processSticker) {
/* if(!location.sticker || WebpManager.isWebpSupported()) { return appWebpManager.convertToPng(bytes);
return qSync.when(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 downloadPromise.then((result: any) => {
return processDownloaded(result.bytes).then((proccessedResult) => { return processDownloaded(result.bytes).then((proccessedResult) => {
return FileManager.write(fileWriter, proccessedResult).then(() => { return FileManager.write(fileWriter, proccessedResult).then(() => {
@ -236,12 +257,12 @@ export class ApiFileManager {
}); });
} */ } */
public downloadFile(dcID: number, location: any, size: number, options: { public downloadFile(dcID: number, location: any, size: number, options: Partial<{
mimeType?: string, mimeType: string,
dcID?: number, toFileEntry: any,
toFileEntry?: any, limitPart: number,
limitPart?: number stickerType: number
} = {}): CancellablePromise<Blob> { }> = {}): CancellablePromise<Blob> {
if(!FileManager.isAvailable()) { if(!FileManager.isAvailable()) {
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'}); return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
} }
@ -250,11 +271,21 @@ export class ApiFileManager {
this.getIndexedKeys(); 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) // this.log('Dload file', dcID, location, size)
var fileName = this.getFileName(location); let fileName = this.getFileName(location, options);
var toFileEntry = options.toFileEntry || null; let toFileEntry = options.toFileEntry || null;
var cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName]; let cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
var fileStorage = this.getFileStorage(); let fileStorage = this.getFileStorage();
//this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments); //this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments);
@ -272,7 +303,7 @@ export class ApiFileManager {
if(blob.size < size) { if(blob.size < size) {
this.log('downloadFile need to deleteFile, wrong size:', 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); return this.downloadFile(dcID, location, size, options);
}).catch(() => { }).catch(() => {
return this.downloadFile(dcID, location, size, options); return this.downloadFile(dcID, location, size, options);
@ -303,10 +334,11 @@ export class ApiFileManager {
fileStorage.getFile(fileName, size).then(async(blob: Blob) => { fileStorage.getFile(fileName, size).then(async(blob: Blob) => {
//this.log('is that i wanted'); //this.log('is that i wanted');
//throw '';
if(blob.size < size) { if(blob.size < size) {
this.log('downloadFile need to deleteFile 2, wrong size:', 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; throw false;
} }
@ -322,13 +354,12 @@ export class ApiFileManager {
//var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType); //var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
var fileWriterPromise = toFileEntry ? Promise.resolve(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType); var fileWriterPromise = toFileEntry ? Promise.resolve(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
var processDownloaded = (bytes: any) => { var processDownloaded = (bytes: Uint8Array) => {
return Promise.resolve(bytes); if(processSticker) {
/* if(!processSticker) { return appWebpManager.convertToPng(bytes);
return Promise.resolve(bytes);
} }
return WebpManager.getPngBlobFromWebp(bytes); */ return Promise.resolve(bytes);
}; };
fileWriterPromise.then((fileWriter: any) => { fileWriterPromise.then((fileWriter: any) => {
@ -391,7 +422,7 @@ export class ApiFileManager {
return Promise.resolve(); return Promise.resolve();
} }
return processDownloaded(result.bytes).then((processedResult: Uint8Array) => { return processDownloaded(result.bytes).then((processedResult) => {
return FileManager.write(fileWriter, processedResult).then(() => { return FileManager.write(fileWriter, processedResult).then(() => {
writeFileDeferred.resolve(); writeFileDeferred.resolve();
}, errorHandler).then(() => { }, errorHandler).then(() => {
@ -438,12 +469,13 @@ export class ApiFileManager {
return deferred; return deferred;
} }
public deleteFile(fileName: any) { public deleteFile(fileName: string) {
fileName = typeof(fileName) == 'string' ? fileName : this.getFileName(fileName);
this.log('will delete file:', fileName); this.log('will delete file:', fileName);
delete this.cachedDownloadPromises[fileName]; delete this.cachedDownloadPromises[fileName];
delete this.cachedDownloads[fileName]; delete this.cachedDownloads[fileName];
delete this.cachedSavePromises[fileName]; delete this.cachedSavePromises[fileName];
return this.getFileStorage().deleteFile(fileName); return this.getFileStorage().deleteFile(fileName);
} }

9
src/lib/mtproto/networker.ts

@ -1,7 +1,7 @@
import {isObject} from '../bin_utils'; import {isObject} from '../bin_utils';
import {convertToUint8Array, import {convertToUint8Array,
bufferConcat, nextRandomInt, bytesToHex, longToBytes, bufferConcat, nextRandomInt, bytesToHex, longToBytes,
bytesCmp, uintToInt, bigStringInt} from '../bin_utils'; bytesCmp, bigStringInt} from '../bin_utils';
import {TLDeserialization, TLSerialization} from './tl_utils'; import {TLDeserialization, TLSerialization} from './tl_utils';
import CryptoWorker from '../crypto/cryptoworker'; import CryptoWorker from '../crypto/cryptoworker';
import AppStorage from '../storage'; import AppStorage from '../storage';
@ -296,8 +296,7 @@ class MTPNetworker {
var isClean = this.cleanupSent(); var isClean = this.cleanupSent();
//this.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean, this); //this.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean, this);
if((this.longPollPending && Date.now() < this.longPollPending) || if((this.longPollPending && Date.now() < this.longPollPending) ||
this.offline || this.offline) {
NetworkerFactory.akStopped) {
//this.log('No lp this time'); //this.log('No lp this time');
return false; return false;
} }
@ -502,7 +501,7 @@ class MTPNetworker {
public performScheduledRequest() { public performScheduledRequest() {
// this.log('scheduled', this.dcID, this.iii) // this.log('scheduled', this.dcID, this.iii)
if(this.offline || NetworkerFactory.akStopped) { if(this.offline) {
this.log('Cancel scheduled'); this.log('Cancel scheduled');
return false; return false;
} }
@ -1027,7 +1026,7 @@ class MTPNetworker {
public processError(rawError: {error_message: string, error_code: number}) { public processError(rawError: {error_message: string, error_code: number}) {
var matches = (rawError.error_message || '').match(/^([A-Z_0-9]+\b)(: (.+))?/) || []; 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 { return {
code: !rawError.error_code || rawError.error_code <= 0 ? 500 : rawError.error_code, code: !rawError.error_code || rawError.error_code <= 0 ? 500 : rawError.error_code,

18
src/lib/mtproto/networkerFactory.ts

@ -2,24 +2,6 @@ import { MTPNetworker } from "./networker";
export class NetworkerFactory { export class NetworkerFactory {
public updatesProcessor: (obj: any, bool: boolean) => void = null; 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) { public setUpdatesProcessor(callback: (obj: any, bool: boolean) => void) {
this.updatesProcessor = callback; this.updatesProcessor = callback;

14
src/lib/mtproto/tl_utils.ts

@ -5,7 +5,7 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * 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'; import Schema from './schema';
/// #if MTPROTO_WORKER /// #if MTPROTO_WORKER
@ -131,8 +131,8 @@ class TLSerialization {
} }
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]'); this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]');
this.writeInt(intToUint(divRem[0].intValue()), (field || '') + ':long[high]'); this.writeInt(divRem[0].intValue(), (field || '') + ':long[high]');
} }
public storeDouble(f: any, field?: string) { public storeDouble(f: any, field?: string) {
@ -250,7 +250,7 @@ class TLSerialization {
throw new Error('No method ' + methodName + ' found'); throw new Error('No method ' + methodName + ' found');
} }
this.storeInt(intToUint(methodData.id), methodName + '[id]'); this.storeInt(methodData.id, methodName + '[id]');
var param, type; var param, type;
var i, condType; var i, condType;
@ -347,7 +347,7 @@ class TLSerialization {
} }
if(!isBare) { if(!isBare) {
this.writeInt(intToUint(constructorData.id), field + '[' + predicate + '][id]'); this.writeInt(constructorData.id, field + '[' + predicate + '][id]');
} }
var param, type: string; var param, type: string;
@ -601,7 +601,7 @@ class TLDeserialization {
if(type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') { if(type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') {
if(type.charAt(0) == 'V') { if(type.charAt(0) == 'V') {
var constructor = this.readInt(field + '[id]'); var constructor = this.readInt(field + '[id]');
var constructorCmp = uintToInt(constructor); var constructorCmp = constructor;
if(constructorCmp == gzipPacked) { // Gzip packed if(constructorCmp == gzipPacked) { // Gzip packed
var compressed = this.fetchBytes(field + '[packed_string]'); var compressed = this.fetchBytes(field + '[packed_string]');
@ -657,7 +657,7 @@ class TLDeserialization {
} }
} else { } else {
var constructor = this.readInt(field + '[id]'); var constructor = this.readInt(field + '[id]');
var constructorCmp = uintToInt(constructor); var constructorCmp = constructor;
if(constructorCmp == gzipPacked) { // Gzip packed if(constructorCmp == gzipPacked) { // Gzip packed
var compressed = this.fetchBytes(field + '[packed_string]'); var compressed = this.fetchBytes(field + '[packed_string]');

6
src/lib/utils.js

@ -289,8 +289,10 @@ export function getSelectedText() {
export const $rootScope = { export const $rootScope = {
$broadcast: (name/* : string */, detail/*? : any */) => { $broadcast: (name/* : string */, detail/*? : any */) => {
console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail); if(name != 'user_update') {
//console.trace(); console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail);
}
let myCustomEvent = new CustomEvent(name, {detail}); let myCustomEvent = new CustomEvent(name, {detail});
document.dispatchEvent(myCustomEvent); document.dispatchEvent(myCustomEvent);
}, },

44
src/lib/webp.ts

@ -1,7 +1,6 @@
import {Webp} from "webp-hero/libwebp/dist/webp.js" 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 {} 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) * - can only decode images one-at-a-time (otherwise will throw busy error)
*/ */
export class WebpMachine { export class WebpMachine {
private readonly webp: Webp private readonly webp: Webp;
private readonly webpSupport: Promise<boolean> private busy = false;
private busy = false
private cache: {[key: string]: string} = {} constructor() {
this.webp = new Webp();
constructor({
webp = new Webp(),
webpSupport = detectWebpSupport()
} = {}) {
this.webp = webp;
this.webp.Module.doNotCaptureKeyboard = true; this.webp.Module.doNotCaptureKeyboard = true;
this.webpSupport = webpSupport;
} }
/** /**
* Decode raw webp data into a png data url * 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"); if(this.busy) throw new WebpMachineError("cannot decode when already busy");
this.busy = true; this.busy = true;
try { try {
await relax(); return relax().then(() => {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
this.webp.setCanvas(canvas); this.webp.setCanvas(canvas);
this.webp.webpToSdl(webpData, webpData.length); this.webp.webpToSdl(webpData, webpData.length);
this.busy = false; this.busy = false;
return canvas.toDataURL();
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) { } catch(error) {
this.busy = false; this.busy = false;
error.name = WebpMachineError.name; error.name = WebpMachineError.name;

75
src/pages/pageAuthCode.ts

@ -28,7 +28,12 @@ let sentTypeElement: HTMLParagraphElement = null;
let onFirstMount = (): Promise<any> => { let onFirstMount = (): Promise<any> => {
let needFrame = 0, lastLength = 0; 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; 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) => { let submitCode = (code: string) => {
codeInput.setAttribute('disabled', 'true'); codeInput.setAttribute('disabled', 'true');
@ -135,7 +147,7 @@ let onFirstMount = (): Promise<any> => {
}); });
pageIm.mount(); pageIm.mount();
if(animation) animation.destroy(); cleanup();
break; break;
case 'auth.authorizationSignUpRequired': case 'auth.authorizationSignUpRequired':
console.log('Registration needed!'); console.log('Registration needed!');
@ -145,7 +157,7 @@ let onFirstMount = (): Promise<any> => {
'phone_code_hash': authCode.phone_code_hash 'phone_code_hash': authCode.phone_code_hash
}); });
if(animation) animation.destroy(); cleanup();
break; break;
default: default:
codeInput.innerText = response._; codeInput.innerText = response._;
@ -158,7 +170,7 @@ let onFirstMount = (): Promise<any> => {
case 'SESSION_PASSWORD_NEEDED': case 'SESSION_PASSWORD_NEEDED':
console.warn('pageAuthCode: SESSION_PASSWORD_NEEDED'); console.warn('pageAuthCode: SESSION_PASSWORD_NEEDED');
err.handled = true; err.handled = true;
if(animation) animation.destroy(); cleanup();
pagePassword.mount(); pagePassword.mount();
break; break;
case 'PHONE_CODE_EMPTY': case 'PHONE_CODE_EMPTY':
@ -196,8 +208,14 @@ let onFirstMount = (): Promise<any> => {
if(!animation) return; if(!animation) return;
let frame: number; let frame: number;
if(length) frame = Math.round((length > max ? max : length) * (165 / max) + 11.33); if(length) {
else frame = 0; 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]); //animation.playSegments([1, 2]);
let direction = needFrame > frame ? -1 : 1; let direction = needFrame > frame ? -1 : 1;
@ -217,33 +235,68 @@ let onFirstMount = (): Promise<any> => {
//animation.goToAndStop(length / max * ); //animation.goToAndStop(length / max * );
}); });
let imageDiv = page.pageEl.querySelector('.auth-image');
return Promise.all([ return Promise.all([
LottieLoader.loadLottie(), 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') fetch('assets/img/TwoFactorSetupMonkeyTracking.tgs')
.then(res => res.arrayBuffer()) .then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true)) .then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimation({ .then(str => LottieLoader.loadAnimation({
container: page.pageEl.querySelector('.auth-image'), container: imageDiv,
renderer: 'svg', renderer: 'svg',
loop: false, loop: false,
autoplay: false, autoplay: false,
animationData: JSON.parse(str) animationData: JSON.parse(str),
rendererSettings: {
className: 'monkey-tracking'
}
})) }))
.then(_animation => { .then(_animation => {
animation = _animation; animation = _animation;
animation.setSpeed(1); animation.setSpeed(1);
//console.log(animation.getDuration(), animation.getDuration(true)); //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) => { animation.addEventListener('enterFrame', (e: any) => {
//console.log('enterFrame', e, needFrame); //console.log('enterFrame', e, needFrame);
let currentFrame = Math.round(e.currentTime); let currentFrame = Math.round(e.currentTime);
if((e.direction == 1 && currentFrame >= needFrame) || if((e.direction == 1 && currentFrame >= needFrame) ||
(e.direction == -1 && currentFrame <= needFrame)) { (e.direction == -1 && currentFrame <= needFrame)) {
animation.setSpeed(1); animation.setSpeed(1);
animation.pause(); animation.pause();
} }
if(currentFrame == 0 && needFrame == 0 && mIdleSvg) {
mTrackingSvg.style.display = 'none';
mIdleSvg.style.display = '';
idleAnimation.stop();
idleAnimation.play();
}
}); });
}) })
]); ]);

2
src/pages/pageSignIn.ts

@ -206,6 +206,8 @@ let onFirstMount = () => {
putPreloader(this); putPreloader(this);
//this.innerHTML = 'PLEASE WAIT...'; //this.innerHTML = 'PLEASE WAIT...';
return;
let phone_number = telEl.value; let phone_number = telEl.value;
apiManager.invokeApi('auth.sendCode', { apiManager.invokeApi('auth.sendCode', {
//flags: 0, //flags: 0,

1
src/scss/partials/_chat.scss

@ -205,6 +205,7 @@ $time-background: rgba(0, 0, 0, .35);
#attach-file { #attach-file {
&.menu-open { &.menu-open {
color: $color-blue; color: $color-blue;
background-color: transparent;
} }
.btn-menu { .btn-menu {

16
src/scss/partials/_chatBubble.scss

@ -355,8 +355,6 @@
position: relative; position: relative;
img, video { img, video {
width: auto;
height: auto;
max-width: 100%; max-width: 100%;
cursor: pointer; cursor: pointer;
opacity: 1; opacity: 1;
@ -394,13 +392,13 @@
width: max-content; width: max-content;
} }
}
img:not(.emoji), video {
/* object-fit: contain; */ img:not(.emoji), video {
object-fit: cover; /* object-fit: contain; */
width: 100%; object-fit: cover;
height: 100%; width: 100%;
} height: 100%;
} }
&.is-album { &.is-album {

9
src/scss/partials/_emojiDropdown.scss

@ -68,7 +68,7 @@
.category-items { .category-items {
display: grid; display: grid;
grid-column-gap: 2.44px; 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; font-size: 2.25rem;
line-height: 2.25rem; line-height: 2.25rem;
@ -121,10 +121,9 @@
.category-items { .category-items {
width: 100%; width: 100%;
display: flex; display: grid;
align-items: center; grid-template-columns: repeat(5, 1fr);
justify-content: space-between; grid-column-gap: 1px;
flex-wrap: wrap;
> div { > div {
width: 80px; width: 80px;

17
src/scss/partials/_rightSIdebar.scss

@ -1,20 +1,13 @@
#column-right { #column-right {
width: 0%; width: 0%;
/* grid-column: 3; */
position: relative; position: relative;
transition: .2s ease-in-out; transition: .2s ease-in-out;
.profile-container { .sidebar-content {
> .scrollable { min-width: 25vw;
min-width: 25vw;
display: flex;
flex-direction: column;
}
@media (min-width: $large-screen) { @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; flex: 1 1 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; /* height: 100%; */
position: relative; position: relative;
width: 100%; width: 100%;
@ -172,7 +165,7 @@
&-content { &-content {
//min-height: 100%; //min-height: 100%;
min-height: calc(100% - 49px); min-height: calc(100% - 49px);
position: absolute; // FIX THE SAFARI! //position: absolute; // FIX THE SAFARI!
//position: relative; //position: relative;
/* width: 500%; /* width: 500%;
margin-left: -100%; margin-left: -100%;

8
src/scss/partials/_scrollable.scss

@ -34,8 +34,8 @@ div.scrollable::-webkit-scrollbar-thumb {
left: 0px; left: 0px;
bottom: 0px; bottom: 0px;
right: 0px; right: 0px;
display: flex; /* display: flex;
flex-direction: column; flex-direction: column; */
&.scrollable-x { &.scrollable-x {
overflow-x: auto; overflow-x: auto;
@ -50,7 +50,7 @@ div.scrollable::-webkit-scrollbar-thumb {
-ms-overflow-style: none; -ms-overflow-style: none;
} }
&-sentinel { /* &-sentinel {
position: relative; position: relative;
left: 0; left: 0;
height: 1px; height: 1px;
@ -58,7 +58,7 @@ div.scrollable::-webkit-scrollbar-thumb {
background-color: transparent; background-color: transparent;
width: 1px; width: 1px;
min-width: 1px; min-width: 1px;
} } */
/* &.scrollable-x ~ .scrollbar-thumb { /* &.scrollable-x ~ .scrollbar-thumb {
top: auto; top: auto;

1
src/scss/partials/popups/_mediaAttacher.scss

@ -15,7 +15,6 @@
#{$parent}-photo { #{$parent}-photo {
max-height: 320px; max-height: 320px;
margin: 0 auto; margin: 0 auto;
padding-bottom: 8px;
img { img {
object-fit: contain; object-fit: contain;

34
src/scss/style.scss

@ -418,18 +418,11 @@ avatar-element {
@keyframes ripple-effect { @keyframes ripple-effect {
0% { 0% {
//transform: translate(-50%, -50%) scale(0);
transform: scale(0); transform: scale(0);
} }
/* 50% {
opacity: 1;
} */
to { to {
transform: scale(2); 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 { .btn-primary {
background: $color-blue; background: $color-blue;
color: #fff; color: #fff;
@ -1106,9 +1075,8 @@ input:focus, button:focus {
svg, use { svg, use {
height: calc(100% - 20px); height: calc(100% - 20px);
right: 12.5px; right: 15px;
left: auto; left: auto;
margin: 4px 0 auto;
} }
} }

Loading…
Cancel
Save