import type Chat from "../chat/chat"; import { isTouchSupported } from "../../helpers/touchSupport"; import { calcImageInBox, placeCaretAtEnd, isSendShortcutPressed } from "../../helpers/dom"; import InputField from "../inputField"; import PopupElement from "."; import Scrollable from "../scrollable"; import { toast } from "../toast"; import { prepareAlbum, wrapDocument } from "../wrappers"; import CheckboxField from "../checkbox"; import SendContextMenu from "../chat/sendContextMenu"; import { createPosterForVideo, createPosterFromVideo, onVideoLoad } from "../../helpers/files"; import { MyDocument } from "../../lib/appManagers/appDocsManager"; type SendFileParams = Partial<{ file: File, objectURL: string, thumbBlob: Blob, thumbURL: string, width: number, height: number, duration: number }>; const MAX_LENGTH_CAPTION = 1024; // TODO: .gif upload as video export default class PopupNewMedia extends PopupElement { private input: HTMLElement; private mediaContainer: HTMLElement; private groupCheckboxField: { label: HTMLLabelElement; input: HTMLInputElement; span: HTMLSpanElement; }; private wasInputValue = ''; private willAttach: Partial<{ type: 'media' | 'document', isMedia: true, group: boolean, sendFileDetails: SendFileParams[] }> = { sendFileDetails: [], group: false }; inputField: InputField; constructor(private chat: Chat, files: File[], willAttachType: PopupNewMedia['willAttach']['type']) { super('popup-send-photo popup-new-media', null, {closable: true, withConfirm: 'SEND'}); this.willAttach.type = willAttachType; this.btnConfirm.addEventListener('click', () => this.send()); if(this.chat.type !== 'scheduled') { const sendMenu = new SendContextMenu({ onSilentClick: () => { this.chat.input.sendSilent = true; this.send(); }, onScheduleClick: () => { this.chat.input.scheduleSending(() => { this.send(); }); }, openSide: 'bottom-left', onContextElement: this.btnConfirm, }); sendMenu.setPeerId(this.chat.peerId); this.header.append(sendMenu.sendMenu); } this.mediaContainer = document.createElement('div'); this.mediaContainer.classList.add('popup-photo'); const scrollable = new Scrollable(null); scrollable.container.append(this.mediaContainer); this.inputField = new InputField({ placeholder: 'Add a caption...', label: 'Caption', name: 'photo-caption', maxLength: MAX_LENGTH_CAPTION, showLengthOn: 80 }); this.input = this.inputField.input; this.inputField.value = this.wasInputValue = this.chat.input.messageInputField.value; this.chat.input.messageInputField.value = ''; this.container.append(scrollable.container); if(files.length > 1) { this.groupCheckboxField = CheckboxField({ text: 'Group items', name: 'group-items' }); this.container.append(this.groupCheckboxField.label, this.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(this.inputField.container); this.attachFiles(files); } private onKeyDown = (e: KeyboardEvent) => { const target = e.target as HTMLElement; if(target !== this.input) { this.input.focus(); placeCaretAtEnd(this.input); } if(isSendShortcutPressed(e)) { this.btnConfirm.click(); } }; public send(force = false) { if(this.chat.type === 'scheduled' && !force) { this.chat.input.scheduleSending(() => { this.send(true); }); return; } let caption = this.inputField.value; if(caption.length > MAX_LENGTH_CAPTION) { toast('Caption is too long.'); return; } this.destroy(); const willAttach = this.willAttach; willAttach.isMedia = willAttach.type === 'media' ? true : undefined; //console.log('will send files with options:', willAttach); const peerId = this.chat.peerId; const input = this.chat.input; const silent = input.sendSilent; const scheduleDate = input.scheduleDate; 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); this.chat.appMessagesManager.sendAlbum(peerId, w.sendFileDetails.map(d => d.file), Object.assign({ caption, replyToMsgId: input.replyToMsgId, threadId: this.chat.threadId, isMedia: willAttach.isMedia, silent, scheduleDate, clearDraft: true as true }, w)); caption = undefined; input.replyToMsgId = this.chat.threadId; } } else { if(caption) { if(willAttach.sendFileDetails.length > 1) { this.chat.appMessagesManager.sendText(peerId, caption, { replyToMsgId: input.replyToMsgId, threadId: this.chat.threadId, silent, scheduleDate, clearDraft: true }); caption = ''; //input.replyToMsgId = undefined; } } const promises = willAttach.sendFileDetails.map(params => { const promise = this.chat.appMessagesManager.sendFile(peerId, params.file, Object.assign({ //isMedia: willAttach.isMedia, isMedia: willAttach.isMedia, caption, replyToMsgId: input.replyToMsgId, threadId: this.chat.threadId, silent, scheduleDate, clearDraft: true as true }, params)); caption = ''; return promise; }); input.replyToMsgId = this.chat.threadId; } //Promise.all(promises); //appMessagesManager.sendFile(appImManager.peerId, willAttach.file, willAttach); input.onMessageSent(); } public attachFile = (file: File) => { const willAttach = this.willAttach; return new Promise((resolve) => { const params: SendFileParams = {}; params.file = file; //console.log('selected file:', file, typeof(file), willAttach); const itemDiv = document.createElement('div'); switch(willAttach.type) { case 'media': { const isVideo = file.type.indexOf('video/') === 0; itemDiv.classList.add('popup-item-media'); if(isVideo) { const video = document.createElement('video'); const source = document.createElement('source'); source.src = params.objectURL = URL.createObjectURL(file); video.autoplay = false; video.controls = false; video.muted = true; video.setAttribute('playsinline', 'true'); onVideoLoad(video).then(() => { params.width = video.videoWidth; params.height = video.videoHeight; params.duration = Math.floor(video.duration); itemDiv.append(video); createPosterFromVideo(video).then(blob => { params.thumbBlob = blob; params.thumbURL = URL.createObjectURL(blob); resolve(itemDiv); }); }); video.append(source); } else { const img = new Image(); img.src = params.objectURL = URL.createObjectURL(file); img.onload = () => { params.width = img.naturalWidth; params.height = img.naturalHeight; itemDiv.append(img); resolve(itemDiv); }; } break; } case 'document': { const isPhoto = file.type.indexOf('image/') !== -1; const isAudio = file.type.indexOf('audio/') !== -1; if(isPhoto || isAudio) { params.objectURL = URL.createObjectURL(file); } const docDiv = wrapDocument({ message: { _: 'message', pFlags: { is_outgoing: true }, mid: 0, peerId: 0, media: { _: 'messageMediaDocument', document: { _: 'document', file: file, file_name: file.name || '', size: file.size, type: isPhoto ? 'photo' : 'doc', url: params.objectURL, downloaded: true } as MyDocument } } as any }); const finish = () => { itemDiv.append(docDiv); resolve(itemDiv); }; if(isPhoto) { const img = new Image(); img.src = params.objectURL; img.onload = () => { params.width = img.naturalWidth; params.height = img.naturalHeight; finish(); }; img.onerror = finish; } else { finish(); } break; } } willAttach.sendFileDetails.push(params); }); }; public attachFiles(files: File[]) { const container = this.container; const willAttach = this.willAttach; /* if(files.length > 10 && willAttach.type === 'media') { willAttach.type = 'document'; } */ files = files.filter(file => { if(willAttach.type === 'media') { return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0); } else { return true; } }); 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'); 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 && willAttach.group) { this.title.innerText = 'Send Album'; } else if(foundPhotos) { this.title.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo'); } else if(foundVideos) { this.title.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video'); } } } if(willAttach.type === 'media') { if(willAttach.sendFileDetails.length > 1 && willAttach.group) { container.classList.add('is-album'); 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: 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 { 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 = () => { if(this.wasInputValue) { this.chat.input.messageInputField.value = this.wasInputValue; } document.body.removeEventListener('keydown', this.onKeyDown); }; this.show(); } }); } }