diff --git a/src/components/emoticonsDropdown.ts b/src/components/emoticonsDropdown.ts index 7a887026..e1a99760 100644 --- a/src/components/emoticonsDropdown.ts +++ b/src/components/emoticonsDropdown.ts @@ -1,6 +1,6 @@ import { AppImManager } from "../lib/appManagers/appImManager"; import { AppMessagesManager } from "../lib/appManagers/appMessagesManager"; -import { LazyLoadQueue, horizontalMenu, MTDocument, wrapSticker } from "./misc"; +import { horizontalMenu } from "./misc"; import lottieLoader from "../lib/lottieLoader"; import Scrollable from "./scrollable"; import { findUpTag, whichChild } from "../lib/utils"; @@ -8,6 +8,8 @@ import { RichTextProcessor } from "../lib/richtextprocessor"; import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager"; import apiManager from '../lib/mtproto/apiManager'; import CryptoWorker from '../lib/crypto/cryptoworker'; +import LazyLoadQueue from "./lazyLoadQueue"; +import { MTDocument, wrapSticker } from "./wrappers"; export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts new file mode 100644 index 00000000..39e8261d --- /dev/null +++ b/src/components/lazyLoadQueue.ts @@ -0,0 +1,44 @@ +import { isElementInViewport } from "../lib/utils"; + +export default class LazyLoadQueue { + private lazyLoadMedia: Array<{div: HTMLDivElement, load: () => Promise}> = []; + + public check(id?: number) { + /* let length = this.lazyLoadMedia.length; + for(let i = length - 1; i >= 0; --i) { + let {div, load} = this.lazyLoadMedia[i]; + + if(isElementInViewport(div)) { + console.log('will load div:', div); + load(); + this.lazyLoadMedia.splice(i, 1); + } + } */ + if(id !== undefined) { + let {div, load} = this.lazyLoadMedia[id]; + if(isElementInViewport(div)) { + //console.log('will load div by id:', div, div.getBoundingClientRect()); + load(); + this.lazyLoadMedia.splice(id, 1); + } + + return; + } + + this.lazyLoadMedia = this.lazyLoadMedia.filter(({div, load}) => { + if(isElementInViewport(div)) { + //console.log('will load div:', div, div.getBoundingClientRect()); + load(); + return false; + } + + return true; + }); + } + + public push(el: {div: HTMLDivElement, load: () => Promise}) { + let id = this.lazyLoadMedia.push(el) - 1; + + this.check(id); + } +} diff --git a/src/components/misc.ts b/src/components/misc.ts index ac63f44b..3b530bcc 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -1,47 +1,5 @@ -import { MTProto } from "../lib/mtproto/mtproto"; -import { formatBytes, whichChild, isElementInViewport, isInDOM, findUpTag } from "../lib/utils"; -import appPhotosManager from '../lib/appManagers/appPhotosManager'; -import CryptoWorker from '../lib/crypto/cryptoworker'; -import LottieLoader from '../lib/lottieLoader'; -import appStickersManager from "../lib/appManagers/appStickersManager"; -import appDocsManager from "../lib/appManagers/appDocsManager"; -import {AppImManager} from "../lib/appManagers/appImManager"; -import {AppMediaViewer} from '../lib/appManagers/appMediaViewer'; -import { RichTextProcessor } from "../lib/richtextprocessor"; -import lottieLoader from "../lib/lottieLoader"; - -export type MTDocument = { - _: 'document', - pFlags: any, - flags: number, - id: string, - access_hash: string, - file_reference: Uint8Array | number[], - date: number, - mime_type: string, - size: number, - thumbs: MTPhotoSize[], - dc_id: number, - attributes: any[], - - type?: string, - h?: number, - w?: number, - file_name?: string, - file?: File -}; - -export type MTPhotoSize = { - _: string, - w?: number, - h?: number, - size?: number, - type?: string, // i, m, x, y, w by asc - location?: any, - bytes?: Uint8Array, // if type == 'i' - - preloaded?: boolean // custom added -}; +import apiManager from "../lib/mtproto/apiManager"; +import { whichChild, isElementInViewport, isInDOM, findUpTag } from "../lib/utils"; let onRippleClick = function(this: HTMLElement, e: MouseEvent) { var $circle = this.firstElementChild as HTMLSpanElement;//this.querySelector('.c-ripple__circle') as HTMLSpanElement; @@ -103,580 +61,6 @@ export function putPreloader(elem: Element) { elem.innerHTML += html; } -export class ProgressivePreloader { - public preloader: HTMLDivElement = null; - private circle: SVGCircleElement = null; - private progress = 0; - constructor(elem?: Element, private cancelable = true) { - this.preloader = document.createElement('div'); - this.preloader.classList.add('preloader-container'); - - this.preloader.innerHTML = ` -
- - - -
`; - - if(cancelable) { - this.preloader.innerHTML += ` - - - - `; - } else { - this.preloader.classList.add('preloader-swing'); - } - - this.circle = this.preloader.firstElementChild.firstElementChild.firstElementChild as SVGCircleElement; - - if(elem) { - this.attach(elem); - } - } - - public attach(elem: Element, reset = true) { - if(this.cancelable && reset) { - this.setProgress(0); - } - - elem.append(this.preloader); - /* let isIn = isInDOM(this.preloader); - - if(isIn && this.progress != this.defaultProgress) { - this.setProgress(this.defaultProgress); - } - - elem.append(this.preloader); - - if(!isIn && this.progress != this.defaultProgress) { - this.setProgress(this.defaultProgress); - } */ - } - - public detach() { - if(this.preloader.parentElement) { - this.preloader.parentElement.removeChild(this.preloader); - } - } - - public setProgress(percents: number) { - this.progress = percents; - - if(!isInDOM(this.circle)) { - return; - } - - if(percents == 0) { - this.circle.style.strokeDasharray = ''; - return; - } - - let totalLength = this.circle.getTotalLength(); - console.log('setProgress', (percents / 100 * totalLength)); - this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200'; - } -} - -export class LazyLoadQueue { - private lazyLoadMedia: Array<{div: HTMLDivElement, load: () => Promise}> = []; - - public check(id?: number) { - /* let length = this.lazyLoadMedia.length; - for(let i = length - 1; i >= 0; --i) { - let {div, load} = this.lazyLoadMedia[i]; - - if(isElementInViewport(div)) { - console.log('will load div:', div); - load(); - this.lazyLoadMedia.splice(i, 1); - } - } */ - if(id !== undefined) { - let {div, load} = this.lazyLoadMedia[id]; - if(isElementInViewport(div)) { - //console.log('will load div by id:', div, div.getBoundingClientRect()); - load(); - this.lazyLoadMedia.splice(id, 1); - } - - return; - } - - this.lazyLoadMedia = this.lazyLoadMedia.filter(({div, load}) => { - if(isElementInViewport(div)) { - //console.log('will load div:', div, div.getBoundingClientRect()); - load(); - return false; - } - - return true; - }); - } - - public push(el: {div: HTMLDivElement, load: () => Promise}) { - let id = this.lazyLoadMedia.push(el) - 1; - - this.check(id); - } -} - -export function wrapVideo(this: any, doc: MTDocument, container: HTMLDivElement, message: any, justLoader = true, preloader?: ProgressivePreloader, controls = true) { - if(!container.firstElementChild || container.firstElementChild.tagName != 'IMG') { - let size = appPhotosManager.setAttachmentSize(doc, container); - } - - let peerID = this.peerID ? this.peerID : this.currentMessageID; - - //container.classList.add('video'); - - let img = container.firstElementChild as HTMLImageElement || new Image(); - img.setAttribute('message-id', '' + message.id); - - if(!container.contains(img)) { - container.append(img); - } - - //return Promise.resolve(); - - if(!preloader) { - preloader = new ProgressivePreloader(container, false); - } - - let loadVideo = () => { - let promise = appDocsManager.downloadDoc(doc); - - /* promise.notify = (details: {done: number, total: number}) => { - console.log('doc download', promise, details); - preloader.setProgress(details.done); - }; */ - - return promise.then(blob => { - if((this.peerID ? this.peerID : this.currentMessageID) != peerID) { - this.log.warn('peer changed'); - return; - } - - console.log('loaded doc:', doc, blob, container); - - let video = document.createElement('video'); - video.loop = controls; - video.autoplay = controls; - - if(!justLoader) { - video.controls = controls; - } else { - video.volume = 0; - } - - video.setAttribute('message-id', '' + message.id); - - let source = document.createElement('source'); - //source.src = doc.url; - source.src = URL.createObjectURL(blob); - source.type = doc.mime_type; - - if(img && container.contains(img)) { - container.removeChild(img); - } - - video.append(source); - container.append(video); - - //container.style.width = ''; - //container.style.height = ''; - - preloader.detach(); - }); - }; - - if(doc.type == 'gif' || true) { // extra fix - return this.peerID ? this.loadMediaQueuePush(loadVideo) : loadVideo(); - } else { // if video - let load = () => appPhotosManager.preloadPhoto(doc).then((blob) => { - if((this.peerID ? this.peerID : this.currentMessageID) != peerID) { - this.log.warn('peer changed'); - return; - } - - img.src = URL.createObjectURL(blob); - - /* image.style.height = doc.h + 'px'; - image.style.width = doc.w + 'px'; */ - - /* if(justLoader) { // extra fix - justLoader = false; - controls = false; - } */ - - if(!justLoader) { - return loadVideo(); - } else { - container.style.width = ''; - container.style.height = ''; - preloader.detach(); - } - }); - - return this.peerID ? this.loadMediaQueuePush(load) : load(); - } -} - -export function wrapDocument(doc: MTDocument, withTime = false): HTMLDivElement { - let docDiv = document.createElement('div'); - docDiv.classList.add('document'); - - let iconDiv = document.createElement('div'); - iconDiv.classList.add('tgico-document'); - - let extSplitted = doc.file_name ? doc.file_name.split('.') : ''; - let ext = ''; - ext = extSplitted.length > 1 && Array.isArray(extSplitted) ? extSplitted.pop().toLowerCase() : 'file'; - - let ext2 = ext; - if(doc.type == 'photo') { - docDiv.classList.add('photo'); - ext2 = ``; - } - - let fileName = doc.file_name || 'Unknown.file'; - let size = formatBytes(doc.size); - - if(withTime) { - let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - let date = new Date(doc.date * 1000); - - size += ' · ' + months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear() - + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2); - } - - docDiv.innerHTML = ` -
${ext2}
-
${fileName}
-
${size}
- `; - - return docDiv; -} - -export function scrollable(el: HTMLDivElement, x = false, y = true) { - let container = document.createElement('div'); - container.classList.add('scrollable'); - if(x) container.classList.add('scrollable-x'); - if(y) container.classList.add('scrollable-y'); - - let type = x ? 'width' : 'height'; - let side = x ? 'left' : 'top'; - let scrollType = x ? 'scrollWidth' : 'scrollHeight'; - let scrollSide = x ? 'scrollLeft' : 'scrollTop'; - - container.addEventListener('mouseover', () => { - resize(); - /* container.classList.add('active'); - - container.addEventListener('mouseout', () => { - container.classList.remove('active'); - }, {once: true}); */ - }); - - let thumb = document.createElement('div'); - thumb.className = 'scrollbar-thumb'; - - // @ts-ignore - thumb.style[type] = '30px'; - - let resize = () => { - // @ts-ignore - scrollSize = container[scrollType]; - - let rect = container.getBoundingClientRect(); - - // @ts-ignore - size = rect[type]; - - if(!size || size == scrollSize) { - thumbSize = 0; - - // @ts-ignore - thumb.style[type] = thumbSize + 'px'; - return; - } - //if(!height) return; - - let divider = scrollSize / size / 0.5; - thumbSize = size / divider; - - if(thumbSize < 20) thumbSize = 20; - - // @ts-ignore - thumb.style[type] = thumbSize + 'px'; - - // @ts-ignore - //console.log('onresize', thumb.style[type], thumbHeight, height); - }; - - let scrollSize = -1; - let size = 0; - let thumbSize = 0; - window.addEventListener('resize', resize); - //container.addEventListener('DOMNodeInserted', resize); - - let hiddenElements: { - up: Element[], - down: Element[] - } = { - up: [], - down: [] - }; - - let paddings = {up: 0, down: 0}; - - let paddingTopDiv = document.createElement('div'); - paddingTopDiv.classList.add('scroll-padding'); - let paddingBottomDiv = document.createElement('div'); - paddingBottomDiv.classList.add('scroll-padding'); - - let onScroll = (e: Event) => { - // @ts-ignore - //let st = container[scrollSide]; - - // @ts-ignore - if(container[scrollType] != scrollSize || thumbSize == 0) { - resize(); - } - - //let splitUp = container.querySelector('ul'); - let splitUp = container.children[1]; - let children = Array.from(splitUp.children) as HTMLElement[]; - let firstVisible = -1, lastVisible = -1; - let length = children.length; - for(let i = 0; i < length; ++i) { - let child = children[i]; - if(isElementInViewport(child)) { - if(firstVisible < 0) firstVisible = i; - lastVisible = i; - } - } - - if(firstVisible > 0) { - let sliced = children.slice(0, firstVisible); - - for(let child of sliced) { - paddings.up += child.scrollHeight; - hiddenElements.up.push(child); - child.parentElement.removeChild(child); - } - - //console.log('sliced up', sliced.length); - - //sliced.forEach(child => child.style.display = 'none'); - paddingTopDiv.style.height = paddings.up + 'px'; - //console.log('onscroll need to add padding: ', paddings.up); - } else if(hiddenElements.up.length) { - while(isElementInViewport(paddingTopDiv) && paddings.up) { - let child = hiddenElements.up.pop(); - - splitUp.prepend(child); - - paddings.up -= child.scrollHeight; - paddingTopDiv.style.height = paddings.up + 'px'; - } - } - - if(lastVisible < (length - 1)) { - let sliced = children.slice(lastVisible + 1).reverse(); - - for(let child of sliced) { - paddings.down += child.scrollHeight; - hiddenElements.down.unshift(child); - child.parentElement.removeChild(child); - } - - //console.log('onscroll sliced down', sliced.length); - - //sliced.forEach(child => child.style.display = 'none'); - paddingBottomDiv.style.height = paddings.down + 'px'; - //console.log('onscroll need to add padding: ', paddings.up); - } else if(hiddenElements.down.length) { - while(isElementInViewport(paddingBottomDiv) && paddings.down) { - let child = hiddenElements.down.shift(); - - splitUp.append(child); - - paddings.down -= child.scrollHeight; - paddingBottomDiv.style.height = paddings.down + 'px'; - } - } - - //console.log('onscroll', container, firstVisible, lastVisible, hiddenElements); - - // @ts-ignore - let value = container[scrollSide] / (scrollSize - size) * 100; - let maxValue = 100 - (thumbSize / size * 100); - - //console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue); - - // @ts-ignore - thumb.style[side] = (value >= maxValue ? maxValue : value) + '%'; - - //lastScrollPos = st; - }; - - let lastScrollPos = 0; - container.addEventListener('scroll', onScroll); - - container.append(paddingTopDiv); - Array.from(el.children).forEach(c => container.append(c)); - container.append(paddingBottomDiv); - - el.append(container);//container.append(el); - container.parentElement.append(thumb); - resize(); - return {container, hiddenElements, onScroll}; -} - -export function wrapPhoto(this: AppImManager, photo: any, message: any, container: HTMLDivElement) { - //container.classList.add('photo'); - - let peerID = this.peerID; - - let size = appPhotosManager.setAttachmentSize(photo.id, container); - let image = container.firstElementChild as HTMLImageElement || new Image(); - //let size = appPhotosManager.setAttachmentSize(photo.id, image); - image.setAttribute('message-id', message.mid); - - if(!container.contains(image)) { - container.append(image); - } - - let preloader = new ProgressivePreloader(container, false); - - let load = () => appPhotosManager.preloadPhoto(photo.id, size).then((blob) => { - if(this.peerID != peerID) { - this.log.warn('peer changed'); - return; - } - - image.src = URL.createObjectURL(blob); - - preloader.detach(); - - //image.style.width = ''; - //image.style.height = ''; - //container.style.width = ''; - //container.style.height = ''; - }); - - console.log('wrapPhoto', load, container, image); - - return this.loadMediaQueue ? this.loadMediaQueuePush(load) : load(); -} - -export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: () => boolean, lazyLoadQueue?: LazyLoadQueue, group?: string, canvas?: boolean, play = false) { - let stickerType = doc.mime_type == "application/x-tgsticker" ? 2 : (doc.mime_type == "image/webp" ? 1 : 0); - - if(!stickerType) { - console.error('wrong doc for wrapSticker!', doc, div); - } - - //console.log('wrap sticker', doc); - - if(doc.thumbs && !div.firstElementChild) { - let thumb = doc.thumbs[0]; - - if(thumb.bytes) { - MTProto.apiFileManager.saveSmallFile(thumb.location, thumb.bytes); - - appPhotosManager.setAttachmentPreview(thumb.bytes, div, true); - } - } - - let load = () => MTProto.apiFileManager.downloadSmallFile({ - _: 'inputDocumentFileLocation', - access_hash: doc.access_hash, - file_reference: doc.file_reference, - thumb_size: ''/* document.thumbs[0].type */, - id: doc.id, - stickerType: stickerType - }, {mimeType: doc.mime_type, dcID: doc.dc_id}).then(blob => { - //console.log('loaded sticker:', blob, div); - if(middleware && !middleware()) return; - - if(div.firstElementChild) { - div.firstElementChild.remove(); - } - - if(stickerType == 2) { - const reader = new FileReader(); - - reader.addEventListener('loadend', async(e) => { - // @ts-ignore - const text = e.srcElement.result; - let json = await CryptoWorker.gzipUncompress(text, true); - - let animation = await LottieLoader.loadAnimation({ - container: div, - loop: false, - autoplay: false, - animationData: JSON.parse(json), - renderer: canvas ? 'canvas' : 'svg' - }, group); - - if(!canvas) { - div.addEventListener('mouseover', (e) => { - let animation = LottieLoader.getAnimation(div, group); - - if(animation) { - //console.log('sticker hover', animation, div); - - // @ts-ignore - animation.loop = true; - - // @ts-ignore - if(animation.currentFrame == animation.totalFrames - 1) { - animation.goToAndPlay(0, true); - } else { - animation.play(); - } - - div.addEventListener('mouseout', () => { - // @ts-ignore - animation.loop = false; - }, {once: true}); - } - }); - } /* else { - let canvas = div.firstElementChild as HTMLCanvasElement; - if(!canvas.width && !canvas.height) { - console.log('Need lottie resize'); - - // @ts-ignore - animation.resize(); - } - } */ - - if(play) { - animation.play(); - } - }); - - reader.readAsArrayBuffer(blob); - } else if(stickerType == 1) { - let img = new Image(); - img.src = URL.createObjectURL(blob); - - /* div.style.height = doc.h + 'px'; - div.style.width = doc.w + 'px'; */ - div.append(img); - } - - div.setAttribute('file-id', doc.id); - appStickersManager.saveSticker(doc); - }); - - return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); -} - export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void) { let hideTimeout: number = 0; let prevTabContent: HTMLDivElement = null; @@ -761,10 +145,10 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, } export function getNearestDc() { - return MTProto.apiManager.invokeApi('help.getNearestDc').then((nearestDcResult: any) => { + return apiManager.invokeApi('help.getNearestDc').then((nearestDcResult: any) => { if(nearestDcResult.nearest_dc != nearestDcResult.this_dc) { //MTProto.apiManager.baseDcID = nearestDcResult.nearest_dc; - MTProto.apiManager.getNetworker(nearestDcResult.nearest_dc); + apiManager.getNetworker(nearestDcResult.nearest_dc); } return nearestDcResult; diff --git a/src/components/pageIm.ts b/src/components/pageIm.ts index cd9e0b54..32d41d96 100644 --- a/src/components/pageIm.ts +++ b/src/components/pageIm.ts @@ -1,5 +1,5 @@ //import { appImManager, appMessagesManager, appDialogsManager, apiUpdatesManager, appUsersManager } from "../lib/services"; -import { LazyLoadQueue, openBtnMenu } from "./misc"; +import { openBtnMenu } from "./misc"; import Scrollable from './scrollable'; import {stackBlurImage} from '../lib/StackBlur'; @@ -49,13 +49,6 @@ export default () => import('../lib/services').then(services => { } }); - // @ts-ignore - document.addEventListener('history_multiappend', (e: CustomEvent) => { - //let msgIDsByPeer = e.detail; - - appDialogsManager.sortDom(); - }); - // @ts-ignore document.addEventListener('dialog_top', (e: CustomEvent) => { let dialog: any = e.detail; @@ -64,16 +57,6 @@ export default () => import('../lib/services').then(services => { appDialogsManager.sortDom(); }); - // @ts-ignore - document.addEventListener('history_delete', (e: CustomEvent) => { - let detail: { - peerID: string, - msgs: {[x: number]: boolean} - } = e.detail; - - appImManager.deleteMessagesByIDs(Object.keys(detail.msgs).map(s => +s)); - }); - // @ts-ignore document.addEventListener('dialogs_multiupdate', (e: CustomEvent) => { let dialogs = e.detail; diff --git a/src/components/pageSignIn.ts b/src/components/pageSignIn.ts index f17277e0..7e1cc530 100644 --- a/src/components/pageSignIn.ts +++ b/src/components/pageSignIn.ts @@ -1,4 +1,3 @@ -import { MTProto } from "../lib/mtproto/mtproto"; import { putPreloader, getNearestDc, formatPhoneNumber } from "./misc"; import Scrollable from './scrollable'; import {RichTextProcessor} from '../lib/richtextprocessor'; @@ -6,6 +5,7 @@ import * as Config from '../lib/config'; import { findUpTag } from "../lib/utils"; import pageAuthCode from "./pageAuthCode"; +import apiManager from "../lib/mtproto/apiManager"; let installed = false; @@ -193,8 +193,8 @@ export default () => { this.removeAttribute('readonly'); // fix autocomplete });*/ - /* MTProto.authorizer.auth(2); - MTProto.networkerFactory.startAll(); */ + /* authorizer.auth(2); + networkerFactory.startAll(); */ btnNext.addEventListener('click', function(this: HTMLElement, e) { this.setAttribute('disabled', 'true'); @@ -204,7 +204,7 @@ export default () => { //this.innerHTML = 'PLEASE WAIT...'; let phone_number = telEl.value; - MTProto.apiManager.invokeApi('auth.sendCode', { + apiManager.invokeApi('auth.sendCode', { /* flags: 0, */ phone_number: phone_number, api_id: Config.App.id, diff --git a/src/components/preloader.ts b/src/components/preloader.ts new file mode 100644 index 00000000..d48ad018 --- /dev/null +++ b/src/components/preloader.ts @@ -0,0 +1,76 @@ +import { isInDOM } from "../lib/utils"; + +export default class ProgressivePreloader { + public preloader: HTMLDivElement = null; + private circle: SVGCircleElement = null; + private progress = 0; + constructor(elem?: Element, private cancelable = true) { + this.preloader = document.createElement('div'); + this.preloader.classList.add('preloader-container'); + + this.preloader.innerHTML = ` +
+ + + +
`; + + if(cancelable) { + this.preloader.innerHTML += ` + + + + `; + } else { + this.preloader.classList.add('preloader-swing'); + } + + this.circle = this.preloader.firstElementChild.firstElementChild.firstElementChild as SVGCircleElement; + + if(elem) { + this.attach(elem); + } + } + + public attach(elem: Element, reset = true) { + if(this.cancelable && reset) { + this.setProgress(0); + } + + elem.append(this.preloader); + /* let isIn = isInDOM(this.preloader); + + if(isIn && this.progress != this.defaultProgress) { + this.setProgress(this.defaultProgress); + } + + elem.append(this.preloader); + + if(!isIn && this.progress != this.defaultProgress) { + this.setProgress(this.defaultProgress); + } */ + } + + public detach() { + if(this.preloader.parentElement) { + this.preloader.parentElement.removeChild(this.preloader); + } + } + + public setProgress(percents: number) { + this.progress = percents; + + if(!isInDOM(this.circle)) { + return; + } + + if(percents == 0) { + this.circle.style.strokeDasharray = ''; + return; + } + + let totalLength = this.circle.getTotalLength(); + console.log('setProgress', (percents / 100 * totalLength)); + this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200'; + } +} diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts new file mode 100644 index 00000000..35c5caf5 --- /dev/null +++ b/src/components/wrappers.ts @@ -0,0 +1,323 @@ +import appPhotosManager from '../lib/appManagers/appPhotosManager'; +import CryptoWorker from '../lib/crypto/cryptoworker'; +import LottieLoader from '../lib/lottieLoader'; +import appStickersManager from "../lib/appManagers/appStickersManager"; +import appDocsManager from "../lib/appManagers/appDocsManager"; +import {AppImManager} from "../lib/appManagers/appImManager"; +import { formatBytes } from "../lib/utils"; +import ProgressivePreloader from './preloader'; +import LazyLoadQueue from './lazyLoadQueue'; +import apiFileManager from '../lib/mtproto/apiFileManager'; + +export type MTDocument = { + _: 'document', + pFlags: any, + flags: number, + id: string, + access_hash: string, + file_reference: Uint8Array | number[], + date: number, + mime_type: string, + size: number, + thumbs: MTPhotoSize[], + dc_id: number, + attributes: any[], + + type?: string, + h?: number, + w?: number, + file_name?: string, + file?: File +}; + +export type MTPhotoSize = { + _: string, + w?: number, + h?: number, + size?: number, + type?: string, // i, m, x, y, w by asc + location?: any, + bytes?: Uint8Array, // if type == 'i' + + preloaded?: boolean // custom added +}; + +export function wrapVideo(this: any, doc: MTDocument, container: HTMLDivElement, message: any, justLoader = true, preloader?: ProgressivePreloader, controls = true) { + if(!container.firstElementChild || container.firstElementChild.tagName != 'IMG') { + let size = appPhotosManager.setAttachmentSize(doc, container); + } + + let peerID = this.peerID ? this.peerID : this.currentMessageID; + + //container.classList.add('video'); + + let img = container.firstElementChild as HTMLImageElement || new Image(); + img.setAttribute('message-id', '' + message.id); + + if(!container.contains(img)) { + container.append(img); + } + + //return Promise.resolve(); + + if(!preloader) { + preloader = new ProgressivePreloader(container, false); + } + + let loadVideo = () => { + let promise = appDocsManager.downloadDoc(doc); + + /* promise.notify = (details: {done: number, total: number}) => { + console.log('doc download', promise, details); + preloader.setProgress(details.done); + }; */ + + return promise.then(blob => { + if((this.peerID ? this.peerID : this.currentMessageID) != peerID) { + this.log.warn('peer changed'); + return; + } + + console.log('loaded doc:', doc, blob, container); + + let video = document.createElement('video'); + video.loop = controls; + video.autoplay = controls; + + if(!justLoader) { + video.controls = controls; + } else { + video.volume = 0; + } + + video.setAttribute('message-id', '' + message.id); + + let source = document.createElement('source'); + //source.src = doc.url; + source.src = URL.createObjectURL(blob); + source.type = doc.mime_type; + + if(img && container.contains(img)) { + container.removeChild(img); + } + + video.append(source); + container.append(video); + + //container.style.width = ''; + //container.style.height = ''; + + preloader.detach(); + }); + }; + + if(doc.type == 'gif' || true) { // extra fix + return this.peerID ? this.loadMediaQueuePush(loadVideo) : loadVideo(); + } else { // if video + let load = () => appPhotosManager.preloadPhoto(doc).then((blob) => { + if((this.peerID ? this.peerID : this.currentMessageID) != peerID) { + this.log.warn('peer changed'); + return; + } + + img.src = URL.createObjectURL(blob); + + /* image.style.height = doc.h + 'px'; + image.style.width = doc.w + 'px'; */ + + /* if(justLoader) { // extra fix + justLoader = false; + controls = false; + } */ + + if(!justLoader) { + return loadVideo(); + } else { + container.style.width = ''; + container.style.height = ''; + preloader.detach(); + } + }); + + return this.peerID ? this.loadMediaQueuePush(load) : load(); + } +} + +export function wrapDocument(doc: MTDocument, withTime = false): HTMLDivElement { + let docDiv = document.createElement('div'); + docDiv.classList.add('document'); + + let iconDiv = document.createElement('div'); + iconDiv.classList.add('tgico-document'); + + let extSplitted = doc.file_name ? doc.file_name.split('.') : ''; + let ext = ''; + ext = extSplitted.length > 1 && Array.isArray(extSplitted) ? extSplitted.pop().toLowerCase() : 'file'; + + let ext2 = ext; + if(doc.type == 'photo') { + docDiv.classList.add('photo'); + ext2 = ``; + } + + let fileName = doc.file_name || 'Unknown.file'; + let size = formatBytes(doc.size); + + if(withTime) { + let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + let date = new Date(doc.date * 1000); + + size += ' · ' + months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear() + + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2); + } + + docDiv.innerHTML = ` +
${ext2}
+
${fileName}
+
${size}
+ `; + + return docDiv; +} + +export function wrapPhoto(this: AppImManager, photo: any, message: any, container: HTMLDivElement) { + //container.classList.add('photo'); + + let peerID = this.peerID; + + let size = appPhotosManager.setAttachmentSize(photo.id, container); + let image = container.firstElementChild as HTMLImageElement || new Image(); + //let size = appPhotosManager.setAttachmentSize(photo.id, image); + image.setAttribute('message-id', message.mid); + + if(!container.contains(image)) { + container.append(image); + } + + let preloader = new ProgressivePreloader(container, false); + + let load = () => appPhotosManager.preloadPhoto(photo.id, size).then((blob) => { + if(this.peerID != peerID) { + this.log.warn('peer changed'); + return; + } + + image.src = URL.createObjectURL(blob); + + preloader.detach(); + + //image.style.width = ''; + //image.style.height = ''; + //container.style.width = ''; + //container.style.height = ''; + }); + + console.log('wrapPhoto', load, container, image); + + return this.loadMediaQueue ? this.loadMediaQueuePush(load) : load(); +} + +export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: () => boolean, lazyLoadQueue?: LazyLoadQueue, group?: string, canvas?: boolean, play = false) { + let stickerType = doc.mime_type == "application/x-tgsticker" ? 2 : (doc.mime_type == "image/webp" ? 1 : 0); + + if(!stickerType) { + console.error('wrong doc for wrapSticker!', doc, div); + } + + //console.log('wrap sticker', doc); + + if(doc.thumbs && !div.firstElementChild) { + let thumb = doc.thumbs[0]; + + if(thumb.bytes) { + apiFileManager.saveSmallFile(thumb.location, thumb.bytes); + + appPhotosManager.setAttachmentPreview(thumb.bytes, div, true); + } + } + + let load = () => apiFileManager.downloadSmallFile({ + _: 'inputDocumentFileLocation', + access_hash: doc.access_hash, + file_reference: doc.file_reference, + thumb_size: ''/* document.thumbs[0].type */, + id: doc.id, + stickerType: stickerType + }, {mimeType: doc.mime_type, dcID: doc.dc_id}).then(blob => { + //console.log('loaded sticker:', blob, div); + if(middleware && !middleware()) return; + + if(div.firstElementChild) { + div.firstElementChild.remove(); + } + + if(stickerType == 2) { + const reader = new FileReader(); + + reader.addEventListener('loadend', async(e) => { + // @ts-ignore + const text = e.srcElement.result; + let json = await CryptoWorker.gzipUncompress(text, true); + + let animation = await LottieLoader.loadAnimation({ + container: div, + loop: false, + autoplay: false, + animationData: JSON.parse(json), + renderer: canvas ? 'canvas' : 'svg' + }, group); + + if(!canvas) { + div.addEventListener('mouseover', (e) => { + let animation = LottieLoader.getAnimation(div, group); + + if(animation) { + //console.log('sticker hover', animation, div); + + // @ts-ignore + animation.loop = true; + + // @ts-ignore + if(animation.currentFrame == animation.totalFrames - 1) { + animation.goToAndPlay(0, true); + } else { + animation.play(); + } + + div.addEventListener('mouseout', () => { + // @ts-ignore + animation.loop = false; + }, {once: true}); + } + }); + } /* else { + let canvas = div.firstElementChild as HTMLCanvasElement; + if(!canvas.width && !canvas.height) { + console.log('Need lottie resize'); + + // @ts-ignore + animation.resize(); + } + } */ + + if(play) { + animation.play(); + } + }); + + reader.readAsArrayBuffer(blob); + } else if(stickerType == 1) { + let img = new Image(); + img.src = URL.createObjectURL(blob); + + /* div.style.height = doc.h + 'px'; + div.style.width = doc.w + 'px'; */ + div.append(img); + } + + div.setAttribute('file-id', doc.id); + appStickersManager.saveSticker(doc); + }); + + return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); +} diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index c48a1aa8..e3e1551a 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -23,7 +23,7 @@ export class AppChatsManager { } public saveApiChats(apiChats: any[]) { - apiChats.forEach(this.saveApiChat.bind(this)); + apiChats.forEach(chat => this.saveApiChat(chat)); } public saveApiChat(apiChat: any) { @@ -254,27 +254,6 @@ export class AppChatsManager { } return participants; } - - /* public openChat(chatID: number, accessHash: string) { - var scope = $rootScope.$new() - scope.chatID = chatID - - if(this.isChannel(chatID)) { - var modalInstance = $modal.open({ - templateUrl: templateUrl('channel_modal'), - controller: 'ChannelModalController', - scope: scope, - windowClass: 'chat_modal_window channel_modal_window mobile_modal' - }) - } else { - var modalInstance = $modal.open({ - templateUrl: templateUrl('chat_modal'), - controller: 'ChatModalController', - scope: scope, - windowClass: 'chat_modal_window mobile_modal' - }) - } - } */ } export default new AppChatsManager(); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 58ccaa07..7d4ff5ca 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -22,11 +22,14 @@ type DialogDom = { export class AppDialogsManager { public chatList = document.getElementById('dialogs') as HTMLUListElement; + public chatListArchived = document.getElementById('dialogs-archived') as HTMLUListElement; public pinnedDelimiter: HTMLDivElement; public chatsHidden: any; public myID = 0; - public doms: {[x: number]: any} = {}; + public doms: {[peerID: number]: DialogDom} = {}; + public domsArchived: {[peerID: number]: DialogDom} = {}; + public lastActiveListElement: HTMLElement = null; constructor() { this.pinnedDelimiter = document.createElement('div'); @@ -56,6 +59,10 @@ export class AppDialogsManager { return; } + if(this.lastActiveListElement) { + this.lastActiveListElement.classList.remove('active'); + } + if(elem) { /* if(chatClosedDiv) { chatClosedDiv.style.display = 'none'; @@ -66,6 +73,8 @@ export class AppDialogsManager { let peerID = +elem.getAttribute('data-peerID'); let lastMsgID = +elem.getAttribute('data-mid'); appImManager.setPeer(peerID, lastMsgID); + elem.classList.add('active'); + this.lastActiveListElement = elem; } else /* if(chatClosedDiv) */ { appImManager.setPeer(0); //chatClosedDiv.style.display = ''; @@ -140,7 +149,7 @@ export class AppDialogsManager { return true; } - public sortDom() { + public sortDom(archived = false) { //return; let dialogs = appMessagesManager.dialogsStorage.dialogs; @@ -153,11 +162,17 @@ export class AppDialogsManager { let dialog = dialogs[i]; if(!dialog.pFlags.pinned) break; pinnedDialogs.push(dialog); + } - let dom = this.getDialogDom(dialog.peerID); + if(pinnedDialogs.length) { + let dom = this.getDialogDom(pinnedDialogs[pinnedDialogs.length - 1].peerID); if(dom) { dom.listEl.append(this.pinnedDelimiter); } + } else { + if(this.pinnedDelimiter.parentElement) { + this.pinnedDelimiter.parentElement.removeChild(this.pinnedDelimiter); + } } let sorted = dialogs @@ -372,13 +387,14 @@ export class AppDialogsManager { } public getDialogDom(peerID: number) { - return this.doms[peerID] as DialogDom; + return this.doms[peerID] || this.domsArchived[peerID]; } public addDialog(dialog: { peerID: number, pFlags: any, - peer: any + peer: any, + folder_id?: number }, container?: HTMLUListElement, drawStatus = true) { let peerID: number = dialog.peerID; @@ -481,7 +497,14 @@ export class AppDialogsManager { }; if(!container) { - this.chatList.append(li); + if(dialog.folder_id) { + this.chatListArchived.append(li); + this.domsArchived[dialog.peerID] = dom; + } else { + this.chatList.append(li); + this.doms[dialog.peerID] = dom; + } + //this.appendTo.push(li); if(dialog.pFlags.pinned) { @@ -490,7 +513,6 @@ export class AppDialogsManager { dom.listEl.append(this.pinnedDelimiter); } - this.doms[dialog.peerID] = dom; this.setLastMessage(dialog); } else { container.append(li); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index e085cdcf..9e13b31e 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -4,7 +4,7 @@ import appUsersManager from "./appUsersManager"; import appMessagesManager from "./appMessagesManager"; import appPeersManager from "./appPeersManager"; import appProfileManager from "./appProfileManager"; -import { ProgressivePreloader, wrapDocument, wrapSticker, wrapVideo, wrapPhoto, openBtnMenu, LazyLoadQueue } from "../../components/misc"; +//import { ProgressivePreloader, wrapDocument, wrapSticker, wrapVideo, wrapPhoto, openBtnMenu, LazyLoadQueue } from "../../components/misc"; import appDialogsManager from "./appDialogsManager"; import { RichTextProcessor } from "../richtextprocessor"; import appPhotosManager from "./appPhotosManager"; @@ -19,6 +19,10 @@ import appChatsManager from "./appChatsManager"; import appMessagesIDsManager from "./appMessagesIDsManager"; import apiUpdatesManager from './apiUpdatesManager'; import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from '../../components/emoticonsDropdown'; +import LazyLoadQueue from '../../components/lazyLoadQueue'; +import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker } from '../../components/wrappers'; +import ProgressivePreloader from '../../components/preloader'; +import { openBtnMenu } from '../../components/misc'; console.log('appImManager included!'); @@ -213,7 +217,7 @@ class ChatInput { event.preventDefault(); }); - /* this.messageInput.addEventListener('paste', (e) => { + this.messageInput.addEventListener('paste', (e) => { e.preventDefault(); // @ts-ignore let text = (e.originalEvent || e).clipboardData.getData('text/plain'); @@ -229,7 +233,7 @@ class ChatInput { // @ts-ignore //console.log('paste text', text, ); window.document.execCommand('insertHTML', false, text); - }); */ + }); let attachFile = (file: File) => { console.log('selected file:', file, typeof(file)); @@ -240,6 +244,8 @@ class ChatInput { this.attachMediaPopUp.captionInput.value = ''; this.attachMediaPopUp.mediaContainer.innerHTML = ''; + this.attachMediaPopUp.mediaContainer.style.width = ''; + this.attachMediaPopUp.mediaContainer.style.height = ''; switch(willAttach) { case 'media': { @@ -248,11 +254,14 @@ class ChatInput { img.onload = () => { willAttachWidth = img.naturalWidth; willAttachHeight = img.naturalHeight; + + let {w, h} = calcImageInBox(willAttachWidth, willAttachHeight, 378, 256); + this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; + this.attachMediaPopUp.mediaContainer.style.height = h + 'px'; + this.attachMediaPopUp.mediaContainer.append(img); }; this.attachMediaPopUp.titleEl.innerText = 'Send Photo'; - - this.attachMediaPopUp.mediaContainer.append(img); this.attachMediaPopUp.container.classList.add('active'); break; @@ -308,7 +317,7 @@ class ChatInput { // @ts-ignore var items = (event.clipboardData || event.originalEvent.clipboardData).items; - //console.log(items); // will give you the mime types + //console.log('item', event.clipboardData.getData()); for(let i = 0; i < items.length; ++i) { if(items[i].kind == 'file') { event.cancelBubble = true; @@ -526,15 +535,42 @@ export class AppImManager { let msgIDs = msgIDsByPeer[this.peerID]; this.renderMessagesByIDs(msgIDs); + + appDialogsManager.sortDom(); }); + $rootScope.$on('history_delete', (e: CustomEvent) => { + let detail: { + peerID: string, + msgs: {[x: number]: boolean} + } = e.detail; + + this.deleteMessagesByIDs(Object.keys(detail.msgs).map(s => +s)); + }); + + // Calls when message successfully sent and we have an ID $rootScope.$on('message_sent', (e: CustomEvent) => { let {tempID, mid} = e.detail; + this.log('message_sent', e.detail); + let bubble = this.bubbles[tempID]; if(bubble) { this.bubbles[mid] = bubble; + + this.log('message_sent', bubble); + + let media = bubble.querySelector('img, video'); + if(media) { + media.setAttribute('message-id', mid); + } + + bubble.classList.remove('is-sending'); + bubble.classList.add('is-sent'); + delete this.bubbles[tempID]; + } else { + this.log.warn('message_sent there is no bubble', e.detail); } let length = this.unreadOut.length; @@ -964,10 +1000,12 @@ export class AppImManager { let length = history.length; */ // filter negative ids + let lastBadIndex = 0; for(let i = 0; i < history.length; ++i) { - if(history[i] <= 0) history.splice(i, 1); + if(history[i] <= 0) lastBadIndex = i; else break; } + history = history.slice(lastBadIndex + 1); this.getHistoryTimeout = 0; @@ -977,6 +1015,11 @@ export class AppImManager { let msgID = history[i]; let bubble = this.bubbles[msgID]; + + if(!bubble) { + this.log.error('no bubble by msgID:', msgID); + continue; + } if(isElementInViewport(bubble)) { willLoad = true; @@ -994,6 +1037,9 @@ export class AppImManager { } let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; + if(!dialog) { + return; + } // if scroll down after search if(!willLoad && history.indexOf(/* this.lastDialog */dialog.top_message) === -1) { @@ -1162,7 +1208,7 @@ export class AppImManager { } if(this.bubbles[lastMsgID]) { - if(lastMsgID == this.lastDialog.top_message) { + if(this.lastDialog && lastMsgID == this.lastDialog.top_message) { this.scroll.scrollTop = this.scroll.scrollHeight; } else { this.bubbles[lastMsgID].scrollIntoView(); @@ -1179,45 +1225,47 @@ export class AppImManager { this.peerID = $rootScope.selectedPeerID = peerID; // no dialog - if(!appMessagesManager.getDialogByPeerID(this.peerID).length) { + /* if(!appMessagesManager.getDialogByPeerID(this.peerID).length) { this.log.error('No dialog by peerID:', this.peerID); return Promise.reject(); - } + } */ this.pinnedMessageContainer.style.display = 'none'; this.preloader.attach(this.chatInner); - if(this.lastDialog) { - let lastDom = appDialogsManager.getDialogDom(this.lastDialog.peerID); - lastDom.listEl.classList.remove('active'); - } - - let dialog = this.lastDialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; + let dialog = this.lastDialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null; this.log('setPeer peerID:', this.peerID, dialog); - appDialogsManager.loadDialogPhoto(this.avatarEl, dialog.peerID); - appDialogsManager.loadDialogPhoto(appSidebarRight.profileElements.avatar, dialog.peerID); + appDialogsManager.loadDialogPhoto(this.avatarEl, this.peerID); + appDialogsManager.loadDialogPhoto(appSidebarRight.profileElements.avatar, this.peerID); - this.firstTopMsgID = dialog.top_message || 0; + this.firstTopMsgID = dialog ? dialog.top_message : 0; - let dom = appDialogsManager.getDialogDom(this.peerID); + /* let dom = appDialogsManager.getDialogDom(this.peerID); if(!dom) { this.log.warn('No rendered dialog by peerID:', this.peerID); appDialogsManager.addDialog(dialog); dom = appDialogsManager.getDialogDom(this.peerID); } // warning need check - dom.listEl.classList.add('active'); + dom.listEl.classList.add('active'); */ this.setPeerStatus(); - this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = dom.titleSpan.innerHTML; + //this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = dom.titleSpan.innerHTML; + this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = appPeersManager.getPeerTitle(this.peerID); this.topbar.style.display = ''; appSidebarRight.toggleSidebar(true); this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : ''; + if(appPeersManager.isAnyGroup(peerID)) { + this.chatInner.classList.add('is-chat'); + } else { + this.chatInner.classList.remove('is-chat'); + } + return Promise.all([ this.getHistory(lastMsgID).then(() => { this.log('setPeer removing preloader'); @@ -1230,7 +1278,7 @@ export class AppImManager { } else { this.scroll.scrollTop = this.scroll.scrollHeight; } - } else if(dialog.top_message) { // add last message, bc in getHistory will load < max_id + } else if(dialog && dialog.top_message) { // add last message, bc in getHistory will load < max_id this.renderMessage(appMessagesManager.getMessage(dialog.top_message)); } @@ -1240,10 +1288,10 @@ export class AppImManager { this.preloader.detach(); - setTimeout(() => { + //setTimeout(() => { //appSidebarRight.fillProfileElements(); appSidebarRight.loadSidebarMedia(); - }, 0); + //}, 500); return true; })/* .catch(err => { @@ -1271,15 +1319,17 @@ export class AppImManager { } public updateUnreadByDialog(dialog: any) { - let maxID = dialog.read_outbox_max_id; + let maxID = this.peerID == this.myID ? dialog.read_inbox_max_id : dialog.read_outbox_max_id; + + this.log('updateUnreadByDialog', maxID, dialog, this.unreadOut); let length = this.unreadOut.length; for(let i = length - 1; i >= 0; --i) { let msgID = this.unreadOut[i]; - if(msgID <= maxID) { + if(msgID > 0 && msgID <= maxID) { let bubble = this.bubbles[msgID]; - bubble.classList.remove('sent'); - bubble.classList.add('read'); + bubble.classList.remove('is-sent'); + bubble.classList.add('is-read'); this.unreadOut.splice(i, 1); } } @@ -1391,8 +1441,10 @@ export class AppImManager { //bubble.prepend(timeSpan, messageDiv); // that's bad if(our) { - if(message.pFlags.unread) this.unreadOut.push(message.mid); - let status = message.pFlags.unread ? 'sent' : 'read'; + if(message.pFlags.unread || message.mid < 0) this.unreadOut.push(message.mid); // message.mid < 0 added 11.02.2020 + let status = ''; + if(message.mid < 0) status = 'is-sending'; + else status = message.pFlags.unread ? 'is-sent' : 'is-read'; bubble.classList.add(status); } else { //this.log('not our message', message, message.pFlags.unread); @@ -1416,7 +1468,7 @@ export class AppImManager { switch(pending.type) { case 'photo': { - if(pending.size < 1e6) { + if(pending.size < 5e6) { let img = new Image(); img.src = URL.createObjectURL(pending.file); @@ -1487,8 +1539,6 @@ export class AppImManager { let textDiv = document.createElement('div'); textDiv.classList.add('text'); - let loadedVideo = false; - let preview: HTMLDivElement = null; if(webpage.photo || webpage.document) { preview = document.createElement('div'); @@ -1631,7 +1681,7 @@ export class AppImManager { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); nameDiv.innerHTML = 'Forwarded from ' + title; - nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false); + //nameDiv.style.color = appPeersManager.getPeerColorByID(message.fromID, false); bubble.append(nameDiv); } } else { @@ -1768,9 +1818,9 @@ export class AppImManager { this.chatInner.append(containerDiv); } - if(bubble.classList.contains('webpage')) { + /* if(bubble.classList.contains('webpage')) { this.log('night running', bubble, bubble.scrollHeight); - } + } */ //return //this.scrollPosition.restore(); @@ -1825,7 +1875,7 @@ export class AppImManager { public getHistory(maxID = 0, reverse = false, isBackLimit = false) { let peerID = this.peerID; - if(!maxID && this.lastDialog.top_message) { + if(!maxID && this.lastDialog && this.lastDialog.top_message) { maxID = this.lastDialog.top_message/* + 1 */; } diff --git a/src/lib/appManagers/appMediaViewer.ts b/src/lib/appManagers/appMediaViewer.ts index e908f75d..33034ab4 100644 --- a/src/lib/appManagers/appMediaViewer.ts +++ b/src/lib/appManagers/appMediaViewer.ts @@ -1,13 +1,15 @@ -import { MTDocument, ProgressivePreloader, wrapVideo } from "../../components/misc"; +//import { MTDocument, ProgressivePreloader, wrapVideo } from "../../components/misc"; import appPeersManager from "./appPeersManager"; import appDialogsManager from "./appDialogsManager"; import appPhotosManager from "./appPhotosManager"; import appSidebarRight from "./appSidebarRight"; import { $rootScope } from "../utils"; import appMessagesManager from "./appMessagesManager"; -import { CancellablePromise } from "../mtproto/apiFileManager"; +//import { CancellablePromise } from "../mtproto/apiFileManager"; import { RichTextProcessor } from "../richtextprocessor"; import { logger } from "../polyfill"; +import ProgressivePreloader from "../../components/preloader"; +import { wrapVideo } from "../../components/wrappers"; export class AppMediaViewer { private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 6d1aa427..daf3e31c 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -4,7 +4,7 @@ import appChatsManager from "./appChatsManager"; import appUsersManager from "./appUsersManager"; import { RichTextProcessor } from "../richtextprocessor"; import { nextRandomInt, bigint } from "../bin_utils"; -import { MTProto, telegramMeWebService } from "../mtproto/mtproto"; +import { telegramMeWebService } from "../mtproto/mtproto"; import apiUpdatesManager from "./apiUpdatesManager"; import appPhotosManager from "./appPhotosManager"; @@ -12,9 +12,12 @@ import AppStorage from '../storage'; import AppPeersManager from "./appPeersManager"; import ServerTimeManager from "../mtproto/serverTimeManager"; import apiFileManager, { CancellablePromise } from "../mtproto/apiFileManager"; -import { MTDocument, ProgressivePreloader } from "../../components/misc"; import appDocsManager from "./appDocsManager"; import appImManager from "./appImManager"; +import { MTDocument } from "../../components/wrappers"; +import ProgressivePreloader from "../../components/preloader"; +import serverTimeManager from "../mtproto/serverTimeManager"; +import apiManager from "../mtproto/apiManager"; type HistoryStorage = { count: number | null, @@ -221,7 +224,7 @@ export class AppMessagesManager { to_id: AppPeersManager.getOutputPeer(peerID), flags: flags, pFlags: pFlags, - date: tsNow(true) + MTProto.serverTimeManager.serverTimeOffset, + date: tsNow(true) + serverTimeManager.serverTimeOffset, message: text, random_id: randomIDS, reply_to_msg_id: replyToMsgID, @@ -274,7 +277,7 @@ export class AppMessagesManager { var apiPromise: any; if(options.viaBotID) { - apiPromise = MTProto.apiManager.invokeApi('messages.sendInlineBotResult', { + apiPromise = apiManager.invokeApi('messages.sendInlineBotResult', { flags: flags, peer: AppPeersManager.getInputPeerByID(peerID), random_id: randomID, @@ -287,7 +290,7 @@ export class AppMessagesManager { flags |= 8; } - apiPromise = MTProto.apiManager.invokeApi('messages.sendMessage', { + apiPromise = apiManager.invokeApi('messages.sendMessage', { flags: flags, no_webpage: noWebPage, peer: AppPeersManager.getInputPeerByID(peerID), @@ -523,7 +526,7 @@ export class AppMessagesManager { let invoke = (flags: number, inputMedia: any) => { appImManager.setTyping('sendMessageCancelAction'); - return MTProto.apiManager.invokeApi('messages.sendMedia', { + return apiManager.invokeApi('messages.sendMedia', { flags: flags, peer: AppPeersManager.getInputPeerByID(peerID), media: inputMedia, @@ -767,7 +770,7 @@ export class AppMessagesManager { var flags = 0; if(this.dialogsOffsetDate) { - offsetDate = this.dialogsOffsetDate + MTProto.serverTimeManager.serverTimeOffset; + offsetDate = this.dialogsOffsetDate + serverTimeManager.serverTimeOffset; offsetIndex = this.dialogsOffsetDate * 0x10000; flags |= 1; } @@ -776,7 +779,7 @@ export class AppMessagesManager { /* let id = 296814355; hash = (((hash * 0x4F25) & 0x7FFFFFFF) + id) & 0x7FFFFFFF; */ - return MTProto.apiManager.invokeApi('messages.getDialogs', { + return apiManager.invokeApi('messages.getDialogs', { flags: flags, offset_date: offsetDate, offset_id: appMessagesIDsManager.getMessageLocalID(offsetID), @@ -851,7 +854,7 @@ export class AppMessagesManager { public generateDialogIndex(date?: any) { if(date === undefined) { - date = tsNow(true) + MTProto.serverTimeManager.serverTimeOffset; + date = tsNow(true) + serverTimeManager.serverTimeOffset; } return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF); } @@ -925,7 +928,7 @@ export class AppMessagesManager { console.log('will reloadConversation', peerID); - return MTProto.apiManager.invokeApi('messages.getPeerDialogs', { + return apiManager.invokeApi('messages.getPeerDialogs', { peers: peers }).then(this.applyConversations.bind(this)); } @@ -972,7 +975,7 @@ export class AppMessagesManager { apiMessage.reply_to_mid = appMessagesIDsManager.getFullMessageID(apiMessage.reply_to_msg_id, channelID); } - apiMessage.date -= MTProto.serverTimeManager.serverTimeOffset; + apiMessage.date -= serverTimeManager.serverTimeOffset; apiMessage.peerID = peerID; apiMessage.fromID = apiMessage.pFlags.post ? peerID : apiMessage.from_id; @@ -993,7 +996,7 @@ export class AppMessagesManager { apiMessage.fwdPostID = fwdHeader.channel_post; } - fwdHeader.date -= MTProto.serverTimeManager.serverTimeOffset; + fwdHeader.date -= serverTimeManager.serverTimeOffset; } if(apiMessage.via_bot_id > 0) { @@ -1579,7 +1582,7 @@ export class AppMessagesManager { notification.silent = message.pFlags.silent || false if(notificationPhoto.location && !notificationPhoto.location.empty) { - MTProto.apiFileManager.downloadSmallFile(notificationPhoto.location/* , notificationPhoto.size */) + apiFileManager.downloadSmallFile(notificationPhoto.location/* , notificationPhoto.size */) .then((blob) => { if(message.pFlags.unread) { notification.image = blob @@ -1798,7 +1801,7 @@ export class AppMessagesManager { var apiPromise if(peerID || !query) { - apiPromise = MTProto.apiManager.invokeApi('messages.search', { + apiPromise = apiManager.invokeApi('messages.search', { flags: 0, peer: AppPeersManager.getInputPeerByID(peerID), q: query || '', @@ -1826,7 +1829,7 @@ export class AppMessagesManager { offsetPeerID = this.getMessagePeer(offsetMessage); } - apiPromise = MTProto.apiManager.invokeApi('messages.searchGlobal', { + apiPromise = apiManager.invokeApi('messages.searchGlobal', { q: query, offset_rate: offsetRate, offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID), @@ -1950,12 +1953,12 @@ export class AppMessagesManager { var apiPromise: any; if(isChannel) { - apiPromise = MTProto.apiManager.invokeApi('channels.readHistory', { + apiPromise = apiManager.invokeApi('channels.readHistory', { channel: appChatsManager.getChannelInput(-peerID), max_id: maxID }); } else { - apiPromise = MTProto.apiManager.invokeApi('messages.readHistory', { + apiPromise = apiManager.invokeApi('messages.readHistory', { peer: AppPeersManager.getInputPeerByID(peerID), max_id: maxID }).then((affectedMessages: any) => { @@ -2041,7 +2044,7 @@ export class AppMessagesManager { let msgIDs = splitted.msgIDs[channelID]; if(channelID > 0) { - MTProto.apiManager.invokeApi('channels.readMessageContents', { + apiManager.invokeApi('channels.readMessageContents', { channel: appChatsManager.getChannelInput(channelID), id: msgIDs }).then(() => { @@ -2055,7 +2058,7 @@ export class AppMessagesManager { }); }); } else { - MTProto.apiManager.invokeApi('messages.readMessageContents', { + apiManager.invokeApi('messages.readMessageContents', { id: msgIDs }).then((affectedMessages: any) => { apiUpdatesManager.processUpdateMessage({ @@ -2096,7 +2099,7 @@ export class AppMessagesManager { var msgs: any = {} msgs[tempID] = true; - $rootScope.$broadcast('history_delete', {peerID: peerID, msgs: msgs}); + //$rootScope.$broadcast('history_delete', {peerID: peerID, msgs: msgs}); // commented 11.02.2020 this.finalizePendingMessageCallbacks(tempID, mid); } else { @@ -2255,7 +2258,7 @@ export class AppMessagesManager { case 'updatePinnedDialogs': { var newPinned: any = {}; if(!update.order) { - MTProto.apiManager.invokeApi('messages.getPinnedDialogs', {}).then((dialogsResult: any) => { + apiManager.invokeApi('messages.getPinnedDialogs', {}).then((dialogsResult: any) => { dialogsResult.dialogs.reverse(); this.applyConversations(dialogsResult); @@ -2343,7 +2346,7 @@ export class AppMessagesManager { } else { var msgs: any = {}; msgs[mid] = true; - $rootScope.$broadcast('history_delete', {peerID: peerID, msgs: msgs}); + /////////$rootScope.$broadcast('history_delete', {peerID: peerID, msgs: msgs}); // commented 11.02.2020 } } else { $rootScope.$broadcast('message_edit', { @@ -2654,7 +2657,7 @@ export class AppMessagesManager { to_id: AppPeersManager.getOutputPeer(peerID), flags: 0, pFlags: {unread: true}, - date: (update.inbox_date || tsNow(true)) + MTProto.serverTimeManager.serverTimeOffset, + date: (update.inbox_date || tsNow(true)) + serverTimeManager.serverTimeOffset, message: update.message, media: update.media, entities: update.entities @@ -2845,7 +2848,7 @@ export class AppMessagesManager { max_seen_msg: maxID }); - MTProto.apiManager.invokeApi('messages.receivedMessages', { + apiManager.invokeApi('messages.receivedMessages', { max_id: maxID }); } @@ -3079,7 +3082,7 @@ export class AppMessagesManager { //console.trace('requestHistory', peerID, maxID, limit, offset); - return MTProto.apiManager.invokeApi('messages.getHistory', { + return apiManager.invokeApi('messages.getHistory', { peer: AppPeersManager.getInputPeerByID(peerID), offset_id: maxID ? appMessagesIDsManager.getMessageLocalID(maxID) : 0, offset_date: 0, @@ -3124,7 +3127,7 @@ export class AppMessagesManager { to_id: AppPeersManager.getOutputPeer(peerID), flags: 0, pFlags: {}, - date: tsNow(true) + MTProto.serverTimeManager.serverTimeOffset, + date: tsNow(true) + serverTimeManager.serverTimeOffset, action: { _: 'messageActionBotIntro', description: description @@ -3233,12 +3236,12 @@ export class AppMessagesManager { var promise; channelID = +channelID; if(channelID > 0) { - promise = MTProto.apiManager.invokeApi('channels.getMessages', { + promise = apiManager.invokeApi('channels.getMessages', { channel: appChatsManager.getChannelInput(channelID), id: msgIDs }); } else { - promise = MTProto.apiManager.invokeApi('messages.getMessages', { + promise = apiManager.invokeApi('messages.getMessages', { id: msgIDs }); } diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts index 464b8f60..665cbfb8 100644 --- a/src/lib/appManagers/appPeersManager.ts +++ b/src/lib/appManagers/appPeersManager.ts @@ -105,6 +105,22 @@ const AppPeersManager = { return (peerID < 0) && appChatsManager.isChannel(-peerID); }, + isMegagroup: (peerID: number) => { + return (peerID < 0) && appChatsManager.isMegagroup(-peerID); + }, + + isAnyGroup: (peerID: number): boolean => { + return (peerID < 0) && !appChatsManager.isBroadcast(-peerID); + }, + + isBroadcast: (id: number): boolean => { + return AppPeersManager.isChannel(id) && !AppPeersManager.isMegagroup(id); + }, + + isBot: (peerID: number): boolean => { + return (peerID > 0) && appUsersManager.isBot(peerID); + }, + getInputPeerByID: (peerID: number) => { if (!peerID) { return {_: 'inputPeerEmpty'} @@ -137,10 +153,6 @@ const AppPeersManager = { return color; }, - isMegagroup: (peerID: number) => { - return (peerID < 0) && appChatsManager.isMegagroup(-peerID); - }, - getPeerSearchText: (peerID: number) => { var text if(peerID > 0) { diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 2c1036f7..7ab0e465 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -1,9 +1,11 @@ -import { MTProto } from "../mtproto/mtproto"; import appUsersManager from "./appUsersManager"; import { copy, calcImageInBox } from "../utils"; import fileManager from '../filemanager'; import { bytesFromHex } from "../bin_utils"; -import { MTPhotoSize } from "../../components/misc"; +import { MTPhotoSize } from "../../components/wrappers"; +import apiFileManager from "../mtproto/apiFileManager"; +import apiManager from "../mtproto/apiManager"; +//import { MTPhotoSize } from "../../components/misc"; type MTPhoto = { _: 'photo', @@ -53,7 +55,7 @@ export class AppPhotosManager { apiPhoto.sizes.forEach((photoSize: any) => { if(photoSize._ == 'photoCachedSize') { - MTProto.apiFileManager.saveSmallFile(photoSize.location, photoSize.bytes); + apiFileManager.saveSmallFile(photoSize.location, photoSize.bytes); console.log('clearing photo cached size', apiPhoto); @@ -109,7 +111,7 @@ export class AppPhotosManager { public getUserPhotos(userID: number, maxID: number, limit: number) { var inputUser = appUsersManager.getUserInput(userID); - return MTProto.apiManager.invokeApi('photos.getUserPhotos', { + return apiManager.invokeApi('photos.getUserPhotos', { user_id: inputUser, offset: 0, limit: limit || 20, @@ -221,26 +223,26 @@ export class AppPhotosManager { } : photoSize.location; /* if(overwrite) { - await MTProto.apiFileManager.deleteFile(location); + await apiFileManager.deleteFile(location); console.log('Photos deleted file!'); } */ if(isPhoto/* && photoSize.size >= 1e6 */) { console.log('Photos downloadFile exec', photo); - /* let promise = MTProto.apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); + /* let promise = apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); let blob = await promise; if(blob.size < photoSize.size && overwrite) { - await MTProto.apiFileManager.deleteFile(location); + await apiFileManager.deleteFile(location); console.log('Photos deleted file!'); - return MTProto.apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); + return apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); } return blob; */ - return MTProto.apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); + return apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); } else { console.log('Photos downloadSmallFile exec', photo, location); - return MTProto.apiFileManager.downloadSmallFile(location); + return apiFileManager.downloadSmallFile(location); } } else return Promise.reject('no photoSize'); } @@ -359,7 +361,7 @@ export class AppPhotosManager { fileManager.chooseSaveFile(fileName, ext, mimeType).then((writableFileEntry) => { if(writableFileEntry) { - MTProto.apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, { + apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, { mimeType: mimeType, toFileEntry: writableFileEntry }).then(() => { @@ -369,12 +371,12 @@ export class AppPhotosManager { }); } }, () => { - var cachedBlob = MTProto.apiFileManager.getCachedFile(inputFileLocation) + var cachedBlob = apiFileManager.getCachedFile(inputFileLocation) if (cachedBlob) { return fileManager.download(cachedBlob, mimeType, fileName); } - MTProto.apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {mimeType: mimeType}) + apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {mimeType: mimeType}) .then((blob: Blob) => { fileManager.download(blob, mimeType, fileName); }, (e: any) => { diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts index 3b184b9b..a5313c0c 100644 --- a/src/lib/appManagers/appSidebarLeft.ts +++ b/src/lib/appManagers/appSidebarLeft.ts @@ -1,11 +1,42 @@ import { logger } from "../polyfill"; -import { putPreloader } from "../../components/misc"; +import { putPreloader, formatPhoneNumber } from "../../components/misc"; import Scrollable from '../../components/scrollable'; import appMessagesManager from "./appMessagesManager"; import appDialogsManager from "./appDialogsManager"; -import { isElementInViewport } from "../utils"; +import { isElementInViewport, numberWithCommas } from "../utils"; import appMessagesIDsManager from "./appMessagesIDsManager"; import appImManager from "./appImManager"; +import appUsersManager from "./appUsersManager"; +import { appPeersManager } from "../services"; + +class SearchGroup { + container: HTMLDivElement; + nameEl: HTMLDivElement; + list: HTMLUListElement; + + constructor(public name: string, public type: string) { + this.list = document.createElement('ul'); + this.container = document.createElement('div'); + this.nameEl = document.createElement('div'); + this.nameEl.classList.add('search-group__name'); + this.nameEl.innerText = name; + + this.container.classList.add('search-group'); + this.container.append(this.nameEl, this.list); + this.container.style.display = 'none'; + + appDialogsManager.setListClickListener(this.list); + } + + clear() { + this.container.style.display = 'none'; + this.list.innerHTML = ''; + } + + setActive() { + this.container.style.display = ''; + } +} class AppSidebarLeft { private sidebarEl = document.querySelector('.page-chats .chats-container') as HTMLDivElement; @@ -15,10 +46,11 @@ class AppSidebarLeft { private menuEl = this.toolsBtn.querySelector('.btn-menu'); private savedBtn = this.menuEl.querySelector('.menu-saved'); + private archivedBtn = this.menuEl.querySelector('.menu-archive'); private listsContainer: HTMLDivElement = null; - private searchMessagesList: HTMLUListElement = null; + private chatsArchivedContainer = document.getElementById('chats-archived-container') as HTMLDivElement; private chatsContainer = document.getElementById('chats-container') as HTMLDivElement; private chatsOffsetIndex = 0; private chatsPreloader: HTMLDivElement; @@ -39,6 +71,13 @@ class AppSidebarLeft { private query = ''; public scroll: Scrollable = null; + + public searchGroups: {[group: string]: SearchGroup} = { + contacts: new SearchGroup('Contacts and Chats', 'contacts'), + globalContacts: new SearchGroup('Global Search', 'contacts'), + globalMessages: new SearchGroup('Global Search', 'messages'), + privateMessages: new SearchGroup('Private Search', 'messages') + }; constructor() { this.chatsPreloader = document.createElement('div'); @@ -55,7 +94,10 @@ class AppSidebarLeft { this.scroll.container.addEventListener('scroll', this.onChatsScroll.bind(this)); this.listsContainer = new Scrollable(this.searchContainer).container; - this.searchMessagesList = document.createElement('ul'); + + for(let i in this.searchGroups) { + this.listsContainer.append(this.searchGroups[i].container); + } this.savedBtn.addEventListener('click', (e) => { this.log('savedbtn click'); @@ -64,6 +106,12 @@ class AppSidebarLeft { }, 0); }); + this.archivedBtn.addEventListener('click', (e) => { + this.chatsArchivedContainer.classList.add('active'); + this.toolsBtn.classList.remove('tgico-menu', 'btn-menu-toggle'); + this.toolsBtn.classList.add('tgico-back'); + }); + /* this.listsContainer.insertBefore(this.searchMessagesList, this.listsContainer.lastElementChild); for(let i = 0; i < 25; ++i) { let li = document.createElement('li'); @@ -75,8 +123,6 @@ class AppSidebarLeft { //this.searchContainer.append(this.listsContainer); - appDialogsManager.setListClickListener(this.searchMessagesList); - let clickTimeout = 0; this.searchInput.addEventListener('focus', (e) => { this.toolsBtn.classList.remove('tgico-menu', 'btn-menu-toggle'); @@ -84,7 +130,9 @@ class AppSidebarLeft { this.searchContainer.classList.add('active'); if(!this.searchInput.value) { - this.searchMessagesList.innerHTML = ''; + for(let i in this.searchGroups) { + this.searchGroups[i].clear(); + } } this.searchInput.addEventListener('blur', (e) => { @@ -111,10 +159,6 @@ class AppSidebarLeft { let value = this.searchInput.value; this.log('input', value); - if(this.listsContainer.contains(this.searchMessagesList)) { - this.listsContainer.removeChild(this.searchMessagesList); - } - if(!value.trim()) { return; } @@ -124,11 +168,13 @@ class AppSidebarLeft { this.loadedCount = 0; this.foundCount = 0; this.offsetRate = 0; - this.searchMessagesList.innerHTML = ''; + + for(let i in this.searchGroups) { + this.searchGroups[i].clear(); + } + this.searchPromise = null; - this.searchMore().then(() => { - this.listsContainer.append(this.searchMessagesList); - }); + this.searchMore(); }); this.toolsBtn.addEventListener('click', (e) => { @@ -138,6 +184,7 @@ class AppSidebarLeft { this.toolsBtn.classList.add('tgico-menu', 'btn-menu-toggle'); this.toolsBtn.classList.remove('tgico-back'); this.searchContainer.classList.remove('active'); + this.chatsArchivedContainer.classList.remove('active'); this.peerID = 0; e.stopPropagation(); e.cancelBubble = true; @@ -156,6 +203,10 @@ class AppSidebarLeft { this.onChatsScroll(); }, 0); }); + + /* appUsersManager.getTopPeers().then(categories => { + this.log('got top categories:', categories); + }); */ } public async loadDialogs() { @@ -207,7 +258,7 @@ class AppSidebarLeft { public onSidebarScroll() { if(!this.query.trim()) return; - let elements = Array.from(this.searchMessagesList.childNodes).slice(-5); + let elements = Array.from(this.searchGroups[this.peerID ? 'privateMessages' : 'globalMessages'].list.childNodes).slice(-5); for(let li of elements) { if(isElementInViewport(li)) { this.log('Will load more search'); @@ -244,6 +295,64 @@ class AppSidebarLeft { } let maxID = appMessagesIDsManager.getMessageIDInfo(this.minMsgID)[0]; + + if(!this.peerID && !maxID) { + appUsersManager.searchContacts(query, 20).then((contacts: any) => { + if(this.searchInput.value != query) { + return; + } + + this.log('input search contacts result:', contacts); + + let setResults = (results: any, group: SearchGroup, showMembersCount = false) => { + results.forEach((inputPeer: any) => { + let peerID = appPeersManager.getPeerID(inputPeer); + let peer = appPeersManager.getPeer(peerID); + let originalDialog = appMessagesManager.getDialogByPeerID(peerID)[0]; + + this.log('contacts peer', peer); + + if(!originalDialog) { + this.log('no original dialog by peerID:', peerID); + + originalDialog = { + peerID: peerID, + pFlags: {}, + peer: peer + }; + } + + let {dialog, dom} = appDialogsManager.addDialog(originalDialog, group.list, false); + + if(showMembersCount && (peer.participants_count || peer.participants)) { + let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID); + let participants_count = peer.participants_count || peer.participants.participants.length; + let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); + dom.lastMessageSpan.innerText = subtitle; + } else { + let username = appPeersManager.getPeerUsername(peerID); + if(!username) { + let user = appUsersManager.getUser(peerID); + if(user && user.phone) { + username = '+' + formatPhoneNumber(user.phone).formatted; + } + } else { + username = '@' + username; + } + + dom.lastMessageSpan.innerText = username; + } + }); + + if(results.length) { + group.setActive(); + } + }; + + setResults(contacts.my_results, this.searchGroups.contacts, true); + setResults(contacts.results, this.searchGroups.globalContacts); + }); + } return this.searchPromise = appMessagesManager.getSearch(this.peerID, query, null, maxID, 20, this.offsetRate).then(res => { this.searchPromise = null; @@ -260,6 +369,9 @@ class AppSidebarLeft { history.shift(); } + let searchGroup = this.searchGroups[this.peerID ? 'privateMessages' : 'globalMessages']; + searchGroup.setActive(); + history.forEach((msgID: number) => { let message = appMessagesManager.getMessage(msgID); let originalDialog = appMessagesManager.getDialogByPeerID(message.peerID)[0]; @@ -274,7 +386,7 @@ class AppSidebarLeft { }; } - let {dialog, dom} = appDialogsManager.addDialog(originalDialog, this.searchMessagesList, false); + let {dialog, dom} = appDialogsManager.addDialog(originalDialog, searchGroup.list, false); appDialogsManager.setLastMessage(dialog, message, dom); }); diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index 4a11af69..590a632d 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -1,4 +1,4 @@ -import { LazyLoadQueue, horizontalMenu, wrapDocument, formatPhoneNumber } from "../../components/misc"; +import { horizontalMenu, formatPhoneNumber } from "../../components/misc"; import Scrollable from '../../components/scrollable'; import { isElementInViewport, $rootScope } from "../utils"; import appMessagesManager from "./appMessagesManager"; @@ -10,6 +10,8 @@ import { RichTextProcessor } from "../richtextprocessor"; import { logger } from "../polyfill"; import appImManager from "./appImManager"; import appMediaViewer from "./appMediaViewer"; +import LazyLoadQueue from "../../components/lazyLoadQueue"; +import { wrapDocument } from "../../components/wrappers"; class AppSidebarRight { public sidebarEl = document.querySelector('.profile-container') as HTMLDivElement; diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index b2bd118c..c368c109 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -1,6 +1,7 @@ -import { MTDocument } from "../../components/misc"; import AppStorage from '../storage'; -import { MTProto } from "../mtproto/mtproto"; +import { MTDocument } from '../../components/wrappers'; +import apiManager from '../mtproto/apiManager'; +import apiFileManager from '../mtproto/apiFileManager'; export type MTStickerSet = { _: 'stickerSet', @@ -78,7 +79,7 @@ class appStickersManager { }) { if(this.stickerSets[set.id]) return this.stickerSets[set.id]; - let promise = MTProto.apiManager.invokeApi('messages.getStickerSet', { + let promise = apiManager.invokeApi('messages.getStickerSet', { stickerset: { _: 'inputStickerSetID', id: set.id, @@ -132,7 +133,7 @@ class appStickersManager { let thumb = stickerSet.thumb; let dcID = stickerSet.thumb_dc_id; - let promise = MTProto.apiFileManager.downloadSmallFile({ + let promise = apiFileManager.downloadSmallFile({ _: 'inputStickerSetThumb', stickerset: { _: 'inputStickerSetID', diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 2b7e5d92..55ece52c 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -1,6 +1,8 @@ import { SearchIndexManager, safeReplaceObject, isObject, tsNow, copy, $rootScope } from "../utils"; -import { MTProto } from "../mtproto/mtproto"; import { RichTextProcessor } from "../richtextprocessor"; +import appChatsManager from "./appChatsManager"; +import apiManager from "../mtproto/apiManager"; +import serverTimeManager from "../mtproto/serverTimeManager"; export class AppUsersManager { public users: any = {}; @@ -13,7 +15,7 @@ export class AppUsersManager { public myID: number; constructor() { - MTProto.apiManager.getUserID().then((id) => { + apiManager.getUserID().then((id) => { this.myID = id; }); @@ -37,11 +39,11 @@ export class AppUsersManager { user.status = update.status; if(user.status) { if(user.status.expires) { - user.status.expires -= MTProto.serverTimeManager.serverTimeOffset; + user.status.expires -= serverTimeManager.serverTimeOffset; } if(user.status.was_online) { - user.status.was_online -= MTProto.serverTimeManager.serverTimeOffset; + user.status.was_online -= serverTimeManager.serverTimeOffset; } } @@ -149,8 +151,7 @@ export class AppUsersManager { } public saveApiUsers(apiUsers: any[]) { - // @ts-ignore - apiUsers.forEach(this.saveApiUser.bind(this)); + apiUsers.forEach((user) => this.saveApiUser(user)); } public saveApiUser(apiUser: any, noReplace?: boolean) { @@ -200,11 +201,11 @@ export class AppUsersManager { if(apiUser.status) { if(apiUser.status.expires) { - apiUser.status.expires -= MTProto.serverTimeManager.serverTimeOffset + apiUser.status.expires -= serverTimeManager.serverTimeOffset } if(apiUser.status.was_online) { - apiUser.status.was_online -= MTProto.serverTimeManager.serverTimeOffset + apiUser.status.was_online -= serverTimeManager.serverTimeOffset } } @@ -349,20 +350,6 @@ export class AppUsersManager { return user; } - /* public openUser(userID: number, override) { - var scope = $rootScope.$new() - scope.userID = userID - scope.override = override || {} - - var modalInstance = $modal.open({ - templateUrl: templateUrl('user_modal'), - controller: 'UserModalController', - scope: scope, - windowClass: 'user_modal_window mobile_modal', - backdrop: 'single' - }) - } */ - /* function importContact (phone, firstName, lastName) { return MtpApiManager.invokeApi('contacts.importContacts', { contacts: [{ @@ -424,7 +411,7 @@ export class AppUsersManager { ids.push(this.getUserInput(userID)); }) - return MTProto.apiManager.invokeApi('contacts.deleteContacts', { + return apiManager.invokeApi('contacts.deleteContacts', { id: ids }).then(() => { userIDs.forEach((userID) => { @@ -433,6 +420,35 @@ export class AppUsersManager { }); } + public getTopPeers() { + return apiManager.invokeApi('contacts.getTopPeers', { + flags: 1, + correspondents: true, + offset: 0, + limit: 5, + hash: 0, + }).then((peers: any) => { + //console.log(peers); + this.saveApiUsers(peers.users); + appChatsManager.saveApiChats(peers.chats); + + return peers.categories; + }); + } + + public searchContacts(query: string, limit = 20) { + return apiManager.invokeApi('contacts.search', { + q: query, + limit + }).then((peers: any) => { + //console.log(peers); + this.saveApiUsers(peers.users); + appChatsManager.saveApiChats(peers.chats); + + return peers; + }); + } + public onContactUpdated(userID: number, isContact: boolean) { userID = parseInt('' + userID); @@ -453,19 +469,6 @@ export class AppUsersManager { } } - /* function openImportContact () { - return $modal.open({ - templateUrl: templateUrl('import_contact_modal'), - controller: 'ImportContactModalController', - windowClass: 'md_simple_modal_window mobile_modal' - }).result.then(function (foundUserID) { - if (!foundUserID) { - return $q.reject() - } - return foundUserID - }) - } */ - public setUserStatus(userID: number, offline: boolean) { if(this.isBot(userID)) { return; diff --git a/src/lib/richtextprocessor.js b/src/lib/richtextprocessor.js index bba32d4f..56e4da23 100644 --- a/src/lib/richtextprocessor.js +++ b/src/lib/richtextprocessor.js @@ -8,7 +8,7 @@ var EmojiHelper = { var emojiData = Config.Emoji; var emojiIconSize = emojiData.img_size; -var emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1 && false, +var emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1/* && false */, emojiCode; //var emojiRegExp = '\\u0023\\u20E3|\\u00a9|\\u00ae|\\u203c|\\u2049|\\u2139|[\\u2194-\\u2199]|\\u21a9|\\u21aa|\\u231a|\\u231b|\\u23e9|[\\u23ea-\\u23ec]|\\u23f0|\\u24c2|\\u25aa|\\u25ab|\\u25b6|\\u2611|\\u2614|\\u26fd|\\u2705|\\u2709|[\\u2795-\\u2797]|\\u27a1|\\u27b0|\\u27bf|\\u2934|\\u2935|[\\u2b05-\\u2b07]|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u3030|\\u303d|\\u3297|\\u3299|[\\uE000-\\uF8FF\\u270A-\\u2764\\u2122\\u25C0\\u25FB-\\u25FE\\u2615\\u263a\\u2648-\\u2653\\u2660-\\u2668\\u267B\\u267F\\u2693\\u261d\\u26A0-\\u26FA\\u2708\\u2702\\u2601\\u260E]|[\\u2600\\u26C4\\u26BE\\u23F3\\u2764]|\\uD83D[\\uDC00-\\uDFFF]|\\uD83C[\\uDDE8-\\uDDFA\uDDEC]\\uD83C[\\uDDEA-\\uDDFA\uDDE7]|[0-9]\\u20e3|\\uD83C[\\uDC00-\\uDFFF]'; //var emojiRegExp = '\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff]'; diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index e89b837d..6fa1c840 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -137,6 +137,12 @@ box-sizing: border-box; min-height: 100%; justify-content: flex-end; + + &.is-chat { + .in { + padding-left: 36px; + } + } } .service { @@ -474,12 +480,12 @@ .user-avatar { position: absolute; - left: -2.5rem; - width: 32px; - height: 32px; - line-height: 32px; + left: -3rem; + width: 40px; + height: 40px; + line-height: 40px; bottom: 0; - font-size: .85rem; + font-size: 1rem; } &:not(.forwarded).hide-name, &.emoji-big { @@ -657,17 +663,27 @@ } } - .bubble.read { + .bubble.is-read { .time .tgico:after { content: $tgico-checks; } } - .bubble.sent { + .bubble.is-sent { .time .tgico:after { content: $tgico-check; } } + + .bubble.is-sending { + .time .tgico:after { + content: $tgico-sending; + } + } + + .bubble.is-reply .name { + display: none; + } .bubble { background-color: #eeffde; diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index eb906909..9531fcad 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -46,6 +46,7 @@ display: grid; grid-auto-columns: 1fr; /* grid-gap: 4px; */ + width: 100%; } li { @@ -133,11 +134,19 @@ .user-title { max-width: 80%; - .emoji { + img.emoji { vertical-align: top; width: 18px; height: 18px; } + + span.emoji { + overflow: visible; + margin: 0; + width: auto; + font-size: 14px; + vertical-align: unset; + } } .user-last-message { @@ -148,11 +157,15 @@ color: $darkblue; } - .emoji { - font-size: 1.2rem; + img.emoji { width: 20px; height: 20px; + } + + span.emoji { + font-size: 1.2rem; margin: 0 .125rem; + overflow: visible; } } diff --git a/src/scss/partials/_fonts.scss b/src/scss/partials/_fonts.scss index 98948b40..067f0c5c 100644 --- a/src/scss/partials/_fonts.scss +++ b/src/scss/partials/_fonts.scss @@ -108,7 +108,7 @@ content: "\e918"; } .tgico-sending:before { - content: "\e919"; + content: $tgico-sending; } .tgico-sendingerror:before { content: "\e91a"; diff --git a/src/scss/partials/_ico.scss b/src/scss/partials/_ico.scss index f6be8558..1095cb61 100644 --- a/src/scss/partials/_ico.scss +++ b/src/scss/partials/_ico.scss @@ -3,3 +3,4 @@ $tgico-font-path: "../../assets/fonts" !default; $tgico-check: "\e900"; $tgico-checks: "\e95a"; +$tgico-sending: "\e919"; diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index ea2b4fb3..2366808d 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -21,7 +21,7 @@ position: relative; } - #search-container { + #search-container, #chats-archived-container { display: none; width: 100%; max-height: 100%; @@ -37,4 +37,17 @@ display: flex; } } + + .search-group { + width: 100%; + border-bottom: 1px solid #DADCE0; + padding: 1rem 0 .5rem; + margin-bottom: .5rem; + + &__name { + color: $color-gray; + padding: 0 1.85rem; + padding-bottom: 1rem; + } + } } diff --git a/src/scss/style.scss b/src/scss/style.scss index 252137f4..4b7727c5 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -314,7 +314,10 @@ input { } .c-ripple.active .c-ripple__circle { //-webkit-animation: a-ripple 750ms ease-in; - animation: a-ripple 750ms ease-in-out; + //animation: a-ripple 750ms ease-in-out; + will-change: padding-bottom, width, opacity; + -webkit-animation: a-ripple 625ms ease-in-out; + animation: a-ripple 625ms ease-in-out; } /** @@ -324,6 +327,7 @@ input { @-webkit-keyframes a-ripple { 0% { opacity: 0; + //opacity: 1; } 25% { opacity: 1; @@ -337,6 +341,7 @@ input { @keyframes a-ripple { 0% { opacity: 0; + //opacity: 1; } 25% { opacity: 1; @@ -396,6 +401,12 @@ input { background-image: url('../assets/img/doc-in.svg'); } } + + &.photo { + .document-ico { + border-radius: $border-radius; + } + } .document-name { white-space: nowrap; @@ -488,7 +499,7 @@ input { transform: translateY(-50%); background-color: #fff; font-size: 0.85rem; - transition: .2s all; + transition: .2s all, .1s opacity; display: inline-block; cursor: text; } @@ -542,6 +553,8 @@ input { transform: none; padding: 0 5px; left: 7.5px; + font-size: 0.85rem!important; + opacity: 1; } } } @@ -930,8 +943,6 @@ $width: 100px; display: inline-block; /* width: 100%; height: 100%; */ - width: 18px; - height: 18px; max-width: 100%; max-height: 100%; vertical-align: middle; @@ -940,7 +951,11 @@ $width: 100px; font-size: 1em; font-family: apple color emoji,segoe ui emoji,noto color emoji,android emoji,emojisymbols,emojione mozilla,twemoji mozilla,segoe ui symbol; +} +img.emoji { + width: 18px; + height: 18px; } .popup { @@ -1309,6 +1324,7 @@ div.scrollable::-webkit-scrollbar-thumb { .popup-send-photo { .popup-container { + width: 420px; max-width: 420px; max-height: 425px; overflow: hidden; @@ -1352,6 +1368,7 @@ div.scrollable::-webkit-scrollbar-thumb { justify-content: center; width: fit-content; border-radius: $border-radius-medium; + margin: 0 auto; /* align-items: center; */ .document { @@ -1366,6 +1383,12 @@ div.scrollable::-webkit-scrollbar-thumb { overflow: hidden; text-overflow: ellipsis; } + + /* &.photo { + .document-ico { + border-radius: $border-radius; + } + } */ } img { @@ -1381,6 +1404,15 @@ div.scrollable::-webkit-scrollbar-thumb { font-size: 1.15rem; padding: 0 15px; border-radius: $border-radius-medium; + + &:focus { + padding: 0 14.5px; + } + } + + label { + font-size: inherit; + opacity: 0; } } }