From a5f46230288a5aa0722f341d22b3ef66826a3143 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sun, 19 Jun 2022 20:19:41 +0400 Subject: [PATCH] Refactor backgrounds uploading --- src/components/sidebarLeft/tabs/background.ts | 246 +++++++----------- src/config/tabId.ts | 2 + src/helpers/getFileNameForUpload.ts | 6 +- src/lib/appManagers/appDocsManager.ts | 47 ++-- src/lib/appManagers/appDownloadManager.ts | 25 +- 5 files changed, 154 insertions(+), 172 deletions(-) create mode 100644 src/config/tabId.ts diff --git a/src/components/sidebarLeft/tabs/background.ts b/src/components/sidebarLeft/tabs/background.ts index e4b1e995..6d9699b1 100644 --- a/src/components/sidebarLeft/tabs/background.ts +++ b/src/components/sidebarLeft/tabs/background.ts @@ -7,7 +7,7 @@ import { generateSection } from ".."; import { averageColor, averageColorFromCanvas } from "../../../helpers/averageColor"; import blur from "../../../helpers/blur"; -import deferredPromise from "../../../helpers/cancellablePromise"; +import deferredPromise, { CancellablePromise } from "../../../helpers/cancellablePromise"; import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import findUpClassName from "../../../helpers/dom/findUpClassName"; import highlightningColor from "../../../helpers/highlightningColor"; @@ -30,25 +30,24 @@ import { STATE_INIT, Theme } from "../../../config/state"; import themeController from "../../../helpers/themeController"; import requestFile from "../../../helpers/files/requestFile"; -let uploadTempId = 0; - export default class AppBackgroundTab extends SliderSuperTab { private grid: HTMLElement; private tempId = 0; - private theme: Theme; private clicked: Set = new Set(); private blurCheckboxField: CheckboxField; - private wallpapersByElement: Map = new Map(); + private wallPapersByElement: Map = new Map(); private elementsByKey: Map = new Map(); + private get theme() { + return themeController.getTheme(); + } + init() { this.header.classList.add('with-border'); this.container.classList.add('background-container', 'background-image-container'); this.setTitle('ChatBackground'); - this.theme = themeController.getTheme(); - { const container = generateSection(this.scrollable); @@ -71,16 +70,16 @@ export default class AppBackgroundTab extends SliderSuperTab { withRipple: true }); - this.listenerSetter.add(blurCheckboxField.input)('change', () => { + this.listenerSetter.add(blurCheckboxField.input)('change', async() => { this.theme.background.blur = blurCheckboxField.input.checked; - this.managers.appStateManager.pushToState('settings', rootScope.settings); + await this.managers.appStateManager.pushToState('settings', rootScope.settings); // * wait for animation end setTimeout(() => { const active = grid.querySelector('.active') as HTMLElement; if(!active) return; - const wallpaper = this.wallpapersByElement.get(active); + const wallpaper = this.wallPapersByElement.get(active); if((wallpaper as WallPaper.wallPaper).pFlags.pattern || wallpaper._ === 'wallPaperNoFile') { return; } @@ -109,89 +108,39 @@ export default class AppBackgroundTab extends SliderSuperTab { private onUploadClick = () => { requestFile('image/x-png,image/png,image/jpeg').then(async(file) => { - // const id = 'wallpaper-upload-' + ++uploadTempId; - - // const thumb = { - // _: 'photoSize', - // h: 0, - // w: 0, - // location: {} as any, - // size: file.size, - // type: 'full', - // } as PhotoSize.photoSize; - // let document: MyDocument = { - // _: 'document', - // access_hash: '', - // attributes: [], - // dc_id: 0, - // file_reference: [], - // id, - // mime_type: file.type, - // size: file.size, - // date: Date.now() / 1000, - // pFlags: {}, - // thumbs: [thumb], - // file_name: file.name - // }; - - // document = await this.managers.appDocsManager.saveDoc(document); - - // const cacheContext = appDownloadManager.getCacheContext(document); - // cacheContext.downloaded = file.size; - // cacheContext.url = URL.createObjectURL(file); - - // let wallpaper: WallPaper.wallPaper = { - // _: 'wallPaper', - // access_hash: '', - // document: document, - // id, - // slug: id, - // pFlags: {} - // }; - - // const upload = appDownloadManager.upload(file, file.name); - - // const deferred = deferredPromise(); - // deferred.addNotifyListener = upload.addNotifyListener; - // deferred.cancel = upload.cancel; - - // upload.then((inputFile) => { - // this.managers.apiManager.invokeApi('account.uploadWallPaper', { - // file: inputFile, - // mime_type: file.type, - // settings: { - // _: 'wallPaperSettings' - // } - // }).then(async(_wallpaper) => { - // const newDoc = (_wallpaper as WallPaper.wallPaper).document as MyDocument; - // const newCacheContext = appDownloadManager.getCacheContext(newDoc); - // Object.assign(newCacheContext, cacheContext); - - // wallpaper = _wallpaper as WallPaper.wallPaper; - // wallpaper.document = await this.managers.appDocsManager.saveDoc(wallpaper.document); - - // this.setBackgroundDocument(wallpaper).then(deferred.resolve, deferred.reject); - // }, deferred.reject); - // }, deferred.reject); - - // const key = this.getWallpaperKey(wallpaper); - // deferred.then(() => { - // this.clicked.delete(key); - // }, (err) => { - // container.remove(); - // //console.error('i saw the body drop', err); - // }); - - // const preloader = new ProgressivePreloader({ - // isUpload: true, - // cancelable: true, - // tryAgainOnFail: false - // }); - - // const container = this.addWallPaper(wallpaper, false); - // this.clicked.add(key); - - // preloader.attach(container, false, deferred); + const wallPaper = await this.managers.appDocsManager.prepareWallPaperUpload(file); + const uploadPromise = this.managers.appDocsManager.uploadWallPaper(wallPaper.id); + const uploadDeferred: CancellablePromise = appDownloadManager.getNewDeferredForUpload(file.name, uploadPromise); + + const deferred = deferredPromise(); + deferred.addNotifyListener = uploadDeferred.addNotifyListener; + deferred.cancel = uploadDeferred.cancel; + + uploadDeferred.then((wallPaper) => { + this.clicked.delete(key); + this.elementsByKey.delete(key); + this.wallPapersByElement.set(container, wallPaper); + const newKey = this.getWallPaperKey(wallPaper); + this.elementsByKey.set(newKey, container); + + this.setBackgroundDocument(wallPaper).then(deferred.resolve, deferred.reject); + }, deferred.reject); + + const key = this.getWallPaperKey(wallPaper); + deferred.catch(() => { + container.remove(); + }); + + const preloader = new ProgressivePreloader({ + isUpload: true, + cancelable: true, + tryAgainOnFail: false + }); + + const container = this.addWallPaper(wallPaper, false); + this.clicked.add(key); + + preloader.attach(container, false, deferred); }); }; @@ -206,98 +155,105 @@ export default class AppBackgroundTab extends SliderSuperTab { } }; - private getColorsFromWallpaper(wallpaper: WallPaper) { - return wallpaper.settings ? [ - wallpaper.settings.background_color, - wallpaper.settings.second_background_color, - wallpaper.settings.third_background_color, - wallpaper.settings.fourth_background_color + private getColorsFromWallPaper(wallPaper: WallPaper) { + return wallPaper.settings ? [ + wallPaper.settings.background_color, + wallPaper.settings.second_background_color, + wallPaper.settings.third_background_color, + wallPaper.settings.fourth_background_color ].filter(Boolean).map((color) => '#' + color.toString(16)).join(',') : ''; } - private getWallpaperKey(wallpaper: WallPaper) { - return '' + wallpaper.id; + private getWallPaperKey(wallPaper: WallPaper) { + return '' + wallPaper.id; } - private getWallpaperKeyFromTheme(theme: Theme) { + private getWallPaperKeyFromTheme(theme: Theme) { return '' + theme.background.id; } - private async addWallPaper(wallpaper: WallPaper, append = true) { - const colors = this.getColorsFromWallpaper(wallpaper); - const hasFile = wallpaper._ === 'wallPaper'; - if((hasFile && wallpaper.pFlags.pattern && !colors)/* || + private addWallPaper(wallPaper: WallPaper, append = true) { + const colors = this.getColorsFromWallPaper(wallPaper); + const hasFile = wallPaper._ === 'wallPaper'; + if((hasFile && wallPaper.pFlags.pattern && !colors)/* || (wallpaper.document as MyDocument).mime_type.indexOf('application/') === 0 */) { return; } - const isDark = !!wallpaper.pFlags.dark; + const isDark = !!wallPaper.pFlags.dark; - const doc = hasFile ? wallpaper.document as Document.document : undefined; + const doc = hasFile ? wallPaper.document as Document.document : undefined; const container = document.createElement('div'); container.classList.add('grid-item'); - container.dataset.id = '' + wallpaper.id; + container.dataset.id = '' + wallPaper.id; - const key = this.getWallpaperKey(wallpaper); - this.wallpapersByElement.set(container, wallpaper); + const key = this.getWallPaperKey(wallPaper); + this.wallPapersByElement.set(container, wallPaper); this.elementsByKey.set(key, container); const media = document.createElement('div'); media.classList.add('grid-item-media'); - let wrapped: Awaited>, size: PhotoSize; + let wrapped: ReturnType, size: PhotoSize; if(hasFile) { size = choosePhotoSize(doc, 200, 200); - wrapped = await wrapPhoto({ + wrapped = wrapPhoto({ photo: doc, message: null, container: media, withoutPreloader: true, size: size, - noFadeIn: wallpaper.pFlags.pattern + noFadeIn: wallPaper.pFlags.pattern }); - (wrapped.loadPromises.thumb || wrapped.loadPromises.full).then(() => { + if(wallPaper.pFlags.pattern) { + media.classList.add('is-pattern'); + } + + wrapped.then(async({loadPromises, images}) => { + await loadPromises.thumb || loadPromises.full; + return images; + }).then((images) => { + if(wallPaper.pFlags.pattern) { + if(isDark) { + images.full.style.display = 'none'; + if(images.thumb) { + images.thumb.style.display = 'none'; + } + } else if(wallPaper.settings?.intensity) { + images.full.style.opacity = '' + Math.abs(wallPaper.settings.intensity) / 100; + } + } + sequentialDom.mutate(() => { container.append(media); }); }); - - if(wallpaper.pFlags.pattern) { - media.classList.add('is-pattern'); - - if(isDark) { - wrapped.images.full.style.display = 'none'; - if(wrapped.images.thumb) { - wrapped.images.thumb.style.display = 'none'; - } - } else if(wallpaper.settings?.intensity) { - wrapped.images.full.style.opacity = '' + Math.abs(wallpaper.settings.intensity) / 100; - } - } } else { container.append(media); } - if(wallpaper.settings && wallpaper.settings.background_color !== undefined) { + if(wallPaper.settings && wallPaper.settings.background_color !== undefined) { const {canvas} = ChatBackgroundGradientRenderer.create(colors); canvas.classList.add('background-colors-canvas'); if(isDark && hasFile) { - wrapped.loadPromises.full.then(async() => { - const cacheContext = await this.managers.thumbsStorage.getCacheContext(doc, size.type); - canvas.style.webkitMaskImage = `url(${cacheContext.url})`; - canvas.style.opacity = '' + Math.abs(wallpaper.settings.intensity) / 100; - media.append(canvas); + wrapped.then(({loadPromises}) => { + loadPromises.full.then(async() => { + const cacheContext = await this.managers.thumbsStorage.getCacheContext(doc, size.type); + canvas.style.webkitMaskImage = `url(${cacheContext.url})`; + canvas.style.opacity = '' + Math.abs(wallPaper.settings.intensity) / 100; + media.append(canvas); + }); }); } else { media.append(canvas); } } - if(this.getWallpaperKeyFromTheme(this.theme) === key) { + if(this.getWallPaperKeyFromTheme(this.theme) === key) { container.classList.add('active'); } @@ -310,13 +266,13 @@ export default class AppBackgroundTab extends SliderSuperTab { const target = findUpClassName(e.target, 'grid-item') as HTMLElement; if(!target) return; - const wallpaper = this.wallpapersByElement.get(target); + const wallpaper = this.wallPapersByElement.get(target); if(wallpaper._ === 'wallPaperNoFile') { this.setBackgroundDocument(wallpaper); return; } - const key = this.getWallpaperKey(wallpaper); + const key = this.getWallPaperKey(wallpaper); if(this.clicked.has(key)) return; this.clicked.add(key); @@ -356,11 +312,11 @@ export default class AppBackgroundTab extends SliderSuperTab { }); }; - private setBackgroundDocument = (wallpaper: WallPaper) => { + private setBackgroundDocument = (wallPaper: WallPaper) => { let _tempId = ++this.tempId; const middleware = () => _tempId === this.tempId; - const doc = (wallpaper as WallPaper.wallPaper).document as MyDocument; + const doc = (wallPaper as WallPaper.wallPaper).document as MyDocument; const deferred = deferredPromise(); let download: Promise | ReturnType; if(doc) { @@ -384,7 +340,7 @@ export default class AppBackgroundTab extends SliderSuperTab { if(url && !this.theme.background.color) { getPixelPromise = averageColor(url); } else { - const {canvas} = ChatBackgroundGradientRenderer.create(this.getColorsFromWallpaper(wallpaper)); + const {canvas} = ChatBackgroundGradientRenderer.create(this.getColorsFromWallPaper(wallPaper)); getPixelPromise = Promise.resolve(averageColorFromCanvas(canvas)); } @@ -398,10 +354,10 @@ export default class AppBackgroundTab extends SliderSuperTab { // const hsla = 'rgba(0, 0, 0, 0.3)'; //console.log(doc, hsla, performance.now() - perf); - const slug = (wallpaper as WallPaper.wallPaper).slug ?? ''; - background.id = wallpaper.id; - background.intensity = wallpaper.settings?.intensity ?? 0; - background.color = this.getColorsFromWallpaper(wallpaper); + const slug = (wallPaper as WallPaper.wallPaper).slug ?? ''; + background.id = wallPaper.id; + background.intensity = wallPaper.settings?.intensity ?? 0; + background.color = this.getColorsFromWallPaper(wallPaper); background.slug = slug; background.highlightningColor = hsla; this.managers.appStateManager.pushToState('settings', rootScope.settings); @@ -442,7 +398,7 @@ export default class AppBackgroundTab extends SliderSuperTab { private setActive = () => { const active = this.grid.querySelector('.active'); - const target = this.elementsByKey.get(this.getWallpaperKeyFromTheme(this.theme)); + const target = this.elementsByKey.get(this.getWallPaperKeyFromTheme(this.theme)); if(active === target) { return; } diff --git a/src/config/tabId.ts b/src/config/tabId.ts new file mode 100644 index 00000000..3bead9e7 --- /dev/null +++ b/src/config/tabId.ts @@ -0,0 +1,2 @@ +const tabId = Date.now() % Math.random() * 100000000 | 0; +export default tabId; diff --git a/src/helpers/getFileNameForUpload.ts b/src/helpers/getFileNameForUpload.ts index d47948d8..aeb676bb 100644 --- a/src/helpers/getFileNameForUpload.ts +++ b/src/helpers/getFileNameForUpload.ts @@ -4,13 +4,14 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import tabId from "../config/tabId"; let uploadId = 0; export default function getFileNameForUpload(file: File | Blob) { let fileName: string; const mimeType = file?.type; if(mimeType) { // the same like apiFileName in appMessagesManager for upload! - const ext = uploadId++ + '.' + mimeType.split('/')[1]; + const ext = `${tabId}_${uploadId++}.mimeType.split('/')[1]`; if(['image/jpeg', 'image/png', 'image/bmp'].indexOf(mimeType) >= 0) { fileName = 'photo' + ext; @@ -21,9 +22,8 @@ export default function getFileNameForUpload(file: File | Blob) { } else { fileName = 'document' + ext; } - } else { - fileName = 'upload-' + uploadId++; + fileName = `upload-${tabId}_${uploadId++}`; } return fileName; diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 422d8041..b5da6ba9 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -22,7 +22,7 @@ import { isServiceWorkerOnline } from '../mtproto/mtproto.worker'; import MTProtoMessagePort from '../mtproto/mtprotoMessagePort'; import getDocumentInput from './utils/docs/getDocumentInput'; import getDocumentURL from './utils/docs/getDocumentURL'; -import { CancellablePromise } from '../../helpers/cancellablePromise'; +import type { ThumbCache } from '../storages/thumbs'; export type MyDocument = Document.document; @@ -34,6 +34,8 @@ const EXTENSION_MIME_TYPE_MAP = { pdf: 'application/pdf', }; +type WallPaperId = WallPaper.wallPaper['id']; + let uploadWallPaperTempId = 0; export class AppDocsManager extends AppManager { @@ -41,9 +43,12 @@ export class AppDocsManager extends AppManager { private stickerCachedThumbs: {[docId: DocId]: {[toneIndex: number]: {url: string, w: number, h: number}}}; + private uploadingWallPapers: {[id: WallPaperId]: {cacheContext: ThumbCache, file: File}}; + protected after() { this.docs = {}; this.stickerCachedThumbs = {}; + this.uploadingWallPapers = {}; MTProtoMessagePort.getInstance().addEventListener('serviceWorkerOnline', (online) => { if(!online) { @@ -64,8 +69,8 @@ export class AppDocsManager extends AppManager { }; public saveDoc(doc: Document, context?: ReferenceContext): MyDocument { - if(doc._ === 'documentEmpty') { - return undefined; + if(!doc || doc._ === 'documentEmpty') { + return; } const oldDoc = this.docs[doc.id]; @@ -313,7 +318,7 @@ export class AppDocsManager extends AppManager { }); } - public uploadWallPaper(file: File) { + public prepareWallPaperUpload(file: File) { const id = 'wallpaper-upload-' + ++uploadWallPaperTempId; const thumb = { @@ -343,7 +348,7 @@ export class AppDocsManager extends AppManager { const cacheContext = this.thumbsStorage.setCacheContextURL(document, undefined, URL.createObjectURL(file), file.size); - let wallpaper: WallPaper.wallPaper = { + const wallpaper: WallPaper.wallPaper = { _: 'wallPaper', access_hash: '', document: document, @@ -352,25 +357,35 @@ export class AppDocsManager extends AppManager { pFlags: {} }; - const upload = this.apiFileManager.upload({file, fileName: file.name}); + this.uploadingWallPapers[id] = { + cacheContext, + file, + }; - upload.then((inputFile) => { - this.apiManager.invokeApi('account.uploadWallPaper', { + return wallpaper; + } + + public uploadWallPaper(id: WallPaperId) { + const {cacheContext, file} = this.uploadingWallPapers[id]; + delete this.uploadingWallPapers[id]; + + const upload = this.apiFileManager.upload({file, fileName: file.name}); + return upload.then((inputFile) => { + return this.apiManager.invokeApi('account.uploadWallPaper', { file: inputFile, mime_type: file.type, settings: { - _: 'wallPaperSettings' + _: 'wallPaperSettings', + } - }).then((_wallpaper) => { - const newDoc = (_wallpaper as WallPaper.wallPaper).document as MyDocument; - this.thumbsStorage.setCacheContextURL(newDoc, undefined, cacheContext.url, cacheContext.downloaded); + }).then((wallPaper) => { + assumeType(wallPaper); + wallPaper.document = this.saveDoc(wallPaper.document); + this.thumbsStorage.setCacheContextURL(wallPaper.document, undefined, cacheContext.url, cacheContext.downloaded); - wallpaper = _wallpaper as WallPaper.wallPaper; - wallpaper.document = this.saveDoc(wallpaper.document); + return wallPaper; }); }); - - return wallpaper; } public getGifs() { diff --git a/src/lib/appManagers/appDownloadManager.ts b/src/lib/appManagers/appDownloadManager.ts index b040e1e8..2a59e848 100644 --- a/src/lib/appManagers/appDownloadManager.ts +++ b/src/lib/appManagers/appDownloadManager.ts @@ -104,6 +104,17 @@ export class AppDownloadManager { return download[type] = deferred as any; } + public getNewDeferredForUpload>(fileName: string, promise: T) { + const deferred = this.getNewDeferred(fileName); + promise.then(deferred.resolve, deferred.reject); + + deferred.finally(() => { + this.clearDownload(fileName); + }); + + return deferred as CancellablePromise>; + } + private clearDownload(fileName: string) { delete this.downloads[fileName]; } @@ -162,18 +173,16 @@ export class AppDownloadManager { return this.downloadMedia(options, 'void'); } - public upload(file: File | Blob, fileName?: string) { + public upload(file: File | Blob, fileName?: string, promise?: Promise) { if(!fileName) { fileName = getFileNameForUpload(file); } - const deferred = this.getNewDeferred(fileName); - this.managers.apiFileManager.upload({file, fileName}).then(deferred.resolve, deferred.reject); - - deferred.finally(() => { - this.clearDownload(fileName); - }); - + if(!promise) { + promise = this.managers.apiFileManager.upload({file, fileName}); + } + + const deferred = this.getNewDeferredForUpload(fileName, promise); return deferred as any as CancellablePromise; }