From 730bd1168deabac6bde8699646b325c61ef7d2fb Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 6 Feb 2021 14:44:48 +0200 Subject: [PATCH] Documents improve Set uploaded source to new message media --- src/components/chat/bubbles.ts | 14 +-- src/components/popups/newMedia.ts | 12 ++- src/components/preloader.ts | 3 +- src/components/wrappers.ts | 97 +++++++++++------- src/helpers/cancellablePromise.ts | 4 + src/helpers/number.ts | 2 +- src/lib/appManagers/appDocsManager.ts | 24 ++--- src/lib/appManagers/appDownloadManager.ts | 15 ++- src/lib/appManagers/appMessagesManager.ts | 34 +++++-- src/scss/partials/_document.scss | 100 +++++++++++-------- src/scss/partials/popups/_mediaAttacher.scss | 8 +- 11 files changed, 194 insertions(+), 119 deletions(-) diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 9432dda2..11bd4990 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -215,12 +215,14 @@ export default class ChatBubbles { } if(message.media?.document) { - if(['audio', 'voice'].includes(message.media.document.type)) { - const audio = bubble.querySelector(`audio-element[message-id="${tempId}"]`) as AudioElement; - audio.setAttribute('doc-id', message.media.document.id); - audio.setAttribute('message-id', '' + mid); - audio.message = message; - audio.onLoad(true); + const element = bubble.querySelector(`audio-element[message-id="${tempId}"], .document[data-doc-id="${tempId}"]`) as HTMLElement; + if(element instanceof AudioElement) { + element.setAttribute('doc-id', message.media.document.id); + element.setAttribute('message-id', '' + mid); + element.message = message; + element.onLoad(true); + } else { + element.dataset.docId = message.media.document.id; } } diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index 87cce0ad..26b7f8b2 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -8,7 +8,8 @@ import { toast } from "../toast"; import { prepareAlbum, wrapDocument } from "../wrappers"; import CheckboxField from "../checkbox"; import SendContextMenu from "../chat/sendContextMenu"; -import { createPosterForVideo, createPosterFromVideo } from "../../helpers/files"; +import { createPosterForVideo, createPosterFromVideo, onVideoLoad } from "../../helpers/files"; +import { MyDocument } from "../../lib/appManagers/appDocsManager"; type SendFileParams = Partial<{ file: File, @@ -241,7 +242,7 @@ export default class PopupNewMedia extends PopupElement { video.muted = true; video.setAttribute('playsinline', 'true'); - video.onloadeddata = () => { + onVideoLoad(video).then(() => { params.width = video.videoWidth; params.height = video.videoHeight; params.duration = Math.floor(video.duration); @@ -252,7 +253,7 @@ export default class PopupNewMedia extends PopupElement { params.thumbURL = URL.createObjectURL(blob); resolve(itemDiv); }); - }; + }); video.append(source); } else { @@ -293,8 +294,9 @@ export default class PopupNewMedia extends PopupElement { file_name: file.name || '', size: file.size, type: isPhoto ? 'photo' : 'doc', - url: params.objectURL - } + url: params.objectURL, + downloaded: true + } as MyDocument } } as any }); diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 0c57002d..84cf84ab 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -14,8 +14,7 @@ export default class ProgressivePreloader { private tempId = 0; private detached = true; - private promise: CancellablePromise = null; - public onCancel: () => any = null; + public promise: CancellablePromise = null; public isUpload = false; private cancelable = true; diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index b104b8d3..b4bf08b1 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -385,8 +385,6 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS }): HTMLElement { if(!fontWeight) fontWeight = 500; - const uploading = message.pFlags.is_outgoing; - const doc = (message.media.document || message.media.webpage.document) as MyDocument; if(doc.type === 'audio' || doc.type === 'voice') { const audioElement = new AudioElement(); @@ -394,11 +392,11 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS audioElement.setAttribute('peer-id', '' + message.peerId); audioElement.withTime = withTime; audioElement.message = message; - + if(voiceAsMusic) audioElement.voiceAsMusic = voiceAsMusic; if(searchContext) audioElement.searchContext = searchContext; if(showSender) audioElement.showSender = showSender; - + const isPending = message.pFlags.is_outgoing; if(isPending) { audioElement.preloader = message.media.preloader; @@ -408,6 +406,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS audioElement.render(); return audioElement; } + + const uploading = message.pFlags.is_outgoing && message.media?.preloader; let extSplitted = doc.file_name ? doc.file_name.split('.') : ''; let ext = ''; @@ -415,17 +415,20 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS let docDiv = document.createElement('div'); docDiv.classList.add('document', `ext-${ext}`); + docDiv.dataset.docId = doc.id; const icoDiv = document.createElement('div'); icoDiv.classList.add('document-ico'); - if(doc.thumbs?.length || (uploading && doc.url && doc.type === 'photo')) { + if(doc.thumbs?.length || (message.pFlags.is_outgoing && doc.url && doc.type === 'photo')) { docDiv.classList.add('document-with-thumb'); - if(uploading) { + let imgs: HTMLImageElement[] = []; + if(message.pFlags.is_outgoing) { icoDiv.innerHTML = ``; + imgs.push(icoDiv.firstElementChild as HTMLImageElement); } else { - wrapPhoto({ + const wrapped = wrapPhoto({ photo: doc, message: null, container: icoDiv, @@ -435,10 +438,11 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS withoutPreloader: true }); icoDiv.style.width = icoDiv.style.height = ''; + if(wrapped.images.thumb) imgs.push(wrapped.images.thumb); + if(wrapped.images.full) imgs.push(wrapped.images.full); } - const img = icoDiv.querySelector('img'); - if(img) img.classList.add('document-thumb'); + imgs.forEach(img => img.classList.add('document-thumb')); } else { icoDiv.innerText = ext; } @@ -461,48 +465,69 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS } docDiv.innerHTML = ` - ${!uploading ? `
` : ''} + ${doc.downloaded && !uploading ? '' : `
`}
${fileName}${titleAdditionHTML}
${size}
`; docDiv.prepend(icoDiv); - if(!uploading) { - const downloadDiv = docDiv.querySelector('.document-download') as HTMLDivElement; - const preloader = new ProgressivePreloader(); + if(!uploading && message.pFlags.is_outgoing) { + return docDiv; + } - const load = () => { - const download = appDocsManager.saveDocFile(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0); + let downloadDiv: HTMLElement, preloader: ProgressivePreloader = null; + const onLoad = () => { + if(downloadDiv) { + downloadDiv.classList.add('downloaded'); + const _downloadDiv = downloadDiv; + setTimeout(() => { + _downloadDiv.remove(); + }, 200); + downloadDiv = null; + } - download.then(() => { - downloadDiv.classList.add('downloaded'); - setTimeout(() => { - downloadDiv.remove(); - }, 200); - }); + if(preloader) { + preloader = null; + } + }; + + const load = () => { + const doc = appDocsManager.getDoc(docDiv.dataset.docId); + const download = appDocsManager.saveDocFile(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0); + if(downloadDiv) { + download.then(onLoad); preloader.attach(downloadDiv, true, download); + } - return {download}; - }; + return {download}; + }; - preloader.construct(); - preloader.setManual(); - preloader.attach(downloadDiv); - preloader.setDownloadFunction(load); + if(!(doc.downloaded && !uploading)) { + downloadDiv = docDiv.querySelector('.document-download'); + preloader = message.media.preloader as ProgressivePreloader; - attachClickEvent(docDiv, (e) => { - preloader.onClick(e); - }); + if(!preloader) { + preloader = new ProgressivePreloader(); - /* if(doc.downloaded) { - downloadDiv.classList.add('downloaded'); - } */ - } else if(message.media?.preloader) { - const icoDiv = docDiv.querySelector('.document-ico'); - message.media.preloader.attach(icoDiv, false); + preloader.construct(); + preloader.setManual(); + preloader.attach(downloadDiv); + preloader.setDownloadFunction(load); + } else { + preloader.attach(downloadDiv); + message.media.promise.then(onLoad); + } } + + attachClickEvent(docDiv, (e) => { + if(preloader) { + preloader.onClick(e); + } else { + load(); + } + }); return docDiv; } diff --git a/src/helpers/cancellablePromise.ts b/src/helpers/cancellablePromise.ts index beea4ba5..fe46ed04 100644 --- a/src/helpers/cancellablePromise.ts +++ b/src/helpers/cancellablePromise.ts @@ -61,6 +61,10 @@ export function deferredPromise() { deferred.notify = null; deferred.listeners.length = 0; deferred.lastNotify = null; + + if(deferred.cancel) { + deferred.cancel = () => {}; + } }); Object.assign(deferred, deferredHelper); diff --git a/src/helpers/number.ts b/src/helpers/number.ts index 12ebab23..55f925ef 100644 --- a/src/helpers/number.ts +++ b/src/helpers/number.ts @@ -5,7 +5,7 @@ export function numberThousandSplitter(x: number, joiner = ',') { } export function formatBytes(bytes: number, decimals = 2) { - if (bytes === 0) return '0 Bytes'; + if(bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 3da51464..f29afb78 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -278,27 +278,20 @@ export class AppDocsManager { return getFileNameByLocation(this.getInput(doc, thumbSize), {fileName: doc.file_name}); } - public downloadDoc(doc: MyDocument/* , thumb?: PhotoSize.photoSize */, queueId?: number): DownloadBlob { - const fileName = this.getInputFileName(doc/* , thumb?.type */); + public downloadDoc(doc: MyDocument, queueId?: number): DownloadBlob { + const fileName = this.getInputFileName(doc); let download: DownloadBlob = appDownloadManager.getDownload(fileName); if(download) { return download; } - const downloadOptions = this.getFileDownloadOptions(doc, undefined/* thumb */, queueId); + const downloadOptions = this.getFileDownloadOptions(doc, undefined, queueId); download = appDownloadManager.download(downloadOptions); const originalPromise = download; originalPromise.then((blob) => { - /* if(thumb) { - defineNotNumerableProperties(thumb, ['url']); - thumb.url = URL.createObjectURL(blob); - return; - } else */if(!doc.supportsStreaming) { - doc.url = URL.createObjectURL(blob); - } - + doc.url = URL.createObjectURL(blob); doc.downloaded = true; }); @@ -390,8 +383,13 @@ export class AppDocsManager { } public saveDocFile(doc: MyDocument, queueId?: number) { - const options = this.getFileDownloadOptions(doc, undefined, queueId); - return appDownloadManager.downloadToDisc(options, doc.file_name); + /* const options = this.getFileDownloadOptions(doc, undefined, queueId); + return appDownloadManager.downloadToDisc(options, doc.file_name); */ + const promise = this.downloadDoc(doc, queueId); + promise.then(() => { + appDownloadManager.createDownloadAnchor(doc.url, doc.file_name); + }); + return promise; } } diff --git a/src/lib/appManagers/appDownloadManager.ts b/src/lib/appManagers/appDownloadManager.ts index 881c2c65..b45ee91c 100644 --- a/src/lib/appManagers/appDownloadManager.ts +++ b/src/lib/appManagers/appDownloadManager.ts @@ -77,6 +77,19 @@ export class AppDownloadManager { delete this.downloads[fileName]; } + public fakeDownload(fileName: string, value: Blob | string) { + const deferred = this.getNewDeferred(fileName); + if(typeof(value) === 'string') { + fetch(value) + .then(response => response.blob()) + .then(blob => deferred.resolve(blob)); + } else { + deferred.resolve(value); + } + + return deferred; + } + public download(options: DownloadOptions): DownloadBlob { const fileName = getFileNameByLocation(options.location, {fileName: options.fileName}); if(this.downloads.hasOwnProperty(fileName)) return this.downloads[fileName]; @@ -174,7 +187,7 @@ export class AppDownloadManager { } } - private createDownloadAnchor(url: string, fileName: string, onRemove?: () => void) { + public createDownloadAnchor(url: string, fileName: string, onRemove?: () => void) { const a = document.createElement('a'); a.href = url; a.download = fileName; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index b5fe9ef9..d58e1308 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -34,6 +34,7 @@ import appUsersManager from "./appUsersManager"; import appWebPagesManager from "./appWebPagesManager"; import appDraftsManager from "./appDraftsManager"; import pushHeavyTask from "../../helpers/heavyQueue"; +import { getFileNameByLocation } from "../../helpers/fileName"; //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет @@ -786,18 +787,21 @@ export class AppMessagesManager { this.log('sendFile', attachType, apiFileName, file.type, options); - const preloader = new ProgressivePreloader({ + const preloader = isDocument ? undefined : new ProgressivePreloader({ attachMethod: 'prepend', tryAgainOnFail: false, isUpload: true }); - const media = { + const sentDeferred = deferredPromise(); + + const media = isDocument ? undefined : { _: photo ? 'messageMediaPhoto' : 'messageMediaDocument', pFlags: {}, preloader, photo, - document + document, + promise: sentDeferred }; const message: any = { @@ -833,7 +837,6 @@ export class AppMessagesManager { let uploaded = false, uploadPromise: ReturnType = null; - const sentDeferred = deferredPromise(); message.send = () => { if(isDocument) { const {id, access_hash, file_reference} = file as MyDocument; @@ -4394,18 +4397,31 @@ export class AppMessagesManager { if(message.media.photo) { const photo = appPhotosManager.getPhoto('' + tempId); if(/* photo._ !== 'photoEmpty' */photo) { - const newPhoto = message.media.photo; + const newPhoto = message.media.photo as MyPhoto; // костыль defineNotNumerableProperties(newPhoto, ['downloaded', 'url']); newPhoto.downloaded = photo.downloaded; newPhoto.url = photo.url; + + const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize; + defineNotNumerableProperties(photoSize, ['url']); + photoSize.url = photo.url; + + const downloadOptions = appPhotosManager.getPhotoDownloadOptions(newPhoto, photoSize); + const fileName = getFileNameByLocation(downloadOptions.location); + appDownloadManager.fakeDownload(fileName, photo.url); } } else if(message.media.document) { const doc = appDocsManager.getDoc('' + tempId); - if(/* doc._ !== 'documentEmpty' && */doc?.type && doc.type !== 'sticker') { - const newDoc = message.media.document; - newDoc.downloaded = doc.downloaded; - newDoc.url = doc.url; + if(doc) { + if(/* doc._ !== 'documentEmpty' && */doc.type && doc.type !== 'sticker') { + const newDoc = message.media.document; + newDoc.downloaded = doc.downloaded; + newDoc.url = doc.url; + + const fileName = appDocsManager.getInputFileName(newDoc); + appDownloadManager.fakeDownload(fileName, doc.url); + } } } else if(message.media.poll) { delete appPollsManager.polls[tempId]; diff --git a/src/scss/partials/_document.scss b/src/scss/partials/_document.scss index 4342d985..5c0b5156 100644 --- a/src/scss/partials/_document.scss +++ b/src/scss/partials/_document.scss @@ -1,15 +1,17 @@ .document { + --background-color: #{$color-blue}; + $border-radius: .375rem; padding-left: 4.25rem; height: 70px; &-ico { - background-color: $color-blue; - border-radius: .5rem; - line-height: 10px; + background-color: var(--background-color); + border-radius: $border-radius; + line-height: 1; text-align: center; .document:not(.document-with-thumb) & { - padding: 1.5rem .25rem 0 .25rem; + padding: 1.5625rem .25rem 0 .25rem; @include respond-to(handhelds) { padding: 14px 0px 0px 0px; @@ -34,9 +36,7 @@ } &-ico, &-download { - font-weight: 500; - letter-spacing: 1px; - font-size: 1.1rem; + font-size: 1.125rem; background-size: contain; } @@ -47,39 +47,26 @@ } &-download { - background-color: $color-blue; - border-radius: .5rem; + background-color: var(--background-color); + border-radius: $border-radius; } &.ext-zip { - .document-ico, .document-download { - background-color: #FB8C00; - } + --background-color: #FB8C00; } &.ext-pdf { - .document-ico, .document-download { - background-color: #DF3F40; - } + --background-color: #DF3F40; } &.ext-apk { - .document-ico, .document-download { - background-color: #43A047; - } + --background-color: #43A047; } &.document-with-thumb { - .document-ico { - background: #fff; - border-radius: $border-radius; - - .document-thumb { - object-fit: cover; - width: 100%; - height: 100%; - } + --background-color: #fff; + .document-ico { &:after { display: none; } @@ -88,6 +75,22 @@ .document-download { background-color: rgba(0, 0, 0, .15); } + + .preloader-circular { + transition: background-color .2s; + } + + .preloader-container:not(.manual) { + .preloader-circular { + background-color: rgba(0, 0, 0, .3) !important; + } + } + } + + &-thumb { + object-fit: cover; + width: 100%; + height: 100%; } &-name { @@ -96,7 +99,7 @@ line-height: 1.3; font-size: 1rem; } - + &-size { white-space: nowrap; color: $color-gray; @@ -106,14 +109,22 @@ } .preloader-container { - width: 2.375rem; - height: 2.375rem; + width: 2.5rem; + height: 2.5rem; @include respond-to(handhelds) { - width: 26px; - height: 26px; + width: 1.625rem; + height: 1.625rem; } } + + .preloader-circular { + background-color: transparent !important; + } + + .preloader-path-new { + stroke-width: 2.5; + } } .document, .audio { @@ -127,20 +138,20 @@ &-ico, &-download { position: absolute; left: 0; - width: 54px; - height: 54px; + width: 3.375rem; + height: 3.375rem; color: #fff; @include respond-to(handhelds) { - height: 36px; - width: 36px; + height: 2.25rem; + width: 2.25rem; } } &-download { z-index: 1; align-items: center; - font-size: 24px; + font-size: 1.5rem; cursor: pointer; display: flex; justify-content: center; @@ -155,14 +166,8 @@ } .preloader-container:not(.preloader-streamable) { - width: 100%; - height: 100%; transform: scale(1) !important; } - - .preloader-circular { - background-color: transparent !important; - } } .audio { @@ -174,4 +179,13 @@ margin-right: -1px; } } + + .preloader-circular { + background-color: transparent !important; + } + + .preloader-container:not(.preloader-streamable) { + width: 100%; + height: 100%; + } } \ No newline at end of file diff --git a/src/scss/partials/popups/_mediaAttacher.scss b/src/scss/partials/popups/_mediaAttacher.scss index 28f9a1ff..568a4dcc 100644 --- a/src/scss/partials/popups/_mediaAttacher.scss +++ b/src/scss/partials/popups/_mediaAttacher.scss @@ -167,16 +167,18 @@ .checkbox-field { margin-bottom: 0; - padding-left: 0; + margin-left: 0; } - .popup-album, .popup-container:not(.is-album) .popup-item-media { + .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 { + .popup-album + .popup-album, + .popup-container:not(.is-album) .popup-item-media + .popup-item-media { margin-top: .5rem; } } \ No newline at end of file