Browse Source

Send multiple files

Refactor sendAlbum
master
Eduard Kuzmenko 4 years ago
parent
commit
eda9f5a306
  1. 107
      src/components/chat/bubbles.ts
  2. 97
      src/components/popupNewMedia.ts
  3. 81
      src/components/wrappers.ts
  4. 475
      src/lib/appManagers/appMessagesManager.ts
  5. 21
      src/scss/partials/popups/_mediaAttacher.scss

107
src/components/chat/bubbles.ts

@ -30,7 +30,7 @@ import AudioElement from "../audio"; @@ -30,7 +30,7 @@ import AudioElement from "../audio";
import AvatarElement from "../avatar";
import { formatPhoneNumber } from "../misc";
import { ripple } from "../ripple";
import { wrapAlbum, wrapPhoto, wrapVideo, wrapDocument, wrapSticker, wrapPoll, wrapReply } from "../wrappers";
import { wrapAlbum, wrapPhoto, wrapVideo, wrapDocument, wrapSticker, wrapPoll, wrapReply, wrapGroupedDocuments } from "../wrappers";
import { MessageRender } from "./messageRender";
import LazyLoadQueue from "../lazyLoadQueue";
import { AppChatsManager } from "../../lib/appManagers/appChatsManager";
@ -197,19 +197,17 @@ export default class ChatBubbles { @@ -197,19 +197,17 @@ export default class ChatBubbles {
appSidebarRight.sharedMediaTab.renderNewMessages(message.peerID, [mid]);
const bubble = this.bubbles[tempID];
if(bubble) {
this.bubbles[mid] = bubble;
const mounted = this.getMountedBubble(tempID) || this.getMountedBubble(mid);
if(mounted) {
const bubble = mounted.bubble;
//this.bubbles[mid] = bubble;
/////this.log('message_sent', bubble);
// set new mids to album items for mediaViewer
if(message.grouped_id) {
const items = bubble.querySelectorAll('.grouped-item');
const groupIDs = getObjectKeysAndSort(appMessagesManager.groupedMessagesStorage[message.grouped_id]);
(Array.from(items) as HTMLElement[]).forEach((item, idx) => {
item.dataset.mid = '' + groupIDs[idx];
});
const item = bubble.querySelector(`.grouped-item[data-mid="${tempID}"]`) as HTMLElement;
item.dataset.mid = '' + mid;
}
if(message.media?.poll) {
@ -222,16 +220,16 @@ export default class ChatBubbles { @@ -222,16 +220,16 @@ export default class ChatBubbles {
}
if(['audio', 'voice'].includes(message.media?.document?.type)) {
const audio = bubble.querySelector('audio-element');
const audio = bubble.querySelector(`audio-element[message-id="${tempID}"]`);
audio.setAttribute('doc-id', message.media.document.id);
audio.setAttribute('message-id', '' + mid);
}
bubble.classList.remove('is-sending');
/* bubble.classList.remove('is-sending');
bubble.classList.add('is-sent');
bubble.dataset.mid = '' + mid;
this.bubbleGroups.removeBubble(bubble, tempID);
this.bubbleGroups.removeBubble(bubble, tempID); */
if(message.media?.webpage && !bubble.querySelector('.web')) {
const mounted = this.getMountedBubble(mid);
@ -239,11 +237,23 @@ export default class ChatBubbles { @@ -239,11 +237,23 @@ export default class ChatBubbles {
this.renderMessage(mounted.message, true, false, mounted.bubble, false);
}
delete this.bubbles[tempID];
//delete this.bubbles[tempID];
} else {
this.log.warn('message_sent there is no bubble', e.detail);
}
if(this.bubbles[tempID]) {
const bubble = this.bubbles[tempID];
this.bubbles[mid] = bubble;
delete this.bubbles[tempID];
bubble.classList.remove('is-sending');
bubble.classList.add('is-sent');
bubble.dataset.mid = '' + mid;
this.bubbleGroups.removeBubble(bubble, tempID);
}
if(this.unreadOut.has(tempID)) {
this.unreadOut.delete(tempID);
this.unreadOut.add(mid);
@ -1360,7 +1370,7 @@ export default class ChatBubbles { @@ -1360,7 +1370,7 @@ export default class ChatBubbles {
let messageMedia = message.media;
let messageMessage: string, totalEntities: any[];
if(messageMedia?.document && !messageMedia.document.type) {
if(messageMedia?.document && messageMedia.document.type !== 'video') {
// * just filter this case
} else if(message.grouped_id && albumMustBeRenderedFull) {
const t = this.appMessagesManager.getAlbumText(message.grouped_id);
@ -1575,16 +1585,15 @@ export default class ChatBubbles { @@ -1575,16 +1585,15 @@ export default class ChatBubbles {
case 'audio':
case 'voice':
case 'document': {
const doc = this.appDocsManager.getDoc(message.id);
//if(doc._ == 'documentEmpty') break;
this.log('will wrap pending doc:', doc);
const docDiv = wrapDocument(doc, false, true, message.id);
const newNameContainer = wrapGroupedDocuments({
albumMustBeRenderedFull,
message,
bubble,
messageDiv
});
if(doc.type == 'audio' || doc.type == 'voice') {
(docDiv as AudioElement).preloader = preloader;
} else {
const icoDiv = docDiv.querySelector('.audio-download, .document-ico');
preloader.attach(icoDiv, false);
if(newNameContainer) {
nameContainer = newNameContainer;
}
if(pending.type == 'voice') {
@ -1593,7 +1602,6 @@ export default class ChatBubbles { @@ -1593,7 +1602,6 @@ export default class ChatBubbles {
bubble.classList.remove('is-message-empty');
messageDiv.classList.add((pending.type || 'document') + '-message');
messageDiv.append(docDiv);
processingWebPage = true;
break;
}
@ -1805,52 +1813,15 @@ export default class ChatBubbles { @@ -1805,52 +1813,15 @@ export default class ChatBubbles {
break;
} else {
//const storage = this.appMessagesManager.groupedMessagesStorage[message.grouped_id];
//const isFullAlbum = storage && Object.keys(storage).length != 1;
const mids = albumMustBeRenderedFull ? this.appMessagesManager.getMidsByMid(message.mid) : [message.mid];
mids.forEach((mid, idx) => {
const message = this.appMessagesManager.getMessage(mid);
const doc = message.media.document;
const div = wrapDocument(doc, false, false, mid);
const container = document.createElement('div');
container.classList.add('document-container');
container.dataset.mid = '' + mid;
const wrapper = document.createElement('div');
wrapper.classList.add('document-wrapper');
if(message.message) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('document-message');
const richText = RichTextProcessor.wrapRichText(message.message, {
entities: message.totalEntities
});
messageDiv.innerHTML = richText;
wrapper.append(messageDiv);
}
if(mids.length > 1) {
const selection = document.createElement('div');
selection.classList.add('document-selection');
container.append(selection);
container.classList.add('grouped-item');
if(idx === 0) {
nameContainer = wrapper;
}
}
wrapper.append(div);
container.append(wrapper);
messageDiv.append(container);
const newNameContainer = wrapGroupedDocuments({
albumMustBeRenderedFull,
message,
bubble,
messageDiv
});
if(mids.length > 1) {
bubble.classList.add('is-multiple-documents', 'is-grouped');
if(newNameContainer) {
nameContainer = newNameContainer;
}
bubble.classList.remove('is-message-empty');

97
src/components/popupNewMedia.ts

@ -8,6 +8,7 @@ import { ripple } from "./ripple"; @@ -8,6 +8,7 @@ import { ripple } from "./ripple";
import Scrollable from "./scrollable";
import { toast } from "./toast";
import { prepareAlbum, wrapDocument } from "./wrappers";
import CheckboxField from "./checkbox";
type SendFileParams = Partial<{
file: File,
@ -25,13 +26,16 @@ export default class PopupNewMedia extends PopupElement { @@ -25,13 +26,16 @@ export default class PopupNewMedia extends PopupElement {
private btnSend: HTMLElement;
private input: HTMLInputElement;
private mediaContainer: HTMLElement;
private groupCheckboxField: { label: HTMLLabelElement; input: HTMLInputElement; span: HTMLSpanElement; };
private willAttach: Partial<{
type: 'media' | 'document',
isMedia: boolean,
isMedia: true,
group: boolean,
sendFileDetails: SendFileParams[]
}> = {
sendFileDetails: []
sendFileDetails: [],
group: false
};
constructor(files: File[], willAttachType: PopupNewMedia['willAttach']['type']) {
@ -60,7 +64,29 @@ export default class PopupNewMedia extends PopupElement { @@ -60,7 +64,29 @@ export default class PopupNewMedia extends PopupElement {
showLengthOn: 80
});
this.input = inputField.input;
this.container.append(scrollable.container, inputField.container);
this.container.append(scrollable.container);
if(files.length > 1) {
this.groupCheckboxField = CheckboxField('Group items', 'group-items');
this.container.append(this.groupCheckboxField.label, inputField.container);
this.groupCheckboxField.input.checked = true;
this.willAttach.group = true;
this.groupCheckboxField.input.addEventListener('change', () => {
const checked = this.groupCheckboxField.input.checked;
this.willAttach.group = checked;
this.willAttach.sendFileDetails.length = 0;
//this.mediaContainer.innerHTML = '';
//this.container.classList.remove('is-media', 'is-document', 'is-album');
this.attachFiles(files);
});
}
this.container.append(inputField.container);
this.attachFiles(files);
}
@ -85,18 +111,35 @@ export default class PopupNewMedia extends PopupElement { @@ -85,18 +111,35 @@ export default class PopupNewMedia extends PopupElement {
this.destroy();
const willAttach = this.willAttach;
willAttach.isMedia = willAttach.type == 'media';
willAttach.isMedia = willAttach.type == 'media' ? true : undefined;
//console.log('will send files with options:', willAttach);
const peerID = appImManager.chat.peerID;
const chatInputC = appImManager.chat.input;
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) {
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({
if(willAttach.sendFileDetails.length > 1 && willAttach.group) {
for(let i = 0; i < willAttach.sendFileDetails.length;) {
let firstType = willAttach.sendFileDetails[i].file.type.split('/')[0];
for(var k = 0; k < 10 && i < willAttach.sendFileDetails.length; ++i, ++k) {
const type = willAttach.sendFileDetails[i].file.type.split('/')[0];
if(firstType != type) {
break;
}
}
const w = {...willAttach};
w.sendFileDetails = willAttach.sendFileDetails.slice(i - k, i);
appMessagesManager.sendAlbum(peerID, w.sendFileDetails.map(d => d.file), Object.assign({
caption,
replyToMsgID: chatInputC.replyToMsgID
}, willAttach));
replyToMsgID: chatInputC.replyToMsgID,
isMedia: willAttach.isMedia
}, w));
caption = undefined;
chatInputC.replyToMsgID = 0;
}
} else {
if(caption) {
if(willAttach.sendFileDetails.length > 1) {
@ -109,7 +152,7 @@ export default class PopupNewMedia extends PopupElement { @@ -109,7 +152,7 @@ export default class PopupNewMedia extends PopupElement {
const promises = willAttach.sendFileDetails.map(params => {
const promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({
//isMedia: willAttach.isMedia,
isMedia: params.file.type.includes('audio/') || willAttach.isMedia,
isMedia: willAttach.isMedia,
caption,
replyToMsgID: chatInputC.replyToMsgID
}, params));
@ -176,7 +219,8 @@ export default class PopupNewMedia extends PopupElement { @@ -176,7 +219,8 @@ export default class PopupNewMedia extends PopupElement {
case 'document': {
const isPhoto = file.type.indexOf('image/') !== -1;
if(isPhoto) {
const isAudio = file.type.indexOf('audio/') !== -1;
if(isPhoto || isAudio) {
params.objectURL = URL.createObjectURL(file);
}
@ -220,9 +264,9 @@ export default class PopupNewMedia extends PopupElement { @@ -220,9 +264,9 @@ export default class PopupNewMedia extends PopupElement {
const container = this.container;
const willAttach = this.willAttach;
if(files.length > 10 && willAttach.type == 'media') {
/* if(files.length > 10 && willAttach.type == 'media') {
willAttach.type = 'document';
}
} */
files = files.filter(file => {
if(willAttach.type == 'media') {
@ -232,6 +276,10 @@ export default class PopupNewMedia extends PopupElement { @@ -232,6 +276,10 @@ export default class PopupNewMedia extends PopupElement {
}
});
Promise.all(files.map(this.attachFile)).then(results => {
this.container.classList.remove('is-media', 'is-document', 'is-album');
this.mediaContainer.innerHTML = '';
if(files.length) {
if(willAttach.type == 'document') {
this.title.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File');
@ -246,7 +294,7 @@ export default class PopupNewMedia extends PopupElement { @@ -246,7 +294,7 @@ export default class PopupNewMedia extends PopupElement {
else if(file.type.indexOf('video/') === 0) ++foundVideos;
});
if(foundPhotos && foundVideos) {
if(foundPhotos && foundVideos && willAttach.group) {
this.title.innerText = 'Send Album';
} else if(foundPhotos) {
this.title.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo');
@ -256,40 +304,49 @@ export default class PopupNewMedia extends PopupElement { @@ -256,40 +304,49 @@ export default class PopupNewMedia extends PopupElement {
}
}
Promise.all(files.map(this.attachFile)).then(results => {
if(willAttach.type == 'media') {
if(willAttach.sendFileDetails.length > 1) {
if(willAttach.sendFileDetails.length > 1 && willAttach.group) {
container.classList.add('is-album');
this.mediaContainer.append(...results);
for(let i = 0; i < results.length; i += 10) {
const albumContainer = document.createElement('div');
albumContainer.classList.add('popup-album');
albumContainer.append(...results.slice(i, i + 10));
prepareAlbum({
container: this.mediaContainer,
items: willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})),
container: albumContainer,
items: willAttach.sendFileDetails.slice(i, i + 10).map(o => ({w: o.width, h: o.height})),
maxWidth: 380,
minWidth: 100,
spacing: 4
});
this.mediaContainer.append(albumContainer);
}
//console.log('chatInput album layout:', layout);
} else {
const params = willAttach.sendFileDetails[0];
const div = results[0];
for(let i = 0; i < results.length; ++i) {
const params = willAttach.sendFileDetails[i];
const div = results[i];
const {w, h} = calcImageInBox(params.width, params.height, 380, 320);
div.style.width = w + 'px';
div.style.height = h + 'px';
this.mediaContainer.append(div);
}
}
} else {
this.mediaContainer.append(...results);
}
// show now
if(!this.element.classList.contains('active')) {
document.body.addEventListener('keydown', this.onKeyDown);
this.onClose = () => {
document.body.removeEventListener('keydown', this.onKeyDown);
};
this.show();
}
});
}
}

81
src/components/wrappers.ts

@ -25,6 +25,7 @@ import PollElement from './poll'; @@ -25,6 +25,7 @@ import PollElement from './poll';
import ProgressivePreloader from './preloader';
import './middleEllipsis';
import { nextRandomInt } from '../helpers/random';
import RichTextProcessor from '../lib/richtextprocessor';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -337,7 +338,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals @@ -337,7 +338,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals
const icoDiv = document.createElement('div');
icoDiv.classList.add('document-ico');
if(doc.thumbs?.length || (uploading && doc.url)) {
if(doc.thumbs?.length || (uploading && doc.url && doc.type == 'photo')) {
docDiv.classList.add('document-with-thumb');
if(uploading) {
@ -858,7 +859,7 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo @@ -858,7 +859,7 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
}) {
const items: {size: PhotoSize.photoSize, media: any, message: any}[] = [];
// !higher msgID will be the FIRST in album
// !lowest msgID will be the FIRST in album
const storage = appMessagesManager.getMidsByAlbum(groupID);
for(const mid of storage) {
const m = appMessagesManager.getMessage(mid);
@ -868,6 +869,11 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo @@ -868,6 +869,11 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
items.push({size, media, message: m});
}
// * pending
if(storage[0] < 0) {
items.reverse();
}
prepareAlbum({
container: attachmentDiv,
items: items.map(i => ({w: i.size.w, h: i.size.h})),
@ -912,6 +918,77 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo @@ -912,6 +918,77 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
});
}
export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, messageDiv}: {
albumMustBeRenderedFull: boolean,
message: any,
messageDiv: HTMLElement,
bubble: HTMLElement,
uploading?: boolean
}) {
let nameContainer: HTMLDivElement;
const mids = albumMustBeRenderedFull ? appMessagesManager.getMidsByMid(message.mid) : [message.mid];
const isPending = message.mid < 0;
if(isPending) {
mids.reverse();
}
mids.forEach((mid, idx) => {
const message = appMessagesManager.getMessage(mid);
const doc = message.media.document;
const div = wrapDocument(doc, false, isPending, mid);
const container = document.createElement('div');
container.classList.add('document-container');
container.dataset.mid = '' + mid;
const wrapper = document.createElement('div');
wrapper.classList.add('document-wrapper');
if(message.message) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('document-message');
const richText = RichTextProcessor.wrapRichText(message.message, {
entities: message.totalEntities
});
messageDiv.innerHTML = richText;
wrapper.append(messageDiv);
}
if(mids.length > 1) {
const selection = document.createElement('div');
selection.classList.add('document-selection');
container.append(selection);
container.classList.add('grouped-item');
if(idx === 0) {
nameContainer = wrapper;
}
}
if(isPending) {
if(doc.type == 'audio' || doc.type == 'voice') {
(div as AudioElement).preloader = message.media.preloader;
} else {
const icoDiv = div.querySelector('.audio-download, .document-ico');
message.media.preloader.attach(icoDiv, false);
}
}
wrapper.append(div);
container.append(wrapper);
messageDiv.append(container);
});
if(mids.length > 1) {
bubble.classList.add('is-multiple-documents', 'is-grouped');
}
return nameContainer;
}
export function wrapPoll(pollID: string, mid: number) {
const elem = new PollElement();
elem.setAttribute('poll-id', pollID);

475
src/lib/appManagers/appMessagesManager.ts

@ -4,7 +4,7 @@ import { tsNow } from "../../helpers/date"; @@ -4,7 +4,7 @@ import { tsNow } from "../../helpers/date";
import { copy, defineNotNumerableProperties, safeReplaceObject, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols } from "../../helpers/string";
import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMessage, InputNotifyPeer, InputPeerNotifySettings, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer";
import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputNotifyPeer, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer";
import { InvokeApiOptions } from "../../types";
import { langPack } from "../langPack";
import { logger, LogLevels } from "../logger";
@ -569,31 +569,32 @@ export class AppMessagesManager { @@ -569,31 +569,32 @@ export class AppMessagesManager {
}
public sendFile(peerID: number, file: File | Blob | MyDocument, options: Partial<{
isMedia: boolean,
isRoundMessage: true,
isVoiceMessage: true,
isGroupedItem: true,
isMedia: true,
replyToMsgID: number,
caption: string,
entities: any[],
entities: MessageEntity[],
width: number,
height: number,
objectURL: string,
isRoundMessage: boolean,
duration: number,
background: boolean,
background: true,
isVoiceMessage: boolean,
waveform: Uint8Array
}> = {}) {
peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID;
var messageID = this.tempID--;
var randomIDS = randomLong();
var historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {count: null, history: [], pending: []});
var flags = 0;
var pFlags: any = {};
var replyToMsgID = options.replyToMsgID;
var isChannel = appPeersManager.isChannel(peerID);
var isMegagroup = isChannel && appPeersManager.isMegagroup(peerID);
var asChannel = isChannel && !isMegagroup ? true : false;
var attachType: string, apiFileName: string;
const messageID = this.tempID--;
const randomIDS = randomLong();
const historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {count: null, history: [], pending: []});
const pFlags: any = {};
const replyToMsgID = options.replyToMsgID;
const isChannel = appPeersManager.isChannel(peerID);
const isMegagroup = isChannel && appPeersManager.isMegagroup(peerID);
const asChannel = !!(isChannel && !isMegagroup);
let attachType: string, apiFileName: string;
const fileType = 'mime_type' in file ? file.mime_type : file.type;
const fileName = file instanceof File ? file.name : '';
@ -613,20 +614,42 @@ export class AppMessagesManager { @@ -613,20 +614,42 @@ export class AppMessagesManager {
const isPhoto = ['image/jpeg', 'image/png', 'image/bmp'].indexOf(fileType) >= 0;
let photo: MyPhoto, document: MyDocument;
let actionName = '';
if(!options.isMedia) {
if(isDocument) { // maybe it's a sticker or gif
attachType = 'document';
apiFileName = '';
} else if(fileType.indexOf('audio/') === 0 || ['video/ogg'].indexOf(fileType) >= 0) {
attachType = 'audio';
apiFileName = 'audio.' + (fileType.split('/')[1] == 'ogg' ? 'ogg' : 'mp3');
actionName = 'sendMessageUploadAudioAction';
if(options.isVoiceMessage) {
attachType = 'voice';
pFlags.media_unread = true;
}
let attribute: DocumentAttribute.documentAttributeAudio = {
_: 'documentAttributeAudio',
pFlags: {
voice: options.isVoiceMessage
},
waveform: options.waveform,
duration: options.duration || 0
};
attributes.push(attribute);
} else if(!options.isMedia) {
attachType = 'document';
apiFileName = 'document.' + fileType.split('/')[1];
actionName = 'sendMessageUploadDocumentAction';
} else if(isDocument) { // maybe it's a sticker or gif
attachType = 'document';
apiFileName = '';
} else if(isPhoto) {
attachType = 'photo';
apiFileName = 'photo.' + fileType.split('/')[1];
actionName = 'sendMessageUploadPhotoAction';
let photo: MyPhoto = {
photo = {
_: 'photo',
id: '' + messageID,
sizes: [{
@ -646,30 +669,6 @@ export class AppMessagesManager { @@ -646,30 +669,6 @@ export class AppMessagesManager {
photo.url = options.objectURL || '';
appPhotosManager.savePhoto(photo);
} else if(fileType.indexOf('audio/') === 0 || ['video/ogg'].indexOf(fileType) >= 0) {
attachType = 'audio';
apiFileName = 'audio.' + (fileType.split('/')[1] == 'ogg' ? 'ogg' : 'mp3');
actionName = 'sendMessageUploadAudioAction';
let flags = 0;
if(options.isVoiceMessage) {
flags |= 1 << 10;
flags |= 1 << 2;
attachType = 'voice';
pFlags.media_unread = true;
}
let attribute: DocumentAttribute.documentAttributeAudio = {
_: 'documentAttributeAudio',
flags: flags,
pFlags: { // that's only for client, not going to telegram
voice: options.isVoiceMessage || undefined
},
waveform: options.waveform,
duration: options.duration || 0
};
attributes.push(attribute);
} else if(fileType.indexOf('video/') === 0) {
attachType = 'video';
apiFileName = 'video.mp4';
@ -677,9 +676,8 @@ export class AppMessagesManager { @@ -677,9 +676,8 @@ export class AppMessagesManager {
let videoAttribute: DocumentAttribute.documentAttributeVideo = {
_: 'documentAttributeVideo',
pFlags: { // that's only for client, not going to telegram
supports_streaming: true,
round_message: options.isRoundMessage || undefined
pFlags: {
round_message: options.isRoundMessage
},
duration: options.duration,
w: options.width,
@ -697,7 +695,7 @@ export class AppMessagesManager { @@ -697,7 +695,7 @@ export class AppMessagesManager {
if(['document', 'video', 'audio', 'voice'].indexOf(attachType) !== -1 && !isDocument) {
const thumbs: PhotoSize[] = [];
const doc: MyDocument = {
document = {
_: 'document',
id: '' + messageID,
duration: options.duration,
@ -709,10 +707,10 @@ export class AppMessagesManager { @@ -709,10 +707,10 @@ export class AppMessagesManager {
size: file.size
} as any;
defineNotNumerableProperties(doc, ['downloaded', 'url']);
defineNotNumerableProperties(document, ['downloaded', 'url']);
// @ts-ignore
doc.downloaded = file.size;
doc.url = options.objectURL || '';
document.downloaded = file.size;
document.url = options.objectURL || '';
if(isPhoto) {
attributes.push({
@ -732,42 +730,36 @@ export class AppMessagesManager { @@ -732,42 +730,36 @@ export class AppMessagesManager {
});
}
appDocsManager.saveDoc(doc);
appDocsManager.saveDoc(document);
}
this.log('AMM: sendFile', attachType, apiFileName, file.type, options);
var fromID = appUsersManager.getSelf().id;
let fromID = appUsersManager.getSelf().id;
if(peerID != fromID) {
flags |= 2;
pFlags.out = true;
if(!isChannel && !appUsersManager.isBot(peerID)) {
flags |= 1;
pFlags.unread = true;
}
}
if(replyToMsgID) {
flags |= 8;
}
if(asChannel) {
fromID = 0;
pFlags.post = true;
} else {
flags |= 256;
}
const preloader = new ProgressivePreloader(null, true, false, 'prepend');
const media = {
_: 'messageMediaPending',
type: attachType,
type: options.isGroupedItem && options.isMedia ? 'album' : attachType,
file_name: fileName || apiFileName,
size: file.size,
file: file,
preloader: preloader,
file,
preloader,
photo,
document,
w: options.width,
h: options.height,
url: options.objectURL
@ -778,8 +770,8 @@ export class AppMessagesManager { @@ -778,8 +770,8 @@ export class AppMessagesManager {
id: messageID,
from_id: appPeersManager.getOutputPeer(fromID),
peer_id: appPeersManager.getOutputPeer(peerID),
pFlags: pFlags,
date: date,
pFlags,
date,
message: caption,
media: isDocument ? {
_: 'messageMediaDocument',
@ -805,59 +797,22 @@ export class AppMessagesManager { @@ -805,59 +797,22 @@ export class AppMessagesManager {
let uploaded = false,
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
const invoke = (flags: number, inputMedia: any) => {
this.setTyping(peerID, 'sendMessageCancelAction');
return apiManager.invokeApi('messages.sendMedia', {
flags: flags,
background: options.background || undefined,
clear_draft: true,
peer: appPeersManager.getInputPeerByID(peerID),
media: inputMedia,
message: caption,
random_id: randomIDS,
reply_to_msg_id: appMessagesIDsManager.getMessageLocalID(replyToMsgID)
}).then((updates) => {
apiUpdatesManager.processUpdateMessage(updates);
}, (error) => {
if(attachType == 'photo' &&
error.code == 400 &&
(error.type == 'PHOTO_INVALID_DIMENSIONS' ||
error.type == 'PHOTO_SAVE_FILE_INVALID')) {
error.handled = true
attachType = 'document'
message.send();
return;
}
toggleError(true);
});
};
const sentDeferred = deferredPromise<InputMedia>();
message.send = () => {
let flags = 0;
if(replyToMsgID) {
flags |= 1;
}
if(options.background) {
flags |= 64;
}
flags |= 128; // clear_draft
if(isDocument) {
const {id, access_hash, file_reference} = file as MyDocument;
const inputMedia = {
const inputMedia: InputMedia = {
_: 'inputMediaDocument',
id: {
_: 'inputDocument',
id: id,
access_hash: access_hash,
file_reference: file_reference
id,
access_hash,
file_reference
}
};
invoke(flags, inputMedia);
sentDeferred.resolve(inputMedia);
} else if(file instanceof File || file instanceof Blob) {
const deferred = deferredPromise<void>();
@ -873,7 +828,7 @@ export class AppMessagesManager { @@ -873,7 +828,7 @@ export class AppMessagesManager {
inputFile.name = apiFileName;
uploaded = true;
var inputMedia;
let inputMedia: InputMedia;
switch(attachType) {
case 'photo':
inputMedia = {
@ -887,11 +842,11 @@ export class AppMessagesManager { @@ -887,11 +842,11 @@ export class AppMessagesManager {
_: 'inputMediaUploadedDocument',
file: inputFile,
mime_type: fileType,
attributes: attributes
attributes
};
}
invoke(flags, inputMedia);
sentDeferred.resolve(inputMedia);
}, (/* error */) => {
toggleError(true);
});
@ -907,6 +862,7 @@ export class AppMessagesManager { @@ -907,6 +862,7 @@ export class AppMessagesManager {
this.log('cancelling upload', media);
deferred.resolve();
sentDeferred.reject(err);
this.cancelPendingMessage(randomIDS);
this.setTyping(peerID, 'sendMessageCancelAction');
}
@ -917,19 +873,52 @@ export class AppMessagesManager { @@ -917,19 +873,52 @@ export class AppMessagesManager {
this.sendFilePromise = deferred;
}
return sentDeferred;
};
this.saveMessages([message]);
historyStorage.pending.unshift(messageID);
this.pendingByRandomID[randomIDS] = [peerID, messageID];
if(!options.isGroupedItem) {
this.saveMessages([message]);
rootScope.broadcast('history_append', {peerID, messageID, my: true});
setTimeout(message.send, 0);
sentDeferred.then(inputMedia => {
this.setTyping(peerID, 'sendMessageCancelAction');
return apiManager.invokeApi('messages.sendMedia', {
background: options.background,
clear_draft: true,
peer: appPeersManager.getInputPeerByID(peerID),
media: inputMedia,
message: caption,
random_id: randomIDS,
reply_to_msg_id: appMessagesIDsManager.getMessageLocalID(replyToMsgID)
}).then((updates) => {
apiUpdatesManager.processUpdateMessage(updates);
}, (error) => {
if(attachType == 'photo' &&
error.code == 400 &&
(error.type == 'PHOTO_INVALID_DIMENSIONS' ||
error.type == 'PHOTO_SAVE_FILE_INVALID')) {
error.handled = true;
attachType = 'document';
message.send();
return;
}
setTimeout(message.send.bind(this), 0);
toggleError(true);
});
});
}
this.pendingByRandomID[randomIDS] = [peerID, messageID];
return {message, promise: sentDeferred};
}
public async sendAlbum(peerID: number, files: File[], options: Partial<{
entities: any[],
isMedia: true,
entities: MessageEntity[],
replyToMsgID: number,
caption: string,
sendFileDetails: Partial<{
@ -939,146 +928,50 @@ export class AppMessagesManager { @@ -939,146 +928,50 @@ export class AppMessagesManager {
objectURL: string,
}>[]
}> = {}) {
if(files.length === 1) {
return this.sendFile(peerID, files[0], {...options, ...options.sendFileDetails[0]});
}
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;
const replyToMsgID = options.replyToMsgID;
let caption = options.caption || '';
let date = tsNow(true) + serverTimeManager.serverTimeOffset;
let entities: MessageEntity[];
if(caption) {
let entities = options.entities || [];
entities = options.entities || [];
caption = RichTextProcessor.parseMarkdown(caption, entities);
}
this.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 randomIDS = randomLong();
let preloader = new ProgressivePreloader(null, true, false, 'prepend');
let details = options.sendFileDetails[idx];
let media = {
_: 'messageMediaPending',
type: 'album',
preloader: preloader,
document: undefined as any,
photo: undefined as any
};
if(file.type.indexOf('video/') === 0) {
let videoAttribute: DocumentAttribute.documentAttributeVideo = {
_: 'documentAttributeVideo',
pFlags: { // that's only for client, not going to telegram
supports_streaming: true
},
duration: details.duration,
w: details.width,
h: details.height
const messages = files.map((file, idx) => {
const details = options.sendFileDetails[idx];
const o: any = {
isGroupedItem: true,
isMedia: options.isMedia,
...details
};
let doc: MyDocument = {
_: 'document',
id: '' + messageID,
attributes: [videoAttribute],
thumbs: [],
mime_type: file.type,
size: file.size
} as any;
defineNotNumerableProperties(doc, ['downloaded', 'url']);
// @ts-ignore
doc.downloaded = file.size;
doc.url = details.objectURL || '';
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 PhotoSize],
w: details.width,
h: details.height
};
defineNotNumerableProperties(photo, ['downloaded', 'url']);
// @ts-ignore
photo.downloaded = file.size;
photo.url = details.objectURL || '';
appPhotosManager.savePhoto(photo);
media.photo = photo;
if(idx === 0) {
o.caption = caption;
o.entities = entities;
o.replyToMsgID = replyToMsgID;
}
let message = {
_: 'message',
id: messageID,
from_id: appPeersManager.getOutputPeer(fromID),
grouped_id: groupID,
peer_id: appPeersManager.getOutputPeer(peerID),
pFlags: pFlags,
date: date,
message: caption,
media: media,
random_id: randomIDS,
reply_to: {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 this.sendFile(peerID, file, o).message;
});
return message;
const groupID = messages[0].id;
messages.forEach(message => {
message.grouped_id = groupID;
});
this.saveMessages(messages);
rootScope.broadcast('history_append', {peerID, messageID: groupID, my: true});
rootScope.broadcast('history_append', {peerID, messageID: messages[messages.length - 1].id, my: true});
//return;
let toggleError = (message: any, on: boolean) => {
const toggleError = (message: any, on: boolean) => {
if(on) {
message.error = true;
} else {
@ -1088,15 +981,11 @@ export class AppMessagesManager { @@ -1088,15 +981,11 @@ export class AppMessagesManager {
rootScope.broadcast('messages_pending');
};
let uploaded = false,
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
let inputPeer = appPeersManager.getInputPeerByID(peerID);
let invoke = (multiMedia: any[]) => {
const inputPeer = appPeersManager.getInputPeerByID(peerID);
const invoke = (multiMedia: any[]) => {
this.setTyping(peerID, 'sendMessageCancelAction');
return apiManager.invokeApi('messages.sendMultiMedia', {
flags: flags,
peer: inputPeer,
multi_media: multiMedia,
reply_to_msg_id: appMessagesIDsManager.getMessageLocalID(replyToMsgID)
@ -1107,84 +996,15 @@ export class AppMessagesManager { @@ -1107,84 +996,15 @@ export class AppMessagesManager {
});
};
let inputs: any[] = [];
for(let i = 0, length = files.length; i < length; ++i) {
const file = files[i];
const message = messages[i];
const media = message.media;
const preloader = media.preloader;
const actionName = file.type.indexOf('video/') === 0 ? 'sendMessageUploadVideoAction' : 'sendMessageUploadPhotoAction';
const deferred = deferredPromise<void>();
let canceled = false;
let apiFileName: string;
if(file.type.indexOf('video/') === 0) {
apiFileName = 'video.mp4';
} else {
apiFileName = 'photo.' + file.type.split('/')[1];
}
await this.sendFilePromise;
this.sendFilePromise = deferred;
if(!uploaded || message.error) {
uploaded = false;
uploadPromise = appDownloadManager.upload(file);
preloader.attachPromise(uploadPromise);
}
uploadPromise.addNotifyListener((progress: {done: number, total: number}) => {
this.log('upload progress', progress);
const percents = Math.max(1, Math.floor(100 * progress.done / progress.total));
this.setTyping(peerID, {_: actionName, progress: percents | 0});
});
uploadPromise.catch(err => {
if(err.name === 'AbortError' && !uploaded) {
this.log('cancelling upload item', media);
canceled = true;
}
});
await uploadPromise.then((inputFile) => {
this.log('appMessagesManager: sendAlbum file uploaded:', inputFile);
if(canceled) {
return;
}
inputFile.name = apiFileName;
let inputMedia: any;
let details = options.sendFileDetails[i];
if(details.duration) {
inputMedia = {
_: 'inputMediaUploadedDocument',
file: inputFile,
mime_type: file.type,
attributes: [{
_: 'documentAttributeVideo',
supports_streaming: true,
duration: details.duration,
w: details.width,
h: details.height
}]
};
} else {
inputMedia = {
_: 'inputMediaUploadedPhoto',
file: inputFile
};
}
const inputs: InputSingleMedia[] = [];
for(const message of messages) {
const inputMedia: InputMedia = await message.send();
this.log('sendAlbum uploaded item:', inputMedia);
return apiManager.invokeApi('messages.uploadMedia', {
await apiManager.invokeApi('messages.uploadMedia', {
peer: inputPeer,
media: inputMedia
}).then(messageMedia => {
if(canceled) {
return;
}
let inputMedia: any;
if(messageMedia._ == 'messageMediaPhoto') {
const photo = appPhotosManager.savePhoto(messageMedia.photo);
@ -1199,22 +1019,19 @@ export class AppMessagesManager { @@ -1199,22 +1019,19 @@ export class AppMessagesManager {
media: inputMedia,
random_id: message.random_id,
message: caption,
entities: []
entities
});
caption = ''; // only 1 caption for all inputs
}, () => {
toggleError(message, true);
});
// * only 1 caption for all inputs
if(caption) {
caption = '';
entities = [];
}
}, () => {
toggleError(message, true);
});
this.log('appMessagesManager: sendAlbum uploadPromise.finally!');
deferred.resolve();
}
uploaded = true;
invoke(inputs);
}

21
src/scss/partials/popups/_mediaAttacher.scss

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
/* max-height: 425px; */
#{$parent}-photo {
max-height: 320px;
//max-height: 320px;
margin: 0 auto;
img {
@ -24,6 +24,7 @@ @@ -24,6 +24,7 @@
> div {
display: flex;
justify-content: center;
margin: 0 auto;
}
}
}
@ -33,7 +34,7 @@ @@ -33,7 +34,7 @@
margin: 0 auto;
position: relative;
> div {
.album-item {
position: absolute;
}
@ -87,6 +88,7 @@ @@ -87,6 +88,7 @@
//justify-content: center;
width: fit-content;
border-radius: $border-radius-medium;
user-select: none;
/* align-items: center; */
.document {
@ -152,4 +154,19 @@ @@ -152,4 +154,19 @@
.popup-header {
padding: 0;
}
.checkbox-field {
margin-bottom: 0;
padding-left: 0;
}
.popup-album, .popup-container:not(.is-album) .popup-item-media {
position: relative;
border-radius: inherit;
overflow: hidden;
}
.popup-album + .popup-album, .popup-container:not(.is-album) .popup-item-media + .popup-item-media {
margin-top: .5rem;
}
}
Loading…
Cancel
Save