From d15abda503af645afd06249673a39afc3d00e07e Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sat, 2 May 2020 00:28:40 +0300 Subject: [PATCH] Albums send & render after send --- src/components/chatInput.ts | 260 ++++++++++---- src/components/preloader.ts | 3 - src/components/wrappers.ts | 169 ++++++++- src/lib/appManagers/appDocsManager.ts | 15 + src/lib/appManagers/appImManager.ts | 354 +++++++++---------- src/lib/appManagers/appMediaViewer.ts | 18 +- src/lib/appManagers/appMessagesManager.ts | 320 ++++++++++++++++- src/lib/appManagers/appPhotosManager.ts | 15 + src/lib/appManagers/appSidebarRight.ts | 2 +- src/lib/mtproto/mtprotoworker.ts | 1 + src/scss/partials/_chat.scss | 18 +- src/scss/partials/_emojiDropdown.scss | 2 +- src/scss/partials/_mediaViewer.scss | 2 +- src/scss/partials/popups/_editAvatar.scss | 52 +++ src/scss/partials/popups/_mediaAttacher.scss | 150 ++++++++ src/scss/partials/popups/_popup.scss | 80 +++++ src/scss/style.scss | 238 +------------ 17 files changed, 1173 insertions(+), 526 deletions(-) create mode 100644 src/scss/partials/popups/_editAvatar.scss create mode 100644 src/scss/partials/popups/_mediaAttacher.scss create mode 100644 src/scss/partials/popups/_popup.scss diff --git a/src/components/chatInput.ts b/src/components/chatInput.ts index 0300ffd3..230f2631 100644 --- a/src/components/chatInput.ts +++ b/src/components/chatInput.ts @@ -5,11 +5,12 @@ import { RichTextProcessor } from "../lib/richtextprocessor"; import apiManager from "../lib/mtproto/mtprotoworker"; import appWebPagesManager from "../lib/appManagers/appWebPagesManager"; import appImManager from "../lib/appManagers/appImManager"; -import { calcImageInBox, getRichValue } from "../lib/utils"; +import { getRichValue, calcImageInBox } from "../lib/utils"; import { wrapDocument, wrapReply } from "./wrappers"; import appMessagesManager from "../lib/appManagers/appMessagesManager"; import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from "./emoticonsDropdown"; import lottieLoader from "../lib/lottieLoader"; +import { Layouter, RectPart } from "./groupedLayout"; export class ChatInput { public pageEl = document.querySelector('.page-chats') as HTMLDivElement; @@ -187,108 +188,178 @@ export class ChatInput { }); let attachFile = (file: File) => { - willAttach.file = file; - delete willAttach.objectURL; - delete willAttach.duration; - delete willAttach.width; - delete willAttach.height; + return new Promise((resolve, reject) => { + let params: SendFileParams = {}; + params.file = file; + console.log('selected file:', file, typeof(file), willAttach); + let itemDiv = document.createElement('div'); + switch(willAttach.type) { + case 'media': { + let isVideo = file.type.indexOf('video/') === 0; + + itemDiv.classList.add('popup-item-media'); + if(isVideo) { + let video = document.createElement('video'); + let source = document.createElement('source'); + source.src = params.objectURL = URL.createObjectURL(file); + video.autoplay = false; + video.controls = false; + + video.onloadeddata = () => { + params.width = video.videoWidth; + params.height = video.videoHeight; + params.duration = Math.floor(video.duration); + + itemDiv.append(video); + resolve(itemDiv); + }; + + video.append(source); + } else { + let img = new Image(); + img.src = params.objectURL = URL.createObjectURL(file); + img.onload = () => { + params.width = img.naturalWidth; + params.height = img.naturalHeight; + + itemDiv.append(img); + resolve(itemDiv); + }; + } + + break; + } + + case 'document': { + let docDiv = wrapDocument({ + file: file, + file_name: file.name || '', + size: file.size, + type: file.type.indexOf('image/') !== -1 ? 'photo' : 'doc' + } as any, false, true); + + itemDiv.append(docDiv); + resolve(itemDiv); + break; + } + } + + willAttach.sendFileDetails.push(params); + }); + }; + + let attachFiles = (files: File[]) => { this.fileInput.value = ''; + let container = this.attachMediaPopUp.container.firstElementChild as HTMLElement; + container.classList.remove('is-media', 'is-document', 'is-album'); + this.attachMediaPopUp.captionInput.value = ''; this.attachMediaPopUp.mediaContainer.innerHTML = ''; - this.attachMediaPopUp.mediaContainer.style.width = ''; - this.attachMediaPopUp.mediaContainer.style.height = ''; - this.attachMediaPopUp.mediaContainer.classList.remove('is-document'); + this.attachMediaPopUp.mediaContainer.style.width = this.attachMediaPopUp.mediaContainer.style.height = ''; + //willAttach.sendFileDetails.length = 0; + willAttach.sendFileDetails = []; // need new array - if(willAttach.type == 'media' && !['image/', 'video/'].find(s => file.type.indexOf(s) === 0)) { - willAttach.type = 'document'; + files = files.filter(file => { + if(willAttach.type == 'media') { + return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0); + } else { + return true; + } + }); + + if(files.length) { + if(willAttach.type == 'document') { + this.attachMediaPopUp.titleEl.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File'); + container.classList.add('is-document'); + } else { + container.classList.add('is-media'); + + let foundPhotos = 0; + let foundVideos = 0; + files.forEach(file => { + if(file.type.indexOf('image/') === 0) ++foundPhotos; + else if(file.type.indexOf('video/') === 0) ++foundVideos; + }); + + if(foundPhotos && foundVideos) { + this.attachMediaPopUp.titleEl.innerText = 'Send Album'; + } else if(foundPhotos) { + this.attachMediaPopUp.titleEl.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo'); + } else if(foundVideos) { + this.attachMediaPopUp.titleEl.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video'); + } + } } - console.log('selected file:', file, typeof(file), willAttach); + Promise.all(files.map(attachFile)).then(results => { + if(willAttach.type == 'media') { + if(willAttach.sendFileDetails.length > 1) { + container.classList.add('is-album'); - switch(willAttach.type) { - case 'media': { - let isVideo = file.type.indexOf('video/') === 0; + let layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4); + let layout = layouter.layout(); - if(isVideo) { - let video = document.createElement('video'); - let source = document.createElement('source'); - source.src = willAttach.objectURL = URL.createObjectURL(file); - video.autoplay = false; - video.controls = false; + for(let {geometry, sides} of layout) { + let div = results.shift(); - video.onloadeddata = () => { - willAttach.width = video.videoWidth; - willAttach.height = video.videoHeight; - willAttach.duration = Math.floor(video.duration); - - let {w, h} = calcImageInBox(willAttach.width, willAttach.height, 378, 256); - this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; - this.attachMediaPopUp.mediaContainer.style.height = h + 'px'; - this.attachMediaPopUp.mediaContainer.append(video); - this.attachMediaPopUp.container.classList.add('active'); - }; - - video.append(source); - - this.attachMediaPopUp.titleEl.innerText = 'Send Video'; - } else { - let img = new Image(); - img.src = willAttach.objectURL = URL.createObjectURL(file); - img.onload = () => { - willAttach.width = img.naturalWidth; - willAttach.height = img.naturalHeight; - - let {w, h} = calcImageInBox(willAttach.width, willAttach.height, 378, 256); - this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; - this.attachMediaPopUp.mediaContainer.style.height = h + 'px'; - this.attachMediaPopUp.mediaContainer.append(img); - this.attachMediaPopUp.container.classList.add('active'); - }; - - this.attachMediaPopUp.titleEl.innerText = 'Send Photo'; - } - - break; - } + div.style.width = geometry.width + 'px'; + div.style.height = geometry.height + 'px'; + div.style.top = geometry.y + 'px'; + div.style.left = geometry.x + 'px'; - case 'document': { - let docDiv = wrapDocument({ - file: file, - file_name: file.name || '', - size: file.size, - type: file.type.indexOf('image/') !== -1 ? 'photo' : 'doc' - } as any, false, true); + if(sides & RectPart.Right) { + this.attachMediaPopUp.mediaContainer.style.width = geometry.width + geometry.x + 'px'; + } - this.attachMediaPopUp.titleEl.innerText = 'Send File'; + if(sides & RectPart.Bottom) { + this.attachMediaPopUp.mediaContainer.style.height = geometry.height + geometry.y + 'px'; + } - this.attachMediaPopUp.mediaContainer.append(docDiv); - this.attachMediaPopUp.mediaContainer.classList.add('is-document'); - this.attachMediaPopUp.container.classList.add('active'); - break; + this.attachMediaPopUp.mediaContainer.append(div); + } + + console.log('chatInput album layout:', layout); + } else { + let params = willAttach.sendFileDetails[0]; + let div = results[0]; + let {w, h} = calcImageInBox(params.width, params.height, 380, 320); + div.style.width = w + 'px'; + div.style.height = h + 'px'; + this.attachMediaPopUp.mediaContainer.append(div); + } + } else { + this.attachMediaPopUp.mediaContainer.append(...results); } - } + + this.attachMediaPopUp.container.classList.add('active'); + }); }; - let willAttach: Partial<{ - type: 'media' | 'document', - isMedia: boolean, + type SendFileParams = Partial<{ file: File, - caption: string, objectURL: string, width: number, height: number, duration: number - }> = {}; + }>; + + let willAttach: Partial<{ + type: 'media' | 'document', + isMedia: boolean, + sendFileDetails: SendFileParams[] + }> = { + sendFileDetails: [] + }; this.fileInput.addEventListener('change', (e) => { - var file = (e.target as HTMLInputElement & EventTarget).files[0]; - if(!file) { + let files = (e.target as HTMLInputElement & EventTarget).files; + if(!files.length) { return; } - attachFile(file); + attachFiles(Array.from(files)); }, false); this.attachMenu.media.addEventListener('click', () => { @@ -329,10 +400,43 @@ export class ChatInput { this.attachMediaPopUp.sendBtn.addEventListener('click', () => { this.attachMediaPopUp.container.classList.remove('active'); - willAttach.caption = this.attachMediaPopUp.captionInput.value; + let caption = this.attachMediaPopUp.captionInput.value; willAttach.isMedia = willAttach.type == 'media'; - appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach); + console.log('will send files with options:', willAttach); + + let peerID = appImManager.peerID; + + if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) { + appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({ + caption, + replyToMsgID: this.replyToMsgID + }, willAttach)); + } else { + if(caption) { + if(willAttach.sendFileDetails.length > 1) { + appMessagesManager.sendText(peerID, caption, {replyToMsgID: this.replyToMsgID}); + caption = ''; + this.replyToMsgID = 0; + } + } + + let promises = willAttach.sendFileDetails.map(params => { + let promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({ + isMedia: willAttach.isMedia, + caption, + replyToMsgID: this.replyToMsgID + }, params)); + + caption = ''; + this.replyToMsgID = 0; + return promise; + }); + } + + //Promise.all(promises); + + //appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach); this.onMessageSent(); }); diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 414fb718..68e66fbb 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -4,7 +4,6 @@ import { CancellablePromise } from "../lib/polyfill"; export default class ProgressivePreloader { public preloader: HTMLDivElement = null; private circle: SVGCircleElement = null; - private progress = 0; private promise: CancellablePromise = null; private tempID = 0; private detached = true; @@ -103,8 +102,6 @@ export default class ProgressivePreloader { } public setProgress(percents: number) { - this.progress = percents; - if(!isInDOM(this.circle)) { return; } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 804378c9..9599ce18 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -1,4 +1,4 @@ -import appPhotosManager from '../lib/appManagers/appPhotosManager'; +import appPhotosManager, { MTPhoto } from '../lib/appManagers/appPhotosManager'; //import CryptoWorker from '../lib/crypto/cryptoworker'; import apiManager from '../lib/mtproto/mtprotoworker'; import LottieLoader from '../lib/lottieLoader'; @@ -13,6 +13,8 @@ import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer'; import { RichTextProcessor } from '../lib/richtextprocessor'; import { CancellablePromise } from '../lib/polyfill'; import { renderImageFromUrl } from './misc'; +import appMessagesManager from '../lib/appManagers/appMessagesManager'; +import { Layouter, RectPart } from './groupedLayout'; export type MTDocument = { _: 'document' | 'documentEmpty', @@ -75,6 +77,17 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai if(withTail) { img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut); + } else if(!boxWidth && !boxHeight) { // album + let sizes = doc.thumbs; + if(!doc.downloaded && sizes && sizes[0].bytes) { + appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false); + } + + img = container.firstElementChild as HTMLImageElement || new Image(); + + if(!container.contains(img)) { + container.append(img); + } } else { if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) { let size = appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight); @@ -106,7 +119,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai let loadVideo = () => { let promise = appDocsManager.downloadDoc(doc); - if(!doc.downloaded) { + if(message.media.preloader) { // means upload + message.media.preloader.attach(container); + } else if(!doc.downloaded) { let preloader = new ProgressivePreloader(container, true); preloader.attach(container, true, promise); } @@ -600,14 +615,24 @@ function wrapMediaWithTail(photo: any, message: {mid: number, message: string}, return image; } -export function wrapPhoto(photoID: string, message: any, container: HTMLDivElement, boxWidth = 380, boxHeight = 380, withTail = true, isOut = false, lazyLoadQueue: LazyLoadQueue, middleware: () => boolean) { +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 size: MTPhotoSize; let image: SVGImageElement | HTMLImageElement; if(withTail) { image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut); - } else { // means webpage's preview + } else if(size) { // album + let sizes = photo.sizes; + if(!photo.downloaded && sizes && sizes[0].bytes) { + appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false); + } + + image = container.firstElementChild as HTMLImageElement || new Image(); + + if(!container.contains(image)) { + container.appendChild(image); + } + } else if(boxWidth && boxHeight) { // means webpage's preview size = appPhotosManager.setAttachmentSize(photoID, container, boxWidth, boxHeight, false); image = container.firstElementChild as HTMLImageElement || new Image(); @@ -626,7 +651,12 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme } */ let preloader: ProgressivePreloader; - if(!photo.downloaded) preloader = new ProgressivePreloader(container, false); + if(message.media.preloader) { // means upload + message.media.preloader.attach(container); + } else if(!photo.downloaded) { + preloader = new ProgressivePreloader(container, false); + } + let load = () => { let promise = appPhotosManager.preloadPhoto(photoID, size); @@ -637,7 +667,7 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme return promise.then(() => { if(middleware && !middleware()) return; - renderImageFromUrl(image, photo.url); + renderImageFromUrl(image || container, photo.url); }); }; @@ -854,3 +884,128 @@ export function wrapReply(title: string, subtitle: string, media?: any) { return div; } + +export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut}: { + groupID: string, + attachmentDiv: HTMLElement, + middleware?: () => boolean, + lazyLoadQueue?: LazyLoadQueue, + uploading?: boolean, + isOut: boolean +}) { + let items: {size: MTPhotoSize, media: any, message: any}[] = []; + + // higher msgID will be the last in album + let storage = appMessagesManager.groupedMessagesStorage[groupID]; + for(let mid in storage) { + let m = appMessagesManager.getMessage(+mid); + let media = m.media.photo || m.media.document; + + let size: any = media._ == 'photo' ? appPhotosManager.choosePhotoSize(media, 380, 380) : {w: media.w, h: media.h}; + items.push({size, media, message: m}); + } + + let spacing = 2; + let layouter = new Layouter(items.map(i => ({w: i.size.w, h: i.size.h})), 451, 100, spacing); + let layout = layouter.layout(); + console.log('layout:', layout, items.map(i => ({w: i.size.w, h: i.size.h}))); + + /* let borderRadius = window.getComputedStyle(realParent).getPropertyValue('border-radius'); + let brSplitted = fillPropertyValue(borderRadius); */ + + for(let {geometry, sides} of layout) { + let {size, media, message} = items.shift(); + let div = document.createElement('div'); + div.classList.add('album-item'); + div.dataset.mid = message.mid; + + div.style.width = geometry.width + 'px'; + div.style.height = geometry.height + 'px'; + div.style.top = geometry.y + 'px'; + div.style.left = geometry.x + 'px'; + + if(sides & RectPart.Right) { + attachmentDiv.style.width = geometry.width + geometry.x + 'px'; + } + + if(sides & RectPart.Bottom) { + attachmentDiv.style.height = geometry.height + geometry.y + 'px'; + } + + if(sides & RectPart.Left && sides & RectPart.Top) { + div.style.borderTopLeftRadius = 'inherit'; + } + + if(sides & RectPart.Left && sides & RectPart.Bottom) { + div.style.borderBottomLeftRadius = 'inherit'; + } + + if(sides & RectPart.Right && sides & RectPart.Top) { + div.style.borderTopRightRadius = 'inherit'; + } + + if(sides & RectPart.Right && sides & RectPart.Bottom) { + div.style.borderBottomRightRadius = 'inherit'; + } + + if(media._ == 'photo') { + wrapPhoto( + media.id, + message, + div, + 0, + 0, + false, + isOut, + lazyLoadQueue, + middleware, + size + ); + } else { + wrapVideo({ + doc: message.media.document, + container: div, + message, + boxWidth: 0, + boxHeight: 0, + withTail: false, + isOut, + lazyLoadQueue, + middleware + }); + } + + /* let load = () => appPhotosManager.preloadPhoto(media._ == 'photo' ? media.id : media, size) + .then((blob) => { + if(middleware && !middleware()) { + console.warn('peer changed'); + return; + } + + if(!uploading) { + preloader.detach(); + } + + if(media && media.url) { + renderImageFromUrl(div, media.url); + } else { + let url = URL.createObjectURL(blob); + + let img = new Image(); + img.src = url; + img.onload = () => { + div.style.backgroundImage = 'url(' + url + ')'; + }; + } + + //div.style.backgroundImage = 'url(' + url + ')'; + }); + + load(); */ + + // @ts-ignore + //div.style.backgroundColor = '#' + Math.floor(Math.random() * (2 ** 24 - 1)).toString(16).padStart(6, '0'); + + attachmentDiv.append(div); + } +} diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 9c54b9b9..c4ebcd70 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -144,6 +144,21 @@ class AppDocsManager { public getDoc(docID: any): MTDocument { return isObject(docID) ? docID : this.docs[docID]; } + + public getInputByID(docID: any) { + let doc = this.getDoc(docID); + return { + _: 'inputMediaDocument', + flags: 0, + id: { + _: 'inputDocument', + id: doc.id, + access_hash: doc.access_hash, + file_reference: doc.file_reference + }, + ttl_seconds: 0 + }; + } public getFileName(doc: MTDocument) { if(doc.file_name) { diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index ab567a28..922dd9a3 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -17,15 +17,14 @@ import appSidebarLeft from "./appSidebarLeft"; import appChatsManager from "./appChatsManager"; import appMessagesIDsManager from "./appMessagesIDsManager"; import apiUpdatesManager from './apiUpdatesManager'; -import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, MTPhotoSize } from '../../components/wrappers'; +import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum } from '../../components/wrappers'; import ProgressivePreloader from '../../components/preloader'; -import { openBtnMenu, renderImageFromUrl, formatPhoneNumber } from '../../components/misc'; +import { openBtnMenu, formatPhoneNumber } from '../../components/misc'; import { ChatInput } from '../../components/chatInput'; import Scrollable from '../../components/scrollable'; import BubbleGroups from '../../components/bubbleGroups'; import LazyLoadQueue from '../../components/lazyLoadQueue'; import appDocsManager from './appDocsManager'; -import { Layouter, RectPart } from '../../components/groupedLayout'; console.log('appImManager included!'); @@ -192,7 +191,27 @@ export class AppImManager { $rootScope.$on('message_sent', (e: CustomEvent) => { let {tempID, mid} = e.detail; - ////this.log('message_sent', e.detail); + this.log('message_sent', e.detail); + + // set cached url to media + let message = appMessagesManager.getMessage(mid); + if(message.media) { + if(message.media.photo) { + let photo = appPhotosManager.getPhoto(tempID); + if(photo) { + let newPhoto = message.media.photo; + newPhoto.downloaded = photo.downloaded; + newPhoto.url = photo.url; + } + } else if(message.media.document) { + let doc = appDocsManager.getDoc(tempID); + if(doc && doc.type && doc.type != 'sticker') { + let newDoc = message.media.document; + newDoc.downloaded = doc.downloaded; + newDoc.url = doc.url; + } + } + } let bubble = this.bubbles[tempID]; if(bubble) { @@ -200,26 +219,15 @@ export class AppImManager { /////this.log('message_sent', bubble); - // set cached url to media - let message = appMessagesManager.getMessage(mid); - if(message.media) { - if(message.media.photo) { - let photo = appPhotosManager.getPhoto(tempID); - if(photo) { - let newPhoto = message.media.photo; - newPhoto.downloaded = photo.downloaded; - newPhoto.url = photo.url; - } - } else if(message.media.document) { - let doc = appDocsManager.getDoc(tempID); - if(doc && doc.type && doc.type != 'sticker') { - let newDoc = message.media.document; - newDoc.downloaded = doc.downloaded; - newDoc.url = doc.url; - } - } + // set new mids to album items for mediaViewer + if(message.grouped_id) { + let items = bubble.querySelectorAll('.album-item'); + let groupIDs = Object.keys(appMessagesManager.groupedMessagesStorage[message.grouped_id]).map(i => +i).sort((a, b) => a - b); + (Array.from(items) as HTMLElement[]).forEach((item, idx) => { + item.dataset.mid = '' + groupIDs[idx]; + }); } - + bubble.classList.remove('is-sending'); bubble.classList.add('is-sent'); bubble.dataset.mid = mid; @@ -243,11 +251,16 @@ export class AppImManager { let {peerID, mid, id, justMedia} = e.detail; if(peerID != this.peerID) return; + let message = appMessagesManager.getMessage(mid); let bubble = this.bubbles[mid]; + if(!bubble && message.grouped_id) { + let a = this.getAlbumBubble(message.grouped_id); + bubble = a.bubble; + message = a.message; + } if(!bubble) return; - let message = appMessagesManager.getMessage(mid); this.renderMessage(message, true, false, bubble, false); }); @@ -319,8 +332,51 @@ export class AppImManager { } catch(err) {} if(!bubble) return; + + if((target.tagName == 'IMG' && !target.classList.contains('emoji') && !target.parentElement.classList.contains('user-avatar')) + || target.tagName == 'image' + || target.classList.contains('album-item') + || (target.tagName == 'VIDEO' && !bubble.classList.contains('round'))) { + let messageID = +findUpClassName(target, 'album-item')?.dataset.mid || +bubble.dataset.mid; + let message = appMessagesManager.getMessage(messageID); + if(!message) { + this.log.warn('no message by messageID:', messageID); + return; + } + + let targets: {element: HTMLElement, mid: number}[] = []; + let ids = Object.keys(this.bubbles).map(k => +k).filter(id => { + //if(!this.scrollable.visibleElements.find(e => e.element == this.bubbles[id])) return false; + + let message = appMessagesManager.getMessage(id); + + return message.media && (message.media.photo || (message.media.document && (message.media.document.type == 'video' || message.media.document.type == 'gif')) || (message.media.webpage && (message.media.webpage.document || message.media.webpage.photo))); + }).sort((a, b) => a - b); + + ids.forEach(id => { + let bubble = this.bubbles[id]; + + let elements = this.bubbles[id].querySelectorAll('.attachment img, .preview img, video, .bubble__media-container') as NodeListOf; + Array.from(elements).forEach((element: HTMLElement) => { + let albumItem = findUpClassName(element, 'album-item'); + targets.push({ + element, + mid: +albumItem?.dataset.mid || id + }); + }); + }); + + let idx = targets.findIndex(t => t.mid == messageID); + + this.log('open mediaViewer single with ids:', ids, idx, targets); + + appMediaViewer.openMedia(message, targets[idx].element, true, + this.scroll.parentElement, targets.slice(0, idx), targets.slice(idx + 1)/* , !message.grouped_id */); + + //appMediaViewer.openMedia(message, target as HTMLImageElement); + } - if(['IMG', 'VIDEO', 'SVG', 'DIV', 'image'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); + if(['IMG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); if(target.tagName == 'DIV') { if(target.classList.contains('forward')) { @@ -351,49 +407,12 @@ export class AppImManager { let originalMessageID = +bubble.getAttribute('data-original-mid'); this.setPeer(this.peerID, originalMessageID); } - } else if(bubble.classList.contains('round')) { - } else if(target.tagName == 'IMG' && target.parentElement.classList.contains('user-avatar')) { let peerID = +target.parentElement.dataset.peerID; if(!isNaN(peerID)) { this.setPeer(peerID); } - } else if((target.tagName == 'IMG' && !target.classList.contains('emoji')) || target.tagName == 'image' || target.tagName == 'VIDEO') { - let messageID = 0; - for(let mid in this.bubbles) { - if(this.bubbles[mid] == bubble) { - messageID = +mid; - break; - } - } - let message = appMessagesManager.getMessage(messageID); - if(!message) { - this.log.warn('no message by messageID:', messageID); - return; - } - - let ids = Object.keys(this.bubbles).map(k => +k).filter(id => { - //if(!this.scrollable.visibleElements.find(e => e.element == this.bubbles[id])) return false; - - let message = appMessagesManager.getMessage(id); - - return message.media && (message.media.photo || (message.media.document && (message.media.document.type == 'video' || message.media.document.type == 'gif')) || (message.media.webpage && (message.media.webpage.document || message.media.webpage.photo))); - }).sort((a, b) => a - b); - let idx = ids.findIndex(i => i == messageID); - - let targets = ids.map(id => ({ - //element: (this.bubbles[id].querySelector('img, video') || this.bubbles[id].querySelector('image')) as HTMLElement, - element: this.bubbles[id].querySelector('.attachment img, .preview img, video, .bubble__media-container, .album-item') as HTMLElement, - mid: id - })); - - this.log('open mediaViewer with ids:', ids, idx, targets); - - appMediaViewer.openMedia(message, targets[idx].element, true, - this.scroll.parentElement, targets.slice(0, idx), targets.slice(idx + 1)); - - //appMediaViewer.openMedia(message, target as HTMLImageElement); } //console.log('chatInner click', e); @@ -656,6 +675,19 @@ export class AppImManager { return apiManager.invokeApi('account.updateStatus', {offline: this.offline}); } + public getAlbumBubble(groupID: string) { + let group = appMessagesManager.groupedMessagesStorage[groupID]; + for(let i in group) { + let mid = +i; + if(this.bubbles[mid]) return { + bubble: this.bubbles[mid], + message: appMessagesManager.getMessage(mid) + }; + } + + return null; + } + public loadMoreHistory(top: boolean) { this.log('loadMoreHistory', top); if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return; @@ -1294,14 +1326,38 @@ export class AppImManager { let timeInner = document.createElement('div'); timeInner.classList.add('inner', 'tgico'); timeInner.innerHTML = time; + + let messageMessage: string, totalEntities: any[]; + if(message.grouped_id) { + let group = appMessagesManager.groupedMessagesStorage[message.grouped_id]; + let foundMessages = 0; + for(let i in group) { + let m = group[i]; + if(m.message) { + if(++foundMessages > 1) break; + messageMessage = m.message; + totalEntities = m.totalEntities; + } + } + + if(foundMessages > 1) { + messageMessage = undefined; + totalEntities = undefined; + } + } + + if(!messageMessage && !totalEntities) { + messageMessage = message.message; + totalEntities = message.totalEntities; + } - let richText = RichTextProcessor.wrapRichText(message.message, { - entities: message.totalEntities + let richText = RichTextProcessor.wrapRichText(messageMessage, { + entities: totalEntities }); - if(message.totalEntities) { - let emojiEntities = message.totalEntities.filter((e: any) => e._ == 'messageEntityEmoji'); - let strLength = message.message.length; + if(totalEntities) { + let emojiEntities = totalEntities.filter((e: any) => e._ == 'messageEntityEmoji'); + let strLength = messageMessage.length; let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0); if(emojiStrLength == strLength && emojiEntities.length <= 3) { @@ -1348,23 +1404,37 @@ export class AppImManager { let attachmentDiv = document.createElement('div'); attachmentDiv.classList.add('attachment'); - if(!message.message) { + if(!messageMessage) { bubble.classList.add('is-message-empty'); } let processingWebPage = false; + switch(message.media._) { case 'messageMediaPending': { let pending = message.media; let preloader = pending.preloader as ProgressivePreloader; switch(pending.type) { + case 'album': { + this.log('will wrap pending album'); + + bubble.classList.add('hide-name', 'photo', 'is-album'); + wrapAlbum({ + groupID: '' + message.id, + attachmentDiv, + uploading: true, + isOut: true + }); + + break; + } + case 'photo': { //if(pending.size < 5e6) { this.log('will wrap pending photo:', pending, message, appPhotosManager.getPhoto(message.id)); wrapPhoto(message.id, message, attachmentDiv, undefined, undefined, true, true, this.lazyLoadQueue, null); - preloader.attach(attachmentDiv, false); bubble.classList.add('hide-name', 'photo'); //} @@ -1401,6 +1471,7 @@ export class AppImManager { preloader.attach(icoDiv, false); bubble.classList.remove('is-message-empty'); + messageDiv.classList.add((pending.type || 'document') + '-message'); messageDiv.append(docDiv); processingWebPage = true; break; @@ -1416,105 +1487,18 @@ export class AppImManager { ////////this.log('messageMediaPhoto', photo); bubble.classList.add('hide-name', 'photo'); - if(message.grouped_id) { bubble.classList.add('is-album'); - let items: {size: MTPhotoSize, media: any}[] = []; - - // higher msgID will be the last in album - let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id]; - for(let mid in storage) { - let m = appMessagesManager.getMessage(+mid); - let media = m.media.photo || m.media.document; - - let size = appPhotosManager.choosePhotoSize(media, 380, 380); - items.push({size, media}); - } - - let spacing = 2; - let layouter = new Layouter(items.map(i => ({w: i.size.w, h: i.size.h})), 451, 100, spacing); - let layout = layouter.layout(); - this.log('layout:', layout); - - /* let borderRadius = window.getComputedStyle(realParent).getPropertyValue('border-radius'); - let brSplitted = fillPropertyValue(borderRadius); */ - - for(let {geometry, sides} of layout) { - let {size, media} = items.shift(); - let div = document.createElement('div'); - div.classList.add('album-item'); - - div.style.width = geometry.width + 'px'; - div.style.height = geometry.height + 'px'; - div.style.top = geometry.y + 'px'; - div.style.left = geometry.x + 'px'; - - if(sides & RectPart.Right) { - attachmentDiv.style.width = geometry.width + geometry.x + 'px'; - } - - if(sides & RectPart.Bottom) { - attachmentDiv.style.height = geometry.height + geometry.y + 'px'; - } - - if(sides & RectPart.Left && sides & RectPart.Top) { - div.style.borderTopLeftRadius = 'inherit'; - } - - if(sides & RectPart.Left && sides & RectPart.Bottom) { - div.style.borderBottomLeftRadius = 'inherit'; - } - - if(sides & RectPart.Right && sides & RectPart.Top) { - div.style.borderTopRightRadius = 'inherit'; - } - - if(sides & RectPart.Right && sides & RectPart.Bottom) { - div.style.borderBottomRightRadius = 'inherit'; - } - - /* if(geometry.y != 0) { - div.style.marginTop = spacing + 'px'; - } - - if(geometry.x != 0) { - div.style.marginLeft = spacing + 'px'; - } */ - - let preloader = new ProgressivePreloader(div); - - let load = () => appPhotosManager.preloadPhoto(media._ == 'photo' ? media.id : media, size) - .then((blob) => { - if(this.peerID != peerID) { - this.log.warn('peer changed'); - return; - } - - preloader.detach(); - if(media && media.url) { - renderImageFromUrl(div, media.url); - }/* else { - let url = URL.createObjectURL(blob); - this.urlsToRevoke.push(url); - - let img = new Image(); - img.src = url; - img.onload = () => { - div.style.backgroundImage = 'url(' + url + ')'; - }; - } */ - - //div.style.backgroundImage = 'url(' + url + ')'; - }); - - load(); - - // @ts-ignore - //div.style.backgroundColor = '#' + Math.floor(Math.random() * (2 ** 24 - 1)).toString(16).padStart(6, '0'); - - attachmentDiv.append(div); - } + wrapAlbum({ + groupID: message.grouped_id, + attachmentDiv, + middleware: () => { + return this.peerID == peerID; + }, + isOut: our, + lazyLoadQueue: this.lazyLoadQueue + }); } else { wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, our, this.lazyLoadQueue, () => { return this.peerID == peerID; @@ -1650,19 +1634,33 @@ export class AppImManager { } bubble.classList.add('hide-name', 'video'); - wrapVideo({ - doc, - container: attachmentDiv, - message, - boxWidth: 380, - boxHeight: 380, - withTail: doc.type != 'round', - isOut: our, - lazyLoadQueue: this.lazyLoadQueue, - middleware: () => { - return this.peerID == peerID; - } - }); + if(message.grouped_id) { + bubble.classList.add('is-album'); + + wrapAlbum({ + groupID: message.grouped_id, + attachmentDiv, + middleware: () => { + return this.peerID == peerID; + }, + isOut: our, + lazyLoadQueue: this.lazyLoadQueue + }); + } else { + wrapVideo({ + doc, + container: attachmentDiv, + message, + boxWidth: 380, + boxHeight: 380, + withTail: doc.type != 'round', + isOut: our, + lazyLoadQueue: this.lazyLoadQueue, + middleware: () => { + return this.peerID == peerID; + } + }); + } break; } else if(doc.mime_type == 'audio/ogg') { diff --git a/src/lib/appManagers/appMediaViewer.ts b/src/lib/appManagers/appMediaViewer.ts index 2aa8fdb9..4de47e47 100644 --- a/src/lib/appManagers/appMediaViewer.ts +++ b/src/lib/appManagers/appMediaViewer.ts @@ -56,6 +56,7 @@ export class AppMediaViewer { private loadedAllMediaDown = false; private reverse = false; // reverse means next = higher msgid + private needLoadMore = true; constructor() { this.log = logger('AMV'); @@ -517,7 +518,7 @@ export class AppMediaViewer { } public openMedia(message: any, target?: HTMLElement, reverse = false, targetContainer?: HTMLElement, - prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], loadMore: () => void = null) { + prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) { ////////this.log('openMedia doc:', message, prevTarget, nextTarget); let media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo; @@ -530,6 +531,7 @@ export class AppMediaViewer { this.prevTargets = prevTargets; this.nextTargets = nextTargets; this.reverse = reverse; + this.needLoadMore = needLoadMore; //this.loadMore = loadMore; } @@ -557,12 +559,14 @@ export class AppMediaViewer { this.currentMessageID = message.mid; this.lastTarget = target; - if(this.nextTargets.length < 20) { - this.loadMoreMedia(!this.reverse); - } - - if(this.prevTargets.length < 20) { - this.loadMoreMedia(this.reverse); + if(this.needLoadMore) { + if(this.nextTargets.length < 20) { + this.loadMoreMedia(!this.reverse); + } + + if(this.prevTargets.length < 20) { + this.loadMoreMedia(this.reverse); + } } if(container.firstElementChild) { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 08a8abf7..884ee8b3 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -47,7 +47,7 @@ export class AppMessagesManager { count: any, dialogs: any[] } = {count: null, dialogs: []}; - public pendingByRandomID: any = {}; + public pendingByRandomID: {[randomID: string]: [number, number]} = {}; public pendingByMessageID: any = {}; public pendingAfterMsgs: any = {}; public pendingTopMsgs: any = {}; @@ -449,17 +449,18 @@ export class AppMessagesManager { this.pendingByRandomID[randomIDS] = [peerID, messageID]; } - public sendFile(peerID: number, file: File | Blob | MTDocument, options: { - isMedia?: boolean, - replyToMsgID?: number, - caption?: string, - entities?: any[], - width?: number, - height?: number, - objectURL?: string, - isRoundMessage?: boolean, - duration?: number - } = {}) { + public sendFile(peerID: number, file: File | Blob | MTDocument, options: Partial<{ + isMedia: boolean, + replyToMsgID: number, + caption: string, + entities: any[], + width: number, + height: number, + objectURL: string, + isRoundMessage: boolean, + duration: number, + background: boolean + }> = {}) { peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; var messageID = this.tempID--; var randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]; @@ -656,6 +657,8 @@ export class AppMessagesManager { return apiManager.invokeApi('messages.sendMedia', { flags: flags, + background: options.background, + clear_draft: true, peer: AppPeersManager.getInputPeerByID(peerID), media: inputMedia, message: caption, @@ -683,9 +686,10 @@ export class AppMessagesManager { if(replyToMsgID) { flags |= 1; } - if(asChannel) { - flags |= 16; + if(options.background) { + flags |= 64; } + flags |= 128; // clear_draft if(isDocument) { let {id, access_hash, file_reference} = file as MTDocument; @@ -777,6 +781,293 @@ export class AppMessagesManager { this.pendingByRandomID[randomIDS] = [peerID, messageID]; } + public async sendAlbum(peerID: number, files: File[], options: Partial<{ + entities: any[], + replyToMsgID: number, + caption: string, + sendFileDetails: Partial<{ + duration: number, + width: number, + height: number, + objectURL: string, + }>[] + }> = {}) { + peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; + let groupID: number; + let historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {count: null, history: [], pending: []}); + let flags = 0; + let pFlags: any = {}; + let replyToMsgID = options.replyToMsgID; + let isChannel = AppPeersManager.isChannel(peerID); + let isMegagroup = isChannel && AppPeersManager.isMegagroup(peerID); + let asChannel = isChannel && !isMegagroup ? true : false; + + let caption = options.caption || ''; + + let date = tsNow(true) + ServerTimeManager.serverTimeOffset; + + if(caption) { + let entities = options.entities || []; + caption = RichTextProcessor.parseMarkdown(caption, entities); + } + + console.log('AMM: sendAlbum', files, options); + + let fromID = appUsersManager.getSelf().id; + if(peerID != fromID) { + pFlags.out = true; + + if(!isChannel && !appUsersManager.isBot(peerID)) { + pFlags.unread = true; + } + } + + if(replyToMsgID) { + flags |= 1; + } + + if(asChannel) { + fromID = 0; + pFlags.post = true; + } else { + flags |= 128; // clear_draft + } + + let ids = files.map(() => this.tempID--).reverse(); + groupID = ids[ids.length - 1]; + let messages = files.map((file, idx) => { + //let messageID = this.tempID--; + //if(!groupID) groupID = messageID; + let messageID = ids[idx]; + let randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]; + let randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(); + let preloader = new ProgressivePreloader(null, true); + + let details = options.sendFileDetails[idx]; + + let media = { + _: 'messageMediaPending', + type: 'album', + preloader: preloader, + progress: { + percent: 1, + total: file.size, + done: 0, + cancel: () => {} + }, + document: undefined as any, + photo: undefined as any + }; + + if(file.type.indexOf('video/') === 0) { + let flags = 1; + let videoAttribute = { + _: 'documentAttributeVideo', + flags: flags, + pFlags: { // that's only for client, not going to telegram + supports_streaming: true, + round_message: false + }, + round_message: false, + supports_streaming: true, + duration: details.duration, + w: details.width, + h: details.height + }; + + let doc: any = { + _: 'document', + id: '' + messageID, + attributes: [videoAttribute], + downloaded: file.size, + thumbs: [], + mime_type: file.type, + url: details.objectURL || '', + size: file.size + }; + + appDocsManager.saveDoc(doc); + media.document = doc; + } else { + let photo: any = { + _: 'photo', + id: '' + messageID, + sizes: [{ + _: 'photoSize', + w: details.width, + h: details.height, + type: 'm', + size: file.size + } as MTPhotoSize], + w: details.width, + h: details.height, + downloaded: file.size, + url: details.objectURL || '' + }; + + appPhotosManager.savePhoto(photo); + media.photo = photo; + } + + preloader.preloader.onclick = () => { + console.log('cancelling upload', media); + appImManager.setTyping('sendMessageCancelAction'); + media.progress.cancel(); + }; + + let message = { + _: 'message', + id: messageID, + from_id: fromID, + grouped_id: groupID, + to_id: AppPeersManager.getOutputPeer(peerID), + flags: flags, + pFlags: pFlags, + date: date, + message: caption, + media: media, + random_id: randomIDS, + randomID: randomID, + reply_to_msg_id: replyToMsgID, + views: asChannel && 1, + pending: true, + error: false + }; + + this.saveMessages([message]); + historyStorage.pending.unshift(messageID); + //$rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true}); + + this.pendingByRandomID[randomIDS] = [peerID, messageID]; + + return message; + }); + + $rootScope.$broadcast('history_append', {peerID: peerID, messageID: messages[messages.length - 1].id, my: true}); + + let toggleError = (message: any, on: boolean) => { + if(on) { + message.error = true; + } else { + delete message.error; + } + + $rootScope.$broadcast('messages_pending'); + }; + + let uploaded = false, + uploadPromise: ReturnType = null; + + let inputPeer = AppPeersManager.getInputPeerByID(peerID); + let invoke = (multiMedia: any[]) => { + appImManager.setTyping('sendMessageCancelAction'); + + return apiManager.invokeApi('messages.sendMultiMedia', { + flags: flags, + peer: inputPeer, + multi_media: multiMedia, + reply_to_msg_id: appMessagesIDsManager.getMessageLocalID(replyToMsgID) + }).then((updates) => { + apiUpdatesManager.processUpdateMessage(updates); + }, (error) => { + messages.forEach(message => toggleError(message, true)); + }); + }; + + let inputs: any[] = []; + for(let i = 0, length = files.length; i < length; ++i) { + let file = files[i]; + let message = messages[i]; + let media = message.media; + let preloader = media.preloader; + let actionName = file.type.indexOf('video/') === 0 ? 'sendMessageUploadVideoAction' : 'sendMessageUploadPhotoAction'; + let deferred = deferredPromise(); + + await this.sendFilePromise; + this.sendFilePromise = deferred; + + if(!uploaded || message.error) { + uploaded = false; + uploadPromise = apiFileManager.uploadFile(file); + } + + uploadPromise.notify = (progress: {done: number, total: number}) => { + console.log('upload progress', progress); + media.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); + appImManager.setTyping({_: actionName, progress: media.progress.percent | 0}); + preloader.setProgress(media.progress.percent); // lol, nice + $rootScope.$broadcast('history_update', {peerID: peerID}); + }; + + await uploadPromise.then((inputFile) => { + console.log('appMessagesManager: sendAlbum file uploaded:', inputFile); + + let inputMedia: any; + let details = options.sendFileDetails[i]; + if(details.duration) { + inputMedia = { + _: 'inputMediaUploadedDocument', + flags: 0, + file: inputFile, + mime_type: file.type, + attributes: [{ + _: 'documentAttributeVideo', + flags: 2, + supports_streaming: true, + duration: details.duration, + w: details.width, + h: details.height + }] + }; + } else { + inputMedia = { + _: 'inputMediaUploadedPhoto', + flags: 0, + file: inputFile + }; + } + + return apiManager.invokeApi('messages.uploadMedia', { + peer: inputPeer, + media: inputMedia + }).then(messageMedia => { + let inputMedia: any; + if(messageMedia.photo) { + let photo = messageMedia.photo; + appPhotosManager.savePhoto(photo); + inputMedia = appPhotosManager.getInputByID(photo.id); + } else { + let doc = messageMedia.document; + appDocsManager.saveDoc(doc); + inputMedia = appDocsManager.getInputByID(doc.id); + } + + inputs.push({ + _: 'inputSingleMedia', + flags: 0, + media: inputMedia, + random_id: message.randomID, + message: caption, + entities: [] + }); + + caption = ''; // only 1 caption for all inputs + }, () => { + toggleError(message, true); + }); + }, () => { + toggleError(message, true); + }); + + console.log('appMessagesManager: sendAlbum uploadPromise.finally!'); + deferred.resolve(); + preloader.detach(); + } + + uploaded = true; + invoke(inputs); + } + public cancelPendingMessage(randomID: string) { var pendingData = this.pendingByRandomID[randomID]; @@ -2273,6 +2564,7 @@ export class AppMessagesManager { case 'updateMessageID': { var randomID = update.random_id; var pendingData = this.pendingByRandomID[randomID]; + console.log('AMM updateMessageID:', update, pendingData); if(pendingData) { var peerID: number = pendingData[0]; var tempID = pendingData[1]; diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 145e39da..135f1c46 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -315,6 +315,21 @@ export class AppPhotosManager { return isObject(photoID) ? photoID : this.photos[photoID]; } + public getInputByID(photoID: any) { + let photo = this.getPhoto(photoID); + return { + _: 'inputMediaPhoto', + flags: 0, + id: { + _: 'inputPhoto', + id: photo.id, + access_hash: photo.access_hash, + file_reference: photo.file_reference + }, + ttl_seconds: 0 + }; + } + public downloadPhoto(photoID: string) { var photo = this.photos[photoID]; var ext = 'jpg'; diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index e143b966..1503e0de 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -171,7 +171,7 @@ class AppSidebarRight { let targets = ids.map(id => ({element: this.mediaDivsByIDs[id], mid: id})); - appMediaViewer.openMedia(message, target, false, this.sidebarEl, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), () => this.loadSidebarMedia(true)); + appMediaViewer.openMedia(message, target, false, this.sidebarEl, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), true); }); this.profileElements.notificationsCheckbox.addEventListener('change', () => { diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 0b24e0c2..07e09ebc 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -121,6 +121,7 @@ class ApiManagerProxy extends CryptoWorkerMethods { stopTime?: number, rawError?: any } = {}): Promise { + console.log('will invokeApi:', method, params, options); return this.performTaskWorker('invokeApi', method, params, options); } diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 3ec9699f..4a4e3227 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -589,6 +589,10 @@ $chat-max-width: 696px; max-width: 100%; cursor: pointer; position: absolute; + + img, video { + border-radius: inherit; + } } } } @@ -1132,8 +1136,8 @@ $chat-max-width: 696px; } } - .audio-subtitle, .contact-number { - color: #707579; + .audio-subtitle, .contact-number, .audio-time { + color: #707579 !important; } } @@ -1376,6 +1380,16 @@ $chat-max-width: 696px; &.menu-open { color: $blue; } + + .btn-menu { + padding: 8px 0; + right: -8px; + bottom: calc(100% + 16px); + + > div { + padding: 0 38px 0 16px; + } + } } > div { diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss index b37095f2..25a57fe1 100644 --- a/src/scss/partials/_emojiDropdown.scss +++ b/src/scss/partials/_emojiDropdown.scss @@ -1,7 +1,7 @@ .emoji-dropdown { position: absolute; left: 0; - top: calc(-420px + -0.75rem); + top: calc(-420px + -4px); display: flex; flex-direction: column; width: 420px; diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss index cf3630b4..26b9da80 100644 --- a/src/scss/partials/_mediaViewer.scss +++ b/src/scss/partials/_mediaViewer.scss @@ -4,7 +4,7 @@ left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, .9); + background: rgba(0, 0, 0, .88); /* color: $darkgrey; */ display: flex; align-items: center; diff --git a/src/scss/partials/popups/_editAvatar.scss b/src/scss/partials/popups/_editAvatar.scss new file mode 100644 index 00000000..9804b572 --- /dev/null +++ b/src/scss/partials/popups/_editAvatar.scss @@ -0,0 +1,52 @@ +.popup-avatar { + $parent: ".popup"; + + #{$parent} { + &-container { + max-width: 600px; + //max-height: 600px; + padding: 15px 16px 16px 24px; + overflow: hidden; + display: flex; + flex-direction: column; + + > button { + position: absolute; + bottom: 20px; + right: 20px; + } + } + + &-close { + font-size: 1.5rem; + margin-top: 4px; + } + + &-header { + margin-bottom: 1px; + } + } + + h6 { + font-size: 1.25rem; + text-align: left; + margin: 0; + margin-left: 2rem; + } + + .crop { + max-width: 100%; + max-height: 100%; + padding: 24px 54px 46px 46px; + border-radius: $border-radius; + + > img { + display: none; + } + + img { + //height: 100%; + border-radius: $border-radius; + } + } +} \ No newline at end of file diff --git a/src/scss/partials/popups/_mediaAttacher.scss b/src/scss/partials/popups/_mediaAttacher.scss new file mode 100644 index 00000000..23e67610 --- /dev/null +++ b/src/scss/partials/popups/_mediaAttacher.scss @@ -0,0 +1,150 @@ +.popup-send-photo { + $parent: ".popup"; + + #{$parent} { + &-container { + width: 420px; + max-width: 420px; + overflow: hidden; + /* padding: 12px 20px 50px; */ + padding: 12px 20px 32.5px; + + &.is-media:not(.is-album) { + /* max-height: 425px; */ + + #{$parent}-photo { + max-height: 320px; + margin: 0 auto; + padding-bottom: 8px; + + img { + object-fit: contain; + } + + > div { + display: flex; + justify-content: center; + } + } + } + + &.is-album { + #{$parent}-photo { + margin: 0 auto; + position: relative; + + > div { + position: absolute; + } + } + } + + &.is-document, &.is-album { + #{$parent}-photo { + img, video { + object-fit: cover; + width: 100%; + height: 100%; + } + } + } + } + + &-header { + justify-content: space-between; + align-items: center; + margin-bottom: 9px; + + .btn-primary { + width: 79px; + height: 36px; + font-size: 14px; + font-weight: normal; + padding: 0; + padding-top: 2px; + margin-top: -3px; + border-radius: $border-radius-medium; + } + } + + &-close { + font-size: 1.5rem; + margin: -1px 0 0 -4px; + } + + &-title { + flex: 1; + padding: 0 2rem 0 1.5rem; + margin: 0; + margin-top: -3px; + font-size: 1.25rem; + font-weight: 500; + } + + &-photo { + max-width: 380px; + //display: flex; + overflow: hidden; + //justify-content: center; + width: fit-content; + border-radius: $border-radius-medium; + /* align-items: center; */ + + .document { + max-width: 100%; + overflow: hidden; + cursor: default; + padding-left: 3.75rem; + height: 4.5rem; + + &-name { + font-weight: normal; + width: 100%; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.5; + } + + &-ico { + height: 48px; + width: 48px; + font-size: 16px; + font-weight: normal; + line-height: 11px; + letter-spacing: 0; + } + + /* &.photo { + .document-ico { + border-radius: $border-radius; + } + } */ + } + } + } + + .input-field { + margin-top: 1rem; + + &::placeholder { + color: #a2acb4; + } + + input { + height: 54px; + font-size: 1rem; + padding: 0 15px; + border-radius: $border-radius-medium; + + &:focus { + padding: 0 14.5px; + } + } + + label { + font-size: inherit; + opacity: 0; + } + } +} \ No newline at end of file diff --git a/src/scss/partials/popups/_popup.scss b/src/scss/partials/popups/_popup.scss new file mode 100644 index 00000000..6ab84e3d --- /dev/null +++ b/src/scss/partials/popups/_popup.scss @@ -0,0 +1,80 @@ +.popup { + position: fixed!important; + left: 0; + top: 0; + height: 100%; + max-width: none; + width: 100%; + z-index: 3; + background-color: rgba(0, 0, 0, .3); + margin: 0; + padding: 0; + box-shadow: none; + opacity: 0; + visibility: hidden; + -webkit-transition: opacity 0.3s 0s, visibility 0s 0.3s; + -moz-transition: opacity 0.3s 0s, visibility 0s 0.3s; + transition: opacity 0.3s 0s, visibility 0s 0.3s; + overflow: auto; + /* text-align: center; */ + display: flex; + align-items: center; + justify-content: center; + + &.active { + opacity: 1; + visibility: visible; + -webkit-transition: opacity 0.3s 0s, visibility 0s 0s; + -moz-transition: opacity 0.3s 0s, visibility 0s 0s; + transition: opacity 0.3s 0s, visibility 0s 0s; + + .popup-container { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -ms-transform: translateY(0); + -o-transform: translateY(0); + transform: translateY(0); + } + } + + &-container { + position: relative; + /* max-width: 400px; */ + border-radius: $border-radius-medium; + background-color: #fff; + padding: 1rem; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -webkit-transition-property: -webkit-transform; + -moz-transition-property: -moz-transform; + transition-property: transform; + -webkit-transition-duration: 0.3s; + -moz-transition-duration: 0.3s; + transition-duration: 0.3s; + } + + &-close { + cursor: pointer; + color: $color-gray; + z-index: 3; + text-align: center; + justify-self: center; + line-height: 1; + transition: .2s; + + &:hover { + color: #000; + } + } + + &-header { + display: flex; + margin-bottom: 2rem; + align-items: center; + } +} diff --git a/src/scss/style.scss b/src/scss/style.scss index b8810142..f2869bd6 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -43,6 +43,10 @@ $large-screen: 1680px; @import "partials/emojiDropdown"; @import "partials/scrollable"; +@import "partials/popups/popup"; +@import "partials/popups/editAvatar"; +@import "partials/popups/mediaAttacher"; + html, body { height: 100%; width: 100%; @@ -1156,81 +1160,6 @@ img.emoji { height: 18px; } -.popup { - position: fixed!important; - left: 0; - top: 0; - height: 100%; - max-width: none; - width: 100%; - z-index: 3; - background-color: rgba(0, 0, 0, .35); - margin: 0; - padding: 0; - box-shadow: none; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 0.3s 0s, visibility 0s 0.3s; - -moz-transition: opacity 0.3s 0s, visibility 0s 0.3s; - transition: opacity 0.3s 0s, visibility 0s 0.3s; - overflow: auto; - /* text-align: center; */ - display: flex; - align-items: center; - justify-content: center; -} - -.popup.active { - opacity: 1; - visibility: visible; - -webkit-transition: opacity 0.3s 0s, visibility 0s 0s; - -moz-transition: opacity 0.3s 0s, visibility 0s 0s; - transition: opacity 0.3s 0s, visibility 0s 0s; -} - -.popup-container { - position: relative; - /* max-width: 400px; */ - border-radius: $border-radius; - background-color: #fff; - padding: 1rem; - -webkit-transform: translateY(-40px); - -moz-transform: translateY(-40px); - -ms-transform: translateY(-40px); - -o-transform: translateY(-40px); - transform: translateY(-40px); - backface-visibility: hidden; - -webkit-backface-visibility: hidden; - -webkit-transition-property: -webkit-transform; - -moz-transition-property: -moz-transform; - transition-property: transform; - -webkit-transition-duration: 0.3s; - -moz-transition-duration: 0.3s; - transition-duration: 0.3s; -} - -span.popup-close { - cursor: pointer; - color: $color-gray; - z-index: 3; - text-align: center; - justify-self: center; - line-height: 1; - transition: .2s; - - &:hover { - color: #000; - } -} - -.popup.active .popup-container { - -webkit-transform: translateY(0); - -moz-transform: translateY(0); - -ms-transform: translateY(0); - -o-transform: translateY(0); - transform: translateY(0); -} - .btn-circle { border-radius: 50%; height: 54px; @@ -1241,66 +1170,6 @@ span.popup-close { } } -.popup-header { - /* display: grid; - align-items: center; - grid-template-columns: 9.5% 86.5%; - justify-content: space-between; */ - display: flex; - margin-bottom: 2rem; - align-items: center; -} - -.popup-avatar { - .popup-container { - max-width: 600px; - //max-height: 600px; - border-radius: $border-radius-medium; - padding: 15px 16px 16px 24px; - overflow: hidden; - display: flex; - flex-direction: column; - - > button { - position: absolute; - bottom: 20px; - right: 20px; - } - } - - .popup-close { - font-size: 1.5rem; - margin-top: 4px; - } - - .popup-header { - margin-bottom: 1px; - } - - h6 { - font-size: 1.25rem; - text-align: left; - margin: 0; - margin-left: 2rem; - } - - .crop { - max-width: 100%; - max-height: 100%; - padding: 24px 54px 46px 46px; - border-radius: $border-radius; - - > img { - display: none; - } - - img { - //height: 100%; - border-radius: $border-radius; - } - } -} - .overlay::selection { background: transparent; } @@ -1487,105 +1356,6 @@ span.popup-close { justify-content: flex-start!important; } -.popup-send-photo { - .popup-container { - width: 420px; - max-width: 420px; - max-height: 425px; - overflow: hidden; - /* padding: 12px 20px 50px; */ - padding: 12px 20px 32.5px; - border-radius: $border-radius-medium; - } - - .popup-header { - justify-content: space-between; - align-items: center; - margin-bottom: 12.5px; - - .popup-close { - font-size: 1.5rem; - margin-left: .5rem; - } - - .popup-title { - flex: 1; - padding: 0 2rem; - margin: 0; - font-size: 1.35rem; - font-weight: 500; - } - - .btn-primary { - width: 80px; - height: 35px; - font-size: 1rem; - padding: 0; - border-radius: $border-radius-medium; - } - } - - .popup-photo { - max-width: 378px; - max-height: 256px; - display: flex; - overflow: hidden; - justify-content: center; - width: fit-content; - border-radius: $border-radius-medium; - margin: 0 auto; - /* align-items: center; */ - - &.is-document { - margin-left: 0; - } - - .document { - max-width: 100%; - overflow: hidden; - cursor: default; - - .document-name { - font-weight: normal; - width: 100%; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - } - - /* &.photo { - .document-ico { - border-radius: $border-radius; - } - } */ - } - - img { - object-fit: contain; - } - } - - .input-field { - margin-top: 25px; - - input { - height: 55px; - font-size: 1.15rem; - padding: 0 15px; - border-radius: $border-radius-medium; - - &:focus { - padding: 0 14.5px; - } - } - - label { - font-size: inherit; - opacity: 0; - } - } -} - .page-chats { /* display: grid; */ /* grid-template-columns: 25% 50%; */