Browse Source

Albums send & render after send

master
morethanwords 5 years ago
parent
commit
d15abda503
  1. 218
      src/components/chatInput.ts
  2. 3
      src/components/preloader.ts
  3. 169
      src/components/wrappers.ts
  4. 15
      src/lib/appManagers/appDocsManager.ts
  5. 300
      src/lib/appManagers/appImManager.ts
  6. 6
      src/lib/appManagers/appMediaViewer.ts
  7. 320
      src/lib/appManagers/appMessagesManager.ts
  8. 15
      src/lib/appManagers/appPhotosManager.ts
  9. 2
      src/lib/appManagers/appSidebarRight.ts
  10. 1
      src/lib/mtproto/mtprotoworker.ts
  11. 18
      src/scss/partials/_chat.scss
  12. 2
      src/scss/partials/_emojiDropdown.scss
  13. 2
      src/scss/partials/_mediaViewer.scss
  14. 52
      src/scss/partials/popups/_editAvatar.scss
  15. 150
      src/scss/partials/popups/_mediaAttacher.scss
  16. 80
      src/scss/partials/popups/_popup.scss
  17. 238
      src/scss/style.scss

218
src/components/chatInput.ts

@ -5,11 +5,12 @@ import { RichTextProcessor } from "../lib/richtextprocessor";
import apiManager from "../lib/mtproto/mtprotoworker"; import apiManager from "../lib/mtproto/mtprotoworker";
import appWebPagesManager from "../lib/appManagers/appWebPagesManager"; import appWebPagesManager from "../lib/appManagers/appWebPagesManager";
import appImManager from "../lib/appManagers/appImManager"; import appImManager from "../lib/appManagers/appImManager";
import { calcImageInBox, getRichValue } from "../lib/utils"; import { getRichValue, calcImageInBox } from "../lib/utils";
import { wrapDocument, wrapReply } from "./wrappers"; import { wrapDocument, wrapReply } from "./wrappers";
import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appMessagesManager from "../lib/appManagers/appMessagesManager";
import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from "./emoticonsDropdown"; import initEmoticonsDropdown, { EMOTICONSSTICKERGROUP } from "./emoticonsDropdown";
import lottieLoader from "../lib/lottieLoader"; import lottieLoader from "../lib/lottieLoader";
import { Layouter, RectPart } from "./groupedLayout";
export class ChatInput { export class ChatInput {
public pageEl = document.querySelector('.page-chats') as HTMLDivElement; public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
@ -187,67 +188,44 @@ export class ChatInput {
}); });
let attachFile = (file: File) => { let attachFile = (file: File) => {
willAttach.file = file; return new Promise<HTMLDivElement>((resolve, reject) => {
delete willAttach.objectURL; let params: SendFileParams = {};
delete willAttach.duration; params.file = file;
delete willAttach.width;
delete willAttach.height;
this.fileInput.value = '';
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');
if(willAttach.type == 'media' && !['image/', 'video/'].find(s => file.type.indexOf(s) === 0)) {
willAttach.type = 'document';
}
console.log('selected file:', file, typeof(file), willAttach); console.log('selected file:', file, typeof(file), willAttach);
let itemDiv = document.createElement('div');
switch(willAttach.type) { switch(willAttach.type) {
case 'media': { case 'media': {
let isVideo = file.type.indexOf('video/') === 0; let isVideo = file.type.indexOf('video/') === 0;
itemDiv.classList.add('popup-item-media');
if(isVideo) { if(isVideo) {
let video = document.createElement('video'); let video = document.createElement('video');
let source = document.createElement('source'); let source = document.createElement('source');
source.src = willAttach.objectURL = URL.createObjectURL(file); source.src = params.objectURL = URL.createObjectURL(file);
video.autoplay = false; video.autoplay = false;
video.controls = false; video.controls = false;
video.onloadeddata = () => { video.onloadeddata = () => {
willAttach.width = video.videoWidth; params.width = video.videoWidth;
willAttach.height = video.videoHeight; params.height = video.videoHeight;
willAttach.duration = Math.floor(video.duration); params.duration = Math.floor(video.duration);
let {w, h} = calcImageInBox(willAttach.width, willAttach.height, 378, 256); itemDiv.append(video);
this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; resolve(itemDiv);
this.attachMediaPopUp.mediaContainer.style.height = h + 'px';
this.attachMediaPopUp.mediaContainer.append(video);
this.attachMediaPopUp.container.classList.add('active');
}; };
video.append(source); video.append(source);
this.attachMediaPopUp.titleEl.innerText = 'Send Video';
} else { } else {
let img = new Image(); let img = new Image();
img.src = willAttach.objectURL = URL.createObjectURL(file); img.src = params.objectURL = URL.createObjectURL(file);
img.onload = () => { img.onload = () => {
willAttach.width = img.naturalWidth; params.width = img.naturalWidth;
willAttach.height = img.naturalHeight; params.height = img.naturalHeight;
let {w, h} = calcImageInBox(willAttach.width, willAttach.height, 378, 256); itemDiv.append(img);
this.attachMediaPopUp.mediaContainer.style.width = w + 'px'; resolve(itemDiv);
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; break;
@ -261,34 +239,127 @@ export class ChatInput {
type: file.type.indexOf('image/') !== -1 ? 'photo' : 'doc' type: file.type.indexOf('image/') !== -1 ? 'photo' : 'doc'
} as any, false, true); } as any, false, true);
this.attachMediaPopUp.titleEl.innerText = 'Send File'; itemDiv.append(docDiv);
resolve(itemDiv);
this.attachMediaPopUp.mediaContainer.append(docDiv);
this.attachMediaPopUp.mediaContainer.classList.add('is-document');
this.attachMediaPopUp.container.classList.add('active');
break; break;
} }
} }
willAttach.sendFileDetails.push(params);
});
}; };
let willAttach: Partial<{ let attachFiles = (files: File[]) => {
type: 'media' | 'document', this.fileInput.value = '';
isMedia: boolean,
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 = '';
//willAttach.sendFileDetails.length = 0;
willAttach.sendFileDetails = []; // need new array
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');
}
}
}
Promise.all(files.map(attachFile)).then(results => {
if(willAttach.type == 'media') {
if(willAttach.sendFileDetails.length > 1) {
container.classList.add('is-album');
let layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4);
let layout = layouter.layout();
for(let {geometry, sides} of layout) {
let div = results.shift();
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) {
this.attachMediaPopUp.mediaContainer.style.width = geometry.width + geometry.x + 'px';
}
if(sides & RectPart.Bottom) {
this.attachMediaPopUp.mediaContainer.style.height = geometry.height + geometry.y + 'px';
}
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');
});
};
type SendFileParams = Partial<{
file: File, file: File,
caption: string,
objectURL: string, objectURL: string,
width: number, width: number,
height: number, height: number,
duration: number duration: number
}> = {}; }>;
let willAttach: Partial<{
type: 'media' | 'document',
isMedia: boolean,
sendFileDetails: SendFileParams[]
}> = {
sendFileDetails: []
};
this.fileInput.addEventListener('change', (e) => { this.fileInput.addEventListener('change', (e) => {
var file = (e.target as HTMLInputElement & EventTarget).files[0]; let files = (e.target as HTMLInputElement & EventTarget).files;
if(!file) { if(!files.length) {
return; return;
} }
attachFile(file); attachFiles(Array.from(files));
}, false); }, false);
this.attachMenu.media.addEventListener('click', () => { this.attachMenu.media.addEventListener('click', () => {
@ -329,10 +400,43 @@ export class ChatInput {
this.attachMediaPopUp.sendBtn.addEventListener('click', () => { this.attachMediaPopUp.sendBtn.addEventListener('click', () => {
this.attachMediaPopUp.container.classList.remove('active'); this.attachMediaPopUp.container.classList.remove('active');
willAttach.caption = this.attachMediaPopUp.captionInput.value; let caption = this.attachMediaPopUp.captionInput.value;
willAttach.isMedia = willAttach.type == 'media'; 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(); this.onMessageSent();
}); });

3
src/components/preloader.ts

@ -4,7 +4,6 @@ import { CancellablePromise } from "../lib/polyfill";
export default class ProgressivePreloader { export default class ProgressivePreloader {
public preloader: HTMLDivElement = null; public preloader: HTMLDivElement = null;
private circle: SVGCircleElement = null; private circle: SVGCircleElement = null;
private progress = 0;
private promise: CancellablePromise<any> = null; private promise: CancellablePromise<any> = null;
private tempID = 0; private tempID = 0;
private detached = true; private detached = true;
@ -103,8 +102,6 @@ export default class ProgressivePreloader {
} }
public setProgress(percents: number) { public setProgress(percents: number) {
this.progress = percents;
if(!isInDOM(this.circle)) { if(!isInDOM(this.circle)) {
return; return;
} }

169
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 CryptoWorker from '../lib/crypto/cryptoworker';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import LottieLoader from '../lib/lottieLoader'; import LottieLoader from '../lib/lottieLoader';
@ -13,6 +13,8 @@ import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer';
import { RichTextProcessor } from '../lib/richtextprocessor'; import { RichTextProcessor } from '../lib/richtextprocessor';
import { CancellablePromise } from '../lib/polyfill'; import { CancellablePromise } from '../lib/polyfill';
import { renderImageFromUrl } from './misc'; import { renderImageFromUrl } from './misc';
import appMessagesManager from '../lib/appManagers/appMessagesManager';
import { Layouter, RectPart } from './groupedLayout';
export type MTDocument = { export type MTDocument = {
_: 'document' | 'documentEmpty', _: 'document' | 'documentEmpty',
@ -75,6 +77,17 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
if(withTail) { if(withTail) {
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut); img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut);
} else if(!boxWidth && !boxHeight) { // album
let sizes = doc.thumbs;
if(!doc.downloaded && sizes && sizes[0].bytes) {
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
}
img = container.firstElementChild as HTMLImageElement || new Image();
if(!container.contains(img)) {
container.append(img);
}
} else { } else {
if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) { if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) {
let size = appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight); let size = appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight);
@ -106,7 +119,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
let loadVideo = () => { let loadVideo = () => {
let promise = appDocsManager.downloadDoc(doc); 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); let preloader = new ProgressivePreloader(container, true);
preloader.attach(container, true, promise); preloader.attach(container, true, promise);
} }
@ -600,14 +615,24 @@ function wrapMediaWithTail(photo: any, message: {mid: number, message: string},
return image; 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 photo = appPhotosManager.getPhoto(photoID);
let size: MTPhotoSize;
let image: SVGImageElement | HTMLImageElement; let image: SVGImageElement | HTMLImageElement;
if(withTail) { if(withTail) {
image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut); 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); size = appPhotosManager.setAttachmentSize(photoID, container, boxWidth, boxHeight, false);
image = container.firstElementChild as HTMLImageElement || new Image(); image = container.firstElementChild as HTMLImageElement || new Image();
@ -626,7 +651,12 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme
} */ } */
let preloader: ProgressivePreloader; 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 load = () => {
let promise = appPhotosManager.preloadPhoto(photoID, size); let promise = appPhotosManager.preloadPhoto(photoID, size);
@ -637,7 +667,7 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme
return promise.then(() => { return promise.then(() => {
if(middleware && !middleware()) return; 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; 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);
}
}

15
src/lib/appManagers/appDocsManager.ts

@ -145,6 +145,21 @@ class AppDocsManager {
return isObject(docID) ? docID : this.docs[docID]; 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) { public getFileName(doc: MTDocument) {
if(doc.file_name) { if(doc.file_name) {
return doc.file_name; return doc.file_name;

300
src/lib/appManagers/appImManager.ts

@ -17,15 +17,14 @@ import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import appMessagesIDsManager from "./appMessagesIDsManager"; import appMessagesIDsManager from "./appMessagesIDsManager";
import apiUpdatesManager from './apiUpdatesManager'; 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 ProgressivePreloader from '../../components/preloader';
import { openBtnMenu, renderImageFromUrl, formatPhoneNumber } from '../../components/misc'; import { openBtnMenu, formatPhoneNumber } from '../../components/misc';
import { ChatInput } from '../../components/chatInput'; import { ChatInput } from '../../components/chatInput';
import Scrollable from '../../components/scrollable'; import Scrollable from '../../components/scrollable';
import BubbleGroups from '../../components/bubbleGroups'; import BubbleGroups from '../../components/bubbleGroups';
import LazyLoadQueue from '../../components/lazyLoadQueue'; import LazyLoadQueue from '../../components/lazyLoadQueue';
import appDocsManager from './appDocsManager'; import appDocsManager from './appDocsManager';
import { Layouter, RectPart } from '../../components/groupedLayout';
console.log('appImManager included!'); console.log('appImManager included!');
@ -192,13 +191,7 @@ export class AppImManager {
$rootScope.$on('message_sent', (e: CustomEvent) => { $rootScope.$on('message_sent', (e: CustomEvent) => {
let {tempID, mid} = e.detail; let {tempID, mid} = e.detail;
////this.log('message_sent', e.detail); this.log('message_sent', e.detail);
let bubble = this.bubbles[tempID];
if(bubble) {
this.bubbles[mid] = bubble;
/////this.log('message_sent', bubble);
// set cached url to media // set cached url to media
let message = appMessagesManager.getMessage(mid); let message = appMessagesManager.getMessage(mid);
@ -220,6 +213,21 @@ export class AppImManager {
} }
} }
let bubble = this.bubbles[tempID];
if(bubble) {
this.bubbles[mid] = bubble;
/////this.log('message_sent', bubble);
// 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.remove('is-sending');
bubble.classList.add('is-sent'); bubble.classList.add('is-sent');
bubble.dataset.mid = mid; bubble.dataset.mid = mid;
@ -243,11 +251,16 @@ export class AppImManager {
let {peerID, mid, id, justMedia} = e.detail; let {peerID, mid, id, justMedia} = e.detail;
if(peerID != this.peerID) return; if(peerID != this.peerID) return;
let message = appMessagesManager.getMessage(mid);
let bubble = this.bubbles[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; if(!bubble) return;
let message = appMessagesManager.getMessage(mid);
this.renderMessage(message, true, false, bubble, false); this.renderMessage(message, true, false, bubble, false);
}); });
@ -320,7 +333,50 @@ export class AppImManager {
if(!bubble) return; if(!bubble) return;
if(['IMG', 'VIDEO', 'SVG', 'DIV', 'image'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); 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<HTMLElement>;
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', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
if(target.tagName == 'DIV') { if(target.tagName == 'DIV') {
if(target.classList.contains('forward')) { if(target.classList.contains('forward')) {
@ -351,49 +407,12 @@ export class AppImManager {
let originalMessageID = +bubble.getAttribute('data-original-mid'); let originalMessageID = +bubble.getAttribute('data-original-mid');
this.setPeer(this.peerID, originalMessageID); this.setPeer(this.peerID, originalMessageID);
} }
} else if(bubble.classList.contains('round')) {
} else if(target.tagName == 'IMG' && target.parentElement.classList.contains('user-avatar')) { } else if(target.tagName == 'IMG' && target.parentElement.classList.contains('user-avatar')) {
let peerID = +target.parentElement.dataset.peerID; let peerID = +target.parentElement.dataset.peerID;
if(!isNaN(peerID)) { if(!isNaN(peerID)) {
this.setPeer(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); //console.log('chatInner click', e);
@ -656,6 +675,19 @@ export class AppImManager {
return apiManager.invokeApi('account.updateStatus', {offline: this.offline}); 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) { public loadMoreHistory(top: boolean) {
this.log('loadMoreHistory', top); this.log('loadMoreHistory', top);
if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return; if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return;
@ -1295,13 +1327,37 @@ export class AppImManager {
timeInner.classList.add('inner', 'tgico'); timeInner.classList.add('inner', 'tgico');
timeInner.innerHTML = time; timeInner.innerHTML = time;
let richText = RichTextProcessor.wrapRichText(message.message, { let messageMessage: string, totalEntities: any[];
entities: message.totalEntities 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(messageMessage, {
entities: totalEntities
}); });
if(message.totalEntities) { if(totalEntities) {
let emojiEntities = message.totalEntities.filter((e: any) => e._ == 'messageEntityEmoji'); let emojiEntities = totalEntities.filter((e: any) => e._ == 'messageEntityEmoji');
let strLength = message.message.length; let strLength = messageMessage.length;
let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0); let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0);
if(emojiStrLength == strLength && emojiEntities.length <= 3) { if(emojiStrLength == strLength && emojiEntities.length <= 3) {
@ -1348,23 +1404,37 @@ export class AppImManager {
let attachmentDiv = document.createElement('div'); let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment'); attachmentDiv.classList.add('attachment');
if(!message.message) { if(!messageMessage) {
bubble.classList.add('is-message-empty'); bubble.classList.add('is-message-empty');
} }
let processingWebPage = false; let processingWebPage = false;
switch(message.media._) { switch(message.media._) {
case 'messageMediaPending': { case 'messageMediaPending': {
let pending = message.media; let pending = message.media;
let preloader = pending.preloader as ProgressivePreloader; let preloader = pending.preloader as ProgressivePreloader;
switch(pending.type) { 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': { case 'photo': {
//if(pending.size < 5e6) { //if(pending.size < 5e6) {
this.log('will wrap pending photo:', pending, message, appPhotosManager.getPhoto(message.id)); this.log('will wrap pending photo:', pending, message, appPhotosManager.getPhoto(message.id));
wrapPhoto(message.id, message, attachmentDiv, undefined, undefined, true, true, this.lazyLoadQueue, null); wrapPhoto(message.id, message, attachmentDiv, undefined, undefined, true, true, this.lazyLoadQueue, null);
preloader.attach(attachmentDiv, false);
bubble.classList.add('hide-name', 'photo'); bubble.classList.add('hide-name', 'photo');
//} //}
@ -1401,6 +1471,7 @@ export class AppImManager {
preloader.attach(icoDiv, false); preloader.attach(icoDiv, false);
bubble.classList.remove('is-message-empty'); bubble.classList.remove('is-message-empty');
messageDiv.classList.add((pending.type || 'document') + '-message');
messageDiv.append(docDiv); messageDiv.append(docDiv);
processingWebPage = true; processingWebPage = true;
break; break;
@ -1416,105 +1487,18 @@ export class AppImManager {
////////this.log('messageMediaPhoto', photo); ////////this.log('messageMediaPhoto', photo);
bubble.classList.add('hide-name', 'photo'); bubble.classList.add('hide-name', 'photo');
if(message.grouped_id) { if(message.grouped_id) {
bubble.classList.add('is-album'); bubble.classList.add('is-album');
let items: {size: MTPhotoSize, media: any}[] = []; wrapAlbum({
groupID: message.grouped_id,
// higher msgID will be the last in album attachmentDiv,
let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id]; middleware: () => {
for(let mid in storage) { return this.peerID == peerID;
let m = appMessagesManager.getMessage(+mid); },
let media = m.media.photo || m.media.document; isOut: our,
lazyLoadQueue: this.lazyLoadQueue
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);
}
} else { } else {
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, our, this.lazyLoadQueue, () => { wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, our, this.lazyLoadQueue, () => {
return this.peerID == peerID; return this.peerID == peerID;
@ -1650,6 +1634,19 @@ export class AppImManager {
} }
bubble.classList.add('hide-name', 'video'); bubble.classList.add('hide-name', 'video');
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({ wrapVideo({
doc, doc,
container: attachmentDiv, container: attachmentDiv,
@ -1663,6 +1660,7 @@ export class AppImManager {
return this.peerID == peerID; return this.peerID == peerID;
} }
}); });
}
break; break;
} else if(doc.mime_type == 'audio/ogg') { } else if(doc.mime_type == 'audio/ogg') {

6
src/lib/appManagers/appMediaViewer.ts

@ -56,6 +56,7 @@ export class AppMediaViewer {
private loadedAllMediaDown = false; private loadedAllMediaDown = false;
private reverse = false; // reverse means next = higher msgid private reverse = false; // reverse means next = higher msgid
private needLoadMore = true;
constructor() { constructor() {
this.log = logger('AMV'); this.log = logger('AMV');
@ -517,7 +518,7 @@ export class AppMediaViewer {
} }
public openMedia(message: any, target?: HTMLElement, reverse = false, targetContainer?: HTMLElement, 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); ////////this.log('openMedia doc:', message, prevTarget, nextTarget);
let media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo; 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.prevTargets = prevTargets;
this.nextTargets = nextTargets; this.nextTargets = nextTargets;
this.reverse = reverse; this.reverse = reverse;
this.needLoadMore = needLoadMore;
//this.loadMore = loadMore; //this.loadMore = loadMore;
} }
@ -557,6 +559,7 @@ export class AppMediaViewer {
this.currentMessageID = message.mid; this.currentMessageID = message.mid;
this.lastTarget = target; this.lastTarget = target;
if(this.needLoadMore) {
if(this.nextTargets.length < 20) { if(this.nextTargets.length < 20) {
this.loadMoreMedia(!this.reverse); this.loadMoreMedia(!this.reverse);
} }
@ -564,6 +567,7 @@ export class AppMediaViewer {
if(this.prevTargets.length < 20) { if(this.prevTargets.length < 20) {
this.loadMoreMedia(this.reverse); this.loadMoreMedia(this.reverse);
} }
}
if(container.firstElementChild) { if(container.firstElementChild) {
container.innerHTML = ''; container.innerHTML = '';

320
src/lib/appManagers/appMessagesManager.ts

@ -47,7 +47,7 @@ export class AppMessagesManager {
count: any, count: any,
dialogs: any[] dialogs: any[]
} = {count: null, dialogs: []}; } = {count: null, dialogs: []};
public pendingByRandomID: any = {}; public pendingByRandomID: {[randomID: string]: [number, number]} = {};
public pendingByMessageID: any = {}; public pendingByMessageID: any = {};
public pendingAfterMsgs: any = {}; public pendingAfterMsgs: any = {};
public pendingTopMsgs: any = {}; public pendingTopMsgs: any = {};
@ -449,17 +449,18 @@ export class AppMessagesManager {
this.pendingByRandomID[randomIDS] = [peerID, messageID]; this.pendingByRandomID[randomIDS] = [peerID, messageID];
} }
public sendFile(peerID: number, file: File | Blob | MTDocument, options: { public sendFile(peerID: number, file: File | Blob | MTDocument, options: Partial<{
isMedia?: boolean, isMedia: boolean,
replyToMsgID?: number, replyToMsgID: number,
caption?: string, caption: string,
entities?: any[], entities: any[],
width?: number, width: number,
height?: number, height: number,
objectURL?: string, objectURL: string,
isRoundMessage?: boolean, isRoundMessage: boolean,
duration?: number duration: number,
} = {}) { background: boolean
}> = {}) {
peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID;
var messageID = this.tempID--; var messageID = this.tempID--;
var randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]; var randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)];
@ -656,6 +657,8 @@ export class AppMessagesManager {
return apiManager.invokeApi('messages.sendMedia', { return apiManager.invokeApi('messages.sendMedia', {
flags: flags, flags: flags,
background: options.background,
clear_draft: true,
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
media: inputMedia, media: inputMedia,
message: caption, message: caption,
@ -683,9 +686,10 @@ export class AppMessagesManager {
if(replyToMsgID) { if(replyToMsgID) {
flags |= 1; flags |= 1;
} }
if(asChannel) { if(options.background) {
flags |= 16; flags |= 64;
} }
flags |= 128; // clear_draft
if(isDocument) { if(isDocument) {
let {id, access_hash, file_reference} = file as MTDocument; let {id, access_hash, file_reference} = file as MTDocument;
@ -777,6 +781,293 @@ export class AppMessagesManager {
this.pendingByRandomID[randomIDS] = [peerID, messageID]; 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<typeof apiFileManager.uploadFile> = 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<void>();
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) { public cancelPendingMessage(randomID: string) {
var pendingData = this.pendingByRandomID[randomID]; var pendingData = this.pendingByRandomID[randomID];
@ -2273,6 +2564,7 @@ export class AppMessagesManager {
case 'updateMessageID': { case 'updateMessageID': {
var randomID = update.random_id; var randomID = update.random_id;
var pendingData = this.pendingByRandomID[randomID]; var pendingData = this.pendingByRandomID[randomID];
console.log('AMM updateMessageID:', update, pendingData);
if(pendingData) { if(pendingData) {
var peerID: number = pendingData[0]; var peerID: number = pendingData[0];
var tempID = pendingData[1]; var tempID = pendingData[1];

15
src/lib/appManagers/appPhotosManager.ts

@ -315,6 +315,21 @@ export class AppPhotosManager {
return isObject(photoID) ? photoID : this.photos[photoID]; 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) { public downloadPhoto(photoID: string) {
var photo = this.photos[photoID]; var photo = this.photos[photoID];
var ext = 'jpg'; var ext = 'jpg';

2
src/lib/appManagers/appSidebarRight.ts

@ -171,7 +171,7 @@ class AppSidebarRight {
let targets = ids.map(id => ({element: this.mediaDivsByIDs[id], mid: id})); 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', () => { this.profileElements.notificationsCheckbox.addEventListener('change', () => {

1
src/lib/mtproto/mtprotoworker.ts

@ -121,6 +121,7 @@ class ApiManagerProxy extends CryptoWorkerMethods {
stopTime?: number, stopTime?: number,
rawError?: any rawError?: any
} = {}): Promise<any> { } = {}): Promise<any> {
console.log('will invokeApi:', method, params, options);
return this.performTaskWorker('invokeApi', method, params, options); return this.performTaskWorker('invokeApi', method, params, options);
} }

18
src/scss/partials/_chat.scss

@ -589,6 +589,10 @@ $chat-max-width: 696px;
max-width: 100%; max-width: 100%;
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
img, video {
border-radius: inherit;
}
} }
} }
} }
@ -1132,8 +1136,8 @@ $chat-max-width: 696px;
} }
} }
.audio-subtitle, .contact-number { .audio-subtitle, .contact-number, .audio-time {
color: #707579; color: #707579 !important;
} }
} }
@ -1376,6 +1380,16 @@ $chat-max-width: 696px;
&.menu-open { &.menu-open {
color: $blue; color: $blue;
} }
.btn-menu {
padding: 8px 0;
right: -8px;
bottom: calc(100% + 16px);
> div {
padding: 0 38px 0 16px;
}
}
} }
> div { > div {

2
src/scss/partials/_emojiDropdown.scss

@ -1,7 +1,7 @@
.emoji-dropdown { .emoji-dropdown {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc(-420px + -0.75rem); top: calc(-420px + -4px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 420px; width: 420px;

2
src/scss/partials/_mediaViewer.scss

@ -4,7 +4,7 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: rgba(0, 0, 0, .9); background: rgba(0, 0, 0, .88);
/* color: $darkgrey; */ /* color: $darkgrey; */
display: flex; display: flex;
align-items: center; align-items: center;

52
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;
}
}
}

150
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;
}
}
}

80
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;
}
}

238
src/scss/style.scss

@ -43,6 +43,10 @@ $large-screen: 1680px;
@import "partials/emojiDropdown"; @import "partials/emojiDropdown";
@import "partials/scrollable"; @import "partials/scrollable";
@import "partials/popups/popup";
@import "partials/popups/editAvatar";
@import "partials/popups/mediaAttacher";
html, body { html, body {
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -1156,81 +1160,6 @@ img.emoji {
height: 18px; 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 { .btn-circle {
border-radius: 50%; border-radius: 50%;
height: 54px; 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 { .overlay::selection {
background: transparent; background: transparent;
} }
@ -1487,105 +1356,6 @@ span.popup-close {
justify-content: flex-start!important; 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 { .page-chats {
/* display: grid; */ /* display: grid; */
/* grid-template-columns: 25% 50%; */ /* grid-template-columns: 25% 50%; */

Loading…
Cancel
Save